ox 0.14.44 → 0.14.45
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 +6 -0
- package/_cjs/tempo/KeyAuthorization.js +43 -10
- package/_cjs/tempo/KeyAuthorization.js.map +1 -1
- package/_cjs/tempo/MultisigConfig.js +33 -11
- package/_cjs/tempo/MultisigConfig.js.map +1 -1
- package/_cjs/tempo/MultisigOperation.js +18 -94
- package/_cjs/tempo/MultisigOperation.js.map +1 -1
- package/_cjs/tempo/MultisigSimulation.js +18 -46
- package/_cjs/tempo/MultisigSimulation.js.map +1 -1
- package/_cjs/tempo/SignatureEnvelope.js +72 -109
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/tempo/TransactionRequest.js +5 -1
- package/_cjs/tempo/TransactionRequest.js.map +1 -1
- package/_cjs/tempo/index.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/tempo/KeyAuthorization.js +42 -9
- package/_esm/tempo/KeyAuthorization.js.map +1 -1
- package/_esm/tempo/MultisigConfig.js +41 -22
- package/_esm/tempo/MultisigConfig.js.map +1 -1
- package/_esm/tempo/MultisigOperation.js +20 -100
- package/_esm/tempo/MultisigOperation.js.map +1 -1
- package/_esm/tempo/MultisigSimulation.js +18 -46
- package/_esm/tempo/MultisigSimulation.js.map +1 -1
- package/_esm/tempo/SignatureEnvelope.js +107 -169
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/tempo/TransactionRequest.js +5 -1
- package/_esm/tempo/TransactionRequest.js.map +1 -1
- package/_esm/tempo/index.js +3 -1
- package/_esm/tempo/index.js.map +1 -1
- package/_esm/version.js +1 -1
- package/_types/tempo/KeyAuthorization.d.ts +48 -38
- package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
- package/_types/tempo/MultisigConfig.d.ts +15 -14
- package/_types/tempo/MultisigConfig.d.ts.map +1 -1
- package/_types/tempo/MultisigOperation.d.ts +3 -2
- package/_types/tempo/MultisigOperation.d.ts.map +1 -1
- package/_types/tempo/MultisigSimulation.d.ts +2 -49
- package/_types/tempo/MultisigSimulation.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +69 -128
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/tempo/TransactionRequest.d.ts +5 -2
- package/_types/tempo/TransactionRequest.d.ts.map +1 -1
- package/_types/tempo/index.d.ts +3 -1
- package/_types/tempo/index.d.ts.map +1 -1
- package/_types/version.d.ts +1 -1
- package/package.json +1 -1
- package/tempo/AuthorizationTempo.test.ts +8 -4
- package/tempo/KeyAuthorization.test-d.ts +68 -33
- package/tempo/KeyAuthorization.test.ts +89 -8
- package/tempo/KeyAuthorization.ts +124 -61
- package/tempo/MultisigConfig.test.ts +34 -26
- package/tempo/MultisigConfig.ts +54 -24
- package/tempo/MultisigOperation.test.ts +79 -550
- package/tempo/MultisigOperation.ts +31 -146
- package/tempo/MultisigSimulation.test-d.ts +2 -2
- package/tempo/MultisigSimulation.test.ts +21 -110
- package/tempo/MultisigSimulation.ts +25 -99
- package/tempo/SignatureEnvelope.test-d.ts +29 -67
- package/tempo/SignatureEnvelope.test.ts +301 -568
- package/tempo/SignatureEnvelope.ts +156 -274
- package/tempo/TransactionRequest.test-d.ts +6 -5
- package/tempo/TransactionRequest.test.ts +17 -80
- package/tempo/TransactionRequest.ts +23 -3
- package/tempo/index.ts +3 -1
- package/tempo/multisig.e2e.test.ts +322 -861
- package/version.ts +1 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Address,
|
|
3
|
-
Hash,
|
|
4
3
|
Hex,
|
|
5
4
|
P256,
|
|
6
5
|
PublicKey,
|
|
@@ -68,9 +67,6 @@ const signature_keychain_webauthn = SignatureEnvelope.from({
|
|
|
68
67
|
version: 'v2',
|
|
69
68
|
})
|
|
70
69
|
|
|
71
|
-
const signature_multisig_rpc =
|
|
72
|
-
'0xf89794c4a590afa7337e5cd5eb3aa60cacf91c5400044bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b' as const
|
|
73
|
-
|
|
74
70
|
describe('assert', () => {
|
|
75
71
|
describe('secp256k1', () => {
|
|
76
72
|
test('behavior: validates valid signature', () => {
|
|
@@ -1104,86 +1100,30 @@ describe('from', () => {
|
|
|
1104
1100
|
})
|
|
1105
1101
|
|
|
1106
1102
|
describe('multisig', () => {
|
|
1107
|
-
|
|
1108
|
-
owners: [
|
|
1109
|
-
{
|
|
1110
|
-
owner: '0x1111111111111111111111111111111111111111',
|
|
1111
|
-
weight: 1,
|
|
1112
|
-
},
|
|
1113
|
-
],
|
|
1114
|
-
threshold: 1,
|
|
1115
|
-
})
|
|
1116
|
-
|
|
1117
|
-
test('behavior: derives an initial account from config', () => {
|
|
1103
|
+
test('normalizes the current config', () => {
|
|
1118
1104
|
const envelope = SignatureEnvelope.from({
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
{
|
|
1125
|
-
signatures: [
|
|
1126
|
-
{
|
|
1127
|
-
signature: {
|
|
1128
|
-
r: expect.anything(),
|
|
1129
|
-
s: expect.anything(),
|
|
1130
|
-
yParity: expect.any(Number),
|
|
1131
|
-
},
|
|
1132
|
-
},
|
|
1105
|
+
account: '0x2222222222222222222222222222222222222222',
|
|
1106
|
+
config: {
|
|
1107
|
+
threshold: 1,
|
|
1108
|
+
owners: [
|
|
1109
|
+
{ owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
1133
1110
|
],
|
|
1134
1111
|
},
|
|
1135
|
-
|
|
1112
|
+
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
1113
|
+
})
|
|
1114
|
+
expect(envelope.config).toMatchInlineSnapshot(`
|
|
1136
1115
|
{
|
|
1137
|
-
"
|
|
1138
|
-
"config": {
|
|
1139
|
-
"owners": [
|
|
1140
|
-
{
|
|
1141
|
-
"owner": "0x1111111111111111111111111111111111111111",
|
|
1142
|
-
"weight": 1,
|
|
1143
|
-
},
|
|
1144
|
-
],
|
|
1145
|
-
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
1146
|
-
"threshold": 1,
|
|
1147
|
-
"version": 0n,
|
|
1148
|
-
},
|
|
1149
|
-
"signatures": [
|
|
1116
|
+
"owners": [
|
|
1150
1117
|
{
|
|
1151
|
-
"
|
|
1152
|
-
|
|
1153
|
-
"s": Anything,
|
|
1154
|
-
"yParity": Any<Number>,
|
|
1155
|
-
},
|
|
1156
|
-
"type": "secp256k1",
|
|
1118
|
+
"owner": "0x1111111111111111111111111111111111111111",
|
|
1119
|
+
"weight": 1,
|
|
1157
1120
|
},
|
|
1158
1121
|
],
|
|
1159
|
-
"
|
|
1122
|
+
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
1123
|
+
"threshold": 1,
|
|
1124
|
+
"version": 0n,
|
|
1160
1125
|
}
|
|
1161
|
-
|
|
1162
|
-
)
|
|
1163
|
-
})
|
|
1164
|
-
|
|
1165
|
-
test('behavior: accepts an explicit account for a current config', () => {
|
|
1166
|
-
const envelope = SignatureEnvelope.from({
|
|
1167
|
-
account: '0x2222222222222222222222222222222222222222',
|
|
1168
|
-
config: { ...config, version: 1 },
|
|
1169
|
-
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
1170
|
-
})
|
|
1171
|
-
|
|
1172
|
-
expect(envelope.account).toBe(
|
|
1173
|
-
'0x2222222222222222222222222222222222222222',
|
|
1174
|
-
)
|
|
1175
|
-
expect(envelope.config.version).toBe(1n)
|
|
1176
|
-
})
|
|
1177
|
-
|
|
1178
|
-
test('error: requires an account for a current config', () => {
|
|
1179
|
-
expect(() =>
|
|
1180
|
-
SignatureEnvelope.from({
|
|
1181
|
-
config: { ...config, version: 1n },
|
|
1182
|
-
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
1183
|
-
} as never),
|
|
1184
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
1185
|
-
`[MultisigConfig.InvalidConfigError: Invalid native multisig config: account address requires version zero.]`,
|
|
1186
|
-
)
|
|
1126
|
+
`)
|
|
1187
1127
|
})
|
|
1188
1128
|
})
|
|
1189
1129
|
})
|
|
@@ -1787,6 +1727,8 @@ describe('serialize', () => {
|
|
|
1787
1727
|
})
|
|
1788
1728
|
|
|
1789
1729
|
describe('sortMultisigApprovals', () => {
|
|
1730
|
+
// Build owner key pairs first so we can construct a real genesis config
|
|
1731
|
+
// whose owner set matches the keys that produce the approvals below.
|
|
1790
1732
|
const ownerKeys = Array.from({ length: 3 }, () => {
|
|
1791
1733
|
const privateKey = Secp256k1.randomPrivateKey()
|
|
1792
1734
|
const address = Address.fromPublicKey(
|
|
@@ -1798,16 +1740,19 @@ describe('sortMultisigApprovals', () => {
|
|
|
1798
1740
|
Hex.toBigInt(a.address) < Hex.toBigInt(b.address) ? -1 : 1,
|
|
1799
1741
|
)
|
|
1800
1742
|
|
|
1801
|
-
const
|
|
1802
|
-
owners: ascendingOwners.map((o) => ({ owner: o.address, weight: 1 })),
|
|
1743
|
+
const genesisConfig = MultisigConfig.from({
|
|
1803
1744
|
threshold: 2,
|
|
1745
|
+
owners: ascendingOwners.map((o) => ({ owner: o.address, weight: 1 })),
|
|
1804
1746
|
})
|
|
1805
|
-
const account = MultisigConfig.getAddress(config)
|
|
1806
1747
|
const payload = `0x${'42'.repeat(32)}` as const
|
|
1748
|
+
const account = MultisigConfig.getAddress(genesisConfig, {
|
|
1749
|
+
factory: '0x7171717171717171717171717171717171717171',
|
|
1750
|
+
})
|
|
1751
|
+
const version = 3n
|
|
1807
1752
|
const digest = MultisigConfig.getSignPayload({
|
|
1808
|
-
account,
|
|
1809
|
-
config,
|
|
1810
1753
|
payload,
|
|
1754
|
+
account,
|
|
1755
|
+
config: { version },
|
|
1811
1756
|
})
|
|
1812
1757
|
|
|
1813
1758
|
const owners = ownerKeys.map((owner) => ({
|
|
@@ -1824,7 +1769,7 @@ describe('sortMultisigApprovals', () => {
|
|
|
1824
1769
|
test('behavior: orders approvals ascending by recovered owner address', () => {
|
|
1825
1770
|
const ordered = SignatureEnvelope.sortMultisigApprovals({
|
|
1826
1771
|
account,
|
|
1827
|
-
config,
|
|
1772
|
+
config: { version },
|
|
1828
1773
|
payload,
|
|
1829
1774
|
// Provide approvals in reverse of the canonical order.
|
|
1830
1775
|
signatures: [...ascending].reverse().map((owner) => owner.signature),
|
|
@@ -1837,7 +1782,7 @@ describe('sortMultisigApprovals', () => {
|
|
|
1837
1782
|
expect(
|
|
1838
1783
|
SignatureEnvelope.sortMultisigApprovals({
|
|
1839
1784
|
account,
|
|
1840
|
-
config,
|
|
1785
|
+
config: { version },
|
|
1841
1786
|
payload,
|
|
1842
1787
|
signatures,
|
|
1843
1788
|
}),
|
|
@@ -1847,7 +1792,7 @@ describe('sortMultisigApprovals', () => {
|
|
|
1847
1792
|
test('behavior: recovered order matches the config owner order', () => {
|
|
1848
1793
|
const ordered = SignatureEnvelope.sortMultisigApprovals({
|
|
1849
1794
|
account,
|
|
1850
|
-
config,
|
|
1795
|
+
config: { version },
|
|
1851
1796
|
payload,
|
|
1852
1797
|
signatures: owners.map((owner) => owner.signature),
|
|
1853
1798
|
})
|
|
@@ -2471,72 +2416,6 @@ describe('fromRpc', () => {
|
|
|
2471
2416
|
expect(envelope.keyId).toBe('0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c')
|
|
2472
2417
|
})
|
|
2473
2418
|
})
|
|
2474
|
-
|
|
2475
|
-
describe('multisig', () => {
|
|
2476
|
-
test('behavior: converts an untagged serialized multisig signature', () => {
|
|
2477
|
-
expect(
|
|
2478
|
-
SignatureEnvelope.fromRpc(signature_multisig_rpc),
|
|
2479
|
-
).toMatchInlineSnapshot(`
|
|
2480
|
-
{
|
|
2481
|
-
"account": "0xc4a590afa7337e5cd5eb3aa60cacf91c5400044b",
|
|
2482
|
-
"config": {
|
|
2483
|
-
"owners": [
|
|
2484
|
-
{
|
|
2485
|
-
"owner": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
|
|
2486
|
-
"weight": 1,
|
|
2487
|
-
},
|
|
2488
|
-
],
|
|
2489
|
-
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
2490
|
-
"threshold": 1,
|
|
2491
|
-
"version": 0n,
|
|
2492
|
-
},
|
|
2493
|
-
"signatures": [
|
|
2494
|
-
{
|
|
2495
|
-
"signature": {
|
|
2496
|
-
"r": 60871800714128149016591846789173983752390955456021923556141688418770269698437n,
|
|
2497
|
-
"s": 24367763726302312398084372528434045669788111037623467391175388828437596076276n,
|
|
2498
|
-
"yParity": 0,
|
|
2499
|
-
},
|
|
2500
|
-
"type": "secp256k1",
|
|
2501
|
-
},
|
|
2502
|
-
],
|
|
2503
|
-
"type": "multisig",
|
|
2504
|
-
}
|
|
2505
|
-
`)
|
|
2506
|
-
})
|
|
2507
|
-
|
|
2508
|
-
test('error: rejects a payload with the transaction type byte', () => {
|
|
2509
|
-
expect(() =>
|
|
2510
|
-
SignatureEnvelope.fromRpc(`0x05${signature_multisig_rpc.slice(2)}`),
|
|
2511
|
-
).toThrowErrorMatchingInlineSnapshot(`
|
|
2512
|
-
[SignatureEnvelope.InvalidSerializedError: Unable to deserialize signature envelope: invalid multisig wire shape: expected exactly three fields
|
|
2513
|
-
|
|
2514
|
-
Serialized: 0x0505f89794c4a590afa7337e5cd5eb3aa60cacf91c5400044bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b]
|
|
2515
|
-
`)
|
|
2516
|
-
})
|
|
2517
|
-
|
|
2518
|
-
test('error: rejects trailing bytes', () => {
|
|
2519
|
-
expect(() =>
|
|
2520
|
-
SignatureEnvelope.fromRpc(`${signature_multisig_rpc}00`),
|
|
2521
|
-
).toThrowErrorMatchingInlineSnapshot(`
|
|
2522
|
-
[SignatureEnvelope.InvalidSerializedError: Unable to deserialize signature envelope: invalid multisig RPC signature
|
|
2523
|
-
|
|
2524
|
-
Serialized: 0xf89794c4a590afa7337e5cd5eb3aa60cacf91c5400044bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b00]
|
|
2525
|
-
`)
|
|
2526
|
-
})
|
|
2527
|
-
|
|
2528
|
-
test('error: rejects a structured multisig signature', () => {
|
|
2529
|
-
expect(() =>
|
|
2530
|
-
SignatureEnvelope.fromRpc({
|
|
2531
|
-
account: '0xc4a590afa7337e5cd5eb3aa60cacf91c5400044b',
|
|
2532
|
-
config: {},
|
|
2533
|
-
signatures: [],
|
|
2534
|
-
} as never),
|
|
2535
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
2536
|
-
`[SignatureEnvelope.CoercionError: Unable to coerce value (\`{"account":"0xc4a590afa7337e5cd5eb3aa60cacf91c5400044b","config":{},"signatures":[]}\`) to a valid signature envelope.]`,
|
|
2537
|
-
)
|
|
2538
|
-
})
|
|
2539
|
-
})
|
|
2540
2419
|
})
|
|
2541
2420
|
|
|
2542
2421
|
describe('toRpc', () => {
|
|
@@ -2634,10 +2513,11 @@ describe('toRpc', () => {
|
|
|
2634
2513
|
const rpc = SignatureEnvelope.toRpc(
|
|
2635
2514
|
signature_keychain_p256,
|
|
2636
2515
|
) as SignatureEnvelope.KeychainRpc
|
|
2637
|
-
if (typeof rpc.signature === 'string') throw new Error('unreachable')
|
|
2638
2516
|
|
|
2639
2517
|
expect(rpc.type).toBe('keychain')
|
|
2640
2518
|
expect(rpc.userAddress).toBe(signature_keychain_p256.userAddress)
|
|
2519
|
+
if (typeof rpc.signature === 'string')
|
|
2520
|
+
throw new Error('Expected structured primitive signature')
|
|
2641
2521
|
expect(rpc.signature.type).toBe('p256')
|
|
2642
2522
|
expect(typeof rpc.signature.pubKeyX).toBe('string')
|
|
2643
2523
|
expect(typeof rpc.signature.pubKeyY).toBe('string')
|
|
@@ -2647,10 +2527,11 @@ describe('toRpc', () => {
|
|
|
2647
2527
|
const rpc = SignatureEnvelope.toRpc(
|
|
2648
2528
|
signature_keychain_webauthn,
|
|
2649
2529
|
) as SignatureEnvelope.KeychainRpc
|
|
2650
|
-
if (typeof rpc.signature === 'string') throw new Error('unreachable')
|
|
2651
2530
|
|
|
2652
2531
|
expect(rpc.type).toBe('keychain')
|
|
2653
2532
|
expect(rpc.userAddress).toBe(signature_keychain_webauthn.userAddress)
|
|
2533
|
+
if (typeof rpc.signature === 'string')
|
|
2534
|
+
throw new Error('Expected structured primitive signature')
|
|
2654
2535
|
expect(rpc.signature.type).toBe('webAuthn')
|
|
2655
2536
|
expect(typeof rpc.signature.pubKeyX).toBe('string')
|
|
2656
2537
|
expect(typeof rpc.signature.webauthnData).toBe('string')
|
|
@@ -2691,18 +2572,6 @@ describe('toRpc', () => {
|
|
|
2691
2572
|
expect(rpc.keyId).toBeUndefined()
|
|
2692
2573
|
})
|
|
2693
2574
|
})
|
|
2694
|
-
|
|
2695
|
-
describe('multisig', () => {
|
|
2696
|
-
test('behavior: removes the transaction type byte', () => {
|
|
2697
|
-
const envelope = SignatureEnvelope.deserialize(
|
|
2698
|
-
`0x05${signature_multisig_rpc.slice(2)}`,
|
|
2699
|
-
)
|
|
2700
|
-
|
|
2701
|
-
expect(SignatureEnvelope.toRpc(envelope)).toMatchInlineSnapshot(
|
|
2702
|
-
`"${signature_multisig_rpc}"`,
|
|
2703
|
-
)
|
|
2704
|
-
})
|
|
2705
|
-
})
|
|
2706
2575
|
})
|
|
2707
2576
|
|
|
2708
2577
|
describe('roundtrip: toRpc <-> fromRpc', () => {
|
|
@@ -2936,12 +2805,278 @@ describe('CoercionError', () => {
|
|
|
2936
2805
|
})
|
|
2937
2806
|
|
|
2938
2807
|
describe('multisig', () => {
|
|
2808
|
+
const account = '0x2222222222222222222222222222222222222222'
|
|
2809
|
+
const config = MultisigConfig.from({
|
|
2810
|
+
threshold: 1,
|
|
2811
|
+
owners: [
|
|
2812
|
+
{ owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
2813
|
+
],
|
|
2814
|
+
})
|
|
2815
|
+
const primitive = SignatureEnvelope.from(signature_secp256k1)
|
|
2816
|
+
const envelope = SignatureEnvelope.from({
|
|
2817
|
+
account,
|
|
2818
|
+
config,
|
|
2819
|
+
signatures: [primitive],
|
|
2820
|
+
})
|
|
2821
|
+
|
|
2822
|
+
test('serialize carries account, config, and primitive approvals', () => {
|
|
2823
|
+
const serialized = SignatureEnvelope.serialize(envelope)
|
|
2824
|
+
expect(Hex.slice(serialized, 0, 1)).toMatchInlineSnapshot('"0x05"')
|
|
2825
|
+
expect(Rlp.toHex(Hex.slice(serialized, 1))).toEqual([
|
|
2826
|
+
account,
|
|
2827
|
+
[
|
|
2828
|
+
MultisigConfig.zeroSalt,
|
|
2829
|
+
'0x',
|
|
2830
|
+
'0x01',
|
|
2831
|
+
[['0x1111111111111111111111111111111111111111', '0x01']],
|
|
2832
|
+
],
|
|
2833
|
+
[SignatureEnvelope.serialize(primitive)],
|
|
2834
|
+
])
|
|
2835
|
+
expect(SignatureEnvelope.deserialize(serialized)).toEqual(envelope)
|
|
2836
|
+
})
|
|
2837
|
+
|
|
2838
|
+
test('RPC contains RLP bytes without the type byte', () => {
|
|
2839
|
+
const rpc = SignatureEnvelope.toRpc(envelope)
|
|
2840
|
+
expect(rpc).toBe(Hex.slice(SignatureEnvelope.serialize(envelope), 1))
|
|
2841
|
+
expect(SignatureEnvelope.fromRpc(rpc)).toEqual(envelope)
|
|
2842
|
+
})
|
|
2843
|
+
|
|
2844
|
+
test('round-trips updated configs and maximum versions', () => {
|
|
2845
|
+
for (const version of [1n, 0xffffffffffffffffn]) {
|
|
2846
|
+
const updated = SignatureEnvelope.from({
|
|
2847
|
+
...envelope,
|
|
2848
|
+
config: { ...config, config: { version } },
|
|
2849
|
+
})
|
|
2850
|
+
expect(
|
|
2851
|
+
SignatureEnvelope.deserialize(SignatureEnvelope.serialize(updated)),
|
|
2852
|
+
).toEqual(updated)
|
|
2853
|
+
expect(
|
|
2854
|
+
SignatureEnvelope.fromRpc(SignatureEnvelope.toRpc(updated)),
|
|
2855
|
+
).toEqual(updated)
|
|
2856
|
+
}
|
|
2857
|
+
})
|
|
2858
|
+
|
|
2859
|
+
test('extractAddress returns the named account', () => {
|
|
2860
|
+
expect(
|
|
2861
|
+
SignatureEnvelope.extractAddress({
|
|
2862
|
+
signature: envelope,
|
|
2863
|
+
payload: '0x00',
|
|
2864
|
+
}),
|
|
2865
|
+
).toMatchInlineSnapshot('"0x2222222222222222222222222222222222222222"')
|
|
2866
|
+
})
|
|
2867
|
+
|
|
2868
|
+
test('rejects old wire and RPC shapes', () => {
|
|
2869
|
+
for (const first of [account, MultisigConfig.toTuple(config)] as const)
|
|
2870
|
+
expect(() =>
|
|
2871
|
+
SignatureEnvelope.deserialize(
|
|
2872
|
+
Hex.concat(
|
|
2873
|
+
'0x05',
|
|
2874
|
+
Rlp.fromHex([first, [SignatureEnvelope.serialize(primitive)]]),
|
|
2875
|
+
),
|
|
2876
|
+
),
|
|
2877
|
+
).toThrowError(SignatureEnvelope.InvalidSerializedError)
|
|
2878
|
+
expect(() =>
|
|
2879
|
+
SignatureEnvelope.fromRpc({ account, signatures: [] } as never),
|
|
2880
|
+
).toThrowError(SignatureEnvelope.CoercionError)
|
|
2881
|
+
})
|
|
2882
|
+
|
|
2883
|
+
test('rejects malformed configs', () => {
|
|
2884
|
+
const valid = MultisigConfig.toTuple(config)
|
|
2885
|
+
for (const tuple of [
|
|
2886
|
+
['0x00', ...valid.slice(1)],
|
|
2887
|
+
[valid[0], '0x00', valid[2], valid[3]],
|
|
2888
|
+
[valid[0], '0x010000000000000000', valid[2], valid[3]],
|
|
2889
|
+
[valid[0], '0x', '0x0001', valid[3]],
|
|
2890
|
+
[valid[0], '0x', '0x01', [[config.owners[0]!.owner, '0x0001']]],
|
|
2891
|
+
[valid[0], '0x', '0x01', []],
|
|
2892
|
+
])
|
|
2893
|
+
expect(() =>
|
|
2894
|
+
SignatureEnvelope.deserialize(
|
|
2895
|
+
Hex.concat(
|
|
2896
|
+
'0x05',
|
|
2897
|
+
Rlp.fromHex([
|
|
2898
|
+
account,
|
|
2899
|
+
tuple as never,
|
|
2900
|
+
[SignatureEnvelope.serialize(primitive)],
|
|
2901
|
+
]),
|
|
2902
|
+
),
|
|
2903
|
+
),
|
|
2904
|
+
).toThrowError(MultisigConfig.InvalidConfigError)
|
|
2905
|
+
})
|
|
2906
|
+
|
|
2907
|
+
test('rejects invalid accounts, missing configs, and invalid approval counts', () => {
|
|
2908
|
+
for (const value of [
|
|
2909
|
+
{ ...envelope, account: '0x11' },
|
|
2910
|
+
{ ...envelope, account: '0x0000000000000000000000000000000000000000' },
|
|
2911
|
+
{ ...envelope, config: undefined },
|
|
2912
|
+
{ ...envelope, signatures: [] },
|
|
2913
|
+
{ ...envelope, signatures: Array(9).fill(primitive) },
|
|
2914
|
+
])
|
|
2915
|
+
expect(() => SignatureEnvelope.assert(value as never)).toThrowError()
|
|
2916
|
+
expect(() =>
|
|
2917
|
+
SignatureEnvelope.assert({
|
|
2918
|
+
...envelope,
|
|
2919
|
+
signatures: Array(8).fill(primitive),
|
|
2920
|
+
}),
|
|
2921
|
+
).not.toThrow()
|
|
2922
|
+
})
|
|
2923
|
+
|
|
2924
|
+
test('rejects self-ownership in the initial configuration', () => {
|
|
2925
|
+
const self = {
|
|
2926
|
+
...envelope,
|
|
2927
|
+
config: { ...config, owners: [{ owner: account, weight: 1 }] },
|
|
2928
|
+
} as const
|
|
2929
|
+
expect(() => SignatureEnvelope.assert(self)).toThrowError(
|
|
2930
|
+
SignatureEnvelope.InvalidMultisigApprovalError,
|
|
2931
|
+
)
|
|
2932
|
+
expect(() =>
|
|
2933
|
+
SignatureEnvelope.assert({
|
|
2934
|
+
...self,
|
|
2935
|
+
config: { ...self.config, version: 1n },
|
|
2936
|
+
}),
|
|
2937
|
+
).not.toThrow()
|
|
2938
|
+
})
|
|
2939
|
+
|
|
2940
|
+
test('rejects recursive owner approvals before decoding them', () => {
|
|
2941
|
+
for (const signature of [envelope, signature_keychain_secp256k1]) {
|
|
2942
|
+
expect(() =>
|
|
2943
|
+
SignatureEnvelope.serialize({
|
|
2944
|
+
...envelope,
|
|
2945
|
+
signatures: [signature],
|
|
2946
|
+
} as never),
|
|
2947
|
+
).toThrowError(SignatureEnvelope.InvalidMultisigApprovalError)
|
|
2948
|
+
const wire = Hex.concat(
|
|
2949
|
+
'0x05',
|
|
2950
|
+
Rlp.fromHex([
|
|
2951
|
+
account,
|
|
2952
|
+
MultisigConfig.toTuple(config),
|
|
2953
|
+
[SignatureEnvelope.serialize(signature)],
|
|
2954
|
+
]),
|
|
2955
|
+
)
|
|
2956
|
+
expect(() => SignatureEnvelope.deserialize(wire)).toThrowError(
|
|
2957
|
+
SignatureEnvelope.InvalidSerializedError,
|
|
2958
|
+
)
|
|
2959
|
+
}
|
|
2960
|
+
})
|
|
2961
|
+
|
|
2962
|
+
test('rejects oversized owner approvals', () => {
|
|
2963
|
+
const oversized = {
|
|
2964
|
+
...signature_webauthn,
|
|
2965
|
+
metadata: {
|
|
2966
|
+
...signature_webauthn.metadata,
|
|
2967
|
+
clientDataJSON: 'x'.repeat(2048),
|
|
2968
|
+
},
|
|
2969
|
+
}
|
|
2970
|
+
expect(() =>
|
|
2971
|
+
SignatureEnvelope.assert({ ...envelope, signatures: [oversized] }),
|
|
2972
|
+
).toThrowError(SignatureEnvelope.InvalidMultisigApprovalError)
|
|
2973
|
+
})
|
|
2974
|
+
test('requires keychain V2 for multisig delegates', () => {
|
|
2975
|
+
const keychain = SignatureEnvelope.from({
|
|
2976
|
+
userAddress: account,
|
|
2977
|
+
version: 'v2',
|
|
2978
|
+
inner: envelope,
|
|
2979
|
+
})
|
|
2980
|
+
const serialized = SignatureEnvelope.serialize(keychain)
|
|
2981
|
+
expect(SignatureEnvelope.deserialize(serialized)).toEqual(keychain)
|
|
2982
|
+
expect(() =>
|
|
2983
|
+
SignatureEnvelope.serialize({ ...keychain, version: 'v1' }),
|
|
2984
|
+
).toThrowError(SignatureEnvelope.InvalidMultisigApprovalError)
|
|
2985
|
+
expect(() =>
|
|
2986
|
+
SignatureEnvelope.deserialize(
|
|
2987
|
+
Hex.concat('0x03', Hex.slice(serialized, 1)),
|
|
2988
|
+
),
|
|
2989
|
+
).toThrowError(SignatureEnvelope.InvalidMultisigApprovalError)
|
|
2990
|
+
expect(() =>
|
|
2991
|
+
SignatureEnvelope.fromRpc({
|
|
2992
|
+
type: 'keychain',
|
|
2993
|
+
userAddress: account,
|
|
2994
|
+
version: 'v1',
|
|
2995
|
+
signature: SignatureEnvelope.toRpc(envelope),
|
|
2996
|
+
}),
|
|
2997
|
+
).toThrowError(SignatureEnvelope.InvalidMultisigApprovalError)
|
|
2998
|
+
})
|
|
2999
|
+
test('rejects V1 wrappers around recursively nested multisig delegates', () => {
|
|
3000
|
+
let inner: SignatureEnvelope.SignatureEnvelope = envelope
|
|
3001
|
+
for (let depth = 0; depth < 3; depth++) {
|
|
3002
|
+
inner = { inner, type: 'keychain', userAddress: account, version: 'v2' }
|
|
3003
|
+
const valid: SignatureEnvelope.Keychain = {
|
|
3004
|
+
inner,
|
|
3005
|
+
type: 'keychain',
|
|
3006
|
+
userAddress: account,
|
|
3007
|
+
version: 'v2',
|
|
3008
|
+
}
|
|
3009
|
+
const invalid = { ...valid, version: 'v1' } as const
|
|
3010
|
+
expect(SignatureEnvelope.validate(valid)).toBe(true)
|
|
3011
|
+
expect(SignatureEnvelope.validate(invalid)).toBe(false)
|
|
3012
|
+
expect(() => SignatureEnvelope.serialize(invalid)).toThrowError(
|
|
3013
|
+
SignatureEnvelope.InvalidMultisigApprovalError,
|
|
3014
|
+
)
|
|
3015
|
+
expect(() => SignatureEnvelope.toRpc(invalid)).toThrowError(
|
|
3016
|
+
SignatureEnvelope.InvalidMultisigApprovalError,
|
|
3017
|
+
)
|
|
3018
|
+
expect(() =>
|
|
3019
|
+
SignatureEnvelope.fromRpc({
|
|
3020
|
+
type: 'keychain',
|
|
3021
|
+
userAddress: account,
|
|
3022
|
+
version: 'v1',
|
|
3023
|
+
signature: SignatureEnvelope.toRpc(inner),
|
|
3024
|
+
}),
|
|
3025
|
+
).toThrowError(SignatureEnvelope.InvalidMultisigApprovalError)
|
|
3026
|
+
expect(() =>
|
|
3027
|
+
SignatureEnvelope.deserialize(
|
|
3028
|
+
Hex.concat('0x03', account, SignatureEnvelope.serialize(inner)),
|
|
3029
|
+
),
|
|
3030
|
+
).toThrowError(SignatureEnvelope.InvalidMultisigApprovalError)
|
|
3031
|
+
expect(() =>
|
|
3032
|
+
SignatureEnvelope.serialize({ ...valid, inner: invalid }),
|
|
3033
|
+
).toThrowError(SignatureEnvelope.InvalidMultisigApprovalError)
|
|
3034
|
+
}
|
|
3035
|
+
expect(
|
|
3036
|
+
SignatureEnvelope.validate({
|
|
3037
|
+
inner: {
|
|
3038
|
+
inner: primitive,
|
|
3039
|
+
type: 'keychain',
|
|
3040
|
+
userAddress: account,
|
|
3041
|
+
version: 'v2',
|
|
3042
|
+
},
|
|
3043
|
+
type: 'keychain',
|
|
3044
|
+
userAddress: account,
|
|
3045
|
+
version: 'v1',
|
|
3046
|
+
}),
|
|
3047
|
+
).toBe(true)
|
|
3048
|
+
})
|
|
3049
|
+
test('RPC rejects 64-byte values that resemble primitive signatures', () => {
|
|
3050
|
+
expect(() =>
|
|
3051
|
+
SignatureEnvelope.fromRpc(
|
|
3052
|
+
Hex.concat(Hex.fromNumber(1, { size: 63 }), '0x1b'),
|
|
3053
|
+
),
|
|
3054
|
+
).toThrowError()
|
|
3055
|
+
})
|
|
3056
|
+
test('round-trips mixed primitive approvals', () => {
|
|
3057
|
+
const signature = { r: p256Signature.r, s: p256Signature.s }
|
|
3058
|
+
const mixed = SignatureEnvelope.from({
|
|
3059
|
+
...envelope,
|
|
3060
|
+
signatures: [
|
|
3061
|
+
primitive,
|
|
3062
|
+
{ ...signature_p256, signature },
|
|
3063
|
+
{ ...signature_webauthn, signature },
|
|
3064
|
+
],
|
|
3065
|
+
})
|
|
3066
|
+
expect(
|
|
3067
|
+
SignatureEnvelope.deserialize(SignatureEnvelope.serialize(mixed)),
|
|
3068
|
+
).toEqual(mixed)
|
|
3069
|
+
expect(SignatureEnvelope.fromRpc(SignatureEnvelope.toRpc(mixed))).toEqual(
|
|
3070
|
+
mixed,
|
|
3071
|
+
)
|
|
3072
|
+
})
|
|
3073
|
+
})
|
|
3074
|
+
|
|
3075
|
+
describe('multisig vectors', () => {
|
|
2939
3076
|
const initial =
|
|
2940
3077
|
'0x05f89794c4a590afa7337e5cd5eb3aa60cacf91c5400044bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b' as const
|
|
2941
3078
|
const current =
|
|
2942
3079
|
'0x05f89794c4a590afa7337e5cd5eb3aa60cacf91c5400044bf83ba000000000000000000000000000000000000000000000000000000000000000000101d7d6942b5ad5c4795c026514f8317c7a215e218dccd6cf01f843b8414a0e5b5b4a90f08e6e8d676a73a22dc2cf022ffdcc9299512b5d9a6daf66e0504a395a2ef6f6c0f173910b875c45d01aa66d928e56ba6f49bbc8186f80268bf51b' as const
|
|
2943
|
-
const nested =
|
|
2944
|
-
'0x05f8f0945f0287385ec182f906c238bec5f87692d48b23f2f83ba033333333333333333333333333333333333333333333333333333333333333338001d7d69470845f2897b639686cb383799d605c52315d785501f89cb89a05f8979470845f2897b639686cb383799d605c52315d7855f83ba022222222222222222222222222222222222222222222222222222222222222228001d7d6946813eb9362372eef6200f3b1dbc3f819671cba6901f843b841032aa6f3ea7b0b7069720d0f3891983c493d149326c5c957d864ed7371b8475e3e95325079b24491f4b6d68920e4b3bacd7c6094df6ed88b8674864ab559c8fb1b' as const
|
|
2945
3080
|
|
|
2946
3081
|
test('example: decodes and re-encodes the frozen initial signature', () => {
|
|
2947
3082
|
const envelope = SignatureEnvelope.deserialize(initial)
|
|
@@ -3009,56 +3144,6 @@ describe('multisig', () => {
|
|
|
3009
3144
|
expect(SignatureEnvelope.serialize(envelope)).toBe(current)
|
|
3010
3145
|
})
|
|
3011
3146
|
|
|
3012
|
-
test('example: decodes and re-encodes the frozen nested signature', () => {
|
|
3013
|
-
const envelope = SignatureEnvelope.deserialize(nested)
|
|
3014
|
-
|
|
3015
|
-
expect(envelope).toMatchInlineSnapshot(`
|
|
3016
|
-
{
|
|
3017
|
-
"account": "0x5f0287385ec182f906c238bec5f87692d48b23f2",
|
|
3018
|
-
"config": {
|
|
3019
|
-
"owners": [
|
|
3020
|
-
{
|
|
3021
|
-
"owner": "0x70845f2897b639686cb383799d605c52315d7855",
|
|
3022
|
-
"weight": 1,
|
|
3023
|
-
},
|
|
3024
|
-
],
|
|
3025
|
-
"salt": "0x3333333333333333333333333333333333333333333333333333333333333333",
|
|
3026
|
-
"threshold": 1,
|
|
3027
|
-
"version": 0n,
|
|
3028
|
-
},
|
|
3029
|
-
"signatures": [
|
|
3030
|
-
{
|
|
3031
|
-
"account": "0x70845f2897b639686cb383799d605c52315d7855",
|
|
3032
|
-
"config": {
|
|
3033
|
-
"owners": [
|
|
3034
|
-
{
|
|
3035
|
-
"owner": "0x6813eb9362372eef6200f3b1dbc3f819671cba69",
|
|
3036
|
-
"weight": 1,
|
|
3037
|
-
},
|
|
3038
|
-
],
|
|
3039
|
-
"salt": "0x2222222222222222222222222222222222222222222222222222222222222222",
|
|
3040
|
-
"threshold": 1,
|
|
3041
|
-
"version": 0n,
|
|
3042
|
-
},
|
|
3043
|
-
"signatures": [
|
|
3044
|
-
{
|
|
3045
|
-
"signature": {
|
|
3046
|
-
"r": 1432298388324792555676061659976215111517531547814803085200940835913455912798n,
|
|
3047
|
-
"s": 28307004081743690677157914067925034813425317008577473150162358055484664695035n,
|
|
3048
|
-
"yParity": 0,
|
|
3049
|
-
},
|
|
3050
|
-
"type": "secp256k1",
|
|
3051
|
-
},
|
|
3052
|
-
],
|
|
3053
|
-
"type": "multisig",
|
|
3054
|
-
},
|
|
3055
|
-
],
|
|
3056
|
-
"type": "multisig",
|
|
3057
|
-
}
|
|
3058
|
-
`)
|
|
3059
|
-
expect(SignatureEnvelope.serialize(envelope)).toBe(nested)
|
|
3060
|
-
})
|
|
3061
|
-
|
|
3062
3147
|
test('behavior: preserves exact 65-byte secp256k1 precedence', () => {
|
|
3063
3148
|
const serialized = ('0x05' +
|
|
3064
3149
|
'00'.repeat(31) +
|
|
@@ -3069,356 +3154,4 @@ describe('multisig', () => {
|
|
|
3069
3154
|
expect(Hex.size(serialized)).toBe(65)
|
|
3070
3155
|
expect(SignatureEnvelope.deserialize(serialized).type).toBe('secp256k1')
|
|
3071
3156
|
})
|
|
3072
|
-
|
|
3073
|
-
test('behavior: serializes account, config, and approvals in wire order', () => {
|
|
3074
|
-
const envelope = SignatureEnvelope.deserialize(initial)
|
|
3075
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3076
|
-
const serialized = SignatureEnvelope.serialize(envelope)
|
|
3077
|
-
const decoded = Rlp.toHex(Hex.slice(serialized, 1))
|
|
3078
|
-
|
|
3079
|
-
expect(decoded).toMatchInlineSnapshot(`
|
|
3080
|
-
[
|
|
3081
|
-
"0xc4a590afa7337e5cd5eb3aa60cacf91c5400044b",
|
|
3082
|
-
[
|
|
3083
|
-
"0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
3084
|
-
"0x",
|
|
3085
|
-
"0x01",
|
|
3086
|
-
[
|
|
3087
|
-
[
|
|
3088
|
-
"0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
|
|
3089
|
-
"0x01",
|
|
3090
|
-
],
|
|
3091
|
-
],
|
|
3092
|
-
],
|
|
3093
|
-
[
|
|
3094
|
-
"0x869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b",
|
|
3095
|
-
],
|
|
3096
|
-
]
|
|
3097
|
-
`)
|
|
3098
|
-
})
|
|
3099
|
-
|
|
3100
|
-
test('behavior: round trips initial and current RPC signatures', () => {
|
|
3101
|
-
const initialEnvelope = SignatureEnvelope.deserialize(initial)
|
|
3102
|
-
const currentEnvelope = SignatureEnvelope.deserialize(current)
|
|
3103
|
-
const rpc = {
|
|
3104
|
-
current: SignatureEnvelope.toRpc(currentEnvelope),
|
|
3105
|
-
initial: SignatureEnvelope.toRpc(initialEnvelope),
|
|
3106
|
-
}
|
|
3107
|
-
|
|
3108
|
-
expect(rpc).toMatchInlineSnapshot(`
|
|
3109
|
-
{
|
|
3110
|
-
"current": "0xf89794c4a590afa7337e5cd5eb3aa60cacf91c5400044bf83ba000000000000000000000000000000000000000000000000000000000000000000101d7d6942b5ad5c4795c026514f8317c7a215e218dccd6cf01f843b8414a0e5b5b4a90f08e6e8d676a73a22dc2cf022ffdcc9299512b5d9a6daf66e0504a395a2ef6f6c0f173910b875c45d01aa66d928e56ba6f49bbc8186f80268bf51b",
|
|
3111
|
-
"initial": "0xf89794c4a590afa7337e5cd5eb3aa60cacf91c5400044bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b",
|
|
3112
|
-
}
|
|
3113
|
-
`)
|
|
3114
|
-
expect(SignatureEnvelope.fromRpc(rpc.initial)).toStrictEqual(
|
|
3115
|
-
initialEnvelope,
|
|
3116
|
-
)
|
|
3117
|
-
expect(SignatureEnvelope.fromRpc(rpc.current)).toStrictEqual(
|
|
3118
|
-
currentEnvelope,
|
|
3119
|
-
)
|
|
3120
|
-
})
|
|
3121
|
-
|
|
3122
|
-
test('behavior: derives the account only for an initial config', () => {
|
|
3123
|
-
const initialEnvelope = SignatureEnvelope.deserialize(initial)
|
|
3124
|
-
if (initialEnvelope.type !== 'multisig') throw new Error('unreachable')
|
|
3125
|
-
if (initialEnvelope.config.version !== 0n) throw new Error('unreachable')
|
|
3126
|
-
const derived = SignatureEnvelope.from({
|
|
3127
|
-
config: initialEnvelope.config as MultisigConfig.Config<0n>,
|
|
3128
|
-
signatures: initialEnvelope.signatures,
|
|
3129
|
-
})
|
|
3130
|
-
const currentEnvelope = SignatureEnvelope.deserialize(current)
|
|
3131
|
-
if (currentEnvelope.type !== 'multisig') throw new Error('unreachable')
|
|
3132
|
-
|
|
3133
|
-
expect(derived.account).toBe(initialEnvelope.account)
|
|
3134
|
-
expect(() =>
|
|
3135
|
-
SignatureEnvelope.from({
|
|
3136
|
-
config: currentEnvelope.config,
|
|
3137
|
-
signatures: currentEnvelope.signatures,
|
|
3138
|
-
} as never),
|
|
3139
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3140
|
-
`[MultisigConfig.InvalidConfigError: Invalid native multisig config: account address requires version zero.]`,
|
|
3141
|
-
)
|
|
3142
|
-
})
|
|
3143
|
-
|
|
3144
|
-
test('behavior: extracts the multisig account', () => {
|
|
3145
|
-
const envelope = SignatureEnvelope.deserialize(initial)
|
|
3146
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3147
|
-
|
|
3148
|
-
expect(
|
|
3149
|
-
SignatureEnvelope.extractAddress({
|
|
3150
|
-
payload: '0xdeadbeef',
|
|
3151
|
-
signature: envelope,
|
|
3152
|
-
}),
|
|
3153
|
-
).toBe(envelope.account)
|
|
3154
|
-
})
|
|
3155
|
-
|
|
3156
|
-
test('example: matches the frozen eight-approval boundary vector', () => {
|
|
3157
|
-
const config = MultisigConfig.from({
|
|
3158
|
-
owners: Array.from({ length: 8 }, (_, index) => ({
|
|
3159
|
-
owner:
|
|
3160
|
-
`0x${(index + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
|
|
3161
|
-
weight: 1,
|
|
3162
|
-
})),
|
|
3163
|
-
salt: `0x${'66'.repeat(32)}`,
|
|
3164
|
-
threshold: 8,
|
|
3165
|
-
})
|
|
3166
|
-
const approval = SignatureEnvelope.from({
|
|
3167
|
-
r: 0x840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565n,
|
|
3168
|
-
s: 0x25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1n,
|
|
3169
|
-
yParity: 0,
|
|
3170
|
-
})
|
|
3171
|
-
const envelope = SignatureEnvelope.from({
|
|
3172
|
-
config,
|
|
3173
|
-
signatures: Array.from(
|
|
3174
|
-
{ length: MultisigConfig.maxSignatures },
|
|
3175
|
-
() => approval,
|
|
3176
|
-
),
|
|
3177
|
-
})
|
|
3178
|
-
const serialized = SignatureEnvelope.serialize(envelope)
|
|
3179
|
-
|
|
3180
|
-
expect({
|
|
3181
|
-
hash: Hash.keccak256(serialized),
|
|
3182
|
-
length: Hex.size(serialized),
|
|
3183
|
-
signatureCount: envelope.signatures.length,
|
|
3184
|
-
}).toMatchInlineSnapshot(`
|
|
3185
|
-
{
|
|
3186
|
-
"hash": "0x507fa2d2bc6cf5e7753d4d7cb92fe3d199f64c6eaa9a0bd1d14bfb366028a379",
|
|
3187
|
-
"length": 787,
|
|
3188
|
-
"signatureCount": 8,
|
|
3189
|
-
}
|
|
3190
|
-
`)
|
|
3191
|
-
})
|
|
3192
|
-
|
|
3193
|
-
test('behavior: accepts initial and current nested signatures', () => {
|
|
3194
|
-
const initialEnvelope = SignatureEnvelope.deserialize(initial)
|
|
3195
|
-
const currentEnvelope = SignatureEnvelope.deserialize(current)
|
|
3196
|
-
if (
|
|
3197
|
-
initialEnvelope.type !== 'multisig' ||
|
|
3198
|
-
currentEnvelope.type !== 'multisig'
|
|
3199
|
-
)
|
|
3200
|
-
throw new Error('unreachable')
|
|
3201
|
-
|
|
3202
|
-
for (const signature of [initialEnvelope, currentEnvelope]) {
|
|
3203
|
-
const config = MultisigConfig.from({
|
|
3204
|
-
owners: [{ owner: signature.account, weight: 1 }],
|
|
3205
|
-
salt: ('0x' + 'ff'.repeat(32)) as Hex.Hex,
|
|
3206
|
-
threshold: 1,
|
|
3207
|
-
version: 1n,
|
|
3208
|
-
})
|
|
3209
|
-
const envelope = SignatureEnvelope.from({
|
|
3210
|
-
account: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
3211
|
-
config,
|
|
3212
|
-
signatures: [signature],
|
|
3213
|
-
})
|
|
3214
|
-
|
|
3215
|
-
expect(
|
|
3216
|
-
SignatureEnvelope.deserialize(SignatureEnvelope.serialize(envelope)),
|
|
3217
|
-
).toStrictEqual(envelope)
|
|
3218
|
-
}
|
|
3219
|
-
})
|
|
3220
|
-
|
|
3221
|
-
test('error: rejects an initial account mismatch', () => {
|
|
3222
|
-
const envelope = SignatureEnvelope.deserialize(initial)
|
|
3223
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3224
|
-
|
|
3225
|
-
expect(() =>
|
|
3226
|
-
SignatureEnvelope.assert({
|
|
3227
|
-
...envelope,
|
|
3228
|
-
account: '0x1111111111111111111111111111111111111111',
|
|
3229
|
-
}),
|
|
3230
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3231
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: initial multisig config does not derive account.]`,
|
|
3232
|
-
)
|
|
3233
|
-
})
|
|
3234
|
-
|
|
3235
|
-
test('error: rejects a self-owned current signature', () => {
|
|
3236
|
-
const envelope = SignatureEnvelope.deserialize(current)
|
|
3237
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3238
|
-
const account = envelope.config.owners[0]!.owner
|
|
3239
|
-
|
|
3240
|
-
expect(() =>
|
|
3241
|
-
SignatureEnvelope.assert({ ...envelope, account }),
|
|
3242
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3243
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig account cannot be an owner.]`,
|
|
3244
|
-
)
|
|
3245
|
-
})
|
|
3246
|
-
|
|
3247
|
-
test('error: rejects empty and excess approvals', () => {
|
|
3248
|
-
const envelope = SignatureEnvelope.deserialize(current)
|
|
3249
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3250
|
-
|
|
3251
|
-
expect(() =>
|
|
3252
|
-
SignatureEnvelope.assert({ ...envelope, signatures: [] }),
|
|
3253
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3254
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig signatures cannot be empty.]`,
|
|
3255
|
-
)
|
|
3256
|
-
expect(() =>
|
|
3257
|
-
SignatureEnvelope.assert({
|
|
3258
|
-
...envelope,
|
|
3259
|
-
signatures: Array.from(
|
|
3260
|
-
{ length: MultisigConfig.maxSignatures + 1 },
|
|
3261
|
-
() => envelope.signatures[0]!,
|
|
3262
|
-
),
|
|
3263
|
-
}),
|
|
3264
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3265
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig signatures exceed 8.]`,
|
|
3266
|
-
)
|
|
3267
|
-
})
|
|
3268
|
-
|
|
3269
|
-
test('error: rejects keychain owner approvals', () => {
|
|
3270
|
-
const envelope = SignatureEnvelope.deserialize(current)
|
|
3271
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3272
|
-
|
|
3273
|
-
expect(() =>
|
|
3274
|
-
SignatureEnvelope.assert({
|
|
3275
|
-
...envelope,
|
|
3276
|
-
signatures: [signature_keychain_secp256k1],
|
|
3277
|
-
} as never),
|
|
3278
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3279
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: keychain owner approvals are not allowed.]`,
|
|
3280
|
-
)
|
|
3281
|
-
})
|
|
3282
|
-
|
|
3283
|
-
test('error: rejects oversized primitive approvals', () => {
|
|
3284
|
-
const envelope = SignatureEnvelope.deserialize(current)
|
|
3285
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3286
|
-
const oversized = {
|
|
3287
|
-
...signature_webauthn,
|
|
3288
|
-
metadata: {
|
|
3289
|
-
...signature_webauthn.metadata,
|
|
3290
|
-
clientDataJSON: JSON.stringify({ value: 'x'.repeat(2048) }),
|
|
3291
|
-
},
|
|
3292
|
-
}
|
|
3293
|
-
|
|
3294
|
-
expect(() =>
|
|
3295
|
-
SignatureEnvelope.assert({
|
|
3296
|
-
...envelope,
|
|
3297
|
-
signatures: [oversized],
|
|
3298
|
-
}),
|
|
3299
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3300
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig owner signature exceeds 2049 bytes.]`,
|
|
3301
|
-
)
|
|
3302
|
-
})
|
|
3303
|
-
|
|
3304
|
-
test('error: rejects nesting deeper than two nodes', () => {
|
|
3305
|
-
const envelope = SignatureEnvelope.deserialize(initial)
|
|
3306
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3307
|
-
const depth2 = SignatureEnvelope.from({
|
|
3308
|
-
...envelope,
|
|
3309
|
-
signatures: [envelope],
|
|
3310
|
-
})
|
|
3311
|
-
|
|
3312
|
-
expect(() =>
|
|
3313
|
-
SignatureEnvelope.assert({
|
|
3314
|
-
...envelope,
|
|
3315
|
-
signatures: [depth2],
|
|
3316
|
-
}),
|
|
3317
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3318
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig nesting depth exceeds 2.]`,
|
|
3319
|
-
)
|
|
3320
|
-
})
|
|
3321
|
-
|
|
3322
|
-
test.each([
|
|
3323
|
-
{
|
|
3324
|
-
name: 'two fields',
|
|
3325
|
-
value: Rlp.fromHex([
|
|
3326
|
-
'0xc4a590afa7337e5cd5eb3aa60cacf91c5400044b',
|
|
3327
|
-
[Hex.slice(initial, -65)],
|
|
3328
|
-
]),
|
|
3329
|
-
},
|
|
3330
|
-
{
|
|
3331
|
-
name: 'trailing field',
|
|
3332
|
-
value: Rlp.fromHex([
|
|
3333
|
-
'0xc4a590afa7337e5cd5eb3aa60cacf91c5400044b',
|
|
3334
|
-
MultisigConfig.toTuple(
|
|
3335
|
-
MultisigConfig.from({
|
|
3336
|
-
owners: [
|
|
3337
|
-
{
|
|
3338
|
-
owner: '0x7e5f4552091a69125d5dfcb7b8c2659029395bdf',
|
|
3339
|
-
weight: 1,
|
|
3340
|
-
},
|
|
3341
|
-
],
|
|
3342
|
-
threshold: 1,
|
|
3343
|
-
}),
|
|
3344
|
-
),
|
|
3345
|
-
[Hex.slice(initial, -65)],
|
|
3346
|
-
'0x',
|
|
3347
|
-
]),
|
|
3348
|
-
},
|
|
3349
|
-
])('error: rejects malformed $name wire shape', ({ value }) => {
|
|
3350
|
-
expect(() =>
|
|
3351
|
-
SignatureEnvelope.deserialize(Hex.concat('0x05', value)),
|
|
3352
|
-
).toThrowError(SignatureEnvelope.InvalidSerializedError)
|
|
3353
|
-
})
|
|
3354
|
-
|
|
3355
|
-
test.each([
|
|
3356
|
-
{
|
|
3357
|
-
field: 'salt',
|
|
3358
|
-
value: [
|
|
3359
|
-
('0x' + '00'.repeat(31)) as Hex.Hex,
|
|
3360
|
-
'0x',
|
|
3361
|
-
'0x01',
|
|
3362
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3363
|
-
],
|
|
3364
|
-
},
|
|
3365
|
-
{
|
|
3366
|
-
field: 'oversized version',
|
|
3367
|
-
value: [
|
|
3368
|
-
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3369
|
-
('0x' + '01'.repeat(9)) as Hex.Hex,
|
|
3370
|
-
'0x01',
|
|
3371
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3372
|
-
],
|
|
3373
|
-
},
|
|
3374
|
-
{
|
|
3375
|
-
field: 'zero version',
|
|
3376
|
-
value: [
|
|
3377
|
-
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3378
|
-
'0x00',
|
|
3379
|
-
'0x01',
|
|
3380
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3381
|
-
],
|
|
3382
|
-
},
|
|
3383
|
-
{
|
|
3384
|
-
field: 'zero-prefixed version',
|
|
3385
|
-
value: [
|
|
3386
|
-
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3387
|
-
'0x0001',
|
|
3388
|
-
'0x01',
|
|
3389
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3390
|
-
],
|
|
3391
|
-
},
|
|
3392
|
-
{
|
|
3393
|
-
field: 'threshold',
|
|
3394
|
-
value: [
|
|
3395
|
-
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3396
|
-
'0x',
|
|
3397
|
-
'0x0001',
|
|
3398
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3399
|
-
],
|
|
3400
|
-
},
|
|
3401
|
-
{
|
|
3402
|
-
field: 'weight',
|
|
3403
|
-
value: [
|
|
3404
|
-
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3405
|
-
'0x',
|
|
3406
|
-
'0x01',
|
|
3407
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x0001']],
|
|
3408
|
-
],
|
|
3409
|
-
},
|
|
3410
|
-
])('error: rejects a noncanonical $field', ({ value }) => {
|
|
3411
|
-
const malformed = Hex.concat(
|
|
3412
|
-
'0x05',
|
|
3413
|
-
Rlp.fromHex([
|
|
3414
|
-
'0xc4a590afa7337e5cd5eb3aa60cacf91c5400044b',
|
|
3415
|
-
value as never,
|
|
3416
|
-
[Hex.slice(initial, -65)],
|
|
3417
|
-
]),
|
|
3418
|
-
)
|
|
3419
|
-
|
|
3420
|
-
expect(() => SignatureEnvelope.deserialize(malformed)).toThrowError(
|
|
3421
|
-
SignatureEnvelope.InvalidSerializedError,
|
|
3422
|
-
)
|
|
3423
|
-
})
|
|
3424
3157
|
})
|