@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
@@ -3,6 +3,21 @@ import JPoint from './JacobianPoint.js'
3
3
  import BigNumber from './BigNumber.js'
4
4
  import { toArray, toHex } from './utils.js'
5
5
 
6
+ function ctSwap (
7
+ swap: bigint,
8
+ a: JacobianPointBI,
9
+ b: JacobianPointBI
10
+ ): void {
11
+ const mask = -swap
12
+ const swapX = (a.X ^ b.X) & mask
13
+ const swapY = (a.Y ^ b.Y) & mask
14
+ const swapZ = (a.Z ^ b.Z) & mask
15
+
16
+ a.X ^= swapX; b.X ^= swapX
17
+ a.Y ^= swapY; b.Y ^= swapY
18
+ a.Z ^= swapZ; b.Z ^= swapZ
19
+ }
20
+
6
21
  // -----------------------------------------------------------------------------
7
22
  // BigInt helpers & constants (secp256k1) – hoisted so we don't recreate them on
8
23
  // every Point.mul() call.
@@ -44,14 +59,17 @@ export const biModInv = (a: bigint): bigint => { // binary‑ext GCD
44
59
  export const biModSqr = (a: bigint): bigint => biModMul(a, a)
45
60
 
46
61
  export const biModPow = (base: bigint, exp: bigint): bigint => {
47
- let result = BI_ONE
62
+ let result = 1n
48
63
  base = biMod(base)
49
- let e = exp
50
- while (e > BI_ZERO) {
51
- if ((e & BI_ONE) === BI_ONE) result = biModMul(result, base)
64
+
65
+ while (exp > 0n) {
66
+ if ((exp & 1n) !== 0n) {
67
+ result = biModMul(result, base)
68
+ }
52
69
  base = biModMul(base, base)
53
- e >>= BI_ONE
70
+ exp >>= 1n
54
71
  }
72
+
55
73
  return result
56
74
  }
57
75
 
@@ -59,7 +77,12 @@ export const P_PLUS1_DIV4 = (P_BIGINT + 1n) >> 2n
59
77
 
60
78
  export const biModSqrt = (a: bigint): bigint | null => {
61
79
  const r = biModPow(a, P_PLUS1_DIV4)
62
- return biModMul(r, r) === biMod(a) ? r : null
80
+
81
+ if (biModMul(r, r) !== biMod(a)) {
82
+ return null
83
+ }
84
+
85
+ return r
63
86
  }
64
87
 
65
88
  const toBigInt = (x: BigNumber | number | number[] | string): bigint => {
@@ -94,6 +117,10 @@ export const jpDouble = (P: JacobianPointBI): JacobianPointBI => {
94
117
  return { X: X3, Y: Y3, Z: Z3 }
95
118
  }
96
119
 
120
+ // NOTE:
121
+ // jpAdd contains conditional branches.
122
+ // In mulCT, jpAdd and jpDouble are executed in a fixed pattern
123
+ // independent of scalar bits, satisfying TOB-4 constant-time requirements.
97
124
  export const jpAdd = (P: JacobianPointBI, Q: JacobianPointBI): JacobianPointBI => {
98
125
  if (P.Z === BI_ZERO) return Q
99
126
  if (Q.Z === BI_ZERO) return P
@@ -220,6 +247,13 @@ export default class Point extends BasePoint {
220
247
  y: BigNumber | null
221
248
  inf: boolean
222
249
 
250
+ static _assertOnCurve (p: Point): Point {
251
+ if (!p.validate()) {
252
+ throw new Error('Invalid point')
253
+ }
254
+ return p
255
+ }
256
+
223
257
  /**
224
258
  * Creates a point object from a given Array. These numbers can represent coordinates in hex format, or points
225
259
  * in multiple established formats.
@@ -238,7 +272,6 @@ export default class Point extends BasePoint {
238
272
  */
239
273
  static fromDER (bytes: number[]): Point {
240
274
  const len = 32
241
- // uncompressed, hybrid-odd, hybrid-even
242
275
  if (
243
276
  (bytes[0] === 0x04 || bytes[0] === 0x06 || bytes[0] === 0x07) &&
244
277
  bytes.length - 1 === 2 * len
@@ -258,12 +291,14 @@ export default class Point extends BasePoint {
258
291
  bytes.slice(1 + len, 1 + 2 * len)
259
292
  )
260
293
 
261
- return res
294
+ return Point._assertOnCurve(res)
262
295
  } else if (
263
296
  (bytes[0] === 0x02 || bytes[0] === 0x03) &&
264
297
  bytes.length - 1 === len
265
298
  ) {
266
- return Point.fromX(bytes.slice(1, 1 + len), bytes[0] === 0x03)
299
+ return Point._assertOnCurve(
300
+ Point.fromX(bytes.slice(1, 1 + len), bytes[0] === 0x03)
301
+ )
267
302
  }
268
303
  throw new Error('Unknown point format')
269
304
  }
@@ -287,7 +322,7 @@ export default class Point extends BasePoint {
287
322
  */
288
323
  static fromString (str: string): Point {
289
324
  const bytes = toArray(str, 'hex')
290
- return Point.fromDER(bytes)
325
+ return Point._assertOnCurve(Point.fromDER(bytes))
291
326
  }
292
327
 
293
328
  /**
@@ -308,16 +343,22 @@ export default class Point extends BasePoint {
308
343
  static fromX (x: BigNumber | number | number[] | string, odd: boolean): Point {
309
344
  let xBigInt = toBigInt(x)
310
345
  xBigInt = biMod(xBigInt)
346
+
311
347
  const y2 = biModAdd(biModMul(biModSqr(xBigInt), xBigInt), 7n)
312
348
  const y = biModSqrt(y2)
313
- if (y === null) throw new Error('Invalid point')
349
+ if (y === null) {
350
+ throw new Error('Invalid point')
351
+ }
352
+
314
353
  let yBig = y
315
354
  if ((yBig & BI_ONE) !== (odd ? BI_ONE : BI_ZERO)) {
316
355
  yBig = biModSub(P_BIGINT, yBig)
317
356
  }
357
+
318
358
  const xBN = new BigNumber(xBigInt.toString(16), 16)
319
359
  const yBN = new BigNumber(yBig.toString(16), 16)
320
- return new Point(xBN, yBN)
360
+
361
+ return Point._assertOnCurve(new Point(xBN, yBN))
321
362
  }
322
363
 
323
364
  /**
@@ -339,33 +380,45 @@ export default class Point extends BasePoint {
339
380
  if (typeof obj === 'string') {
340
381
  obj = JSON.parse(obj)
341
382
  }
342
- const res = new Point(obj[0], obj[1], isRed)
343
- if (typeof obj[2] !== 'object') {
383
+
384
+ let res = new Point(obj[0], obj[1], isRed)
385
+ res = Point._assertOnCurve(res)
386
+
387
+ if (typeof obj[2] !== 'object' || obj[2] === null) {
344
388
  return res
345
389
  }
346
390
 
347
- const obj2point = (obj): Point => {
348
- return new Point(obj[0], obj[1], isRed)
391
+ const pre = obj[2]
392
+
393
+ const obj2point = (p): Point => {
394
+ const pt = new Point(p[0], p[1], isRed)
395
+ return Point._assertOnCurve(pt)
349
396
  }
350
397
 
351
- const pre = obj[2]
352
398
  res.precomputed = {
353
399
  beta: null,
400
+
354
401
  doubles:
355
402
  typeof pre.doubles === 'object' && pre.doubles !== null
356
403
  ? {
357
404
  step: pre.doubles.step,
358
- points: [res].concat(pre.doubles.points.map(obj2point))
405
+ points: [res].concat(
406
+ pre.doubles.points.map(obj2point)
407
+ )
359
408
  }
360
409
  : undefined,
410
+
361
411
  naf:
362
412
  typeof pre.naf === 'object' && pre.naf !== null
363
413
  ? {
364
414
  wnd: pre.naf.wnd,
365
- points: [res].concat(pre.naf.points.map(obj2point))
415
+ points: [res].concat(
416
+ pre.naf.points.map(obj2point)
417
+ )
366
418
  }
367
419
  : undefined
368
420
  }
421
+
369
422
  return res
370
423
  }
371
424
 
@@ -426,7 +479,20 @@ export default class Point extends BasePoint {
426
479
  * const isValid = aPoint.validate();
427
480
  */
428
481
  validate (): boolean {
429
- return this.curve.validate(this)
482
+ if (this.inf || this.x == null || this.y == null) return false
483
+
484
+ try {
485
+ const xBig = BigInt('0x' + this.x.fromRed().toString(16))
486
+ const yBig = BigInt('0x' + this.y.fromRed().toString(16))
487
+
488
+ // compute y² and x³ + 7 using bigint-secure field ops
489
+ const lhs = biModMul(yBig, yBig)
490
+ const rhs = biModAdd(biModMul(biModMul(xBig, xBig), xBig), 7n)
491
+
492
+ return lhs === rhs
493
+ } catch {
494
+ return false
495
+ }
430
496
  }
431
497
 
432
498
  /**
@@ -687,13 +753,17 @@ export default class Point extends BasePoint {
687
753
  return this
688
754
  }
689
755
 
690
- let kBig = BigInt('0x' + k.toString(16))
691
- const isNeg = kBig < BI_ZERO
692
- if (isNeg) kBig = -kBig
756
+ const isNeg = k.isNeg()
757
+ const kAbs = isNeg ? k.neg() : k
758
+ let kBig = BigInt('0x' + kAbs.toString(16))
759
+
693
760
  kBig = biMod(kBig)
694
761
  if (kBig === BI_ZERO) {
695
762
  return new Point(null, null)
696
763
  }
764
+ if (kBig === BI_ZERO) {
765
+ return new Point(null, null)
766
+ }
697
767
 
698
768
  if (this.x === null || this.y === null) {
699
769
  throw new Error('Point coordinates cannot be null')
@@ -727,6 +797,55 @@ export default class Point extends BasePoint {
727
797
  return result
728
798
  }
729
799
 
800
+ mulCT (k: BigNumber | number | number[] | string): Point {
801
+ if (!BigNumber.isBN(k)) {
802
+ k = new BigNumber(k as any, 16)
803
+ }
804
+ k = k as BigNumber
805
+
806
+ if (this.inf) return new Point(null, null)
807
+
808
+ // ✅ SAFE sign handling (this is the fix)
809
+ const isNeg = k.isNeg()
810
+ const kAbs = isNeg ? k.neg() : k
811
+ let kBig = BigInt('0x' + kAbs.toString(16))
812
+
813
+ kBig = biMod(kBig)
814
+ if (kBig === 0n) return new Point(null, null)
815
+
816
+ const Px =
817
+ this === this.curve.g
818
+ ? GX_BIGINT
819
+ : BigInt('0x' + this.getX().toString(16))
820
+
821
+ const Py =
822
+ this === this.curve.g
823
+ ? GY_BIGINT
824
+ : BigInt('0x' + this.getY().toString(16))
825
+
826
+ let R0: JacobianPointBI = { X: 0n, Y: 1n, Z: 0n }
827
+ let R1: JacobianPointBI = { X: Px, Y: Py, Z: 1n }
828
+
829
+ const bits = kBig.toString(2)
830
+ for (let i = 0; i < bits.length; i++) {
831
+ const bit = bits[i] === '1' ? 1n : 0n
832
+ ctSwap(bit, R0, R1)
833
+ R1 = jpAdd(R0, R1)
834
+ R0 = jpDouble(R0)
835
+ ctSwap(bit, R0, R1)
836
+ }
837
+
838
+ if (R0.Z === 0n) return new Point(null, null)
839
+
840
+ const zInv = biModInv(R0.Z)
841
+ const zInv2 = biModMul(zInv, zInv)
842
+ const x = biModMul(R0.X, zInv2)
843
+ const y = biModMul(R0.Y, biModMul(zInv2, zInv))
844
+
845
+ const result = new Point(x.toString(16), y.toString(16))
846
+ return isNeg ? result.neg() : result
847
+ }
848
+
730
849
  /**
731
850
  * Performs a multiplication and addition operation in a single step.
732
851
  * Multiplies this Point by k1, adds the resulting Point to the result of p2 multiplied by k2.
@@ -260,7 +260,7 @@ export default class PrivateKey extends BigNumber {
260
260
  */
261
261
  toPublicKey (): PublicKey {
262
262
  const c = new Curve()
263
- const p = c.g.mul(this)
263
+ const p = c.g.mulCT(this)
264
264
  return new PublicKey(p.x, p.y)
265
265
  }
266
266
 
@@ -352,7 +352,7 @@ export default class PrivateKey extends BigNumber {
352
352
  if (!key.validate()) {
353
353
  throw new Error('Public key not valid for ECDH secret derivation')
354
354
  }
355
- return key.mul(this)
355
+ return key.mulCT(this)
356
356
  }
357
357
 
358
358
  /**
@@ -114,7 +114,7 @@ export default class PublicKey extends Point {
114
114
  if (!this.validate()) {
115
115
  throw new Error('Public key not valid for ECDH secret derivation')
116
116
  }
117
- return this.mul(priv)
117
+ return this.mulCT(priv)
118
118
  }
119
119
 
120
120
  /**
@@ -41,19 +41,27 @@ export default class SymmetricKey extends BigNumber {
41
41
  * const encryptedMessage = key.encrypt('plainText', 'utf8');
42
42
  */
43
43
  encrypt (msg: number[] | string, enc?: 'hex'): string | number[] {
44
- const iv = Random(32)
45
- msg = toArray(msg, enc)
46
- const keyBytes = this.toArray('be', 32)
47
- const { result, authenticationTag } = AESGCM(msg, [], iv, keyBytes)
44
+ const iv = new Uint8Array(Random(32))
45
+ const msgBytes = new Uint8Array(toArray(msg, enc))
46
+ const keyBytes = new Uint8Array(this.toArray('be', 32))
47
+
48
+ const { result, authenticationTag } = AESGCM(
49
+ msgBytes,
50
+ iv,
51
+ keyBytes
52
+ )
53
+
48
54
  const totalLength = iv.length + result.length + authenticationTag.length
49
- const combined = new Array(totalLength)
55
+ const combined = new Uint8Array(totalLength)
50
56
  let offset = 0
51
- for (const chunk of [iv, result, authenticationTag]) {
52
- for (let i = 0; i < chunk.length; i++) {
53
- combined[offset++] = chunk[i]
54
- }
55
- }
56
- return encode(combined, enc)
57
+
58
+ combined.set(iv, offset)
59
+ offset += iv.length
60
+ combined.set(result, offset)
61
+ offset += result.length
62
+ combined.set(authenticationTag, offset)
63
+
64
+ return encode(Array.from(combined), enc)
57
65
  }
58
66
 
59
67
  /**
@@ -73,30 +81,30 @@ export default class SymmetricKey extends BigNumber {
73
81
  * @throws {Error} Will throw an error if the decryption fails, likely due to message tampering or incorrect decryption key.
74
82
  */
75
83
  decrypt (msg: number[] | string, enc?: 'hex' | 'utf8'): string | number[] {
76
- msg = toArray(msg, enc)
84
+ const msgBytes = new Uint8Array(toArray(msg, enc))
77
85
 
78
86
  const ivLength = 32
79
87
  const tagLength = 16
80
88
 
81
- if (msg.length < ivLength + tagLength) {
89
+ if (msgBytes.length < ivLength + tagLength) {
82
90
  throw new Error('Ciphertext too short')
83
91
  }
84
92
 
85
- const iv = msg.slice(0, ivLength)
86
- const tagStart = msg.length - tagLength
87
- const ciphertext = msg.slice(ivLength, tagStart)
88
- const messageTag = msg.slice(tagStart)
93
+ const iv = msgBytes.slice(0, ivLength)
94
+ const tagStart = msgBytes.length - tagLength
95
+ const ciphertext = msgBytes.slice(ivLength, tagStart)
96
+ const messageTag = msgBytes.slice(tagStart)
89
97
 
98
+ const keyBytes = new Uint8Array(this.toArray('be', 32))
90
99
  const result = AESGCMDecrypt(
91
100
  ciphertext,
92
- [],
93
101
  iv,
94
102
  messageTag,
95
- this.toArray('be', 32)
103
+ keyBytes
96
104
  )
97
105
  if (result === null) {
98
106
  throw new Error('Decryption failed!')
99
107
  }
100
- return encode(result, enc)
108
+ return encode(Array.from(result), enc)
101
109
  }
102
110
  }