@bsv/sdk 1.9.18 → 1.9.19

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.
@@ -843,7 +843,7 @@ export default class DRBG {
843
843
  V: number[];
844
844
  constructor(entropy: number[] | string, nonce: number[] | string)
845
845
  hmac(): SHA256HMAC
846
- update(seed?): void
846
+ update(seed?: number[]): void
847
847
  generate(len: number): string
848
848
  }
849
849
  ```
@@ -899,7 +899,7 @@ Updates the `K` and `V` values of the instance based on the seed.
899
899
  The seed if not provided uses `V` as seed.
900
900
 
901
901
  ```ts
902
- update(seed?): void
902
+ update(seed?: number[]): void
903
903
  ```
904
904
 
905
905
  Returns
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.9.18",
3
+ "version": "1.9.19",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -10,6 +10,7 @@ import { toHex, toArray } from './utils.js'
10
10
  * @param nonce - Initial nonce either in number array or hexadecimal string.
11
11
  *
12
12
  * @throws Throws an error message 'Not enough entropy. Minimum is 256 bits' when entropy's length is less than 32.
13
+ * @throws Thrown an error message 'Nonce must be exactly 32 bytes (256 bits)' when nonce's length is less than 32.
13
14
  *
14
15
  * @example
15
16
  * const drbg = new DRBG('af12de...', '123ef...');
@@ -19,13 +20,18 @@ export default class DRBG {
19
20
  V: number[]
20
21
 
21
22
  constructor (entropy: number[] | string, nonce: number[] | string) {
22
- entropy = toArray(entropy, 'hex')
23
- nonce = toArray(nonce, 'hex')
23
+ const entropyBytes = toArray(entropy, 'hex')
24
+ const nonceBytes = toArray(nonce, 'hex')
24
25
 
25
- if (entropy.length < 32) {
26
- throw new Error('Not enough entropy. Minimum is 256 bits')
26
+ // RFC 6979 for secp256k1 assumes 256-bit x and h1.
27
+ if (entropyBytes.length !== 32) {
28
+ throw new Error('Entropy must be exactly 32 bytes (256 bits)')
27
29
  }
28
- const seed = entropy.concat(nonce)
30
+ if (nonceBytes.length !== 32) {
31
+ throw new Error('Nonce must be exactly 32 bytes (256 bits)')
32
+ }
33
+
34
+ const seedMaterial = entropyBytes.concat(nonceBytes)
29
35
 
30
36
  this.K = new Array(32)
31
37
  this.V = new Array(32)
@@ -33,7 +39,8 @@ export default class DRBG {
33
39
  this.K[i] = 0x00
34
40
  this.V[i] = 0x01
35
41
  }
36
- this.update(seed)
42
+
43
+ this.update(seedMaterial)
37
44
  }
38
45
 
39
46
  /**
@@ -60,7 +67,7 @@ export default class DRBG {
60
67
  * @example
61
68
  * drbg.update('e13af...');
62
69
  */
63
- update (seed?): void {
70
+ update (seed?: number[]): void {
64
71
  let kmac = this.hmac().update(this.V).update([0x00])
65
72
  if (seed !== undefined) {
66
73
  kmac = kmac.update(seed)
@@ -87,7 +87,7 @@ export const sign = (
87
87
  if (kBN == null) throw new Error('k is undefined')
88
88
  kBN = truncateToN(kBN, true)
89
89
 
90
- if (kBN.cmpn(1) <= 0 || kBN.cmp(ns1) >= 0) {
90
+ if (kBN.cmpn(1) < 0 || kBN.cmp(ns1) > 0) {
91
91
  if (BigNumber.isBN(customK)) {
92
92
  throw new Error('Invalid fixed custom K value (must be >1 and <N‑1)')
93
93
  }
@@ -1,18 +1,281 @@
1
1
  import DRBG from '../../primitives/DRBG'
2
2
  import DRBGVectors from './DRBG.vectors'
3
+ import { toArray, toHex } from '../../primitives/utils'
4
+ import { SHA256 } from '../../primitives/Hash'
3
5
 
4
- describe('Hmac_DRBG', () => {
5
- describe('NIST vector', function () {
6
- DRBGVectors.forEach(function (opt) {
7
- it('should not fail at ' + opt.name, function () {
8
- const drbg = new DRBG(opt.entropy, opt.nonce)
6
+ describe('DRBG', () => {
7
+ describe('NIST vector compatibility', () => {
8
+ DRBGVectors.forEach((opt, index) => {
9
+ it(`handles NIST-style vector ${index} consistently`, () => {
10
+ const entropyBytes = toArray(opt.entropy, 'hex')
11
+ const nonceBytes = toArray(opt.nonce, 'hex')
9
12
 
10
- let last
11
- for (let i = 0; i < opt.add.length; i++) {
12
- last = drbg.generate(opt.expected.length / 2)
13
+ const expectedByteLen = opt.expected.length / 2
14
+
15
+ if (entropyBytes.length !== 32 || nonceBytes.length !== 32) {
16
+ expect(() => new DRBG(opt.entropy, opt.nonce)).toThrow()
17
+ return
13
18
  }
14
- expect(last).toEqual(opt.expected)
19
+
20
+ const drbg1 = new DRBG(opt.entropy, opt.nonce)
21
+ const out1 = drbg1.generate(expectedByteLen)
22
+
23
+ const drbg2 = new DRBG(opt.entropy, opt.nonce)
24
+ const out2 = drbg2.generate(expectedByteLen)
25
+
26
+ expect(out1).toEqual(out2)
27
+ expect(out1.length).toBe(opt.expected.length)
15
28
  })
16
29
  })
17
30
  })
31
+
32
+ describe('constructor input validation', () => {
33
+ it('throws if entropy is shorter than 32 bytes', () => {
34
+ const entropy = new Array(31).fill(0x01)
35
+ const nonce = new Array(32).fill(0x02)
36
+
37
+ expect(() => {
38
+ new DRBG(entropy, nonce)
39
+ }).toThrow('Entropy must be exactly 32 bytes (256 bits)')
40
+ })
41
+
42
+ it('throws if entropy is longer than 32 bytes', () => {
43
+ const entropy = new Array(33).fill(0x01)
44
+ const nonce = new Array(32).fill(0x02)
45
+
46
+ expect(() => {
47
+ new DRBG(entropy, nonce)
48
+ }).toThrow('Entropy must be exactly 32 bytes (256 bits)')
49
+ })
50
+
51
+ it('throws if nonce is shorter than 32 bytes', () => {
52
+ const entropy = new Array(32).fill(0x01)
53
+ const nonce = new Array(31).fill(0x02)
54
+
55
+ expect(() => {
56
+ new DRBG(entropy, nonce)
57
+ }).toThrow('Nonce must be exactly 32 bytes (256 bits)')
58
+ })
59
+
60
+ it('throws if nonce is longer than 32 bytes', () => {
61
+ const entropy = new Array(32).fill(0x01)
62
+ const nonce = new Array(33).fill(0x02)
63
+
64
+ expect(() => {
65
+ new DRBG(entropy, nonce)
66
+ }).toThrow('Nonce must be exactly 32 bytes (256 bits)')
67
+ })
68
+
69
+ it('accepts both hex strings and number[] inputs equivalently', () => {
70
+ const entropyArr = new Array(32).fill(0x11)
71
+ const nonceArr = new Array(32).fill(0x22)
72
+
73
+ const entropyHex = Buffer.from(entropyArr).toString('hex')
74
+ const nonceHex = Buffer.from(nonceArr).toString('hex')
75
+
76
+ const drbgArray = new DRBG(entropyArr, nonceArr)
77
+ const drbgHex = new DRBG(entropyHex, nonceHex)
78
+
79
+ const outArray = drbgArray.generate(32)
80
+ const outHex = drbgHex.generate(32)
81
+
82
+ expect(outArray).toEqual(outHex)
83
+ })
84
+ })
85
+
86
+ describe('determinism', () => {
87
+ const entropyHex =
88
+ '1b2e3d4c5f60718293a4b5c6d7e8f9011b2e3d4c5f60718293a4b5c6d7e8f901'
89
+ const nonceHex =
90
+ 'abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd'
91
+
92
+ it('produces the same sequence for the same inputs', () => {
93
+ const drbg1 = new DRBG(entropyHex, nonceHex)
94
+ const drbg2 = new DRBG(entropyHex, nonceHex)
95
+
96
+ const seq1 = [
97
+ drbg1.generate(32),
98
+ drbg1.generate(32),
99
+ drbg1.generate(16)
100
+ ]
101
+
102
+ const seq2 = [
103
+ drbg2.generate(32),
104
+ drbg2.generate(32),
105
+ drbg2.generate(16)
106
+ ]
107
+
108
+ expect(seq1).toEqual(seq2)
109
+ })
110
+
111
+ it('produces different sequences if entropy changes', () => {
112
+ const entropyHex2 =
113
+ '2b3e4d5c6f708192a3b4c5d6e7f809112b3e4d5c6f708192a3b4c5d6e7f80911'
114
+ const drbg1 = new DRBG(entropyHex, nonceHex)
115
+ const drbg2 = new DRBG(entropyHex2, nonceHex)
116
+
117
+ const out1 = drbg1.generate(32)
118
+ const out2 = drbg2.generate(32)
119
+
120
+ expect(out1).not.toEqual(out2)
121
+ })
122
+
123
+ it('produces different sequences if nonce changes', () => {
124
+ const nonceHex2 =
125
+ '00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff'
126
+ const drbg1 = new DRBG(entropyHex, nonceHex)
127
+ const drbg2 = new DRBG(entropyHex, nonceHex2)
128
+
129
+ const out1 = drbg1.generate(32)
130
+ const out2 = drbg2.generate(32)
131
+
132
+ expect(out1).not.toEqual(out2)
133
+ })
134
+ })
135
+
136
+ describe('output length and state advancement', () => {
137
+ const entropyBytes = new Array(32).fill(0x33)
138
+ const nonceBytes = new Array(32).fill(0x44)
139
+
140
+ it('returns hex strings of length 2 * len', () => {
141
+ const drbg = new DRBG(entropyBytes, nonceBytes)
142
+
143
+ const out16 = drbg.generate(16)
144
+ const out32 = drbg.generate(32)
145
+
146
+ expect(out16.length).toBe(16 * 2)
147
+ expect(out32.length).toBe(32 * 2)
148
+ })
149
+
150
+ it('advances internal state between generate calls', () => {
151
+ const drbg = new DRBG(entropyBytes, nonceBytes)
152
+
153
+ const first = drbg.generate(32)
154
+ const second = drbg.generate(32)
155
+
156
+ expect(second).not.toEqual(first)
157
+ })
158
+
159
+ it('matches output when seeded with explicit number[] arrays', () => {
160
+ const entropyHex = 'aa'.repeat(32)
161
+ const nonceHex = 'bb'.repeat(32)
162
+ const entropyArr = toArray(entropyHex, 'hex')
163
+ const nonceArr = toArray(nonceHex, 'hex')
164
+
165
+ const drbgFromHex = new DRBG(entropyHex, nonceHex)
166
+ const drbgFromArr = new DRBG(entropyArr, nonceArr)
167
+
168
+ const outHex = drbgFromHex.generate(32)
169
+ const outArr = drbgFromArr.generate(32)
170
+
171
+ expect(outHex).toEqual(outArr)
172
+ })
173
+ })
174
+
175
+ describe('RFC 6979 ECDSA P-256 / SHA-256 vectors', () => {
176
+ // q for NIST P-256, from RFC 6979 A.2.5
177
+ const qHex =
178
+ 'FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551'
179
+ const q = BigInt('0x' + qHex)
180
+
181
+ // int2octets: convert a non-negative bigint < 2^256 to a 32-byte big-endian array
182
+ const intToOctets = (x: bigint): number[] => {
183
+ const out = new Array<number>(32)
184
+ let v = x
185
+ for (let i = 31; i >= 0; i--) {
186
+ out[i] = Number(v & 0xffn)
187
+ v >>= 8n
188
+ }
189
+ return out
190
+ }
191
+
192
+ // bits2octets(h1): convert hash output to int, reduce mod q, return 32-byte big-endian
193
+ const bits2octets = (h1: number[]): number[] => {
194
+ const h1Int = BigInt('0x' + toHex(h1))
195
+ const z1 = h1Int % q
196
+ return intToOctets(z1)
197
+ }
198
+
199
+ const normalizeHex = (hex: string): string => hex.toLowerCase()
200
+
201
+ it('reproduces RFC 6979 k for P-256, SHA-256, message "sample"', () => {
202
+ // From RFC 6979 A.2.5 (ECDSA, 256 bits, P-256):
203
+ // private key x:
204
+ const xHex =
205
+ 'C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721'
206
+
207
+ // expected k for SHA-256, message = "sample"
208
+ const expectedKHex =
209
+ 'A6E3C57DD01ABE90086538398355DD4C3B17AA873382B0F24D6129493D8AAD60'
210
+
211
+ const msg = 'sample'
212
+
213
+ const x = BigInt('0x' + xHex)
214
+ const entropy = intToOctets(x)
215
+
216
+ // h1 = SHA-256(message)
217
+ const h1Bytes = new SHA256().update(msg, 'utf8').digest()
218
+ const nonce = bits2octets(h1Bytes)
219
+
220
+ const drbg = new DRBG(entropy, nonce)
221
+
222
+ // First 32-byte block from DRBG
223
+ const tHex = drbg.generate(32) // 32 bytes = 64 hex chars
224
+ const tInt = BigInt('0x' + tHex)
225
+
226
+ // RFC 6979 derives k as tInt mod q (and retries if out of range; here it’s fine)
227
+ const k = tInt % q
228
+ const kHex = k.toString(16).padStart(64, '0')
229
+
230
+ expect(normalizeHex(kHex)).toBe(normalizeHex(expectedKHex))
231
+ })
232
+
233
+ it('reproduces RFC 6979 k for P-256, SHA-256, message "test"', () => {
234
+ // Same key x as above (RFC 6979 A.2.5), different message:
235
+ const xHex =
236
+ 'C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721'
237
+
238
+ // expected k for SHA-256, message = "test"
239
+ const expectedKHex =
240
+ 'D16B6AE827F17175E040871A1C7EC3500192C4C92677336EC2537ACAEE0008E0'
241
+
242
+ const msg = 'test'
243
+
244
+ const x = BigInt('0x' + xHex)
245
+ const entropy = intToOctets(x)
246
+
247
+ const h1Bytes = new SHA256().update(msg, 'utf8').digest()
248
+ const nonce = bits2octets(h1Bytes)
249
+
250
+ const drbg = new DRBG(entropy, nonce)
251
+
252
+ const tHex = drbg.generate(32)
253
+ const tInt = BigInt('0x' + tHex)
254
+ const k = tInt % q
255
+ const kHex = k.toString(16).padStart(64, '0')
256
+
257
+ expect(normalizeHex(kHex)).toBe(normalizeHex(expectedKHex))
258
+ })
259
+
260
+ it('is deterministic for the same RFC 6979 key and message', () => {
261
+ const xHex =
262
+ 'C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721'
263
+ const msg = 'sample'
264
+
265
+ const x = BigInt('0x' + xHex)
266
+ const entropy = intToOctets(x)
267
+ const h1Bytes = new SHA256().update(msg, 'utf8').digest()
268
+ const nonce = bits2octets(h1Bytes)
269
+
270
+ const drbg1 = new DRBG(entropy, nonce)
271
+ const drbg2 = new DRBG(entropy, nonce)
272
+
273
+ const out1 = drbg1.generate(32)
274
+ const out2 = drbg2.generate(32)
275
+
276
+ expect(out1).toBe(out2)
277
+ })
278
+ })
18
279
  })
280
+
281
+
@@ -61,4 +61,33 @@ describe('ECDSA', () => {
61
61
  const signature = ECDSA.sign(msg, key)
62
62
  expect(ECDSA.verify(msg, signature, wrongPub)).toBeFalsy()
63
63
  })
64
+
65
+ it('should accept custom k = 1 and k = n-1', () => {
66
+ const n = curve.n
67
+ const one = new BigNumber(1)
68
+
69
+ // k = 1 → valid
70
+ const k1 = one
71
+ const sig1 = ECDSA.sign(msg, key, undefined, k1)
72
+ expect(ECDSA.verify(msg, sig1, pub)).toBeTruthy()
73
+
74
+ // k = n-1 → valid
75
+ const km1 = n.subn(1)
76
+ const sig2 = ECDSA.sign(msg, key, undefined, km1)
77
+ expect(ECDSA.verify(msg, sig2, pub)).toBeTruthy()
78
+ })
79
+
80
+ it('should reject custom k < 1 or k > n-1', () => {
81
+ const n = curve.n
82
+
83
+ // k = 0 → invalid
84
+ expect(() =>
85
+ ECDSA.sign(msg, key, undefined, new BigNumber(0))
86
+ ).toThrow()
87
+
88
+ // k = n → invalid
89
+ expect(() =>
90
+ ECDSA.sign(msg, key, undefined, n)
91
+ ).toThrow()
92
+ })
64
93
  })