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.
Files changed (105) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/core/AbiEvent.d.ts.map +1 -1
  3. package/dist/core/AbiEvent.js +10 -9
  4. package/dist/core/AbiEvent.js.map +1 -1
  5. package/dist/core/Blobs.d.ts.map +1 -1
  6. package/dist/core/Blobs.js +6 -2
  7. package/dist/core/Blobs.js.map +1 -1
  8. package/dist/core/Signature.d.ts +6 -5
  9. package/dist/core/Signature.d.ts.map +1 -1
  10. package/dist/core/Signature.js +17 -7
  11. package/dist/core/Signature.js.map +1 -1
  12. package/dist/core/Siwe.d.ts.map +1 -1
  13. package/dist/core/Siwe.js +21 -1
  14. package/dist/core/Siwe.js.map +1 -1
  15. package/dist/tempo/KeyAuthorization.d.ts +42 -38
  16. package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
  17. package/dist/tempo/KeyAuthorization.js +24 -4
  18. package/dist/tempo/KeyAuthorization.js.map +1 -1
  19. package/dist/tempo/MultisigConfig.d.ts +18 -15
  20. package/dist/tempo/MultisigConfig.d.ts.map +1 -1
  21. package/dist/tempo/MultisigConfig.js +49 -20
  22. package/dist/tempo/MultisigConfig.js.map +1 -1
  23. package/dist/tempo/MultisigOperation.d.ts +3 -2
  24. package/dist/tempo/MultisigOperation.d.ts.map +1 -1
  25. package/dist/tempo/MultisigOperation.js +20 -100
  26. package/dist/tempo/MultisigOperation.js.map +1 -1
  27. package/dist/tempo/MultisigSimulation.d.ts +2 -49
  28. package/dist/tempo/MultisigSimulation.d.ts.map +1 -1
  29. package/dist/tempo/MultisigSimulation.js +18 -46
  30. package/dist/tempo/MultisigSimulation.js.map +1 -1
  31. package/dist/tempo/SignatureEnvelope.d.ts +72 -140
  32. package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
  33. package/dist/tempo/SignatureEnvelope.js +130 -181
  34. package/dist/tempo/SignatureEnvelope.js.map +1 -1
  35. package/dist/tempo/TransactionRequest.d.ts +4 -2
  36. package/dist/tempo/TransactionRequest.d.ts.map +1 -1
  37. package/dist/tempo/TransactionRequest.js +6 -1
  38. package/dist/tempo/TransactionRequest.js.map +1 -1
  39. package/dist/tempo/index.d.ts +3 -1
  40. package/dist/tempo/index.d.ts.map +1 -1
  41. package/dist/tempo/index.js +3 -1
  42. package/dist/tempo/index.js.map +1 -1
  43. package/dist/zod/tempo/AuthorizationTempo.d.ts +350 -10
  44. package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
  45. package/dist/zod/tempo/KeyAuthorization.d.ts +242 -422
  46. package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
  47. package/dist/zod/tempo/KeyAuthorization.js +29 -31
  48. package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
  49. package/dist/zod/tempo/MultisigSimulation.d.ts +8 -76
  50. package/dist/zod/tempo/MultisigSimulation.d.ts.map +1 -1
  51. package/dist/zod/tempo/MultisigSimulation.js +3 -37
  52. package/dist/zod/tempo/MultisigSimulation.js.map +1 -1
  53. package/dist/zod/tempo/RpcSchemaTempo.d.ts +284 -228
  54. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  55. package/dist/zod/tempo/SignatureEnvelope.d.ts +105 -3
  56. package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
  57. package/dist/zod/tempo/SignatureEnvelope.js +21 -9
  58. package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
  59. package/dist/zod/tempo/Transaction.d.ts +676 -448
  60. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  61. package/dist/zod/tempo/TransactionRequest.d.ts +590 -473
  62. package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
  63. package/dist/zod/tempo/TransactionRequest.js +8 -0
  64. package/dist/zod/tempo/TransactionRequest.js.map +1 -1
  65. package/dist/zod/tempo/TxEnvelopeTempo.d.ts +327 -213
  66. package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  67. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +105 -3
  68. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts.map +1 -1
  69. package/package.json +1 -1
  70. package/src/core/AbiEvent.ts +11 -9
  71. package/src/core/Blobs.ts +6 -2
  72. package/src/core/Signature.ts +41 -11
  73. package/src/core/Siwe.ts +20 -1
  74. package/src/core/_test/AbiEvent.test.ts +45 -0
  75. package/src/core/_test/Blobs.test.ts +22 -0
  76. package/src/core/_test/Signature.test.ts +166 -0
  77. package/src/core/_test/Siwe.test.ts +100 -1
  78. package/src/tempo/AuthorizationTempo.test.ts +8 -4
  79. package/src/tempo/KeyAuthorization.test-d.ts +16 -63
  80. package/src/tempo/KeyAuthorization.test.ts +69 -11
  81. package/src/tempo/KeyAuthorization.ts +107 -63
  82. package/src/tempo/MultisigConfig.test.ts +44 -20
  83. package/src/tempo/MultisigConfig.ts +65 -23
  84. package/src/tempo/MultisigOperation.test.ts +79 -550
  85. package/src/tempo/MultisigOperation.ts +31 -146
  86. package/src/tempo/MultisigSimulation.test-d.ts +2 -2
  87. package/src/tempo/MultisigSimulation.test.ts +21 -110
  88. package/src/tempo/MultisigSimulation.ts +25 -99
  89. package/src/tempo/SignatureEnvelope.test-d.ts +29 -47
  90. package/src/tempo/SignatureEnvelope.test.ts +328 -541
  91. package/src/tempo/SignatureEnvelope.ts +196 -291
  92. package/src/tempo/Transaction.test.ts +4 -4
  93. package/src/tempo/TransactionRequest.test.ts +17 -80
  94. package/src/tempo/TransactionRequest.ts +19 -2
  95. package/src/tempo/index.ts +3 -1
  96. package/src/tempo/multisig.e2e.test.ts +321 -860
  97. package/src/version.ts +1 -1
  98. package/src/zod/tempo/KeyAuthorization.ts +50 -39
  99. package/src/zod/tempo/MultisigSimulation.ts +3 -48
  100. package/src/zod/tempo/SignatureEnvelope.ts +46 -35
  101. package/src/zod/tempo/TransactionRequest.ts +14 -0
  102. package/src/zod/tempo/_test/KeyAuthorization.test.ts +69 -20
  103. package/src/zod/tempo/_test/MultisigSimulation.test.ts +6 -31
  104. package/src/zod/tempo/_test/SignatureEnvelope.test.ts +118 -85
  105. 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
- const config = MultisigConfig.from({
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
- config,
1136
- signatures: [SignatureEnvelope.from(signature_secp256k1)],
1137
- })
1138
-
1139
- expect(envelope).toMatchInlineSnapshot(
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
- "account": "0x8820d1497eeaf4f68e00b2cfc00a2f3b1dbb00da",
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
- "signature": {
1168
- "r": Anything,
1169
- "s": Anything,
1170
- "yParity": Any<Number>,
1171
- },
1172
- "type": "secp256k1",
1134
+ "owner": "0x1111111111111111111111111111111111111111",
1135
+ "weight": 1,
1173
1136
  },
1174
1137
  ],
1175
- "type": "multisig",
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 config = MultisigConfig.from({
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
- '0x05f897949dba7f426b711d4893c11611eacf7cc334e7146bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b' as const
3128
+ '0x05f89794c4a590afa7337e5cd5eb3aa60cacf91c5400044bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b' as const
2938
3129
  const current =
2939
- '0x05f897949dba7f426b711d4893c11611eacf7cc334e7146bf83ba000000000000000000000000000000000000000000000000000000000000000000101d7d6942b5ad5c4795c026514f8317c7a215e218dccd6cf01f843b8414a0e5b5b4a90f08e6e8d676a73a22dc2cf022ffdcc9299512b5d9a6daf66e0504a395a2ef6f6c0f173910b875c45d01aa66d928e56ba6f49bbc8186f80268bf51b' as const
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": "0x9dba7f426b711d4893c11611eacf7cc334e7146b",
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": "0x9dba7f426b711d4893c11611eacf7cc334e7146b",
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
  })