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
|
@@ -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
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Address, Bytes, Engine, Hex, PublicKey, Secp256k1 } from 'ox'
|
|
1
|
+
import { Address, Bytes, Engine, Hex, Mnemonic, PublicKey, Secp256k1 } from 'ox'
|
|
2
2
|
import { describe, expect, test } from 'vp/test'
|
|
3
3
|
import { accounts } from '../../../test/constants/accounts.js'
|
|
4
4
|
|
|
@@ -211,6 +211,114 @@ describe('fromPrf', () => {
|
|
|
211
211
|
})
|
|
212
212
|
})
|
|
213
213
|
|
|
214
|
+
describe('fromMnemonic', () => {
|
|
215
|
+
const mnemonic = 'test test test test test test test test test test test junk'
|
|
216
|
+
|
|
217
|
+
test('default', () => {
|
|
218
|
+
const privateKey = Secp256k1.fromMnemonic(mnemonic)
|
|
219
|
+
|
|
220
|
+
expect(privateKey).toBe(Mnemonic.toPrivateKey(mnemonic, { as: 'Hex' }))
|
|
221
|
+
expect(privateKey).toMatchInlineSnapshot(
|
|
222
|
+
`"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"`,
|
|
223
|
+
)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
test('options: passphrase', () => {
|
|
227
|
+
expect(
|
|
228
|
+
Secp256k1.fromMnemonic(mnemonic, { passphrase: 'qwerty' }),
|
|
229
|
+
).toMatchInlineSnapshot(
|
|
230
|
+
`"0x0bef893b1cc27e9ce726d5f12f75d61a07d4df87c02106083463cd712ac5c478"`,
|
|
231
|
+
)
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
test('options: as', () => {
|
|
235
|
+
const privateKey = Secp256k1.fromMnemonic(mnemonic, { as: 'Bytes' })
|
|
236
|
+
|
|
237
|
+
expect(privateKey).toBeInstanceOf(Uint8Array)
|
|
238
|
+
expect(privateKey).toHaveLength(32)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
test('options: path', () => {
|
|
242
|
+
const path = "m/44'/60'/0'/0/1"
|
|
243
|
+
|
|
244
|
+
expect(Secp256k1.fromMnemonic(mnemonic, { path })).toBe(
|
|
245
|
+
Mnemonic.toPrivateKey(mnemonic, { as: 'Hex', path }),
|
|
246
|
+
)
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
describe('fromSeed', () => {
|
|
251
|
+
const seed =
|
|
252
|
+
'0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
|
|
253
|
+
|
|
254
|
+
test('vector', () => {
|
|
255
|
+
expect(Secp256k1.fromSeed(seed)).toMatchInlineSnapshot(
|
|
256
|
+
`"0x5f7dcbba41adaafa378861d78b144f7b2827e79fa994a341628f5470d5bfbdd4"`,
|
|
257
|
+
)
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
test('value: Bytes', () => {
|
|
261
|
+
expect(Secp256k1.fromSeed(Bytes.fromHex(seed))).toMatchInlineSnapshot(
|
|
262
|
+
`"0x5f7dcbba41adaafa378861d78b144f7b2827e79fa994a341628f5470d5bfbdd4"`,
|
|
263
|
+
)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
test('options: as', () => {
|
|
267
|
+
expect(Secp256k1.fromSeed(seed, { as: 'Bytes' })).toEqual(
|
|
268
|
+
Bytes.fromHex(
|
|
269
|
+
'0x5f7dcbba41adaafa378861d78b144f7b2827e79fa994a341628f5470d5bfbdd4',
|
|
270
|
+
),
|
|
271
|
+
)
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
test('behavior: accepts seeds longer than 32 bytes', () => {
|
|
275
|
+
const privateKey = Secp256k1.fromSeed(new Uint8Array(64), { as: 'Bytes' })
|
|
276
|
+
|
|
277
|
+
expect(Secp256k1.noble.utils.isValidSecretKey(privateKey)).toBe(true)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
test('behavior: domain is distinct from fromPrf', () => {
|
|
281
|
+
expect(Secp256k1.fromSeed(seed)).not.toBe(Secp256k1.fromPrf(seed))
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
test('behavior: skips invalid scalar candidates', () => {
|
|
285
|
+
const invalid = new Uint8Array(32).fill(0xff)
|
|
286
|
+
const messages: Hex.Hex[] = []
|
|
287
|
+
const privateKey = Engine.with(
|
|
288
|
+
{
|
|
289
|
+
Hash: {
|
|
290
|
+
hmacSha256: (_key, message) => {
|
|
291
|
+
messages.push(Hex.fromBytes(message))
|
|
292
|
+
if (messages.length === 1) return invalid
|
|
293
|
+
return Bytes.fromHex(
|
|
294
|
+
'0xef3f982137910a4c362017e69ba173fbb342325802ec827767aa6942027af35b',
|
|
295
|
+
)
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
() => Secp256k1.fromSeed(new Uint8Array(32)),
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
expect(privateKey).toBe(
|
|
303
|
+
'0xef3f982137910a4c362017e69ba173fbb342325802ec827767aa6942027af35b',
|
|
304
|
+
)
|
|
305
|
+
expect(invalid).toEqual(new Uint8Array(32))
|
|
306
|
+
expect(messages).toMatchInlineSnapshot(`
|
|
307
|
+
[
|
|
308
|
+
"0x6f782e736563703235366b312e66726f6d536565642e763100000000",
|
|
309
|
+
"0x6f782e736563703235366b312e66726f6d536565642e763100000001",
|
|
310
|
+
]
|
|
311
|
+
`)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
test('error: seed is too short', () => {
|
|
315
|
+
expect(() => Secp256k1.fromSeed(new Uint8Array(31)))
|
|
316
|
+
.toThrowErrorMatchingInlineSnapshot(`
|
|
317
|
+
[Secp256k1.InvalidSeedSizeError: Seed must contain at least 32 bytes. Received 31 bytes.]
|
|
318
|
+
`)
|
|
319
|
+
})
|
|
320
|
+
})
|
|
321
|
+
|
|
214
322
|
describe('getSharedSecret', () => {
|
|
215
323
|
test('default', () => {
|
|
216
324
|
const privateKeyA = accounts[0].privateKey
|
|
@@ -572,6 +680,8 @@ test('exports', () => {
|
|
|
572
680
|
"noble",
|
|
573
681
|
"createKeyPair",
|
|
574
682
|
"fromPrf",
|
|
683
|
+
"fromMnemonic",
|
|
684
|
+
"fromSeed",
|
|
575
685
|
"getPublicKey",
|
|
576
686
|
"getSharedSecret",
|
|
577
687
|
"randomPrivateKey",
|
|
@@ -580,6 +690,7 @@ test('exports', () => {
|
|
|
580
690
|
"sign",
|
|
581
691
|
"verify",
|
|
582
692
|
"InvalidPrfSizeError",
|
|
693
|
+
"InvalidSeedSizeError",
|
|
583
694
|
]
|
|
584
695
|
`)
|
|
585
696
|
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as Bytes from '../Bytes.js'
|
|
2
|
+
import type * as Errors from '../Errors.js'
|
|
3
|
+
import * as Hash from '../Hash.js'
|
|
4
|
+
|
|
5
|
+
/** @internal */
|
|
6
|
+
export function derive(
|
|
7
|
+
seed: Bytes.Bytes,
|
|
8
|
+
domain: Bytes.Bytes,
|
|
9
|
+
options: derive.Options = {},
|
|
10
|
+
): Bytes.Bytes {
|
|
11
|
+
const { validate } = options
|
|
12
|
+
for (let counter = 0; ; counter++) {
|
|
13
|
+
const key = Hash.hmac256(
|
|
14
|
+
seed,
|
|
15
|
+
Bytes.concat(domain, Bytes.fromNumber(counter, { size: 4 })),
|
|
16
|
+
{ as: 'Bytes' },
|
|
17
|
+
)
|
|
18
|
+
if (!validate || validate(key)) return key
|
|
19
|
+
key.fill(0)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** @internal */
|
|
24
|
+
export declare namespace derive {
|
|
25
|
+
type Options = {
|
|
26
|
+
validate?: ((key: Bytes.Bytes) => boolean) | undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type ErrorType =
|
|
30
|
+
| Bytes.concat.ErrorType
|
|
31
|
+
| Bytes.fromNumber.ErrorType
|
|
32
|
+
| Hash.hmac256.ErrorType
|
|
33
|
+
| Errors.GlobalErrorType
|
|
34
|
+
}
|
|
@@ -23,6 +23,12 @@ const multisig = {
|
|
|
23
23
|
type: 'multisig',
|
|
24
24
|
} as const satisfies SignatureEnvelope.Multisig
|
|
25
25
|
|
|
26
|
+
const keychain = {
|
|
27
|
+
inner: signature,
|
|
28
|
+
type: 'keychain',
|
|
29
|
+
userAddress: authorization.address,
|
|
30
|
+
} as const satisfies SignatureEnvelope.Keychain
|
|
31
|
+
|
|
26
32
|
test('accepts primitive signatures', () => {
|
|
27
33
|
const signed = KeyAuthorization.from(authorization, { signature })
|
|
28
34
|
|
|
@@ -31,11 +37,12 @@ test('accepts primitive signatures', () => {
|
|
|
31
37
|
>()
|
|
32
38
|
})
|
|
33
39
|
|
|
34
|
-
test('
|
|
35
|
-
KeyAuthorization.from(authorization, {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
test('accepts multisig signatures', () => {
|
|
41
|
+
const signed = KeyAuthorization.from(authorization, { signature: multisig })
|
|
42
|
+
|
|
43
|
+
expectTypeOf(signed.signature).toMatchTypeOf<
|
|
44
|
+
KeyAuthorization.Signed['signature']
|
|
45
|
+
>()
|
|
39
46
|
|
|
40
47
|
const multisigRpc = {
|
|
41
48
|
account: multisig.account,
|
|
@@ -54,9 +61,37 @@ test('rejects multisig signatures', () => {
|
|
|
54
61
|
expiry: null,
|
|
55
62
|
keyId: authorization.address,
|
|
56
63
|
keyType: authorization.type,
|
|
57
|
-
// @ts-expect-error Key authorizations accept only primitive RPC signatures.
|
|
58
64
|
signature: multisigRpc,
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
expectTypeOf(rpc).toEqualTypeOf<KeyAuthorization.Rpc>()
|
|
62
68
|
})
|
|
69
|
+
|
|
70
|
+
test('rejects keychain signatures', () => {
|
|
71
|
+
KeyAuthorization.from(authorization, {
|
|
72
|
+
// @ts-expect-error Key authorizations do not accept keychain signatures.
|
|
73
|
+
signature: keychain,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const keychainRpc = {
|
|
77
|
+
signature: {
|
|
78
|
+
r: '0x01',
|
|
79
|
+
s: '0x02',
|
|
80
|
+
type: 'secp256k1',
|
|
81
|
+
yParity: '0x0',
|
|
82
|
+
},
|
|
83
|
+
type: 'keychain',
|
|
84
|
+
userAddress: authorization.address,
|
|
85
|
+
} as const satisfies SignatureEnvelope.KeychainRpc
|
|
86
|
+
|
|
87
|
+
const rpc: KeyAuthorization.Rpc = {
|
|
88
|
+
chainId: '0x1',
|
|
89
|
+
expiry: null,
|
|
90
|
+
keyId: authorization.address,
|
|
91
|
+
keyType: authorization.type,
|
|
92
|
+
// @ts-expect-error Key authorizations do not accept keychain RPC signatures.
|
|
93
|
+
signature: keychainRpc,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
expectTypeOf(rpc).toEqualTypeOf<KeyAuthorization.Rpc>()
|
|
97
|
+
})
|
|
@@ -53,11 +53,17 @@ const signature_webauthn = SignatureEnvelope.from({
|
|
|
53
53
|
},
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
-
const signature_multisig =
|
|
56
|
+
const signature_multisig = {
|
|
57
57
|
account: address,
|
|
58
58
|
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
59
59
|
type: 'multisig',
|
|
60
|
-
}
|
|
60
|
+
} as const satisfies SignatureEnvelope.Multisig
|
|
61
|
+
|
|
62
|
+
const signature_keychain = {
|
|
63
|
+
inner: SignatureEnvelope.from(signature_secp256k1),
|
|
64
|
+
type: 'keychain',
|
|
65
|
+
userAddress: address,
|
|
66
|
+
} as const satisfies SignatureEnvelope.Keychain
|
|
61
67
|
|
|
62
68
|
describe('from', () => {
|
|
63
69
|
test('default', () => {
|
|
@@ -423,7 +429,34 @@ describe('from', () => {
|
|
|
423
429
|
`)
|
|
424
430
|
})
|
|
425
431
|
|
|
426
|
-
test('
|
|
432
|
+
test('with signature (multisig)', () => {
|
|
433
|
+
const authorization = KeyAuthorization.from({
|
|
434
|
+
address,
|
|
435
|
+
chainId: 1n,
|
|
436
|
+
type: 'secp256k1',
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
const signed = KeyAuthorization.from(authorization, {
|
|
440
|
+
signature: signature_multisig,
|
|
441
|
+
})
|
|
442
|
+
expect(signed.signature).toEqual(signature_multisig)
|
|
443
|
+
expect(
|
|
444
|
+
KeyAuthorization.from(authorization, {
|
|
445
|
+
signature: SignatureEnvelope.serialize(signature_multisig),
|
|
446
|
+
}).signature,
|
|
447
|
+
).toEqual(signature_multisig)
|
|
448
|
+
expect(
|
|
449
|
+
KeyAuthorization.from({
|
|
450
|
+
...authorization,
|
|
451
|
+
signature: signature_multisig,
|
|
452
|
+
}).signature,
|
|
453
|
+
).toEqual(signature_multisig)
|
|
454
|
+
expect(
|
|
455
|
+
KeyAuthorization.deserialize(KeyAuthorization.serialize(signed)),
|
|
456
|
+
).toEqual(signed)
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
test('rejects a keychain signature', () => {
|
|
427
460
|
const authorization = KeyAuthorization.from({
|
|
428
461
|
address,
|
|
429
462
|
chainId: 1n,
|
|
@@ -432,43 +465,55 @@ describe('from', () => {
|
|
|
432
465
|
|
|
433
466
|
expect(() =>
|
|
434
467
|
KeyAuthorization.from(authorization, {
|
|
435
|
-
signature:
|
|
468
|
+
signature: signature_keychain as never,
|
|
436
469
|
}),
|
|
437
470
|
).toThrowErrorMatchingInlineSnapshot(
|
|
438
|
-
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`
|
|
471
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
|
|
439
472
|
)
|
|
440
473
|
|
|
441
474
|
expect(() =>
|
|
442
475
|
KeyAuthorization.from(authorization, {
|
|
443
|
-
signature: SignatureEnvelope.serialize(
|
|
476
|
+
signature: SignatureEnvelope.serialize(signature_keychain),
|
|
444
477
|
}),
|
|
445
478
|
).toThrowErrorMatchingInlineSnapshot(
|
|
446
|
-
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`
|
|
479
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
|
|
447
480
|
)
|
|
448
481
|
|
|
449
482
|
expect(() =>
|
|
450
483
|
KeyAuthorization.from({
|
|
451
484
|
...authorization,
|
|
452
|
-
signature:
|
|
485
|
+
signature: signature_keychain,
|
|
453
486
|
} as never),
|
|
454
487
|
).toThrowErrorMatchingInlineSnapshot(
|
|
455
|
-
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`
|
|
488
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
|
|
456
489
|
)
|
|
457
490
|
})
|
|
458
491
|
})
|
|
459
492
|
|
|
460
493
|
describe('fromRpc', () => {
|
|
461
|
-
test('
|
|
494
|
+
test('multisig', () => {
|
|
495
|
+
const authorization = KeyAuthorization.fromRpc({
|
|
496
|
+
chainId: '0x1',
|
|
497
|
+
expiry: null,
|
|
498
|
+
keyId: address,
|
|
499
|
+
keyType: 'secp256k1',
|
|
500
|
+
signature: SignatureEnvelope.toRpc(signature_multisig),
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
expect(authorization.signature).toEqual(signature_multisig)
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
test('rejects a keychain signature', () => {
|
|
462
507
|
expect(() =>
|
|
463
508
|
KeyAuthorization.fromRpc({
|
|
464
509
|
chainId: '0x1',
|
|
465
510
|
expiry: null,
|
|
466
511
|
keyId: address,
|
|
467
512
|
keyType: 'secp256k1',
|
|
468
|
-
signature: SignatureEnvelope.toRpc(
|
|
513
|
+
signature: SignatureEnvelope.toRpc(signature_keychain),
|
|
469
514
|
} as never),
|
|
470
515
|
).toThrowErrorMatchingInlineSnapshot(
|
|
471
|
-
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`
|
|
516
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
|
|
472
517
|
)
|
|
473
518
|
})
|
|
474
519
|
|
|
@@ -2401,12 +2446,12 @@ describe('admin keys (TIP-1049)', () => {
|
|
|
2401
2446
|
expect(restored.account).toBeUndefined()
|
|
2402
2447
|
})
|
|
2403
2448
|
|
|
2404
|
-
test('fromTuple:
|
|
2449
|
+
test('fromTuple: preserves a non-admin account binding', () => {
|
|
2405
2450
|
const restored = KeyAuthorization.fromTuple([
|
|
2406
2451
|
['0x01', '0x', address, '0x', [], [], '0x', '0x', account],
|
|
2407
2452
|
])
|
|
2408
|
-
expect(restored.isAdmin).
|
|
2409
|
-
expect(restored.account).
|
|
2453
|
+
expect(restored.isAdmin).toBe(false)
|
|
2454
|
+
expect(restored.account).toBe(account)
|
|
2410
2455
|
})
|
|
2411
2456
|
|
|
2412
2457
|
test('fromTuple: extracts isAdmin + account together', () => {
|
|
@@ -2441,6 +2486,20 @@ describe('admin keys (TIP-1049)', () => {
|
|
|
2441
2486
|
expect(restored.account).toBe(account)
|
|
2442
2487
|
})
|
|
2443
2488
|
|
|
2489
|
+
test('serialize/deserialize: roundtrip with non-admin account binding', () => {
|
|
2490
|
+
const authorization = KeyAuthorization.from({
|
|
2491
|
+
address,
|
|
2492
|
+
account,
|
|
2493
|
+
chainId: 1n,
|
|
2494
|
+
isAdmin: false,
|
|
2495
|
+
type: 'secp256k1',
|
|
2496
|
+
})
|
|
2497
|
+
const serialized = KeyAuthorization.serialize(authorization)
|
|
2498
|
+
const restored = KeyAuthorization.deserialize(serialized)
|
|
2499
|
+
expect(restored.isAdmin).toBe(false)
|
|
2500
|
+
expect(restored.account).toBe(account)
|
|
2501
|
+
})
|
|
2502
|
+
|
|
2444
2503
|
test('toRpc/fromRpc: roundtrip with isAdmin + account', () => {
|
|
2445
2504
|
const authorization = KeyAuthorization.from(
|
|
2446
2505
|
{
|
|
@@ -2471,15 +2530,15 @@ describe('admin keys (TIP-1049)', () => {
|
|
|
2471
2530
|
expect(restored.account).toBeUndefined()
|
|
2472
2531
|
})
|
|
2473
2532
|
|
|
2474
|
-
test('fromRpc:
|
|
2533
|
+
test('fromRpc: preserves a non-admin account binding', () => {
|
|
2475
2534
|
const authorization = KeyAuthorization.from(
|
|
2476
2535
|
{ address, chainId: 1n, type: 'secp256k1' },
|
|
2477
2536
|
{ signature: SignatureEnvelope.from(signature_secp256k1) },
|
|
2478
2537
|
)
|
|
2479
2538
|
const rpc = KeyAuthorization.toRpc(authorization)
|
|
2480
2539
|
const restored = KeyAuthorization.fromRpc({ ...rpc, account })
|
|
2481
|
-
expect(restored.isAdmin).
|
|
2482
|
-
expect(restored.account).
|
|
2540
|
+
expect(restored.isAdmin).toBe(false)
|
|
2541
|
+
expect(restored.account).toBe(account)
|
|
2483
2542
|
})
|
|
2484
2543
|
|
|
2485
2544
|
test('hash: changes when admin pair is added', () => {
|