ox 0.14.30 → 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 (51) hide show
  1. package/CHANGELOG.md +12 -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 +12 -2
  9. package/_cjs/tempo/MultisigConfig.js.map +1 -1
  10. package/_cjs/tempo/SignatureEnvelope.js +167 -46
  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 +16 -4
  20. package/_esm/tempo/MultisigConfig.js.map +1 -1
  21. package/_esm/tempo/SignatureEnvelope.js +172 -55
  22. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  23. package/_esm/version.js +1 -1
  24. package/_types/core/AbiParameters.d.ts.map +1 -1
  25. package/_types/core/internal/abiParameters.d.ts.map +1 -1
  26. package/_types/core/internal/rpcSchemas/eth.d.ts +16 -0
  27. package/_types/core/internal/rpcSchemas/eth.d.ts.map +1 -1
  28. package/_types/tempo/KeyAuthorization.d.ts +22 -16
  29. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  30. package/_types/tempo/MultisigConfig.d.ts +8 -4
  31. package/_types/tempo/MultisigConfig.d.ts.map +1 -1
  32. package/_types/tempo/SignatureEnvelope.d.ts +33 -13
  33. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  34. package/_types/version.d.ts +1 -1
  35. package/core/AbiParameters.ts +3 -1
  36. package/core/internal/abiParameters.ts +17 -2
  37. package/core/internal/rpcSchemas/eth.ts +16 -0
  38. package/package.json +11 -1
  39. package/tempo/KeyAuthorization.test-d/package.json +6 -0
  40. package/tempo/KeyAuthorization.test-d.ts +62 -0
  41. package/tempo/KeyAuthorization.test.ts +132 -0
  42. package/tempo/KeyAuthorization.ts +76 -29
  43. package/tempo/MultisigConfig.test.ts +34 -2
  44. package/tempo/MultisigConfig.ts +18 -4
  45. package/tempo/SignatureEnvelope.test-d/package.json +6 -0
  46. package/tempo/SignatureEnvelope.test-d.ts +97 -0
  47. package/tempo/SignatureEnvelope.test.ts +227 -36
  48. package/tempo/SignatureEnvelope.ts +276 -85
  49. package/tempo/e2e.test.ts +0 -416
  50. package/tempo/multisig.e2e.test.ts +582 -0
  51. package/version.ts +1 -1
@@ -2886,18 +2886,45 @@ describe('multisig', () => {
2886
2886
  expect(SignatureEnvelope.deserialize(serialized)).toEqual(envelope)
2887
2887
  })
2888
2888
 
2889
- test('deserialize: rejects the empty `init` placeholder (`0x80`)', () => {
2890
- const withPlaceholder = Hex.concat(
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(
2891
2907
  '0x05',
2892
- Rlp.fromHex([account, [SignatureEnvelope.serialize(innerP256)], '0x']),
2908
+ Rlp.fromHex(['0x11', [SignatureEnvelope.serialize(innerP256)]]),
2893
2909
  )
2894
- expect(() => SignatureEnvelope.deserialize(withPlaceholder)).toThrowError(
2910
+ expect(() => SignatureEnvelope.deserialize(malformed)).toThrowError(
2895
2911
  SignatureEnvelope.InvalidSerializedError,
2896
2912
  )
2897
2913
  })
2898
2914
 
2899
2915
  test('getType', () => {
2900
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')
2901
2928
  })
2902
2929
 
2903
2930
  test('extractAddress returns the multisig account', () => {
@@ -2910,9 +2937,19 @@ describe('multisig', () => {
2910
2937
  })
2911
2938
 
2912
2939
  test('toRpc/fromRpc round-trip', () => {
2913
- const rpc = SignatureEnvelope.toRpc(envelope)
2914
- 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)
2915
2949
  expect(SignatureEnvelope.fromRpc(rpc)).toEqual(envelope)
2950
+ expect(
2951
+ SignatureEnvelope.fromRpc({ ...rpc, type: 'multisig' } as never),
2952
+ ).toEqual(envelope)
2916
2953
  })
2917
2954
 
2918
2955
  test('assert: missing properties', () => {
@@ -2921,6 +2958,63 @@ describe('multisig', () => {
2921
2958
  ).toThrowError()
2922
2959
  })
2923
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
+
2924
3018
  test('assert: rejects keychain owner approvals', () => {
2925
3019
  expect(() =>
2926
3020
  SignatureEnvelope.assert({
@@ -2952,42 +3046,44 @@ describe('multisig', () => {
2952
3046
  })
2953
3047
 
2954
3048
  test('assert: accepts the maximum nesting depth', () => {
2955
- const depth3 = SignatureEnvelope.from({
3049
+ const depth2 = SignatureEnvelope.from({
2956
3050
  type: 'multisig',
2957
3051
  account,
2958
- signatures: [
2959
- SignatureEnvelope.from({
2960
- type: 'multisig',
2961
- account: nestedAccount,
2962
- signatures: [nested],
2963
- }),
2964
- ],
3052
+ signatures: [nested],
2965
3053
  })
2966
- expect(() => SignatureEnvelope.assert(depth3)).not.toThrowError()
3054
+ expect(() => SignatureEnvelope.assert(depth2)).not.toThrowError()
2967
3055
  })
2968
3056
 
2969
3057
  test('assert: rejects nesting deeper than the maximum', () => {
2970
- const depth4 = SignatureEnvelope.from({
3058
+ const depth3 = SignatureEnvelope.from({
2971
3059
  type: 'multisig',
2972
3060
  account,
2973
3061
  signatures: [
2974
3062
  SignatureEnvelope.from({
2975
3063
  type: 'multisig',
2976
3064
  account: nestedAccount,
2977
- signatures: [
2978
- SignatureEnvelope.from({
2979
- type: 'multisig',
2980
- account: nestedAccount,
2981
- signatures: [nested],
2982
- }),
2983
- ],
3065
+ signatures: [nested],
2984
3066
  }),
2985
3067
  ],
2986
3068
  })
2987
3069
  expect(() =>
2988
- SignatureEnvelope.assert(depth4),
3070
+ SignatureEnvelope.assert(depth3),
2989
3071
  ).toThrowErrorMatchingInlineSnapshot(
2990
- `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig nesting depth exceeds 3.]`,
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,
2991
3087
  )
2992
3088
  })
2993
3089
 
@@ -3023,10 +3119,11 @@ describe('multisig', () => {
3023
3119
  },
3024
3120
  ],
3025
3121
  }
3122
+ const bootstrapAccount = MultisigConfig.getAddress(init)
3026
3123
 
3027
3124
  const bootstrapEnvelope = SignatureEnvelope.from({
3028
3125
  type: 'multisig',
3029
- account,
3126
+ account: bootstrapAccount,
3030
3127
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
3031
3128
  init,
3032
3129
  })
@@ -3038,12 +3135,68 @@ describe('multisig', () => {
3038
3135
  )
3039
3136
  })
3040
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
+
3041
3190
  test('serialize/deserialize round-trip preserves non-zero salt', () => {
3191
+ const saltedInit = {
3192
+ ...init,
3193
+ salt: `0x${'42'.repeat(32)}` as const,
3194
+ }
3042
3195
  const salted = SignatureEnvelope.from({
3043
3196
  type: 'multisig',
3044
- account,
3197
+ account: MultisigConfig.getAddress(saltedInit),
3045
3198
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
3046
- init: { ...init, salt: `0x${'42'.repeat(32)}` },
3199
+ init: saltedInit,
3047
3200
  })
3048
3201
  const serialized = SignatureEnvelope.serialize(salted)
3049
3202
  const deserialized = SignatureEnvelope.deserialize(
@@ -3066,27 +3219,52 @@ describe('multisig', () => {
3066
3219
  })
3067
3220
 
3068
3221
  test('toRpc/fromRpc round-trip with init', () => {
3069
- const rpc = SignatureEnvelope.toRpc(bootstrapEnvelope)
3222
+ const rpc = SignatureEnvelope.toRpc(
3223
+ bootstrapEnvelope,
3224
+ ) as SignatureEnvelope.MultisigRpc
3070
3225
  expect(rpc.init).toEqual(init)
3226
+ expect(rpc.account).toBeUndefined()
3227
+ expect('type' in rpc).toBe(false)
3071
3228
  expect(SignatureEnvelope.fromRpc(rpc)).toEqual(bootstrapEnvelope)
3072
3229
  })
3073
3230
 
3074
- test('toRpc encodes owner approvals as serialized hex (node `Vec<Bytes>`)', () => {
3231
+ test('toRpc encodes owner approvals as structured RPC envelopes', () => {
3075
3232
  const multisig = bootstrapEnvelope as SignatureEnvelope.Multisig
3076
3233
  const rpc = SignatureEnvelope.toRpc(
3077
3234
  multisig,
3078
3235
  ) as SignatureEnvelope.MultisigRpc
3079
3236
  expect(rpc.signatures).toEqual(
3080
- multisig.signatures.map((s) => SignatureEnvelope.serialize(s)),
3237
+ multisig.signatures.map((signature) =>
3238
+ SignatureEnvelope.toRpc(signature),
3239
+ ),
3081
3240
  )
3082
3241
  })
3083
3242
 
3084
3243
  test('fromRpc detects multisig by shape (no `type` field)', () => {
3085
3244
  const rpc = SignatureEnvelope.toRpc(bootstrapEnvelope)
3086
- // The node omits the `type` discriminant; detection is shape-based.
3087
- const { type: _type, ...untyped } = rpc
3088
- 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(
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(
3089
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\`.]`,
3090
3268
  )
3091
3269
  })
3092
3270
 
@@ -3094,11 +3272,24 @@ describe('multisig', () => {
3094
3272
  expect(() =>
3095
3273
  SignatureEnvelope.assert({
3096
3274
  type: 'multisig',
3097
- account,
3098
- signatures: [],
3275
+ account: bootstrapAccount,
3276
+ signatures: [innerP256],
3099
3277
  init: { threshold: 1, owners: [] },
3100
3278
  } as never),
3101
3279
  ).toThrowError()
3102
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
+ })
3103
3294
  })
3104
3295
  })