ox 0.14.33 → 0.14.34
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/_cjs/tempo/KeyAuthorization.js +6 -6
- package/_cjs/tempo/KeyAuthorization.js.map +1 -1
- package/_cjs/tempo/MultisigConfig.js +4 -4
- package/_cjs/tempo/MultisigConfig.js.map +1 -1
- package/_cjs/tempo/SignatureEnvelope.js +23 -15
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/tempo/KeyAuthorization.js +7 -13
- package/_esm/tempo/KeyAuthorization.js.map +1 -1
- package/_esm/tempo/MultisigConfig.js +17 -16
- package/_esm/tempo/MultisigConfig.js.map +1 -1
- package/_esm/tempo/SignatureEnvelope.js +42 -28
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/tempo/index.js +2 -2
- package/_esm/version.js +1 -1
- package/_types/tempo/KeyAuthorization.d.ts +16 -12
- package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
- package/_types/tempo/MultisigConfig.d.ts +20 -17
- package/_types/tempo/MultisigConfig.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +26 -18
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/tempo/index.d.ts +2 -2
- package/_types/version.d.ts +1 -1
- package/package.json +1 -1
- package/tempo/KeyAuthorization.test-d.ts +41 -6
- package/tempo/KeyAuthorization.test.ts +106 -60
- package/tempo/KeyAuthorization.ts +36 -38
- package/tempo/MultisigConfig.test.ts +28 -8
- package/tempo/MultisigConfig.ts +28 -19
- package/tempo/SignatureEnvelope.test.ts +64 -19
- package/tempo/SignatureEnvelope.ts +52 -36
- package/tempo/index.ts +2 -2
- package/tempo/multisig.e2e.test.ts +402 -145
- package/version.ts +1 -1
|
@@ -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', () => {
|
|
@@ -448,7 +454,34 @@ describe('from', () => {
|
|
|
448
454
|
`)
|
|
449
455
|
})
|
|
450
456
|
|
|
451
|
-
test('
|
|
457
|
+
test('with signature (multisig)', () => {
|
|
458
|
+
const authorization = KeyAuthorization.from({
|
|
459
|
+
address,
|
|
460
|
+
chainId: 1n,
|
|
461
|
+
type: 'secp256k1',
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
const signed = KeyAuthorization.from(authorization, {
|
|
465
|
+
signature: signature_multisig,
|
|
466
|
+
})
|
|
467
|
+
expect(signed.signature).toEqual(signature_multisig)
|
|
468
|
+
expect(
|
|
469
|
+
KeyAuthorization.from(authorization, {
|
|
470
|
+
signature: SignatureEnvelope.serialize(signature_multisig),
|
|
471
|
+
}).signature,
|
|
472
|
+
).toEqual(signature_multisig)
|
|
473
|
+
expect(
|
|
474
|
+
KeyAuthorization.from({
|
|
475
|
+
...authorization,
|
|
476
|
+
signature: signature_multisig,
|
|
477
|
+
}).signature,
|
|
478
|
+
).toEqual(signature_multisig)
|
|
479
|
+
expect(
|
|
480
|
+
KeyAuthorization.deserialize(KeyAuthorization.serialize(signed)),
|
|
481
|
+
).toEqual(signed)
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
test('rejects a keychain signature', () => {
|
|
452
485
|
const authorization = KeyAuthorization.from({
|
|
453
486
|
address,
|
|
454
487
|
chainId: 1n,
|
|
@@ -457,50 +490,55 @@ describe('from', () => {
|
|
|
457
490
|
|
|
458
491
|
expect(() =>
|
|
459
492
|
KeyAuthorization.from(authorization, {
|
|
460
|
-
signature:
|
|
493
|
+
signature: signature_keychain as never,
|
|
461
494
|
}),
|
|
462
495
|
).toThrowErrorMatchingInlineSnapshot(
|
|
463
|
-
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`
|
|
496
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
|
|
464
497
|
)
|
|
465
498
|
|
|
466
499
|
expect(() =>
|
|
467
500
|
KeyAuthorization.from(authorization, {
|
|
468
|
-
signature: SignatureEnvelope.serialize(
|
|
501
|
+
signature: SignatureEnvelope.serialize(signature_keychain),
|
|
469
502
|
}),
|
|
470
503
|
).toThrowErrorMatchingInlineSnapshot(
|
|
471
|
-
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`
|
|
504
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
|
|
472
505
|
)
|
|
473
506
|
|
|
474
507
|
expect(() =>
|
|
475
508
|
KeyAuthorization.from({
|
|
476
509
|
...authorization,
|
|
477
|
-
signature:
|
|
510
|
+
signature: signature_keychain,
|
|
478
511
|
} as never),
|
|
479
512
|
).toThrowErrorMatchingInlineSnapshot(
|
|
480
|
-
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`
|
|
513
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
|
|
481
514
|
)
|
|
482
515
|
})
|
|
483
516
|
})
|
|
484
517
|
|
|
485
518
|
describe('fromRpc', () => {
|
|
486
|
-
test('
|
|
519
|
+
test('multisig', () => {
|
|
520
|
+
const authorization = KeyAuthorization.fromRpc({
|
|
521
|
+
chainId: '0x1',
|
|
522
|
+
expiry: null,
|
|
523
|
+
keyId: address,
|
|
524
|
+
keyType: 'secp256k1',
|
|
525
|
+
signature: SignatureEnvelope.toRpc(signature_multisig),
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
expect(authorization.signature).toEqual(signature_multisig)
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
test('rejects a keychain signature', () => {
|
|
487
532
|
expect(() =>
|
|
488
533
|
KeyAuthorization.fromRpc({
|
|
489
534
|
chainId: '0x1',
|
|
490
535
|
expiry: null,
|
|
491
536
|
keyId: address,
|
|
492
537
|
keyType: 'secp256k1',
|
|
493
|
-
signature:
|
|
494
|
-
account: address,
|
|
495
|
-
signatures: [
|
|
496
|
-
SignatureEnvelope.toRpc(
|
|
497
|
-
SignatureEnvelope.from(signature_secp256k1),
|
|
498
|
-
),
|
|
499
|
-
],
|
|
500
|
-
},
|
|
538
|
+
signature: SignatureEnvelope.toRpc(signature_keychain),
|
|
501
539
|
} as never),
|
|
502
540
|
).toThrowErrorMatchingInlineSnapshot(
|
|
503
|
-
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`
|
|
541
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
|
|
504
542
|
)
|
|
505
543
|
})
|
|
506
544
|
|
|
@@ -721,15 +759,13 @@ describe('fromRpc', () => {
|
|
|
721
759
|
})
|
|
722
760
|
|
|
723
761
|
describe('fromTuple', () => {
|
|
724
|
-
test('
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
).
|
|
731
|
-
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
|
|
732
|
-
)
|
|
762
|
+
test('multisig', () => {
|
|
763
|
+
const authorization = KeyAuthorization.fromTuple([
|
|
764
|
+
['0x1', '0x', address],
|
|
765
|
+
SignatureEnvelope.serialize(signature_multisig),
|
|
766
|
+
])
|
|
767
|
+
|
|
768
|
+
expect(authorization.signature).toEqual(signature_multisig)
|
|
733
769
|
})
|
|
734
770
|
|
|
735
771
|
test('default', () => {
|
|
@@ -1069,16 +1105,14 @@ describe('getSignPayload', () => {
|
|
|
1069
1105
|
})
|
|
1070
1106
|
|
|
1071
1107
|
describe('deserialize', () => {
|
|
1072
|
-
test('
|
|
1108
|
+
test('multisig', () => {
|
|
1073
1109
|
const serialized = Rlp.fromHex([
|
|
1074
1110
|
['0x1', '0x', address],
|
|
1075
1111
|
SignatureEnvelope.serialize(signature_multisig),
|
|
1076
1112
|
])
|
|
1077
1113
|
|
|
1078
|
-
expect(()
|
|
1079
|
-
|
|
1080
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
1081
|
-
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
|
|
1114
|
+
expect(KeyAuthorization.deserialize(serialized).signature).toEqual(
|
|
1115
|
+
signature_multisig,
|
|
1082
1116
|
)
|
|
1083
1117
|
})
|
|
1084
1118
|
|
|
@@ -1252,16 +1286,16 @@ describe('serialize', () => {
|
|
|
1252
1286
|
})
|
|
1253
1287
|
|
|
1254
1288
|
describe('toRpc', () => {
|
|
1255
|
-
test('
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
).
|
|
1264
|
-
|
|
1289
|
+
test('multisig', () => {
|
|
1290
|
+
const authorization = KeyAuthorization.toRpc({
|
|
1291
|
+
address,
|
|
1292
|
+
chainId: 1n,
|
|
1293
|
+
signature: signature_multisig,
|
|
1294
|
+
type: 'secp256k1',
|
|
1295
|
+
})
|
|
1296
|
+
|
|
1297
|
+
expect(authorization.signature).toEqual(
|
|
1298
|
+
SignatureEnvelope.toRpc(signature_multisig),
|
|
1265
1299
|
)
|
|
1266
1300
|
})
|
|
1267
1301
|
|
|
@@ -1522,17 +1556,15 @@ describe('toRpc', () => {
|
|
|
1522
1556
|
})
|
|
1523
1557
|
|
|
1524
1558
|
describe('toTuple', () => {
|
|
1525
|
-
test('
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
).
|
|
1534
|
-
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
|
|
1535
|
-
)
|
|
1559
|
+
test('multisig', () => {
|
|
1560
|
+
const tuple = KeyAuthorization.toTuple({
|
|
1561
|
+
address,
|
|
1562
|
+
chainId: 1n,
|
|
1563
|
+
signature: signature_multisig,
|
|
1564
|
+
type: 'secp256k1',
|
|
1565
|
+
})
|
|
1566
|
+
|
|
1567
|
+
expect(tuple[1]).toBe(SignatureEnvelope.serialize(signature_multisig))
|
|
1536
1568
|
})
|
|
1537
1569
|
|
|
1538
1570
|
test('default', () => {
|
|
@@ -2483,12 +2515,12 @@ describe('admin keys (TIP-1049)', () => {
|
|
|
2483
2515
|
expect(restored.account).toBeUndefined()
|
|
2484
2516
|
})
|
|
2485
2517
|
|
|
2486
|
-
test('fromTuple:
|
|
2518
|
+
test('fromTuple: preserves a non-admin account binding', () => {
|
|
2487
2519
|
const restored = KeyAuthorization.fromTuple([
|
|
2488
2520
|
['0x01', '0x', address, '0x', [], [], '0x', '0x', account],
|
|
2489
2521
|
])
|
|
2490
|
-
expect(restored.isAdmin).
|
|
2491
|
-
expect(restored.account).
|
|
2522
|
+
expect(restored.isAdmin).toBe(false)
|
|
2523
|
+
expect(restored.account).toBe(account)
|
|
2492
2524
|
})
|
|
2493
2525
|
|
|
2494
2526
|
test('fromTuple: extracts isAdmin + account together', () => {
|
|
@@ -2523,6 +2555,20 @@ describe('admin keys (TIP-1049)', () => {
|
|
|
2523
2555
|
expect(restored.account).toBe(account)
|
|
2524
2556
|
})
|
|
2525
2557
|
|
|
2558
|
+
test('serialize/deserialize: roundtrip with non-admin account binding', () => {
|
|
2559
|
+
const authorization = KeyAuthorization.from({
|
|
2560
|
+
address,
|
|
2561
|
+
account,
|
|
2562
|
+
chainId: 1n,
|
|
2563
|
+
isAdmin: false,
|
|
2564
|
+
type: 'secp256k1',
|
|
2565
|
+
})
|
|
2566
|
+
const serialized = KeyAuthorization.serialize(authorization)
|
|
2567
|
+
const restored = KeyAuthorization.deserialize(serialized)
|
|
2568
|
+
expect(restored.isAdmin).toBe(false)
|
|
2569
|
+
expect(restored.account).toBe(account)
|
|
2570
|
+
})
|
|
2571
|
+
|
|
2526
2572
|
test('toRpc/fromRpc: roundtrip with isAdmin + account', () => {
|
|
2527
2573
|
const authorization = KeyAuthorization.from(
|
|
2528
2574
|
{
|
|
@@ -2553,15 +2599,15 @@ describe('admin keys (TIP-1049)', () => {
|
|
|
2553
2599
|
expect(restored.account).toBeUndefined()
|
|
2554
2600
|
})
|
|
2555
2601
|
|
|
2556
|
-
test('fromRpc:
|
|
2602
|
+
test('fromRpc: preserves a non-admin account binding', () => {
|
|
2557
2603
|
const authorization = KeyAuthorization.from(
|
|
2558
2604
|
{ address, chainId: 1n, type: 'secp256k1' },
|
|
2559
2605
|
{ signature: SignatureEnvelope.from(signature_secp256k1) },
|
|
2560
2606
|
)
|
|
2561
2607
|
const rpc = KeyAuthorization.toRpc(authorization)
|
|
2562
2608
|
const restored = KeyAuthorization.fromRpc({ ...rpc, account })
|
|
2563
|
-
expect(restored.isAdmin).
|
|
2564
|
-
expect(restored.account).
|
|
2609
|
+
expect(restored.isAdmin).toBe(false)
|
|
2610
|
+
expect(restored.account).toBe(account)
|
|
2565
2611
|
})
|
|
2566
2612
|
|
|
2567
2613
|
test('hash: changes when admin pair is added', () => {
|
|
@@ -81,13 +81,22 @@ export type KeyAuthorization<
|
|
|
81
81
|
| {}
|
|
82
82
|
> &
|
|
83
83
|
(signed extends true
|
|
84
|
-
? { signature:
|
|
84
|
+
? { signature: Signature<bigintType, numberType> }
|
|
85
85
|
: {
|
|
86
|
-
signature?:
|
|
87
|
-
| SignatureEnvelope.Primitive<bigintType, numberType>
|
|
88
|
-
| undefined
|
|
86
|
+
signature?: Signature<bigintType, numberType> | undefined
|
|
89
87
|
})
|
|
90
88
|
|
|
89
|
+
/** Signature that can authorize an access key. */
|
|
90
|
+
export type Signature<bigintType = bigint, numberType = number> = OneOf<
|
|
91
|
+
| SignatureEnvelope.Primitive<bigintType, numberType>
|
|
92
|
+
| SignatureEnvelope.Multisig<bigintType, numberType>
|
|
93
|
+
>
|
|
94
|
+
|
|
95
|
+
/** RPC-formatted signature that can authorize an access key. */
|
|
96
|
+
export type SignatureRpc = OneOf<
|
|
97
|
+
SignatureEnvelope.PrimitiveRpc | SignatureEnvelope.MultisigRpc
|
|
98
|
+
>
|
|
99
|
+
|
|
91
100
|
/** Input type for a Key Authorization. */
|
|
92
101
|
export type Input = KeyAuthorization<
|
|
93
102
|
false,
|
|
@@ -114,8 +123,8 @@ export type Rpc = {
|
|
|
114
123
|
keyType: SignatureEnvelope.Type
|
|
115
124
|
/** Token spending limits. */
|
|
116
125
|
limits?: readonly RpcTokenLimit[] | undefined
|
|
117
|
-
/**
|
|
118
|
-
signature:
|
|
126
|
+
/** Signature authorizing this key. */
|
|
127
|
+
signature: SignatureRpc
|
|
119
128
|
/** Optional 32-byte witness (hex). */
|
|
120
129
|
witness?: Hex.Hex | null | undefined
|
|
121
130
|
}
|
|
@@ -146,10 +155,11 @@ export type Signed<
|
|
|
146
155
|
addressType = Address.Address,
|
|
147
156
|
> = KeyAuthorization<true, bigintType, numberType, addressType>
|
|
148
157
|
|
|
149
|
-
type
|
|
150
|
-
| UnionPartialBy<
|
|
158
|
+
type SignatureValue =
|
|
159
|
+
| UnionPartialBy<Signature, 'prehash' | 'type'>
|
|
151
160
|
| SignatureEnvelope.Secp256k1Flat
|
|
152
161
|
| SignatureEnvelope.Serialized
|
|
162
|
+
| SignatureEnvelope.from.MultisigFromInitialConfig
|
|
153
163
|
|
|
154
164
|
type BaseTuple = readonly [
|
|
155
165
|
chainId: Hex.Hex,
|
|
@@ -408,7 +418,7 @@ export type TokenLimit<
|
|
|
408
418
|
*/
|
|
409
419
|
export function from<
|
|
410
420
|
const authorization extends Input | Rpc,
|
|
411
|
-
const signature extends
|
|
421
|
+
const signature extends SignatureValue | undefined = undefined,
|
|
412
422
|
>(
|
|
413
423
|
authorization: authorization | KeyAuthorization,
|
|
414
424
|
options: from.Options<signature> = {},
|
|
@@ -467,29 +477,25 @@ export function from<
|
|
|
467
477
|
|
|
468
478
|
export declare namespace from {
|
|
469
479
|
type Options<
|
|
470
|
-
signature extends
|
|
471
|
-
| PrimitiveSignatureValue
|
|
472
|
-
| undefined,
|
|
480
|
+
signature extends SignatureValue | undefined = SignatureValue | undefined,
|
|
473
481
|
> = {
|
|
474
|
-
/** The
|
|
475
|
-
signature?: signature |
|
|
482
|
+
/** The signature to attach to the Key Authorization. */
|
|
483
|
+
signature?: signature | Signature | undefined
|
|
476
484
|
}
|
|
477
485
|
|
|
478
486
|
type ReturnType<
|
|
479
487
|
authorization extends KeyAuthorization | Input | Rpc = KeyAuthorization,
|
|
480
|
-
signature extends
|
|
481
|
-
| PrimitiveSignatureValue
|
|
482
|
-
| undefined,
|
|
488
|
+
signature extends SignatureValue | undefined = SignatureValue | undefined,
|
|
483
489
|
> = Compute<
|
|
484
490
|
authorization extends Rpc
|
|
485
491
|
? Signed
|
|
486
492
|
: TempoAddress.ResolveAddresses<
|
|
487
493
|
authorization &
|
|
488
|
-
(signature extends
|
|
494
|
+
(signature extends SignatureValue
|
|
489
495
|
? {
|
|
490
496
|
signature: Extract<
|
|
491
497
|
SignatureEnvelope.from.ReturnValue<signature>,
|
|
492
|
-
|
|
498
|
+
Signature
|
|
493
499
|
>
|
|
494
500
|
}
|
|
495
501
|
: {})
|
|
@@ -551,11 +557,8 @@ export function fromRpc(authorization: Rpc): Signed {
|
|
|
551
557
|
})
|
|
552
558
|
: undefined
|
|
553
559
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
// assignability without `exactOptionalPropertyTypes` (#290).
|
|
557
|
-
const adminPair =
|
|
558
|
-
account !== undefined && isAdmin ? { account, isAdmin: true as const } : {}
|
|
560
|
+
const accountBinding =
|
|
561
|
+
account !== undefined ? { account, isAdmin: isAdmin ?? false } : {}
|
|
559
562
|
|
|
560
563
|
return {
|
|
561
564
|
address: keyId,
|
|
@@ -572,7 +575,7 @@ export function fromRpc(authorization: Rpc): Signed {
|
|
|
572
575
|
signature,
|
|
573
576
|
type: keyType,
|
|
574
577
|
...(witness !== undefined ? { witness } : {}),
|
|
575
|
-
...
|
|
578
|
+
...accountBinding,
|
|
576
579
|
}
|
|
577
580
|
}
|
|
578
581
|
|
|
@@ -694,11 +697,8 @@ export function fromTuple<const tuple extends Tuple>(
|
|
|
694
697
|
const account = isAbsent(rawAccount)
|
|
695
698
|
? undefined
|
|
696
699
|
: (rawAccount as Address.Address)
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
// but the orphan field is dropped (since the public API requires both).
|
|
700
|
-
const adminPair =
|
|
701
|
-
account !== undefined && isAdmin ? { account, isAdmin: true as const } : {}
|
|
700
|
+
const accountBinding =
|
|
701
|
+
account !== undefined ? { account, isAdmin: isAdmin ?? false } : {}
|
|
702
702
|
const args: KeyAuthorization = {
|
|
703
703
|
address: keyId,
|
|
704
704
|
chainId: chainId === '0x' ? 0n : Hex.toBigInt(chainId),
|
|
@@ -707,7 +707,7 @@ export function fromTuple<const tuple extends Tuple>(
|
|
|
707
707
|
...(limits !== undefined ? { limits } : {}),
|
|
708
708
|
...(scopes !== undefined ? { scopes } : {}),
|
|
709
709
|
...(witness !== undefined ? { witness } : {}),
|
|
710
|
-
...
|
|
710
|
+
...accountBinding,
|
|
711
711
|
}
|
|
712
712
|
if (signatureSerialized) {
|
|
713
713
|
const signature = SignatureEnvelope.deserialize(signatureSerialized)
|
|
@@ -963,9 +963,7 @@ export function toRpc(authorization: Signed): Rpc {
|
|
|
963
963
|
limit: Hex.fromNumber(limit),
|
|
964
964
|
...(period ? { period: numberToHex(period) } : {}),
|
|
965
965
|
})),
|
|
966
|
-
signature: SignatureEnvelope.toRpc(
|
|
967
|
-
signature,
|
|
968
|
-
) as SignatureEnvelope.PrimitiveRpc,
|
|
966
|
+
signature: SignatureEnvelope.toRpc(signature) as SignatureRpc,
|
|
969
967
|
...(allowedCalls ? { allowedCalls } : {}),
|
|
970
968
|
...(witness !== undefined ? { witness } : {}),
|
|
971
969
|
...(isAdmin ? { isAdmin: true } : {}),
|
|
@@ -1162,8 +1160,8 @@ function assertWitness(witness: Hex.Hex): void {
|
|
|
1162
1160
|
|
|
1163
1161
|
function assertSignature<bigintType, numberType>(
|
|
1164
1162
|
signature: SignatureEnvelope.SignatureEnvelope<bigintType, numberType>,
|
|
1165
|
-
): asserts signature is
|
|
1166
|
-
if (signature.type === 'keychain'
|
|
1163
|
+
): asserts signature is Signature<bigintType, numberType> {
|
|
1164
|
+
if (signature.type === 'keychain')
|
|
1167
1165
|
throw new InvalidSignatureTypeError(signature.type)
|
|
1168
1166
|
}
|
|
1169
1167
|
|
|
@@ -1191,12 +1189,12 @@ export class InvalidAdminMarkerError extends Error {
|
|
|
1191
1189
|
}
|
|
1192
1190
|
}
|
|
1193
1191
|
|
|
1194
|
-
/** Thrown when a key authorization contains a
|
|
1192
|
+
/** Thrown when a key authorization contains a keychain signature. */
|
|
1195
1193
|
export class InvalidSignatureTypeError extends Error {
|
|
1196
1194
|
override readonly name = 'KeyAuthorization.InvalidSignatureTypeError'
|
|
1197
1195
|
constructor(type: SignatureEnvelope.SignatureEnvelope['type']) {
|
|
1198
1196
|
super(
|
|
1199
|
-
`Signature type \`${type}\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`
|
|
1197
|
+
`Signature type \`${type}\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.`,
|
|
1200
1198
|
)
|
|
1201
1199
|
}
|
|
1202
1200
|
}
|
|
@@ -86,24 +86,44 @@ describe('getSignPayload', () => {
|
|
|
86
86
|
test('matches independent ground truth', () => {
|
|
87
87
|
expect(
|
|
88
88
|
MultisigConfig.getSignPayload({
|
|
89
|
-
payload: `0x${'
|
|
90
|
-
|
|
89
|
+
payload: `0x${'42'.repeat(32)}`,
|
|
90
|
+
initialConfig: singleOwnerConfig,
|
|
91
91
|
}),
|
|
92
92
|
).toMatchInlineSnapshot(
|
|
93
|
-
`"
|
|
93
|
+
`"0xbf944a7a752b2cfab0418d5fb4591c5a7ff62976488edce11794d7f35fb34f41"`,
|
|
94
94
|
)
|
|
95
95
|
})
|
|
96
96
|
|
|
97
|
-
test('behavior: `
|
|
97
|
+
test('behavior: `initialConfig` and `{ account }` produce identical digests', () => {
|
|
98
98
|
const account = MultisigConfig.getAddress(singleOwnerConfig)
|
|
99
99
|
const payload = `0x${'42'.repeat(32)}` as const
|
|
100
100
|
expect(
|
|
101
101
|
MultisigConfig.getSignPayload({
|
|
102
102
|
payload,
|
|
103
|
-
|
|
103
|
+
initialConfig: singleOwnerConfig,
|
|
104
104
|
}),
|
|
105
105
|
).toBe(MultisigConfig.getSignPayload({ payload, account }))
|
|
106
106
|
})
|
|
107
|
+
|
|
108
|
+
test('differs for a different config version', () => {
|
|
109
|
+
const value = {
|
|
110
|
+
payload: `0x${'42'.repeat(32)}` as const,
|
|
111
|
+
initialConfig: singleOwnerConfig,
|
|
112
|
+
}
|
|
113
|
+
expect(MultisigConfig.getSignPayload(value)).not.toBe(
|
|
114
|
+
MultisigConfig.getSignPayload({ ...value, version: 1n }),
|
|
115
|
+
)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('defaults the config version to zero', () => {
|
|
119
|
+
const value = {
|
|
120
|
+
payload: `0x${'42'.repeat(32)}` as const,
|
|
121
|
+
initialConfig: singleOwnerConfig,
|
|
122
|
+
}
|
|
123
|
+
expect(MultisigConfig.getSignPayload(value)).toBe(
|
|
124
|
+
MultisigConfig.getSignPayload({ ...value, version: 0n }),
|
|
125
|
+
)
|
|
126
|
+
})
|
|
107
127
|
})
|
|
108
128
|
|
|
109
129
|
describe('toTuple / fromTuple', () => {
|
|
@@ -149,8 +169,8 @@ describe('assert / validate', () => {
|
|
|
149
169
|
expect(MultisigConfig.validate({ threshold: 1, owners: [] })).toBe(false)
|
|
150
170
|
})
|
|
151
171
|
|
|
152
|
-
test('accepts
|
|
153
|
-
const owners = Array.from({ length:
|
|
172
|
+
test('accepts 50 owners', () => {
|
|
173
|
+
const owners = Array.from({ length: 50 }, (_, i) => ({
|
|
154
174
|
owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
|
|
155
175
|
weight: 1,
|
|
156
176
|
}))
|
|
@@ -163,7 +183,7 @@ describe('assert / validate', () => {
|
|
|
163
183
|
})
|
|
164
184
|
|
|
165
185
|
test('too many owners', () => {
|
|
166
|
-
const owners = Array.from({ length:
|
|
186
|
+
const owners = Array.from({ length: 51 }, (_, i) => ({
|
|
167
187
|
owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
|
|
168
188
|
weight: 1,
|
|
169
189
|
}))
|
package/tempo/MultisigConfig.ts
CHANGED
|
@@ -6,7 +6,7 @@ import * as Hex from '../core/Hex.js'
|
|
|
6
6
|
import type { Compute, OneOf } from '../core/internal/types.js'
|
|
7
7
|
|
|
8
8
|
/** Maximum number of owners allowed in a native multisig config. */
|
|
9
|
-
export const maxOwners =
|
|
9
|
+
export const maxOwners = 50
|
|
10
10
|
|
|
11
11
|
/** Maximum threshold accepted by a native multisig config. */
|
|
12
12
|
export const maxThreshold = 8
|
|
@@ -20,7 +20,7 @@ export const maxSignatures = maxThreshold
|
|
|
20
20
|
*/
|
|
21
21
|
export const maxNestingDepth = 2
|
|
22
22
|
|
|
23
|
-
/** Maximum encoded byte length for one owner approval. */
|
|
23
|
+
/** Maximum encoded byte length for one primitive owner approval. */
|
|
24
24
|
export const maxOwnerSignatureBytes = 2049
|
|
25
25
|
|
|
26
26
|
/** Tempo signature type byte for native multisig signatures. */
|
|
@@ -224,14 +224,14 @@ export function fromTuple(tuple: Tuple): Config {
|
|
|
224
224
|
* ```ts twoslash
|
|
225
225
|
* import { MultisigConfig } from 'ox/tempo'
|
|
226
226
|
*
|
|
227
|
-
* const
|
|
227
|
+
* const initialConfig = MultisigConfig.from({
|
|
228
228
|
* threshold: 1,
|
|
229
229
|
* owners: [
|
|
230
230
|
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
231
231
|
* ],
|
|
232
232
|
* })
|
|
233
233
|
*
|
|
234
|
-
* const address = MultisigConfig.getAddress(
|
|
234
|
+
* const address = MultisigConfig.getAddress(initialConfig)
|
|
235
235
|
* ```
|
|
236
236
|
*
|
|
237
237
|
* @param config - The initial (bootstrap) multisig config.
|
|
@@ -272,13 +272,13 @@ export declare namespace getAddress {
|
|
|
272
272
|
/**
|
|
273
273
|
* Computes the digest a native multisig owner approves (signs).
|
|
274
274
|
*
|
|
275
|
-
* `keccak256("tempo:multisig:signature" || inner_digest || account)`,
|
|
275
|
+
* `keccak256("tempo:multisig:signature" || inner_digest || account || uint64be(version))`,
|
|
276
276
|
* where `inner_digest` is the transaction sign payload
|
|
277
277
|
* ({@link ox#TxEnvelopeTempo.(getSignPayload:function)}).
|
|
278
278
|
*
|
|
279
|
-
* The digest is keyed on the permanent `account` derived from the
|
|
280
|
-
* (bootstrap) config
|
|
281
|
-
* is the
|
|
279
|
+
* The digest is keyed on the permanent `account` derived from the initial
|
|
280
|
+
* (bootstrap) config and the current config `version`. Bootstrap approvals use
|
|
281
|
+
* version `0n`, which is also the default; each config update increments it.
|
|
282
282
|
*
|
|
283
283
|
* For a nested multisig owner approval, the parent digest becomes the nested
|
|
284
284
|
* approval's `payload`, with the nested multisig `account`.
|
|
@@ -287,7 +287,7 @@ export declare namespace getAddress {
|
|
|
287
287
|
* ```ts twoslash
|
|
288
288
|
* import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
|
|
289
289
|
*
|
|
290
|
-
* const
|
|
290
|
+
* const initialConfig = MultisigConfig.from({
|
|
291
291
|
* threshold: 1,
|
|
292
292
|
* owners: [
|
|
293
293
|
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
@@ -301,7 +301,7 @@ export declare namespace getAddress {
|
|
|
301
301
|
*
|
|
302
302
|
* const digest = MultisigConfig.getSignPayload({
|
|
303
303
|
* payload: TxEnvelopeTempo.getSignPayload(envelope),
|
|
304
|
-
*
|
|
304
|
+
* initialConfig,
|
|
305
305
|
* })
|
|
306
306
|
* ```
|
|
307
307
|
*
|
|
@@ -314,19 +314,20 @@ export declare namespace getAddress {
|
|
|
314
314
|
* ```ts twoslash
|
|
315
315
|
* import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
|
|
316
316
|
*
|
|
317
|
-
* const
|
|
317
|
+
* const initialConfig = MultisigConfig.from({
|
|
318
318
|
* threshold: 1,
|
|
319
319
|
* owners: [
|
|
320
320
|
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
321
321
|
* ],
|
|
322
322
|
* })
|
|
323
|
-
* const account = MultisigConfig.getAddress(
|
|
323
|
+
* const account = MultisigConfig.getAddress(initialConfig)
|
|
324
324
|
*
|
|
325
325
|
* const envelope = TxEnvelopeTempo.from({ chainId: 1, calls: [] })
|
|
326
326
|
*
|
|
327
327
|
* const digest = MultisigConfig.getSignPayload({
|
|
328
328
|
* payload: TxEnvelopeTempo.getSignPayload(envelope),
|
|
329
|
-
* account
|
|
329
|
+
* account,
|
|
330
|
+
* version: 1n
|
|
330
331
|
* })
|
|
331
332
|
* ```
|
|
332
333
|
*
|
|
@@ -334,13 +335,18 @@ export declare namespace getAddress {
|
|
|
334
335
|
* @returns The owner approval digest.
|
|
335
336
|
*/
|
|
336
337
|
export function getSignPayload(value: getSignPayload.Value): Hex.Hex {
|
|
337
|
-
const { payload } = value
|
|
338
|
+
const { payload, version = 0n } = value
|
|
338
339
|
const account =
|
|
339
340
|
'account' in value && value.account
|
|
340
341
|
? value.account
|
|
341
|
-
: getAddress((value as {
|
|
342
|
+
: getAddress((value as { initialConfig: Config }).initialConfig)
|
|
342
343
|
return Hash.keccak256(
|
|
343
|
-
Hex.concat(
|
|
344
|
+
Hex.concat(
|
|
345
|
+
Hex.fromString(signatureDomain),
|
|
346
|
+
Hex.from(payload),
|
|
347
|
+
account,
|
|
348
|
+
Hex.fromNumber(version, { size: 8 }),
|
|
349
|
+
),
|
|
344
350
|
)
|
|
345
351
|
}
|
|
346
352
|
|
|
@@ -348,6 +354,8 @@ export declare namespace getSignPayload {
|
|
|
348
354
|
type Value = {
|
|
349
355
|
/** The inner transaction sign payload (`tx.signature_hash()`). */
|
|
350
356
|
payload: Hex.Hex | Bytes.Bytes
|
|
357
|
+
/** Current multisig config version. Defaults to `0n`. */
|
|
358
|
+
version?: bigint | undefined
|
|
351
359
|
} & OneOf<
|
|
352
360
|
| {
|
|
353
361
|
/** The native multisig account address. */
|
|
@@ -357,10 +365,10 @@ export declare namespace getSignPayload {
|
|
|
357
365
|
/**
|
|
358
366
|
* The initial multisig config (the bootstrap config that derived the
|
|
359
367
|
* permanent `account`). Used to derive the account automatically.
|
|
360
|
-
* Config updates never change `account`, so the
|
|
361
|
-
*
|
|
368
|
+
* Config updates never change `account`, so the initial config can
|
|
369
|
+
* continue deriving the account for post-update transactions.
|
|
362
370
|
*/
|
|
363
|
-
|
|
371
|
+
initialConfig: Config
|
|
364
372
|
}
|
|
365
373
|
>
|
|
366
374
|
|
|
@@ -369,6 +377,7 @@ export declare namespace getSignPayload {
|
|
|
369
377
|
| Hash.keccak256.ErrorType
|
|
370
378
|
| Hex.concat.ErrorType
|
|
371
379
|
| Hex.from.ErrorType
|
|
380
|
+
| Hex.fromNumber.ErrorType
|
|
372
381
|
| Errors.GlobalErrorType
|
|
373
382
|
}
|
|
374
383
|
|