• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

taichunmin / chameleon-ultra.js / 15847482271

24 Jun 2025 10:04AM UTC coverage: 69.194% (+5.8%) from 63.351%
15847482271

push

github

web-flow
v0.3.30: change to vitest and lodash-es (#196)

502 of 657 branches covered (76.41%)

Branch coverage included in aggregate %.

12 of 18 new or added lines in 14 files covered. (66.67%)

218 existing lines in 5 files now uncovered.

2683 of 3946 relevant lines covered (67.99%)

3159783.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.07
/src/Crypto1.ts
1
/**
2
 * @example
3
 * ```js
4
 * import Crypto1 from 'chameleon-ultra.js/Crypto1'
5
 * ```
6
 */
7
import { Buffer } from '@taichunmin/buffer'
1✔
8
import * as _ from 'lodash-es'
1✔
9
import { setObject } from './iifeExportHelper'
1✔
10

11
const LF_POLY_ODD = 0x29CE5C
1✔
12
const LF_POLY_EVEN = 0x870804
1✔
13

14
const S1 = [
1✔
15
  0x62141, 0x310A0, 0x18850, 0x0C428, 0x06214,
1✔
16
  0x0310A, 0x85E30, 0xC69AD, 0x634D6, 0xB5CDE,
1✔
17
  0xDE8DA, 0x6F46D, 0xB3C83, 0x59E41, 0xA8995,
1✔
18
  0xD027F, 0x6813F, 0x3409F, 0x9E6FA,
1✔
19
]
1✔
20

21
const S2 = [
1✔
22
  0x3A557B00, 0x5D2ABD80, 0x2E955EC0, 0x174AAF60, 0x0BA557B0,
1✔
23
  0x05D2ABD8, 0x0449DE68, 0x048464B0, 0x42423258, 0x278192A8,
1✔
24
  0x156042D0, 0x0AB02168, 0x43F89B30, 0x61FC4D98, 0x765EAD48,
1✔
25
  0x7D8FDD20, 0x7EC7EE90, 0x7F63F748, 0x79117020,
1✔
26
]
1✔
27

28
const T1 = [
1✔
29
  0x4F37D, 0x279BE, 0x97A6A, 0x4BD35, 0x25E9A,
1✔
30
  0x12F4D, 0x097A6, 0x80D66, 0xC4006, 0x62003,
1✔
31
  0xB56B4, 0x5AB5A, 0xA9318, 0xD0F39, 0x6879C,
1✔
32
  0xB057B, 0x582BD, 0x2C15E, 0x160AF, 0x8F6E2,
1✔
33
  0xC3DC4, 0xE5857, 0x72C2B, 0x39615, 0x98DBF,
1✔
34
  0xC806A, 0xE0680, 0x70340, 0x381A0, 0x98665,
1✔
35
  0x4C332, 0xA272C,
1✔
36
]
1✔
37

38
const T2 = [
1✔
39
  0x3C88B810, 0x5E445C08, 0x2982A580, 0x14C152C0, 0x4A60A960,
1✔
40
  0x253054B0, 0x52982A58, 0x2FEC9EA8, 0x1156C4D0, 0x08AB6268,
1✔
41
  0x42F53AB0, 0x217A9D58, 0x161DC528, 0x0DAE6910, 0x46D73488,
1✔
42
  0x25CB11C0, 0x52E588E0, 0x6972C470, 0x34B96238, 0x5CFC3A98,
1✔
43
  0x28DE96C8, 0x12CFC0E0, 0x4967E070, 0x64B3F038, 0x74F97398,
1✔
44
  0x7CDC3248, 0x38CE92A0, 0x1C674950, 0x0E33A4A8, 0x01B959D0,
1✔
45
  0x40DCACE8, 0x26CEDDF0,
1✔
46
]
1✔
47

48
const C1 = [0x00846B5, 0x0004235A, 0x000211AD]
1✔
49
const C2 = [0x1A822E0, 0x21A822E0, 0x21A822E0]
1✔
50

51
const fastfwd = [
1✔
52
  0, 0x4BC53, 0xECB1, 0x450E2, 0x25E29, 0x6E27A, 0x2B298, 0x60ECB,
1✔
53
  0, 0x1D962, 0x4BC53, 0x56531, 0xECB1, 0x135D3, 0x450E2, 0x58980,
1✔
54
]
1✔
55

56
/**
57
 * JavaScript implementation of the Crypto1 cipher.
58
 * @see [crypto1.c | RfidResearchGroup/proxmark3](https://github.com/RfidResearchGroup/proxmark3/blob/master/common/crapto1/crypto1.c)
59
 */
60
export default class Crypto1 {
1,326,788✔
61
  /**
62
   * @group Internal
63
   * @internal
64
   */
65
  static evenParityCache: number[] = []
1,326,788✔
66

67
  /**
68
   * @group Internal
69
   * @internal
70
   */
71
  static lfsrBuf = new Buffer(6)
1,326,788✔
72

73
  /**
74
   * @group Internal
75
   * @internal
76
   */
77
  even: number = 0
1,326,788✔
78

79
  /**
80
   * @group Internal
81
   * @internal
82
   */
83
  odd: number = 0
1,326,788✔
84

85
  /**
86
   * @param opts -
87
   * @param opts.even - The even bits of lfsr.
88
   * @param opts.odd - The odd bits of lfsr.
89
   * @see [mfkey source code from RfidResearchGroup/proxmark3](https://github.com/RfidResearchGroup/proxmark3/tree/master/tools/mfkey)
90
   * @example
91
   * ```js
92
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
93
   *
94
   * const state1 = new Crypto1()
95
   * const state2 = new Crypto1({ even: 0, odd: 0 })
96
   * ```
97
   */
98
  constructor ({ even = 0, odd = 0 }: { even?: number, odd?: number } = {}) {
1,326,788✔
99
    if (!_.isNil(even) && !_.isNil(odd)) {
1,326,788✔
100
      ;[this.even, this.odd] = [even, odd]
1,326,788✔
101
    }
1,326,788✔
102
  }
1,326,788✔
103

104
  /**
105
   * Reset the internal lfsr.
106
   * @example
107
   * ```js
108
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
109
   *
110
   * const state1 = new Crypto1({ even: 1, odd: 1 })
111
   * state1.reset()
112
   * ```
113
   */
114
  reset (): this {
1,326,788✔
115
    ;[this.odd, this.even] = [0, 0]
17✔
116
    return this
17✔
117
  }
17✔
118

119
  /**
120
   * Set the internal lfsr with the key.
121
   * @param key - The key to set the internal lfsr.
122
   * @example
123
   * ```js
124
   * const { Buffer } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/+esm')
125
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
126
   *
127
   * const state1 = new Crypto1()
128
   * state1.setLfsr(new Buffer('FFFFFFFFFFFF'))
129
   * ```
130
   */
131
  setLfsr (key: number): this {
1,326,788✔
132
    const { lfsrBuf } = Crypto1
16✔
133
    this.reset()
16✔
134
    lfsrBuf.writeUIntBE(key, 0, 6)
16✔
135
    for (let i = 47; i > 0; i -= 2) {
16✔
136
      ;[this.odd, this.even] = [
384✔
137
        (this.odd << 1) | lfsrBuf.readBitLSB((i - 1) ^ 7),
384✔
138
        (this.even << 1) | lfsrBuf.readBitLSB(i ^ 7),
384✔
139
      ]
384✔
140
    }
384✔
141
    return this
16✔
142
  }
16✔
143

144
  /**
145
   * Get the value of lfsr.
146
   * @returns lfsr.
147
   * @example
148
   * ```js
149
   * const { Buffer } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/+esm')
150
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
151
   *
152
   * const state1 = new Crypto1()
153
   * console.log(state1.setLfsr(new Buffer('FFFFFFFFFFFF')).getLfsr().toString(16)) // 'FFFFFFFFFFFF'
154
   * ```
155
   */
156
  getLfsr (): number {
1,326,788✔
157
    const { bit } = Crypto1
1,267,546✔
158
    let lfsr = 0
1,267,546✔
159
    for (let i = 23, j = (i ^ 3); i >= 0; i--, j = (i ^ 3)) {
1,267,546✔
160
      lfsr = lfsr * 4 + (bit(this.odd, j) > 0 ? 2 : 0) + bit(this.even, j)
30,421,104✔
161
    }
30,421,104✔
162
    return lfsr
1,267,546✔
163
  }
1,267,546✔
164

165
  /**
166
   * Get the lfsr output bit and update lfsr by input bit.
167
   * @param input - The input bit.
168
   * @param isEncrypted - Indicates whether the input bit is encrypted or not.
169
   * @returns The lfsr output bit.
170
   */
171
  lfsrBit (input: number, isEncrypted: number): number {
1,326,788✔
172
    const { evenParity32, filter, toBool } = Crypto1
858,080✔
173
    const output = filter(this.odd)
858,080✔
174

175
    const feedin = (output & toBool(isEncrypted)) ^
858,080✔
176
      toBool(input) ^
858,080✔
177
      (LF_POLY_ODD & this.odd) ^
858,080✔
178
      (LF_POLY_EVEN & this.even)
858,080✔
179

180
    ;[this.odd, this.even] = [
858,080✔
181
      (this.even << 1) & 0xFFFFFF | evenParity32(feedin),
858,080✔
182
      this.odd,
858,080✔
183
    ]
858,080✔
184

185
    return output
858,080✔
186
  }
858,080✔
187

188
  /**
189
   * Get the lfsr output byte and update lfsr by input byte.
190
   * @param input - The input byte.
191
   * @param isEncrypted - Indicates whether the input byte is encrypted or not.
192
   * @returns The lfsr output byte.
193
   */
194
  lfsrByte (input: number, isEncrypted: number): number {
1,326,788✔
195
    const { bit } = Crypto1
60✔
196
    let ret = 0
60✔
197
    for (let i = 0; i < 8; i++) ret |= this.lfsrBit(bit(input, i), isEncrypted) << i
60✔
198
    return ret
60✔
199
  }
60✔
200

201
  /**
202
   * Get the lfsr 32-bit output word and update lfsr by 32-bit input word.
203
   * @param input - The 32-bit input word.
204
   * @param isEncrypted - Indicates whether the 32-bit input word is encrypted or not.
205
   * @returns The lfsr 32-bit output word.
206
   */
207
  lfsrWord (input: number, isEncrypted: number): number {
1,326,788✔
208
    const { beBit } = Crypto1
26,800✔
209
    const u32 = new Uint32Array([0])
26,800✔
210
    for (let i = 0; i < 32; i++) u32[0] |= this.lfsrBit(beBit(input, i), isEncrypted) << (i ^ 24)
26,800✔
211
    return u32[0]
26,800✔
212
  }
26,800✔
213

214
  /**
215
   * Rollback the lfsr in order to get previous states
216
   * @param input - The input bit.
217
   * @param isEncrypted - Indicates whether the input bit is encrypted or not.
218
   * @returns The lfsr output bit.
219
   */
220
  lfsrRollbackBit (input: number, isEncrypted: number): number {
1,326,788✔
221
    const { evenParity32, filter, toBit, toBool, toUint24, toUint32 } = Crypto1
49,584,320✔
222
    ;[this.even, this.odd] = [toUint24(this.odd), this.even]
49,584,320✔
223
    const ret = filter(this.odd)
49,584,320✔
224

225
    let out = toBit(this.even)
49,584,320✔
226
    out ^= LF_POLY_EVEN & (this.even >>>= 1)
49,584,320✔
227
    out ^= LF_POLY_ODD & this.odd
49,584,320✔
228
    out ^= toBool(input) ^ (ret & toBool(isEncrypted))
49,584,320✔
229

230
    this.even = toUint32(this.even | evenParity32(out) << 23)
49,584,320✔
231
    return ret
49,584,320✔
232
  }
49,584,320✔
233

234
  /**
235
   * Rollback the lfsr in order to get previous states
236
   * @param input - The input byte.
237
   * @param isEncrypted - Indicates whether the input byte is encrypted or not.
238
   * @returns The lfsr output byte.
239
   */
240
  lfsrRollbackByte (input: number, isEncrypted: number): number {
1,326,788✔
241
    const { bit } = Crypto1
×
242
    let ret = 0
×
243
    for (let i = 7; i >= 0; i--) ret |= this.lfsrRollbackBit(bit(input, i), isEncrypted) << i
×
244
    return ret
×
UNCOV
245
  }
×
246

247
  /**
248
   * Rollback the lfsr in order to get previous states
249
   * @param input - The 32-bit input word.
250
   * @param isEncrypted - Indicates whether the 32-bit input word is encrypted or not.
251
   * @returns The lfsr 32-bit output word.
252
   */
253
  lfsrRollbackWord (input: number, isEncrypted: number): number {
1,326,788✔
254
    const { beBit } = Crypto1
1,537,684✔
255
    const u32 = new Uint32Array(1)
1,537,684✔
256
    for (let i = 31; i >= 0; i--) u32[0] |= this.lfsrRollbackBit(beBit(input, i), isEncrypted) << (i ^ 24)
1,537,684✔
257
    return u32[0]
1,537,684✔
258
  }
1,537,684✔
259

260
  /**
261
   * Get bit of the unsigned reversed endian 32-bit integer `x` at position `n`.
262
   * @param x - The reversed endian unsigned 32-bit integer.
263
   * @param n - The bit position.
264
   * @returns The bit at position `n`.
265
   * @internal
266
   * @group Internal
267
   * @example
268
   * ```js
269
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
270
   *
271
   * console.log(Crypto1.beBit(0x01000000, 0)) // 1
272
   * ```
273
   */
274
  static beBit (x: number, n: number): number { return Crypto1.bit(x, n ^ 24) }
1,326,788✔
275

276
  /**
277
   * Get bit of the unsigned 32-bit integer `x` at position `n`.
278
   * @param x - The unsigned 32-bit integer.
279
   * @param n - The bit position.
280
   * @returns The bit at position `n`.
281
   * @internal
282
   * @group Internal
283
   * @example
284
   * ```js
285
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
286
   *
287
   * console.log(Crypto1.bit(0x1, 0)) // 1
288
   * ```
289
   */
290
  static bit (x: number, n: number): number { return Crypto1.toBit(x >>> n) }
1,326,788✔
291

292
  /**
293
   * Cast the number `x` to bit.
294
   * @param x - The number.
295
   * @returns The casted bit.
296
   * @internal
297
   * @group Internal
298
   * @example
299
   * ```js
300
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
301
   *
302
   * console.log(Crypto1.toBit(1)) // 1
303
   * console.log(Crypto1.toBit(2)) // 0
304
   * ```
305
   */
306
  static toBit (x: number): number { return x & 1 }
1,326,788✔
307

308
  /**
309
   * Indicates whether the number is truly or not.
310
   * @param x - The number.
311
   * @returns Return `1` if the number is not falsey, otherwise return `0`.
312
   * @internal
313
   * @group Internal
314
   * @example
315
   * ```js
316
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
317
   *
318
   * console.log(Crypto1.toBool(1)) // 1
319
   * console.log(Crypto1.toBool(2)) // 1
320
   * ```
321
   */
322
  static toBool (x: number): number { return x !== 0 ? 1 : 0 }
1,326,788✔
323

324
  /**
325
   * Cast the number `x` to unsigned 24-bit integer.
326
   * @param x - The number.
327
   * @returns The casted unsigned 24-bit integer.
328
   * @internal
329
   * @group Internal
330
   * @example
331
   * ```js
332
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
333
   *
334
   * console.log(Crypto1.toUint24(-1).toString(16)) // 'ffffff'
335
   * ```
336
   */
337
  static toUint24 (x: number): number { return x & 0xFFFFFF }
1,326,788✔
338

339
  /**
340
   * Cast the number `x` to unsigned 32-bit integer.
341
   * @param x - The number.
342
   * @returns The casted unsigned 32-bit integer.
343
   * @internal
344
   * @group Internal
345
   * @example
346
   * ```js
347
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
348
   *
349
   * console.log(Crypto1.toUint32(-1).toString(16)) // 'ffffffff'
350
   * ```
351
   */
352
  static toUint32 (x: number): number { return x >>> 0 }
1,326,788✔
353

354
  /**
355
   * Cast Buffer, hex string or number to UInt32
356
   * @param x - Buffer, string or number
357
   * @returns UInt32
358
   * @internal
359
   * @group Internal
360
   */
361
  static castToUint32 (x: UInt32Like): number {
1,326,788✔
362
    const { toUint32 } = Crypto1
56✔
363
    if (_.isSafeInteger(x)) return toUint32(x as number)
56✔
364
    if (_.isString(x)) return Buffer.from(x, 'hex').readUInt32BE(0)
56✔
365
    return Buffer.from(x as any).readUInt32BE(0)
12✔
366
  }
56✔
367

368
  /**
369
   * Cast the number `x` to unsigned 8-bit integer.
370
   * @param x - The number.
371
   * @returns The casted unsigned 8-bit integer.
372
   * @internal
373
   * @group Internal
374
   * @example
375
   * ```js
376
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
377
   *
378
   * console.log(Crypto1.toUint8(-1).toString(16)) // 'ff'
379
   * ```
380
   */
381
  static toUint8 (x: number): number { return x & 0xFF }
1,326,788✔
382

383
  /**
384
   * The filter function of Crypto1.
385
   * @param x - The unsigned 32-bit integer.
386
   * @returns The filtered bit.
387
   * @internal
388
   * @group Internal
389
   */
390
  static filter (x: number): number {
1,326,788✔
391
    let f = 0
680,184,628✔
392
    f |= 0xF22C0 >>> (x & 0xF) & 16
680,184,628✔
393
    f |= 0x6C9C0 >>> (x >>> 4 & 0xF) & 8
680,184,628✔
394
    f |= 0x3C8B0 >>> (x >>> 8 & 0xF) & 4
680,184,628✔
395
    f |= 0x1E458 >>> (x >>> 12 & 0xF) & 2
680,184,628✔
396
    f |= 0x0D938 >>> (x >>> 16 & 0xF) & 1
680,184,628✔
397
    return Crypto1.bit(0xEC57E80A, f)
680,184,628✔
398
  }
680,184,628✔
399

400
  /**
401
   * Return the even parity of the unsigned 8-bit integer `x`.
402
   * @param x - The unsigned 8-bit integer.
403
   * @returns The even parity of `x`.
404
   * @internal
405
   * @group Internal
406
   */
407
  static evenParity8 (x: number): number {
1,326,788✔
408
    const { evenParityCache, toBit } = Crypto1
462,939,702✔
409
    if (evenParityCache.length !== 256) {
462,939,702✔
410
      for (let i = 0; i < 256; i++) {
1✔
411
        let tmp = i
256✔
412
        tmp ^= tmp >>> 4
256✔
413
        tmp ^= tmp >>> 2
256✔
414
        Crypto1.evenParityCache[i] = toBit(tmp ^ (tmp >>> 1))
256✔
415
      }
256✔
416
    }
1✔
417
    return evenParityCache[x & 0xFF]
462,939,702✔
418
  }
462,939,702✔
419

420
  /**
421
   * Return the odd parity of the unsigned 8-bit integer `x`.
422
   * @param x - The unsigned 8-bit integer.
423
   * @returns The odd parity of `x`.
424
   * @internal
425
   * @group Internal
426
   */
427
  static oddParity8 (x: number): number {
1,326,788✔
428
    return 1 - Crypto1.evenParity8(x)
×
UNCOV
429
  }
×
430

431
  /**
432
   * Return the even parity of the unsigned 32-bit integer `x`.
433
   * @param x - The unsigned 32-bit integer.
434
   * @returns The even parity of `x`.
435
   * @internal
436
   * @group Internal
437
   */
438
  static evenParity32 (x: number): number {
1,326,788✔
439
    x ^= x >>> 16
462,939,498✔
440
    return Crypto1.evenParity8(x ^ (x >>> 8))
462,939,498✔
441
  }
462,939,498✔
442

443
  /**
444
   * Swap endian of the unsigned 32-bit integer `x`.
445
   * @param x - The unsigned 32-bit integer.
446
   * @returns The unsigned 32-bit integer after swap endian.
447
   * @internal
448
   * @group Internal
449
   * @example
450
   * ```js
451
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
452
   *
453
   * console.log(Crypto1.swapEndian(0x12345678).toString(16)) // '78563412'
454
   * ```
455
   */
456
  static swapEndian (x: number): number {
1,326,788✔
457
    return Crypto1.lfsrBuf.writeUInt32BE(x, 0).readUInt32LE(0)
149✔
458
  }
149✔
459

460
  /**
461
   * Generate the new prng state from the current prng state `x` by `n` times.
462
   * @param x - The current prng state.
463
   * @param n - The number of times to generate the new prng state.
464
   * @returns The new prng state.
465
   */
466
  static prngSuccessor (x: number, n: number): number {
1,326,788✔
467
    const { swapEndian } = Crypto1
73✔
468
    x = swapEndian(x)
73✔
469
    while ((n--) !== 0) x = x >>> 1 | (x >>> 16 ^ x >>> 18 ^ x >>> 19 ^ x >>> 21) << 31
73✔
470
    return swapEndian(x)
73✔
471
  }
73✔
472

473
  /**
474
   * A helper function to calculates the partial linear feedback contributions and puts in MSB (Most Significant Bit).
475
   * @param item - The input number.
476
   * @param mask1 -
477
   * @param mask2 -
478
   * @internal
479
   * @group Internal
480
   */
481
  static updateContribution (item: number, mask1: number, mask2: number): number {
1,326,788✔
482
    const { evenParity32, toUint32 } = Crypto1
204,283,172✔
483
    let p = item >>> 25
204,283,172✔
484
    p = p << 2 | (evenParity32(item & mask1) > 0 ? 2 : 0) | evenParity32(item & mask2)
204,283,172✔
485
    return toUint32(p << 24 | item & 0xFFFFFF)
204,283,172✔
486
  }
204,283,172✔
487

488
  /**
489
   * Using a bit of the keystream extend the table of possible lfsr states. (complex version)
490
   * @param tbl - An array of the even/odd bits of lfsr.
491
   * @param size - Size of array.
492
   * @param bit - The bit of the keystream.
493
   * @param m1 - mask1
494
   * @param m2 - mask2
495
   * @param input - The value that was fed into the lfsr at the time the keystream was generated.
496
   * @returns The new size of array.
497
   * @internal
498
   * @group Internal
499
   */
500
  static extendTable (tbl: Uint32Array, size: number, bit: number, m1: number, m2: number, input: number): number {
1,326,788✔
501
    const { filter, toUint32, updateContribution } = Crypto1
6,521,510✔
502
    input = toUint32(input << 24)
6,521,510✔
503
    for (let i = 0; i < size; i++) {
6,521,510✔
504
      const iFilter = filter(tbl[i] *= 2)
203,593,226✔
505
      if ((iFilter ^ filter(tbl[i] | 1)) !== 0) { // replace
203,593,226✔
506
        tbl[i] = updateContribution(tbl[i] + (iFilter ^ bit), m1, m2) ^ input
18,381,306✔
507
      } else if (iFilter === bit) { // insert
203,593,226✔
508
        tbl[size++] = tbl[++i]
92,950,933✔
509
        tbl[i] = updateContribution(tbl[i - 1] + 1, m1, m2) ^ input
92,950,933✔
510
        tbl[i - 1] = updateContribution(tbl[i - 1], m1, m2) ^ input
92,950,933✔
511
      } else tbl[i--] = tbl[--size] // remove
185,211,920✔
512
    }
203,593,226✔
513
    return size
6,521,510✔
514
  }
6,521,510✔
515

516
  /**
517
   * Using a bit of the keystream extend the table of possible lfsr states. (simple version)
518
   * @param tbl - An array of the even/odd bits of lfsr.
519
   * @param size - Size of array.
520
   * @param bit - The bit of the keystream.
521
   * @returns The new size of array.
522
   * @internal
523
   * @group Internal
524
   */
525
  static extendTableSimple (tbl: Uint32Array, size: number, bit: number): number {
1,326,788✔
526
    const { filter } = Crypto1
1,044,026✔
527
    for (let i = 0; i < size; i++) {
1,044,026✔
528
      const iFilter = filter(tbl[i] *= 2)
83,332,105✔
529
      if ((iFilter ^ filter(tbl[i] | 1)) !== 0) { // replace
83,332,105✔
530
        tbl[i] += iFilter ^ bit
7,946,563✔
531
      } else if (iFilter === bit) { // insert
83,332,105✔
532
        tbl[size++] = tbl[++i]
37,518,496✔
533
        tbl[i] = tbl[i - 1] + 1
37,518,496✔
534
      } else tbl[i--] = tbl[--size] // remove
75,385,542✔
535
    }
83,332,105✔
536
    return size
1,044,026✔
537
  }
1,044,026✔
538

539
  /**
540
   * Recursively narrow down the search space, 4 bits of keystream at a time.
541
   * @param ctx -
542
   * @param ctx.evens - The array of even bits of possible lfsr states.
543
   * @param ctx.odds - The array of odd bits of possible lfsr states.
544
   * @param ctx.states - The array of recovered lfsr states.
545
   * @internal
546
   * @group Internal
547
   */
548
  static mfkeyRecoverState (ctx: {
1,326,788✔
549
    eks: number
550
    evens: RecoverContextUint32Array
551
    input: number
552
    odds: RecoverContextUint32Array
553
    oks: number
554
    rem: number
555
    states: Crypto1[]
556
  }): void {
2,285,289✔
557
    const { evenParity32, extendTable, mfkeyRecoverState, toBit, toBool, toUint32 } = Crypto1
2,285,289✔
558
    const { evens, odds, states } = ctx
2,285,289✔
559
    if (ctx.rem < 0) {
2,285,289✔
560
      for (let i = 0; i < evens.s; i++) {
1,102,116✔
561
        evens.d[i] = (evens.d[i] << 1) ^ evenParity32(evens.d[i] & LF_POLY_EVEN) ^ toBool(ctx.input & 4)
1,149,125✔
562
        for (let j = 0; j < odds.s; j++) {
1,149,125✔
563
          states.push(new Crypto1({
1,200,623✔
564
            even: odds.d[j],
1,200,623✔
565
            odd: toUint32(evens.d[i] ^ evenParity32(odds.d[j] & LF_POLY_ODD)),
1,200,623✔
566
          }))
1,200,623✔
567
        }
1,200,623✔
568
      }
1,149,125✔
569
      return
1,102,116✔
570
    }
1,102,116✔
571

572
    for (let i = 0; i < 4 && (ctx.rem--) !== 0; i++) {
2,285,289✔
573
      ;[ctx.oks, ctx.eks, ctx.input] = [ctx.oks >>> 1, ctx.eks >>> 1, ctx.input >>> 2]
3,350,284✔
574
      odds.s = extendTable(odds.d, odds.s, toBit(ctx.oks), LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0)
3,350,284✔
575
      if (odds.s === 0) return
3,350,284✔
576
      evens.s = extendTable(evens.d, evens.s, toBit(ctx.eks), LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, ctx.input & 3)
3,171,226✔
577
      if (evens.s === 0) return
3,171,226✔
578
    }
3,350,284✔
579

580
    evens.d.subarray(0, evens.s).sort()
804,990✔
581
    odds.d.subarray(0, odds.s).sort()
804,990✔
582

583
    while ((odds.s + evens.s) !== 0) {
2,285,289✔
584
      const [oddBucket, evenBucket] = _.map([odds.d[odds.s - 1], evens.d[evens.s - 1]], num => toUint32(num & 0xFF000000))
14,919,611✔
585
      if (oddBucket !== evenBucket) {
14,919,611✔
586
        if (oddBucket > evenBucket) odds.s = _.sortedIndex(odds.d.subarray(0, odds.s), oddBucket)
12,634,340✔
587
        else evens.s = _.sortedIndex(evens.d.subarray(0, evens.s), evenBucket)
6,204,424✔
588
        continue
12,634,340✔
589
      }
12,634,340✔
590
      const [evenStart, oddStart] = [
2,285,271✔
591
        _.sortedIndex(evens.d.subarray(0, evens.s), oddBucket),
2,285,271✔
592
        _.sortedIndex(odds.d.subarray(0, odds.s), evenBucket),
2,285,271✔
593
      ]
2,285,271✔
594
      mfkeyRecoverState({
2,285,271✔
595
        ...ctx,
2,285,271✔
596
        evens: { d: evens.d.subarray(evenStart), s: evens.s - evenStart },
2,285,271✔
597
        odds: { d: odds.d.subarray(oddStart), s: odds.s - oddStart },
2,285,271✔
598
      })
2,285,271✔
599
      ;[evens.s, odds.s] = [evenStart, oddStart]
2,285,271✔
600
    }
2,285,271✔
601
  }
2,285,289✔
602

603
  /**
604
   * Recover the state of the lfsr given 32 bits of the keystream.
605
   * Additionally you can use the in parameter to specify the value that was fed into the lfsr at the time the keystream was generated
606
   * @param ks2 -
607
   * @param input -
608
   * @returns The array of recovered lfsr states.
609
   * @internal
610
   * @group Internal
611
   */
612
  static lfsrRecovery32 (ks2: number, input: number): Crypto1[] {
1,326,788✔
613
    const { beBit, extendTableSimple, filter, mfkeyRecoverState, toBit, toUint32 } = Crypto1
18✔
614
    const evens = { s: 0, d: new Uint32Array(1 << 21) } // possible evens for ks2
18✔
615
    const odds = { s: 0, d: new Uint32Array(1 << 21) } // possible odds for ks2
18✔
616
    const states: Crypto1[] = [] // possible states for ks2
18✔
617
    // split the keystream into an odd and even part
618
    let [oks, eks] = [0, 0]
18✔
619
    for (let i = 31; i > 0; i -= 2) {
18✔
620
      oks = toUint32((oks << 1) | beBit(ks2, i))
288✔
621
      eks = toUint32((eks << 1) | beBit(ks2, i - 1))
288✔
622
    }
288✔
623

624
    for (let [i, eksBit, oksBit] = [1 << 20, toBit(eks), toBit(oks)]; i >= 0; i--) {
18✔
625
      if (filter(i) === oksBit) odds.d[odds.s++] = i
18,874,386✔
626
      if (filter(i) === eksBit) evens.d[evens.s++] = i
18,874,386✔
627
    }
18,874,386✔
628

629
    for (let i = 0; i < 4; i++) {
18✔
630
      ;[eks, oks] = [eks >>> 1, oks >>> 1]
72✔
631
      evens.s = extendTableSimple(evens.d, evens.s, toBit(eks))
72✔
632
      odds.s = extendTableSimple(odds.d, odds.s, toBit(oks))
72✔
633
    }
72✔
634

635
    input = (input << 16) | (input >>> 16 & 0xff) | (input & 0xff00) // Byte swapping
18✔
636
    mfkeyRecoverState({ eks, evens, odds, oks, states, rem: 11, input: input << 1 })
18✔
637
    return states
18✔
638
  }
18✔
639

640
  /**
641
   * Reverse 64 bits of keystream into possible lfsr states.
642
   * Variation mentioned in the paper. Somewhat optimized version
643
   * @param ks2 - keystream 2
644
   * @param ks3 - keystream 3
645
   * @returns The recovered lfsr state.
646
   * @internal
647
   * @group Internal
648
   */
649
  static lfsrRecovery64 (ks2: number, ks3: number): Crypto1 {
1,326,788✔
650
    const { beBit, evenParity32, extendTableSimple, filter } = Crypto1
1✔
651
    const oks = new Uint8Array(32)
1✔
652
    const eks = new Uint8Array(32)
1✔
653
    const hi = new Uint8Array(32)
1✔
654
    let [low, win] = [0, 0]
1✔
655
    const tbl = { d: new Uint32Array(1 << 16), s: 0 }
1✔
656

657
    for (let i = 30; i >= 0; i -= 2) {
1✔
658
      oks[i >>> 1] = beBit(ks2, i)
16✔
659
      oks[16 + (i >>> 1)] = beBit(ks3, i)
16✔
660
    }
16✔
661
    for (let i = 31; i >= 0; i -= 2) {
1✔
662
      eks[i >>> 1] = beBit(ks2, i)
16✔
663
      eks[16 + (i >>> 1)] = beBit(ks3, i)
16✔
664
    }
16✔
665

666
    for (let i = 0xFFFFF; i >= 0; i--) {
1✔
667
      if (filter(i) !== oks[0]) continue
696,099✔
668
      tbl.s = 0 // reset
350,595✔
669
      tbl.d[tbl.s++] = i
350,595✔
670
      for (let j = 1; tbl.s !== 0 && j < 29; j++) tbl.s = extendTableSimple(tbl.d, tbl.s, oks[j])
696,099✔
671
      if (tbl.s === 0) continue
350,595✔
672

673
      for (let j = 0; j < 19; j++) low = low << 1 | evenParity32(i & S1[j])
696,099✔
674
      for (let j = 0; j < 32; j++) hi[j] = evenParity32(i & T1[j])
696,099✔
675

676
      for (let k = tbl.s - 1; k >= 0; k--) {
696,099✔
677
        try {
300,863✔
678
          for (let j = 0; j < 3; j++) {
300,863✔
679
            tbl.d[k] <<= 1
539,461✔
680
            tbl.d[k] |= evenParity32((i & C1[j]) ^ (tbl.d[k] & C2[j]))
539,461✔
681
            if (filter(tbl.d[k]) !== oks[29 + j]) throw new Error('cont2')
539,461✔
682
          }
539,461✔
683
          for (let j = 0; j < 19; j++) win = win << 1 | evenParity32(tbl.d[k] & S2[j])
300,863✔
684
          win ^= low
43,071✔
685
          for (let j = 0; j < 32; j++) {
300,863✔
686
            win = (win << 1) ^ hi[j] ^ evenParity32(tbl.d[k] & T2[j])
86,867✔
687
            if (filter(win) !== eks[j]) throw new Error('cont2')
86,867✔
688
          }
86,867✔
689

690
          tbl.d[k] = tbl.d[k] << 1 | evenParity32(LF_POLY_EVEN & tbl.d[k])
1✔
691
          return new Crypto1({ even: win, odd: tbl.d[k] ^ evenParity32(LF_POLY_ODD & win) })
1✔
692
        } catch (err) {
300,863✔
693
          if (err.message !== 'cont2') throw err
300,862!
694
        }
300,862✔
695
      }
300,863✔
696
    }
2,672!
697
    throw new Error('failed to recover lfsr')
×
698
  }
1✔
699

700
  /**
701
   * Recover the key with the two authentication attempts from reader.
702
   * @param opts -
703
   * @param opts.uid - The 4-bytes uid in the authentication attempt.
704
   * @param opts.nt0 - The nonce from tag in the first authentication attempt.
705
   * @param opts.nr0 - The calculated nonce response from reader in the first authentication attempt.
706
   * @param opts.ar0 - The random challenge from reader in the first authentication attempt.
707
   * @param opts.nt1 - The nonce from tag in the second authentication attempt.
708
   * @param opts.nr1 - The calculated nonce response from reader in the second authentication attempt.
709
   * @param opts.ar1 - The random challenge from reader in the second authentication attempt.
710
   * @returns The recovered key.
711
   * @example
712
   * ```js
713
   * const { Buffer } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/+esm')
714
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
715
   *
716
   * console.log(Crypto1.mfkey32v2({
717
   *   uid: 0x65535D33,
718
   *   nt0: 0xCB7B9ED9,
719
   *   nr0: 0x5A8FFEC6,
720
   *   ar0: 0x5C7C6F89,
721
   *   nt1: 0x1E6D9228,
722
   *   nr1: 0x6FB8B4A8,
723
   *   ar1: 0xEF4039FB,
724
   * }).toString('hex')) // A9AC67832330
725
   * console.log(Crypto1.mfkey32v2({
726
   *   uid: Buffer.fromHex('65535D33'),
727
   *   nt0: Buffer.fromHex('CB7B9ED9'),
728
   *   nr0: Buffer.fromHex('5A8FFEC6'),
729
   *   ar0: Buffer.fromHex('5C7C6F89'),
730
   *   nt1: Buffer.fromHex('1E6D9228'),
731
   *   nr1: Buffer.fromHex('6FB8B4A8'),
732
   *   ar1: Buffer.fromHex('EF4039FB'),
733
   * }).toString('hex')) // A9AC67832330
734
   * console.log(Crypto1.mfkey32v2({
735
   *   uid: '65535D33',
736
   *   nt0: 'CB7B9ED9',
737
   *   nr0: '5A8FFEC6',
738
   *   ar0: '5C7C6F89',
739
   *   nt1: '1E6D9228',
740
   *   nr1: '6FB8B4A8',
741
   *   ar1: 'EF4039FB',
742
   * }).toString('hex')) // A9AC67832330
743
   * ```
744
   */
745
  static mfkey32v2 (opts: {
1,326,788✔
746
    uid: UInt32Like
747
    nt0: UInt32Like
748
    nr0: UInt32Like
749
    ar0: UInt32Like
750
    nt1: UInt32Like
751
    nr1: UInt32Like
752
    ar1: UInt32Like
753
  }): Buffer {
1✔
754
    const { castToUint32, lfsrRecovery32, prngSuccessor, toUint32 } = Crypto1
1✔
755
    const [uid, nt0, nr0, ar0, nt1, nr1, ar1] = _.map(['uid', 'nt0', 'nr0', 'ar0', 'nt1', 'nr1', 'ar1'] as const, k => castToUint32(opts[k]))
1✔
756
    const p640 = prngSuccessor(nt0, 64)
1✔
757
    const p641 = prngSuccessor(nt1, 64)
1✔
758

759
    const states = lfsrRecovery32(ar0 ^ p640, 0)
1✔
760
    for (const state of states) {
1✔
761
      state.lfsrRollbackWord(0, 0)
8,925✔
762
      state.lfsrRollbackWord(nr0, 1)
8,925✔
763
      state.lfsrRollbackWord(uid ^ nt0, 0)
8,925✔
764
      const key = state.getLfsr()
8,925✔
765
      state.lfsrWord(uid ^ nt1, 0)
8,925✔
766
      state.lfsrWord(nr1, 1)
8,925✔
767
      if (toUint32(state.lfsrWord(0, 0) ^ p641) === ar1) return new Buffer(6).writeUIntBE(key, 0, 6)
8,925✔
768
    }
8,925!
769
    throw new Error('failed to recover key')
×
770
  }
1✔
771

772
  /**
773
   * A method for Tag to validate Reader has the correct key.
774
   * @param opts.ar - The encrypted prng successor of `opts.nt`.
775
   * @param opts.key - The 6-bytes key to be test.
776
   * @param opts.nr - The encrypted nonce from reader.
777
   * @param opts.nt - The nonce from tag.
778
   * @param opts.uid - The 4-bytes uid of tag.
779
   * @example
780
   * ```js
781
   * const { Buffer } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/+esm')
782
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
783
   *
784
   * console.log(Crypto1.mfkey32IsReaderHasKey({
785
   *   ar: 'CF0A3C7E',
786
   *   key: 'A9AC67832330',
787
   *   nr: 'FEDAC6D2',
788
   *   nt: '2C198BE4',
789
   *   uid: '65535D33',
790
   * }).toString('hex')) // true
791
   * ```
792
   */
793
  static mfkey32IsReaderHasKey (opts: {
1,326,788✔
794
    ar: UInt32Like
795
    key: Buffer
796
    nr: UInt32Like
797
    nt: UInt32Like
798
    uid: UInt32Like
799
  }): boolean {
3✔
800
    if (!Buffer.isBuffer(opts.key) || opts.key.length !== 6) throw new TypeError('invalid opts.key')
3!
801
    const { castToUint32, prngSuccessor, toUint32 } = Crypto1
3✔
802
    const tag: Record<string, any> = { state: new Crypto1() }
3✔
803
    ;[tag.uid, tag.nt, tag.nrEnc, tag.arEnc] = _.map(['uid', 'nt', 'nr', 'ar'] as const, k => castToUint32(opts[k]))
3✔
804
    tag.state.setLfsr(opts.key.readUIntBE(0, 6))
3✔
805
    tag.ks0 = tag.state.lfsrWord(tag.uid ^ tag.nt, 0)
3✔
806
    tag.ks1 = tag.state.lfsrWord(tag.nrEnc, 1)
3✔
807
    tag.ks2 = tag.state.lfsrWord(0, 0)
3✔
808
    tag.ar = toUint32(tag.ks2 ^ tag.arEnc)
3✔
809
    return tag.ar === prngSuccessor(tag.nt, 64)
3✔
810
  }
3✔
811

812
  /**
813
   * Recover the key with the successfully authentication between the reader and the tag.
814
   * @param opts -
815
   * @param opts.uid - The 4-bytes uid in the authentication.
816
   * @param opts.nt - The nonce from tag in the authentication.
817
   * @param opts.nr - The calculated response of `args.nt` from reader in the authentication.
818
   * @param opts.ar - The random challenge from reader in the authentication.
819
   * @param opts.at - The calculated response of `args.ar` from tag in the authentication.
820
   * @returns The recovered key.
821
   * @example
822
   * ```js
823
   * const { Buffer } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/+esm')
824
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
825
   *
826
   * console.log(Crypto1.mfkey32v2({
827
   *   uid: 0x65535D33,
828
   *   nt: 0x2C198BE4,
829
   *   nr: 0xFEDAC6D2,
830
   *   ar: 0xCF0A3C7E,
831
   *   at: 0xF4A81AF8,
832
   * }).toString('hex')) // A9AC67832330
833
   * console.log(Crypto1.mfkey32v2({
834
   *   uid: Buffer.fromHex('65535D33'),
835
   *   nt: Buffer.fromHex('2C198BE4'),
836
   *   nr: Buffer.fromHex('FEDAC6D2'),
837
   *   ar: Buffer.fromHex('CF0A3C7E'),
838
   *   at: Buffer.fromHex('F4A81AF8'),
839
   * }).toString('hex')) // A9AC67832330
840
   * console.log(Crypto1.mfkey32v2({
841
   *   uid: '65535D33',
842
   *   nt: '2C198BE4',
843
   *   nr: 'FEDAC6D2',
844
   *   ar: 'CF0A3C7E',
845
   *   at: 'F4A81AF8',
846
   * }).toString('hex')) // A9AC67832330
847
   * ```
848
   */
849
  static mfkey64 (opts: {
1,326,788✔
850
    uid: UInt32Like
851
    nt: UInt32Like
852
    nr: UInt32Like
853
    ar: UInt32Like
854
    at: UInt32Like
855
  }): Buffer {
1✔
856
    const { castToUint32, lfsrRecovery64, prngSuccessor } = Crypto1
1✔
857
    const [uid, nt, nr, ar, at] = _.map(['uid', 'nt', 'nr', 'ar', 'at'] as const, k => castToUint32(opts[k]))
1✔
858
    const p64 = prngSuccessor(nt, 64)
1✔
859
    const [ks2, ks3] = [ar ^ p64, at ^ prngSuccessor(p64, 32)]
1✔
860
    const state = lfsrRecovery64(ks2, ks3)
1✔
861
    state.lfsrRollbackWord(0, 0)
1✔
862
    state.lfsrRollbackWord(0, 0)
1✔
863
    state.lfsrRollbackWord(nr, 1)
1✔
864
    state.lfsrRollbackWord(uid ^ nt, 0)
1✔
865
    return new Buffer(6).writeUIntBE(state.getLfsr(), 0, 6)
1✔
866
  }
1✔
867

868
  /**
869
   * Decrypt the data.
870
   * @param opts -
871
   * @param opts.uid - The 4-bytes uid in the authentication.
872
   * @param opts.nt - The nonce from tag in the authentication.
873
   * @param opts.nr - The calculated response of `args.nt` from reader in the authentication.
874
   * @param opts.data - The encrypted data.
875
   * @param opts.key - The 6-bytes key to decrypt the data.
876
   * @returns The decrypted data.
877
   */
878
  static decrypt (opts: {
1,326,788✔
879
    uid: UInt32Like
880
    nt: UInt32Like
881
    nr: UInt32Like
882
    data: Buffer
883
    key: Buffer
884
  }): Buffer {
4✔
885
    const { castToUint32 } = Crypto1
4✔
886
    if (!Buffer.isBuffer(opts.key) || opts.key.length !== 6) throw new TypeError('invalid opts.key')
4!
887
    if (!Buffer.isBuffer(opts.data)) throw new TypeError('invalid opts.data')
4!
888
    const [uid, nt, nr] = _.map(['uid', 'nt', 'nr'] as const, k => castToUint32(opts[k]))
4✔
889
    const data = opts.data.slice() // clone data
4✔
890

891
    const state = new Crypto1()
4✔
892
    state.setLfsr(opts.key.readUIntBE(0, 6))
4✔
893
    state.lfsrWord(uid ^ nt, 0)
4✔
894
    state.lfsrWord(nr, 1)
4✔
895
    for (let i = 0; i < 2; i++) state.lfsrWord(0, 0)
4✔
896

897
    for (let i = 0; i < data.length; i++) data[i] ^= state.lfsrByte(0, 0)
4✔
898
    return data
4✔
899
  }
4✔
900

901
  /**
902
   * @group Internal
903
   * @internal
904
   */
905
  static nestedRecoverState (opts: {
1,326,788✔
906
    uid: number
907
    atks: Array<{
908
      ntp: number
909
      ks1: number
910
    }>
911
  }): Buffer[] {
3✔
912
    const { lfsrRecovery32, toUint32 } = Crypto1
3✔
913
    const keyCnt = new Map<number, number>()
3✔
914
    for (const { ntp, ks1 } of opts.atks) {
3✔
915
      const tmp = toUint32(ntp ^ opts.uid)
17✔
916
      const states = lfsrRecovery32(ks1, tmp)
17✔
917
      for (const state of states) {
17✔
918
        state.lfsrRollbackWord(tmp, 0)
1,132,470✔
919
        const key = state.getLfsr()
1,132,470✔
920
        keyCnt.set(key, (keyCnt.get(key) ?? 0) + 1)
1,132,470✔
921
      }
1,132,470✔
922
    }
17✔
923
    return _.chain([...keyCnt.entries()])
3✔
924
      .orderBy([1], ['desc'])
3✔
925
      .take(50)
3✔
926
      .map(key => new Buffer(6).writeUIntBE(key[0], 0, 6))
3✔
927
      .value()
3✔
928
  }
3✔
929

930
  /**
931
   * Recover key from mifare tags with static nonce
932
   * @param opts -
933
   * @param opts.uid - The 4-bytes uid in the authentication.
934
   * @param opts.keyType - The key type of target block.
935
   * @param opts.atks - The nonce logs of the authentication.
936
   * @returns candidates keys
937
   * @example
938
   * ```js
939
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
940
   * const { Mf1KeyType } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/+esm')
941
   * const args = {
942
   *   uid: 'b908a16d',
943
   *   keyType: Mf1KeyType.KEY_A,
944
   *   atks: [
945
   *     { nt1: '01200145', nt2: '81901975' },
946
   *     { nt1: '01200145', nt2: 'cdd400f3' },
947
   *   ],
948
   * }
949
   * const keys = Crypto1.staticnested(args)
950
   * console.log(`keys = ${JSON.stringify(_.map(keys, key => key.toString('hex')))}`)
951
   * ```
952
   */
953
  static staticnested (opts: {
1,326,788✔
954
    uid: UInt32Like
955
    keyType: Mf1KeyType
956
    atks: Array<{
957
      nt1: UInt32Like
958
      nt2: UInt32Like
959
    }>
960
  }): Buffer[] {
2✔
961
    const { castToUint32, nestedRecoverState, prngSuccessor, toUint32 } = Crypto1
2✔
962

963
    // dist
964
    const firstNt = castToUint32(opts.atks[0].nt1)
2✔
965
    let dist = 0
2✔
966
    // st gen1: There is no loophole in this generation. This tag can be decrypted with the default parameter value 160!
967
    if (firstNt === 0x01200145) dist = 160
2✔
968
    // st gen2: tag is vulnerable too but parameter must be adapted depending on the attacked key type
969
    else if (firstNt === 0x009080A2) dist = opts.keyType === Mf1KeyType.KEY_A ? 160 : 161
1!
970
    if (dist === 0) throw new Error('unknown static nonce')
2!
971

972
    return nestedRecoverState({
2✔
973
      uid: castToUint32(opts.uid),
2✔
974
      atks: _.map(opts.atks, tmp => {
2✔
975
        const [nt1, nt2] = _.map([tmp.nt1, tmp.nt2], castToUint32)
4✔
976
        const ntp = prngSuccessor(nt1, dist)
4✔
977
        const ks1 = toUint32(nt2 ^ ntp)
4✔
978
        dist += 160
4✔
979
        return { ntp, ks1 }
4✔
980
      }),
2✔
981
    })
2✔
982
  }
2✔
983

984
  /**
985
   * Recover key from mifare tags with weak prng
986
   * @param opts -
987
   * @param opts.uid - The 4-bytes uid in the authentication.
988
   * @param opts.dist - The nonce distance between two authentication.
989
   * @param opts.atks - The logs of the nested attack.
990
   * @returns candidates keys
991
   * @example
992
   * ```js
993
   * const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
994
   * const args = {
995
   *   uid: '877209e1',
996
   *   dist: '00000080',
997
   *   atks: [
998
   *     { nt1: 'b4a08a09', nt2: '8a15bbf2', par: 5 },
999
   *     { nt1: '1613293d', nt2: '912e6760', par: 7 }
1000
   *   ]
1001
   * }
1002
   * const keys = Crypto1.nested(args)
1003
   * console.log(`keys = ${JSON.stringify(_.map(keys, key => key.toString('hex')))}`)
1004
   * ```
1005
   */
1006
  static nested (opts: {
1,326,788✔
1007
    uid: UInt32Like
1008
    dist: UInt32Like
1009
    atks: Array<{ nt1: UInt32Like, nt2: UInt32Like, par: UInt32Like }>
1010
  }): Buffer[] {
1✔
1011
    const { castToUint32, nestedIsValidNonce, nestedRecoverState, prngSuccessor, toUint32 } = Crypto1
1✔
1012

1013
    const dist = castToUint32(opts.dist)
1✔
1014
    const atks: Array<{ ntp: number, ks1: number }> = []
1✔
1015

1016
    for (let i = 0; i < opts.atks.length; i++) {
1✔
1017
      const tmp = opts.atks[i]
2✔
1018
      const [nt1, nt2, par] = _.map([tmp.nt1, tmp.nt2, tmp.par], castToUint32)
2✔
1019
      let ntp = prngSuccessor(nt1, dist - 14)
2✔
1020
      for (let j = 0; j < 29; j++, ntp = prngSuccessor(ntp, 1)) {
2✔
1021
        const ks1 = toUint32(nt2 ^ ntp)
58✔
1022
        if (nestedIsValidNonce(ntp, nt2, ks1, par)) atks.push({ ntp, ks1 })
58✔
1023
      }
58✔
1024
    }
2✔
1025

1026
    return nestedRecoverState({ uid: castToUint32(opts.uid), atks })
1✔
1027
  }
1✔
1028

1029
  /**
1030
   * @group Internal
1031
   * @internal
1032
   */
1033
  static nestedIsValidNonce (nt1: number, nt2: number, ks1: number, par: number): boolean {
1,326,788✔
1034
    const { evenParity8, bit } = Crypto1
58✔
1035
    if (evenParity8((nt1 >>> 24) & 0xFF) !== (bit(par, 0) ^ evenParity8((nt2 >>> 24) & 0xFF) ^ bit(ks1, 16))) return false
58✔
1036
    if (evenParity8((nt1 >>> 16) & 0xFF) !== (bit(par, 1) ^ evenParity8((nt2 >>> 16) & 0xFF) ^ bit(ks1, 8))) return false
58✔
1037
    if (evenParity8((nt1 >>> 8) & 0xFF) !== (bit(par, 2) ^ evenParity8((nt2 >>> 8) & 0xFF) ^ bit(ks1, 0))) return false
58✔
1038
    return true
13✔
1039
  }
58✔
1040

1041
  /**
1042
   * @group Internal
1043
   * @internal
1044
   */
1045
  static lfsrPrefixKs (ks: Buffer, isOdd: boolean): number[] {
1,326,788✔
1046
    const { bit, filter, toUint32 } = Crypto1
4✔
1047
    const candidates: number[] = []
4✔
1048
    for (let i = 0; i < 2097152; i++) { // 2**21 = 2097152
4✔
1049
      let isCandidate = true
8,388,608✔
1050
      for (let j = 0; isCandidate && j < 8; j++) {
8,388,608✔
1051
        const tmp = toUint32(i ^ fastfwd[isOdd ? (8 + j) : j])
11,203,442✔
1052
        isCandidate = bit(ks[j], isOdd ? 1 : 0) === filter(tmp >>> 1) && bit(ks[j], isOdd ? 3 : 2) === filter(tmp)
11,203,442✔
1053
      }
11,203,442✔
1054
      if (isCandidate) candidates.push(i)
8,388,608✔
1055
    }
8,388,608✔
1056
    return candidates
4✔
1057
  }
4✔
1058

1059
  /**
1060
   * helper function which eliminates possible secret states using parity bits
1061
   * @internal
1062
   * @group Internal
1063
   */
1064
  static checkPfxParity (pfx: number, ar: number, par: number[][], odd: number, even: number, isZeroPar: boolean): Crypto1 | undefined {
1,326,788✔
1065
    const { evenParity32, bit, toUint32 } = Crypto1
126,144✔
1066
    const state = new Crypto1()
126,144✔
1067
    for (let i = 0; i < 8; i++) {
126,144✔
1068
      ;[state.odd, state.even] = [toUint32(odd ^ fastfwd[8 + i]), toUint32(even ^ fastfwd[i])]
126,144✔
1069
      state.lfsrRollbackBit(0, 0)
126,144✔
1070
      state.lfsrRollbackBit(0, 0)
126,144✔
1071
      const ks3 = state.lfsrRollbackBit(0, 0)
126,144✔
1072
      const ks2 = state.lfsrRollbackWord(0, 0)
126,144✔
1073
      const ks1 = state.lfsrRollbackWord(pfx | (i << 5), 1)
126,144✔
1074
      if (isZeroPar) return state
126,144!
1075

1076
      const nr = toUint32(ks1 ^ (pfx | (i << 5)))
×
1077
      const arEnc = toUint32(ks2 ^ ar)
×
1078

1079
      if ((evenParity32(nr & 0x000000FF) ^ par[i][3] ^ bit(ks2, 24)) < 1) return
×
1080
      if ((evenParity32(arEnc & 0xFF000000) ^ par[i][4] ^ bit(ks2, 16)) < 1) return
×
1081
      if ((evenParity32(arEnc & 0x00FF0000) ^ par[i][5] ^ bit(ks2, 8)) < 1) return
×
1082
      if ((evenParity32(arEnc & 0x0000FF00) ^ par[i][6] ^ bit(ks2, 0)) < 1) return
×
1083
      if ((evenParity32(arEnc & 0x000000FF) ^ par[i][7] ^ ks3) < 1) return
×
1084
    }
126,144!
1085
    return state
×
1086
  }
126,144✔
1087

1088
  /**
1089
   * @group Internal
1090
   * @internal
1091
   */
1092
  static lfsrCommonPrefix (pfx: number, ar: number, ks: Buffer, par: number[][], isZeroPar: boolean): Crypto1[] {
1,326,788✔
1093
    const { lfsrPrefixKs, checkPfxParity } = Crypto1
2✔
1094
    const odds = lfsrPrefixKs(ks, true)
2✔
1095
    const evens = lfsrPrefixKs(ks, false)
2✔
1096

1097
    const states: Crypto1[] = []
2✔
1098
    for (let odd of odds) {
2✔
1099
      for (let even of evens) {
53✔
1100
        for (let i = 0; i < 64; i++) {
1,971✔
1101
          odd += 2097152
126,144✔
1102
          even += (i & 0x7) > 0 ? 2097152 : 4194304
126,144✔
1103
          const tmp = checkPfxParity(pfx, ar, par, odd, even, isZeroPar)
126,144✔
1104
          if (!_.isNil(tmp)) states.push(tmp)
126,144✔
1105
        }
126,144✔
1106
      }
1,971✔
1107
    }
53✔
1108
    return states
2✔
1109
  }
2✔
1110

1111
  /**
1112
   * Recover the key from the tag with the darkside attack.
1113
   * @param fnAcquire - An async function to acquire the darkside attack data.
1114
   * @param fnCheckKey - An async function to check the key.
1115
   * @param attempts - The maximum number of attempts to try.
1116
   * @returns The recovered key.
1117
   * @example
1118
   * ```js
1119
   * // you can run in DevTools of https://taichunmin.idv.tw/chameleon-ultra.js/test.html
1120
   * await (async ultra => {
1121
   *   const { Buffer, DarksideStatus, DeviceMode, Mf1KeyType } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/+esm')
1122
   *   const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/dist/Crypto1.mjs/+esm')
1123
   *   await ultra.cmdChangeDeviceMode(DeviceMode.READER)
1124
   *   const block = 0
1125
   *   const keyType = Mf1KeyType.KEY_A
1126
   *   const key = await Crypto1.darkside(
1127
   *     async attempt => {
1128
   *       const accquired = await ultra.cmdMf1AcquireDarkside(block, keyType, attempt === 0)
1129
   *       console.log(_.mapValues(accquired, buf => Buffer.isBuffer(buf) ? buf.toString('hex') : buf))
1130
   *       if (acquired.status === DarksideStatus.LUCKY_AUTH_OK) throw new Error('LUCKY_AUTH_OK')
1131
   *       if (acquired.status !== DarksideStatus.OK) throw new Error('card is not vulnerable to Darkside attack')
1132
   *       return accquired
1133
   *     },
1134
   *     async key => {
1135
   *       return await ultra.cmdMf1CheckBlockKey({ block, keyType, key })
1136
   *     },
1137
   *   )
1138
   *   console.log(`key founded: ${key.toString('hex')}`)
1139
   * })(vm.ultra)
1140
   * ```
1141
   */
1142
  static async darkside (
1,326,788✔
1143
    fnAcquire: (attempt: number) => Promise<{ uid: Buffer, nt: Buffer, nr: Buffer, ar: Buffer, par: Buffer, ks: Buffer }>,
1✔
1144
    fnCheckKey: (key: Buffer) => Promise<boolean>,
1✔
1145
    attempts: number = 256,
1✔
1146
  ): Promise<Buffer> {
1✔
1147
    const { bit, lfsrCommonPrefix, toUint32 } = Crypto1
1✔
1148
    const checkedKeys = new Set<number>()
1✔
1149
    let prevKeys = new Set<number>()
1✔
1150
    for (let i = 0; i < attempts; i++) {
1✔
1151
      const acquired = (await fnAcquire(i)) ?? {}
2!
1152

1153
      const [uid, nt, ar] = _.map(['uid', 'nt', 'ar'] as const, k => {
2✔
1154
        if (!Buffer.isBuffer(acquired[k]) || acquired[k].length !== 4) throw new TypeError(`Failed to acquire darkside result: invalid ${k}`)
6!
1155
        return acquired[k].readUInt32BE(0)
6✔
1156
      })
2✔
1157
      if (!Buffer.isBuffer(acquired.nr) || acquired.nr.length !== 4) throw new TypeError('Failed to acquire darkside result: invalid nr')
2!
1158
      const nr = acquired.nr.readUInt32BE(0) & 0xFFFFFF1F
2✔
1159
      if (!Buffer.isBuffer(acquired.ks) || acquired.ks.length !== 8) throw new TypeError('Failed to acquire darkside result: invalid ks')
2!
1160
      const ks = new Buffer(_.map(acquired.ks, u8 => u8 & 0x0F))
2✔
1161
      if (!Buffer.isBuffer(acquired.par) || acquired.par.length !== 8) throw new TypeError('Failed to acquire darkside result: invalid par')
2!
1162
      const par = _.map(acquired.par, u8 => _.times(8, j => bit(u8, j)))
2✔
1163
      const isZeroPar = acquired.par.readBigUInt64BE(0) === 0n
2✔
1164
      let keys = new Set<number>()
2✔
1165
      const tmp = toUint32(uid ^ nt)
2✔
1166

1167
      const states = lfsrCommonPrefix(nr, ar, ks, par, isZeroPar)
2✔
1168
      for (const state of states) {
2✔
1169
        state.lfsrRollbackWord(tmp, 0)
126,144✔
1170
        keys.add(state.getLfsr())
126,144✔
1171
      }
126,144✔
1172
      if (keys.size === 0) continue // no candidates, need to try with a different reader nonce
2!
1173
      if (isZeroPar) { // no parity bits
2✔
1174
        ;[prevKeys, keys] = [keys, prevKeys] // swap
2✔
1175
        keys.forEach(key => { if (!prevKeys.has(key)) keys.delete(key) }) // keys intersection
2✔
1176
      }
2✔
1177
      for (const keyUint32 of [...keys]) {
2✔
1178
        if (checkedKeys.has(keyUint32)) continue
1!
1179
        checkedKeys.add(keyUint32)
1✔
1180
        const key = new Buffer(6).writeUIntBE(keyUint32, 0, 6)
1✔
1181
        if (await fnCheckKey(key)) return key
1✔
1182
      }
1✔
1183
    }
1✔
1184
    throw new Error(`failed to find key, darkside attempts = ${attempts}, keys checked = ${checkedKeys.size}`)
1✔
1185
  }
1✔
1186
}
1,326,788✔
1187

1188
setObject(globalThis, ['Crypto1'], Crypto1)
1✔
1189

1190
/** @inline */
1191
type UInt32Like = Buffer | number | string
1192

1193
/** @inline */
1194
interface RecoverContextUint32Array {
1195
  s: number
1196
  d: Uint32Array
1197
}
1198

1199
enum Mf1KeyType {
1✔
1200
  KEY_A = 0x60,
1✔
1201
  KEY_B = 0x61,
1✔
1202
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc