ox 1.6.3 → 1.7.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 (48) hide show
  1. package/CHANGELOG.md +12 -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/Siwe.d.ts.map +1 -1
  23. package/dist/core/Siwe.js +45 -6
  24. package/dist/core/Siwe.js.map +1 -1
  25. package/dist/core/internal/keyDerivation.d.ts +13 -0
  26. package/dist/core/internal/keyDerivation.d.ts.map +1 -0
  27. package/dist/core/internal/keyDerivation.js +13 -0
  28. package/dist/core/internal/keyDerivation.js.map +1 -0
  29. package/package.json +1 -1
  30. package/src/core/AesGcm.ts +137 -30
  31. package/src/core/Ed25519.ts +135 -4
  32. package/src/core/MlDsa44.ts +135 -4
  33. package/src/core/P256.ts +135 -1
  34. package/src/core/Secp256k1.ts +134 -3
  35. package/src/core/Siwe.ts +45 -5
  36. package/src/core/_test/AesGcm.test-d.ts +19 -0
  37. package/src/core/_test/AesGcm.test.ts +90 -1
  38. package/src/core/_test/Ed25519.test-d.ts +24 -0
  39. package/src/core/_test/Ed25519.test.ts +87 -1
  40. package/src/core/_test/MlDsa44.test-d.ts +24 -0
  41. package/src/core/_test/MlDsa44.test.ts +69 -1
  42. package/src/core/_test/P256.test-d.ts +26 -0
  43. package/src/core/_test/P256.test.ts +98 -1
  44. package/src/core/_test/Secp256k1.test-d.ts +27 -0
  45. package/src/core/_test/Secp256k1.test.ts +112 -1
  46. package/src/core/_test/Siwe.test.ts +98 -0
  47. package/src/core/internal/keyDerivation.ts +34 -0
  48. package/src/version.ts +1 -1
@@ -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
  })
@@ -546,6 +546,81 @@ Expiration Time: 2022-02-04T00:00:00.000Z`
546
546
  )
547
547
  })
548
548
 
549
+ test.each([
550
+ ['never', '2023-02-01T00:00:00Z'],
551
+ ['2023-02-29T00:00:00Z', '2023-02-28T12:00:00Z'],
552
+ ['2023-04-31T00:00:00Z', '2023-04-30T12:00:00Z'],
553
+ ['2023-02-01T24:00:00Z', '2023-02-01T12:00:00Z'],
554
+ ])(
555
+ 'behavior: invalid RFC 3339 expirationTime `%s`',
556
+ (expirationTime, time) => {
557
+ const message = `https://example.com wants you to sign in with your Ethereum account:
558
+ 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
559
+
560
+ URI: https://example.com/path
561
+ Version: 1
562
+ Chain ID: 1
563
+ Nonce: foobarbaz
564
+ Issued At: 2023-02-01T00:00:00.000Z
565
+ Expiration Time: ${expirationTime}`
566
+ const parsed = Siwe.parseMessage(message)
567
+ expect({
568
+ invalid: Number.isNaN(parsed.expirationTime?.getTime()),
569
+ valid: Siwe.validateMessage({
570
+ message: parsed,
571
+ time: new Date(time),
572
+ }),
573
+ }).toMatchInlineSnapshot(`
574
+ {
575
+ "invalid": true,
576
+ "valid": false,
577
+ }
578
+ `)
579
+ },
580
+ )
581
+
582
+ test.each([
583
+ '2030-02-04t00:00:00Z',
584
+ '2030-02-04T00:00:00z',
585
+ '2030-02-04t00:00:00z',
586
+ ])('behavior: lowercase RFC 3339 expirationTime `%s`', (expirationTime) => {
587
+ const message = `https://example.com wants you to sign in with your Ethereum account:
588
+ 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
589
+
590
+ URI: https://example.com/path
591
+ Version: 1
592
+ Chain ID: 1
593
+ Nonce: foobarbaz
594
+ Issued At: 2023-02-01T00:00:00.000Z
595
+ Expiration Time: ${expirationTime}`
596
+ const parsed = Siwe.parseMessage(message)
597
+ expect(parsed.expirationTime).toMatchInlineSnapshot(
598
+ '2030-02-04T00:00:00.000Z',
599
+ )
600
+ expect(
601
+ Siwe.validateMessage({
602
+ message: parsed,
603
+ time: new Date('2029-02-04T00:00:00Z'),
604
+ }),
605
+ ).toBeTruthy()
606
+ })
607
+
608
+ test('behavior: leap-year expirationTime', () => {
609
+ const message = `https://example.com wants you to sign in with your Ethereum account:
610
+ 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
611
+
612
+ URI: https://example.com/path
613
+ Version: 1
614
+ Chain ID: 1
615
+ Nonce: foobarbaz
616
+ Issued At: 2023-02-01T00:00:00.000Z
617
+ Expiration Time: 2032-02-29T00:00:00Z`
618
+ const parsed = Siwe.parseMessage(message)
619
+ expect(parsed.expirationTime).toMatchInlineSnapshot(
620
+ '2032-02-29T00:00:00.000Z',
621
+ )
622
+ })
623
+
549
624
  test('behavior: with notBefore', () => {
550
625
  const message = `https://example.com wants you to sign in with your Ethereum account:
551
626
  0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
@@ -730,6 +805,29 @@ describe('validateMessage', () => {
730
805
  ).toBeFalsy()
731
806
  })
732
807
 
808
+ test.each(['expirationTime', 'notBefore'] as const)(
809
+ 'behavior: invalid %s',
810
+ (field) => {
811
+ expect(
812
+ Siwe.validateMessage({
813
+ message: {
814
+ ...message,
815
+ [field]: new Date('never'),
816
+ },
817
+ }),
818
+ ).toBeFalsy()
819
+ },
820
+ )
821
+
822
+ test('behavior: invalid time', () => {
823
+ expect(
824
+ Siwe.validateMessage({
825
+ message,
826
+ time: new Date('never'),
827
+ }),
828
+ ).toBeFalsy()
829
+ })
830
+
733
831
  test('behavior: time is before notBefore', () => {
734
832
  vi.useFakeTimers()
735
833
  vi.setSystemTime(new Date(Date.UTC(2023, 1, 1)))
@@ -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.1'