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/Siwe.ts
CHANGED
|
@@ -23,6 +23,36 @@ export const prefixRegex =
|
|
|
23
23
|
export const suffixRegex =
|
|
24
24
|
/(?:URI: (?<uri>.+))\n(?:Version: (?<version>.+))\n(?:Chain ID: (?<chainId>\d+))\n(?:Nonce: (?<nonce>[a-zA-Z0-9]+))\n(?:Issued At: (?<issuedAt>.+))(?:\nExpiration Time: (?<expirationTime>.+))?(?:\nNot Before: (?<notBefore>.+))?(?:\nRequest ID: (?<requestId>.+))?/
|
|
25
25
|
|
|
26
|
+
const siweDateTimeRegex =
|
|
27
|
+
/^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])[Tt]([01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:[Zz]|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/
|
|
28
|
+
|
|
29
|
+
function parseSiweDateTime(value: string): Date {
|
|
30
|
+
const match = value.match(siweDateTimeRegex)
|
|
31
|
+
if (!match) return new Date(Number.NaN)
|
|
32
|
+
|
|
33
|
+
const year = Number(match[1])
|
|
34
|
+
const month = Number(match[2])
|
|
35
|
+
const day = Number(match[3])
|
|
36
|
+
const days = [
|
|
37
|
+
31,
|
|
38
|
+
year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28,
|
|
39
|
+
31,
|
|
40
|
+
30,
|
|
41
|
+
31,
|
|
42
|
+
30,
|
|
43
|
+
31,
|
|
44
|
+
31,
|
|
45
|
+
30,
|
|
46
|
+
31,
|
|
47
|
+
30,
|
|
48
|
+
31,
|
|
49
|
+
]
|
|
50
|
+
const daysInMonth = days[month - 1]
|
|
51
|
+
if (!daysInMonth || day > daysInMonth) return new Date(Number.NaN)
|
|
52
|
+
|
|
53
|
+
return new Date(value.toUpperCase())
|
|
54
|
+
}
|
|
55
|
+
|
|
26
56
|
/** [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) message fields. */
|
|
27
57
|
export type Message = {
|
|
28
58
|
/**
|
|
@@ -388,9 +418,11 @@ export function parseMessage(message: string): ExactPartial<Message> {
|
|
|
388
418
|
...prefix,
|
|
389
419
|
...suffix,
|
|
390
420
|
...(chainId ? { chainId: Number(chainId) } : {}),
|
|
391
|
-
...(expirationTime
|
|
392
|
-
|
|
393
|
-
|
|
421
|
+
...(expirationTime
|
|
422
|
+
? { expirationTime: parseSiweDateTime(expirationTime) }
|
|
423
|
+
: {}),
|
|
424
|
+
...(issuedAt ? { issuedAt: parseSiweDateTime(issuedAt) } : {}),
|
|
425
|
+
...(notBefore ? { notBefore: parseSiweDateTime(notBefore) } : {}),
|
|
394
426
|
...(requestId ? { requestId } : {}),
|
|
395
427
|
...(resources ? { resources } : {}),
|
|
396
428
|
...(scheme ? { scheme } : {}),
|
|
@@ -431,8 +463,16 @@ export function validateMessage(value: validateMessage.Value): boolean {
|
|
|
431
463
|
if (nonce && message.nonce !== nonce) return false
|
|
432
464
|
if (scheme && message.scheme !== scheme) return false
|
|
433
465
|
|
|
434
|
-
if (
|
|
435
|
-
|
|
466
|
+
if (Number.isNaN(time.getTime())) return false
|
|
467
|
+
|
|
468
|
+
if (message.expirationTime) {
|
|
469
|
+
if (Number.isNaN(message.expirationTime.getTime())) return false
|
|
470
|
+
if (time >= message.expirationTime) return false
|
|
471
|
+
}
|
|
472
|
+
if (message.notBefore) {
|
|
473
|
+
if (Number.isNaN(message.notBefore.getTime())) return false
|
|
474
|
+
if (time < message.notBefore) return false
|
|
475
|
+
}
|
|
436
476
|
|
|
437
477
|
try {
|
|
438
478
|
if (!message.address) return false
|
|
@@ -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
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Bytes, Hex, P256 } from 'ox'
|
|
1
|
+
import { Bytes, Engine, Hex, Mnemonic, P256 } from 'ox'
|
|
2
2
|
import { describe, expect, test } from 'vp/test'
|
|
3
3
|
import { accounts } from '../../../test/constants/accounts.js'
|
|
4
4
|
|
|
@@ -145,6 +145,100 @@ describe('createKeyPair', () => {
|
|
|
145
145
|
})
|
|
146
146
|
})
|
|
147
147
|
|
|
148
|
+
describe('fromMnemonic', () => {
|
|
149
|
+
const mnemonic = 'test test test test test test test test test test test junk'
|
|
150
|
+
|
|
151
|
+
test('default', () => {
|
|
152
|
+
const privateKey = P256.fromMnemonic(mnemonic)
|
|
153
|
+
|
|
154
|
+
expect(privateKey).toBe(P256.fromSeed(Mnemonic.toSeed(mnemonic)))
|
|
155
|
+
expect(privateKey).toMatchInlineSnapshot(
|
|
156
|
+
`"0x882f5b02a84c96bceeebf9c868bec73041d51b041ae05380e66a41508648ebc3"`,
|
|
157
|
+
)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test('options: passphrase', () => {
|
|
161
|
+
expect(
|
|
162
|
+
P256.fromMnemonic(mnemonic, { passphrase: 'qwerty' }),
|
|
163
|
+
).toMatchInlineSnapshot(
|
|
164
|
+
`"0x364ff539f37ce477d49a35476c9ad32a2a3fed1d1237eaee7abdfd07655ad9f5"`,
|
|
165
|
+
)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('options: as', () => {
|
|
169
|
+
const privateKey = P256.fromMnemonic(mnemonic, { as: 'Bytes' })
|
|
170
|
+
|
|
171
|
+
expect(privateKey).toBeInstanceOf(Uint8Array)
|
|
172
|
+
expect(privateKey).toHaveLength(32)
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
describe('fromSeed', () => {
|
|
177
|
+
const seed =
|
|
178
|
+
'0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
|
|
179
|
+
|
|
180
|
+
test('vector', () => {
|
|
181
|
+
expect(P256.fromSeed(seed)).toMatchInlineSnapshot(
|
|
182
|
+
`"0x724009e76eb27e34eb77b93db354b9015ecd4296deb57561f5214bab68b94a6f"`,
|
|
183
|
+
)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test('value: Bytes', () => {
|
|
187
|
+
expect(P256.fromSeed(Bytes.fromHex(seed))).toMatchInlineSnapshot(
|
|
188
|
+
`"0x724009e76eb27e34eb77b93db354b9015ecd4296deb57561f5214bab68b94a6f"`,
|
|
189
|
+
)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('options: as', () => {
|
|
193
|
+
expect(P256.fromSeed(seed, { as: 'Bytes' })).toEqual(
|
|
194
|
+
Bytes.fromHex(
|
|
195
|
+
'0x724009e76eb27e34eb77b93db354b9015ecd4296deb57561f5214bab68b94a6f',
|
|
196
|
+
),
|
|
197
|
+
)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test('behavior: output is a valid P256 private key', () => {
|
|
201
|
+
const privateKey = P256.fromSeed(new Uint8Array(64), { as: 'Bytes' })
|
|
202
|
+
|
|
203
|
+
expect(P256.noble.utils.isValidSecretKey(privateKey)).toBe(true)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('behavior: skips invalid scalar candidates', () => {
|
|
207
|
+
const messages: Hex.Hex[] = []
|
|
208
|
+
const privateKey = Engine.with(
|
|
209
|
+
{
|
|
210
|
+
Hash: {
|
|
211
|
+
hmacSha256: (_key, message) => {
|
|
212
|
+
messages.push(Hex.fromBytes(message))
|
|
213
|
+
if (messages.length === 1) return new Uint8Array(32)
|
|
214
|
+
return Bytes.fromHex(
|
|
215
|
+
'0xef3f982137910a4c362017e69ba173fbb342325802ec827767aa6942027af35b',
|
|
216
|
+
)
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
() => P256.fromSeed(new Uint8Array(32)),
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
expect(privateKey).toBe(
|
|
224
|
+
'0xef3f982137910a4c362017e69ba173fbb342325802ec827767aa6942027af35b',
|
|
225
|
+
)
|
|
226
|
+
expect(messages).toMatchInlineSnapshot(`
|
|
227
|
+
[
|
|
228
|
+
"0x6f782e703235362e66726f6d536565642e763100000000",
|
|
229
|
+
"0x6f782e703235362e66726f6d536565642e763100000001",
|
|
230
|
+
]
|
|
231
|
+
`)
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
test('error: seed is too short', () => {
|
|
235
|
+
expect(() => P256.fromSeed(new Uint8Array(31)))
|
|
236
|
+
.toThrowErrorMatchingInlineSnapshot(`
|
|
237
|
+
[P256.InvalidSeedSizeError: Seed must contain at least 32 bytes. Received 31 bytes.]
|
|
238
|
+
`)
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
|
|
148
242
|
describe('getSharedSecret', () => {
|
|
149
243
|
test('default', () => {
|
|
150
244
|
const { privateKey: privateKeyA, publicKey: publicKeyA } =
|
|
@@ -546,12 +640,15 @@ test('exports', () => {
|
|
|
546
640
|
[
|
|
547
641
|
"noble",
|
|
548
642
|
"createKeyPair",
|
|
643
|
+
"fromMnemonic",
|
|
644
|
+
"fromSeed",
|
|
549
645
|
"getPublicKey",
|
|
550
646
|
"getSharedSecret",
|
|
551
647
|
"randomPrivateKey",
|
|
552
648
|
"recoverPublicKey",
|
|
553
649
|
"sign",
|
|
554
650
|
"verify",
|
|
651
|
+
"InvalidSeedSizeError",
|
|
555
652
|
]
|
|
556
653
|
`)
|
|
557
654
|
})
|
|
@@ -12,3 +12,30 @@ test('fromPrf', () => {
|
|
|
12
12
|
Secp256k1.fromPrf(prf, { as: 'Bytes' }),
|
|
13
13
|
).toEqualTypeOf<Bytes.Bytes>()
|
|
14
14
|
})
|
|
15
|
+
|
|
16
|
+
test('fromMnemonic', () => {
|
|
17
|
+
const mnemonic = 'test test test test test test test test test test test junk'
|
|
18
|
+
|
|
19
|
+
expectTypeOf(Secp256k1.fromMnemonic(mnemonic)).toEqualTypeOf<Hex.Hex>()
|
|
20
|
+
expectTypeOf(
|
|
21
|
+
Secp256k1.fromMnemonic(mnemonic, { passphrase: 'qwerty' }),
|
|
22
|
+
).toEqualTypeOf<Hex.Hex>()
|
|
23
|
+
expectTypeOf(
|
|
24
|
+
Secp256k1.fromMnemonic(mnemonic, { as: 'Bytes' }),
|
|
25
|
+
).toEqualTypeOf<Bytes.Bytes>()
|
|
26
|
+
expectTypeOf(
|
|
27
|
+
Secp256k1.fromMnemonic(mnemonic, { path: "m/44'/60'/0'/0/1" }),
|
|
28
|
+
).toEqualTypeOf<Hex.Hex>()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('fromSeed', () => {
|
|
32
|
+
const seed =
|
|
33
|
+
'0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
|
|
34
|
+
|
|
35
|
+
expectTypeOf(Secp256k1.fromSeed(seed)).toEqualTypeOf<Hex.Hex>()
|
|
36
|
+
expectTypeOf(Secp256k1.fromSeed(new Uint8Array(32))).toEqualTypeOf<Hex.Hex>()
|
|
37
|
+
expectTypeOf(Secp256k1.fromSeed(seed, { as: 'Hex' })).toEqualTypeOf<Hex.Hex>()
|
|
38
|
+
expectTypeOf(
|
|
39
|
+
Secp256k1.fromSeed(seed, { as: 'Bytes' }),
|
|
40
|
+
).toEqualTypeOf<Bytes.Bytes>()
|
|
41
|
+
})
|