ox 1.6.3 → 1.7.0

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/CHANGELOG.md +6 -0
  2. package/dist/core/AesGcm.d.ts +67 -2
  3. package/dist/core/AesGcm.d.ts.map +1 -1
  4. package/dist/core/AesGcm.js +79 -10
  5. package/dist/core/AesGcm.js.map +1 -1
  6. package/dist/core/Ed25519.d.ts +83 -2
  7. package/dist/core/Ed25519.d.ts.map +1 -1
  8. package/dist/core/Ed25519.js +79 -4
  9. package/dist/core/Ed25519.js.map +1 -1
  10. package/dist/core/MlDsa44.d.ts +84 -5
  11. package/dist/core/MlDsa44.d.ts.map +1 -1
  12. package/dist/core/MlDsa44.js +79 -4
  13. package/dist/core/MlDsa44.js.map +1 -1
  14. package/dist/core/P256.d.ts +82 -1
  15. package/dist/core/P256.d.ts.map +1 -1
  16. package/dist/core/P256.js +78 -0
  17. package/dist/core/P256.js.map +1 -1
  18. package/dist/core/Secp256k1.d.ts +85 -1
  19. package/dist/core/Secp256k1.d.ts.map +1 -1
  20. package/dist/core/Secp256k1.js +74 -3
  21. package/dist/core/Secp256k1.js.map +1 -1
  22. package/dist/core/internal/keyDerivation.d.ts +13 -0
  23. package/dist/core/internal/keyDerivation.d.ts.map +1 -0
  24. package/dist/core/internal/keyDerivation.js +13 -0
  25. package/dist/core/internal/keyDerivation.js.map +1 -0
  26. package/package.json +1 -1
  27. package/src/core/AesGcm.ts +137 -30
  28. package/src/core/Ed25519.ts +135 -4
  29. package/src/core/MlDsa44.ts +135 -4
  30. package/src/core/P256.ts +135 -1
  31. package/src/core/Secp256k1.ts +134 -3
  32. package/src/core/_test/AesGcm.test-d.ts +19 -0
  33. package/src/core/_test/AesGcm.test.ts +90 -1
  34. package/src/core/_test/Ed25519.test-d.ts +24 -0
  35. package/src/core/_test/Ed25519.test.ts +87 -1
  36. package/src/core/_test/MlDsa44.test-d.ts +24 -0
  37. package/src/core/_test/MlDsa44.test.ts +69 -1
  38. package/src/core/_test/P256.test-d.ts +26 -0
  39. package/src/core/_test/P256.test.ts +98 -1
  40. package/src/core/_test/Secp256k1.test-d.ts +27 -0
  41. package/src/core/_test/Secp256k1.test.ts +112 -1
  42. package/src/core/internal/keyDerivation.ts +34 -0
  43. package/src/version.ts +1 -1
@@ -1,4 +1,4 @@
1
- import { Bytes, Ed25519, Engine, Hex, X25519 } from 'ox'
1
+ import { Bytes, Ed25519, Engine, Hex, Mnemonic, X25519 } from 'ox'
2
2
  import { describe, expect, test } from 'vp/test'
3
3
 
4
4
  describe('createKeyPair', () => {
@@ -146,6 +146,89 @@ describe('fromPrf', () => {
146
146
  })
147
147
  })
148
148
 
149
+ describe('fromMnemonic', () => {
150
+ const mnemonic = 'test test test test test test test test test test test junk'
151
+
152
+ test('default', () => {
153
+ const privateKey = Ed25519.fromMnemonic(mnemonic)
154
+
155
+ expect(privateKey).toBe(Ed25519.fromSeed(Mnemonic.toSeed(mnemonic)))
156
+ expect(privateKey).toMatchInlineSnapshot(
157
+ `"0x23fb07a427d2bc36141d1e6c7e56c72679739eff374a8e8def282f1048674567"`,
158
+ )
159
+ })
160
+
161
+ test('options: passphrase', () => {
162
+ expect(
163
+ Ed25519.fromMnemonic(mnemonic, { passphrase: 'qwerty' }),
164
+ ).toMatchInlineSnapshot(
165
+ `"0xe888791c9d783e772d92b0bf2aa326b1cda8214a03c3c07933615b1a98fc8651"`,
166
+ )
167
+ })
168
+
169
+ test('options: as', () => {
170
+ const privateKey = Ed25519.fromMnemonic(mnemonic, { as: 'Bytes' })
171
+
172
+ expect(privateKey).toBeInstanceOf(Uint8Array)
173
+ expect(privateKey).toHaveLength(32)
174
+ })
175
+ })
176
+
177
+ describe('fromSeed', () => {
178
+ const seed =
179
+ '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
180
+
181
+ test('vector', () => {
182
+ expect(Ed25519.fromSeed(seed)).toMatchInlineSnapshot(
183
+ `"0xfd09c7b2acda46034df7e428377fdc37200094c9b51690fa1b733ee3c16a2d07"`,
184
+ )
185
+ })
186
+
187
+ test('value: Bytes', () => {
188
+ expect(Ed25519.fromSeed(Bytes.fromHex(seed))).toMatchInlineSnapshot(
189
+ `"0xfd09c7b2acda46034df7e428377fdc37200094c9b51690fa1b733ee3c16a2d07"`,
190
+ )
191
+ })
192
+
193
+ test('options: as', () => {
194
+ expect(Ed25519.fromSeed(seed, { as: 'Bytes' })).toEqual(
195
+ Bytes.fromHex(
196
+ '0xfd09c7b2acda46034df7e428377fdc37200094c9b51690fa1b733ee3c16a2d07',
197
+ ),
198
+ )
199
+ })
200
+
201
+ test('behavior: output signs and verifies', () => {
202
+ const privateKey = Ed25519.fromSeed(new Uint8Array(64))
203
+ const publicKey = Ed25519.getPublicKey({ privateKey })
204
+ const payload = '0xdeadbeef'
205
+ const signature = Ed25519.sign({ payload, privateKey })
206
+
207
+ expect(Ed25519.verify({ payload, publicKey, signature })).toBe(true)
208
+ })
209
+
210
+ test('behavior: zeroes intermediate key for Hex output', () => {
211
+ const candidate = new Uint8Array(32).fill(1)
212
+ Engine.with(
213
+ {
214
+ Hash: {
215
+ hmacSha256: () => candidate,
216
+ },
217
+ },
218
+ () => Ed25519.fromSeed(new Uint8Array(32)),
219
+ )
220
+
221
+ expect(candidate).toEqual(new Uint8Array(32))
222
+ })
223
+
224
+ test('error: seed is too short', () => {
225
+ expect(() => Ed25519.fromSeed(new Uint8Array(31)))
226
+ .toThrowErrorMatchingInlineSnapshot(`
227
+ [Ed25519.InvalidSeedSizeError: Seed must contain at least 32 bytes. Received 31 bytes.]
228
+ `)
229
+ })
230
+ })
231
+
149
232
  describe('getPublicKey', () => {
150
233
  const privateKey =
151
234
  '0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'
@@ -545,6 +628,8 @@ test('exports', () => {
545
628
  "noble",
546
629
  "createKeyPair",
547
630
  "fromPrf",
631
+ "fromMnemonic",
632
+ "fromSeed",
548
633
  "getPublicKey",
549
634
  "randomPrivateKey",
550
635
  "sign",
@@ -552,6 +637,7 @@ test('exports', () => {
552
637
  "toX25519PublicKey",
553
638
  "toX25519PrivateKey",
554
639
  "InvalidPrfSizeError",
640
+ "InvalidSeedSizeError",
555
641
  ]
556
642
  `)
557
643
  })
@@ -24,6 +24,30 @@ test('fromPrf', () => {
24
24
  ).toEqualTypeOf<Bytes.Bytes>()
25
25
  })
26
26
 
27
+ test('fromMnemonic', () => {
28
+ const mnemonic = 'test test test test test test test test test test test junk'
29
+
30
+ expectTypeOf(MlDsa44.fromMnemonic(mnemonic)).toEqualTypeOf<Hex.Hex>()
31
+ expectTypeOf(
32
+ MlDsa44.fromMnemonic(mnemonic, { passphrase: 'qwerty' }),
33
+ ).toEqualTypeOf<Hex.Hex>()
34
+ expectTypeOf(
35
+ MlDsa44.fromMnemonic(mnemonic, { as: 'Bytes' }),
36
+ ).toEqualTypeOf<Bytes.Bytes>()
37
+ })
38
+
39
+ test('fromSeed', () => {
40
+ const seed =
41
+ '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
42
+
43
+ expectTypeOf(MlDsa44.fromSeed(seed)).toEqualTypeOf<Hex.Hex>()
44
+ expectTypeOf(MlDsa44.fromSeed(new Uint8Array(32))).toEqualTypeOf<Hex.Hex>()
45
+ expectTypeOf(MlDsa44.fromSeed(seed, { as: 'Hex' })).toEqualTypeOf<Hex.Hex>()
46
+ expectTypeOf(
47
+ MlDsa44.fromSeed(seed, { as: 'Bytes' }),
48
+ ).toEqualTypeOf<Bytes.Bytes>()
49
+ })
50
+
27
51
  test('getPublicKey', () => {
28
52
  expectTypeOf(
29
53
  MlDsa44.getPublicKey({ privateKey: '0x' }),
@@ -1,4 +1,4 @@
1
- import { Bytes, Engine, Hash, Hex, MlDsa44 } from 'ox'
1
+ import { Bytes, Engine, Hash, Hex, MlDsa44, Mnemonic } from 'ox'
2
2
  import { describe, expect, test } from 'vp/test'
3
3
 
4
4
  const privateKey = Hex.fromBytes(new Uint8Array(32).fill(7))
@@ -139,6 +139,74 @@ describe('fromPrf', () => {
139
139
  })
140
140
  })
141
141
 
142
+ describe('fromMnemonic', () => {
143
+ const mnemonic = 'test test test test test test test test test test test junk'
144
+
145
+ test('default', () => {
146
+ const privateKey = MlDsa44.fromMnemonic(mnemonic)
147
+
148
+ expect(privateKey).toBe(MlDsa44.fromSeed(Mnemonic.toSeed(mnemonic)))
149
+ expect(privateKey).toMatchInlineSnapshot(
150
+ `"0x787f747b571a3511deac4d045a7876ae2349f45b9f9f4e3a6ee40342313a963f"`,
151
+ )
152
+ })
153
+
154
+ test('options: passphrase', () => {
155
+ expect(
156
+ MlDsa44.fromMnemonic(mnemonic, { passphrase: 'qwerty' }),
157
+ ).toMatchInlineSnapshot(
158
+ `"0x51202c4aaf8b4090f94c43ae43234b133a2646642fbc4ca39d8883a0b39d2b44"`,
159
+ )
160
+ })
161
+
162
+ test('options: as', () => {
163
+ const privateKey = MlDsa44.fromMnemonic(mnemonic, { as: 'Bytes' })
164
+
165
+ expect(privateKey).toBeInstanceOf(Uint8Array)
166
+ expect(privateKey).toHaveLength(32)
167
+ })
168
+ })
169
+
170
+ describe('fromSeed', () => {
171
+ const seed =
172
+ '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
173
+
174
+ test('vector', () => {
175
+ expect(MlDsa44.fromSeed(seed)).toMatchInlineSnapshot(
176
+ `"0xbee74160aef452ec0e90a4c347bbb0ac37e93fb94855f7a98dfe9afabebbdad6"`,
177
+ )
178
+ })
179
+
180
+ test('value: Bytes', () => {
181
+ expect(MlDsa44.fromSeed(Bytes.fromHex(seed))).toMatchInlineSnapshot(
182
+ `"0xbee74160aef452ec0e90a4c347bbb0ac37e93fb94855f7a98dfe9afabebbdad6"`,
183
+ )
184
+ })
185
+
186
+ test('options: as', () => {
187
+ expect(MlDsa44.fromSeed(seed, { as: 'Bytes' })).toEqual(
188
+ Bytes.fromHex(
189
+ '0xbee74160aef452ec0e90a4c347bbb0ac37e93fb94855f7a98dfe9afabebbdad6',
190
+ ),
191
+ )
192
+ })
193
+
194
+ test('behavior: output signs and verifies', () => {
195
+ const privateKey = MlDsa44.fromSeed(new Uint8Array(64))
196
+ const publicKey = MlDsa44.getPublicKey({ privateKey })
197
+ const signature = MlDsa44.sign({ payload, privateKey })
198
+
199
+ expect(MlDsa44.verify({ payload, publicKey, signature })).toBe(true)
200
+ })
201
+
202
+ test('error: seed is too short', () => {
203
+ expect(() => MlDsa44.fromSeed(new Uint8Array(31)))
204
+ .toThrowErrorMatchingInlineSnapshot(`
205
+ [MlDsa44.InvalidSeedSizeError: Seed must contain at least 32 bytes. Received 31 bytes.]
206
+ `)
207
+ })
208
+ })
209
+
142
210
  describe('getPublicKey', () => {
143
211
  test('default', () => {
144
212
  const publicKey = MlDsa44.getPublicKey({ privateKey })
@@ -0,0 +1,26 @@
1
+ import { type Bytes, type Hex, P256 } from 'ox'
2
+ import { expectTypeOf, test } from 'vp/test'
3
+
4
+ test('fromMnemonic', () => {
5
+ const mnemonic = 'test test test test test test test test test test test junk'
6
+
7
+ expectTypeOf(P256.fromMnemonic(mnemonic)).toEqualTypeOf<Hex.Hex>()
8
+ expectTypeOf(
9
+ P256.fromMnemonic(mnemonic, { passphrase: 'qwerty' }),
10
+ ).toEqualTypeOf<Hex.Hex>()
11
+ expectTypeOf(
12
+ P256.fromMnemonic(mnemonic, { as: 'Bytes' }),
13
+ ).toEqualTypeOf<Bytes.Bytes>()
14
+ })
15
+
16
+ test('fromSeed', () => {
17
+ const seed =
18
+ '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
19
+
20
+ expectTypeOf(P256.fromSeed(seed)).toEqualTypeOf<Hex.Hex>()
21
+ expectTypeOf(P256.fromSeed(new Uint8Array(32))).toEqualTypeOf<Hex.Hex>()
22
+ expectTypeOf(P256.fromSeed(seed, { as: 'Hex' })).toEqualTypeOf<Hex.Hex>()
23
+ expectTypeOf(
24
+ P256.fromSeed(seed, { as: 'Bytes' }),
25
+ ).toEqualTypeOf<Bytes.Bytes>()
26
+ })
@@ -1,4 +1,4 @@
1
- import { Bytes, Hex, P256 } from 'ox'
1
+ import { Bytes, Engine, Hex, Mnemonic, P256 } from 'ox'
2
2
  import { describe, expect, test } from 'vp/test'
3
3
  import { accounts } from '../../../test/constants/accounts.js'
4
4
 
@@ -145,6 +145,100 @@ describe('createKeyPair', () => {
145
145
  })
146
146
  })
147
147
 
148
+ describe('fromMnemonic', () => {
149
+ const mnemonic = 'test test test test test test test test test test test junk'
150
+
151
+ test('default', () => {
152
+ const privateKey = P256.fromMnemonic(mnemonic)
153
+
154
+ expect(privateKey).toBe(P256.fromSeed(Mnemonic.toSeed(mnemonic)))
155
+ expect(privateKey).toMatchInlineSnapshot(
156
+ `"0x882f5b02a84c96bceeebf9c868bec73041d51b041ae05380e66a41508648ebc3"`,
157
+ )
158
+ })
159
+
160
+ test('options: passphrase', () => {
161
+ expect(
162
+ P256.fromMnemonic(mnemonic, { passphrase: 'qwerty' }),
163
+ ).toMatchInlineSnapshot(
164
+ `"0x364ff539f37ce477d49a35476c9ad32a2a3fed1d1237eaee7abdfd07655ad9f5"`,
165
+ )
166
+ })
167
+
168
+ test('options: as', () => {
169
+ const privateKey = P256.fromMnemonic(mnemonic, { as: 'Bytes' })
170
+
171
+ expect(privateKey).toBeInstanceOf(Uint8Array)
172
+ expect(privateKey).toHaveLength(32)
173
+ })
174
+ })
175
+
176
+ describe('fromSeed', () => {
177
+ const seed =
178
+ '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
179
+
180
+ test('vector', () => {
181
+ expect(P256.fromSeed(seed)).toMatchInlineSnapshot(
182
+ `"0x724009e76eb27e34eb77b93db354b9015ecd4296deb57561f5214bab68b94a6f"`,
183
+ )
184
+ })
185
+
186
+ test('value: Bytes', () => {
187
+ expect(P256.fromSeed(Bytes.fromHex(seed))).toMatchInlineSnapshot(
188
+ `"0x724009e76eb27e34eb77b93db354b9015ecd4296deb57561f5214bab68b94a6f"`,
189
+ )
190
+ })
191
+
192
+ test('options: as', () => {
193
+ expect(P256.fromSeed(seed, { as: 'Bytes' })).toEqual(
194
+ Bytes.fromHex(
195
+ '0x724009e76eb27e34eb77b93db354b9015ecd4296deb57561f5214bab68b94a6f',
196
+ ),
197
+ )
198
+ })
199
+
200
+ test('behavior: output is a valid P256 private key', () => {
201
+ const privateKey = P256.fromSeed(new Uint8Array(64), { as: 'Bytes' })
202
+
203
+ expect(P256.noble.utils.isValidSecretKey(privateKey)).toBe(true)
204
+ })
205
+
206
+ test('behavior: skips invalid scalar candidates', () => {
207
+ const messages: Hex.Hex[] = []
208
+ const privateKey = Engine.with(
209
+ {
210
+ Hash: {
211
+ hmacSha256: (_key, message) => {
212
+ messages.push(Hex.fromBytes(message))
213
+ if (messages.length === 1) return new Uint8Array(32)
214
+ return Bytes.fromHex(
215
+ '0xef3f982137910a4c362017e69ba173fbb342325802ec827767aa6942027af35b',
216
+ )
217
+ },
218
+ },
219
+ },
220
+ () => P256.fromSeed(new Uint8Array(32)),
221
+ )
222
+
223
+ expect(privateKey).toBe(
224
+ '0xef3f982137910a4c362017e69ba173fbb342325802ec827767aa6942027af35b',
225
+ )
226
+ expect(messages).toMatchInlineSnapshot(`
227
+ [
228
+ "0x6f782e703235362e66726f6d536565642e763100000000",
229
+ "0x6f782e703235362e66726f6d536565642e763100000001",
230
+ ]
231
+ `)
232
+ })
233
+
234
+ test('error: seed is too short', () => {
235
+ expect(() => P256.fromSeed(new Uint8Array(31)))
236
+ .toThrowErrorMatchingInlineSnapshot(`
237
+ [P256.InvalidSeedSizeError: Seed must contain at least 32 bytes. Received 31 bytes.]
238
+ `)
239
+ })
240
+ })
241
+
148
242
  describe('getSharedSecret', () => {
149
243
  test('default', () => {
150
244
  const { privateKey: privateKeyA, publicKey: publicKeyA } =
@@ -546,12 +640,15 @@ test('exports', () => {
546
640
  [
547
641
  "noble",
548
642
  "createKeyPair",
643
+ "fromMnemonic",
644
+ "fromSeed",
549
645
  "getPublicKey",
550
646
  "getSharedSecret",
551
647
  "randomPrivateKey",
552
648
  "recoverPublicKey",
553
649
  "sign",
554
650
  "verify",
651
+ "InvalidSeedSizeError",
555
652
  ]
556
653
  `)
557
654
  })
@@ -12,3 +12,30 @@ test('fromPrf', () => {
12
12
  Secp256k1.fromPrf(prf, { as: 'Bytes' }),
13
13
  ).toEqualTypeOf<Bytes.Bytes>()
14
14
  })
15
+
16
+ test('fromMnemonic', () => {
17
+ const mnemonic = 'test test test test test test test test test test test junk'
18
+
19
+ expectTypeOf(Secp256k1.fromMnemonic(mnemonic)).toEqualTypeOf<Hex.Hex>()
20
+ expectTypeOf(
21
+ Secp256k1.fromMnemonic(mnemonic, { passphrase: 'qwerty' }),
22
+ ).toEqualTypeOf<Hex.Hex>()
23
+ expectTypeOf(
24
+ Secp256k1.fromMnemonic(mnemonic, { as: 'Bytes' }),
25
+ ).toEqualTypeOf<Bytes.Bytes>()
26
+ expectTypeOf(
27
+ Secp256k1.fromMnemonic(mnemonic, { path: "m/44'/60'/0'/0/1" }),
28
+ ).toEqualTypeOf<Hex.Hex>()
29
+ })
30
+
31
+ test('fromSeed', () => {
32
+ const seed =
33
+ '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
34
+
35
+ expectTypeOf(Secp256k1.fromSeed(seed)).toEqualTypeOf<Hex.Hex>()
36
+ expectTypeOf(Secp256k1.fromSeed(new Uint8Array(32))).toEqualTypeOf<Hex.Hex>()
37
+ expectTypeOf(Secp256k1.fromSeed(seed, { as: 'Hex' })).toEqualTypeOf<Hex.Hex>()
38
+ expectTypeOf(
39
+ Secp256k1.fromSeed(seed, { as: 'Bytes' }),
40
+ ).toEqualTypeOf<Bytes.Bytes>()
41
+ })
@@ -1,4 +1,4 @@
1
- import { Address, Bytes, Engine, Hex, PublicKey, Secp256k1 } from 'ox'
1
+ import { Address, Bytes, Engine, Hex, Mnemonic, PublicKey, Secp256k1 } from 'ox'
2
2
  import { describe, expect, test } from 'vp/test'
3
3
  import { accounts } from '../../../test/constants/accounts.js'
4
4
 
@@ -211,6 +211,114 @@ describe('fromPrf', () => {
211
211
  })
212
212
  })
213
213
 
214
+ describe('fromMnemonic', () => {
215
+ const mnemonic = 'test test test test test test test test test test test junk'
216
+
217
+ test('default', () => {
218
+ const privateKey = Secp256k1.fromMnemonic(mnemonic)
219
+
220
+ expect(privateKey).toBe(Mnemonic.toPrivateKey(mnemonic, { as: 'Hex' }))
221
+ expect(privateKey).toMatchInlineSnapshot(
222
+ `"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"`,
223
+ )
224
+ })
225
+
226
+ test('options: passphrase', () => {
227
+ expect(
228
+ Secp256k1.fromMnemonic(mnemonic, { passphrase: 'qwerty' }),
229
+ ).toMatchInlineSnapshot(
230
+ `"0x0bef893b1cc27e9ce726d5f12f75d61a07d4df87c02106083463cd712ac5c478"`,
231
+ )
232
+ })
233
+
234
+ test('options: as', () => {
235
+ const privateKey = Secp256k1.fromMnemonic(mnemonic, { as: 'Bytes' })
236
+
237
+ expect(privateKey).toBeInstanceOf(Uint8Array)
238
+ expect(privateKey).toHaveLength(32)
239
+ })
240
+
241
+ test('options: path', () => {
242
+ const path = "m/44'/60'/0'/0/1"
243
+
244
+ expect(Secp256k1.fromMnemonic(mnemonic, { path })).toBe(
245
+ Mnemonic.toPrivateKey(mnemonic, { as: 'Hex', path }),
246
+ )
247
+ })
248
+ })
249
+
250
+ describe('fromSeed', () => {
251
+ const seed =
252
+ '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
253
+
254
+ test('vector', () => {
255
+ expect(Secp256k1.fromSeed(seed)).toMatchInlineSnapshot(
256
+ `"0x5f7dcbba41adaafa378861d78b144f7b2827e79fa994a341628f5470d5bfbdd4"`,
257
+ )
258
+ })
259
+
260
+ test('value: Bytes', () => {
261
+ expect(Secp256k1.fromSeed(Bytes.fromHex(seed))).toMatchInlineSnapshot(
262
+ `"0x5f7dcbba41adaafa378861d78b144f7b2827e79fa994a341628f5470d5bfbdd4"`,
263
+ )
264
+ })
265
+
266
+ test('options: as', () => {
267
+ expect(Secp256k1.fromSeed(seed, { as: 'Bytes' })).toEqual(
268
+ Bytes.fromHex(
269
+ '0x5f7dcbba41adaafa378861d78b144f7b2827e79fa994a341628f5470d5bfbdd4',
270
+ ),
271
+ )
272
+ })
273
+
274
+ test('behavior: accepts seeds longer than 32 bytes', () => {
275
+ const privateKey = Secp256k1.fromSeed(new Uint8Array(64), { as: 'Bytes' })
276
+
277
+ expect(Secp256k1.noble.utils.isValidSecretKey(privateKey)).toBe(true)
278
+ })
279
+
280
+ test('behavior: domain is distinct from fromPrf', () => {
281
+ expect(Secp256k1.fromSeed(seed)).not.toBe(Secp256k1.fromPrf(seed))
282
+ })
283
+
284
+ test('behavior: skips invalid scalar candidates', () => {
285
+ const invalid = new Uint8Array(32).fill(0xff)
286
+ const messages: Hex.Hex[] = []
287
+ const privateKey = Engine.with(
288
+ {
289
+ Hash: {
290
+ hmacSha256: (_key, message) => {
291
+ messages.push(Hex.fromBytes(message))
292
+ if (messages.length === 1) return invalid
293
+ return Bytes.fromHex(
294
+ '0xef3f982137910a4c362017e69ba173fbb342325802ec827767aa6942027af35b',
295
+ )
296
+ },
297
+ },
298
+ },
299
+ () => Secp256k1.fromSeed(new Uint8Array(32)),
300
+ )
301
+
302
+ expect(privateKey).toBe(
303
+ '0xef3f982137910a4c362017e69ba173fbb342325802ec827767aa6942027af35b',
304
+ )
305
+ expect(invalid).toEqual(new Uint8Array(32))
306
+ expect(messages).toMatchInlineSnapshot(`
307
+ [
308
+ "0x6f782e736563703235366b312e66726f6d536565642e763100000000",
309
+ "0x6f782e736563703235366b312e66726f6d536565642e763100000001",
310
+ ]
311
+ `)
312
+ })
313
+
314
+ test('error: seed is too short', () => {
315
+ expect(() => Secp256k1.fromSeed(new Uint8Array(31)))
316
+ .toThrowErrorMatchingInlineSnapshot(`
317
+ [Secp256k1.InvalidSeedSizeError: Seed must contain at least 32 bytes. Received 31 bytes.]
318
+ `)
319
+ })
320
+ })
321
+
214
322
  describe('getSharedSecret', () => {
215
323
  test('default', () => {
216
324
  const privateKeyA = accounts[0].privateKey
@@ -572,6 +680,8 @@ test('exports', () => {
572
680
  "noble",
573
681
  "createKeyPair",
574
682
  "fromPrf",
683
+ "fromMnemonic",
684
+ "fromSeed",
575
685
  "getPublicKey",
576
686
  "getSharedSecret",
577
687
  "randomPrivateKey",
@@ -580,6 +690,7 @@ test('exports', () => {
580
690
  "sign",
581
691
  "verify",
582
692
  "InvalidPrfSizeError",
693
+ "InvalidSeedSizeError",
583
694
  ]
584
695
  `)
585
696
  })
@@ -0,0 +1,34 @@
1
+ import * as Bytes from '../Bytes.js'
2
+ import type * as Errors from '../Errors.js'
3
+ import * as Hash from '../Hash.js'
4
+
5
+ /** @internal */
6
+ export function derive(
7
+ seed: Bytes.Bytes,
8
+ domain: Bytes.Bytes,
9
+ options: derive.Options = {},
10
+ ): Bytes.Bytes {
11
+ const { validate } = options
12
+ for (let counter = 0; ; counter++) {
13
+ const key = Hash.hmac256(
14
+ seed,
15
+ Bytes.concat(domain, Bytes.fromNumber(counter, { size: 4 })),
16
+ { as: 'Bytes' },
17
+ )
18
+ if (!validate || validate(key)) return key
19
+ key.fill(0)
20
+ }
21
+ }
22
+
23
+ /** @internal */
24
+ export declare namespace derive {
25
+ type Options = {
26
+ validate?: ((key: Bytes.Bytes) => boolean) | undefined
27
+ }
28
+
29
+ type ErrorType =
30
+ | Bytes.concat.ErrorType
31
+ | Bytes.fromNumber.ErrorType
32
+ | Hash.hmac256.ErrorType
33
+ | Errors.GlobalErrorType
34
+ }
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** @internal */
2
- export const version = '1.6.3'
2
+ export const version = '1.7.0'