@bsv/sdk 1.9.24 → 1.9.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/primitives/AESGCM.js +160 -76
- package/dist/cjs/src/primitives/AESGCM.js.map +1 -1
- package/dist/cjs/src/primitives/Point.js +41 -18
- package/dist/cjs/src/primitives/Point.js.map +1 -1
- package/dist/cjs/src/primitives/SymmetricKey.js +20 -19
- package/dist/cjs/src/primitives/SymmetricKey.js.map +1 -1
- package/dist/cjs/src/primitives/hex.js +1 -3
- package/dist/cjs/src/primitives/hex.js.map +1 -1
- package/dist/cjs/src/primitives/utils.js +10 -0
- package/dist/cjs/src/primitives/utils.js.map +1 -1
- package/dist/cjs/src/totp/totp.js +3 -1
- package/dist/cjs/src/totp/totp.js.map +1 -1
- package/dist/cjs/src/wallet/ProtoWallet.js +4 -2
- package/dist/cjs/src/wallet/ProtoWallet.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/primitives/AESGCM.js +158 -75
- package/dist/esm/src/primitives/AESGCM.js.map +1 -1
- package/dist/esm/src/primitives/Point.js +41 -18
- package/dist/esm/src/primitives/Point.js.map +1 -1
- package/dist/esm/src/primitives/SymmetricKey.js +20 -19
- package/dist/esm/src/primitives/SymmetricKey.js.map +1 -1
- package/dist/esm/src/primitives/hex.js +1 -3
- package/dist/esm/src/primitives/hex.js.map +1 -1
- package/dist/esm/src/primitives/utils.js +9 -0
- package/dist/esm/src/primitives/utils.js.map +1 -1
- package/dist/esm/src/totp/totp.js +3 -1
- package/dist/esm/src/totp/totp.js.map +1 -1
- package/dist/esm/src/wallet/ProtoWallet.js +4 -2
- package/dist/esm/src/wallet/ProtoWallet.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/primitives/AESGCM.d.ts +59 -9
- package/dist/types/src/primitives/AESGCM.d.ts.map +1 -1
- package/dist/types/src/primitives/Point.d.ts +1 -0
- package/dist/types/src/primitives/Point.d.ts.map +1 -1
- package/dist/types/src/primitives/SymmetricKey.d.ts.map +1 -1
- package/dist/types/src/primitives/hex.d.ts.map +1 -1
- package/dist/types/src/primitives/utils.d.ts +1 -0
- package/dist/types/src/primitives/utils.d.ts.map +1 -1
- package/dist/types/src/totp/totp.d.ts.map +1 -1
- package/dist/types/src/wallet/ProtoWallet.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +3 -3
- package/dist/umd/bundle.js.map +1 -1
- package/docs/reference/primitives.md +206 -60
- package/package.json +1 -1
- package/src/primitives/AESGCM.ts +225 -103
- package/src/primitives/Point.ts +67 -20
- package/src/primitives/SymmetricKey.ts +28 -20
- package/src/primitives/__tests/AESGCM.test.ts +254 -354
- package/src/primitives/__tests/ECDSA.test.ts +27 -0
- package/src/primitives/__tests/Point.test.ts +52 -0
- package/src/primitives/__tests/utils.test.ts +24 -1
- package/src/primitives/hex.ts +1 -3
- package/src/primitives/utils.ts +10 -0
- package/src/totp/__tests/totp.test.ts +21 -0
- package/src/totp/totp.ts +9 -1
- package/src/wallet/ProtoWallet.ts +8 -3
- package/src/wallet/__tests/ProtoWallet.test.ts +55 -34
package/src/primitives/AESGCM.ts
CHANGED
|
@@ -202,33 +202,73 @@ export const getBytes = function (numericValue: number): number[] {
|
|
|
202
202
|
]
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
const
|
|
206
|
-
|
|
205
|
+
export const getBytes64 = function (numericValue: number): number[] {
|
|
206
|
+
if (numericValue < 0 || numericValue > Number.MAX_SAFE_INTEGER) {
|
|
207
|
+
throw new Error('getBytes64: value out of range')
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const hi = Math.floor(numericValue / 0x100000000)
|
|
211
|
+
const lo = numericValue >>> 0
|
|
212
|
+
|
|
213
|
+
return [
|
|
214
|
+
(hi >>> 24) & 0xFF,
|
|
215
|
+
(hi >>> 16) & 0xFF,
|
|
216
|
+
(hi >>> 8) & 0xFF,
|
|
217
|
+
hi & 0xFF,
|
|
218
|
+
(lo >>> 24) & 0xFF,
|
|
219
|
+
(lo >>> 16) & 0xFF,
|
|
220
|
+
(lo >>> 8) & 0xFF,
|
|
221
|
+
lo & 0xFF
|
|
222
|
+
]
|
|
207
223
|
}
|
|
208
224
|
|
|
209
|
-
|
|
225
|
+
type Bytes = Uint8Array
|
|
210
226
|
|
|
211
|
-
|
|
227
|
+
const createZeroBlock = function (length: number): Bytes {
|
|
228
|
+
// Uint8Array is already zero-filled
|
|
229
|
+
return new Uint8Array(length)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// R = 0xe1 || 15 zero bytes
|
|
233
|
+
const R: Bytes = (() => {
|
|
234
|
+
const r = new Uint8Array(16)
|
|
235
|
+
r[0] = 0xe1
|
|
236
|
+
return r
|
|
237
|
+
})()
|
|
238
|
+
|
|
239
|
+
const concatBytes = (...arrays: Bytes[]): Bytes => {
|
|
240
|
+
let total = 0
|
|
241
|
+
for (const a of arrays) total += a.length
|
|
242
|
+
|
|
243
|
+
const out = new Uint8Array(total)
|
|
244
|
+
let offset = 0
|
|
245
|
+
for (const a of arrays) {
|
|
246
|
+
out.set(a, offset)
|
|
247
|
+
offset += a.length
|
|
248
|
+
}
|
|
249
|
+
return out
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export const exclusiveOR = function (block0: Bytes, block1: Bytes): Bytes {
|
|
212
253
|
const len = block0.length
|
|
213
|
-
const result = new
|
|
254
|
+
const result = new Uint8Array(len)
|
|
214
255
|
for (let i = 0; i < len; i++) {
|
|
215
|
-
result[i] = block0[i] ^ block1[i]
|
|
256
|
+
result[i] = block0[i] ^ (block1[i] ?? 0)
|
|
216
257
|
}
|
|
217
258
|
return result
|
|
218
259
|
}
|
|
219
260
|
|
|
220
|
-
const xorInto = function (target:
|
|
261
|
+
const xorInto = function (target: Bytes, block: Bytes): void {
|
|
221
262
|
for (let i = 0; i < target.length; i++) {
|
|
222
|
-
target[i] ^= block[i]
|
|
263
|
+
target[i] ^= block[i] ?? 0
|
|
223
264
|
}
|
|
224
265
|
}
|
|
225
266
|
|
|
226
|
-
export const rightShift = function (block:
|
|
227
|
-
let i: number
|
|
267
|
+
export const rightShift = function (block: Bytes): Bytes {
|
|
228
268
|
let carry = 0
|
|
229
269
|
let oldCarry = 0
|
|
230
270
|
|
|
231
|
-
for (i = 0; i < block.length; i++) {
|
|
271
|
+
for (let i = 0; i < block.length; i++) {
|
|
232
272
|
oldCarry = carry
|
|
233
273
|
carry = block[i] & 0x01
|
|
234
274
|
block[i] = block[i] >> 1
|
|
@@ -241,7 +281,7 @@ export const rightShift = function (block: number[]): number[] {
|
|
|
241
281
|
return block
|
|
242
282
|
}
|
|
243
283
|
|
|
244
|
-
export const multiply = function (block0:
|
|
284
|
+
export const multiply = function (block0: Bytes, block1: Bytes): Bytes {
|
|
245
285
|
const v = block1.slice()
|
|
246
286
|
const z = createZeroBlock(16)
|
|
247
287
|
|
|
@@ -264,16 +304,14 @@ export const multiply = function (block0: number[], block1: number[]): number[]
|
|
|
264
304
|
}
|
|
265
305
|
|
|
266
306
|
export const incrementLeastSignificantThirtyTwoBits = function (
|
|
267
|
-
block:
|
|
268
|
-
):
|
|
269
|
-
let i
|
|
307
|
+
block: Bytes
|
|
308
|
+
): Bytes {
|
|
270
309
|
const result = block.slice()
|
|
271
|
-
for (i = 15; i !== 11; i--) {
|
|
272
|
-
result[i] = result[i] + 1
|
|
273
310
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
311
|
+
for (let i = 15; i > 11; i--) {
|
|
312
|
+
result[i] = (result[i] + 1) & 0xff // wrap explicitly
|
|
313
|
+
|
|
314
|
+
if (result[i] !== 0) {
|
|
277
315
|
break
|
|
278
316
|
}
|
|
279
317
|
}
|
|
@@ -281,11 +319,12 @@ export const incrementLeastSignificantThirtyTwoBits = function (
|
|
|
281
319
|
return result
|
|
282
320
|
}
|
|
283
321
|
|
|
284
|
-
export function ghash (input:
|
|
322
|
+
export function ghash (input: Bytes, hashSubKey: Bytes): Bytes {
|
|
285
323
|
let result = createZeroBlock(16)
|
|
324
|
+
const block = new Uint8Array(16)
|
|
286
325
|
|
|
287
326
|
for (let i = 0; i < input.length; i += 16) {
|
|
288
|
-
|
|
327
|
+
block.set(result)
|
|
289
328
|
for (let j = 0; j < 16; j++) {
|
|
290
329
|
block[j] ^= input[i + j] ?? 0
|
|
291
330
|
}
|
|
@@ -296,14 +335,14 @@ export function ghash (input: number[], hashSubKey: number[]): number[] {
|
|
|
296
335
|
}
|
|
297
336
|
|
|
298
337
|
function gctr (
|
|
299
|
-
input:
|
|
300
|
-
initialCounterBlock:
|
|
301
|
-
key:
|
|
302
|
-
):
|
|
303
|
-
if (input.length === 0) return
|
|
304
|
-
|
|
305
|
-
const output = new
|
|
306
|
-
let counterBlock = initialCounterBlock
|
|
338
|
+
input: Bytes,
|
|
339
|
+
initialCounterBlock: Bytes,
|
|
340
|
+
key: Bytes
|
|
341
|
+
): Bytes {
|
|
342
|
+
if (input.length === 0) return new Uint8Array(0)
|
|
343
|
+
|
|
344
|
+
const output = new Uint8Array(input.length)
|
|
345
|
+
let counterBlock = initialCounterBlock.slice()
|
|
307
346
|
let pos = 0
|
|
308
347
|
const n = Math.ceil(input.length / 16)
|
|
309
348
|
|
|
@@ -323,12 +362,97 @@ function gctr (
|
|
|
323
362
|
return output
|
|
324
363
|
}
|
|
325
364
|
|
|
365
|
+
function buildAuthInput (cipherText: Bytes): Bytes {
|
|
366
|
+
const aadLenBits = 0
|
|
367
|
+
const ctLenBits = cipherText.length * 8
|
|
368
|
+
|
|
369
|
+
let padLen: number
|
|
370
|
+
if (cipherText.length === 0) {
|
|
371
|
+
padLen = 16
|
|
372
|
+
} else if (cipherText.length % 16 === 0) {
|
|
373
|
+
padLen = 0
|
|
374
|
+
} else {
|
|
375
|
+
padLen = 16 - (cipherText.length % 16)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const total =
|
|
379
|
+
16 +
|
|
380
|
+
cipherText.length +
|
|
381
|
+
padLen +
|
|
382
|
+
16
|
|
383
|
+
|
|
384
|
+
const out = new Uint8Array(total)
|
|
385
|
+
let offset = 0
|
|
386
|
+
|
|
387
|
+
offset += 16
|
|
388
|
+
|
|
389
|
+
out.set(cipherText, offset)
|
|
390
|
+
offset += cipherText.length
|
|
391
|
+
|
|
392
|
+
offset += padLen
|
|
393
|
+
|
|
394
|
+
const aadLen = getBytes64(aadLenBits)
|
|
395
|
+
out.set(aadLen, offset)
|
|
396
|
+
offset += 8
|
|
397
|
+
|
|
398
|
+
const ctLen = getBytes64(ctLenBits)
|
|
399
|
+
out.set(ctLen, offset)
|
|
400
|
+
|
|
401
|
+
return out
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* SECURITY NOTE – NON-STANDARD AES-GCM PADDING
|
|
406
|
+
*
|
|
407
|
+
* This implementation intentionally deviates from NIST SP 800-38D’s AES-GCM
|
|
408
|
+
* specification in how the GHASH input is formed when the additional
|
|
409
|
+
* authenticated data (AAD) or ciphertext length is zero.
|
|
410
|
+
*
|
|
411
|
+
* In the standard, AAD and ciphertext are each padded with the minimum number
|
|
412
|
+
* of zero bytes required to reach a multiple of 16 bytes; when the length is
|
|
413
|
+
* already a multiple of 16 (including the case length = 0), no padding block
|
|
414
|
+
* is added. In this implementation, when AAD.length === 0 or ciphertext.length
|
|
415
|
+
* === 0, an extra 16-byte block of zeros is appended before the length fields
|
|
416
|
+
* are processed. The same formatting logic is used symmetrically in both
|
|
417
|
+
* AESGCM (encryption) and AESGCMDecrypt (decryption).
|
|
418
|
+
*
|
|
419
|
+
* As a result:
|
|
420
|
+
* - Authentication tags produced here are NOT compatible with tags produced
|
|
421
|
+
* by standards-compliant AES-GCM implementations in the cases where AAD
|
|
422
|
+
* or ciphertext are empty.
|
|
423
|
+
* - Ciphertexts generated by this code must be decrypted by this exact
|
|
424
|
+
* implementation (or one that reproduces the same GHASH formatting), and
|
|
425
|
+
* must not be mixed with ciphertexts produced by a strictly standard
|
|
426
|
+
* AES-GCM library.
|
|
427
|
+
*
|
|
428
|
+
* Cryptographic impact: this change alters only the encoding of the message
|
|
429
|
+
* that is input to GHASH; it does not change the block cipher, key derivation,
|
|
430
|
+
* IV handling, or the basic “encrypt-then-MAC over (AAD, ciphertext, lengths)”
|
|
431
|
+
* structure of AES-GCM. Under the usual assumptions that AES is a secure block
|
|
432
|
+
* cipher and GHASH with a secret subkey is a secure polynomial MAC, this
|
|
433
|
+
* variant continues to provide confidentiality and integrity for data encrypted
|
|
434
|
+
* and decrypted consistently with this implementation. We are not aware of any
|
|
435
|
+
* attack that exploits the presence of this extra zero block when AAD or
|
|
436
|
+
* ciphertext are empty.
|
|
437
|
+
*
|
|
438
|
+
* However, this padding behavior is non-compliant with NIST SP 800-38D and has
|
|
439
|
+
* not been analyzed as extensively as standard AES-GCM. Code that requires
|
|
440
|
+
* strict standards compliance or interoperability with external AES-GCM
|
|
441
|
+
* implementations SHOULD NOT use this module as-is. Any future migration to a
|
|
442
|
+
* fully compliant AES-GCM encoding will require a compatibility strategy, as
|
|
443
|
+
* existing ciphertexts produced by this implementation will otherwise become
|
|
444
|
+
* undecryptable.
|
|
445
|
+
*
|
|
446
|
+
* This non-standard padding behavior is retained intentionally for backward
|
|
447
|
+
* compatibility: existing ciphertexts in production were generated with this
|
|
448
|
+
* encoding, and changing it would render previously encrypted data
|
|
449
|
+
* undecryptable by newer versions of the library.
|
|
450
|
+
*/
|
|
326
451
|
export function AESGCM (
|
|
327
|
-
plainText:
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
): { result: number[], authenticationTag: number[] } {
|
|
452
|
+
plainText: Bytes,
|
|
453
|
+
initializationVector: Bytes,
|
|
454
|
+
key: Bytes
|
|
455
|
+
): { result: Bytes, authenticationTag: Bytes } {
|
|
332
456
|
if (initializationVector.length === 0) {
|
|
333
457
|
throw new Error('Initialization vector must not be empty')
|
|
334
458
|
}
|
|
@@ -337,60 +461,54 @@ export function AESGCM (
|
|
|
337
461
|
throw new Error('Key must not be empty')
|
|
338
462
|
}
|
|
339
463
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
464
|
+
const hashSubKey = new Uint8Array(AES(createZeroBlock(16), key))
|
|
465
|
+
|
|
466
|
+
let preCounterBlock: Bytes
|
|
467
|
+
|
|
344
468
|
if (initializationVector.length === 12) {
|
|
345
|
-
preCounterBlock =
|
|
469
|
+
preCounterBlock = concatBytes(initializationVector, createZeroBlock(3), new Uint8Array([0x01]))
|
|
346
470
|
} else {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
471
|
+
let ivPadded = initializationVector
|
|
472
|
+
if (ivPadded.length % 16 !== 0) {
|
|
473
|
+
ivPadded = concatBytes(
|
|
474
|
+
ivPadded,
|
|
475
|
+
createZeroBlock(16 - (ivPadded.length % 16))
|
|
350
476
|
)
|
|
351
477
|
}
|
|
352
478
|
|
|
353
|
-
|
|
479
|
+
const lenBlock = getBytes64(initializationVector.length * 8)
|
|
480
|
+
const s = concatBytes(
|
|
481
|
+
ivPadded,
|
|
482
|
+
createZeroBlock(8),
|
|
483
|
+
new Uint8Array(lenBlock)
|
|
484
|
+
)
|
|
354
485
|
|
|
355
|
-
preCounterBlock = ghash(
|
|
356
|
-
.concat(getBytes(initializationVector.length * 8)), hashSubKey)
|
|
486
|
+
preCounterBlock = ghash(s, hashSubKey)
|
|
357
487
|
}
|
|
358
488
|
|
|
359
|
-
const cipherText = gctr(
|
|
360
|
-
|
|
361
|
-
|
|
489
|
+
const cipherText = gctr(
|
|
490
|
+
plainText,
|
|
491
|
+
incrementLeastSignificantThirtyTwoBits(preCounterBlock),
|
|
492
|
+
key
|
|
493
|
+
)
|
|
362
494
|
|
|
363
|
-
|
|
364
|
-
plainTag = plainTag.concat(createZeroBlock(16))
|
|
365
|
-
} else if (additionalAuthenticatedData.length % 16 !== 0) {
|
|
366
|
-
plainTag = plainTag.concat(createZeroBlock(16 - (additionalAuthenticatedData.length % 16)))
|
|
367
|
-
}
|
|
495
|
+
const authInput = buildAuthInput(cipherText)
|
|
368
496
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if (cipherText.length === 0) {
|
|
372
|
-
plainTag = plainTag.concat(createZeroBlock(16))
|
|
373
|
-
} else if (cipherText.length % 16 !== 0) {
|
|
374
|
-
plainTag = plainTag.concat(createZeroBlock(16 - (cipherText.length % 16)))
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
plainTag = plainTag.concat(createZeroBlock(4))
|
|
378
|
-
.concat(getBytes(additionalAuthenticatedData.length * 8))
|
|
379
|
-
.concat(createZeroBlock(4)).concat(getBytes(cipherText.length * 8))
|
|
497
|
+
const s = ghash(authInput, hashSubKey)
|
|
498
|
+
const authenticationTag = gctr(s, preCounterBlock, key)
|
|
380
499
|
|
|
381
500
|
return {
|
|
382
501
|
result: cipherText,
|
|
383
|
-
authenticationTag
|
|
502
|
+
authenticationTag
|
|
384
503
|
}
|
|
385
504
|
}
|
|
386
505
|
|
|
387
506
|
export function AESGCMDecrypt (
|
|
388
|
-
cipherText:
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
): number[] | null {
|
|
507
|
+
cipherText: Bytes,
|
|
508
|
+
initializationVector: Bytes,
|
|
509
|
+
authenticationTag: Bytes,
|
|
510
|
+
key: Bytes
|
|
511
|
+
): Bytes | null {
|
|
394
512
|
if (cipherText.length === 0) {
|
|
395
513
|
throw new Error('Cipher text must not be empty')
|
|
396
514
|
}
|
|
@@ -403,53 +521,57 @@ export function AESGCMDecrypt (
|
|
|
403
521
|
throw new Error('Key must not be empty')
|
|
404
522
|
}
|
|
405
523
|
|
|
406
|
-
let preCounterBlock
|
|
407
|
-
let compareTag
|
|
408
|
-
|
|
409
524
|
// Generate the hash subkey
|
|
410
|
-
const hashSubKey = AES(createZeroBlock(16), key)
|
|
525
|
+
const hashSubKey = new Uint8Array(AES(createZeroBlock(16), key))
|
|
526
|
+
|
|
527
|
+
let preCounterBlock: Bytes
|
|
411
528
|
|
|
412
|
-
preCounterBlock = [...initializationVector]
|
|
413
529
|
if (initializationVector.length === 12) {
|
|
414
|
-
preCounterBlock =
|
|
530
|
+
preCounterBlock = concatBytes(
|
|
531
|
+
initializationVector,
|
|
532
|
+
createZeroBlock(3),
|
|
533
|
+
new Uint8Array([0x01])
|
|
534
|
+
)
|
|
415
535
|
} else {
|
|
416
|
-
|
|
417
|
-
|
|
536
|
+
let ivPadded = initializationVector
|
|
537
|
+
if (ivPadded.length % 16 !== 0) {
|
|
538
|
+
ivPadded = concatBytes(
|
|
539
|
+
ivPadded,
|
|
540
|
+
createZeroBlock(16 - (ivPadded.length % 16))
|
|
541
|
+
)
|
|
418
542
|
}
|
|
419
543
|
|
|
420
|
-
|
|
544
|
+
const lenBlock = getBytes64(initializationVector.length * 8)
|
|
545
|
+
const s = concatBytes(
|
|
546
|
+
ivPadded,
|
|
547
|
+
createZeroBlock(8),
|
|
548
|
+
new Uint8Array(lenBlock)
|
|
549
|
+
)
|
|
421
550
|
|
|
422
|
-
preCounterBlock = ghash(
|
|
551
|
+
preCounterBlock = ghash(s, hashSubKey)
|
|
423
552
|
}
|
|
424
553
|
|
|
425
554
|
// Decrypt to obtain the plain text
|
|
426
|
-
const plainText = gctr(
|
|
555
|
+
const plainText = gctr(
|
|
556
|
+
cipherText,
|
|
557
|
+
incrementLeastSignificantThirtyTwoBits(preCounterBlock),
|
|
558
|
+
key
|
|
559
|
+
)
|
|
427
560
|
|
|
428
|
-
|
|
561
|
+
const authInput = buildAuthInput(cipherText)
|
|
562
|
+
const s = ghash(authInput, hashSubKey)
|
|
563
|
+
const calculatedTag = gctr(s, preCounterBlock, key)
|
|
429
564
|
|
|
430
|
-
if (
|
|
431
|
-
|
|
432
|
-
} else if (additionalAuthenticatedData.length % 16 !== 0) {
|
|
433
|
-
compareTag = compareTag.concat(createZeroBlock(16 - (additionalAuthenticatedData.length % 16)))
|
|
565
|
+
if (calculatedTag.length !== authenticationTag.length) {
|
|
566
|
+
return null
|
|
434
567
|
}
|
|
435
568
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
compareTag = compareTag.concat(createZeroBlock(16))
|
|
440
|
-
} else if (cipherText.length % 16 !== 0) {
|
|
441
|
-
compareTag = compareTag.concat(createZeroBlock(16 - (cipherText.length % 16)))
|
|
569
|
+
let diff = 0
|
|
570
|
+
for (let i = 0; i < calculatedTag.length; i++) {
|
|
571
|
+
diff |= calculatedTag[i] ^ authenticationTag[i]
|
|
442
572
|
}
|
|
443
573
|
|
|
444
|
-
|
|
445
|
-
.concat(getBytes(additionalAuthenticatedData.length * 8))
|
|
446
|
-
.concat(createZeroBlock(4)).concat(getBytes(cipherText.length * 8))
|
|
447
|
-
|
|
448
|
-
// Generate the authentication tag
|
|
449
|
-
const calculatedTag = gctr(ghash(compareTag, hashSubKey), preCounterBlock, key)
|
|
450
|
-
|
|
451
|
-
// If the calculated tag does not match the provided tag, return null - the decryption failed.
|
|
452
|
-
if (calculatedTag.join() !== authenticationTag.join()) {
|
|
574
|
+
if (diff !== 0) {
|
|
453
575
|
return null
|
|
454
576
|
}
|
|
455
577
|
|
package/src/primitives/Point.ts
CHANGED
|
@@ -44,14 +44,17 @@ export const biModInv = (a: bigint): bigint => { // binary‑ext GCD
|
|
|
44
44
|
export const biModSqr = (a: bigint): bigint => biModMul(a, a)
|
|
45
45
|
|
|
46
46
|
export const biModPow = (base: bigint, exp: bigint): bigint => {
|
|
47
|
-
let result =
|
|
47
|
+
let result = 1n
|
|
48
48
|
base = biMod(base)
|
|
49
|
-
|
|
50
|
-
while (
|
|
51
|
-
if ((
|
|
49
|
+
|
|
50
|
+
while (exp > 0n) {
|
|
51
|
+
if ((exp & 1n) !== 0n) {
|
|
52
|
+
result = biModMul(result, base)
|
|
53
|
+
}
|
|
52
54
|
base = biModMul(base, base)
|
|
53
|
-
|
|
55
|
+
exp >>= 1n
|
|
54
56
|
}
|
|
57
|
+
|
|
55
58
|
return result
|
|
56
59
|
}
|
|
57
60
|
|
|
@@ -59,7 +62,12 @@ export const P_PLUS1_DIV4 = (P_BIGINT + 1n) >> 2n
|
|
|
59
62
|
|
|
60
63
|
export const biModSqrt = (a: bigint): bigint | null => {
|
|
61
64
|
const r = biModPow(a, P_PLUS1_DIV4)
|
|
62
|
-
|
|
65
|
+
|
|
66
|
+
if (biModMul(r, r) !== biMod(a)) {
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return r
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
const toBigInt = (x: BigNumber | number | number[] | string): bigint => {
|
|
@@ -220,6 +228,13 @@ export default class Point extends BasePoint {
|
|
|
220
228
|
y: BigNumber | null
|
|
221
229
|
inf: boolean
|
|
222
230
|
|
|
231
|
+
static _assertOnCurve (p: Point): Point {
|
|
232
|
+
if (!p.validate()) {
|
|
233
|
+
throw new Error('Invalid point')
|
|
234
|
+
}
|
|
235
|
+
return p
|
|
236
|
+
}
|
|
237
|
+
|
|
223
238
|
/**
|
|
224
239
|
* Creates a point object from a given Array. These numbers can represent coordinates in hex format, or points
|
|
225
240
|
* in multiple established formats.
|
|
@@ -238,7 +253,6 @@ export default class Point extends BasePoint {
|
|
|
238
253
|
*/
|
|
239
254
|
static fromDER (bytes: number[]): Point {
|
|
240
255
|
const len = 32
|
|
241
|
-
// uncompressed, hybrid-odd, hybrid-even
|
|
242
256
|
if (
|
|
243
257
|
(bytes[0] === 0x04 || bytes[0] === 0x06 || bytes[0] === 0x07) &&
|
|
244
258
|
bytes.length - 1 === 2 * len
|
|
@@ -258,12 +272,14 @@ export default class Point extends BasePoint {
|
|
|
258
272
|
bytes.slice(1 + len, 1 + 2 * len)
|
|
259
273
|
)
|
|
260
274
|
|
|
261
|
-
return res
|
|
275
|
+
return Point._assertOnCurve(res)
|
|
262
276
|
} else if (
|
|
263
277
|
(bytes[0] === 0x02 || bytes[0] === 0x03) &&
|
|
264
278
|
bytes.length - 1 === len
|
|
265
279
|
) {
|
|
266
|
-
return Point.
|
|
280
|
+
return Point._assertOnCurve(
|
|
281
|
+
Point.fromX(bytes.slice(1, 1 + len), bytes[0] === 0x03)
|
|
282
|
+
)
|
|
267
283
|
}
|
|
268
284
|
throw new Error('Unknown point format')
|
|
269
285
|
}
|
|
@@ -287,7 +303,7 @@ export default class Point extends BasePoint {
|
|
|
287
303
|
*/
|
|
288
304
|
static fromString (str: string): Point {
|
|
289
305
|
const bytes = toArray(str, 'hex')
|
|
290
|
-
return Point.fromDER(bytes)
|
|
306
|
+
return Point._assertOnCurve(Point.fromDER(bytes))
|
|
291
307
|
}
|
|
292
308
|
|
|
293
309
|
/**
|
|
@@ -308,16 +324,22 @@ export default class Point extends BasePoint {
|
|
|
308
324
|
static fromX (x: BigNumber | number | number[] | string, odd: boolean): Point {
|
|
309
325
|
let xBigInt = toBigInt(x)
|
|
310
326
|
xBigInt = biMod(xBigInt)
|
|
327
|
+
|
|
311
328
|
const y2 = biModAdd(biModMul(biModSqr(xBigInt), xBigInt), 7n)
|
|
312
329
|
const y = biModSqrt(y2)
|
|
313
|
-
if (y === null)
|
|
330
|
+
if (y === null) {
|
|
331
|
+
throw new Error('Invalid point')
|
|
332
|
+
}
|
|
333
|
+
|
|
314
334
|
let yBig = y
|
|
315
335
|
if ((yBig & BI_ONE) !== (odd ? BI_ONE : BI_ZERO)) {
|
|
316
336
|
yBig = biModSub(P_BIGINT, yBig)
|
|
317
337
|
}
|
|
338
|
+
|
|
318
339
|
const xBN = new BigNumber(xBigInt.toString(16), 16)
|
|
319
340
|
const yBN = new BigNumber(yBig.toString(16), 16)
|
|
320
|
-
|
|
341
|
+
|
|
342
|
+
return Point._assertOnCurve(new Point(xBN, yBN))
|
|
321
343
|
}
|
|
322
344
|
|
|
323
345
|
/**
|
|
@@ -339,33 +361,45 @@ export default class Point extends BasePoint {
|
|
|
339
361
|
if (typeof obj === 'string') {
|
|
340
362
|
obj = JSON.parse(obj)
|
|
341
363
|
}
|
|
342
|
-
|
|
343
|
-
|
|
364
|
+
|
|
365
|
+
let res = new Point(obj[0], obj[1], isRed)
|
|
366
|
+
res = Point._assertOnCurve(res)
|
|
367
|
+
|
|
368
|
+
if (typeof obj[2] !== 'object' || obj[2] === null) {
|
|
344
369
|
return res
|
|
345
370
|
}
|
|
346
371
|
|
|
347
|
-
const
|
|
348
|
-
|
|
372
|
+
const pre = obj[2]
|
|
373
|
+
|
|
374
|
+
const obj2point = (p): Point => {
|
|
375
|
+
const pt = new Point(p[0], p[1], isRed)
|
|
376
|
+
return Point._assertOnCurve(pt)
|
|
349
377
|
}
|
|
350
378
|
|
|
351
|
-
const pre = obj[2]
|
|
352
379
|
res.precomputed = {
|
|
353
380
|
beta: null,
|
|
381
|
+
|
|
354
382
|
doubles:
|
|
355
383
|
typeof pre.doubles === 'object' && pre.doubles !== null
|
|
356
384
|
? {
|
|
357
385
|
step: pre.doubles.step,
|
|
358
|
-
points: [res].concat(
|
|
386
|
+
points: [res].concat(
|
|
387
|
+
pre.doubles.points.map(obj2point)
|
|
388
|
+
)
|
|
359
389
|
}
|
|
360
390
|
: undefined,
|
|
391
|
+
|
|
361
392
|
naf:
|
|
362
393
|
typeof pre.naf === 'object' && pre.naf !== null
|
|
363
394
|
? {
|
|
364
395
|
wnd: pre.naf.wnd,
|
|
365
|
-
points: [res].concat(
|
|
396
|
+
points: [res].concat(
|
|
397
|
+
pre.naf.points.map(obj2point)
|
|
398
|
+
)
|
|
366
399
|
}
|
|
367
400
|
: undefined
|
|
368
401
|
}
|
|
402
|
+
|
|
369
403
|
return res
|
|
370
404
|
}
|
|
371
405
|
|
|
@@ -426,7 +460,20 @@ export default class Point extends BasePoint {
|
|
|
426
460
|
* const isValid = aPoint.validate();
|
|
427
461
|
*/
|
|
428
462
|
validate (): boolean {
|
|
429
|
-
|
|
463
|
+
if (this.inf || this.x == null || this.y == null) return false
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
const xBig = BigInt('0x' + this.x.fromRed().toString(16))
|
|
467
|
+
const yBig = BigInt('0x' + this.y.fromRed().toString(16))
|
|
468
|
+
|
|
469
|
+
// compute y² and x³ + 7 using bigint-secure field ops
|
|
470
|
+
const lhs = biModMul(yBig, yBig)
|
|
471
|
+
const rhs = biModAdd(biModMul(biModMul(xBig, xBig), xBig), 7n)
|
|
472
|
+
|
|
473
|
+
return lhs === rhs
|
|
474
|
+
} catch {
|
|
475
|
+
return false
|
|
476
|
+
}
|
|
430
477
|
}
|
|
431
478
|
|
|
432
479
|
/**
|