@bsv/sdk 1.9.24 → 1.9.30

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.
Files changed (75) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/primitives/AESGCM.js +160 -76
  3. package/dist/cjs/src/primitives/AESGCM.js.map +1 -1
  4. package/dist/cjs/src/primitives/ECDSA.js +22 -23
  5. package/dist/cjs/src/primitives/ECDSA.js.map +1 -1
  6. package/dist/cjs/src/primitives/Point.js +102 -22
  7. package/dist/cjs/src/primitives/Point.js.map +1 -1
  8. package/dist/cjs/src/primitives/PrivateKey.js +2 -2
  9. package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
  10. package/dist/cjs/src/primitives/PublicKey.js +1 -1
  11. package/dist/cjs/src/primitives/PublicKey.js.map +1 -1
  12. package/dist/cjs/src/primitives/SymmetricKey.js +20 -19
  13. package/dist/cjs/src/primitives/SymmetricKey.js.map +1 -1
  14. package/dist/cjs/src/primitives/hex.js +1 -3
  15. package/dist/cjs/src/primitives/hex.js.map +1 -1
  16. package/dist/cjs/src/primitives/utils.js +10 -0
  17. package/dist/cjs/src/primitives/utils.js.map +1 -1
  18. package/dist/cjs/src/totp/totp.js +3 -1
  19. package/dist/cjs/src/totp/totp.js.map +1 -1
  20. package/dist/cjs/src/wallet/ProtoWallet.js +4 -2
  21. package/dist/cjs/src/wallet/ProtoWallet.js.map +1 -1
  22. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  23. package/dist/esm/src/primitives/AESGCM.js +158 -75
  24. package/dist/esm/src/primitives/AESGCM.js.map +1 -1
  25. package/dist/esm/src/primitives/ECDSA.js +22 -23
  26. package/dist/esm/src/primitives/ECDSA.js.map +1 -1
  27. package/dist/esm/src/primitives/Point.js +102 -22
  28. package/dist/esm/src/primitives/Point.js.map +1 -1
  29. package/dist/esm/src/primitives/PrivateKey.js +2 -2
  30. package/dist/esm/src/primitives/PrivateKey.js.map +1 -1
  31. package/dist/esm/src/primitives/PublicKey.js +1 -1
  32. package/dist/esm/src/primitives/PublicKey.js.map +1 -1
  33. package/dist/esm/src/primitives/SymmetricKey.js +20 -19
  34. package/dist/esm/src/primitives/SymmetricKey.js.map +1 -1
  35. package/dist/esm/src/primitives/hex.js +1 -3
  36. package/dist/esm/src/primitives/hex.js.map +1 -1
  37. package/dist/esm/src/primitives/utils.js +9 -0
  38. package/dist/esm/src/primitives/utils.js.map +1 -1
  39. package/dist/esm/src/totp/totp.js +3 -1
  40. package/dist/esm/src/totp/totp.js.map +1 -1
  41. package/dist/esm/src/wallet/ProtoWallet.js +4 -2
  42. package/dist/esm/src/wallet/ProtoWallet.js.map +1 -1
  43. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  44. package/dist/types/src/primitives/AESGCM.d.ts +59 -9
  45. package/dist/types/src/primitives/AESGCM.d.ts.map +1 -1
  46. package/dist/types/src/primitives/ECDSA.d.ts.map +1 -1
  47. package/dist/types/src/primitives/Point.d.ts +2 -0
  48. package/dist/types/src/primitives/Point.d.ts.map +1 -1
  49. package/dist/types/src/primitives/SymmetricKey.d.ts.map +1 -1
  50. package/dist/types/src/primitives/hex.d.ts.map +1 -1
  51. package/dist/types/src/primitives/utils.d.ts +1 -0
  52. package/dist/types/src/primitives/utils.d.ts.map +1 -1
  53. package/dist/types/src/totp/totp.d.ts.map +1 -1
  54. package/dist/types/src/wallet/ProtoWallet.d.ts.map +1 -1
  55. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  56. package/dist/umd/bundle.js +3 -3
  57. package/dist/umd/bundle.js.map +1 -1
  58. package/docs/reference/primitives.md +206 -60
  59. package/package.json +1 -1
  60. package/src/primitives/AESGCM.ts +225 -103
  61. package/src/primitives/ECDSA.ts +25 -23
  62. package/src/primitives/Point.ts +142 -23
  63. package/src/primitives/PrivateKey.ts +2 -2
  64. package/src/primitives/PublicKey.ts +1 -1
  65. package/src/primitives/SymmetricKey.ts +28 -20
  66. package/src/primitives/__tests/AESGCM.test.ts +254 -354
  67. package/src/primitives/__tests/ECDSA.test.ts +39 -0
  68. package/src/primitives/__tests/Point.test.ts +112 -0
  69. package/src/primitives/__tests/utils.test.ts +24 -1
  70. package/src/primitives/hex.ts +1 -3
  71. package/src/primitives/utils.ts +10 -0
  72. package/src/totp/__tests/totp.test.ts +21 -0
  73. package/src/totp/totp.ts +9 -1
  74. package/src/wallet/ProtoWallet.ts +8 -3
  75. package/src/wallet/__tests/ProtoWallet.test.ts +55 -34
@@ -202,33 +202,73 @@ export const getBytes = function (numericValue: number): number[] {
202
202
  ]
203
203
  }
204
204
 
205
- const createZeroBlock = function (length: number): number[] {
206
- return new Array(length).fill(0)
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
- const R = [0xe1].concat(createZeroBlock(15))
225
+ type Bytes = Uint8Array
210
226
 
211
- export const exclusiveOR = function (block0: number[], block1: number[]): number[] {
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 Array(len)
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: number[], block: number[]): void {
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: number[]): number[] {
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: number[], block1: number[]): number[] {
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: number[]
268
- ): number[] {
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
- if (result[i] === 256) {
275
- result[i] = 0
276
- } else {
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: number[], hashSubKey: number[]): number[] {
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
- const block = result.slice()
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: number[],
300
- initialCounterBlock: number[],
301
- key: number[]
302
- ): number[] {
303
- if (input.length === 0) return []
304
-
305
- const output = new Array(input.length)
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: number[],
328
- additionalAuthenticatedData: number[],
329
- initializationVector: number[],
330
- key: number[]
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
- let preCounterBlock
341
- let plainTag
342
- const hashSubKey = AES(createZeroBlock(16), key)
343
- preCounterBlock = [...initializationVector]
464
+ const hashSubKey = new Uint8Array(AES(createZeroBlock(16), key))
465
+
466
+ let preCounterBlock: Bytes
467
+
344
468
  if (initializationVector.length === 12) {
345
- preCounterBlock = preCounterBlock.concat(createZeroBlock(3)).concat([0x01])
469
+ preCounterBlock = concatBytes(initializationVector, createZeroBlock(3), new Uint8Array([0x01]))
346
470
  } else {
347
- if (initializationVector.length % 16 !== 0) {
348
- preCounterBlock = preCounterBlock.concat(
349
- createZeroBlock(16 - (initializationVector.length % 16))
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
- preCounterBlock = preCounterBlock.concat(createZeroBlock(8))
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(preCounterBlock.concat(createZeroBlock(4))
356
- .concat(getBytes(initializationVector.length * 8)), hashSubKey)
486
+ preCounterBlock = ghash(s, hashSubKey)
357
487
  }
358
488
 
359
- const cipherText = gctr(plainText, incrementLeastSignificantThirtyTwoBits(preCounterBlock), key)
360
-
361
- plainTag = additionalAuthenticatedData.slice()
489
+ const cipherText = gctr(
490
+ plainText,
491
+ incrementLeastSignificantThirtyTwoBits(preCounterBlock),
492
+ key
493
+ )
362
494
 
363
- if (additionalAuthenticatedData.length === 0) {
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
- plainTag = plainTag.concat(cipherText)
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: gctr(ghash(plainTag, hashSubKey), preCounterBlock, key)
502
+ authenticationTag
384
503
  }
385
504
  }
386
505
 
387
506
  export function AESGCMDecrypt (
388
- cipherText: number[],
389
- additionalAuthenticatedData: number[],
390
- initializationVector: number[],
391
- authenticationTag: number[],
392
- key: number[]
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 = preCounterBlock.concat(createZeroBlock(3)).concat([0x01])
530
+ preCounterBlock = concatBytes(
531
+ initializationVector,
532
+ createZeroBlock(3),
533
+ new Uint8Array([0x01])
534
+ )
415
535
  } else {
416
- if (initializationVector.length % 16 !== 0) {
417
- preCounterBlock = preCounterBlock.concat(createZeroBlock(16 - (initializationVector.length % 16)))
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
- preCounterBlock = preCounterBlock.concat(createZeroBlock(8))
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(preCounterBlock.concat(createZeroBlock(4)).concat(getBytes(initializationVector.length * 8)), hashSubKey)
551
+ preCounterBlock = ghash(s, hashSubKey)
423
552
  }
424
553
 
425
554
  // Decrypt to obtain the plain text
426
- const plainText = gctr(cipherText, incrementLeastSignificantThirtyTwoBits(preCounterBlock), key)
555
+ const plainText = gctr(
556
+ cipherText,
557
+ incrementLeastSignificantThirtyTwoBits(preCounterBlock),
558
+ key
559
+ )
427
560
 
428
- compareTag = additionalAuthenticatedData.slice()
561
+ const authInput = buildAuthInput(cipherText)
562
+ const s = ghash(authInput, hashSubKey)
563
+ const calculatedTag = gctr(s, preCounterBlock, key)
429
564
 
430
- if (additionalAuthenticatedData.length === 0) {
431
- compareTag = compareTag.concat(createZeroBlock(16))
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
- compareTag = compareTag.concat(cipherText)
437
-
438
- if (cipherText.length === 0) {
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
- compareTag = compareTag.concat(createZeroBlock(4))
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
 
@@ -39,6 +39,15 @@ function truncateToN (
39
39
  }
40
40
  }
41
41
 
42
+ function bnToBigInt (bn: BigNumber): bigint {
43
+ const bytes = bn.toArray('be')
44
+ let x = 0n
45
+ for (let i = 0; i < bytes.length; i++) {
46
+ x = (x << 8n) | BigInt(bytes[i])
47
+ }
48
+ return x
49
+ }
50
+
42
51
  const curve = new Curve()
43
52
  const bytes = curve.n.byteLength()
44
53
  const ns1 = curve.n.subn(1)
@@ -65,18 +74,15 @@ export const sign = (
65
74
  forceLowS: boolean = false,
66
75
  customK?: BigNumber | ((iter: number) => BigNumber)
67
76
  ): Signature => {
68
- // —— prepare inputs ────────────────────────────────────────────────────────
69
77
  msg = truncateToN(msg)
70
- const msgBig = BigInt('0x' + msg.toString(16))
71
- const keyBig = BigInt('0x' + key.toString(16))
78
+ const msgBig = bnToBigInt(msg)
79
+ const keyBig = bnToBigInt(key)
72
80
 
73
- // DRBG seeding identical to previous implementation
74
81
  const bkey = key.toArray('be', bytes)
75
82
  const nonce = msg.toArray('be', bytes)
76
83
  const drbg = new DRBG(bkey, nonce)
77
84
 
78
85
  for (let iter = 0; ; iter++) {
79
- // —— k generation & basic validity checks ───────────────────────────────
80
86
  let kBN =
81
87
  typeof customK === 'function'
82
88
  ? customK(iter)
@@ -84,31 +90,29 @@ export const sign = (
84
90
  ? customK
85
91
  : new BigNumber(drbg.generate(bytes), 16)
86
92
 
87
- if (kBN == null) throw new Error('k is undefined')
93
+ if (kBN == null) {
94
+ throw new Error('k is undefined')
95
+ }
96
+
88
97
  kBN = truncateToN(kBN, true)
89
98
 
90
99
  if (kBN.cmpn(1) < 0 || kBN.cmp(ns1) > 0) {
91
100
  if (BigNumber.isBN(customK)) {
92
- throw new Error('Invalid fixed custom K value (must be >1 and <N1)')
101
+ throw new Error('Invalid fixed custom K value (must be >1 and <N-1)')
93
102
  }
94
103
  continue
95
104
  }
96
105
 
97
- const kBig = BigInt('0x' + kBN.toString(16))
106
+ const R = curve.g.mulCT(kBN)
98
107
 
99
- // —— R = k·G (Jacobian, window‑NAF) ──────────────────────────────────────
100
- const R = scalarMultiplyWNAF(kBig, { x: GX_BIGINT, y: GY_BIGINT })
101
- if (R.Z === 0n) { // point at infinity – should never happen for valid k
108
+ if (R.isInfinity()) {
102
109
  if (BigNumber.isBN(customK)) {
103
110
  throw new Error('Invalid fixed custom K value (k·G at infinity)')
104
111
  }
105
112
  continue
106
113
  }
107
114
 
108
- // affine X coordinate of R
109
- const zInv = biModInv(R.Z)
110
- const zInv2 = biModMul(zInv, zInv)
111
- const xAff = biModMul(R.X, zInv2)
115
+ const xAff = BigInt('0x' + R.getX().toString(16))
112
116
  const rBig = modN(xAff)
113
117
 
114
118
  if (rBig === 0n) {
@@ -118,7 +122,7 @@ export const sign = (
118
122
  continue
119
123
  }
120
124
 
121
- // —— s = k⁻¹ · (msg + r·key) mod n ─────────────────────────────────────
125
+ const kBig = BigInt('0x' + kBN.toString(16))
122
126
  const kInv = modInvN(kBig)
123
127
  const rTimesKey = modMulN(rBig, keyBig)
124
128
  const sum = modN(msgBig + rTimesKey)
@@ -131,12 +135,10 @@ export const sign = (
131
135
  continue
132
136
  }
133
137
 
134
- // low‑S mitigation (BIP‑62/BIP‑340 style)
135
138
  if (forceLowS && sBig > halfN) {
136
139
  sBig = N_BIGINT - sBig
137
140
  }
138
141
 
139
- // —— convert back to BigNumber & return ─────────────────────────────────
140
142
  const r = new BigNumber(rBig.toString(16), 16)
141
143
  const s = new BigNumber(sBig.toString(16), 16)
142
144
  return new Signature(r, s)
@@ -163,18 +165,18 @@ export const sign = (
163
165
  */
164
166
  export const verify = (msg: BigNumber, sig: Signature, key: Point): boolean => {
165
167
  // Convert inputs to BigInt
166
- const hash = BigInt('0x' + msg.toString(16))
168
+ const hash = bnToBigInt(msg)
167
169
  if ((key.x == null) || (key.y == null)) {
168
170
  throw new Error('Invalid public key: missing coordinates.')
169
171
  }
170
172
 
171
173
  const publicKey = {
172
- x: BigInt('0x' + key.x.toString(16)),
173
- y: BigInt('0x' + key.y.toString(16))
174
+ x: bnToBigInt(key.x),
175
+ y: bnToBigInt(key.y)
174
176
  }
175
177
  const signature = {
176
- r: BigInt('0x' + sig.r.toString(16)),
177
- s: BigInt('0x' + sig.s.toString(16))
178
+ r: bnToBigInt(sig.r),
179
+ s: bnToBigInt(sig.s)
178
180
  }
179
181
 
180
182
  const { r, s } = signature