ox 1.6.2 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -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/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/dist/tempo/KeyAuthorization.d.ts +16 -12
- package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/KeyAuthorization.js +7 -13
- package/dist/tempo/KeyAuthorization.js.map +1 -1
- package/dist/tempo/MultisigConfig.d.ts +20 -17
- package/dist/tempo/MultisigConfig.d.ts.map +1 -1
- package/dist/tempo/MultisigConfig.js +17 -16
- package/dist/tempo/MultisigConfig.js.map +1 -1
- package/dist/tempo/SignatureEnvelope.d.ts +19 -17
- package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/tempo/SignatureEnvelope.js +35 -27
- package/dist/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/tempo/index.d.ts +2 -2
- package/dist/tempo/index.js +2 -2
- package/dist/zod/tempo/KeyAuthorization.d.ts +231 -22
- package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.js +10 -2
- package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
- package/dist/zod/tempo/RpcSchemaTempo.d.ts +82 -8
- package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/zod/tempo/Transaction.d.ts +246 -24
- package/dist/zod/tempo/Transaction.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.d.ts +149 -14
- package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts +78 -6
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
- 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/_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/internal/keyDerivation.ts +34 -0
- package/src/tempo/KeyAuthorization.test-d.ts +41 -6
- package/src/tempo/KeyAuthorization.test.ts +77 -18
- package/src/tempo/KeyAuthorization.ts +36 -36
- package/src/tempo/MultisigConfig.test.ts +28 -8
- package/src/tempo/MultisigConfig.ts +28 -19
- package/src/tempo/SignatureEnvelope.test.ts +64 -19
- package/src/tempo/SignatureEnvelope.ts +45 -35
- package/src/tempo/index.ts +2 -2
- package/src/tempo/multisig.e2e.test.ts +402 -145
- package/src/version.ts +1 -1
- package/src/zod/tempo/KeyAuthorization.ts +12 -2
- package/src/zod/tempo/_test/KeyAuthorization.test.ts +19 -5
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')
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AesGcm } from 'ox'
|
|
2
|
+
import { expectTypeOf, test } from 'vp/test'
|
|
3
|
+
|
|
4
|
+
test('fromMnemonic', () => {
|
|
5
|
+
const mnemonic = 'test test test test test test test test test test test junk'
|
|
6
|
+
|
|
7
|
+
expectTypeOf(AesGcm.fromMnemonic(mnemonic)).toEqualTypeOf<
|
|
8
|
+
Promise<CryptoKey>
|
|
9
|
+
>()
|
|
10
|
+
expectTypeOf(
|
|
11
|
+
AesGcm.fromMnemonic(mnemonic, { passphrase: 'qwerty' }),
|
|
12
|
+
).toEqualTypeOf<Promise<CryptoKey>>()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('fromSeed', () => {
|
|
16
|
+
expectTypeOf(AesGcm.fromSeed(new Uint8Array(32))).toEqualTypeOf<
|
|
17
|
+
Promise<CryptoKey>
|
|
18
|
+
>()
|
|
19
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AesGcm, Bytes, Hex } from 'ox'
|
|
1
|
+
import { AesGcm, Bytes, Hex, Mnemonic } from 'ox'
|
|
2
2
|
import { describe, expect, test } from 'vp/test'
|
|
3
3
|
|
|
4
4
|
describe('decrypt', () => {
|
|
@@ -159,6 +159,92 @@ describe('fromPrf', () => {
|
|
|
159
159
|
})
|
|
160
160
|
})
|
|
161
161
|
|
|
162
|
+
describe('fromMnemonic', () => {
|
|
163
|
+
const mnemonic = 'test test test test test test test test test test test junk'
|
|
164
|
+
|
|
165
|
+
test('default', async () => {
|
|
166
|
+
const key = await AesGcm.fromMnemonic(mnemonic)
|
|
167
|
+
const expectedKey = await AesGcm.fromSeed(Mnemonic.toSeed(mnemonic))
|
|
168
|
+
const value = Bytes.fromString('i am a secret message')
|
|
169
|
+
const encrypted = await AesGcm.encrypt(value, key)
|
|
170
|
+
|
|
171
|
+
await expect(AesGcm.decrypt(encrypted, expectedKey)).resolves.toEqual(value)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('options: passphrase', async () => {
|
|
175
|
+
const key = await AesGcm.fromMnemonic(mnemonic, {
|
|
176
|
+
passphrase: 'qwerty',
|
|
177
|
+
})
|
|
178
|
+
const defaultKey = await AesGcm.fromMnemonic(mnemonic)
|
|
179
|
+
const encrypted = await AesGcm.encrypt('0xdeadbeef', key)
|
|
180
|
+
|
|
181
|
+
await expect(AesGcm.decrypt(encrypted, defaultKey)).rejects.toThrowError()
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
describe('fromSeed', () => {
|
|
186
|
+
const seed =
|
|
187
|
+
'0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
|
|
188
|
+
|
|
189
|
+
test('vector', async () => {
|
|
190
|
+
const key = await AesGcm.fromSeed(seed)
|
|
191
|
+
const expectedKey = await globalThis.crypto.subtle.importKey(
|
|
192
|
+
'raw',
|
|
193
|
+
Bytes.fromHex(
|
|
194
|
+
'0xd42ffe5cb894ce61e9f448e8083f53b2ab90d2acad0929e80b0abb85e915cb3a',
|
|
195
|
+
),
|
|
196
|
+
{ name: 'AES-GCM' },
|
|
197
|
+
false,
|
|
198
|
+
['decrypt'],
|
|
199
|
+
)
|
|
200
|
+
const value = Bytes.fromString('i am a secret message')
|
|
201
|
+
const encrypted = await AesGcm.encrypt(value, key)
|
|
202
|
+
|
|
203
|
+
await expect(AesGcm.decrypt(encrypted, expectedKey)).resolves.toEqual(value)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('value: Bytes', async () => {
|
|
207
|
+
const key = await AesGcm.fromSeed(Bytes.fromHex(seed))
|
|
208
|
+
const value = Bytes.fromString('i am a secret message')
|
|
209
|
+
|
|
210
|
+
await expect(
|
|
211
|
+
AesGcm.decrypt(await AesGcm.encrypt(value, key), key),
|
|
212
|
+
).resolves.toEqual(value)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
test('behavior: accepts seeds longer than 32 bytes', async () => {
|
|
216
|
+
const key = await AesGcm.fromSeed(new Uint8Array(64))
|
|
217
|
+
|
|
218
|
+
expect({
|
|
219
|
+
algorithm: key.algorithm,
|
|
220
|
+
extractable: key.extractable,
|
|
221
|
+
type: key.type,
|
|
222
|
+
usages: key.usages,
|
|
223
|
+
}).toMatchInlineSnapshot(`
|
|
224
|
+
{
|
|
225
|
+
"algorithm": {
|
|
226
|
+
"length": 256,
|
|
227
|
+
"name": "AES-GCM",
|
|
228
|
+
},
|
|
229
|
+
"extractable": false,
|
|
230
|
+
"type": "secret",
|
|
231
|
+
"usages": [
|
|
232
|
+
"encrypt",
|
|
233
|
+
"decrypt",
|
|
234
|
+
],
|
|
235
|
+
}
|
|
236
|
+
`)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
test('error: seed is too short', async () => {
|
|
240
|
+
await expect(
|
|
241
|
+
AesGcm.fromSeed(new Uint8Array(31)),
|
|
242
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
243
|
+
`[AesGcm.InvalidSeedSizeError: Seed must contain at least 32 bytes. Received 31 bytes.]`,
|
|
244
|
+
)
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
|
|
162
248
|
describe('getKey', () => {
|
|
163
249
|
test('default', async () => {
|
|
164
250
|
const key = await AesGcm.getKey({ password: 'qwerty' })
|
|
@@ -180,9 +266,12 @@ test('exports', () => {
|
|
|
180
266
|
"decrypt",
|
|
181
267
|
"encrypt",
|
|
182
268
|
"fromPrf",
|
|
269
|
+
"fromMnemonic",
|
|
270
|
+
"fromSeed",
|
|
183
271
|
"getKey",
|
|
184
272
|
"randomSalt",
|
|
185
273
|
"InvalidPrfSizeError",
|
|
274
|
+
"InvalidSeedSizeError",
|
|
186
275
|
]
|
|
187
276
|
`)
|
|
188
277
|
})
|
|
@@ -40,6 +40,30 @@ test('fromPrf', () => {
|
|
|
40
40
|
).toEqualTypeOf<Bytes.Bytes>()
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
+
test('fromMnemonic', () => {
|
|
44
|
+
const mnemonic = 'test test test test test test test test test test test junk'
|
|
45
|
+
|
|
46
|
+
expectTypeOf(Ed25519.fromMnemonic(mnemonic)).toEqualTypeOf<Hex.Hex>()
|
|
47
|
+
expectTypeOf(
|
|
48
|
+
Ed25519.fromMnemonic(mnemonic, { passphrase: 'qwerty' }),
|
|
49
|
+
).toEqualTypeOf<Hex.Hex>()
|
|
50
|
+
expectTypeOf(
|
|
51
|
+
Ed25519.fromMnemonic(mnemonic, { as: 'Bytes' }),
|
|
52
|
+
).toEqualTypeOf<Bytes.Bytes>()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('fromSeed', () => {
|
|
56
|
+
const seed =
|
|
57
|
+
'0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
|
|
58
|
+
|
|
59
|
+
expectTypeOf(Ed25519.fromSeed(seed)).toEqualTypeOf<Hex.Hex>()
|
|
60
|
+
expectTypeOf(Ed25519.fromSeed(new Uint8Array(32))).toEqualTypeOf<Hex.Hex>()
|
|
61
|
+
expectTypeOf(Ed25519.fromSeed(seed, { as: 'Hex' })).toEqualTypeOf<Hex.Hex>()
|
|
62
|
+
expectTypeOf(
|
|
63
|
+
Ed25519.fromSeed(seed, { as: 'Bytes' }),
|
|
64
|
+
).toEqualTypeOf<Bytes.Bytes>()
|
|
65
|
+
})
|
|
66
|
+
|
|
43
67
|
test('getPublicKey', () => {
|
|
44
68
|
// Default behavior (Hex)
|
|
45
69
|
{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Bytes, Ed25519, Engine, Hex, X25519 } from 'ox'
|
|
1
|
+
import { Bytes, Ed25519, Engine, Hex, Mnemonic, X25519 } from 'ox'
|
|
2
2
|
import { describe, expect, test } from 'vp/test'
|
|
3
3
|
|
|
4
4
|
describe('createKeyPair', () => {
|
|
@@ -146,6 +146,89 @@ describe('fromPrf', () => {
|
|
|
146
146
|
})
|
|
147
147
|
})
|
|
148
148
|
|
|
149
|
+
describe('fromMnemonic', () => {
|
|
150
|
+
const mnemonic = 'test test test test test test test test test test test junk'
|
|
151
|
+
|
|
152
|
+
test('default', () => {
|
|
153
|
+
const privateKey = Ed25519.fromMnemonic(mnemonic)
|
|
154
|
+
|
|
155
|
+
expect(privateKey).toBe(Ed25519.fromSeed(Mnemonic.toSeed(mnemonic)))
|
|
156
|
+
expect(privateKey).toMatchInlineSnapshot(
|
|
157
|
+
`"0x23fb07a427d2bc36141d1e6c7e56c72679739eff374a8e8def282f1048674567"`,
|
|
158
|
+
)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test('options: passphrase', () => {
|
|
162
|
+
expect(
|
|
163
|
+
Ed25519.fromMnemonic(mnemonic, { passphrase: 'qwerty' }),
|
|
164
|
+
).toMatchInlineSnapshot(
|
|
165
|
+
`"0xe888791c9d783e772d92b0bf2aa326b1cda8214a03c3c07933615b1a98fc8651"`,
|
|
166
|
+
)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('options: as', () => {
|
|
170
|
+
const privateKey = Ed25519.fromMnemonic(mnemonic, { as: 'Bytes' })
|
|
171
|
+
|
|
172
|
+
expect(privateKey).toBeInstanceOf(Uint8Array)
|
|
173
|
+
expect(privateKey).toHaveLength(32)
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
describe('fromSeed', () => {
|
|
178
|
+
const seed =
|
|
179
|
+
'0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
|
|
180
|
+
|
|
181
|
+
test('vector', () => {
|
|
182
|
+
expect(Ed25519.fromSeed(seed)).toMatchInlineSnapshot(
|
|
183
|
+
`"0xfd09c7b2acda46034df7e428377fdc37200094c9b51690fa1b733ee3c16a2d07"`,
|
|
184
|
+
)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test('value: Bytes', () => {
|
|
188
|
+
expect(Ed25519.fromSeed(Bytes.fromHex(seed))).toMatchInlineSnapshot(
|
|
189
|
+
`"0xfd09c7b2acda46034df7e428377fdc37200094c9b51690fa1b733ee3c16a2d07"`,
|
|
190
|
+
)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
test('options: as', () => {
|
|
194
|
+
expect(Ed25519.fromSeed(seed, { as: 'Bytes' })).toEqual(
|
|
195
|
+
Bytes.fromHex(
|
|
196
|
+
'0xfd09c7b2acda46034df7e428377fdc37200094c9b51690fa1b733ee3c16a2d07',
|
|
197
|
+
),
|
|
198
|
+
)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
test('behavior: output signs and verifies', () => {
|
|
202
|
+
const privateKey = Ed25519.fromSeed(new Uint8Array(64))
|
|
203
|
+
const publicKey = Ed25519.getPublicKey({ privateKey })
|
|
204
|
+
const payload = '0xdeadbeef'
|
|
205
|
+
const signature = Ed25519.sign({ payload, privateKey })
|
|
206
|
+
|
|
207
|
+
expect(Ed25519.verify({ payload, publicKey, signature })).toBe(true)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test('behavior: zeroes intermediate key for Hex output', () => {
|
|
211
|
+
const candidate = new Uint8Array(32).fill(1)
|
|
212
|
+
Engine.with(
|
|
213
|
+
{
|
|
214
|
+
Hash: {
|
|
215
|
+
hmacSha256: () => candidate,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
() => Ed25519.fromSeed(new Uint8Array(32)),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
expect(candidate).toEqual(new Uint8Array(32))
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
test('error: seed is too short', () => {
|
|
225
|
+
expect(() => Ed25519.fromSeed(new Uint8Array(31)))
|
|
226
|
+
.toThrowErrorMatchingInlineSnapshot(`
|
|
227
|
+
[Ed25519.InvalidSeedSizeError: Seed must contain at least 32 bytes. Received 31 bytes.]
|
|
228
|
+
`)
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
|
|
149
232
|
describe('getPublicKey', () => {
|
|
150
233
|
const privateKey =
|
|
151
234
|
'0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'
|
|
@@ -545,6 +628,8 @@ test('exports', () => {
|
|
|
545
628
|
"noble",
|
|
546
629
|
"createKeyPair",
|
|
547
630
|
"fromPrf",
|
|
631
|
+
"fromMnemonic",
|
|
632
|
+
"fromSeed",
|
|
548
633
|
"getPublicKey",
|
|
549
634
|
"randomPrivateKey",
|
|
550
635
|
"sign",
|
|
@@ -552,6 +637,7 @@ test('exports', () => {
|
|
|
552
637
|
"toX25519PublicKey",
|
|
553
638
|
"toX25519PrivateKey",
|
|
554
639
|
"InvalidPrfSizeError",
|
|
640
|
+
"InvalidSeedSizeError",
|
|
555
641
|
]
|
|
556
642
|
`)
|
|
557
643
|
})
|
|
@@ -24,6 +24,30 @@ test('fromPrf', () => {
|
|
|
24
24
|
).toEqualTypeOf<Bytes.Bytes>()
|
|
25
25
|
})
|
|
26
26
|
|
|
27
|
+
test('fromMnemonic', () => {
|
|
28
|
+
const mnemonic = 'test test test test test test test test test test test junk'
|
|
29
|
+
|
|
30
|
+
expectTypeOf(MlDsa44.fromMnemonic(mnemonic)).toEqualTypeOf<Hex.Hex>()
|
|
31
|
+
expectTypeOf(
|
|
32
|
+
MlDsa44.fromMnemonic(mnemonic, { passphrase: 'qwerty' }),
|
|
33
|
+
).toEqualTypeOf<Hex.Hex>()
|
|
34
|
+
expectTypeOf(
|
|
35
|
+
MlDsa44.fromMnemonic(mnemonic, { as: 'Bytes' }),
|
|
36
|
+
).toEqualTypeOf<Bytes.Bytes>()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('fromSeed', () => {
|
|
40
|
+
const seed =
|
|
41
|
+
'0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
|
|
42
|
+
|
|
43
|
+
expectTypeOf(MlDsa44.fromSeed(seed)).toEqualTypeOf<Hex.Hex>()
|
|
44
|
+
expectTypeOf(MlDsa44.fromSeed(new Uint8Array(32))).toEqualTypeOf<Hex.Hex>()
|
|
45
|
+
expectTypeOf(MlDsa44.fromSeed(seed, { as: 'Hex' })).toEqualTypeOf<Hex.Hex>()
|
|
46
|
+
expectTypeOf(
|
|
47
|
+
MlDsa44.fromSeed(seed, { as: 'Bytes' }),
|
|
48
|
+
).toEqualTypeOf<Bytes.Bytes>()
|
|
49
|
+
})
|
|
50
|
+
|
|
27
51
|
test('getPublicKey', () => {
|
|
28
52
|
expectTypeOf(
|
|
29
53
|
MlDsa44.getPublicKey({ privateKey: '0x' }),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Bytes, Engine, Hash, Hex, MlDsa44 } from 'ox'
|
|
1
|
+
import { Bytes, Engine, Hash, Hex, MlDsa44, Mnemonic } from 'ox'
|
|
2
2
|
import { describe, expect, test } from 'vp/test'
|
|
3
3
|
|
|
4
4
|
const privateKey = Hex.fromBytes(new Uint8Array(32).fill(7))
|
|
@@ -139,6 +139,74 @@ describe('fromPrf', () => {
|
|
|
139
139
|
})
|
|
140
140
|
})
|
|
141
141
|
|
|
142
|
+
describe('fromMnemonic', () => {
|
|
143
|
+
const mnemonic = 'test test test test test test test test test test test junk'
|
|
144
|
+
|
|
145
|
+
test('default', () => {
|
|
146
|
+
const privateKey = MlDsa44.fromMnemonic(mnemonic)
|
|
147
|
+
|
|
148
|
+
expect(privateKey).toBe(MlDsa44.fromSeed(Mnemonic.toSeed(mnemonic)))
|
|
149
|
+
expect(privateKey).toMatchInlineSnapshot(
|
|
150
|
+
`"0x787f747b571a3511deac4d045a7876ae2349f45b9f9f4e3a6ee40342313a963f"`,
|
|
151
|
+
)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('options: passphrase', () => {
|
|
155
|
+
expect(
|
|
156
|
+
MlDsa44.fromMnemonic(mnemonic, { passphrase: 'qwerty' }),
|
|
157
|
+
).toMatchInlineSnapshot(
|
|
158
|
+
`"0x51202c4aaf8b4090f94c43ae43234b133a2646642fbc4ca39d8883a0b39d2b44"`,
|
|
159
|
+
)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
test('options: as', () => {
|
|
163
|
+
const privateKey = MlDsa44.fromMnemonic(mnemonic, { as: 'Bytes' })
|
|
164
|
+
|
|
165
|
+
expect(privateKey).toBeInstanceOf(Uint8Array)
|
|
166
|
+
expect(privateKey).toHaveLength(32)
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
describe('fromSeed', () => {
|
|
171
|
+
const seed =
|
|
172
|
+
'0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
|
|
173
|
+
|
|
174
|
+
test('vector', () => {
|
|
175
|
+
expect(MlDsa44.fromSeed(seed)).toMatchInlineSnapshot(
|
|
176
|
+
`"0xbee74160aef452ec0e90a4c347bbb0ac37e93fb94855f7a98dfe9afabebbdad6"`,
|
|
177
|
+
)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
test('value: Bytes', () => {
|
|
181
|
+
expect(MlDsa44.fromSeed(Bytes.fromHex(seed))).toMatchInlineSnapshot(
|
|
182
|
+
`"0xbee74160aef452ec0e90a4c347bbb0ac37e93fb94855f7a98dfe9afabebbdad6"`,
|
|
183
|
+
)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test('options: as', () => {
|
|
187
|
+
expect(MlDsa44.fromSeed(seed, { as: 'Bytes' })).toEqual(
|
|
188
|
+
Bytes.fromHex(
|
|
189
|
+
'0xbee74160aef452ec0e90a4c347bbb0ac37e93fb94855f7a98dfe9afabebbdad6',
|
|
190
|
+
),
|
|
191
|
+
)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
test('behavior: output signs and verifies', () => {
|
|
195
|
+
const privateKey = MlDsa44.fromSeed(new Uint8Array(64))
|
|
196
|
+
const publicKey = MlDsa44.getPublicKey({ privateKey })
|
|
197
|
+
const signature = MlDsa44.sign({ payload, privateKey })
|
|
198
|
+
|
|
199
|
+
expect(MlDsa44.verify({ payload, publicKey, signature })).toBe(true)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
test('error: seed is too short', () => {
|
|
203
|
+
expect(() => MlDsa44.fromSeed(new Uint8Array(31)))
|
|
204
|
+
.toThrowErrorMatchingInlineSnapshot(`
|
|
205
|
+
[MlDsa44.InvalidSeedSizeError: Seed must contain at least 32 bytes. Received 31 bytes.]
|
|
206
|
+
`)
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
|
|
142
210
|
describe('getPublicKey', () => {
|
|
143
211
|
test('default', () => {
|
|
144
212
|
const publicKey = MlDsa44.getPublicKey({ privateKey })
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Bytes, type Hex, P256 } from 'ox'
|
|
2
|
+
import { expectTypeOf, test } from 'vp/test'
|
|
3
|
+
|
|
4
|
+
test('fromMnemonic', () => {
|
|
5
|
+
const mnemonic = 'test test test test test test test test test test test junk'
|
|
6
|
+
|
|
7
|
+
expectTypeOf(P256.fromMnemonic(mnemonic)).toEqualTypeOf<Hex.Hex>()
|
|
8
|
+
expectTypeOf(
|
|
9
|
+
P256.fromMnemonic(mnemonic, { passphrase: 'qwerty' }),
|
|
10
|
+
).toEqualTypeOf<Hex.Hex>()
|
|
11
|
+
expectTypeOf(
|
|
12
|
+
P256.fromMnemonic(mnemonic, { as: 'Bytes' }),
|
|
13
|
+
).toEqualTypeOf<Bytes.Bytes>()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('fromSeed', () => {
|
|
17
|
+
const seed =
|
|
18
|
+
'0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
|
|
19
|
+
|
|
20
|
+
expectTypeOf(P256.fromSeed(seed)).toEqualTypeOf<Hex.Hex>()
|
|
21
|
+
expectTypeOf(P256.fromSeed(new Uint8Array(32))).toEqualTypeOf<Hex.Hex>()
|
|
22
|
+
expectTypeOf(P256.fromSeed(seed, { as: 'Hex' })).toEqualTypeOf<Hex.Hex>()
|
|
23
|
+
expectTypeOf(
|
|
24
|
+
P256.fromSeed(seed, { as: 'Bytes' }),
|
|
25
|
+
).toEqualTypeOf<Bytes.Bytes>()
|
|
26
|
+
})
|