@bsv/sdk 1.9.29 → 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 (32) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/primitives/ECDSA.js +22 -23
  3. package/dist/cjs/src/primitives/ECDSA.js.map +1 -1
  4. package/dist/cjs/src/primitives/Point.js +61 -4
  5. package/dist/cjs/src/primitives/Point.js.map +1 -1
  6. package/dist/cjs/src/primitives/PrivateKey.js +2 -2
  7. package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
  8. package/dist/cjs/src/primitives/PublicKey.js +1 -1
  9. package/dist/cjs/src/primitives/PublicKey.js.map +1 -1
  10. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  11. package/dist/esm/src/primitives/ECDSA.js +22 -23
  12. package/dist/esm/src/primitives/ECDSA.js.map +1 -1
  13. package/dist/esm/src/primitives/Point.js +61 -4
  14. package/dist/esm/src/primitives/Point.js.map +1 -1
  15. package/dist/esm/src/primitives/PrivateKey.js +2 -2
  16. package/dist/esm/src/primitives/PrivateKey.js.map +1 -1
  17. package/dist/esm/src/primitives/PublicKey.js +1 -1
  18. package/dist/esm/src/primitives/PublicKey.js.map +1 -1
  19. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  20. package/dist/types/src/primitives/ECDSA.d.ts.map +1 -1
  21. package/dist/types/src/primitives/Point.d.ts +1 -0
  22. package/dist/types/src/primitives/Point.d.ts.map +1 -1
  23. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  24. package/dist/umd/bundle.js +3 -3
  25. package/dist/umd/bundle.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/primitives/ECDSA.ts +25 -23
  28. package/src/primitives/Point.ts +75 -3
  29. package/src/primitives/PrivateKey.ts +2 -2
  30. package/src/primitives/PublicKey.ts +1 -1
  31. package/src/primitives/__tests/ECDSA.test.ts +12 -0
  32. package/src/primitives/__tests/Point.test.ts +60 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.9.29",
3
+ "version": "1.9.30",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -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
@@ -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.
@@ -102,6 +117,10 @@ export const jpDouble = (P: JacobianPointBI): JacobianPointBI => {
102
117
  return { X: X3, Y: Y3, Z: Z3 }
103
118
  }
104
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.
105
124
  export const jpAdd = (P: JacobianPointBI, Q: JacobianPointBI): JacobianPointBI => {
106
125
  if (P.Z === BI_ZERO) return Q
107
126
  if (Q.Z === BI_ZERO) return P
@@ -734,13 +753,17 @@ export default class Point extends BasePoint {
734
753
  return this
735
754
  }
736
755
 
737
- let kBig = BigInt('0x' + k.toString(16))
738
- const isNeg = kBig < BI_ZERO
739
- 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
+
740
760
  kBig = biMod(kBig)
741
761
  if (kBig === BI_ZERO) {
742
762
  return new Point(null, null)
743
763
  }
764
+ if (kBig === BI_ZERO) {
765
+ return new Point(null, null)
766
+ }
744
767
 
745
768
  if (this.x === null || this.y === null) {
746
769
  throw new Error('Point coordinates cannot be null')
@@ -774,6 +797,55 @@ export default class Point extends BasePoint {
774
797
  return result
775
798
  }
776
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
+
777
849
  /**
778
850
  * Performs a multiplication and addition operation in a single step.
779
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
  /**
@@ -117,4 +117,16 @@ describe('ECDSA', () => {
117
117
  ECDSA.verify(msg, signature, infinityPub)
118
118
  ).toThrow()
119
119
  })
120
+
121
+ it('sign/verify works with large private key (mulCT stress)', () => {
122
+ const bigKey = new BigNumber(
123
+ 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f',
124
+ 16
125
+ )
126
+
127
+ const sig = ECDSA.sign(msg, bigKey)
128
+ const pub = curve.g.mul(bigKey)
129
+
130
+ expect(ECDSA.verify(msg, sig, pub)).toBe(true)
131
+ })
120
132
  })
@@ -1,4 +1,5 @@
1
1
  import Point from '../../primitives/Point'
2
+ import BigNumber from '../../primitives/BigNumber'
2
3
 
3
4
  describe('Point.fromJSON / fromDER / fromX curve validation (TOB-24)', () => {
4
5
  it('rejects clearly off-curve coordinates', () => {
@@ -50,3 +51,62 @@ describe('Point.fromJSON / fromDER / fromX curve validation (TOB-24)', () => {
50
51
  expect(() => Point.fromX(badX, true)).toThrow(/Invalid point/)
51
52
  })
52
53
  })
54
+
55
+ describe('Point.mulCT (constant-time scalar multiplication)', () => {
56
+ const G = Point.fromString(
57
+ '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'
58
+ )
59
+
60
+ it('returns point at infinity for scalar = 0', () => {
61
+ const r = G.mulCT(0)
62
+ expect(r.isInfinity()).toBe(true)
63
+ })
64
+
65
+ it('matches regular mul for small scalar', () => {
66
+ const k = 5
67
+ const r1 = G.mul(k)
68
+ const r2 = G.mulCT(k)
69
+
70
+ expect(r2.eq(r1)).toBe(true)
71
+ })
72
+
73
+ it('matches regular mul for large scalar', () => {
74
+ const k =
75
+ 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'
76
+
77
+ const r1 = G.mul(k)
78
+ const r2 = G.mulCT(k)
79
+
80
+ expect(r2.eq(r1)).toBe(true)
81
+ })
82
+
83
+ it('works with non-generator base point', () => {
84
+ const base = G.mul(3)
85
+ const k = 11
86
+
87
+ const r1 = base.mul(k)
88
+ const r2 = base.mulCT(k)
89
+
90
+ expect(r2.eq(r1)).toBe(true)
91
+ })
92
+
93
+ it('handles alternating bit patterns (ctSwap exercised)', () => {
94
+ // 101010... pattern forces both swap paths
95
+ const k = BigInt(
96
+ '0b101010101010101010101010101010101010101010101010101010101010101'
97
+ )
98
+
99
+ const r1 = G.mul(k.toString(10))
100
+ const r2 = G.mulCT(k.toString(10))
101
+
102
+ expect(r2.eq(r1)).toBe(true)
103
+ })
104
+
105
+ it('handles negative scalars correctly', () => {
106
+ const k = new BigNumber('123456', 16)
107
+ const r1 = G.mul(k.neg())
108
+ const r2 = G.mulCT(k.neg())
109
+ expect(r2.eq(r1)).toBe(true)
110
+ })
111
+ })
112
+