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.
- package/CHANGELOG.md +12 -0
- package/dist/core/AesGcm.d.ts +67 -2
- package/dist/core/AesGcm.d.ts.map +1 -1
- package/dist/core/AesGcm.js +79 -10
- package/dist/core/AesGcm.js.map +1 -1
- package/dist/core/Ed25519.d.ts +83 -2
- package/dist/core/Ed25519.d.ts.map +1 -1
- package/dist/core/Ed25519.js +79 -4
- package/dist/core/Ed25519.js.map +1 -1
- package/dist/core/MlDsa44.d.ts +84 -5
- package/dist/core/MlDsa44.d.ts.map +1 -1
- package/dist/core/MlDsa44.js +79 -4
- package/dist/core/MlDsa44.js.map +1 -1
- package/dist/core/P256.d.ts +82 -1
- package/dist/core/P256.d.ts.map +1 -1
- package/dist/core/P256.js +78 -0
- package/dist/core/P256.js.map +1 -1
- package/dist/core/Secp256k1.d.ts +85 -1
- package/dist/core/Secp256k1.d.ts.map +1 -1
- package/dist/core/Secp256k1.js +74 -3
- package/dist/core/Secp256k1.js.map +1 -1
- package/dist/core/Siwe.d.ts.map +1 -1
- package/dist/core/Siwe.js +45 -6
- package/dist/core/Siwe.js.map +1 -1
- package/dist/core/internal/keyDerivation.d.ts +13 -0
- package/dist/core/internal/keyDerivation.d.ts.map +1 -0
- package/dist/core/internal/keyDerivation.js +13 -0
- package/dist/core/internal/keyDerivation.js.map +1 -0
- package/package.json +1 -1
- package/src/core/AesGcm.ts +137 -30
- package/src/core/Ed25519.ts +135 -4
- package/src/core/MlDsa44.ts +135 -4
- package/src/core/P256.ts +135 -1
- package/src/core/Secp256k1.ts +134 -3
- package/src/core/Siwe.ts +45 -5
- package/src/core/_test/AesGcm.test-d.ts +19 -0
- package/src/core/_test/AesGcm.test.ts +90 -1
- package/src/core/_test/Ed25519.test-d.ts +24 -0
- package/src/core/_test/Ed25519.test.ts +87 -1
- package/src/core/_test/MlDsa44.test-d.ts +24 -0
- package/src/core/_test/MlDsa44.test.ts +69 -1
- package/src/core/_test/P256.test-d.ts +26 -0
- package/src/core/_test/P256.test.ts +98 -1
- package/src/core/_test/Secp256k1.test-d.ts +27 -0
- package/src/core/_test/Secp256k1.test.ts +112 -1
- package/src/core/_test/Siwe.test.ts +98 -0
- package/src/core/internal/keyDerivation.ts +34 -0
- package/src/version.ts +1 -1
package/src/core/Ed25519.ts
CHANGED
|
@@ -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.
|
|
67
|
-
*
|
|
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(
|
|
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
|
-
|
|
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')
|
package/src/core/MlDsa44.ts
CHANGED
|
@@ -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.
|
|
81
|
-
*
|
|
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(
|
|
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
|
|
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
|
|
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')
|
package/src/core/Secp256k1.ts
CHANGED
|
@@ -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.
|
|
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(
|
|
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
|
-
|
|
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')
|