ox 0.14.33 → 0.14.35

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 (70) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/_cjs/core/AesGcm.js +47 -1
  3. package/_cjs/core/AesGcm.js.map +1 -1
  4. package/_cjs/core/Ed25519.js +42 -1
  5. package/_cjs/core/Ed25519.js.map +1 -1
  6. package/_cjs/core/P256.js +44 -1
  7. package/_cjs/core/P256.js.map +1 -1
  8. package/_cjs/core/Secp256k1.js +38 -1
  9. package/_cjs/core/Secp256k1.js.map +1 -1
  10. package/_cjs/core/internal/keyDerivation.js +15 -0
  11. package/_cjs/core/internal/keyDerivation.js.map +1 -0
  12. package/_cjs/tempo/KeyAuthorization.js +6 -6
  13. package/_cjs/tempo/KeyAuthorization.js.map +1 -1
  14. package/_cjs/tempo/MultisigConfig.js +18 -7
  15. package/_cjs/tempo/MultisigConfig.js.map +1 -1
  16. package/_cjs/tempo/SignatureEnvelope.js +23 -15
  17. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  18. package/_cjs/version.js +1 -1
  19. package/_esm/core/AesGcm.js +85 -0
  20. package/_esm/core/AesGcm.js.map +1 -1
  21. package/_esm/core/Ed25519.js +81 -0
  22. package/_esm/core/Ed25519.js.map +1 -1
  23. package/_esm/core/P256.js +83 -0
  24. package/_esm/core/P256.js.map +1 -1
  25. package/_esm/core/Secp256k1.js +77 -0
  26. package/_esm/core/Secp256k1.js.map +1 -1
  27. package/_esm/core/internal/keyDerivation.js +13 -0
  28. package/_esm/core/internal/keyDerivation.js.map +1 -0
  29. package/_esm/tempo/KeyAuthorization.js +7 -13
  30. package/_esm/tempo/KeyAuthorization.js.map +1 -1
  31. package/_esm/tempo/MultisigConfig.js +33 -20
  32. package/_esm/tempo/MultisigConfig.js.map +1 -1
  33. package/_esm/tempo/SignatureEnvelope.js +42 -28
  34. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  35. package/_esm/tempo/index.js +2 -2
  36. package/_esm/version.js +1 -1
  37. package/_types/core/AesGcm.d.ts +66 -1
  38. package/_types/core/AesGcm.d.ts.map +1 -1
  39. package/_types/core/Ed25519.d.ts +82 -1
  40. package/_types/core/Ed25519.d.ts.map +1 -1
  41. package/_types/core/P256.d.ts +82 -1
  42. package/_types/core/P256.d.ts.map +1 -1
  43. package/_types/core/Secp256k1.d.ts +85 -1
  44. package/_types/core/Secp256k1.d.ts.map +1 -1
  45. package/_types/core/internal/keyDerivation.d.ts +12 -0
  46. package/_types/core/internal/keyDerivation.d.ts.map +1 -0
  47. package/_types/tempo/KeyAuthorization.d.ts +16 -12
  48. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  49. package/_types/tempo/MultisigConfig.d.ts +23 -19
  50. package/_types/tempo/MultisigConfig.d.ts.map +1 -1
  51. package/_types/tempo/SignatureEnvelope.d.ts +26 -18
  52. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  53. package/_types/tempo/index.d.ts +2 -2
  54. package/_types/version.d.ts +1 -1
  55. package/core/AesGcm.ts +137 -1
  56. package/core/Ed25519.ts +133 -1
  57. package/core/P256.ts +135 -1
  58. package/core/Secp256k1.ts +133 -1
  59. package/core/internal/keyDerivation.ts +33 -0
  60. package/package.json +1 -1
  61. package/tempo/KeyAuthorization.test-d.ts +41 -6
  62. package/tempo/KeyAuthorization.test.ts +106 -60
  63. package/tempo/KeyAuthorization.ts +36 -38
  64. package/tempo/MultisigConfig.test.ts +65 -11
  65. package/tempo/MultisigConfig.ts +45 -23
  66. package/tempo/SignatureEnvelope.test.ts +64 -19
  67. package/tempo/SignatureEnvelope.ts +52 -36
  68. package/tempo/index.ts +2 -2
  69. package/tempo/multisig.e2e.test.ts +402 -145
  70. package/version.ts +1 -1
package/core/AesGcm.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as Bytes from './Bytes.js'
2
- import type * as Errors from './Errors.js'
2
+ import * as Errors from './Errors.js'
3
3
  import * as Hex from './Hex.js'
4
+ import * as Mnemonic from './Mnemonic.js'
4
5
 
5
6
  export const ivLength = 16
6
7
 
@@ -130,6 +131,86 @@ export declare namespace encrypt {
130
131
  | Errors.GlobalErrorType
131
132
  }
132
133
 
134
+ /**
135
+ * Derives an AES-256-GCM key from a BIP-39 mnemonic.
136
+ *
137
+ * This is equivalent to passing `Mnemonic.toSeed(mnemonic, { passphrase })`
138
+ * to `AesGcm.fromSeed`.
139
+ *
140
+ * @example
141
+ * ```ts twoslash
142
+ * import { AesGcm } from 'ox'
143
+ *
144
+ * const key = await AesGcm.fromMnemonic(
145
+ * 'test test test test test test test test test test test junk'
146
+ * )
147
+ * ```
148
+ *
149
+ * @param mnemonic - BIP-39 mnemonic phrase.
150
+ * @param options - Options.
151
+ * @returns A nonextractable AES-256-GCM key for encryption and decryption.
152
+ */
153
+ export async function fromMnemonic(
154
+ mnemonic: string,
155
+ options: fromMnemonic.Options = {},
156
+ ): Promise<CryptoKey> {
157
+ const { passphrase } = options
158
+ const seed = Mnemonic.toSeed(mnemonic, { passphrase })
159
+ try {
160
+ return await fromSeed(seed)
161
+ } finally {
162
+ seed.fill(0)
163
+ }
164
+ }
165
+
166
+ export declare namespace fromMnemonic {
167
+ type Options = {
168
+ /** Optional BIP-39 passphrase. */
169
+ passphrase?: string | undefined
170
+ }
171
+
172
+ type ErrorType = fromSeed.ErrorType
173
+ }
174
+
175
+ /**
176
+ * Derives an AES-256-GCM key from a seed.
177
+ *
178
+ * The seed must contain at least 32 bytes of cryptographically strong key
179
+ * material. Do not pass a password directly; use a password KDF first.
180
+ *
181
+ * The permanent derivation contract uses the seed as the HMAC-SHA256
182
+ * key. The HMAC message uses the `ox.aesGcm.fromSeed.v1` domain followed by a
183
+ * 32-bit big-endian counter set to zero.
184
+ *
185
+ * @example
186
+ * ```ts twoslash
187
+ * import { AesGcm } from 'ox'
188
+ *
189
+ * const key = await AesGcm.fromSeed(
190
+ * '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
191
+ * )
192
+ * ```
193
+ *
194
+ * @param seed - Seed containing at least 32 bytes of cryptographically strong key material.
195
+ * @returns A nonextractable AES-256-GCM key for encryption and decryption.
196
+ */
197
+ export async function fromSeed(
198
+ seed: Hex.Hex | Bytes.Bytes,
199
+ ): Promise<CryptoKey> {
200
+ const bytes = Bytes.from(seed)
201
+ if (bytes.length < 32) throw new InvalidSeedSizeError({ size: bytes.length })
202
+ return deriveKey(bytes, fromSeedDomain)
203
+ }
204
+
205
+ export declare namespace fromSeed {
206
+ type ErrorType =
207
+ | Bytes.concat.ErrorType
208
+ | Bytes.from.ErrorType
209
+ | Bytes.fromNumber.ErrorType
210
+ | InvalidSeedSizeError
211
+ | Errors.GlobalErrorType
212
+ }
213
+
133
214
  /**
134
215
  * Derives an AES-GCM key from a password using PBKDF2.
135
216
  *
@@ -202,3 +283,58 @@ export function randomSalt(size = 32): Bytes.Bytes {
202
283
  export declare namespace randomSalt {
203
284
  type ErrorType = Bytes.random.ErrorType | Errors.GlobalErrorType
204
285
  }
286
+
287
+ /** Thrown when a seed contains fewer than 32 bytes. */
288
+ export class InvalidSeedSizeError extends Errors.BaseError {
289
+ override readonly name = 'AesGcm.InvalidSeedSizeError'
290
+
291
+ constructor(options: InvalidSeedSizeError.Options) {
292
+ super(
293
+ `Seed must contain at least 32 bytes. Received ${options.size} bytes.`,
294
+ )
295
+ }
296
+ }
297
+
298
+ export declare namespace InvalidSeedSizeError {
299
+ /** Options for `AesGcm.InvalidSeedSizeError`. */
300
+ type Options = {
301
+ /** Received seed size. */
302
+ size: number
303
+ }
304
+ }
305
+
306
+ async function deriveKey(
307
+ seed: Bytes.Bytes,
308
+ domain: Bytes.Bytes,
309
+ ): Promise<CryptoKey> {
310
+ const baseKey = await globalThis.crypto.subtle.importKey(
311
+ 'raw',
312
+ seed,
313
+ {
314
+ name: 'HMAC',
315
+ hash: 'SHA-256',
316
+ },
317
+ false,
318
+ ['sign'],
319
+ )
320
+ const key = new Uint8Array(
321
+ await globalThis.crypto.subtle.sign(
322
+ 'HMAC',
323
+ baseKey,
324
+ Bytes.concat(domain, Bytes.fromNumber(0, { size: 4 })),
325
+ ),
326
+ )
327
+ try {
328
+ return await globalThis.crypto.subtle.importKey(
329
+ 'raw',
330
+ key,
331
+ { name: 'AES-GCM' },
332
+ false,
333
+ ['encrypt', 'decrypt'],
334
+ )
335
+ } finally {
336
+ key.fill(0)
337
+ }
338
+ }
339
+
340
+ const fromSeedDomain = Bytes.fromString('ox.aesGcm.fromSeed.v1')
package/core/Ed25519.ts CHANGED
@@ -4,8 +4,10 @@ import {
4
4
  edwardsToMontgomeryPub,
5
5
  } from '@noble/curves/ed25519'
6
6
  import * as Bytes from './Bytes.js'
7
- import type * as Errors from './Errors.js'
7
+ import * as Errors from './Errors.js'
8
8
  import * as Hex from './Hex.js'
9
+ import * as keyDerivation from './internal/keyDerivation.js'
10
+ import * as Mnemonic from './Mnemonic.js'
9
11
 
10
12
  /** Re-export of noble/curves Ed25519 utilities. */
11
13
  export const noble = ed25519
@@ -61,6 +63,115 @@ export declare namespace createKeyPair {
61
63
  | Errors.GlobalErrorType
62
64
  }
63
65
 
66
+ /**
67
+ * Derives an Ed25519 private key from a BIP-39 mnemonic.
68
+ *
69
+ * This is equivalent to passing `Mnemonic.toSeed(mnemonic, { passphrase })`
70
+ * to `Ed25519.fromSeed`.
71
+ *
72
+ * @example
73
+ * ```ts twoslash
74
+ * import { Ed25519 } from 'ox'
75
+ *
76
+ * const privateKey = Ed25519.fromMnemonic(
77
+ * 'test test test test test test test test test test test junk'
78
+ * )
79
+ * ```
80
+ *
81
+ * @param mnemonic - BIP-39 mnemonic phrase.
82
+ * @param options - Options.
83
+ * @returns An Ed25519 private key.
84
+ */
85
+ export function fromMnemonic<as extends 'Hex' | 'Bytes' = 'Hex'>(
86
+ mnemonic: string,
87
+ options: fromMnemonic.Options<as> = {},
88
+ ): fromMnemonic.ReturnType<as> {
89
+ const { passphrase } = options
90
+ const seed = Mnemonic.toSeed(mnemonic, { passphrase })
91
+ try {
92
+ return fromSeed(seed, options)
93
+ } finally {
94
+ seed.fill(0)
95
+ }
96
+ }
97
+
98
+ export declare namespace fromMnemonic {
99
+ type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
100
+ /**
101
+ * Format of the returned private key.
102
+ * @default 'Hex'
103
+ */
104
+ as?: as | 'Hex' | 'Bytes' | undefined
105
+ /** Optional BIP-39 passphrase. */
106
+ passphrase?: string | undefined
107
+ }
108
+
109
+ type ReturnType<as extends 'Hex' | 'Bytes'> = fromSeed.ReturnType<as>
110
+
111
+ type ErrorType = fromSeed.ErrorType
112
+ }
113
+
114
+ /**
115
+ * Derives an Ed25519 private key from a seed.
116
+ *
117
+ * The seed must contain at least 32 bytes of cryptographically strong key
118
+ * material. Do not pass a password directly; use a password KDF first.
119
+ *
120
+ * The permanent derivation contract uses the seed as the HMAC-SHA256
121
+ * key. The HMAC message uses the `ox.ed25519.fromSeed.v1` domain followed by a
122
+ * 32-bit big-endian counter set to zero.
123
+ *
124
+ * @example
125
+ * ```ts twoslash
126
+ * import { Ed25519 } from 'ox'
127
+ *
128
+ * const privateKey = Ed25519.fromSeed(
129
+ * '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
130
+ * )
131
+ * ```
132
+ *
133
+ * @param seed - Seed containing at least 32 bytes of cryptographically strong key material.
134
+ * @param options - Options.
135
+ * @returns An Ed25519 private key.
136
+ */
137
+ export function fromSeed<as extends 'Hex' | 'Bytes' = 'Hex'>(
138
+ seed: Hex.Hex | Bytes.Bytes,
139
+ options: fromSeed.Options<as> = {},
140
+ ): fromSeed.ReturnType<as> {
141
+ const { as = 'Hex' } = options
142
+ const bytes = Bytes.from(seed)
143
+ if (bytes.length < 32) throw new InvalidSeedSizeError({ size: bytes.length })
144
+
145
+ const privateKey = keyDerivation.derive(bytes, fromSeedDomain)
146
+ if (as === 'Hex') {
147
+ const value = Hex.fromBytes(privateKey)
148
+ privateKey.fill(0)
149
+ return value as never
150
+ }
151
+ return privateKey as never
152
+ }
153
+
154
+ export declare namespace fromSeed {
155
+ type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
156
+ /**
157
+ * Format of the returned private key.
158
+ * @default 'Hex'
159
+ */
160
+ as?: as | 'Hex' | 'Bytes' | undefined
161
+ }
162
+
163
+ type ReturnType<as extends 'Hex' | 'Bytes'> =
164
+ | (as extends 'Bytes' ? Bytes.Bytes : never)
165
+ | (as extends 'Hex' ? Hex.Hex : never)
166
+
167
+ type ErrorType =
168
+ | Bytes.from.ErrorType
169
+ | Hex.fromBytes.ErrorType
170
+ | keyDerivation.derive.ErrorType
171
+ | InvalidSeedSizeError
172
+ | Errors.GlobalErrorType
173
+ }
174
+
64
175
  /**
65
176
  * Computes the Ed25519 public key from a provided private key.
66
177
  *
@@ -337,3 +448,24 @@ export declare namespace toX25519PrivateKey {
337
448
  | Hex.fromBytes.ErrorType
338
449
  | Errors.GlobalErrorType
339
450
  }
451
+
452
+ /** Thrown when a seed contains fewer than 32 bytes. */
453
+ export class InvalidSeedSizeError extends Errors.BaseError {
454
+ override readonly name = 'Ed25519.InvalidSeedSizeError'
455
+
456
+ constructor(options: InvalidSeedSizeError.Options) {
457
+ super(
458
+ `Seed must contain at least 32 bytes. Received ${options.size} bytes.`,
459
+ )
460
+ }
461
+ }
462
+
463
+ export declare namespace InvalidSeedSizeError {
464
+ /** Options for `Ed25519.InvalidSeedSizeError`. */
465
+ type Options = {
466
+ /** Received seed size. */
467
+ size: number
468
+ }
469
+ }
470
+
471
+ const fromSeedDomain = Bytes.fromString('ox.ed25519.fromSeed.v1')
package/core/P256.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { secp256r1 } from '@noble/curves/p256'
2
2
  import * as Bytes from './Bytes.js'
3
- import type * as Errors from './Errors.js'
3
+ import * as Errors from './Errors.js'
4
4
  import * as Hex from './Hex.js'
5
5
  import * as Entropy from './internal/entropy.js'
6
+ import * as keyDerivation from './internal/keyDerivation.js'
7
+ import * as Mnemonic from './Mnemonic.js'
6
8
  import * as PublicKey from './PublicKey.js'
7
9
  import type * as Signature from './Signature.js'
8
10
 
@@ -57,6 +59,117 @@ export declare namespace createKeyPair {
57
59
  | Errors.GlobalErrorType
58
60
  }
59
61
 
62
+ /**
63
+ * Derives a valid P256 private key from a BIP-39 mnemonic.
64
+ *
65
+ * This is equivalent to passing `Mnemonic.toSeed(mnemonic, { passphrase })`
66
+ * to `P256.fromSeed`.
67
+ *
68
+ * @example
69
+ * ```ts twoslash
70
+ * import { P256 } from 'ox'
71
+ *
72
+ * const privateKey = P256.fromMnemonic(
73
+ * 'test test test test test test test test test test test junk'
74
+ * )
75
+ * ```
76
+ *
77
+ * @param mnemonic - BIP-39 mnemonic phrase.
78
+ * @param options - Options.
79
+ * @returns A valid P256 private key.
80
+ */
81
+ export function fromMnemonic<as extends 'Hex' | 'Bytes' = 'Hex'>(
82
+ mnemonic: string,
83
+ options: fromMnemonic.Options<as> = {},
84
+ ): fromMnemonic.ReturnType<as> {
85
+ const { passphrase } = options
86
+ const seed = Mnemonic.toSeed(mnemonic, { passphrase })
87
+ try {
88
+ return fromSeed(seed, options)
89
+ } finally {
90
+ seed.fill(0)
91
+ }
92
+ }
93
+
94
+ export declare namespace fromMnemonic {
95
+ type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
96
+ /**
97
+ * Format of the returned private key.
98
+ * @default 'Hex'
99
+ */
100
+ as?: as | 'Hex' | 'Bytes' | undefined
101
+ /** Optional BIP-39 passphrase. */
102
+ passphrase?: string | undefined
103
+ }
104
+
105
+ type ReturnType<as extends 'Hex' | 'Bytes'> = fromSeed.ReturnType<as>
106
+
107
+ type ErrorType = fromSeed.ErrorType
108
+ }
109
+
110
+ /**
111
+ * Derives a valid P256 private key from a seed.
112
+ *
113
+ * The seed must contain at least 32 bytes of cryptographically strong key
114
+ * material. Do not pass a password directly; use a password KDF first.
115
+ *
116
+ * The permanent derivation contract uses the seed as the HMAC-SHA256
117
+ * key. The HMAC message uses the `ox.p256.fromSeed.v1` domain followed by a
118
+ * 32-bit big-endian counter starting at zero. Invalid scalars are skipped.
119
+ *
120
+ * @example
121
+ * ```ts twoslash
122
+ * import { P256 } from 'ox'
123
+ *
124
+ * const privateKey = P256.fromSeed(
125
+ * '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
126
+ * )
127
+ * ```
128
+ *
129
+ * @param seed - Seed containing at least 32 bytes of cryptographically strong key material.
130
+ * @param options - Options.
131
+ * @returns A valid P256 private key.
132
+ */
133
+ export function fromSeed<as extends 'Hex' | 'Bytes' = 'Hex'>(
134
+ seed: Hex.Hex | Bytes.Bytes,
135
+ options: fromSeed.Options<as> = {},
136
+ ): fromSeed.ReturnType<as> {
137
+ const { as = 'Hex' } = options
138
+ const bytes = Bytes.from(seed)
139
+ if (bytes.length < 32) throw new InvalidSeedSizeError({ size: bytes.length })
140
+
141
+ const privateKey = keyDerivation.derive(bytes, fromSeedDomain, {
142
+ validate: noble.utils.isValidPrivateKey,
143
+ })
144
+ if (as === 'Hex') {
145
+ const value = Hex.fromBytes(privateKey)
146
+ privateKey.fill(0)
147
+ return value as never
148
+ }
149
+ return privateKey as never
150
+ }
151
+
152
+ export declare namespace fromSeed {
153
+ type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
154
+ /**
155
+ * Format of the returned private key.
156
+ * @default 'Hex'
157
+ */
158
+ as?: as | 'Hex' | 'Bytes' | undefined
159
+ }
160
+
161
+ type ReturnType<as extends 'Hex' | 'Bytes'> =
162
+ | (as extends 'Bytes' ? Bytes.Bytes : never)
163
+ | (as extends 'Hex' ? Hex.Hex : never)
164
+
165
+ type ErrorType =
166
+ | Bytes.from.ErrorType
167
+ | Hex.fromBytes.ErrorType
168
+ | keyDerivation.derive.ErrorType
169
+ | InvalidSeedSizeError
170
+ | Errors.GlobalErrorType
171
+ }
172
+
60
173
  /**
61
174
  * Computes the P256 ECDSA public key from a provided private key.
62
175
  *
@@ -354,3 +467,24 @@ export declare namespace verify {
354
467
 
355
468
  type ErrorType = Errors.GlobalErrorType
356
469
  }
470
+
471
+ /** Thrown when a seed contains fewer than 32 bytes. */
472
+ export class InvalidSeedSizeError extends Errors.BaseError {
473
+ override readonly name = 'P256.InvalidSeedSizeError'
474
+
475
+ constructor(options: InvalidSeedSizeError.Options) {
476
+ super(
477
+ `Seed must contain at least 32 bytes. Received ${options.size} bytes.`,
478
+ )
479
+ }
480
+ }
481
+
482
+ export declare namespace InvalidSeedSizeError {
483
+ /** Options for `P256.InvalidSeedSizeError`. */
484
+ type Options = {
485
+ /** Received seed size. */
486
+ size: number
487
+ }
488
+ }
489
+
490
+ const fromSeedDomain = Bytes.fromString('ox.p256.fromSeed.v1')
package/core/Secp256k1.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import { secp256k1 } from '@noble/curves/secp256k1'
2
2
  import * as Address from './Address.js'
3
3
  import * as Bytes from './Bytes.js'
4
- import type * as Errors from './Errors.js'
4
+ import * as Errors from './Errors.js'
5
5
  import * as Hex from './Hex.js'
6
6
  import * as Entropy from './internal/entropy.js'
7
+ import * as keyDerivation from './internal/keyDerivation.js'
7
8
  import type { OneOf } from './internal/types.js'
9
+ import * as Mnemonic from './Mnemonic.js'
8
10
  import * as PublicKey from './PublicKey.js'
9
11
  import type * as Signature from './Signature.js'
10
12
 
@@ -59,6 +61,115 @@ export declare namespace createKeyPair {
59
61
  | Errors.GlobalErrorType
60
62
  }
61
63
 
64
+ /**
65
+ * Derives a valid secp256k1 private key from a BIP-39 mnemonic.
66
+ *
67
+ * This is equivalent to `Mnemonic.toPrivateKey`, and derives the
68
+ * private key at `m/44'/60'/0'/0/0` by default.
69
+ *
70
+ * @example
71
+ * ```ts twoslash
72
+ * import { Secp256k1 } from 'ox'
73
+ *
74
+ * const privateKey = Secp256k1.fromMnemonic(
75
+ * 'test test test test test test test test test test test junk'
76
+ * )
77
+ * ```
78
+ *
79
+ * @param mnemonic - BIP-39 mnemonic phrase.
80
+ * @param options - Options.
81
+ * @returns A valid secp256k1 private key.
82
+ */
83
+ export function fromMnemonic<as extends 'Hex' | 'Bytes' = 'Hex'>(
84
+ mnemonic: string,
85
+ options: fromMnemonic.Options<as> = {},
86
+ ): fromMnemonic.ReturnType<as> {
87
+ const { as = 'Hex', passphrase, path } = options
88
+ return Mnemonic.toPrivateKey(mnemonic, { as, passphrase, path }) as never
89
+ }
90
+
91
+ export declare namespace fromMnemonic {
92
+ type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
93
+ /**
94
+ * Format of the returned private key.
95
+ * @default 'Hex'
96
+ */
97
+ as?: as | 'Hex' | 'Bytes' | undefined
98
+ /** Derivation path. @default `m/44'/60'/0'/0/0` */
99
+ path?: string | undefined
100
+ /** Optional BIP-39 passphrase. */
101
+ passphrase?: string | undefined
102
+ }
103
+
104
+ type ReturnType<as extends 'Hex' | 'Bytes'> =
105
+ Mnemonic.toPrivateKey.ReturnType<as>
106
+
107
+ type ErrorType = Mnemonic.toPrivateKey.ErrorType
108
+ }
109
+
110
+ /**
111
+ * Derives a valid secp256k1 private key from a seed.
112
+ *
113
+ * The seed must contain at least 32 bytes of cryptographically strong key
114
+ * material. Do not pass a password directly; use a password KDF first.
115
+ *
116
+ * The permanent derivation contract uses the seed as the HMAC-SHA256
117
+ * key. The HMAC message uses the `ox.secp256k1.fromSeed.v1` domain followed by
118
+ * a 32-bit big-endian counter starting at zero. Invalid scalars are skipped.
119
+ *
120
+ * @example
121
+ * ```ts twoslash
122
+ * import { Secp256k1 } from 'ox'
123
+ *
124
+ * const privateKey = Secp256k1.fromSeed(
125
+ * '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
126
+ * )
127
+ * ```
128
+ *
129
+ * @param seed - Seed containing at least 32 bytes of cryptographically strong key material.
130
+ * @param options - Options.
131
+ * @returns A valid secp256k1 private key.
132
+ */
133
+ export function fromSeed<as extends 'Hex' | 'Bytes' = 'Hex'>(
134
+ seed: Hex.Hex | Bytes.Bytes,
135
+ options: fromSeed.Options<as> = {},
136
+ ): fromSeed.ReturnType<as> {
137
+ const { as = 'Hex' } = options
138
+ const bytes = Bytes.from(seed)
139
+ if (bytes.length < 32) throw new InvalidSeedSizeError({ size: bytes.length })
140
+
141
+ const privateKey = keyDerivation.derive(bytes, fromSeedDomain, {
142
+ validate: noble.utils.isValidPrivateKey,
143
+ })
144
+ if (as === 'Hex') {
145
+ const value = Hex.fromBytes(privateKey)
146
+ privateKey.fill(0)
147
+ return value as never
148
+ }
149
+ return privateKey as never
150
+ }
151
+
152
+ export declare namespace fromSeed {
153
+ type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
154
+ /**
155
+ * Format of the returned private key.
156
+ * @default 'Hex'
157
+ */
158
+ as?: as | 'Hex' | 'Bytes' | undefined
159
+ }
160
+
161
+ type ReturnType<as extends 'Hex' | 'Bytes'> =
162
+ | (as extends 'Bytes' ? Bytes.Bytes : never)
163
+ | (as extends 'Hex' ? Hex.Hex : never)
164
+
165
+ type ErrorType =
166
+ | Bytes.from.ErrorType
167
+ | Hex.fromBytes.ErrorType
168
+ | keyDerivation.derive.ErrorType
169
+ | InvalidSeedSizeError
170
+ | Errors.GlobalErrorType
171
+ }
172
+
62
173
  /**
63
174
  * Computes the secp256k1 ECDSA public key from a provided private key.
64
175
  *
@@ -423,3 +534,24 @@ export declare namespace verify {
423
534
 
424
535
  type ErrorType = Errors.GlobalErrorType
425
536
  }
537
+
538
+ /** Thrown when a seed contains fewer than 32 bytes. */
539
+ export class InvalidSeedSizeError extends Errors.BaseError {
540
+ override readonly name = 'Secp256k1.InvalidSeedSizeError'
541
+
542
+ constructor(options: InvalidSeedSizeError.Options) {
543
+ super(
544
+ `Seed must contain at least 32 bytes. Received ${options.size} bytes.`,
545
+ )
546
+ }
547
+ }
548
+
549
+ export declare namespace InvalidSeedSizeError {
550
+ /** Options for `Secp256k1.InvalidSeedSizeError`. */
551
+ type Options = {
552
+ /** Received seed size. */
553
+ size: number
554
+ }
555
+ }
556
+
557
+ const fromSeedDomain = Bytes.fromString('ox.secp256k1.fromSeed.v1')
@@ -0,0 +1,33 @@
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
+ export declare namespace derive {
24
+ type Options = {
25
+ validate?: ((key: Bytes.Bytes) => boolean) | undefined
26
+ }
27
+
28
+ type ErrorType =
29
+ | Bytes.concat.ErrorType
30
+ | Bytes.fromNumber.ErrorType
31
+ | Hash.hmac256.ErrorType
32
+ | Errors.GlobalErrorType
33
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ox",
3
3
  "description": "Ethereum Standard Library",
4
- "version": "0.14.33",
4
+ "version": "0.14.35",
5
5
  "main": "./_cjs/index.js",
6
6
  "module": "./_esm/index.js",
7
7
  "types": "./_types/index.d.ts",