ox 0.14.29 → 0.14.31

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 (54) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/_cjs/core/AbiParameters.js +2 -1
  3. package/_cjs/core/AbiParameters.js.map +1 -1
  4. package/_cjs/core/internal/abiParameters.js +10 -2
  5. package/_cjs/core/internal/abiParameters.js.map +1 -1
  6. package/_cjs/tempo/KeyAuthorization.js +39 -10
  7. package/_cjs/tempo/KeyAuthorization.js.map +1 -1
  8. package/_cjs/tempo/MultisigConfig.js +26 -26
  9. package/_cjs/tempo/MultisigConfig.js.map +1 -1
  10. package/_cjs/tempo/SignatureEnvelope.js +189 -55
  11. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  12. package/_cjs/version.js +1 -1
  13. package/_esm/core/AbiParameters.js +4 -1
  14. package/_esm/core/AbiParameters.js.map +1 -1
  15. package/_esm/core/internal/abiParameters.js +17 -2
  16. package/_esm/core/internal/abiParameters.js.map +1 -1
  17. package/_esm/tempo/KeyAuthorization.js +41 -9
  18. package/_esm/tempo/KeyAuthorization.js.map +1 -1
  19. package/_esm/tempo/MultisigConfig.js +53 -81
  20. package/_esm/tempo/MultisigConfig.js.map +1 -1
  21. package/_esm/tempo/SignatureEnvelope.js +202 -75
  22. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  23. package/_esm/tempo/index.js +2 -2
  24. package/_esm/version.js +1 -1
  25. package/_types/core/AbiParameters.d.ts.map +1 -1
  26. package/_types/core/internal/abiParameters.d.ts.map +1 -1
  27. package/_types/core/internal/rpcSchemas/eth.d.ts +16 -0
  28. package/_types/core/internal/rpcSchemas/eth.d.ts.map +1 -1
  29. package/_types/tempo/KeyAuthorization.d.ts +22 -16
  30. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  31. package/_types/tempo/MultisigConfig.d.ts +43 -83
  32. package/_types/tempo/MultisigConfig.d.ts.map +1 -1
  33. package/_types/tempo/SignatureEnvelope.d.ts +62 -40
  34. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  35. package/_types/tempo/index.d.ts +2 -2
  36. package/_types/version.d.ts +1 -1
  37. package/core/AbiParameters.ts +3 -1
  38. package/core/internal/abiParameters.ts +17 -2
  39. package/core/internal/rpcSchemas/eth.ts +16 -0
  40. package/package.json +11 -1
  41. package/tempo/KeyAuthorization.test-d/package.json +6 -0
  42. package/tempo/KeyAuthorization.test-d.ts +62 -0
  43. package/tempo/KeyAuthorization.test.ts +132 -0
  44. package/tempo/KeyAuthorization.ts +76 -29
  45. package/tempo/MultisigConfig.test.ts +86 -37
  46. package/tempo/MultisigConfig.ts +75 -132
  47. package/tempo/SignatureEnvelope.test-d/package.json +6 -0
  48. package/tempo/SignatureEnvelope.test-d.ts +97 -0
  49. package/tempo/SignatureEnvelope.test.ts +322 -30
  50. package/tempo/SignatureEnvelope.ts +314 -118
  51. package/tempo/e2e.test.ts +22 -207
  52. package/tempo/index.ts +2 -2
  53. package/tempo/multisig.e2e.test.ts +582 -0
  54. package/version.ts +1 -1
@@ -3,6 +3,7 @@ import {
3
3
  Hex,
4
4
  P256,
5
5
  PublicKey,
6
+ Rlp,
6
7
  Secp256k1,
7
8
  Signature,
8
9
  WebAuthnP256,
@@ -1109,7 +1110,7 @@ describe('from', () => {
1109
1110
  ],
1110
1111
  })
1111
1112
 
1112
- test('behavior: derives `account`/`genesisConfigId` from `genesisConfig`', () => {
1113
+ test('behavior: derives `account` from `genesisConfig`', () => {
1113
1114
  const envelope = SignatureEnvelope.from({
1114
1115
  genesisConfig,
1115
1116
  signatures: [SignatureEnvelope.from(signature_secp256k1)],
@@ -1118,7 +1119,6 @@ describe('from', () => {
1118
1119
  expect(envelope).toMatchObject({
1119
1120
  type: 'multisig',
1120
1121
  account: MultisigConfig.getAddress(genesisConfig),
1121
- genesisConfigId: MultisigConfig.toId(genesisConfig),
1122
1122
  })
1123
1123
  expect('genesisConfig' in envelope).toBe(false)
1124
1124
  expect((envelope as SignatureEnvelope.Multisig).init).toBeUndefined()
@@ -1155,19 +1155,16 @@ describe('from', () => {
1155
1155
  expect((envelope as SignatureEnvelope.Multisig).init).toEqual(otherConfig)
1156
1156
  })
1157
1157
 
1158
- test('behavior: `{account, genesisConfigId}` form still works', () => {
1158
+ test('behavior: `{ account }` form still works', () => {
1159
1159
  const account = MultisigConfig.getAddress(genesisConfig)
1160
- const genesisConfigId = MultisigConfig.toId(genesisConfig)
1161
1160
  const envelope = SignatureEnvelope.from({
1162
1161
  account,
1163
- genesisConfigId,
1164
1162
  signatures: [SignatureEnvelope.from(signature_secp256k1)],
1165
1163
  })
1166
1164
 
1167
1165
  expect(envelope).toMatchObject({
1168
1166
  type: 'multisig',
1169
1167
  account,
1170
- genesisConfigId,
1171
1168
  })
1172
1169
  })
1173
1170
  })
@@ -1836,9 +1833,8 @@ describe('sortMultisigApprovals', () => {
1836
1833
  expect(recovered).toEqual(ascending.map((owner) => owner.address))
1837
1834
  })
1838
1835
 
1839
- test('behavior: `genesisConfig` and `{account, genesisConfigId}` produce identical ordering', () => {
1836
+ test('behavior: `genesisConfig` and `{ account }` produce identical ordering', () => {
1840
1837
  const account = MultisigConfig.getAddress(genesisConfig)
1841
- const genesisConfigId = MultisigConfig.toId(genesisConfig)
1842
1838
  const signatures = owners.map((owner) => owner.signature)
1843
1839
 
1844
1840
  const fromConfig = SignatureEnvelope.sortMultisigApprovals({
@@ -1846,13 +1842,12 @@ describe('sortMultisigApprovals', () => {
1846
1842
  payload,
1847
1843
  signatures,
1848
1844
  })
1849
- const fromIds = SignatureEnvelope.sortMultisigApprovals({
1845
+ const fromAccount = SignatureEnvelope.sortMultisigApprovals({
1850
1846
  account,
1851
- genesisConfigId,
1852
1847
  payload,
1853
1848
  signatures,
1854
1849
  })
1855
- expect(fromConfig).toEqual(fromIds)
1850
+ expect(fromConfig).toEqual(fromAccount)
1856
1851
  })
1857
1852
  })
1858
1853
 
@@ -2855,8 +2850,6 @@ describe('CoercionError', () => {
2855
2850
 
2856
2851
  describe('multisig', () => {
2857
2852
  const account = '0x8ba6d26ff5c4e82ba0c8caf8c8ca794e1489a7ae'
2858
- const genesisConfigId =
2859
- '0x01781fe551182476f2422c759e82d81c92e3263737afbbad57def6e8b69d21f5'
2860
2853
 
2861
2854
  // P256 signatures do not carry `yParity` in the wire format, so use a clean
2862
2855
  // inner signature for round-trip equality checks.
@@ -2869,7 +2862,6 @@ describe('multisig', () => {
2869
2862
  const envelope = SignatureEnvelope.from({
2870
2863
  type: 'multisig',
2871
2864
  account,
2872
- genesisConfigId,
2873
2865
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
2874
2866
  })
2875
2867
 
@@ -2878,13 +2870,61 @@ describe('multisig', () => {
2878
2870
  expect(serialized.startsWith('0x05')).toBe(true)
2879
2871
  })
2880
2872
 
2873
+ test('serialize: wire shape is `0x05 || rlp([account, signatures])`', () => {
2874
+ const deterministic = SignatureEnvelope.from({
2875
+ type: 'multisig',
2876
+ account,
2877
+ signatures: [innerP256],
2878
+ })
2879
+ expect(SignatureEnvelope.serialize(deterministic)).toMatchInlineSnapshot(
2880
+ `"0x05f89b948ba6d26ff5c4e82ba0c8caf8c8ca794e1489a7aef884b88201ccbb3485d4726235f13cb15ef394fb7158179fb7b1925eccec0147671090c52e77c3c53373cc1e3b05e7c23f609deb17cea8fe097300c45411237e9fe4166b35ad8ac16e167d6992c3e120d7f17d2376bc1cbcf30c46ba6dd00ce07303e742f511edf6ce1c32de66846f56afa7be1cbd729bc35750b6d0cdcf3ec9d75461aba001"`,
2881
+ )
2882
+ })
2883
+
2881
2884
  test('serialize/deserialize round-trip', () => {
2882
2885
  const serialized = SignatureEnvelope.serialize(envelope)
2883
2886
  expect(SignatureEnvelope.deserialize(serialized)).toEqual(envelope)
2884
2887
  })
2885
2888
 
2889
+ test('deserialize: rejects the legacy three-field wire shape', () => {
2890
+ const init = MultisigConfig.toTuple({
2891
+ threshold: 1,
2892
+ owners: [
2893
+ { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
2894
+ ],
2895
+ })
2896
+ const legacy = Hex.concat(
2897
+ '0x05',
2898
+ Rlp.fromHex([account, [SignatureEnvelope.serialize(innerP256)], init]),
2899
+ )
2900
+ expect(() => SignatureEnvelope.deserialize(legacy)).toThrowError(
2901
+ SignatureEnvelope.InvalidSerializedError,
2902
+ )
2903
+ })
2904
+
2905
+ test('deserialize: rejects an invalid initialized account', () => {
2906
+ const malformed = Hex.concat(
2907
+ '0x05',
2908
+ Rlp.fromHex(['0x11', [SignatureEnvelope.serialize(innerP256)]]),
2909
+ )
2910
+ expect(() => SignatureEnvelope.deserialize(malformed)).toThrowError(
2911
+ SignatureEnvelope.InvalidSerializedError,
2912
+ )
2913
+ })
2914
+
2886
2915
  test('getType', () => {
2887
2916
  expect(SignatureEnvelope.getType(envelope)).toBe('multisig')
2917
+ expect(
2918
+ SignatureEnvelope.getType({
2919
+ init: {
2920
+ threshold: 1,
2921
+ owners: [
2922
+ { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
2923
+ ],
2924
+ },
2925
+ signatures: [innerP256],
2926
+ } as never),
2927
+ ).toBe('multisig')
2888
2928
  })
2889
2929
 
2890
2930
  test('extractAddress returns the multisig account', () => {
@@ -2897,9 +2937,19 @@ describe('multisig', () => {
2897
2937
  })
2898
2938
 
2899
2939
  test('toRpc/fromRpc round-trip', () => {
2900
- const rpc = SignatureEnvelope.toRpc(envelope)
2901
- expect(rpc.type).toBe('multisig')
2940
+ const rpc = SignatureEnvelope.toRpc(
2941
+ envelope,
2942
+ ) as SignatureEnvelope.MultisigRpc
2943
+ expect(rpc).toMatchObject({
2944
+ account,
2945
+ signatures: [{ type: 'secp256k1' }, { type: 'p256' }],
2946
+ })
2947
+ expect(rpc.init).toBeUndefined()
2948
+ expect('type' in rpc).toBe(false)
2902
2949
  expect(SignatureEnvelope.fromRpc(rpc)).toEqual(envelope)
2950
+ expect(
2951
+ SignatureEnvelope.fromRpc({ ...rpc, type: 'multisig' } as never),
2952
+ ).toEqual(envelope)
2903
2953
  })
2904
2954
 
2905
2955
  test('assert: missing properties', () => {
@@ -2908,6 +2958,156 @@ describe('multisig', () => {
2908
2958
  ).toThrowError()
2909
2959
  })
2910
2960
 
2961
+ test('assert: rejects empty owner approvals', () => {
2962
+ expect(() =>
2963
+ SignatureEnvelope.assert({
2964
+ type: 'multisig',
2965
+ account,
2966
+ signatures: [],
2967
+ }),
2968
+ ).toThrowErrorMatchingInlineSnapshot(
2969
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig signatures cannot be empty.]`,
2970
+ )
2971
+ })
2972
+
2973
+ test('assert: accepts at most eight owner approvals', () => {
2974
+ expect(() =>
2975
+ SignatureEnvelope.assert({
2976
+ type: 'multisig',
2977
+ account,
2978
+ signatures: Array.from(
2979
+ { length: MultisigConfig.maxSignatures },
2980
+ () => innerP256,
2981
+ ),
2982
+ }),
2983
+ ).not.toThrow()
2984
+
2985
+ expect(() =>
2986
+ SignatureEnvelope.assert({
2987
+ type: 'multisig',
2988
+ account,
2989
+ signatures: Array.from(
2990
+ { length: MultisigConfig.maxSignatures + 1 },
2991
+ () => innerP256,
2992
+ ),
2993
+ }),
2994
+ ).toThrowErrorMatchingInlineSnapshot(
2995
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig signatures exceed 8.]`,
2996
+ )
2997
+ })
2998
+
2999
+ test('assert: rejects oversized owner approvals', () => {
3000
+ const oversized = {
3001
+ ...signature_webauthn,
3002
+ metadata: {
3003
+ ...signature_webauthn.metadata,
3004
+ clientDataJSON: JSON.stringify({ value: 'x'.repeat(2048) }),
3005
+ },
3006
+ }
3007
+ expect(() =>
3008
+ SignatureEnvelope.assert({
3009
+ type: 'multisig',
3010
+ account,
3011
+ signatures: [oversized],
3012
+ }),
3013
+ ).toThrowErrorMatchingInlineSnapshot(
3014
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig owner signature exceeds 2049 bytes.]`,
3015
+ )
3016
+ })
3017
+
3018
+ test('assert: rejects keychain owner approvals', () => {
3019
+ expect(() =>
3020
+ SignatureEnvelope.assert({
3021
+ type: 'multisig',
3022
+ account,
3023
+ signatures: [signature_keychain_secp256k1],
3024
+ } as never),
3025
+ ).toThrowErrorMatchingInlineSnapshot(
3026
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: keychain owner approvals are not allowed.]`,
3027
+ )
3028
+ })
3029
+
3030
+ describe('nested approvals', () => {
3031
+ const nestedAccount = '0x1111111111111111111111111111111111111111'
3032
+ const nested = SignatureEnvelope.from({
3033
+ type: 'multisig',
3034
+ account: nestedAccount,
3035
+ signatures: [innerP256],
3036
+ })
3037
+
3038
+ test('serialize/deserialize round-trip with a nested multisig approval', () => {
3039
+ const parent = SignatureEnvelope.from({
3040
+ type: 'multisig',
3041
+ account,
3042
+ signatures: [innerP256, nested],
3043
+ })
3044
+ const serialized = SignatureEnvelope.serialize(parent)
3045
+ expect(SignatureEnvelope.deserialize(serialized)).toEqual(parent)
3046
+ })
3047
+
3048
+ test('assert: accepts the maximum nesting depth', () => {
3049
+ const depth2 = SignatureEnvelope.from({
3050
+ type: 'multisig',
3051
+ account,
3052
+ signatures: [nested],
3053
+ })
3054
+ expect(() => SignatureEnvelope.assert(depth2)).not.toThrowError()
3055
+ })
3056
+
3057
+ test('assert: rejects nesting deeper than the maximum', () => {
3058
+ const depth3 = SignatureEnvelope.from({
3059
+ type: 'multisig',
3060
+ account,
3061
+ signatures: [
3062
+ SignatureEnvelope.from({
3063
+ type: 'multisig',
3064
+ account: nestedAccount,
3065
+ signatures: [nested],
3066
+ }),
3067
+ ],
3068
+ })
3069
+ expect(() =>
3070
+ SignatureEnvelope.assert(depth3),
3071
+ ).toThrowErrorMatchingInlineSnapshot(
3072
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig nesting depth exceeds 2.]`,
3073
+ )
3074
+ })
3075
+
3076
+ test('deserialize: rejects nesting deeper than the maximum during decode', () => {
3077
+ const primitive = SignatureEnvelope.serialize(innerP256)
3078
+ const depth1 = Hex.concat(
3079
+ '0x05',
3080
+ Rlp.fromHex([nestedAccount, [primitive]]),
3081
+ )
3082
+ const depth2 = Hex.concat('0x05', Rlp.fromHex([nestedAccount, [depth1]]))
3083
+ const depth3 = Hex.concat('0x05', Rlp.fromHex([account, [depth2]]))
3084
+
3085
+ expect(() => SignatureEnvelope.deserialize(depth3)).toThrowError(
3086
+ SignatureEnvelope.InvalidSerializedError,
3087
+ )
3088
+ })
3089
+
3090
+ test('assert: rejects nested approvals carrying `init`', () => {
3091
+ expect(() =>
3092
+ SignatureEnvelope.assert({
3093
+ type: 'multisig',
3094
+ account,
3095
+ signatures: [
3096
+ {
3097
+ ...nested,
3098
+ init: {
3099
+ threshold: 1,
3100
+ owners: [{ owner: nestedAccount, weight: 1 }],
3101
+ },
3102
+ },
3103
+ ],
3104
+ } as never),
3105
+ ).toThrowErrorMatchingInlineSnapshot(
3106
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: nested multisig owner approvals cannot carry \`init\`.]`,
3107
+ )
3108
+ })
3109
+ })
3110
+
2911
3111
  describe('init (bootstrap)', () => {
2912
3112
  const init = {
2913
3113
  salt: `0x${'00'.repeat(32)}` as const,
@@ -2919,11 +3119,11 @@ describe('multisig', () => {
2919
3119
  },
2920
3120
  ],
2921
3121
  }
3122
+ const bootstrapAccount = MultisigConfig.getAddress(init)
2922
3123
 
2923
3124
  const bootstrapEnvelope = SignatureEnvelope.from({
2924
3125
  type: 'multisig',
2925
- account,
2926
- genesisConfigId,
3126
+ account: bootstrapAccount,
2927
3127
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
2928
3128
  init,
2929
3129
  })
@@ -2935,13 +3135,68 @@ describe('multisig', () => {
2935
3135
  )
2936
3136
  })
2937
3137
 
3138
+ test('deserialize: validates bootstrap init semantics', () => {
3139
+ const invalid = Hex.concat(
3140
+ '0x05',
3141
+ Rlp.fromHex([
3142
+ [MultisigConfig.zeroSalt, '0x01', []],
3143
+ [SignatureEnvelope.serialize(innerP256)],
3144
+ ]),
3145
+ )
3146
+ expect(() => SignatureEnvelope.deserialize(invalid)).toThrowError(
3147
+ MultisigConfig.InvalidConfigError,
3148
+ )
3149
+ })
3150
+
3151
+ test('deserialize: rejects noncanonical bootstrap field widths', () => {
3152
+ const signatures = [SignatureEnvelope.serialize(innerP256)]
3153
+ const shortSalt = Hex.concat(
3154
+ '0x05',
3155
+ Rlp.fromHex([
3156
+ [`0x${'00'.repeat(31)}`, '0x01', [[init.owners[0]!.owner, '0x01']]],
3157
+ signatures,
3158
+ ]),
3159
+ )
3160
+ const wideThreshold = Hex.concat(
3161
+ '0x05',
3162
+ Rlp.fromHex([
3163
+ [init.salt, '0x0001', [[init.owners[0]!.owner, '0x01']]],
3164
+ signatures,
3165
+ ]),
3166
+ )
3167
+ const wideWeight = Hex.concat(
3168
+ '0x05',
3169
+ Rlp.fromHex([
3170
+ [init.salt, '0x01', [[init.owners[0]!.owner, '0x0001']]],
3171
+ signatures,
3172
+ ]),
3173
+ )
3174
+
3175
+ for (const serialized of [shortSalt, wideThreshold, wideWeight])
3176
+ expect(() => SignatureEnvelope.deserialize(serialized)).toThrowError(
3177
+ SignatureEnvelope.InvalidSerializedError,
3178
+ )
3179
+ })
3180
+
3181
+ test('serialize: wire shape is `0x05 || rlp([init, signatures])`', () => {
3182
+ const serialized = SignatureEnvelope.serialize(bootstrapEnvelope)
3183
+ const [address] = Rlp.toHex(Hex.slice(serialized, 1)) as readonly [
3184
+ MultisigConfig.Tuple,
3185
+ readonly Hex.Hex[],
3186
+ ]
3187
+ expect(MultisigConfig.fromTuple(address)).toEqual(init)
3188
+ })
3189
+
2938
3190
  test('serialize/deserialize round-trip preserves non-zero salt', () => {
3191
+ const saltedInit = {
3192
+ ...init,
3193
+ salt: `0x${'42'.repeat(32)}` as const,
3194
+ }
2939
3195
  const salted = SignatureEnvelope.from({
2940
3196
  type: 'multisig',
2941
- account,
2942
- genesisConfigId,
3197
+ account: MultisigConfig.getAddress(saltedInit),
2943
3198
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
2944
- init: { ...init, salt: `0x${'42'.repeat(32)}` },
3199
+ init: saltedInit,
2945
3200
  })
2946
3201
  const serialized = SignatureEnvelope.serialize(salted)
2947
3202
  const deserialized = SignatureEnvelope.deserialize(
@@ -2964,27 +3219,52 @@ describe('multisig', () => {
2964
3219
  })
2965
3220
 
2966
3221
  test('toRpc/fromRpc round-trip with init', () => {
2967
- const rpc = SignatureEnvelope.toRpc(bootstrapEnvelope)
3222
+ const rpc = SignatureEnvelope.toRpc(
3223
+ bootstrapEnvelope,
3224
+ ) as SignatureEnvelope.MultisigRpc
2968
3225
  expect(rpc.init).toEqual(init)
3226
+ expect(rpc.account).toBeUndefined()
3227
+ expect('type' in rpc).toBe(false)
2969
3228
  expect(SignatureEnvelope.fromRpc(rpc)).toEqual(bootstrapEnvelope)
2970
3229
  })
2971
3230
 
2972
- test('toRpc encodes owner approvals as serialized hex (node `Vec<Bytes>`)', () => {
3231
+ test('toRpc encodes owner approvals as structured RPC envelopes', () => {
2973
3232
  const multisig = bootstrapEnvelope as SignatureEnvelope.Multisig
2974
3233
  const rpc = SignatureEnvelope.toRpc(
2975
3234
  multisig,
2976
3235
  ) as SignatureEnvelope.MultisigRpc
2977
3236
  expect(rpc.signatures).toEqual(
2978
- multisig.signatures.map((s) => SignatureEnvelope.serialize(s)),
3237
+ multisig.signatures.map((signature) =>
3238
+ SignatureEnvelope.toRpc(signature),
3239
+ ),
2979
3240
  )
2980
3241
  })
2981
3242
 
2982
3243
  test('fromRpc detects multisig by shape (no `type` field)', () => {
2983
3244
  const rpc = SignatureEnvelope.toRpc(bootstrapEnvelope)
2984
- // The node omits the `type` discriminant; detection is shape-based.
2985
- const { type: _type, ...untyped } = rpc
2986
- expect(SignatureEnvelope.fromRpc(untyped as never)).toEqual(
3245
+ expect(SignatureEnvelope.fromRpc(rpc)).toEqual(bootstrapEnvelope)
3246
+ })
3247
+
3248
+ test('fromRpc accepts the opposite static property as undefined', () => {
3249
+ const rpc = SignatureEnvelope.toRpc(
2987
3250
  bootstrapEnvelope,
3251
+ ) as SignatureEnvelope.MultisigRpc
3252
+ expect(
3253
+ SignatureEnvelope.fromRpc({ ...rpc, account: undefined } as never),
3254
+ ).toEqual(bootstrapEnvelope)
3255
+ })
3256
+
3257
+ test('fromRpc rejects the legacy combined account and init shape', () => {
3258
+ const rpc = SignatureEnvelope.toRpc(
3259
+ bootstrapEnvelope,
3260
+ ) as SignatureEnvelope.MultisigRpc
3261
+ expect(() =>
3262
+ SignatureEnvelope.fromRpc({
3263
+ ...rpc,
3264
+ account: bootstrapAccount,
3265
+ } as never),
3266
+ ).toThrowErrorMatchingInlineSnapshot(
3267
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: RPC multisig must contain exactly one of \`account\` or \`init\`.]`,
2988
3268
  )
2989
3269
  })
2990
3270
 
@@ -2992,12 +3272,24 @@ describe('multisig', () => {
2992
3272
  expect(() =>
2993
3273
  SignatureEnvelope.assert({
2994
3274
  type: 'multisig',
2995
- account,
2996
- genesisConfigId,
2997
- signatures: [],
3275
+ account: bootstrapAccount,
3276
+ signatures: [innerP256],
2998
3277
  init: { threshold: 1, owners: [] },
2999
3278
  } as never),
3000
3279
  ).toThrowError()
3001
3280
  })
3281
+
3282
+ test('assert: init must derive the supplied account', () => {
3283
+ expect(() =>
3284
+ SignatureEnvelope.assert({
3285
+ type: 'multisig',
3286
+ account,
3287
+ signatures: [innerP256],
3288
+ init,
3289
+ }),
3290
+ ).toThrowErrorMatchingInlineSnapshot(
3291
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig init does not derive account.]`,
3292
+ )
3293
+ })
3002
3294
  })
3003
3295
  })