@bsv/sdk 1.9.31 → 1.10.1

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 (43) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/auth/Peer.js +68 -48
  3. package/dist/cjs/src/auth/Peer.js.map +1 -1
  4. package/dist/cjs/src/primitives/BigNumber.js +28 -54
  5. package/dist/cjs/src/primitives/BigNumber.js.map +1 -1
  6. package/dist/cjs/src/primitives/ECDSA.js +36 -1
  7. package/dist/cjs/src/primitives/ECDSA.js.map +1 -1
  8. package/dist/cjs/src/primitives/ReductionContext.js +35 -46
  9. package/dist/cjs/src/primitives/ReductionContext.js.map +1 -1
  10. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  11. package/dist/esm/src/auth/Peer.js +68 -48
  12. package/dist/esm/src/auth/Peer.js.map +1 -1
  13. package/dist/esm/src/primitives/BigNumber.js +28 -54
  14. package/dist/esm/src/primitives/BigNumber.js.map +1 -1
  15. package/dist/esm/src/primitives/ECDSA.js +36 -1
  16. package/dist/esm/src/primitives/ECDSA.js.map +1 -1
  17. package/dist/esm/src/primitives/ReductionContext.js +35 -46
  18. package/dist/esm/src/primitives/ReductionContext.js.map +1 -1
  19. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  20. package/dist/types/src/auth/Peer.d.ts.map +1 -1
  21. package/dist/types/src/auth/types.d.ts +2 -0
  22. package/dist/types/src/auth/types.d.ts.map +1 -1
  23. package/dist/types/src/primitives/BigNumber.d.ts +8 -0
  24. package/dist/types/src/primitives/BigNumber.d.ts.map +1 -1
  25. package/dist/types/src/primitives/ECDSA.d.ts +24 -0
  26. package/dist/types/src/primitives/ECDSA.d.ts.map +1 -1
  27. package/dist/types/src/primitives/ReductionContext.d.ts +9 -0
  28. package/dist/types/src/primitives/ReductionContext.d.ts.map +1 -1
  29. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  30. package/dist/umd/bundle.js +3 -3
  31. package/dist/umd/bundle.js.map +1 -1
  32. package/docs/index.md +15 -1
  33. package/docs/reference/auth.md +2 -0
  34. package/docs/reference/messages.md +0 -24
  35. package/docs/reference/primitives.md +91 -31
  36. package/package.json +1 -1
  37. package/src/auth/Peer.ts +122 -57
  38. package/src/auth/__tests/Peer.test.ts +166 -257
  39. package/src/auth/types.ts +2 -0
  40. package/src/primitives/BigNumber.ts +27 -31
  41. package/src/primitives/ECDSA.ts +41 -2
  42. package/src/primitives/ReductionContext.ts +44 -48
  43. package/src/primitives/__tests/ECDSA.test.ts +16 -0
@@ -22,6 +22,8 @@ import DRBG from './DRBG.js'
22
22
  * @example
23
23
  * let msg = new BigNumber('1234567890abcdef', 16);
24
24
  * let truncatedMsg = truncateToN(msg);
25
+ *
26
+ * This behavior follows the message truncation rules defined in FIPS 186-4.
25
27
  */
26
28
  function truncateToN (
27
29
  msg: BigNumber,
@@ -32,7 +34,7 @@ function truncateToN (
32
34
  if (delta > 0) {
33
35
  msg.iushrn(delta)
34
36
  }
35
- if (truncOnly === null && msg.cmp(curve.n) >= 0) {
37
+ if (truncOnly !== true && msg.cmp(curve.n) >= 0) {
36
38
  return msg.sub(curve.n)
37
39
  } else {
38
40
  return msg
@@ -68,12 +70,34 @@ const halfN = N_BIGINT >> 1n
68
70
  * const key = new BigNumber('123456')
69
71
  * const signature = sign(msg, key)
70
72
  */
73
+ /**
74
+ * SECURITY NOTE:
75
+ *
76
+ * This function implements ECDSA signing and expects `msg` to be the output of
77
+ * a cryptographic hash function (e.g. SHA-256), not an arbitrary-length message.
78
+ *
79
+ * Per FIPS 186-4 / SEC 1, the message representative used by ECDSA must not
80
+ * exceed the bit length of the curve order `n`. Inputs larger than `n` must be
81
+ * hashed before signing.
82
+ *
83
+ * As a short-term mitigation for TOB-22, this implementation explicitly rejects
84
+ * messages whose bit length exceeds that of the curve order.
85
+ *
86
+ * Long-term, callers SHOULD always hash messages before invoking `sign()`.
87
+ */
71
88
  export const sign = (
72
89
  msg: BigNumber,
73
90
  key: BigNumber,
74
91
  forceLowS: boolean = false,
75
92
  customK?: BigNumber | ((iter: number) => BigNumber)
76
93
  ): Signature => {
94
+ const nBitLength = curve.n.bitLength()
95
+ if (msg.bitLength() > nBitLength) {
96
+ throw new Error(
97
+ `ECDSA message is too large: expected <= ${nBitLength} bits. Callers must hash messages before signing.`
98
+ )
99
+ }
100
+
77
101
  msg = truncateToN(msg)
78
102
  const msgBig = bnToBigInt(msg)
79
103
  const keyBig = bnToBigInt(key)
@@ -163,8 +187,23 @@ export const sign = (
163
187
  * const signature = sign(msg, new BigNumber('123456'))
164
188
  * const isVerified = verify(msg, sig, key)
165
189
  */
190
+ /**
191
+ * SECURITY NOTE:
192
+ *
193
+ * This verification routine assumes that `msg` is a hashed message
194
+ * representative produced using the same hash function used during signing.
195
+ *
196
+ * As part of TOB-22 short-term hardening, messages exceeding the curve order
197
+ * bit length are rejected to prevent misuse with non-hashed inputs.
198
+ */
166
199
  export const verify = (msg: BigNumber, sig: Signature, key: Point): boolean => {
167
- // Convert inputs to BigInt
200
+ const nBitLength = curve.n.bitLength()
201
+ if (msg.bitLength() > nBitLength) {
202
+ // could be throw or return false; returning false is typical for verify
203
+ return false
204
+ }
205
+
206
+ // Convert inputs to BigInt
168
207
  const hash = bnToBigInt(msg)
169
208
  if ((key.x == null) || (key.y == null)) {
170
209
  throw new Error('Invalid public key: missing coordinates.')
@@ -2,6 +2,16 @@ import BigNumber from './BigNumber.js'
2
2
  import K256 from './K256.js'
3
3
  import Mersenne from './Mersenne.js'
4
4
 
5
+ /**
6
+ * SECURITY NOTE:
7
+ * This reduction context avoids obvious variable-time constructs (such as
8
+ * sliding-window exponentiation and conditional modular reduction) to reduce
9
+ * timing side-channel leakage. However, JavaScript BigInt arithmetic does not
10
+ * provide constant-time guarantees. These mitigations improve resistance to
11
+ * coarse timing attacks but do not make the implementation suitable for
12
+ * hostile multi-tenant or shared-CPU environments.
13
+ */
14
+
5
15
  /**
6
16
  * A base reduction engine that provides several arithmetic operations over
7
17
  * big numbers under a modulus context. It's particularly suitable for
@@ -152,11 +162,21 @@ export default class ReductionContext {
152
162
  add (a: BigNumber, b: BigNumber): BigNumber {
153
163
  this.verify2(a, b)
154
164
 
155
- const res = a.add(b)
156
- if (res.cmp(this.m) >= 0) {
157
- res.isub(this.m)
165
+ // Start from a red clone
166
+ const res = a.clone()
167
+
168
+ // In-place add keeps red context
169
+ res.iadd(b)
170
+
171
+ // Always subtract modulus (still red)
172
+ res.isub(this.m)
173
+
174
+ // If negative, add modulus back
175
+ if (res.isNeg()) {
176
+ res.iadd(this.m)
158
177
  }
159
- return res.forceRed(this)
178
+
179
+ return res
160
180
  }
161
181
 
162
182
  /**
@@ -178,11 +198,14 @@ export default class ReductionContext {
178
198
  iadd (a: BigNumber, b: BigNumber): BigNumber {
179
199
  this.verify2(a, b)
180
200
 
181
- const res = a.iadd(b)
182
- if (res.cmp(this.m) >= 0) {
183
- res.isub(this.m)
201
+ a.iadd(b)
202
+ a.isub(this.m)
203
+
204
+ if (a.isNeg()) {
205
+ a.iadd(this.m)
184
206
  }
185
- return res
207
+
208
+ return a
186
209
  }
187
210
 
188
211
  /**
@@ -439,52 +462,25 @@ export default class ReductionContext {
439
462
  * context.pow(new BigNumber(3), new BigNumber(2)); // Returns 2 (3^2 % 7)
440
463
  */
441
464
  pow (a: BigNumber, num: BigNumber): BigNumber {
465
+ this.verify1(a)
466
+
442
467
  if (num.isZero()) return new BigNumber(1).toRed(this)
443
- if (num.cmpn(1) === 0) return a.clone()
444
-
445
- const windowSize = 4
446
- const wnd = new Array(1 << windowSize)
447
- wnd[0] = new BigNumber(1).toRed(this)
448
- wnd[1] = a
449
- let i = 2
450
- for (; i < wnd.length; i++) {
451
- wnd[i] = this.mul(wnd[i - 1], a)
452
- }
453
468
 
454
- let res = wnd[0]
455
- let current = 0
456
- let currentLen = 0
457
- let start = num.bitLength() % 26
458
- if (start === 0) {
459
- start = 26
460
- }
469
+ let result = new BigNumber(1).toRed(this)
470
+ const base = a.clone()
471
+ const bits = num.bitLength()
461
472
 
462
- for (i = num.length - 1; i >= 0; i--) {
463
- const word = num.words[i]
464
- for (let j = start - 1; j >= 0; j--) {
465
- const bit = (word >> j) & 1
466
- if (res !== wnd[0]) {
467
- res = this.sqr(res)
468
- }
469
-
470
- if (bit === 0 && current === 0) {
471
- currentLen = 0
472
- continue
473
- }
474
-
475
- current <<= 1
476
- current |= bit
477
- currentLen++
478
- if (currentLen !== windowSize && (i !== 0 || j !== 0)) continue
479
-
480
- res = this.mul(res, wnd[current])
481
- currentLen = 0
482
- current = 0
473
+ for (let i = bits - 1; i >= 0; i--) {
474
+ // Always square
475
+ result = this.sqr(result)
476
+
477
+ // Multiply if bit is set
478
+ if (num.testn(i)) {
479
+ result = this.mul(result, base)
483
480
  }
484
- start = 26
485
481
  }
486
482
 
487
- return res
483
+ return result
488
484
  }
489
485
 
490
486
  /**
@@ -129,4 +129,20 @@ describe('ECDSA', () => {
129
129
 
130
130
  expect(ECDSA.verify(msg, sig, pub)).toBe(true)
131
131
  })
132
+
133
+ it('should reject signing messages larger than curve order bit length (TOB-22)', () => {
134
+ // Create a message definitely larger than secp256k1 order size
135
+ const tooLargeMsg = new BigNumber(1).iushln(curve.n.bitLength() + 1)
136
+
137
+ expect(() =>
138
+ ECDSA.sign(tooLargeMsg, key)
139
+ ).toThrow(/message is too large/i)
140
+ })
141
+
142
+ it('verify should return false for messages larger than curve order bit length (TOB-22)', () => {
143
+ const signature = ECDSA.sign(msg, key)
144
+ const tooLargeMsg = new BigNumber(1).iushln(curve.n.bitLength() + 1)
145
+
146
+ expect(ECDSA.verify(tooLargeMsg, signature, pub)).toBe(false)
147
+ })
132
148
  })