ox 1.7.2 → 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 +43 -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 +43 -39
- 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 +45 -28
- package/dist/tempo/MultisigOperation.d.ts.map +1 -1
- package/dist/tempo/MultisigOperation.js +81 -131
- package/dist/tempo/MultisigOperation.js.map +1 -1
- package/dist/tempo/MultisigSimulation.d.ts +85 -0
- package/dist/tempo/MultisigSimulation.d.ts.map +1 -0
- package/dist/tempo/MultisigSimulation.js +112 -0
- package/dist/tempo/MultisigSimulation.js.map +1 -0
- package/dist/tempo/RpcSchemaTempo.d.ts +20 -10
- package/dist/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/tempo/SignatureEnvelope.d.ts +74 -153
- package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/tempo/SignatureEnvelope.js +130 -187
- package/dist/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/tempo/TransactionRequest.d.ts +9 -7
- package/dist/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/tempo/TransactionRequest.js +12 -7
- package/dist/tempo/TransactionRequest.js.map +1 -1
- package/dist/tempo/index.d.ts +7 -5
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +7 -5
- package/dist/tempo/index.js.map +1 -1
- package/dist/zod/tempo/AuthorizationTempo.d.ts +360 -140
- package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.d.ts +247 -487
- 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/MultisigOperation.d.ts +0 -10
- package/dist/zod/tempo/MultisigOperation.d.ts.map +1 -1
- package/dist/zod/tempo/MultisigOperation.js +0 -3
- package/dist/zod/tempo/MultisigOperation.js.map +1 -1
- package/dist/zod/tempo/MultisigSimulation.d.ts +52 -0
- package/dist/zod/tempo/MultisigSimulation.d.ts.map +1 -0
- package/dist/zod/tempo/MultisigSimulation.js +44 -0
- package/dist/zod/tempo/MultisigSimulation.js.map +1 -0
- package/dist/zod/tempo/RpcSchemaTempo.d.ts +357 -422
- package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/zod/tempo/RpcSchemaTempo.js +19 -10
- package/dist/zod/tempo/RpcSchemaTempo.js.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.d.ts +108 -42
- package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.js +21 -19
- package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/zod/tempo/Transaction.d.ts +678 -666
- package/dist/zod/tempo/Transaction.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.d.ts +580 -619
- package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.js +18 -10
- 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/dist/zod/tempo/z.d.ts +1 -1
- package/dist/zod/tempo/z.d.ts.map +1 -1
- package/dist/zod/tempo/z.js +1 -1
- package/dist/zod/tempo/z.js.map +1 -1
- package/package.json +9 -9
- 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 -0
- package/src/tempo/KeyAuthorization.test-d.ts +16 -74
- package/src/tempo/KeyAuthorization.test.ts +113 -14
- package/src/tempo/KeyAuthorization.ts +110 -66
- package/src/tempo/MultisigConfig.test.ts +44 -20
- package/src/tempo/MultisigConfig.ts +65 -23
- package/src/tempo/MultisigOperation.test-d.ts +8 -6
- package/src/tempo/MultisigOperation.test.ts +187 -591
- package/src/tempo/MultisigOperation.ts +129 -205
- package/src/tempo/MultisigSimulation.test-d.ts +33 -0
- package/src/tempo/MultisigSimulation.test.ts +194 -0
- package/src/tempo/MultisigSimulation.ts +192 -0
- package/src/tempo/RpcSchemaTempo.test-d.ts +45 -10
- package/src/tempo/RpcSchemaTempo.ts +15 -6
- package/src/tempo/SignatureEnvelope.test-d.ts +27 -101
- package/src/tempo/SignatureEnvelope.test.ts +330 -533
- package/src/tempo/SignatureEnvelope.ts +198 -310
- package/src/tempo/Transaction.test.ts +61 -0
- package/src/tempo/TransactionRequest.test-d.ts +7 -7
- package/src/tempo/TransactionRequest.test.ts +81 -223
- package/src/tempo/TransactionRequest.ts +34 -13
- package/src/tempo/index.ts +7 -5
- package/src/tempo/multisig.e2e.test.ts +329 -818
- package/src/version.ts +1 -1
- package/src/zod/tempo/KeyAuthorization.ts +50 -39
- package/src/zod/tempo/MultisigOperation.ts +0 -3
- package/src/zod/tempo/MultisigSimulation.ts +53 -0
- package/src/zod/tempo/RpcSchemaTempo.ts +20 -10
- package/src/zod/tempo/SignatureEnvelope.ts +46 -59
- package/src/zod/tempo/TransactionRequest.ts +28 -14
- package/src/zod/tempo/_test/KeyAuthorization.test.ts +66 -30
- package/src/zod/tempo/_test/MultisigOperation.test.ts +0 -2
- package/src/zod/tempo/_test/MultisigSimulation.test-d.ts +13 -0
- package/src/zod/tempo/_test/MultisigSimulation.test.ts +53 -0
- package/src/zod/tempo/_test/RpcSchemaTempo.test-d.ts +19 -2
- package/src/zod/tempo/_test/RpcSchemaTempo.test.ts +46 -4
- package/src/zod/tempo/_test/SignatureEnvelope.test.ts +108 -129
- package/src/zod/tempo/_test/TransactionRequest.test.ts +17 -168
- package/src/zod/tempo/z.ts +1 -1
- package/dist/tempo/MultisigWitness.d.ts +0 -106
- package/dist/tempo/MultisigWitness.d.ts.map +0 -1
- package/dist/tempo/MultisigWitness.js +0 -118
- package/dist/tempo/MultisigWitness.js.map +0 -1
- package/dist/zod/tempo/MultisigWitness.d.ts +0 -152
- package/dist/zod/tempo/MultisigWitness.d.ts.map +0 -1
- package/dist/zod/tempo/MultisigWitness.js +0 -76
- package/dist/zod/tempo/MultisigWitness.js.map +0 -1
- package/src/tempo/MultisigWitness.test-d.ts +0 -42
- package/src/tempo/MultisigWitness.test.ts +0 -288
- package/src/tempo/MultisigWitness.ts +0 -210
- package/src/zod/tempo/MultisigWitness.ts +0 -101
- package/src/zod/tempo/_test/MultisigWitness.test-d.ts +0 -13
- package/src/zod/tempo/_test/MultisigWitness.test.ts +0 -149
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Address,
|
|
3
|
-
Hash,
|
|
4
3
|
Hex,
|
|
5
4
|
P256,
|
|
6
5
|
PublicKey,
|
|
@@ -1117,86 +1116,30 @@ describe('from', () => {
|
|
|
1117
1116
|
})
|
|
1118
1117
|
|
|
1119
1118
|
describe('multisig', () => {
|
|
1120
|
-
|
|
1121
|
-
owners: [
|
|
1122
|
-
{
|
|
1123
|
-
owner: '0x1111111111111111111111111111111111111111',
|
|
1124
|
-
weight: 1,
|
|
1125
|
-
},
|
|
1126
|
-
],
|
|
1127
|
-
threshold: 1,
|
|
1128
|
-
})
|
|
1129
|
-
|
|
1130
|
-
test('behavior: derives an initial account from config', () => {
|
|
1119
|
+
test('normalizes the current config', () => {
|
|
1131
1120
|
const envelope = SignatureEnvelope.from({
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
{
|
|
1138
|
-
signatures: [
|
|
1139
|
-
{
|
|
1140
|
-
signature: {
|
|
1141
|
-
r: expect.anything(),
|
|
1142
|
-
s: expect.anything(),
|
|
1143
|
-
yParity: expect.any(Number),
|
|
1144
|
-
},
|
|
1145
|
-
},
|
|
1121
|
+
account: '0x2222222222222222222222222222222222222222',
|
|
1122
|
+
config: {
|
|
1123
|
+
threshold: 1,
|
|
1124
|
+
owners: [
|
|
1125
|
+
{ owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
1146
1126
|
],
|
|
1147
1127
|
},
|
|
1148
|
-
|
|
1128
|
+
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
1129
|
+
})
|
|
1130
|
+
expect(envelope.config).toMatchInlineSnapshot(`
|
|
1149
1131
|
{
|
|
1150
|
-
"
|
|
1151
|
-
"config": {
|
|
1152
|
-
"owners": [
|
|
1153
|
-
{
|
|
1154
|
-
"owner": "0x1111111111111111111111111111111111111111",
|
|
1155
|
-
"weight": 1,
|
|
1156
|
-
},
|
|
1157
|
-
],
|
|
1158
|
-
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
1159
|
-
"threshold": 1,
|
|
1160
|
-
"version": 0n,
|
|
1161
|
-
},
|
|
1162
|
-
"signatures": [
|
|
1132
|
+
"owners": [
|
|
1163
1133
|
{
|
|
1164
|
-
"
|
|
1165
|
-
|
|
1166
|
-
"s": Anything,
|
|
1167
|
-
"yParity": Any<Number>,
|
|
1168
|
-
},
|
|
1169
|
-
"type": "secp256k1",
|
|
1134
|
+
"owner": "0x1111111111111111111111111111111111111111",
|
|
1135
|
+
"weight": 1,
|
|
1170
1136
|
},
|
|
1171
1137
|
],
|
|
1172
|
-
"
|
|
1138
|
+
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
1139
|
+
"threshold": 1,
|
|
1140
|
+
"version": 0n,
|
|
1173
1141
|
}
|
|
1174
|
-
|
|
1175
|
-
)
|
|
1176
|
-
})
|
|
1177
|
-
|
|
1178
|
-
test('behavior: accepts an explicit account for a current config', () => {
|
|
1179
|
-
const envelope = SignatureEnvelope.from({
|
|
1180
|
-
account: '0x2222222222222222222222222222222222222222',
|
|
1181
|
-
config: { ...config, version: 1 },
|
|
1182
|
-
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
1183
|
-
})
|
|
1184
|
-
|
|
1185
|
-
expect(envelope.account).toBe(
|
|
1186
|
-
'0x2222222222222222222222222222222222222222',
|
|
1187
|
-
)
|
|
1188
|
-
expect(envelope.config.version).toBe(1n)
|
|
1189
|
-
})
|
|
1190
|
-
|
|
1191
|
-
test('error: requires an account for a current config', () => {
|
|
1192
|
-
expect(() =>
|
|
1193
|
-
SignatureEnvelope.from({
|
|
1194
|
-
config: { ...config, version: 1n },
|
|
1195
|
-
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
1196
|
-
} as never),
|
|
1197
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
1198
|
-
`[MultisigConfig.InvalidConfigError: Invalid native multisig config: account address requires version zero.]`,
|
|
1199
|
-
)
|
|
1142
|
+
`)
|
|
1200
1143
|
})
|
|
1201
1144
|
})
|
|
1202
1145
|
})
|
|
@@ -1808,6 +1751,8 @@ describe('serialize', () => {
|
|
|
1808
1751
|
})
|
|
1809
1752
|
|
|
1810
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.
|
|
1811
1756
|
const ownerKeys = Array.from({ length: 3 }, () => {
|
|
1812
1757
|
const privateKey = Secp256k1.randomPrivateKey()
|
|
1813
1758
|
const address = Address.fromPublicKey(
|
|
@@ -1819,16 +1764,19 @@ describe('sortMultisigApprovals', () => {
|
|
|
1819
1764
|
Hex.toBigInt(a.address) < Hex.toBigInt(b.address) ? -1 : 1,
|
|
1820
1765
|
)
|
|
1821
1766
|
|
|
1822
|
-
const
|
|
1823
|
-
owners: ascendingOwners.map((o) => ({ owner: o.address, weight: 1 })),
|
|
1767
|
+
const genesisConfig = MultisigConfig.from({
|
|
1824
1768
|
threshold: 2,
|
|
1769
|
+
owners: ascendingOwners.map((o) => ({ owner: o.address, weight: 1 })),
|
|
1825
1770
|
})
|
|
1826
|
-
const account = MultisigConfig.getAddress(config)
|
|
1827
1771
|
const payload = `0x${'42'.repeat(32)}` as const
|
|
1772
|
+
const account = MultisigConfig.getAddress(genesisConfig, {
|
|
1773
|
+
factory: '0x7171717171717171717171717171717171717171',
|
|
1774
|
+
})
|
|
1775
|
+
const version = 3n
|
|
1828
1776
|
const digest = MultisigConfig.getSignPayload({
|
|
1829
|
-
account,
|
|
1830
|
-
config,
|
|
1831
1777
|
payload,
|
|
1778
|
+
account,
|
|
1779
|
+
config: { version },
|
|
1832
1780
|
})
|
|
1833
1781
|
|
|
1834
1782
|
const owners = ownerKeys.map((owner) => ({
|
|
@@ -1845,7 +1793,7 @@ describe('sortMultisigApprovals', () => {
|
|
|
1845
1793
|
test('behavior: orders approvals ascending by recovered owner address', () => {
|
|
1846
1794
|
const ordered = SignatureEnvelope.sortMultisigApprovals({
|
|
1847
1795
|
account,
|
|
1848
|
-
config,
|
|
1796
|
+
config: { version },
|
|
1849
1797
|
payload,
|
|
1850
1798
|
// Provide approvals in reverse of the canonical order.
|
|
1851
1799
|
signatures: [...ascending].reverse().map((owner) => owner.signature),
|
|
@@ -1858,7 +1806,7 @@ describe('sortMultisigApprovals', () => {
|
|
|
1858
1806
|
expect(
|
|
1859
1807
|
SignatureEnvelope.sortMultisigApprovals({
|
|
1860
1808
|
account,
|
|
1861
|
-
config,
|
|
1809
|
+
config: { version },
|
|
1862
1810
|
payload,
|
|
1863
1811
|
signatures,
|
|
1864
1812
|
}),
|
|
@@ -1868,7 +1816,7 @@ describe('sortMultisigApprovals', () => {
|
|
|
1868
1816
|
test('behavior: recovered order matches the config owner order', () => {
|
|
1869
1817
|
const ordered = SignatureEnvelope.sortMultisigApprovals({
|
|
1870
1818
|
account,
|
|
1871
|
-
config,
|
|
1819
|
+
config: { version },
|
|
1872
1820
|
payload,
|
|
1873
1821
|
signatures: owners.map((owner) => owner.signature),
|
|
1874
1822
|
})
|
|
@@ -2592,6 +2540,8 @@ describe('toRpc', () => {
|
|
|
2592
2540
|
|
|
2593
2541
|
expect(rpc.type).toBe('keychain')
|
|
2594
2542
|
expect(rpc.userAddress).toBe(signature_keychain_p256.userAddress)
|
|
2543
|
+
if (typeof rpc.signature === 'string')
|
|
2544
|
+
throw new Error('Expected structured primitive signature')
|
|
2595
2545
|
expect(rpc.signature.type).toBe('p256')
|
|
2596
2546
|
expect(typeof rpc.signature.pubKeyX).toBe('string')
|
|
2597
2547
|
expect(typeof rpc.signature.pubKeyY).toBe('string')
|
|
@@ -2604,6 +2554,8 @@ describe('toRpc', () => {
|
|
|
2604
2554
|
|
|
2605
2555
|
expect(rpc.type).toBe('keychain')
|
|
2606
2556
|
expect(rpc.userAddress).toBe(signature_keychain_webauthn.userAddress)
|
|
2557
|
+
if (typeof rpc.signature === 'string')
|
|
2558
|
+
throw new Error('Expected structured primitive signature')
|
|
2607
2559
|
expect(rpc.signature.type).toBe('webAuthn')
|
|
2608
2560
|
expect(typeof rpc.signature.pubKeyX).toBe('string')
|
|
2609
2561
|
expect(typeof rpc.signature.webauthnData).toBe('string')
|
|
@@ -2881,19 +2833,308 @@ describe('CoercionError', () => {
|
|
|
2881
2833
|
})
|
|
2882
2834
|
|
|
2883
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', () => {
|
|
2884
3127
|
const initial =
|
|
2885
|
-
'
|
|
3128
|
+
'0x05f89794c4a590afa7337e5cd5eb3aa60cacf91c5400044bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b' as const
|
|
2886
3129
|
const current =
|
|
2887
|
-
'
|
|
2888
|
-
const nested =
|
|
2889
|
-
'0x05f8f0949969e2243075b27a8eab61009c59b77ab13b83f6f83ba033333333333333333333333333333333333333333333333333333333333333338001d7d6944f9f5b162a7464bcc260ce44d7ae0d935f9c583701f89cb89a05f897944f9f5b162a7464bcc260ce44d7ae0d935f9c5837f83ba022222222222222222222222222222222222222222222222222222222222222228001d7d6946813eb9362372eef6200f3b1dbc3f819671cba6901f843b841032aa6f3ea7b0b7069720d0f3891983c493d149326c5c957d864ed7371b8475e3e95325079b24491f4b6d68920e4b3bacd7c6094df6ed88b8674864ab559c8fb1b' as const
|
|
3130
|
+
'0x05f89794c4a590afa7337e5cd5eb3aa60cacf91c5400044bf83ba000000000000000000000000000000000000000000000000000000000000000000101d7d6942b5ad5c4795c026514f8317c7a215e218dccd6cf01f843b8414a0e5b5b4a90f08e6e8d676a73a22dc2cf022ffdcc9299512b5d9a6daf66e0504a395a2ef6f6c0f173910b875c45d01aa66d928e56ba6f49bbc8186f80268bf51b' as const
|
|
2890
3131
|
|
|
2891
|
-
test('example: decodes and re-encodes the frozen initial
|
|
3132
|
+
test('example: decodes and re-encodes the frozen initial signature', () => {
|
|
2892
3133
|
const envelope = SignatureEnvelope.deserialize(initial)
|
|
2893
3134
|
|
|
2894
3135
|
expect(envelope).toMatchInlineSnapshot(`
|
|
2895
3136
|
{
|
|
2896
|
-
"account": "
|
|
3137
|
+
"account": "0xc4a590afa7337e5cd5eb3aa60cacf91c5400044b",
|
|
2897
3138
|
"config": {
|
|
2898
3139
|
"owners": [
|
|
2899
3140
|
{
|
|
@@ -2921,12 +3162,12 @@ describe('multisig', () => {
|
|
|
2921
3162
|
expect(SignatureEnvelope.serialize(envelope)).toBe(initial)
|
|
2922
3163
|
})
|
|
2923
3164
|
|
|
2924
|
-
test('example: decodes and re-encodes the frozen current
|
|
3165
|
+
test('example: decodes and re-encodes the frozen current signature', () => {
|
|
2925
3166
|
const envelope = SignatureEnvelope.deserialize(current)
|
|
2926
3167
|
|
|
2927
3168
|
expect(envelope).toMatchInlineSnapshot(`
|
|
2928
3169
|
{
|
|
2929
|
-
"account": "
|
|
3170
|
+
"account": "0xc4a590afa7337e5cd5eb3aa60cacf91c5400044b",
|
|
2930
3171
|
"config": {
|
|
2931
3172
|
"owners": [
|
|
2932
3173
|
{
|
|
@@ -2954,56 +3195,6 @@ describe('multisig', () => {
|
|
|
2954
3195
|
expect(SignatureEnvelope.serialize(envelope)).toBe(current)
|
|
2955
3196
|
})
|
|
2956
3197
|
|
|
2957
|
-
test('example: decodes and re-encodes the frozen nested witness', () => {
|
|
2958
|
-
const envelope = SignatureEnvelope.deserialize(nested)
|
|
2959
|
-
|
|
2960
|
-
expect(envelope).toMatchInlineSnapshot(`
|
|
2961
|
-
{
|
|
2962
|
-
"account": "0x9969e2243075b27a8eab61009c59b77ab13b83f6",
|
|
2963
|
-
"config": {
|
|
2964
|
-
"owners": [
|
|
2965
|
-
{
|
|
2966
|
-
"owner": "0x4f9f5b162a7464bcc260ce44d7ae0d935f9c5837",
|
|
2967
|
-
"weight": 1,
|
|
2968
|
-
},
|
|
2969
|
-
],
|
|
2970
|
-
"salt": "0x3333333333333333333333333333333333333333333333333333333333333333",
|
|
2971
|
-
"threshold": 1,
|
|
2972
|
-
"version": 0n,
|
|
2973
|
-
},
|
|
2974
|
-
"signatures": [
|
|
2975
|
-
{
|
|
2976
|
-
"account": "0x4f9f5b162a7464bcc260ce44d7ae0d935f9c5837",
|
|
2977
|
-
"config": {
|
|
2978
|
-
"owners": [
|
|
2979
|
-
{
|
|
2980
|
-
"owner": "0x6813eb9362372eef6200f3b1dbc3f819671cba69",
|
|
2981
|
-
"weight": 1,
|
|
2982
|
-
},
|
|
2983
|
-
],
|
|
2984
|
-
"salt": "0x2222222222222222222222222222222222222222222222222222222222222222",
|
|
2985
|
-
"threshold": 1,
|
|
2986
|
-
"version": 0n,
|
|
2987
|
-
},
|
|
2988
|
-
"signatures": [
|
|
2989
|
-
{
|
|
2990
|
-
"signature": {
|
|
2991
|
-
"r": "0x032aa6f3ea7b0b7069720d0f3891983c493d149326c5c957d864ed7371b8475e",
|
|
2992
|
-
"s": "0x3e95325079b24491f4b6d68920e4b3bacd7c6094df6ed88b8674864ab559c8fb",
|
|
2993
|
-
"yParity": 0,
|
|
2994
|
-
},
|
|
2995
|
-
"type": "secp256k1",
|
|
2996
|
-
},
|
|
2997
|
-
],
|
|
2998
|
-
"type": "multisig",
|
|
2999
|
-
},
|
|
3000
|
-
],
|
|
3001
|
-
"type": "multisig",
|
|
3002
|
-
}
|
|
3003
|
-
`)
|
|
3004
|
-
expect(SignatureEnvelope.serialize(envelope)).toBe(nested)
|
|
3005
|
-
})
|
|
3006
|
-
|
|
3007
3198
|
test('behavior: preserves exact 65-byte secp256k1 precedence', () => {
|
|
3008
3199
|
const serialized = ('0x05' +
|
|
3009
3200
|
'00'.repeat(31) +
|
|
@@ -3014,398 +3205,4 @@ describe('multisig', () => {
|
|
|
3014
3205
|
expect(Hex.size(serialized)).toBe(65)
|
|
3015
3206
|
expect(SignatureEnvelope.deserialize(serialized).type).toBe('secp256k1')
|
|
3016
3207
|
})
|
|
3017
|
-
|
|
3018
|
-
test('behavior: serializes account, config, and approvals in wire order', () => {
|
|
3019
|
-
const envelope = SignatureEnvelope.deserialize(initial)
|
|
3020
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3021
|
-
const serialized = SignatureEnvelope.serialize(envelope)
|
|
3022
|
-
const decoded = Rlp.toHex(Hex.slice(serialized, 1))
|
|
3023
|
-
|
|
3024
|
-
expect(decoded).toMatchInlineSnapshot(`
|
|
3025
|
-
[
|
|
3026
|
-
"0x9dba7f426b711d4893c11611eacf7cc334e7146b",
|
|
3027
|
-
[
|
|
3028
|
-
"0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
3029
|
-
"0x",
|
|
3030
|
-
"0x01",
|
|
3031
|
-
[
|
|
3032
|
-
[
|
|
3033
|
-
"0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
|
|
3034
|
-
"0x01",
|
|
3035
|
-
],
|
|
3036
|
-
],
|
|
3037
|
-
],
|
|
3038
|
-
[
|
|
3039
|
-
"0x869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b",
|
|
3040
|
-
],
|
|
3041
|
-
]
|
|
3042
|
-
`)
|
|
3043
|
-
})
|
|
3044
|
-
|
|
3045
|
-
test('behavior: round trips initial and current RPC witnesses', () => {
|
|
3046
|
-
const initialEnvelope = SignatureEnvelope.deserialize(initial)
|
|
3047
|
-
const currentEnvelope = SignatureEnvelope.deserialize(current)
|
|
3048
|
-
const rpc = {
|
|
3049
|
-
current: SignatureEnvelope.toRpc(currentEnvelope),
|
|
3050
|
-
initial: SignatureEnvelope.toRpc(initialEnvelope),
|
|
3051
|
-
}
|
|
3052
|
-
|
|
3053
|
-
expect(rpc).toMatchInlineSnapshot(`
|
|
3054
|
-
{
|
|
3055
|
-
"current": {
|
|
3056
|
-
"account": "0x9dba7f426b711d4893c11611eacf7cc334e7146b",
|
|
3057
|
-
"config": {
|
|
3058
|
-
"owners": [
|
|
3059
|
-
{
|
|
3060
|
-
"owner": "0x2b5ad5c4795c026514f8317c7a215e218dccd6cf",
|
|
3061
|
-
"weight": 1,
|
|
3062
|
-
},
|
|
3063
|
-
],
|
|
3064
|
-
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
3065
|
-
"threshold": 1,
|
|
3066
|
-
"version": "0x1",
|
|
3067
|
-
},
|
|
3068
|
-
"signatures": [
|
|
3069
|
-
{
|
|
3070
|
-
"r": "0x4a0e5b5b4a90f08e6e8d676a73a22dc2cf022ffdcc9299512b5d9a6daf66e050",
|
|
3071
|
-
"s": "0x4a395a2ef6f6c0f173910b875c45d01aa66d928e56ba6f49bbc8186f80268bf5",
|
|
3072
|
-
"type": "secp256k1",
|
|
3073
|
-
"yParity": "0x0",
|
|
3074
|
-
},
|
|
3075
|
-
],
|
|
3076
|
-
},
|
|
3077
|
-
"initial": {
|
|
3078
|
-
"account": "0x9dba7f426b711d4893c11611eacf7cc334e7146b",
|
|
3079
|
-
"config": {
|
|
3080
|
-
"owners": [
|
|
3081
|
-
{
|
|
3082
|
-
"owner": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
|
|
3083
|
-
"weight": 1,
|
|
3084
|
-
},
|
|
3085
|
-
],
|
|
3086
|
-
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
3087
|
-
"threshold": 1,
|
|
3088
|
-
"version": "0x0",
|
|
3089
|
-
},
|
|
3090
|
-
"signatures": [
|
|
3091
|
-
{
|
|
3092
|
-
"r": "0x869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f985",
|
|
3093
|
-
"s": "0x35dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f4",
|
|
3094
|
-
"type": "secp256k1",
|
|
3095
|
-
"yParity": "0x0",
|
|
3096
|
-
},
|
|
3097
|
-
],
|
|
3098
|
-
},
|
|
3099
|
-
}
|
|
3100
|
-
`)
|
|
3101
|
-
expect(SignatureEnvelope.fromRpc(rpc.initial)).toStrictEqual(
|
|
3102
|
-
initialEnvelope,
|
|
3103
|
-
)
|
|
3104
|
-
expect(SignatureEnvelope.fromRpc(rpc.current)).toStrictEqual(
|
|
3105
|
-
currentEnvelope,
|
|
3106
|
-
)
|
|
3107
|
-
})
|
|
3108
|
-
|
|
3109
|
-
test('behavior: derives the account only for an initial witness', () => {
|
|
3110
|
-
const initialEnvelope = SignatureEnvelope.deserialize(initial)
|
|
3111
|
-
if (initialEnvelope.type !== 'multisig') throw new Error('unreachable')
|
|
3112
|
-
if (initialEnvelope.config.version !== 0n) throw new Error('unreachable')
|
|
3113
|
-
const derived = SignatureEnvelope.from({
|
|
3114
|
-
config: initialEnvelope.config as MultisigConfig.Config<0n>,
|
|
3115
|
-
signatures: initialEnvelope.signatures,
|
|
3116
|
-
})
|
|
3117
|
-
const currentEnvelope = SignatureEnvelope.deserialize(current)
|
|
3118
|
-
if (currentEnvelope.type !== 'multisig') throw new Error('unreachable')
|
|
3119
|
-
|
|
3120
|
-
expect(derived.account).toBe(initialEnvelope.account)
|
|
3121
|
-
expect(() =>
|
|
3122
|
-
SignatureEnvelope.from({
|
|
3123
|
-
config: currentEnvelope.config,
|
|
3124
|
-
signatures: currentEnvelope.signatures,
|
|
3125
|
-
} as never),
|
|
3126
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3127
|
-
`[MultisigConfig.InvalidConfigError: Invalid native multisig config: account address requires version zero.]`,
|
|
3128
|
-
)
|
|
3129
|
-
})
|
|
3130
|
-
|
|
3131
|
-
test('behavior: extracts the multisig account', () => {
|
|
3132
|
-
const envelope = SignatureEnvelope.deserialize(initial)
|
|
3133
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3134
|
-
|
|
3135
|
-
expect(
|
|
3136
|
-
SignatureEnvelope.extractAddress({
|
|
3137
|
-
payload: '0xdeadbeef',
|
|
3138
|
-
signature: envelope,
|
|
3139
|
-
}),
|
|
3140
|
-
).toBe(envelope.account)
|
|
3141
|
-
})
|
|
3142
|
-
|
|
3143
|
-
test('example: matches the frozen eight-approval boundary vector', () => {
|
|
3144
|
-
const config = MultisigConfig.from({
|
|
3145
|
-
owners: Array.from({ length: 8 }, (_, index) => ({
|
|
3146
|
-
owner:
|
|
3147
|
-
`0x${(index + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
|
|
3148
|
-
weight: 1,
|
|
3149
|
-
})),
|
|
3150
|
-
salt: `0x${'66'.repeat(32)}`,
|
|
3151
|
-
threshold: 8,
|
|
3152
|
-
})
|
|
3153
|
-
const approval = SignatureEnvelope.from({
|
|
3154
|
-
r: '0x840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565',
|
|
3155
|
-
s: '0x25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1',
|
|
3156
|
-
yParity: 0,
|
|
3157
|
-
})
|
|
3158
|
-
const envelope = SignatureEnvelope.from({
|
|
3159
|
-
config,
|
|
3160
|
-
signatures: Array.from(
|
|
3161
|
-
{ length: MultisigConfig.maxSignatures },
|
|
3162
|
-
() => approval,
|
|
3163
|
-
),
|
|
3164
|
-
})
|
|
3165
|
-
const serialized = SignatureEnvelope.serialize(envelope)
|
|
3166
|
-
|
|
3167
|
-
expect({
|
|
3168
|
-
hash: Hash.keccak256(serialized),
|
|
3169
|
-
length: Hex.size(serialized),
|
|
3170
|
-
signatureCount: envelope.signatures.length,
|
|
3171
|
-
}).toMatchInlineSnapshot(`
|
|
3172
|
-
{
|
|
3173
|
-
"hash": "0x1cb47fcf31fb1ebb3177ec1744eeda195e4d789920d1214af32baaa6150ad5e2",
|
|
3174
|
-
"length": 787,
|
|
3175
|
-
"signatureCount": 8,
|
|
3176
|
-
}
|
|
3177
|
-
`)
|
|
3178
|
-
})
|
|
3179
|
-
|
|
3180
|
-
test('behavior: accepts initial and current nested witnesses', () => {
|
|
3181
|
-
const initialEnvelope = SignatureEnvelope.deserialize(initial)
|
|
3182
|
-
const currentEnvelope = SignatureEnvelope.deserialize(current)
|
|
3183
|
-
if (
|
|
3184
|
-
initialEnvelope.type !== 'multisig' ||
|
|
3185
|
-
currentEnvelope.type !== 'multisig'
|
|
3186
|
-
)
|
|
3187
|
-
throw new Error('unreachable')
|
|
3188
|
-
|
|
3189
|
-
for (const signature of [initialEnvelope, currentEnvelope]) {
|
|
3190
|
-
const config = MultisigConfig.from({
|
|
3191
|
-
owners: [{ owner: signature.account, weight: 1 }],
|
|
3192
|
-
salt: ('0x' + 'ff'.repeat(32)) as Hex.Hex,
|
|
3193
|
-
threshold: 1,
|
|
3194
|
-
version: 1n,
|
|
3195
|
-
})
|
|
3196
|
-
const envelope = SignatureEnvelope.from({
|
|
3197
|
-
account: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
3198
|
-
config,
|
|
3199
|
-
signatures: [signature],
|
|
3200
|
-
})
|
|
3201
|
-
|
|
3202
|
-
expect(
|
|
3203
|
-
SignatureEnvelope.deserialize(SignatureEnvelope.serialize(envelope)),
|
|
3204
|
-
).toStrictEqual(envelope)
|
|
3205
|
-
}
|
|
3206
|
-
})
|
|
3207
|
-
|
|
3208
|
-
test('error: rejects an initial account mismatch', () => {
|
|
3209
|
-
const envelope = SignatureEnvelope.deserialize(initial)
|
|
3210
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3211
|
-
|
|
3212
|
-
expect(() =>
|
|
3213
|
-
SignatureEnvelope.assert({
|
|
3214
|
-
...envelope,
|
|
3215
|
-
account: '0x1111111111111111111111111111111111111111',
|
|
3216
|
-
}),
|
|
3217
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3218
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: initial multisig config does not derive account.]`,
|
|
3219
|
-
)
|
|
3220
|
-
})
|
|
3221
|
-
|
|
3222
|
-
test('error: rejects a self-owned current witness', () => {
|
|
3223
|
-
const envelope = SignatureEnvelope.deserialize(current)
|
|
3224
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3225
|
-
const account = envelope.config.owners[0]!.owner
|
|
3226
|
-
|
|
3227
|
-
expect(() =>
|
|
3228
|
-
SignatureEnvelope.assert({ ...envelope, account }),
|
|
3229
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3230
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig account cannot be an owner.]`,
|
|
3231
|
-
)
|
|
3232
|
-
})
|
|
3233
|
-
|
|
3234
|
-
test('error: rejects empty and excess approvals', () => {
|
|
3235
|
-
const envelope = SignatureEnvelope.deserialize(current)
|
|
3236
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3237
|
-
|
|
3238
|
-
expect(() =>
|
|
3239
|
-
SignatureEnvelope.assert({ ...envelope, signatures: [] }),
|
|
3240
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3241
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig signatures cannot be empty.]`,
|
|
3242
|
-
)
|
|
3243
|
-
expect(() =>
|
|
3244
|
-
SignatureEnvelope.assert({
|
|
3245
|
-
...envelope,
|
|
3246
|
-
signatures: Array.from(
|
|
3247
|
-
{ length: MultisigConfig.maxSignatures + 1 },
|
|
3248
|
-
() => envelope.signatures[0]!,
|
|
3249
|
-
),
|
|
3250
|
-
}),
|
|
3251
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3252
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig signatures exceed 8.]`,
|
|
3253
|
-
)
|
|
3254
|
-
})
|
|
3255
|
-
|
|
3256
|
-
test('error: rejects keychain owner approvals', () => {
|
|
3257
|
-
const envelope = SignatureEnvelope.deserialize(current)
|
|
3258
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3259
|
-
|
|
3260
|
-
expect(() =>
|
|
3261
|
-
SignatureEnvelope.assert({
|
|
3262
|
-
...envelope,
|
|
3263
|
-
signatures: [signature_keychain_secp256k1],
|
|
3264
|
-
} as never),
|
|
3265
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3266
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: keychain owner approvals are not allowed.]`,
|
|
3267
|
-
)
|
|
3268
|
-
})
|
|
3269
|
-
|
|
3270
|
-
test('error: rejects oversized primitive approvals', () => {
|
|
3271
|
-
const envelope = SignatureEnvelope.deserialize(current)
|
|
3272
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3273
|
-
const oversized = {
|
|
3274
|
-
...signature_webauthn,
|
|
3275
|
-
metadata: {
|
|
3276
|
-
...signature_webauthn.metadata,
|
|
3277
|
-
clientDataJSON: JSON.stringify({ value: 'x'.repeat(2048) }),
|
|
3278
|
-
},
|
|
3279
|
-
}
|
|
3280
|
-
|
|
3281
|
-
expect(() =>
|
|
3282
|
-
SignatureEnvelope.assert({
|
|
3283
|
-
...envelope,
|
|
3284
|
-
signatures: [oversized],
|
|
3285
|
-
}),
|
|
3286
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3287
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig owner signature exceeds 2049 bytes.]`,
|
|
3288
|
-
)
|
|
3289
|
-
})
|
|
3290
|
-
|
|
3291
|
-
test('error: rejects nesting deeper than two nodes', () => {
|
|
3292
|
-
const envelope = SignatureEnvelope.deserialize(initial)
|
|
3293
|
-
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3294
|
-
const depth2 = SignatureEnvelope.from({
|
|
3295
|
-
...envelope,
|
|
3296
|
-
signatures: [envelope],
|
|
3297
|
-
})
|
|
3298
|
-
|
|
3299
|
-
expect(() =>
|
|
3300
|
-
SignatureEnvelope.assert({
|
|
3301
|
-
...envelope,
|
|
3302
|
-
signatures: [depth2],
|
|
3303
|
-
}),
|
|
3304
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3305
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig nesting depth exceeds 2.]`,
|
|
3306
|
-
)
|
|
3307
|
-
})
|
|
3308
|
-
|
|
3309
|
-
test.each([
|
|
3310
|
-
{
|
|
3311
|
-
name: 'two fields',
|
|
3312
|
-
value: Rlp.fromHex([
|
|
3313
|
-
'0x9dba7f426b711d4893c11611eacf7cc334e7146b',
|
|
3314
|
-
[Hex.slice(initial, -65)],
|
|
3315
|
-
]),
|
|
3316
|
-
},
|
|
3317
|
-
{
|
|
3318
|
-
name: 'trailing field',
|
|
3319
|
-
value: Rlp.fromHex([
|
|
3320
|
-
'0x9dba7f426b711d4893c11611eacf7cc334e7146b',
|
|
3321
|
-
MultisigConfig.toTuple(
|
|
3322
|
-
MultisigConfig.from({
|
|
3323
|
-
owners: [
|
|
3324
|
-
{
|
|
3325
|
-
owner: '0x7e5f4552091a69125d5dfcb7b8c2659029395bdf',
|
|
3326
|
-
weight: 1,
|
|
3327
|
-
},
|
|
3328
|
-
],
|
|
3329
|
-
threshold: 1,
|
|
3330
|
-
}),
|
|
3331
|
-
),
|
|
3332
|
-
[Hex.slice(initial, -65)],
|
|
3333
|
-
'0x',
|
|
3334
|
-
]),
|
|
3335
|
-
},
|
|
3336
|
-
])('error: rejects malformed $name wire shape', ({ value }) => {
|
|
3337
|
-
expect(() =>
|
|
3338
|
-
SignatureEnvelope.deserialize(Hex.concat('0x05', value)),
|
|
3339
|
-
).toThrowError(SignatureEnvelope.InvalidSerializedError)
|
|
3340
|
-
})
|
|
3341
|
-
|
|
3342
|
-
test.each([
|
|
3343
|
-
{
|
|
3344
|
-
field: 'salt',
|
|
3345
|
-
value: [
|
|
3346
|
-
('0x' + '00'.repeat(31)) as Hex.Hex,
|
|
3347
|
-
'0x',
|
|
3348
|
-
'0x01',
|
|
3349
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3350
|
-
],
|
|
3351
|
-
},
|
|
3352
|
-
{
|
|
3353
|
-
field: 'oversized version',
|
|
3354
|
-
value: [
|
|
3355
|
-
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3356
|
-
('0x' + '01'.repeat(9)) as Hex.Hex,
|
|
3357
|
-
'0x01',
|
|
3358
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3359
|
-
],
|
|
3360
|
-
},
|
|
3361
|
-
{
|
|
3362
|
-
field: 'zero version',
|
|
3363
|
-
value: [
|
|
3364
|
-
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3365
|
-
'0x00',
|
|
3366
|
-
'0x01',
|
|
3367
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3368
|
-
],
|
|
3369
|
-
},
|
|
3370
|
-
{
|
|
3371
|
-
field: 'zero-prefixed version',
|
|
3372
|
-
value: [
|
|
3373
|
-
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3374
|
-
'0x0001',
|
|
3375
|
-
'0x01',
|
|
3376
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3377
|
-
],
|
|
3378
|
-
},
|
|
3379
|
-
{
|
|
3380
|
-
field: 'threshold',
|
|
3381
|
-
value: [
|
|
3382
|
-
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3383
|
-
'0x',
|
|
3384
|
-
'0x0001',
|
|
3385
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3386
|
-
],
|
|
3387
|
-
},
|
|
3388
|
-
{
|
|
3389
|
-
field: 'weight',
|
|
3390
|
-
value: [
|
|
3391
|
-
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3392
|
-
'0x',
|
|
3393
|
-
'0x01',
|
|
3394
|
-
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x0001']],
|
|
3395
|
-
],
|
|
3396
|
-
},
|
|
3397
|
-
])('error: rejects a noncanonical $field', ({ value }) => {
|
|
3398
|
-
const malformed = Hex.concat(
|
|
3399
|
-
'0x05',
|
|
3400
|
-
Rlp.fromHex([
|
|
3401
|
-
'0x9dba7f426b711d4893c11611eacf7cc334e7146b',
|
|
3402
|
-
value as never,
|
|
3403
|
-
[Hex.slice(initial, -65)],
|
|
3404
|
-
]),
|
|
3405
|
-
)
|
|
3406
|
-
|
|
3407
|
-
expect(() => SignatureEnvelope.deserialize(malformed)).toThrowError(
|
|
3408
|
-
SignatureEnvelope.InvalidSerializedError,
|
|
3409
|
-
)
|
|
3410
|
-
})
|
|
3411
3208
|
})
|