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.
Files changed (142) hide show
  1. package/CHANGELOG.md +43 -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 +43 -39
  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 +45 -28
  24. package/dist/tempo/MultisigOperation.d.ts.map +1 -1
  25. package/dist/tempo/MultisigOperation.js +81 -131
  26. package/dist/tempo/MultisigOperation.js.map +1 -1
  27. package/dist/tempo/MultisigSimulation.d.ts +85 -0
  28. package/dist/tempo/MultisigSimulation.d.ts.map +1 -0
  29. package/dist/tempo/MultisigSimulation.js +112 -0
  30. package/dist/tempo/MultisigSimulation.js.map +1 -0
  31. package/dist/tempo/RpcSchemaTempo.d.ts +20 -10
  32. package/dist/tempo/RpcSchemaTempo.d.ts.map +1 -1
  33. package/dist/tempo/SignatureEnvelope.d.ts +74 -153
  34. package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
  35. package/dist/tempo/SignatureEnvelope.js +130 -187
  36. package/dist/tempo/SignatureEnvelope.js.map +1 -1
  37. package/dist/tempo/TransactionRequest.d.ts +9 -7
  38. package/dist/tempo/TransactionRequest.d.ts.map +1 -1
  39. package/dist/tempo/TransactionRequest.js +12 -7
  40. package/dist/tempo/TransactionRequest.js.map +1 -1
  41. package/dist/tempo/index.d.ts +7 -5
  42. package/dist/tempo/index.d.ts.map +1 -1
  43. package/dist/tempo/index.js +7 -5
  44. package/dist/tempo/index.js.map +1 -1
  45. package/dist/zod/tempo/AuthorizationTempo.d.ts +360 -140
  46. package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
  47. package/dist/zod/tempo/KeyAuthorization.d.ts +247 -487
  48. package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
  49. package/dist/zod/tempo/KeyAuthorization.js +29 -31
  50. package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
  51. package/dist/zod/tempo/MultisigOperation.d.ts +0 -10
  52. package/dist/zod/tempo/MultisigOperation.d.ts.map +1 -1
  53. package/dist/zod/tempo/MultisigOperation.js +0 -3
  54. package/dist/zod/tempo/MultisigOperation.js.map +1 -1
  55. package/dist/zod/tempo/MultisigSimulation.d.ts +52 -0
  56. package/dist/zod/tempo/MultisigSimulation.d.ts.map +1 -0
  57. package/dist/zod/tempo/MultisigSimulation.js +44 -0
  58. package/dist/zod/tempo/MultisigSimulation.js.map +1 -0
  59. package/dist/zod/tempo/RpcSchemaTempo.d.ts +357 -422
  60. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  61. package/dist/zod/tempo/RpcSchemaTempo.js +19 -10
  62. package/dist/zod/tempo/RpcSchemaTempo.js.map +1 -1
  63. package/dist/zod/tempo/SignatureEnvelope.d.ts +108 -42
  64. package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
  65. package/dist/zod/tempo/SignatureEnvelope.js +21 -19
  66. package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
  67. package/dist/zod/tempo/Transaction.d.ts +678 -666
  68. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  69. package/dist/zod/tempo/TransactionRequest.d.ts +580 -619
  70. package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
  71. package/dist/zod/tempo/TransactionRequest.js +18 -10
  72. package/dist/zod/tempo/TransactionRequest.js.map +1 -1
  73. package/dist/zod/tempo/TxEnvelopeTempo.d.ts +327 -213
  74. package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  75. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +105 -3
  76. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts.map +1 -1
  77. package/dist/zod/tempo/z.d.ts +1 -1
  78. package/dist/zod/tempo/z.d.ts.map +1 -1
  79. package/dist/zod/tempo/z.js +1 -1
  80. package/dist/zod/tempo/z.js.map +1 -1
  81. package/package.json +9 -9
  82. package/src/core/AbiEvent.ts +11 -9
  83. package/src/core/Blobs.ts +6 -2
  84. package/src/core/Signature.ts +41 -11
  85. package/src/core/Siwe.ts +20 -1
  86. package/src/core/_test/AbiEvent.test.ts +45 -0
  87. package/src/core/_test/Blobs.test.ts +22 -0
  88. package/src/core/_test/Signature.test.ts +166 -0
  89. package/src/core/_test/Siwe.test.ts +100 -1
  90. package/src/tempo/AuthorizationTempo.test.ts +8 -0
  91. package/src/tempo/KeyAuthorization.test-d.ts +16 -74
  92. package/src/tempo/KeyAuthorization.test.ts +113 -14
  93. package/src/tempo/KeyAuthorization.ts +110 -66
  94. package/src/tempo/MultisigConfig.test.ts +44 -20
  95. package/src/tempo/MultisigConfig.ts +65 -23
  96. package/src/tempo/MultisigOperation.test-d.ts +8 -6
  97. package/src/tempo/MultisigOperation.test.ts +187 -591
  98. package/src/tempo/MultisigOperation.ts +129 -205
  99. package/src/tempo/MultisigSimulation.test-d.ts +33 -0
  100. package/src/tempo/MultisigSimulation.test.ts +194 -0
  101. package/src/tempo/MultisigSimulation.ts +192 -0
  102. package/src/tempo/RpcSchemaTempo.test-d.ts +45 -10
  103. package/src/tempo/RpcSchemaTempo.ts +15 -6
  104. package/src/tempo/SignatureEnvelope.test-d.ts +27 -101
  105. package/src/tempo/SignatureEnvelope.test.ts +330 -533
  106. package/src/tempo/SignatureEnvelope.ts +198 -310
  107. package/src/tempo/Transaction.test.ts +61 -0
  108. package/src/tempo/TransactionRequest.test-d.ts +7 -7
  109. package/src/tempo/TransactionRequest.test.ts +81 -223
  110. package/src/tempo/TransactionRequest.ts +34 -13
  111. package/src/tempo/index.ts +7 -5
  112. package/src/tempo/multisig.e2e.test.ts +329 -818
  113. package/src/version.ts +1 -1
  114. package/src/zod/tempo/KeyAuthorization.ts +50 -39
  115. package/src/zod/tempo/MultisigOperation.ts +0 -3
  116. package/src/zod/tempo/MultisigSimulation.ts +53 -0
  117. package/src/zod/tempo/RpcSchemaTempo.ts +20 -10
  118. package/src/zod/tempo/SignatureEnvelope.ts +46 -59
  119. package/src/zod/tempo/TransactionRequest.ts +28 -14
  120. package/src/zod/tempo/_test/KeyAuthorization.test.ts +66 -30
  121. package/src/zod/tempo/_test/MultisigOperation.test.ts +0 -2
  122. package/src/zod/tempo/_test/MultisigSimulation.test-d.ts +13 -0
  123. package/src/zod/tempo/_test/MultisigSimulation.test.ts +53 -0
  124. package/src/zod/tempo/_test/RpcSchemaTempo.test-d.ts +19 -2
  125. package/src/zod/tempo/_test/RpcSchemaTempo.test.ts +46 -4
  126. package/src/zod/tempo/_test/SignatureEnvelope.test.ts +108 -129
  127. package/src/zod/tempo/_test/TransactionRequest.test.ts +17 -168
  128. package/src/zod/tempo/z.ts +1 -1
  129. package/dist/tempo/MultisigWitness.d.ts +0 -106
  130. package/dist/tempo/MultisigWitness.d.ts.map +0 -1
  131. package/dist/tempo/MultisigWitness.js +0 -118
  132. package/dist/tempo/MultisigWitness.js.map +0 -1
  133. package/dist/zod/tempo/MultisigWitness.d.ts +0 -152
  134. package/dist/zod/tempo/MultisigWitness.d.ts.map +0 -1
  135. package/dist/zod/tempo/MultisigWitness.js +0 -76
  136. package/dist/zod/tempo/MultisigWitness.js.map +0 -1
  137. package/src/tempo/MultisigWitness.test-d.ts +0 -42
  138. package/src/tempo/MultisigWitness.test.ts +0 -288
  139. package/src/tempo/MultisigWitness.ts +0 -210
  140. package/src/zod/tempo/MultisigWitness.ts +0 -101
  141. package/src/zod/tempo/_test/MultisigWitness.test-d.ts +0 -13
  142. 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
- const config = MultisigConfig.from({
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
- config,
1133
- signatures: [SignatureEnvelope.from(signature_secp256k1)],
1134
- })
1135
-
1136
- expect(envelope).toMatchInlineSnapshot(
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
- "account": "0x8820d1497eeaf4f68e00b2cfc00a2f3b1dbb00da",
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
- "signature": {
1165
- "r": Anything,
1166
- "s": Anything,
1167
- "yParity": Any<Number>,
1168
- },
1169
- "type": "secp256k1",
1134
+ "owner": "0x1111111111111111111111111111111111111111",
1135
+ "weight": 1,
1170
1136
  },
1171
1137
  ],
1172
- "type": "multisig",
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 config = MultisigConfig.from({
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
- '0x05f897949dba7f426b711d4893c11611eacf7cc334e7146bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b' as const
3128
+ '0x05f89794c4a590afa7337e5cd5eb3aa60cacf91c5400044bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b' as const
2886
3129
  const current =
2887
- '0x05f897949dba7f426b711d4893c11611eacf7cc334e7146bf83ba000000000000000000000000000000000000000000000000000000000000000000101d7d6942b5ad5c4795c026514f8317c7a215e218dccd6cf01f843b8414a0e5b5b4a90f08e6e8d676a73a22dc2cf022ffdcc9299512b5d9a6daf66e0504a395a2ef6f6c0f173910b875c45d01aa66d928e56ba6f49bbc8186f80268bf51b' as const
2888
- const nested =
2889
- '0x05f8f0949969e2243075b27a8eab61009c59b77ab13b83f6f83ba033333333333333333333333333333333333333333333333333333333333333338001d7d6944f9f5b162a7464bcc260ce44d7ae0d935f9c583701f89cb89a05f897944f9f5b162a7464bcc260ce44d7ae0d935f9c5837f83ba022222222222222222222222222222222222222222222222222222222222222228001d7d6946813eb9362372eef6200f3b1dbc3f819671cba6901f843b841032aa6f3ea7b0b7069720d0f3891983c493d149326c5c957d864ed7371b8475e3e95325079b24491f4b6d68920e4b3bacd7c6094df6ed88b8674864ab559c8fb1b' as const
3130
+ '0x05f89794c4a590afa7337e5cd5eb3aa60cacf91c5400044bf83ba000000000000000000000000000000000000000000000000000000000000000000101d7d6942b5ad5c4795c026514f8317c7a215e218dccd6cf01f843b8414a0e5b5b4a90f08e6e8d676a73a22dc2cf022ffdcc9299512b5d9a6daf66e0504a395a2ef6f6c0f173910b875c45d01aa66d928e56ba6f49bbc8186f80268bf51b' as const
2890
3131
 
2891
- test('example: decodes and re-encodes the frozen initial witness', () => {
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": "0x9dba7f426b711d4893c11611eacf7cc334e7146b",
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 witness', () => {
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": "0x9dba7f426b711d4893c11611eacf7cc334e7146b",
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
  })