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
@@ -4,6 +4,8 @@ import * as Errors from './Errors.js'
4
4
  import * as Hash from './Hash.js'
5
5
  import * as Hex from './Hex.js'
6
6
  import * as engine from './internal/ed25519.js'
7
+ import * as keyDerivation from './internal/keyDerivation.js'
8
+ import * as mnemonic_ from './internal/mnemonic.js'
7
9
 
8
10
  /** Re-export of noble/curves Ed25519 utilities. */
9
11
  export const noble = ed25519
@@ -63,8 +65,8 @@ export declare namespace createKeyPair {
63
65
  * Derives an Ed25519 private key from a 32-byte WebAuthn PRF output.
64
66
  *
65
67
  * The permanent derivation contract uses the PRF output as the HMAC-SHA256
66
- * key. Its message is the UTF-8 bytes of `ox.ed25519.fromPrf.v1` followed by
67
- * a 32-bit big-endian counter set to zero.
68
+ * key. The HMAC message uses the `ox.ed25519.fromPrf.v1` domain followed by a
69
+ * 32-bit big-endian counter set to zero.
68
70
  *
69
71
  * @example
70
72
  * ```ts twoslash
@@ -89,7 +91,7 @@ export function fromPrf<as extends 'Hex' | 'Bytes' = 'Hex'>(
89
91
 
90
92
  const privateKey = Hash.hmac256(
91
93
  bytes,
92
- Bytes.concat(fromPrfLabel, Bytes.fromNumber(0, { size: 4 })),
94
+ Bytes.concat(fromPrfDomain, Bytes.fromNumber(0, { size: 4 })),
93
95
  { as: 'Bytes' },
94
96
  )
95
97
  if (as === 'Hex') {
@@ -123,6 +125,115 @@ export declare namespace fromPrf {
123
125
  | Errors.GlobalErrorType
124
126
  }
125
127
 
128
+ /**
129
+ * Derives an Ed25519 private key from a BIP-39 mnemonic.
130
+ *
131
+ * This is equivalent to passing `Mnemonic.toSeed(mnemonic, { passphrase })`
132
+ * to {@link ox#Ed25519.fromSeed}.
133
+ *
134
+ * @example
135
+ * ```ts twoslash
136
+ * import { Ed25519 } from 'ox'
137
+ *
138
+ * const privateKey = Ed25519.fromMnemonic(
139
+ * 'test test test test test test test test test test test junk'
140
+ * )
141
+ * ```
142
+ *
143
+ * @param mnemonic - BIP-39 mnemonic phrase.
144
+ * @param options - Options.
145
+ * @returns An Ed25519 private key.
146
+ */
147
+ export function fromMnemonic<as extends 'Hex' | 'Bytes' = 'Hex'>(
148
+ mnemonic: string,
149
+ options: fromMnemonic.Options<as> = {},
150
+ ): fromMnemonic.ReturnType<as> {
151
+ const { passphrase } = options
152
+ const seed = mnemonic_.toSeed(mnemonic, passphrase)
153
+ try {
154
+ return fromSeed(seed, options)
155
+ } finally {
156
+ seed.fill(0)
157
+ }
158
+ }
159
+
160
+ export declare namespace fromMnemonic {
161
+ type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
162
+ /**
163
+ * Format of the returned private key.
164
+ * @default 'Hex'
165
+ */
166
+ as?: as | 'Hex' | 'Bytes' | undefined
167
+ /** Optional BIP-39 passphrase. */
168
+ passphrase?: string | undefined
169
+ }
170
+
171
+ type ReturnType<as extends 'Hex' | 'Bytes'> = fromSeed.ReturnType<as>
172
+
173
+ type ErrorType = fromSeed.ErrorType
174
+ }
175
+
176
+ /**
177
+ * Derives an Ed25519 private key from a seed.
178
+ *
179
+ * The seed must contain at least 32 bytes of cryptographically strong key
180
+ * material. Do not pass a password directly; use a password KDF first.
181
+ *
182
+ * The permanent derivation contract uses the seed as the HMAC-SHA256
183
+ * key. The HMAC message uses the `ox.ed25519.fromSeed.v1` domain followed by a
184
+ * 32-bit big-endian counter set to zero.
185
+ *
186
+ * @example
187
+ * ```ts twoslash
188
+ * import { Ed25519 } from 'ox'
189
+ *
190
+ * const privateKey = Ed25519.fromSeed(
191
+ * '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
192
+ * )
193
+ * ```
194
+ *
195
+ * @param seed - Seed containing at least 32 bytes of cryptographically strong key material.
196
+ * @param options - Options.
197
+ * @returns An Ed25519 private key.
198
+ */
199
+ export function fromSeed<as extends 'Hex' | 'Bytes' = 'Hex'>(
200
+ seed: Hex.Hex | Bytes.Bytes,
201
+ options: fromSeed.Options<as> = {},
202
+ ): fromSeed.ReturnType<as> {
203
+ const { as = 'Hex' } = options
204
+ const bytes = Bytes.from(seed)
205
+ if (bytes.length < 32) throw new InvalidSeedSizeError({ size: bytes.length })
206
+
207
+ const privateKey = keyDerivation.derive(bytes, fromSeedDomain)
208
+ if (as === 'Hex') {
209
+ const value = Hex.fromBytes(privateKey)
210
+ privateKey.fill(0)
211
+ return value as never
212
+ }
213
+ return privateKey as never
214
+ }
215
+
216
+ export declare namespace fromSeed {
217
+ type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
218
+ /**
219
+ * Format of the returned private key.
220
+ * @default 'Hex'
221
+ */
222
+ as?: as | 'Hex' | 'Bytes' | undefined
223
+ }
224
+
225
+ type ReturnType<as extends 'Hex' | 'Bytes'> =
226
+ | (as extends 'Bytes' ? Bytes.Bytes : never)
227
+ | (as extends 'Hex' ? Hex.Hex : never)
228
+
229
+ type ErrorType =
230
+ | Bytes.from.ErrorType
231
+ | Hex.fromBytes.ErrorType
232
+ | keyDerivation.derive.ErrorType
233
+ | InvalidSeedSizeError
234
+ | Errors.GlobalErrorType
235
+ }
236
+
126
237
  /**
127
238
  * Computes the Ed25519 public key from a provided private key.
128
239
  *
@@ -430,4 +541,24 @@ export declare namespace InvalidPrfSizeError {
430
541
  }
431
542
  }
432
543
 
433
- const fromPrfLabel = Bytes.fromString('ox.ed25519.fromPrf.v1')
544
+ /** Thrown when a seed contains fewer than 32 bytes. */
545
+ export class InvalidSeedSizeError extends Errors.BaseError {
546
+ override readonly name = 'Ed25519.InvalidSeedSizeError'
547
+
548
+ constructor(options: InvalidSeedSizeError.Options) {
549
+ super(
550
+ `Seed must contain at least 32 bytes. Received ${options.size} bytes.`,
551
+ )
552
+ }
553
+ }
554
+
555
+ export declare namespace InvalidSeedSizeError {
556
+ /** Options for {@link ox#Ed25519.InvalidSeedSizeError}. */
557
+ type Options = {
558
+ /** Received seed size. */
559
+ size: number
560
+ }
561
+ }
562
+
563
+ const fromPrfDomain = Bytes.fromString('ox.ed25519.fromPrf.v1')
564
+ const fromSeedDomain = Bytes.fromString('ox.ed25519.fromSeed.v1')
@@ -4,7 +4,9 @@ import * as Errors from './Errors.js'
4
4
  import * as Hash from './Hash.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 * as engine from './internal/mlDsa44.js'
9
+ import * as mnemonic_ from './internal/mnemonic.js'
8
10
 
9
11
  /** Re-export of noble/post-quantum ML-DSA-44 utilities. */
10
12
  export const noble = ml_dsa44
@@ -77,8 +79,8 @@ export declare namespace createKeyPair {
77
79
  * Derives an ML-DSA-44 private key from a 32-byte WebAuthn PRF output.
78
80
  *
79
81
  * The permanent derivation contract uses the PRF output as the HMAC-SHA256
80
- * key. Its message is the UTF-8 bytes of `ox.mldsa44.fromPrf.v1` followed by
81
- * a 32-bit big-endian counter set to zero.
82
+ * key. The HMAC message uses the `ox.mldsa44.fromPrf.v1` domain followed by a
83
+ * 32-bit big-endian counter set to zero.
82
84
  *
83
85
  * @example
84
86
  * ```ts twoslash
@@ -103,7 +105,7 @@ export function fromPrf<as extends 'Hex' | 'Bytes' = 'Hex'>(
103
105
 
104
106
  const privateKey = Hash.hmac256(
105
107
  bytes,
106
- Bytes.concat(fromPrfLabel, Bytes.fromNumber(0, { size: 4 })),
108
+ Bytes.concat(fromPrfDomain, Bytes.fromNumber(0, { size: 4 })),
107
109
  { as: 'Bytes' },
108
110
  )
109
111
  if (as === 'Hex') {
@@ -137,6 +139,115 @@ export declare namespace fromPrf {
137
139
  | Errors.GlobalErrorType
138
140
  }
139
141
 
142
+ /**
143
+ * Derives an ML-DSA-44 private key from a BIP-39 mnemonic.
144
+ *
145
+ * This is equivalent to passing `Mnemonic.toSeed(mnemonic, { passphrase })`
146
+ * to {@link ox#MlDsa44.fromSeed}.
147
+ *
148
+ * @example
149
+ * ```ts twoslash
150
+ * import { MlDsa44 } from 'ox'
151
+ *
152
+ * const privateKey = MlDsa44.fromMnemonic(
153
+ * 'test test test test test test test test test test test junk'
154
+ * )
155
+ * ```
156
+ *
157
+ * @param mnemonic - BIP-39 mnemonic phrase.
158
+ * @param options - Options.
159
+ * @returns An ML-DSA-44 private key (32-byte seed).
160
+ */
161
+ export function fromMnemonic<as extends 'Hex' | 'Bytes' = 'Hex'>(
162
+ mnemonic: string,
163
+ options: fromMnemonic.Options<as> = {},
164
+ ): fromMnemonic.ReturnType<as> {
165
+ const { passphrase } = options
166
+ const seed = mnemonic_.toSeed(mnemonic, passphrase)
167
+ try {
168
+ return fromSeed(seed, options)
169
+ } finally {
170
+ seed.fill(0)
171
+ }
172
+ }
173
+
174
+ export declare namespace fromMnemonic {
175
+ type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
176
+ /**
177
+ * Format of the returned private key.
178
+ * @default 'Hex'
179
+ */
180
+ as?: as | 'Hex' | 'Bytes' | undefined
181
+ /** Optional BIP-39 passphrase. */
182
+ passphrase?: string | undefined
183
+ }
184
+
185
+ type ReturnType<as extends 'Hex' | 'Bytes'> = fromSeed.ReturnType<as>
186
+
187
+ type ErrorType = fromSeed.ErrorType
188
+ }
189
+
190
+ /**
191
+ * Derives an ML-DSA-44 private key from a seed.
192
+ *
193
+ * The seed must contain at least 32 bytes of cryptographically strong key
194
+ * material. Do not pass a password directly; use a password KDF first.
195
+ *
196
+ * The permanent derivation contract uses the seed as the HMAC-SHA256
197
+ * key. The HMAC message uses the `ox.mldsa44.fromSeed.v1` domain followed by a
198
+ * 32-bit big-endian counter set to zero.
199
+ *
200
+ * @example
201
+ * ```ts twoslash
202
+ * import { MlDsa44 } from 'ox'
203
+ *
204
+ * const privateKey = MlDsa44.fromSeed(
205
+ * '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
206
+ * )
207
+ * ```
208
+ *
209
+ * @param seed - Seed containing at least 32 bytes of cryptographically strong key material.
210
+ * @param options - Options.
211
+ * @returns An ML-DSA-44 private key (32-byte seed).
212
+ */
213
+ export function fromSeed<as extends 'Hex' | 'Bytes' = 'Hex'>(
214
+ seed: Hex.Hex | Bytes.Bytes,
215
+ options: fromSeed.Options<as> = {},
216
+ ): fromSeed.ReturnType<as> {
217
+ const { as = 'Hex' } = options
218
+ const bytes = Bytes.from(seed)
219
+ if (bytes.length < 32) throw new InvalidSeedSizeError({ size: bytes.length })
220
+
221
+ const privateKey = keyDerivation.derive(bytes, fromSeedDomain)
222
+ if (as === 'Hex') {
223
+ const value = Hex.fromBytes(privateKey)
224
+ privateKey.fill(0)
225
+ return value as never
226
+ }
227
+ return privateKey as never
228
+ }
229
+
230
+ export declare namespace fromSeed {
231
+ type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
232
+ /**
233
+ * Format of the returned private key.
234
+ * @default 'Hex'
235
+ */
236
+ as?: as | 'Hex' | 'Bytes' | undefined
237
+ }
238
+
239
+ type ReturnType<as extends 'Hex' | 'Bytes'> =
240
+ | (as extends 'Bytes' ? Bytes.Bytes : never)
241
+ | (as extends 'Hex' ? Hex.Hex : never)
242
+
243
+ type ErrorType =
244
+ | Bytes.from.ErrorType
245
+ | Hex.fromBytes.ErrorType
246
+ | keyDerivation.derive.ErrorType
247
+ | InvalidSeedSizeError
248
+ | Errors.GlobalErrorType
249
+ }
250
+
140
251
  /**
141
252
  * Computes the ML-DSA-44 public key from a provided private key.
142
253
  *
@@ -402,6 +513,25 @@ export declare namespace InvalidPrfSizeError {
402
513
  }
403
514
  }
404
515
 
516
+ /** Thrown when a seed contains fewer than 32 bytes. */
517
+ export class InvalidSeedSizeError extends Errors.BaseError {
518
+ override readonly name = 'MlDsa44.InvalidSeedSizeError'
519
+
520
+ constructor(options: InvalidSeedSizeError.Options) {
521
+ super(
522
+ `Seed must contain at least 32 bytes. Received ${options.size} bytes.`,
523
+ )
524
+ }
525
+ }
526
+
527
+ export declare namespace InvalidSeedSizeError {
528
+ /** Options for {@link ox#MlDsa44.InvalidSeedSizeError}. */
529
+ type Options = {
530
+ /** Received seed size. */
531
+ size: number
532
+ }
533
+ }
534
+
405
535
  function toContextBytes(
406
536
  context: Hex.Hex | Bytes.Bytes | undefined,
407
537
  ): Bytes.Bytes | undefined {
@@ -412,4 +542,5 @@ function toContextBytes(
412
542
  return bytes
413
543
  }
414
544
 
415
- const fromPrfLabel = Bytes.fromString('ox.mldsa44.fromPrf.v1')
545
+ const fromPrfDomain = Bytes.fromString('ox.mldsa44.fromPrf.v1')
546
+ const fromSeedDomain = Bytes.fromString('ox.mldsa44.fromSeed.v1')
package/src/core/P256.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { p256 as noble_p256 } from '@noble/curves/nist.js'
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 {
6
6
  formatPublicKey,
@@ -8,6 +8,8 @@ import {
8
8
  normalizePublicKey,
9
9
  normalizeSignature,
10
10
  } from './internal/cryptoIo.js'
11
+ import * as keyDerivation from './internal/keyDerivation.js'
12
+ import * as mnemonic_ from './internal/mnemonic.js'
11
13
  import * as engine from './internal/p256.js'
12
14
  import * as Entropy from './internal/entropy.js'
13
15
  import {
@@ -69,6 +71,117 @@ export declare namespace createKeyPair {
69
71
  | Errors.GlobalErrorType
70
72
  }
71
73
 
74
+ /**
75
+ * Derives a valid P256 private key from a BIP-39 mnemonic.
76
+ *
77
+ * This is equivalent to passing `Mnemonic.toSeed(mnemonic, { passphrase })`
78
+ * to {@link ox#P256.fromSeed}.
79
+ *
80
+ * @example
81
+ * ```ts twoslash
82
+ * import { P256 } from 'ox'
83
+ *
84
+ * const privateKey = P256.fromMnemonic(
85
+ * 'test test test test test test test test test test test junk'
86
+ * )
87
+ * ```
88
+ *
89
+ * @param mnemonic - BIP-39 mnemonic phrase.
90
+ * @param options - Options.
91
+ * @returns A valid P256 private key.
92
+ */
93
+ export function fromMnemonic<as extends 'Hex' | 'Bytes' = 'Hex'>(
94
+ mnemonic: string,
95
+ options: fromMnemonic.Options<as> = {},
96
+ ): fromMnemonic.ReturnType<as> {
97
+ const { passphrase } = options
98
+ const seed = mnemonic_.toSeed(mnemonic, passphrase)
99
+ try {
100
+ return fromSeed(seed, options)
101
+ } finally {
102
+ seed.fill(0)
103
+ }
104
+ }
105
+
106
+ export declare namespace fromMnemonic {
107
+ type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
108
+ /**
109
+ * Format of the returned private key.
110
+ * @default 'Hex'
111
+ */
112
+ as?: as | 'Hex' | 'Bytes' | undefined
113
+ /** Optional BIP-39 passphrase. */
114
+ passphrase?: string | undefined
115
+ }
116
+
117
+ type ReturnType<as extends 'Hex' | 'Bytes'> = fromSeed.ReturnType<as>
118
+
119
+ type ErrorType = fromSeed.ErrorType
120
+ }
121
+
122
+ /**
123
+ * Derives a valid P256 private key from a seed.
124
+ *
125
+ * The seed must contain at least 32 bytes of cryptographically strong key
126
+ * material. Do not pass a password directly; use a password KDF first.
127
+ *
128
+ * The permanent derivation contract uses the seed as the HMAC-SHA256
129
+ * key. The HMAC message uses the `ox.p256.fromSeed.v1` domain followed by a
130
+ * 32-bit big-endian counter starting at zero. Invalid scalars are skipped.
131
+ *
132
+ * @example
133
+ * ```ts twoslash
134
+ * import { P256 } from 'ox'
135
+ *
136
+ * const privateKey = P256.fromSeed(
137
+ * '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
138
+ * )
139
+ * ```
140
+ *
141
+ * @param seed - Seed containing at least 32 bytes of cryptographically strong key material.
142
+ * @param options - Options.
143
+ * @returns A valid P256 private key.
144
+ */
145
+ export function fromSeed<as extends 'Hex' | 'Bytes' = 'Hex'>(
146
+ seed: Hex.Hex | Bytes.Bytes,
147
+ options: fromSeed.Options<as> = {},
148
+ ): fromSeed.ReturnType<as> {
149
+ const { as = 'Hex' } = options
150
+ const bytes = Bytes.from(seed)
151
+ if (bytes.length < 32) throw new InvalidSeedSizeError({ size: bytes.length })
152
+
153
+ const privateKey = keyDerivation.derive(bytes, fromSeedDomain, {
154
+ validate: noble.utils.isValidSecretKey,
155
+ })
156
+ if (as === 'Hex') {
157
+ const value = Hex.fromBytes(privateKey)
158
+ privateKey.fill(0)
159
+ return value as never
160
+ }
161
+ return privateKey as never
162
+ }
163
+
164
+ export declare namespace fromSeed {
165
+ type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
166
+ /**
167
+ * Format of the returned private key.
168
+ * @default 'Hex'
169
+ */
170
+ as?: as | 'Hex' | 'Bytes' | undefined
171
+ }
172
+
173
+ type ReturnType<as extends 'Hex' | 'Bytes'> =
174
+ | (as extends 'Bytes' ? Bytes.Bytes : never)
175
+ | (as extends 'Hex' ? Hex.Hex : never)
176
+
177
+ type ErrorType =
178
+ | Bytes.from.ErrorType
179
+ | Hex.fromBytes.ErrorType
180
+ | keyDerivation.derive.ErrorType
181
+ | InvalidSeedSizeError
182
+ | Errors.GlobalErrorType
183
+ }
184
+
72
185
  /**
73
186
  * Computes the P256 ECDSA public key from a provided private key.
74
187
  *
@@ -404,3 +517,24 @@ export declare namespace verify {
404
517
 
405
518
  type ErrorType = Errors.GlobalErrorType
406
519
  }
520
+
521
+ /** Thrown when a seed contains fewer than 32 bytes. */
522
+ export class InvalidSeedSizeError extends Errors.BaseError {
523
+ override readonly name = 'P256.InvalidSeedSizeError'
524
+
525
+ constructor(options: InvalidSeedSizeError.Options) {
526
+ super(
527
+ `Seed must contain at least 32 bytes. Received ${options.size} bytes.`,
528
+ )
529
+ }
530
+ }
531
+
532
+ export declare namespace InvalidSeedSizeError {
533
+ /** Options for {@link ox#P256.InvalidSeedSizeError}. */
534
+ type Options = {
535
+ /** Received seed size. */
536
+ size: number
537
+ }
538
+ }
539
+
540
+ const fromSeedDomain = Bytes.fromString('ox.p256.fromSeed.v1')
@@ -10,6 +10,7 @@ import {
10
10
  normalizePublicKey,
11
11
  normalizeSignature,
12
12
  } from './internal/cryptoIo.js'
13
+ import * as keyDerivation from './internal/keyDerivation.js'
13
14
  import * as engine from './internal/secp256k1.js'
14
15
  import * as Entropy from './internal/entropy.js'
15
16
  import {
@@ -18,6 +19,7 @@ import {
18
19
  toRecoveredBytes,
19
20
  } from './internal/signature.js'
20
21
  import type { OneOf } from './internal/types.js'
22
+ import * as Mnemonic from './Mnemonic.js'
21
23
  import * as PublicKey from './PublicKey.js'
22
24
  import type * as Signature from './Signature.js'
23
25
 
@@ -76,7 +78,7 @@ export declare namespace createKeyPair {
76
78
  * Derives a valid secp256k1 private key from a 32-byte WebAuthn PRF output.
77
79
  *
78
80
  * The permanent derivation contract uses the PRF output as the HMAC-SHA256
79
- * key. Its message is the UTF-8 bytes of `ox.secp256k1.fromPrf.v1` followed by
81
+ * key. The HMAC message uses the `ox.secp256k1.fromPrf.v1` domain followed by
80
82
  * a 32-bit big-endian counter starting at zero. Invalid scalars are skipped.
81
83
  *
82
84
  * @example
@@ -103,7 +105,7 @@ export function fromPrf<as extends 'Hex' | 'Bytes' = 'Hex'>(
103
105
  for (let counter = 0; ; counter++) {
104
106
  const candidate = Hash.hmac256(
105
107
  bytes,
106
- Bytes.concat(fromPrfLabel, Bytes.fromNumber(counter, { size: 4 })),
108
+ Bytes.concat(fromPrfDomain, Bytes.fromNumber(counter, { size: 4 })),
107
109
  { as: 'Bytes' },
108
110
  )
109
111
  if (noble.utils.isValidSecretKey(candidate)) {
@@ -141,6 +143,115 @@ export declare namespace fromPrf {
141
143
  | Errors.GlobalErrorType
142
144
  }
143
145
 
146
+ /**
147
+ * Derives a valid secp256k1 private key from a BIP-39 mnemonic.
148
+ *
149
+ * This is equivalent to {@link ox#Mnemonic.toPrivateKey}, and derives the
150
+ * private key at `m/44'/60'/0'/0/0` by default.
151
+ *
152
+ * @example
153
+ * ```ts twoslash
154
+ * import { Secp256k1 } from 'ox'
155
+ *
156
+ * const privateKey = Secp256k1.fromMnemonic(
157
+ * 'test test test test test test test test test test test junk'
158
+ * )
159
+ * ```
160
+ *
161
+ * @param mnemonic - BIP-39 mnemonic phrase.
162
+ * @param options - Options.
163
+ * @returns A valid secp256k1 private key.
164
+ */
165
+ export function fromMnemonic<as extends 'Hex' | 'Bytes' = 'Hex'>(
166
+ mnemonic: string,
167
+ options: fromMnemonic.Options<as> = {},
168
+ ): fromMnemonic.ReturnType<as> {
169
+ const { as = 'Hex', passphrase, path } = options
170
+ return Mnemonic.toPrivateKey(mnemonic, { as, passphrase, path }) as never
171
+ }
172
+
173
+ export declare namespace fromMnemonic {
174
+ type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
175
+ /**
176
+ * Format of the returned private key.
177
+ * @default 'Hex'
178
+ */
179
+ as?: as | 'Hex' | 'Bytes' | undefined
180
+ /** Derivation path. @default `m/44'/60'/0'/0/0` */
181
+ path?: string | undefined
182
+ /** Optional BIP-39 passphrase. */
183
+ passphrase?: string | undefined
184
+ }
185
+
186
+ type ReturnType<as extends 'Hex' | 'Bytes'> =
187
+ Mnemonic.toPrivateKey.ReturnType<as>
188
+
189
+ type ErrorType = Mnemonic.toPrivateKey.ErrorType
190
+ }
191
+
192
+ /**
193
+ * Derives a valid secp256k1 private key from a seed.
194
+ *
195
+ * The seed must contain at least 32 bytes of cryptographically strong key
196
+ * material. Do not pass a password directly; use a password KDF first.
197
+ *
198
+ * The permanent derivation contract uses the seed as the HMAC-SHA256
199
+ * key. The HMAC message uses the `ox.secp256k1.fromSeed.v1` domain followed by
200
+ * a 32-bit big-endian counter starting at zero. Invalid scalars are skipped.
201
+ *
202
+ * @example
203
+ * ```ts twoslash
204
+ * import { Secp256k1 } from 'ox'
205
+ *
206
+ * const privateKey = Secp256k1.fromSeed(
207
+ * '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
208
+ * )
209
+ * ```
210
+ *
211
+ * @param seed - Seed containing at least 32 bytes of cryptographically strong key material.
212
+ * @param options - Options.
213
+ * @returns A valid secp256k1 private key.
214
+ */
215
+ export function fromSeed<as extends 'Hex' | 'Bytes' = 'Hex'>(
216
+ seed: Hex.Hex | Bytes.Bytes,
217
+ options: fromSeed.Options<as> = {},
218
+ ): fromSeed.ReturnType<as> {
219
+ const { as = 'Hex' } = options
220
+ const bytes = Bytes.from(seed)
221
+ if (bytes.length < 32) throw new InvalidSeedSizeError({ size: bytes.length })
222
+
223
+ const privateKey = keyDerivation.derive(bytes, fromSeedDomain, {
224
+ validate: noble.utils.isValidSecretKey,
225
+ })
226
+ if (as === 'Hex') {
227
+ const value = Hex.fromBytes(privateKey)
228
+ privateKey.fill(0)
229
+ return value as never
230
+ }
231
+ return privateKey as never
232
+ }
233
+
234
+ export declare namespace fromSeed {
235
+ type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
236
+ /**
237
+ * Format of the returned private key.
238
+ * @default 'Hex'
239
+ */
240
+ as?: as | 'Hex' | 'Bytes' | undefined
241
+ }
242
+
243
+ type ReturnType<as extends 'Hex' | 'Bytes'> =
244
+ | (as extends 'Bytes' ? Bytes.Bytes : never)
245
+ | (as extends 'Hex' ? Hex.Hex : never)
246
+
247
+ type ErrorType =
248
+ | Bytes.from.ErrorType
249
+ | Hex.fromBytes.ErrorType
250
+ | keyDerivation.derive.ErrorType
251
+ | InvalidSeedSizeError
252
+ | Errors.GlobalErrorType
253
+ }
254
+
144
255
  /**
145
256
  * Computes the secp256k1 ECDSA public key from a provided private key.
146
257
  *
@@ -593,4 +704,24 @@ export declare namespace InvalidPrfSizeError {
593
704
  }
594
705
  }
595
706
 
596
- const fromPrfLabel = Bytes.fromString('ox.secp256k1.fromPrf.v1')
707
+ /** Thrown when a seed contains fewer than 32 bytes. */
708
+ export class InvalidSeedSizeError extends Errors.BaseError {
709
+ override readonly name = 'Secp256k1.InvalidSeedSizeError'
710
+
711
+ constructor(options: InvalidSeedSizeError.Options) {
712
+ super(
713
+ `Seed must contain at least 32 bytes. Received ${options.size} bytes.`,
714
+ )
715
+ }
716
+ }
717
+
718
+ export declare namespace InvalidSeedSizeError {
719
+ /** Options for {@link ox#Secp256k1.InvalidSeedSizeError}. */
720
+ type Options = {
721
+ /** Received seed size. */
722
+ size: number
723
+ }
724
+ }
725
+
726
+ const fromPrfDomain = Bytes.fromString('ox.secp256k1.fromPrf.v1')
727
+ const fromSeedDomain = Bytes.fromString('ox.secp256k1.fromSeed.v1')