ox 1.7.4 → 1.7.5
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 +16 -0
- package/dist/core/AbiEvent.d.ts.map +1 -1
- package/dist/core/AbiEvent.js +10 -9
- package/dist/core/AbiEvent.js.map +1 -1
- package/dist/core/Blobs.d.ts.map +1 -1
- package/dist/core/Blobs.js +6 -2
- package/dist/core/Blobs.js.map +1 -1
- package/dist/core/Signature.d.ts +6 -5
- package/dist/core/Signature.d.ts.map +1 -1
- package/dist/core/Signature.js +17 -7
- package/dist/core/Signature.js.map +1 -1
- package/dist/core/Siwe.d.ts.map +1 -1
- package/dist/core/Siwe.js +21 -1
- package/dist/core/Siwe.js.map +1 -1
- package/dist/tempo/KeyAuthorization.d.ts +42 -38
- package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/KeyAuthorization.js +24 -4
- package/dist/tempo/KeyAuthorization.js.map +1 -1
- package/dist/tempo/MultisigConfig.d.ts +18 -15
- package/dist/tempo/MultisigConfig.d.ts.map +1 -1
- package/dist/tempo/MultisigConfig.js +49 -20
- package/dist/tempo/MultisigConfig.js.map +1 -1
- package/dist/tempo/MultisigOperation.d.ts +3 -2
- package/dist/tempo/MultisigOperation.d.ts.map +1 -1
- package/dist/tempo/MultisigOperation.js +20 -100
- package/dist/tempo/MultisigOperation.js.map +1 -1
- package/dist/tempo/MultisigSimulation.d.ts +2 -49
- package/dist/tempo/MultisigSimulation.d.ts.map +1 -1
- package/dist/tempo/MultisigSimulation.js +18 -46
- package/dist/tempo/MultisigSimulation.js.map +1 -1
- package/dist/tempo/SignatureEnvelope.d.ts +72 -140
- package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/tempo/SignatureEnvelope.js +130 -181
- package/dist/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/tempo/TransactionRequest.d.ts +4 -2
- package/dist/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/tempo/TransactionRequest.js +6 -1
- package/dist/tempo/TransactionRequest.js.map +1 -1
- package/dist/tempo/index.d.ts +3 -1
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +3 -1
- package/dist/tempo/index.js.map +1 -1
- package/dist/zod/tempo/AuthorizationTempo.d.ts +350 -10
- package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.d.ts +242 -422
- package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.js +29 -31
- package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
- package/dist/zod/tempo/MultisigSimulation.d.ts +8 -76
- package/dist/zod/tempo/MultisigSimulation.d.ts.map +1 -1
- package/dist/zod/tempo/MultisigSimulation.js +3 -37
- package/dist/zod/tempo/MultisigSimulation.js.map +1 -1
- package/dist/zod/tempo/RpcSchemaTempo.d.ts +284 -228
- package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.d.ts +105 -3
- package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.js +21 -9
- package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/zod/tempo/Transaction.d.ts +676 -448
- package/dist/zod/tempo/Transaction.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.d.ts +590 -473
- package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.js +8 -0
- package/dist/zod/tempo/TransactionRequest.js.map +1 -1
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts +327 -213
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
- package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +105 -3
- package/dist/zod/tempo/ZoneRpcAuthentication.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/AbiEvent.ts +11 -9
- package/src/core/Blobs.ts +6 -2
- package/src/core/Signature.ts +41 -11
- package/src/core/Siwe.ts +20 -1
- package/src/core/_test/AbiEvent.test.ts +45 -0
- package/src/core/_test/Blobs.test.ts +22 -0
- package/src/core/_test/Signature.test.ts +166 -0
- package/src/core/_test/Siwe.test.ts +100 -1
- package/src/tempo/AuthorizationTempo.test.ts +8 -4
- package/src/tempo/KeyAuthorization.test-d.ts +16 -63
- package/src/tempo/KeyAuthorization.test.ts +69 -11
- package/src/tempo/KeyAuthorization.ts +107 -63
- package/src/tempo/MultisigConfig.test.ts +44 -20
- package/src/tempo/MultisigConfig.ts +65 -23
- package/src/tempo/MultisigOperation.test.ts +79 -550
- package/src/tempo/MultisigOperation.ts +31 -146
- package/src/tempo/MultisigSimulation.test-d.ts +2 -2
- package/src/tempo/MultisigSimulation.test.ts +21 -110
- package/src/tempo/MultisigSimulation.ts +25 -99
- package/src/tempo/SignatureEnvelope.test-d.ts +29 -47
- package/src/tempo/SignatureEnvelope.test.ts +328 -541
- package/src/tempo/SignatureEnvelope.ts +196 -291
- package/src/tempo/Transaction.test.ts +4 -4
- package/src/tempo/TransactionRequest.test.ts +17 -80
- package/src/tempo/TransactionRequest.ts +19 -2
- package/src/tempo/index.ts +3 -1
- package/src/tempo/multisig.e2e.test.ts +321 -860
- package/src/version.ts +1 -1
- package/src/zod/tempo/KeyAuthorization.ts +50 -39
- package/src/zod/tempo/MultisigSimulation.ts +3 -48
- package/src/zod/tempo/SignatureEnvelope.ts +46 -35
- package/src/zod/tempo/TransactionRequest.ts +14 -0
- package/src/zod/tempo/_test/KeyAuthorization.test.ts +69 -20
- package/src/zod/tempo/_test/MultisigSimulation.test.ts +6 -31
- package/src/zod/tempo/_test/SignatureEnvelope.test.ts +118 -85
- package/src/zod/tempo/_test/TransactionRequest.test.ts +9 -19
|
@@ -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
|
-
'0xf897949dba7f426b711d4893c11611eacf7cc334e7146bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b' as const
|
|
73
|
-
|
|
74
70
|
describe('assert', () => {
|
|
75
71
|
describe('secp256k1', () => {
|
|
76
72
|
test('behavior: validates valid signature', () => {
|
|
@@ -1120,86 +1116,30 @@ describe('from', () => {
|
|
|
1120
1116
|
})
|
|
1121
1117
|
|
|
1122
1118
|
describe('multisig', () => {
|
|
1123
|
-
|
|
1124
|
-
owners: [
|
|
1125
|
-
{
|
|
1126
|
-
owner: '0x1111111111111111111111111111111111111111',
|
|
1127
|
-
weight: 1,
|
|
1128
|
-
},
|
|
1129
|
-
],
|
|
1130
|
-
threshold: 1,
|
|
1131
|
-
})
|
|
1132
|
-
|
|
1133
|
-
test('behavior: derives an initial account from config', () => {
|
|
1119
|
+
test('normalizes the current config', () => {
|
|
1134
1120
|
const envelope = SignatureEnvelope.from({
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
{
|
|
1141
|
-
signatures: [
|
|
1142
|
-
{
|
|
1143
|
-
signature: {
|
|
1144
|
-
r: expect.anything(),
|
|
1145
|
-
s: expect.anything(),
|
|
1146
|
-
yParity: expect.any(Number),
|
|
1147
|
-
},
|
|
1148
|
-
},
|
|
1121
|
+
account: '0x2222222222222222222222222222222222222222',
|
|
1122
|
+
config: {
|
|
1123
|
+
threshold: 1,
|
|
1124
|
+
owners: [
|
|
1125
|
+
{ owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
1149
1126
|
],
|
|
1150
1127
|
},
|
|
1151
|
-
|
|
1128
|
+
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
1129
|
+
})
|
|
1130
|
+
expect(envelope.config).toMatchInlineSnapshot(`
|
|
1152
1131
|
{
|
|
1153
|
-
"
|
|
1154
|
-
"config": {
|
|
1155
|
-
"owners": [
|
|
1156
|
-
{
|
|
1157
|
-
"owner": "0x1111111111111111111111111111111111111111",
|
|
1158
|
-
"weight": 1,
|
|
1159
|
-
},
|
|
1160
|
-
],
|
|
1161
|
-
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
1162
|
-
"threshold": 1,
|
|
1163
|
-
"version": 0n,
|
|
1164
|
-
},
|
|
1165
|
-
"signatures": [
|
|
1132
|
+
"owners": [
|
|
1166
1133
|
{
|
|
1167
|
-
"
|
|
1168
|
-
|
|
1169
|
-
"s": Anything,
|
|
1170
|
-
"yParity": Any<Number>,
|
|
1171
|
-
},
|
|
1172
|
-
"type": "secp256k1",
|
|
1134
|
+
"owner": "0x1111111111111111111111111111111111111111",
|
|
1135
|
+
"weight": 1,
|
|
1173
1136
|
},
|
|
1174
1137
|
],
|
|
1175
|
-
"
|
|
1138
|
+
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
1139
|
+
"threshold": 1,
|
|
1140
|
+
"version": 0n,
|
|
1176
1141
|
}
|
|
1177
|
-
|
|
1178
|
-
)
|
|
1179
|
-
})
|
|
1180
|
-
|
|
1181
|
-
test('behavior: accepts an explicit account for a current config', () => {
|
|
1182
|
-
const envelope = SignatureEnvelope.from({
|
|
1183
|
-
account: '0x2222222222222222222222222222222222222222',
|
|
1184
|
-
config: { ...config, version: 1 },
|
|
1185
|
-
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
1186
|
-
})
|
|
1187
|
-
|
|
1188
|
-
expect(envelope.account).toBe(
|
|
1189
|
-
'0x2222222222222222222222222222222222222222',
|
|
1190
|
-
)
|
|
1191
|
-
expect(envelope.config.version).toBe(1n)
|
|
1192
|
-
})
|
|
1193
|
-
|
|
1194
|
-
test('error: requires an account for a current config', () => {
|
|
1195
|
-
expect(() =>
|
|
1196
|
-
SignatureEnvelope.from({
|
|
1197
|
-
config: { ...config, version: 1n },
|
|
1198
|
-
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
1199
|
-
} as never),
|
|
1200
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
1201
|
-
`[MultisigConfig.InvalidConfigError: Invalid native multisig config: account address requires version zero.]`,
|
|
1202
|
-
)
|
|
1142
|
+
`)
|
|
1203
1143
|
})
|
|
1204
1144
|
})
|
|
1205
1145
|
})
|
|
@@ -1811,6 +1751,8 @@ describe('serialize', () => {
|
|
|
1811
1751
|
})
|
|
1812
1752
|
|
|
1813
1753
|
describe('sortMultisigApprovals', () => {
|
|
1754
|
+
// Build owner key pairs first so we can construct a real genesis config
|
|
1755
|
+
// whose owner set matches the keys that produce the approvals below.
|
|
1814
1756
|
const ownerKeys = Array.from({ length: 3 }, () => {
|
|
1815
1757
|
const privateKey = Secp256k1.randomPrivateKey()
|
|
1816
1758
|
const address = Address.fromPublicKey(
|
|
@@ -1822,16 +1764,19 @@ describe('sortMultisigApprovals', () => {
|
|
|
1822
1764
|
Hex.toBigInt(a.address) < Hex.toBigInt(b.address) ? -1 : 1,
|
|
1823
1765
|
)
|
|
1824
1766
|
|
|
1825
|
-
const
|
|
1826
|
-
owners: ascendingOwners.map((o) => ({ owner: o.address, weight: 1 })),
|
|
1767
|
+
const genesisConfig = MultisigConfig.from({
|
|
1827
1768
|
threshold: 2,
|
|
1769
|
+
owners: ascendingOwners.map((o) => ({ owner: o.address, weight: 1 })),
|
|
1828
1770
|
})
|
|
1829
|
-
const account = MultisigConfig.getAddress(config)
|
|
1830
1771
|
const payload = `0x${'42'.repeat(32)}` as const
|
|
1772
|
+
const account = MultisigConfig.getAddress(genesisConfig, {
|
|
1773
|
+
factory: '0x7171717171717171717171717171717171717171',
|
|
1774
|
+
})
|
|
1775
|
+
const version = 3n
|
|
1831
1776
|
const digest = MultisigConfig.getSignPayload({
|
|
1832
|
-
account,
|
|
1833
|
-
config,
|
|
1834
1777
|
payload,
|
|
1778
|
+
account,
|
|
1779
|
+
config: { version },
|
|
1835
1780
|
})
|
|
1836
1781
|
|
|
1837
1782
|
const owners = ownerKeys.map((owner) => ({
|
|
@@ -1848,7 +1793,7 @@ describe('sortMultisigApprovals', () => {
|
|
|
1848
1793
|
test('behavior: orders approvals ascending by recovered owner address', () => {
|
|
1849
1794
|
const ordered = SignatureEnvelope.sortMultisigApprovals({
|
|
1850
1795
|
account,
|
|
1851
|
-
config,
|
|
1796
|
+
config: { version },
|
|
1852
1797
|
payload,
|
|
1853
1798
|
// Provide approvals in reverse of the canonical order.
|
|
1854
1799
|
signatures: [...ascending].reverse().map((owner) => owner.signature),
|
|
@@ -1861,7 +1806,7 @@ describe('sortMultisigApprovals', () => {
|
|
|
1861
1806
|
expect(
|
|
1862
1807
|
SignatureEnvelope.sortMultisigApprovals({
|
|
1863
1808
|
account,
|
|
1864
|
-
config,
|
|
1809
|
+
config: { version },
|
|
1865
1810
|
payload,
|
|
1866
1811
|
signatures,
|
|
1867
1812
|
}),
|
|
@@ -1871,7 +1816,7 @@ describe('sortMultisigApprovals', () => {
|
|
|
1871
1816
|
test('behavior: recovered order matches the config owner order', () => {
|
|
1872
1817
|
const ordered = SignatureEnvelope.sortMultisigApprovals({
|
|
1873
1818
|
account,
|
|
1874
|
-
config,
|
|
1819
|
+
config: { version },
|
|
1875
1820
|
payload,
|
|
1876
1821
|
signatures: owners.map((owner) => owner.signature),
|
|
1877
1822
|
})
|
|
@@ -2495,41 +2440,6 @@ describe('fromRpc', () => {
|
|
|
2495
2440
|
expect(envelope.keyId).toBe('0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c')
|
|
2496
2441
|
})
|
|
2497
2442
|
})
|
|
2498
|
-
describe('multisig', () => {
|
|
2499
|
-
test('behavior: converts an untagged serialized multisig signature', () => {
|
|
2500
|
-
expect(
|
|
2501
|
-
SignatureEnvelope.serialize(
|
|
2502
|
-
SignatureEnvelope.fromRpc(signature_multisig_rpc),
|
|
2503
|
-
),
|
|
2504
|
-
).toMatchInlineSnapshot(`"0x05${signature_multisig_rpc.slice(2)}"`)
|
|
2505
|
-
})
|
|
2506
|
-
|
|
2507
|
-
test('error: rejects a payload with the transaction type byte', () => {
|
|
2508
|
-
expect(() =>
|
|
2509
|
-
SignatureEnvelope.fromRpc(`0x05${signature_multisig_rpc.slice(2)}`),
|
|
2510
|
-
).toThrowError()
|
|
2511
|
-
})
|
|
2512
|
-
|
|
2513
|
-
test('error: rejects trailing bytes', () => {
|
|
2514
|
-
expect(() =>
|
|
2515
|
-
SignatureEnvelope.fromRpc(`${signature_multisig_rpc}00`),
|
|
2516
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
2517
|
-
`[Rlp.TrailingBytesError: RLP payload encodes a single item, but \`1\` trailing byte remains.]`,
|
|
2518
|
-
)
|
|
2519
|
-
})
|
|
2520
|
-
|
|
2521
|
-
test('error: rejects a structured multisig signature', () => {
|
|
2522
|
-
expect(() =>
|
|
2523
|
-
SignatureEnvelope.fromRpc({
|
|
2524
|
-
account: '0x9dba7f426b711d4893c11611eacf7cc334e7146b',
|
|
2525
|
-
config: {},
|
|
2526
|
-
signatures: [],
|
|
2527
|
-
} as never),
|
|
2528
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
2529
|
-
`[SignatureEnvelope.CoercionError: Unable to coerce value (\`{"account":"0x9dba7f426b711d4893c11611eacf7cc334e7146b","config":{},"signatures":[]}\`) to a valid signature envelope.]`,
|
|
2530
|
-
)
|
|
2531
|
-
})
|
|
2532
|
-
})
|
|
2533
2443
|
})
|
|
2534
2444
|
|
|
2535
2445
|
describe('toRpc', () => {
|
|
@@ -2627,10 +2537,11 @@ describe('toRpc', () => {
|
|
|
2627
2537
|
const rpc = SignatureEnvelope.toRpc(
|
|
2628
2538
|
signature_keychain_p256,
|
|
2629
2539
|
) as SignatureEnvelope.KeychainRpc
|
|
2630
|
-
if (typeof rpc.signature === 'string') throw new Error('unreachable')
|
|
2631
2540
|
|
|
2632
2541
|
expect(rpc.type).toBe('keychain')
|
|
2633
2542
|
expect(rpc.userAddress).toBe(signature_keychain_p256.userAddress)
|
|
2543
|
+
if (typeof rpc.signature === 'string')
|
|
2544
|
+
throw new Error('Expected structured primitive signature')
|
|
2634
2545
|
expect(rpc.signature.type).toBe('p256')
|
|
2635
2546
|
expect(typeof rpc.signature.pubKeyX).toBe('string')
|
|
2636
2547
|
expect(typeof rpc.signature.pubKeyY).toBe('string')
|
|
@@ -2640,10 +2551,11 @@ describe('toRpc', () => {
|
|
|
2640
2551
|
const rpc = SignatureEnvelope.toRpc(
|
|
2641
2552
|
signature_keychain_webauthn,
|
|
2642
2553
|
) as SignatureEnvelope.KeychainRpc
|
|
2643
|
-
if (typeof rpc.signature === 'string') throw new Error('unreachable')
|
|
2644
2554
|
|
|
2645
2555
|
expect(rpc.type).toBe('keychain')
|
|
2646
2556
|
expect(rpc.userAddress).toBe(signature_keychain_webauthn.userAddress)
|
|
2557
|
+
if (typeof rpc.signature === 'string')
|
|
2558
|
+
throw new Error('Expected structured primitive signature')
|
|
2647
2559
|
expect(rpc.signature.type).toBe('webAuthn')
|
|
2648
2560
|
expect(typeof rpc.signature.pubKeyX).toBe('string')
|
|
2649
2561
|
expect(typeof rpc.signature.webauthnData).toBe('string')
|
|
@@ -2684,18 +2596,6 @@ describe('toRpc', () => {
|
|
|
2684
2596
|
expect(rpc.keyId).toBeUndefined()
|
|
2685
2597
|
})
|
|
2686
2598
|
})
|
|
2687
|
-
|
|
2688
|
-
describe('multisig', () => {
|
|
2689
|
-
test('behavior: removes the transaction type byte', () => {
|
|
2690
|
-
const envelope = SignatureEnvelope.deserialize(
|
|
2691
|
-
`0x05${signature_multisig_rpc.slice(2)}`,
|
|
2692
|
-
)
|
|
2693
|
-
|
|
2694
|
-
expect(SignatureEnvelope.toRpc(envelope)).toMatchInlineSnapshot(
|
|
2695
|
-
`"${signature_multisig_rpc}"`,
|
|
2696
|
-
)
|
|
2697
|
-
})
|
|
2698
|
-
})
|
|
2699
2599
|
})
|
|
2700
2600
|
|
|
2701
2601
|
describe('roundtrip: toRpc <-> fromRpc', () => {
|
|
@@ -2933,19 +2833,308 @@ describe('CoercionError', () => {
|
|
|
2933
2833
|
})
|
|
2934
2834
|
|
|
2935
2835
|
describe('multisig', () => {
|
|
2836
|
+
const account = '0x2222222222222222222222222222222222222222'
|
|
2837
|
+
const config = MultisigConfig.from({
|
|
2838
|
+
threshold: 1,
|
|
2839
|
+
owners: [
|
|
2840
|
+
{ owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
2841
|
+
],
|
|
2842
|
+
})
|
|
2843
|
+
const primitive = SignatureEnvelope.from(signature_secp256k1)
|
|
2844
|
+
const envelope = SignatureEnvelope.from({
|
|
2845
|
+
account,
|
|
2846
|
+
config,
|
|
2847
|
+
signatures: [primitive],
|
|
2848
|
+
})
|
|
2849
|
+
|
|
2850
|
+
test('serialize carries account, config, and primitive approvals', () => {
|
|
2851
|
+
const serialized = SignatureEnvelope.serialize(envelope)
|
|
2852
|
+
expect(Hex.slice(serialized, 0, 1)).toMatchInlineSnapshot('"0x05"')
|
|
2853
|
+
expect(Rlp.toHex(Hex.slice(serialized, 1))).toEqual([
|
|
2854
|
+
account,
|
|
2855
|
+
[
|
|
2856
|
+
MultisigConfig.zeroSalt,
|
|
2857
|
+
'0x',
|
|
2858
|
+
'0x01',
|
|
2859
|
+
[['0x1111111111111111111111111111111111111111', '0x01']],
|
|
2860
|
+
],
|
|
2861
|
+
[SignatureEnvelope.serialize(primitive)],
|
|
2862
|
+
])
|
|
2863
|
+
expect(SignatureEnvelope.deserialize(serialized)).toEqual(envelope)
|
|
2864
|
+
})
|
|
2865
|
+
|
|
2866
|
+
test('RPC contains RLP bytes without the type byte', () => {
|
|
2867
|
+
const rpc = SignatureEnvelope.toRpc(envelope)
|
|
2868
|
+
expect(rpc).toBe(Hex.slice(SignatureEnvelope.serialize(envelope), 1))
|
|
2869
|
+
expect(SignatureEnvelope.fromRpc(rpc)).toEqual(envelope)
|
|
2870
|
+
})
|
|
2871
|
+
|
|
2872
|
+
test('round-trips updated configs and maximum versions', () => {
|
|
2873
|
+
for (const version of [1n, 0xffffffffffffffffn]) {
|
|
2874
|
+
const updated = SignatureEnvelope.from({
|
|
2875
|
+
...envelope,
|
|
2876
|
+
config: { ...config, config: { version } },
|
|
2877
|
+
})
|
|
2878
|
+
expect(
|
|
2879
|
+
SignatureEnvelope.deserialize(SignatureEnvelope.serialize(updated)),
|
|
2880
|
+
).toEqual(updated)
|
|
2881
|
+
expect(
|
|
2882
|
+
SignatureEnvelope.fromRpc(SignatureEnvelope.toRpc(updated)),
|
|
2883
|
+
).toEqual(updated)
|
|
2884
|
+
}
|
|
2885
|
+
})
|
|
2886
|
+
|
|
2887
|
+
test('extractAddress returns the named account', () => {
|
|
2888
|
+
expect(
|
|
2889
|
+
SignatureEnvelope.extractAddress({
|
|
2890
|
+
signature: envelope,
|
|
2891
|
+
payload: '0x00',
|
|
2892
|
+
}),
|
|
2893
|
+
).toMatchInlineSnapshot('"0x2222222222222222222222222222222222222222"')
|
|
2894
|
+
})
|
|
2895
|
+
|
|
2896
|
+
test('rejects old wire and RPC shapes', () => {
|
|
2897
|
+
for (const first of [account, MultisigConfig.toTuple(config)] as const)
|
|
2898
|
+
expect(() =>
|
|
2899
|
+
SignatureEnvelope.deserialize(
|
|
2900
|
+
Hex.concat(
|
|
2901
|
+
'0x05',
|
|
2902
|
+
Rlp.fromHex([first, [SignatureEnvelope.serialize(primitive)]]),
|
|
2903
|
+
),
|
|
2904
|
+
),
|
|
2905
|
+
).toThrowError(SignatureEnvelope.InvalidSerializedError)
|
|
2906
|
+
expect(() =>
|
|
2907
|
+
SignatureEnvelope.fromRpc({ account, signatures: [] } as never),
|
|
2908
|
+
).toThrowError(SignatureEnvelope.CoercionError)
|
|
2909
|
+
})
|
|
2910
|
+
|
|
2911
|
+
test('rejects malformed configs', () => {
|
|
2912
|
+
const valid = MultisigConfig.toTuple(config)
|
|
2913
|
+
for (const tuple of [
|
|
2914
|
+
['0x00', ...valid.slice(1)],
|
|
2915
|
+
[valid[0], '0x00', valid[2], valid[3]],
|
|
2916
|
+
[valid[0], '0x010000000000000000', valid[2], valid[3]],
|
|
2917
|
+
[valid[0], '0x', '0x0001', valid[3]],
|
|
2918
|
+
[valid[0], '0x', '0x01', [[config.owners[0]!.owner, '0x0001']]],
|
|
2919
|
+
[valid[0], '0x', '0x01', []],
|
|
2920
|
+
])
|
|
2921
|
+
expect(() =>
|
|
2922
|
+
SignatureEnvelope.deserialize(
|
|
2923
|
+
Hex.concat(
|
|
2924
|
+
'0x05',
|
|
2925
|
+
Rlp.fromHex([
|
|
2926
|
+
account,
|
|
2927
|
+
tuple as never,
|
|
2928
|
+
[SignatureEnvelope.serialize(primitive)],
|
|
2929
|
+
]),
|
|
2930
|
+
),
|
|
2931
|
+
),
|
|
2932
|
+
).toThrowError(MultisigConfig.InvalidConfigError)
|
|
2933
|
+
})
|
|
2934
|
+
|
|
2935
|
+
test('rejects invalid accounts, missing configs, and invalid approval counts', () => {
|
|
2936
|
+
for (const value of [
|
|
2937
|
+
{ ...envelope, account: '0x11' },
|
|
2938
|
+
{ ...envelope, account: '0x0000000000000000000000000000000000000000' },
|
|
2939
|
+
{ ...envelope, config: undefined },
|
|
2940
|
+
{ ...envelope, signatures: [] },
|
|
2941
|
+
{ ...envelope, signatures: Array(9).fill(primitive) },
|
|
2942
|
+
])
|
|
2943
|
+
expect(() => SignatureEnvelope.assert(value as never)).toThrowError()
|
|
2944
|
+
expect(() =>
|
|
2945
|
+
SignatureEnvelope.assert({
|
|
2946
|
+
...envelope,
|
|
2947
|
+
signatures: Array(8).fill(primitive),
|
|
2948
|
+
}),
|
|
2949
|
+
).not.toThrow()
|
|
2950
|
+
})
|
|
2951
|
+
|
|
2952
|
+
test('rejects self-ownership in the initial configuration', () => {
|
|
2953
|
+
const self = {
|
|
2954
|
+
...envelope,
|
|
2955
|
+
config: { ...config, owners: [{ owner: account, weight: 1 }] },
|
|
2956
|
+
} as const
|
|
2957
|
+
expect(() => SignatureEnvelope.assert(self)).toThrowError(
|
|
2958
|
+
SignatureEnvelope.InvalidMultisigApprovalError,
|
|
2959
|
+
)
|
|
2960
|
+
expect(() =>
|
|
2961
|
+
SignatureEnvelope.assert({
|
|
2962
|
+
...self,
|
|
2963
|
+
config: { ...self.config, version: 1n },
|
|
2964
|
+
}),
|
|
2965
|
+
).not.toThrow()
|
|
2966
|
+
})
|
|
2967
|
+
|
|
2968
|
+
test('rejects recursive owner approvals before decoding them', () => {
|
|
2969
|
+
for (const signature of [envelope, signature_keychain_secp256k1]) {
|
|
2970
|
+
expect(() =>
|
|
2971
|
+
SignatureEnvelope.serialize({
|
|
2972
|
+
...envelope,
|
|
2973
|
+
signatures: [signature],
|
|
2974
|
+
} as never),
|
|
2975
|
+
).toThrowError(SignatureEnvelope.InvalidMultisigApprovalError)
|
|
2976
|
+
const wire = Hex.concat(
|
|
2977
|
+
'0x05',
|
|
2978
|
+
Rlp.fromHex([
|
|
2979
|
+
account,
|
|
2980
|
+
MultisigConfig.toTuple(config),
|
|
2981
|
+
[SignatureEnvelope.serialize(signature)],
|
|
2982
|
+
]),
|
|
2983
|
+
)
|
|
2984
|
+
expect(() => SignatureEnvelope.deserialize(wire)).toThrowError(
|
|
2985
|
+
SignatureEnvelope.InvalidSerializedError,
|
|
2986
|
+
)
|
|
2987
|
+
}
|
|
2988
|
+
})
|
|
2989
|
+
|
|
2990
|
+
test('rejects oversized owner approvals', () => {
|
|
2991
|
+
const oversized = {
|
|
2992
|
+
...signature_webauthn,
|
|
2993
|
+
metadata: {
|
|
2994
|
+
...signature_webauthn.metadata,
|
|
2995
|
+
clientDataJSON: 'x'.repeat(2048),
|
|
2996
|
+
},
|
|
2997
|
+
}
|
|
2998
|
+
expect(() =>
|
|
2999
|
+
SignatureEnvelope.assert({ ...envelope, signatures: [oversized] }),
|
|
3000
|
+
).toThrowError(SignatureEnvelope.InvalidMultisigApprovalError)
|
|
3001
|
+
})
|
|
3002
|
+
test('requires keychain V2 for multisig delegates', () => {
|
|
3003
|
+
const keychain = SignatureEnvelope.from({
|
|
3004
|
+
userAddress: account,
|
|
3005
|
+
version: 'v2',
|
|
3006
|
+
inner: envelope,
|
|
3007
|
+
})
|
|
3008
|
+
const serialized = SignatureEnvelope.serialize(keychain)
|
|
3009
|
+
expect(SignatureEnvelope.deserialize(serialized)).toEqual(keychain)
|
|
3010
|
+
expect(() =>
|
|
3011
|
+
SignatureEnvelope.serialize({ ...keychain, version: 'v1' }),
|
|
3012
|
+
).toThrowError(SignatureEnvelope.InvalidMultisigApprovalError)
|
|
3013
|
+
expect(() =>
|
|
3014
|
+
SignatureEnvelope.deserialize(
|
|
3015
|
+
Hex.concat('0x03', Hex.slice(serialized, 1)),
|
|
3016
|
+
),
|
|
3017
|
+
).toThrowError(SignatureEnvelope.InvalidMultisigApprovalError)
|
|
3018
|
+
expect(() =>
|
|
3019
|
+
SignatureEnvelope.fromRpc({
|
|
3020
|
+
type: 'keychain',
|
|
3021
|
+
userAddress: account,
|
|
3022
|
+
version: 'v1',
|
|
3023
|
+
signature: SignatureEnvelope.toRpc(envelope),
|
|
3024
|
+
}),
|
|
3025
|
+
).toThrowError(SignatureEnvelope.InvalidMultisigApprovalError)
|
|
3026
|
+
})
|
|
3027
|
+
test('rejects V1 wrappers around recursively nested multisig delegates', () => {
|
|
3028
|
+
let inner: SignatureEnvelope.SignatureEnvelope = envelope
|
|
3029
|
+
for (let depth = 0; depth < 3; depth++) {
|
|
3030
|
+
inner = { inner, type: 'keychain', userAddress: account, version: 'v2' }
|
|
3031
|
+
const valid: SignatureEnvelope.Keychain = {
|
|
3032
|
+
inner,
|
|
3033
|
+
type: 'keychain',
|
|
3034
|
+
userAddress: account,
|
|
3035
|
+
version: 'v2',
|
|
3036
|
+
}
|
|
3037
|
+
const invalid = { ...valid, version: 'v1' } as const
|
|
3038
|
+
expect(SignatureEnvelope.validate(valid)).toBe(true)
|
|
3039
|
+
expect(SignatureEnvelope.validate(invalid)).toBe(false)
|
|
3040
|
+
expect(() => SignatureEnvelope.serialize(invalid)).toThrowError(
|
|
3041
|
+
SignatureEnvelope.InvalidMultisigApprovalError,
|
|
3042
|
+
)
|
|
3043
|
+
expect(() => SignatureEnvelope.toRpc(invalid)).toThrowError(
|
|
3044
|
+
SignatureEnvelope.InvalidMultisigApprovalError,
|
|
3045
|
+
)
|
|
3046
|
+
expect(() =>
|
|
3047
|
+
SignatureEnvelope.fromRpc({
|
|
3048
|
+
type: 'keychain',
|
|
3049
|
+
userAddress: account,
|
|
3050
|
+
version: 'v1',
|
|
3051
|
+
signature: SignatureEnvelope.toRpc(inner),
|
|
3052
|
+
}),
|
|
3053
|
+
).toThrowError(SignatureEnvelope.InvalidMultisigApprovalError)
|
|
3054
|
+
expect(() =>
|
|
3055
|
+
SignatureEnvelope.deserialize(
|
|
3056
|
+
Hex.concat('0x03', account, SignatureEnvelope.serialize(inner)),
|
|
3057
|
+
),
|
|
3058
|
+
).toThrowError(SignatureEnvelope.InvalidMultisigApprovalError)
|
|
3059
|
+
expect(() =>
|
|
3060
|
+
SignatureEnvelope.serialize({ ...valid, inner: invalid }),
|
|
3061
|
+
).toThrowError(SignatureEnvelope.InvalidMultisigApprovalError)
|
|
3062
|
+
}
|
|
3063
|
+
expect(
|
|
3064
|
+
SignatureEnvelope.validate({
|
|
3065
|
+
inner: {
|
|
3066
|
+
inner: primitive,
|
|
3067
|
+
type: 'keychain',
|
|
3068
|
+
userAddress: account,
|
|
3069
|
+
version: 'v2',
|
|
3070
|
+
},
|
|
3071
|
+
type: 'keychain',
|
|
3072
|
+
userAddress: account,
|
|
3073
|
+
version: 'v1',
|
|
3074
|
+
}),
|
|
3075
|
+
).toBe(true)
|
|
3076
|
+
})
|
|
3077
|
+
test('RPC rejects 64-byte values that resemble primitive signatures', () => {
|
|
3078
|
+
expect(() =>
|
|
3079
|
+
SignatureEnvelope.fromRpc(
|
|
3080
|
+
Hex.concat(Hex.fromNumber(1, { size: 63 }), '0x1b'),
|
|
3081
|
+
),
|
|
3082
|
+
).toThrowError()
|
|
3083
|
+
})
|
|
3084
|
+
test('toRpc normalizes numberish fields before RLP encoding', () => {
|
|
3085
|
+
const signature = {
|
|
3086
|
+
...primitive,
|
|
3087
|
+
signature: {
|
|
3088
|
+
...primitive.signature,
|
|
3089
|
+
yParity: Hex.fromNumber(primitive.signature.yParity),
|
|
3090
|
+
},
|
|
3091
|
+
}
|
|
3092
|
+
expect(
|
|
3093
|
+
SignatureEnvelope.toRpc({
|
|
3094
|
+
...envelope,
|
|
3095
|
+
config: {
|
|
3096
|
+
...config,
|
|
3097
|
+
threshold: '0x1' as const,
|
|
3098
|
+
owners: config.owners.map((owner) => ({
|
|
3099
|
+
...owner,
|
|
3100
|
+
weight: '0x1' as const,
|
|
3101
|
+
})),
|
|
3102
|
+
},
|
|
3103
|
+
signatures: [signature],
|
|
3104
|
+
}),
|
|
3105
|
+
).toBe(SignatureEnvelope.toRpc(envelope))
|
|
3106
|
+
})
|
|
3107
|
+
test('round-trips mixed primitive approvals', () => {
|
|
3108
|
+
const signature = { r: p256Signature.r, s: p256Signature.s }
|
|
3109
|
+
const mixed = SignatureEnvelope.from({
|
|
3110
|
+
...envelope,
|
|
3111
|
+
signatures: [
|
|
3112
|
+
primitive,
|
|
3113
|
+
{ ...signature_p256, signature },
|
|
3114
|
+
{ ...signature_webauthn, signature },
|
|
3115
|
+
],
|
|
3116
|
+
})
|
|
3117
|
+
expect(
|
|
3118
|
+
SignatureEnvelope.deserialize(SignatureEnvelope.serialize(mixed)),
|
|
3119
|
+
).toEqual(mixed)
|
|
3120
|
+
expect(SignatureEnvelope.fromRpc(SignatureEnvelope.toRpc(mixed))).toEqual(
|
|
3121
|
+
mixed,
|
|
3122
|
+
)
|
|
3123
|
+
})
|
|
3124
|
+
})
|
|
3125
|
+
|
|
3126
|
+
describe('multisig vectors', () => {
|
|
2936
3127
|
const initial =
|
|
2937
|
-
'
|
|
3128
|
+
'0x05f89794c4a590afa7337e5cd5eb3aa60cacf91c5400044bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b' as const
|
|
2938
3129
|
const current =
|
|
2939
|
-
'
|
|
2940
|
-
const nested =
|
|
2941
|
-
'0x05f8f0949969e2243075b27a8eab61009c59b77ab13b83f6f83ba033333333333333333333333333333333333333333333333333333333333333338001d7d6944f9f5b162a7464bcc260ce44d7ae0d935f9c583701f89cb89a05f897944f9f5b162a7464bcc260ce44d7ae0d935f9c5837f83ba022222222222222222222222222222222222222222222222222222222222222228001d7d6946813eb9362372eef6200f3b1dbc3f819671cba6901f843b841032aa6f3ea7b0b7069720d0f3891983c493d149326c5c957d864ed7371b8475e3e95325079b24491f4b6d68920e4b3bacd7c6094df6ed88b8674864ab559c8fb1b' as const
|
|
3130
|
+
'0x05f89794c4a590afa7337e5cd5eb3aa60cacf91c5400044bf83ba000000000000000000000000000000000000000000000000000000000000000000101d7d6942b5ad5c4795c026514f8317c7a215e218dccd6cf01f843b8414a0e5b5b4a90f08e6e8d676a73a22dc2cf022ffdcc9299512b5d9a6daf66e0504a395a2ef6f6c0f173910b875c45d01aa66d928e56ba6f49bbc8186f80268bf51b' as const
|
|
2942
3131
|
|
|
2943
3132
|
test('example: decodes and re-encodes the frozen initial signature', () => {
|
|
2944
3133
|
const envelope = SignatureEnvelope.deserialize(initial)
|
|
2945
3134
|
|
|
2946
3135
|
expect(envelope).toMatchInlineSnapshot(`
|
|
2947
3136
|
{
|
|
2948
|
-
"account": "
|
|
3137
|
+
"account": "0xc4a590afa7337e5cd5eb3aa60cacf91c5400044b",
|
|
2949
3138
|
"config": {
|
|
2950
3139
|
"owners": [
|
|
2951
3140
|
{
|
|
@@ -2978,7 +3167,7 @@ describe('multisig', () => {
|
|
|
2978
3167
|
|
|
2979
3168
|
expect(envelope).toMatchInlineSnapshot(`
|
|
2980
3169
|
{
|
|
2981
|
-
"account": "
|
|
3170
|
+
"account": "0xc4a590afa7337e5cd5eb3aa60cacf91c5400044b",
|
|
2982
3171
|
"config": {
|
|
2983
3172
|
"owners": [
|
|
2984
3173
|
{
|
|
@@ -3006,56 +3195,6 @@ describe('multisig', () => {
|
|
|
3006
3195
|
expect(SignatureEnvelope.serialize(envelope)).toBe(current)
|
|
3007
3196
|
})
|
|
3008
3197
|
|
|
3009
|
-
test('example: decodes and re-encodes the frozen nested signature', () => {
|
|
3010
|
-
const envelope = SignatureEnvelope.deserialize(nested)
|
|
3011
|
-
|
|
3012
|
-
expect(envelope).toMatchInlineSnapshot(`
|
|
3013
|
-
{
|
|
3014
|
-
"account": "0x9969e2243075b27a8eab61009c59b77ab13b83f6",
|
|
3015
|
-
"config": {
|
|
3016
|
-
"owners": [
|
|
3017
|
-
{
|
|
3018
|
-
"owner": "0x4f9f5b162a7464bcc260ce44d7ae0d935f9c5837",
|
|
3019
|
-
"weight": 1,
|
|
3020
|
-
},
|
|
3021
|
-
],
|
|
3022
|
-
"salt": "0x3333333333333333333333333333333333333333333333333333333333333333",
|
|
3023
|
-
"threshold": 1,
|
|
3024
|
-
"version": 0n,
|
|
3025
|
-
},
|
|
3026
|
-
"signatures": [
|
|
3027
|
-
{
|
|
3028
|
-
"account": "0x4f9f5b162a7464bcc260ce44d7ae0d935f9c5837",
|
|
3029
|
-
"config": {
|
|
3030
|
-
"owners": [
|
|
3031
|
-
{
|
|
3032
|
-
"owner": "0x6813eb9362372eef6200f3b1dbc3f819671cba69",
|
|
3033
|
-
"weight": 1,
|
|
3034
|
-
},
|
|
3035
|
-
],
|
|
3036
|
-
"salt": "0x2222222222222222222222222222222222222222222222222222222222222222",
|
|
3037
|
-
"threshold": 1,
|
|
3038
|
-
"version": 0n,
|
|
3039
|
-
},
|
|
3040
|
-
"signatures": [
|
|
3041
|
-
{
|
|
3042
|
-
"signature": {
|
|
3043
|
-
"r": "0x032aa6f3ea7b0b7069720d0f3891983c493d149326c5c957d864ed7371b8475e",
|
|
3044
|
-
"s": "0x3e95325079b24491f4b6d68920e4b3bacd7c6094df6ed88b8674864ab559c8fb",
|
|
3045
|
-
"yParity": 0,
|
|
3046
|
-
},
|
|
3047
|
-
"type": "secp256k1",
|
|
3048
|
-
},
|
|
3049
|
-
],
|
|
3050
|
-
"type": "multisig",
|
|
3051
|
-
},
|
|
3052
|
-
],
|
|
3053
|
-
"type": "multisig",
|
|
3054
|
-
}
|
|
3055
|
-
`)
|
|
3056
|
-
expect(SignatureEnvelope.serialize(envelope)).toBe(nested)
|
|
3057
|
-
})
|
|
3058
|
-
|
|
3059
3198
|
test('behavior: preserves exact 65-byte secp256k1 precedence', () => {
|
|
3060
3199
|
const serialized = ('0x05' +
|
|
3061
3200
|
'00'.repeat(31) +
|
|
@@ -3066,356 +3205,4 @@ describe('multisig', () => {
|
|
|
3066
3205
|
expect(Hex.size(serialized)).toBe(65)
|
|
3067
3206
|
expect(SignatureEnvelope.deserialize(serialized).type).toBe('secp256k1')
|
|
3068
3207
|
})
|
|
3069
|
-
|
|
3070
|
-
test('behavior: serializes account, config, and approvals in wire order', () => {
|
|
3071
|
-
const envelope = SignatureEnvelope.deserialize(initial)
|
|
3072
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3073
|
-
const serialized = SignatureEnvelope.serialize(envelope)
|
|
3074
|
-
const decoded = Rlp.toHex(Hex.slice(serialized, 1))
|
|
3075
|
-
|
|
3076
|
-
expect(decoded).toMatchInlineSnapshot(`
|
|
3077
|
-
[
|
|
3078
|
-
"0x9dba7f426b711d4893c11611eacf7cc334e7146b",
|
|
3079
|
-
[
|
|
3080
|
-
"0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
3081
|
-
"0x",
|
|
3082
|
-
"0x01",
|
|
3083
|
-
[
|
|
3084
|
-
[
|
|
3085
|
-
"0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
|
|
3086
|
-
"0x01",
|
|
3087
|
-
],
|
|
3088
|
-
],
|
|
3089
|
-
],
|
|
3090
|
-
[
|
|
3091
|
-
"0x869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b",
|
|
3092
|
-
],
|
|
3093
|
-
]
|
|
3094
|
-
`)
|
|
3095
|
-
})
|
|
3096
|
-
|
|
3097
|
-
test('behavior: round trips initial and current RPC signatures', () => {
|
|
3098
|
-
const initialEnvelope = SignatureEnvelope.deserialize(initial)
|
|
3099
|
-
const currentEnvelope = SignatureEnvelope.deserialize(current)
|
|
3100
|
-
const rpc = {
|
|
3101
|
-
current: SignatureEnvelope.toRpc(currentEnvelope),
|
|
3102
|
-
initial: SignatureEnvelope.toRpc(initialEnvelope),
|
|
3103
|
-
}
|
|
3104
|
-
|
|
3105
|
-
expect(rpc).toMatchInlineSnapshot(`
|
|
3106
|
-
{
|
|
3107
|
-
"current": "0xf897949dba7f426b711d4893c11611eacf7cc334e7146bf83ba000000000000000000000000000000000000000000000000000000000000000000101d7d6942b5ad5c4795c026514f8317c7a215e218dccd6cf01f843b8414a0e5b5b4a90f08e6e8d676a73a22dc2cf022ffdcc9299512b5d9a6daf66e0504a395a2ef6f6c0f173910b875c45d01aa66d928e56ba6f49bbc8186f80268bf51b",
|
|
3108
|
-
"initial": "0xf897949dba7f426b711d4893c11611eacf7cc334e7146bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b",
|
|
3109
|
-
}
|
|
3110
|
-
`)
|
|
3111
|
-
expect(SignatureEnvelope.fromRpc(rpc.initial)).toStrictEqual(
|
|
3112
|
-
initialEnvelope,
|
|
3113
|
-
)
|
|
3114
|
-
expect(SignatureEnvelope.fromRpc(rpc.current)).toStrictEqual(
|
|
3115
|
-
currentEnvelope,
|
|
3116
|
-
)
|
|
3117
|
-
})
|
|
3118
|
-
|
|
3119
|
-
test('behavior: derives the account only for an initial config', () => {
|
|
3120
|
-
const initialEnvelope = SignatureEnvelope.deserialize(initial)
|
|
3121
|
-
if (initialEnvelope.type !== 'multisig') throw new Error('unreachable')
|
|
3122
|
-
if (initialEnvelope.config.version !== 0n) throw new Error('unreachable')
|
|
3123
|
-
const derived = SignatureEnvelope.from({
|
|
3124
|
-
config: initialEnvelope.config as MultisigConfig.Config<0n>,
|
|
3125
|
-
signatures: initialEnvelope.signatures,
|
|
3126
|
-
})
|
|
3127
|
-
const currentEnvelope = SignatureEnvelope.deserialize(current)
|
|
3128
|
-
if (currentEnvelope.type !== 'multisig') throw new Error('unreachable')
|
|
3129
|
-
|
|
3130
|
-
expect(derived.account).toBe(initialEnvelope.account)
|
|
3131
|
-
expect(() =>
|
|
3132
|
-
SignatureEnvelope.from({
|
|
3133
|
-
config: currentEnvelope.config,
|
|
3134
|
-
signatures: currentEnvelope.signatures,
|
|
3135
|
-
} as never),
|
|
3136
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3137
|
-
`[MultisigConfig.InvalidConfigError: Invalid native multisig config: account address requires version zero.]`,
|
|
3138
|
-
)
|
|
3139
|
-
})
|
|
3140
|
-
|
|
3141
|
-
test('behavior: extracts the multisig account', () => {
|
|
3142
|
-
const envelope = SignatureEnvelope.deserialize(initial)
|
|
3143
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3144
|
-
|
|
3145
|
-
expect(
|
|
3146
|
-
SignatureEnvelope.extractAddress({
|
|
3147
|
-
payload: '0xdeadbeef',
|
|
3148
|
-
signature: envelope,
|
|
3149
|
-
}),
|
|
3150
|
-
).toBe(envelope.account)
|
|
3151
|
-
})
|
|
3152
|
-
|
|
3153
|
-
test('example: matches the frozen eight-approval boundary vector', () => {
|
|
3154
|
-
const config = MultisigConfig.from({
|
|
3155
|
-
owners: Array.from({ length: 8 }, (_, index) => ({
|
|
3156
|
-
owner:
|
|
3157
|
-
`0x${(index + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
|
|
3158
|
-
weight: 1,
|
|
3159
|
-
})),
|
|
3160
|
-
salt: `0x${'66'.repeat(32)}`,
|
|
3161
|
-
threshold: 8,
|
|
3162
|
-
})
|
|
3163
|
-
const approval = SignatureEnvelope.from({
|
|
3164
|
-
r: '0x840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565',
|
|
3165
|
-
s: '0x25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1',
|
|
3166
|
-
yParity: 0,
|
|
3167
|
-
})
|
|
3168
|
-
const envelope = SignatureEnvelope.from({
|
|
3169
|
-
config,
|
|
3170
|
-
signatures: Array.from(
|
|
3171
|
-
{ length: MultisigConfig.maxSignatures },
|
|
3172
|
-
() => approval,
|
|
3173
|
-
),
|
|
3174
|
-
})
|
|
3175
|
-
const serialized = SignatureEnvelope.serialize(envelope)
|
|
3176
|
-
|
|
3177
|
-
expect({
|
|
3178
|
-
hash: Hash.keccak256(serialized),
|
|
3179
|
-
length: Hex.size(serialized),
|
|
3180
|
-
signatureCount: envelope.signatures.length,
|
|
3181
|
-
}).toMatchInlineSnapshot(`
|
|
3182
|
-
{
|
|
3183
|
-
"hash": "0x1cb47fcf31fb1ebb3177ec1744eeda195e4d789920d1214af32baaa6150ad5e2",
|
|
3184
|
-
"length": 787,
|
|
3185
|
-
"signatureCount": 8,
|
|
3186
|
-
}
|
|
3187
|
-
`)
|
|
3188
|
-
})
|
|
3189
|
-
|
|
3190
|
-
test('behavior: accepts initial and current nested signatures', () => {
|
|
3191
|
-
const initialEnvelope = SignatureEnvelope.deserialize(initial)
|
|
3192
|
-
const currentEnvelope = SignatureEnvelope.deserialize(current)
|
|
3193
|
-
if (
|
|
3194
|
-
initialEnvelope.type !== 'multisig' ||
|
|
3195
|
-
currentEnvelope.type !== 'multisig'
|
|
3196
|
-
)
|
|
3197
|
-
throw new Error('unreachable')
|
|
3198
|
-
|
|
3199
|
-
for (const signature of [initialEnvelope, currentEnvelope]) {
|
|
3200
|
-
const config = MultisigConfig.from({
|
|
3201
|
-
owners: [{ owner: signature.account, weight: 1 }],
|
|
3202
|
-
salt: ('0x' + 'ff'.repeat(32)) as Hex.Hex,
|
|
3203
|
-
threshold: 1,
|
|
3204
|
-
version: 1n,
|
|
3205
|
-
})
|
|
3206
|
-
const envelope = SignatureEnvelope.from({
|
|
3207
|
-
account: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
3208
|
-
config,
|
|
3209
|
-
signatures: [signature],
|
|
3210
|
-
})
|
|
3211
|
-
|
|
3212
|
-
expect(
|
|
3213
|
-
SignatureEnvelope.deserialize(SignatureEnvelope.serialize(envelope)),
|
|
3214
|
-
).toStrictEqual(envelope)
|
|
3215
|
-
}
|
|
3216
|
-
})
|
|
3217
|
-
|
|
3218
|
-
test('error: rejects an initial account mismatch', () => {
|
|
3219
|
-
const envelope = SignatureEnvelope.deserialize(initial)
|
|
3220
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3221
|
-
|
|
3222
|
-
expect(() =>
|
|
3223
|
-
SignatureEnvelope.assert({
|
|
3224
|
-
...envelope,
|
|
3225
|
-
account: '0x1111111111111111111111111111111111111111',
|
|
3226
|
-
}),
|
|
3227
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3228
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: initial multisig config does not derive account.]`,
|
|
3229
|
-
)
|
|
3230
|
-
})
|
|
3231
|
-
|
|
3232
|
-
test('error: rejects a self-owned current signature', () => {
|
|
3233
|
-
const envelope = SignatureEnvelope.deserialize(current)
|
|
3234
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3235
|
-
const account = envelope.config.owners[0]!.owner
|
|
3236
|
-
|
|
3237
|
-
expect(() =>
|
|
3238
|
-
SignatureEnvelope.assert({ ...envelope, account }),
|
|
3239
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3240
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig account cannot be an owner.]`,
|
|
3241
|
-
)
|
|
3242
|
-
})
|
|
3243
|
-
|
|
3244
|
-
test('error: rejects empty and excess approvals', () => {
|
|
3245
|
-
const envelope = SignatureEnvelope.deserialize(current)
|
|
3246
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3247
|
-
|
|
3248
|
-
expect(() =>
|
|
3249
|
-
SignatureEnvelope.assert({ ...envelope, signatures: [] }),
|
|
3250
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3251
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig signatures cannot be empty.]`,
|
|
3252
|
-
)
|
|
3253
|
-
expect(() =>
|
|
3254
|
-
SignatureEnvelope.assert({
|
|
3255
|
-
...envelope,
|
|
3256
|
-
signatures: Array.from(
|
|
3257
|
-
{ length: MultisigConfig.maxSignatures + 1 },
|
|
3258
|
-
() => envelope.signatures[0]!,
|
|
3259
|
-
),
|
|
3260
|
-
}),
|
|
3261
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3262
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig signatures exceed 8.]`,
|
|
3263
|
-
)
|
|
3264
|
-
})
|
|
3265
|
-
|
|
3266
|
-
test('error: rejects keychain owner approvals', () => {
|
|
3267
|
-
const envelope = SignatureEnvelope.deserialize(current)
|
|
3268
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3269
|
-
|
|
3270
|
-
expect(() =>
|
|
3271
|
-
SignatureEnvelope.assert({
|
|
3272
|
-
...envelope,
|
|
3273
|
-
signatures: [signature_keychain_secp256k1],
|
|
3274
|
-
} as never),
|
|
3275
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3276
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: keychain owner approvals are not allowed.]`,
|
|
3277
|
-
)
|
|
3278
|
-
})
|
|
3279
|
-
|
|
3280
|
-
test('error: rejects oversized primitive approvals', () => {
|
|
3281
|
-
const envelope = SignatureEnvelope.deserialize(current)
|
|
3282
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3283
|
-
const oversized = {
|
|
3284
|
-
...signature_webauthn,
|
|
3285
|
-
metadata: {
|
|
3286
|
-
...signature_webauthn.metadata,
|
|
3287
|
-
clientDataJSON: JSON.stringify({ value: 'x'.repeat(2048) }),
|
|
3288
|
-
},
|
|
3289
|
-
}
|
|
3290
|
-
|
|
3291
|
-
expect(() =>
|
|
3292
|
-
SignatureEnvelope.assert({
|
|
3293
|
-
...envelope,
|
|
3294
|
-
signatures: [oversized],
|
|
3295
|
-
}),
|
|
3296
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3297
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig owner signature exceeds 2049 bytes.]`,
|
|
3298
|
-
)
|
|
3299
|
-
})
|
|
3300
|
-
|
|
3301
|
-
test('error: rejects nesting deeper than two nodes', () => {
|
|
3302
|
-
const envelope = SignatureEnvelope.deserialize(initial)
|
|
3303
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3304
|
-
const depth2 = SignatureEnvelope.from({
|
|
3305
|
-
...envelope,
|
|
3306
|
-
signatures: [envelope],
|
|
3307
|
-
})
|
|
3308
|
-
|
|
3309
|
-
expect(() =>
|
|
3310
|
-
SignatureEnvelope.assert({
|
|
3311
|
-
...envelope,
|
|
3312
|
-
signatures: [depth2],
|
|
3313
|
-
}),
|
|
3314
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3315
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig nesting depth exceeds 2.]`,
|
|
3316
|
-
)
|
|
3317
|
-
})
|
|
3318
|
-
|
|
3319
|
-
test.each([
|
|
3320
|
-
{
|
|
3321
|
-
name: 'two fields',
|
|
3322
|
-
value: Rlp.fromHex([
|
|
3323
|
-
'0x9dba7f426b711d4893c11611eacf7cc334e7146b',
|
|
3324
|
-
[Hex.slice(initial, -65)],
|
|
3325
|
-
]),
|
|
3326
|
-
},
|
|
3327
|
-
{
|
|
3328
|
-
name: 'trailing field',
|
|
3329
|
-
value: Rlp.fromHex([
|
|
3330
|
-
'0x9dba7f426b711d4893c11611eacf7cc334e7146b',
|
|
3331
|
-
MultisigConfig.toTuple(
|
|
3332
|
-
MultisigConfig.from({
|
|
3333
|
-
owners: [
|
|
3334
|
-
{
|
|
3335
|
-
owner: '0x7e5f4552091a69125d5dfcb7b8c2659029395bdf',
|
|
3336
|
-
weight: 1,
|
|
3337
|
-
},
|
|
3338
|
-
],
|
|
3339
|
-
threshold: 1,
|
|
3340
|
-
}),
|
|
3341
|
-
),
|
|
3342
|
-
[Hex.slice(initial, -65)],
|
|
3343
|
-
'0x',
|
|
3344
|
-
]),
|
|
3345
|
-
},
|
|
3346
|
-
])('error: rejects malformed $name wire shape', ({ value }) => {
|
|
3347
|
-
expect(() =>
|
|
3348
|
-
SignatureEnvelope.deserialize(Hex.concat('0x05', value)),
|
|
3349
|
-
).toThrowError(SignatureEnvelope.InvalidSerializedError)
|
|
3350
|
-
})
|
|
3351
|
-
|
|
3352
|
-
test.each([
|
|
3353
|
-
{
|
|
3354
|
-
field: 'salt',
|
|
3355
|
-
value: [
|
|
3356
|
-
('0x' + '00'.repeat(31)) as Hex.Hex,
|
|
3357
|
-
'0x',
|
|
3358
|
-
'0x01',
|
|
3359
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3360
|
-
],
|
|
3361
|
-
},
|
|
3362
|
-
{
|
|
3363
|
-
field: 'oversized version',
|
|
3364
|
-
value: [
|
|
3365
|
-
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3366
|
-
('0x' + '01'.repeat(9)) as Hex.Hex,
|
|
3367
|
-
'0x01',
|
|
3368
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3369
|
-
],
|
|
3370
|
-
},
|
|
3371
|
-
{
|
|
3372
|
-
field: 'zero version',
|
|
3373
|
-
value: [
|
|
3374
|
-
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3375
|
-
'0x00',
|
|
3376
|
-
'0x01',
|
|
3377
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3378
|
-
],
|
|
3379
|
-
},
|
|
3380
|
-
{
|
|
3381
|
-
field: 'zero-prefixed version',
|
|
3382
|
-
value: [
|
|
3383
|
-
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3384
|
-
'0x0001',
|
|
3385
|
-
'0x01',
|
|
3386
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3387
|
-
],
|
|
3388
|
-
},
|
|
3389
|
-
{
|
|
3390
|
-
field: 'threshold',
|
|
3391
|
-
value: [
|
|
3392
|
-
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3393
|
-
'0x',
|
|
3394
|
-
'0x0001',
|
|
3395
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3396
|
-
],
|
|
3397
|
-
},
|
|
3398
|
-
{
|
|
3399
|
-
field: 'weight',
|
|
3400
|
-
value: [
|
|
3401
|
-
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3402
|
-
'0x',
|
|
3403
|
-
'0x01',
|
|
3404
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x0001']],
|
|
3405
|
-
],
|
|
3406
|
-
},
|
|
3407
|
-
])('error: rejects a noncanonical $field', ({ value }) => {
|
|
3408
|
-
const malformed = Hex.concat(
|
|
3409
|
-
'0x05',
|
|
3410
|
-
Rlp.fromHex([
|
|
3411
|
-
'0x9dba7f426b711d4893c11611eacf7cc334e7146b',
|
|
3412
|
-
value as never,
|
|
3413
|
-
[Hex.slice(initial, -65)],
|
|
3414
|
-
]),
|
|
3415
|
-
)
|
|
3416
|
-
|
|
3417
|
-
expect(() => SignatureEnvelope.deserialize(malformed)).toThrowError(
|
|
3418
|
-
SignatureEnvelope.InvalidSerializedError,
|
|
3419
|
-
)
|
|
3420
|
-
})
|
|
3421
3208
|
})
|