ox 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/tempo/KeyAuthorization.d.ts +22 -16
- package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/KeyAuthorization.js +31 -7
- package/dist/tempo/KeyAuthorization.js.map +1 -1
- package/dist/tempo/MultisigConfig.d.ts +8 -4
- package/dist/tempo/MultisigConfig.d.ts.map +1 -1
- package/dist/tempo/MultisigConfig.js +16 -4
- package/dist/tempo/MultisigConfig.js.map +1 -1
- package/dist/tempo/SignatureEnvelope.d.ts +31 -13
- package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/tempo/SignatureEnvelope.js +172 -55
- package/dist/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/zod/tempo/AuthorizationTempo.d.ts +70 -60
- package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.d.ts +0 -306
- package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.js +2 -2
- package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
- package/dist/zod/tempo/MultisigConfig.d.ts.map +1 -1
- package/dist/zod/tempo/MultisigConfig.js +5 -2
- package/dist/zod/tempo/MultisigConfig.js.map +1 -1
- package/dist/zod/tempo/RpcSchemaTempo.d.ts +28 -132
- package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.d.ts +79 -18
- package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.js +39 -11
- package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/zod/tempo/Transaction.d.ts +84 -396
- package/dist/zod/tempo/Transaction.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.d.ts +42 -234
- package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts +0 -108
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/tempo/KeyAuthorization.test-d.ts +62 -0
- package/src/tempo/KeyAuthorization.test.ts +53 -0
- package/src/tempo/KeyAuthorization.ts +69 -27
- package/src/tempo/MultisigConfig.test.ts +34 -2
- package/src/tempo/MultisigConfig.ts +18 -4
- package/src/tempo/SignatureEnvelope.test-d.ts +97 -0
- package/src/tempo/SignatureEnvelope.test.ts +247 -36
- package/src/tempo/SignatureEnvelope.ts +271 -85
- package/src/tempo/e2e.test.ts +0 -416
- package/src/tempo/multisig.e2e.test.ts +582 -0
- package/src/version.ts +1 -1
- package/src/zod/tempo/KeyAuthorization.ts +2 -2
- package/src/zod/tempo/MultisigConfig.ts +13 -5
- package/src/zod/tempo/SignatureEnvelope.ts +81 -18
- package/src/zod/tempo/_test/KeyAuthorization.test.ts +25 -0
- package/src/zod/tempo/_test/MultisigConfig.test.ts +44 -0
- package/src/zod/tempo/_test/SignatureEnvelope.test-d.ts +9 -0
- package/src/zod/tempo/_test/SignatureEnvelope.test.ts +142 -6
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { expectTypeOf, test } from 'vp/test'
|
|
2
|
+
import * as SignatureEnvelope from './SignatureEnvelope.js'
|
|
3
|
+
|
|
4
|
+
const signature = {
|
|
5
|
+
r: '0x01',
|
|
6
|
+
s: '0x02',
|
|
7
|
+
type: 'secp256k1',
|
|
8
|
+
yParity: '0x0',
|
|
9
|
+
} as const satisfies SignatureEnvelope.SignatureEnvelopeRpc
|
|
10
|
+
|
|
11
|
+
const signatureDomain = {
|
|
12
|
+
signature: {
|
|
13
|
+
r: '0x01',
|
|
14
|
+
s: '0x02',
|
|
15
|
+
yParity: 0,
|
|
16
|
+
},
|
|
17
|
+
type: 'secp256k1',
|
|
18
|
+
} as const satisfies SignatureEnvelope.Secp256k1
|
|
19
|
+
|
|
20
|
+
test('toRpc preserves the signature type', () => {
|
|
21
|
+
expectTypeOf(
|
|
22
|
+
SignatureEnvelope.toRpc(signatureDomain),
|
|
23
|
+
).toEqualTypeOf<SignatureEnvelope.Secp256k1Rpc>()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('MultisigRpc uses static initialized and bootstrap shapes', () => {
|
|
27
|
+
const initialized = {
|
|
28
|
+
account: '0x1111111111111111111111111111111111111111',
|
|
29
|
+
signatures: [signature],
|
|
30
|
+
} as const satisfies SignatureEnvelope.MultisigRpc
|
|
31
|
+
const bootstrap = {
|
|
32
|
+
init: {
|
|
33
|
+
owners: [
|
|
34
|
+
{
|
|
35
|
+
owner: '0x1111111111111111111111111111111111111111',
|
|
36
|
+
weight: 1,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
threshold: 1,
|
|
40
|
+
},
|
|
41
|
+
signatures: [signature],
|
|
42
|
+
} as const satisfies SignatureEnvelope.MultisigRpc
|
|
43
|
+
const bootstrapWithAccountUndefined: SignatureEnvelope.MultisigRpc = {
|
|
44
|
+
...bootstrap,
|
|
45
|
+
account: undefined,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
expectTypeOf(initialized.signatures).toMatchTypeOf<
|
|
49
|
+
readonly SignatureEnvelope.SignatureEnvelopeRpc[]
|
|
50
|
+
>()
|
|
51
|
+
expectTypeOf(bootstrap.signatures).toMatchTypeOf<
|
|
52
|
+
readonly SignatureEnvelope.SignatureEnvelopeRpc[]
|
|
53
|
+
>()
|
|
54
|
+
expectTypeOf(
|
|
55
|
+
bootstrapWithAccountUndefined,
|
|
56
|
+
).toMatchTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
57
|
+
expectTypeOf<
|
|
58
|
+
SignatureEnvelope.GetType<typeof bootstrap>
|
|
59
|
+
>().toEqualTypeOf<'multisig'>()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('MultisigRpc rejects legacy shapes', () => {
|
|
63
|
+
const combined = {
|
|
64
|
+
account: '0x1111111111111111111111111111111111111111',
|
|
65
|
+
init: {
|
|
66
|
+
owners: [
|
|
67
|
+
{
|
|
68
|
+
owner: '0x1111111111111111111111111111111111111111',
|
|
69
|
+
weight: 1,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
threshold: 1,
|
|
73
|
+
},
|
|
74
|
+
signatures: [signature],
|
|
75
|
+
} as const
|
|
76
|
+
// @ts-expect-error Bootstrap RPC signatures omit `account`.
|
|
77
|
+
const combinedRpc: SignatureEnvelope.MultisigRpc = combined
|
|
78
|
+
|
|
79
|
+
const serialized = {
|
|
80
|
+
account: '0x1111111111111111111111111111111111111111',
|
|
81
|
+
signatures: ['0x1234'],
|
|
82
|
+
} as const
|
|
83
|
+
// @ts-expect-error Owner approvals use structured RPC envelopes.
|
|
84
|
+
const serializedRpc: SignatureEnvelope.MultisigRpc = serialized
|
|
85
|
+
|
|
86
|
+
const tagged = {
|
|
87
|
+
account: '0x1111111111111111111111111111111111111111',
|
|
88
|
+
signatures: [signature],
|
|
89
|
+
type: 'multisig',
|
|
90
|
+
} as const
|
|
91
|
+
// @ts-expect-error Multisig RPC signatures are untagged.
|
|
92
|
+
const taggedRpc: SignatureEnvelope.MultisigRpc = tagged
|
|
93
|
+
|
|
94
|
+
expectTypeOf(combinedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
95
|
+
expectTypeOf(serializedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
96
|
+
expectTypeOf(taggedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
97
|
+
})
|
|
@@ -2914,18 +2914,45 @@ describe('multisig', () => {
|
|
|
2914
2914
|
expect(SignatureEnvelope.deserialize(serialized)).toEqual(envelope)
|
|
2915
2915
|
})
|
|
2916
2916
|
|
|
2917
|
-
test('deserialize: rejects the
|
|
2918
|
-
const
|
|
2917
|
+
test('deserialize: rejects the legacy three-field wire shape', () => {
|
|
2918
|
+
const init = MultisigConfig.toTuple({
|
|
2919
|
+
threshold: 1,
|
|
2920
|
+
owners: [
|
|
2921
|
+
{ owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
2922
|
+
],
|
|
2923
|
+
})
|
|
2924
|
+
const legacy = Hex.concat(
|
|
2919
2925
|
'0x05',
|
|
2920
|
-
Rlp.fromHex([account, [SignatureEnvelope.serialize(innerP256)],
|
|
2926
|
+
Rlp.fromHex([account, [SignatureEnvelope.serialize(innerP256)], init]),
|
|
2921
2927
|
)
|
|
2922
|
-
expect(() => SignatureEnvelope.deserialize(
|
|
2928
|
+
expect(() => SignatureEnvelope.deserialize(legacy)).toThrowError(
|
|
2929
|
+
SignatureEnvelope.InvalidSerializedError,
|
|
2930
|
+
)
|
|
2931
|
+
})
|
|
2932
|
+
|
|
2933
|
+
test('deserialize: rejects an invalid initialized account', () => {
|
|
2934
|
+
const malformed = Hex.concat(
|
|
2935
|
+
'0x05',
|
|
2936
|
+
Rlp.fromHex(['0x11', [SignatureEnvelope.serialize(innerP256)]]),
|
|
2937
|
+
)
|
|
2938
|
+
expect(() => SignatureEnvelope.deserialize(malformed)).toThrowError(
|
|
2923
2939
|
SignatureEnvelope.InvalidSerializedError,
|
|
2924
2940
|
)
|
|
2925
2941
|
})
|
|
2926
2942
|
|
|
2927
2943
|
test('getType', () => {
|
|
2928
2944
|
expect(SignatureEnvelope.getType(envelope)).toBe('multisig')
|
|
2945
|
+
expect(
|
|
2946
|
+
SignatureEnvelope.getType({
|
|
2947
|
+
init: {
|
|
2948
|
+
threshold: 1,
|
|
2949
|
+
owners: [
|
|
2950
|
+
{ owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
2951
|
+
],
|
|
2952
|
+
},
|
|
2953
|
+
signatures: [innerP256],
|
|
2954
|
+
} as never),
|
|
2955
|
+
).toBe('multisig')
|
|
2929
2956
|
})
|
|
2930
2957
|
|
|
2931
2958
|
test('extractAddress returns the multisig account', () => {
|
|
@@ -2938,9 +2965,19 @@ describe('multisig', () => {
|
|
|
2938
2965
|
})
|
|
2939
2966
|
|
|
2940
2967
|
test('toRpc/fromRpc round-trip', () => {
|
|
2941
|
-
const rpc = SignatureEnvelope.toRpc(
|
|
2942
|
-
|
|
2968
|
+
const rpc = SignatureEnvelope.toRpc(
|
|
2969
|
+
envelope,
|
|
2970
|
+
) as SignatureEnvelope.MultisigRpc
|
|
2971
|
+
expect(rpc).toMatchObject({
|
|
2972
|
+
account,
|
|
2973
|
+
signatures: [{ type: 'secp256k1' }, { type: 'p256' }],
|
|
2974
|
+
})
|
|
2975
|
+
expect(rpc.init).toBeUndefined()
|
|
2976
|
+
expect('type' in rpc).toBe(false)
|
|
2943
2977
|
expect(SignatureEnvelope.fromRpc(rpc)).toEqual(envelope)
|
|
2978
|
+
expect(
|
|
2979
|
+
SignatureEnvelope.fromRpc({ ...rpc, type: 'multisig' } as never),
|
|
2980
|
+
).toEqual(envelope)
|
|
2944
2981
|
})
|
|
2945
2982
|
|
|
2946
2983
|
test('assert: missing properties', () => {
|
|
@@ -2949,6 +2986,63 @@ describe('multisig', () => {
|
|
|
2949
2986
|
).toThrowError()
|
|
2950
2987
|
})
|
|
2951
2988
|
|
|
2989
|
+
test('assert: rejects empty owner approvals', () => {
|
|
2990
|
+
expect(() =>
|
|
2991
|
+
SignatureEnvelope.assert({
|
|
2992
|
+
type: 'multisig',
|
|
2993
|
+
account,
|
|
2994
|
+
signatures: [],
|
|
2995
|
+
}),
|
|
2996
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
2997
|
+
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig signatures cannot be empty.]`,
|
|
2998
|
+
)
|
|
2999
|
+
})
|
|
3000
|
+
|
|
3001
|
+
test('assert: accepts at most eight owner approvals', () => {
|
|
3002
|
+
expect(() =>
|
|
3003
|
+
SignatureEnvelope.assert({
|
|
3004
|
+
type: 'multisig',
|
|
3005
|
+
account,
|
|
3006
|
+
signatures: Array.from(
|
|
3007
|
+
{ length: MultisigConfig.maxSignatures },
|
|
3008
|
+
() => innerP256,
|
|
3009
|
+
),
|
|
3010
|
+
}),
|
|
3011
|
+
).not.toThrow()
|
|
3012
|
+
|
|
3013
|
+
expect(() =>
|
|
3014
|
+
SignatureEnvelope.assert({
|
|
3015
|
+
type: 'multisig',
|
|
3016
|
+
account,
|
|
3017
|
+
signatures: Array.from(
|
|
3018
|
+
{ length: MultisigConfig.maxSignatures + 1 },
|
|
3019
|
+
() => innerP256,
|
|
3020
|
+
),
|
|
3021
|
+
}),
|
|
3022
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
3023
|
+
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig signatures exceed 8.]`,
|
|
3024
|
+
)
|
|
3025
|
+
})
|
|
3026
|
+
|
|
3027
|
+
test('assert: rejects oversized owner approvals', () => {
|
|
3028
|
+
const oversized = {
|
|
3029
|
+
...signature_webauthn,
|
|
3030
|
+
metadata: {
|
|
3031
|
+
...signature_webauthn.metadata,
|
|
3032
|
+
clientDataJSON: JSON.stringify({ value: 'x'.repeat(2048) }),
|
|
3033
|
+
},
|
|
3034
|
+
}
|
|
3035
|
+
expect(() =>
|
|
3036
|
+
SignatureEnvelope.assert({
|
|
3037
|
+
type: 'multisig',
|
|
3038
|
+
account,
|
|
3039
|
+
signatures: [oversized],
|
|
3040
|
+
}),
|
|
3041
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
3042
|
+
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig owner signature exceeds 2049 bytes.]`,
|
|
3043
|
+
)
|
|
3044
|
+
})
|
|
3045
|
+
|
|
2952
3046
|
test('assert: rejects keychain owner approvals', () => {
|
|
2953
3047
|
expect(() =>
|
|
2954
3048
|
SignatureEnvelope.assert({
|
|
@@ -2980,42 +3074,44 @@ describe('multisig', () => {
|
|
|
2980
3074
|
})
|
|
2981
3075
|
|
|
2982
3076
|
test('assert: accepts the maximum nesting depth', () => {
|
|
2983
|
-
const
|
|
3077
|
+
const depth2 = SignatureEnvelope.from({
|
|
2984
3078
|
type: 'multisig',
|
|
2985
3079
|
account,
|
|
2986
|
-
signatures: [
|
|
2987
|
-
SignatureEnvelope.from({
|
|
2988
|
-
type: 'multisig',
|
|
2989
|
-
account: nestedAccount,
|
|
2990
|
-
signatures: [nested],
|
|
2991
|
-
}),
|
|
2992
|
-
],
|
|
3080
|
+
signatures: [nested],
|
|
2993
3081
|
})
|
|
2994
|
-
expect(() => SignatureEnvelope.assert(
|
|
3082
|
+
expect(() => SignatureEnvelope.assert(depth2)).not.toThrowError()
|
|
2995
3083
|
})
|
|
2996
3084
|
|
|
2997
3085
|
test('assert: rejects nesting deeper than the maximum', () => {
|
|
2998
|
-
const
|
|
3086
|
+
const depth3 = SignatureEnvelope.from({
|
|
2999
3087
|
type: 'multisig',
|
|
3000
3088
|
account,
|
|
3001
3089
|
signatures: [
|
|
3002
3090
|
SignatureEnvelope.from({
|
|
3003
3091
|
type: 'multisig',
|
|
3004
3092
|
account: nestedAccount,
|
|
3005
|
-
signatures: [
|
|
3006
|
-
SignatureEnvelope.from({
|
|
3007
|
-
type: 'multisig',
|
|
3008
|
-
account: nestedAccount,
|
|
3009
|
-
signatures: [nested],
|
|
3010
|
-
}),
|
|
3011
|
-
],
|
|
3093
|
+
signatures: [nested],
|
|
3012
3094
|
}),
|
|
3013
3095
|
],
|
|
3014
3096
|
})
|
|
3015
3097
|
expect(() =>
|
|
3016
|
-
SignatureEnvelope.assert(
|
|
3098
|
+
SignatureEnvelope.assert(depth3),
|
|
3017
3099
|
).toThrowErrorMatchingInlineSnapshot(
|
|
3018
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig nesting depth exceeds
|
|
3100
|
+
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig nesting depth exceeds 2.]`,
|
|
3101
|
+
)
|
|
3102
|
+
})
|
|
3103
|
+
|
|
3104
|
+
test('deserialize: rejects nesting deeper than the maximum during decode', () => {
|
|
3105
|
+
const primitive = SignatureEnvelope.serialize(innerP256)
|
|
3106
|
+
const depth1 = Hex.concat(
|
|
3107
|
+
'0x05',
|
|
3108
|
+
Rlp.fromHex([nestedAccount, [primitive]]),
|
|
3109
|
+
)
|
|
3110
|
+
const depth2 = Hex.concat('0x05', Rlp.fromHex([nestedAccount, [depth1]]))
|
|
3111
|
+
const depth3 = Hex.concat('0x05', Rlp.fromHex([account, [depth2]]))
|
|
3112
|
+
|
|
3113
|
+
expect(() => SignatureEnvelope.deserialize(depth3)).toThrowError(
|
|
3114
|
+
SignatureEnvelope.InvalidSerializedError,
|
|
3019
3115
|
)
|
|
3020
3116
|
})
|
|
3021
3117
|
|
|
@@ -3051,10 +3147,11 @@ describe('multisig', () => {
|
|
|
3051
3147
|
},
|
|
3052
3148
|
],
|
|
3053
3149
|
}
|
|
3150
|
+
const bootstrapAccount = MultisigConfig.getAddress(init)
|
|
3054
3151
|
|
|
3055
3152
|
const bootstrapEnvelope = SignatureEnvelope.from({
|
|
3056
3153
|
type: 'multisig',
|
|
3057
|
-
account,
|
|
3154
|
+
account: bootstrapAccount,
|
|
3058
3155
|
signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
|
|
3059
3156
|
init,
|
|
3060
3157
|
})
|
|
@@ -3066,12 +3163,68 @@ describe('multisig', () => {
|
|
|
3066
3163
|
)
|
|
3067
3164
|
})
|
|
3068
3165
|
|
|
3166
|
+
test('deserialize: validates bootstrap init semantics', () => {
|
|
3167
|
+
const invalid = Hex.concat(
|
|
3168
|
+
'0x05',
|
|
3169
|
+
Rlp.fromHex([
|
|
3170
|
+
[MultisigConfig.zeroSalt, '0x01', []],
|
|
3171
|
+
[SignatureEnvelope.serialize(innerP256)],
|
|
3172
|
+
]),
|
|
3173
|
+
)
|
|
3174
|
+
expect(() => SignatureEnvelope.deserialize(invalid)).toThrowError(
|
|
3175
|
+
MultisigConfig.InvalidConfigError,
|
|
3176
|
+
)
|
|
3177
|
+
})
|
|
3178
|
+
|
|
3179
|
+
test('deserialize: rejects noncanonical bootstrap field widths', () => {
|
|
3180
|
+
const signatures = [SignatureEnvelope.serialize(innerP256)]
|
|
3181
|
+
const shortSalt = Hex.concat(
|
|
3182
|
+
'0x05',
|
|
3183
|
+
Rlp.fromHex([
|
|
3184
|
+
[`0x${'00'.repeat(31)}`, '0x01', [[init.owners[0]!.owner, '0x01']]],
|
|
3185
|
+
signatures,
|
|
3186
|
+
]),
|
|
3187
|
+
)
|
|
3188
|
+
const wideThreshold = Hex.concat(
|
|
3189
|
+
'0x05',
|
|
3190
|
+
Rlp.fromHex([
|
|
3191
|
+
[init.salt, '0x0001', [[init.owners[0]!.owner, '0x01']]],
|
|
3192
|
+
signatures,
|
|
3193
|
+
]),
|
|
3194
|
+
)
|
|
3195
|
+
const wideWeight = Hex.concat(
|
|
3196
|
+
'0x05',
|
|
3197
|
+
Rlp.fromHex([
|
|
3198
|
+
[init.salt, '0x01', [[init.owners[0]!.owner, '0x0001']]],
|
|
3199
|
+
signatures,
|
|
3200
|
+
]),
|
|
3201
|
+
)
|
|
3202
|
+
|
|
3203
|
+
for (const serialized of [shortSalt, wideThreshold, wideWeight])
|
|
3204
|
+
expect(() => SignatureEnvelope.deserialize(serialized)).toThrowError(
|
|
3205
|
+
SignatureEnvelope.InvalidSerializedError,
|
|
3206
|
+
)
|
|
3207
|
+
})
|
|
3208
|
+
|
|
3209
|
+
test('serialize: wire shape is `0x05 || rlp([init, signatures])`', () => {
|
|
3210
|
+
const serialized = SignatureEnvelope.serialize(bootstrapEnvelope)
|
|
3211
|
+
const [address] = Rlp.toHex(Hex.slice(serialized, 1)) as readonly [
|
|
3212
|
+
MultisigConfig.Tuple,
|
|
3213
|
+
readonly Hex.Hex[],
|
|
3214
|
+
]
|
|
3215
|
+
expect(MultisigConfig.fromTuple(address)).toEqual(init)
|
|
3216
|
+
})
|
|
3217
|
+
|
|
3069
3218
|
test('serialize/deserialize round-trip preserves non-zero salt', () => {
|
|
3219
|
+
const saltedInit = {
|
|
3220
|
+
...init,
|
|
3221
|
+
salt: `0x${'42'.repeat(32)}` as const,
|
|
3222
|
+
}
|
|
3070
3223
|
const salted = SignatureEnvelope.from({
|
|
3071
3224
|
type: 'multisig',
|
|
3072
|
-
account,
|
|
3225
|
+
account: MultisigConfig.getAddress(saltedInit),
|
|
3073
3226
|
signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
|
|
3074
|
-
init:
|
|
3227
|
+
init: saltedInit,
|
|
3075
3228
|
})
|
|
3076
3229
|
const serialized = SignatureEnvelope.serialize(salted)
|
|
3077
3230
|
const deserialized = SignatureEnvelope.deserialize(
|
|
@@ -3094,27 +3247,72 @@ describe('multisig', () => {
|
|
|
3094
3247
|
})
|
|
3095
3248
|
|
|
3096
3249
|
test('toRpc/fromRpc round-trip with init', () => {
|
|
3097
|
-
const rpc = SignatureEnvelope.toRpc(
|
|
3250
|
+
const rpc = SignatureEnvelope.toRpc(
|
|
3251
|
+
bootstrapEnvelope,
|
|
3252
|
+
) as SignatureEnvelope.MultisigRpc
|
|
3098
3253
|
expect(rpc.init).toEqual(init)
|
|
3254
|
+
expect(rpc.account).toBeUndefined()
|
|
3255
|
+
expect('type' in rpc).toBe(false)
|
|
3099
3256
|
expect(SignatureEnvelope.fromRpc(rpc)).toEqual(bootstrapEnvelope)
|
|
3100
3257
|
})
|
|
3101
3258
|
|
|
3102
|
-
test('toRpc
|
|
3259
|
+
test('toRpc normalizes numberish init fields and the default salt', () => {
|
|
3260
|
+
const rpc = SignatureEnvelope.toRpc({
|
|
3261
|
+
...bootstrapEnvelope,
|
|
3262
|
+
init: {
|
|
3263
|
+
threshold: '0x1',
|
|
3264
|
+
owners: init.owners.map((owner) => ({
|
|
3265
|
+
...owner,
|
|
3266
|
+
weight: '0x1' as const,
|
|
3267
|
+
})),
|
|
3268
|
+
},
|
|
3269
|
+
} as SignatureEnvelope.Multisig<
|
|
3270
|
+
number | Hex.Hex
|
|
3271
|
+
>) as SignatureEnvelope.MultisigRpc
|
|
3272
|
+
expect(rpc.init).toEqual({
|
|
3273
|
+
salt: MultisigConfig.zeroSalt,
|
|
3274
|
+
threshold: 1,
|
|
3275
|
+
owners: init.owners,
|
|
3276
|
+
})
|
|
3277
|
+
})
|
|
3278
|
+
|
|
3279
|
+
test('toRpc encodes owner approvals as structured RPC envelopes', () => {
|
|
3103
3280
|
const multisig = bootstrapEnvelope as SignatureEnvelope.Multisig
|
|
3104
3281
|
const rpc = SignatureEnvelope.toRpc(
|
|
3105
3282
|
multisig,
|
|
3106
3283
|
) as SignatureEnvelope.MultisigRpc
|
|
3107
3284
|
expect(rpc.signatures).toEqual(
|
|
3108
|
-
multisig.signatures.map((
|
|
3285
|
+
multisig.signatures.map((signature) =>
|
|
3286
|
+
SignatureEnvelope.toRpc(signature),
|
|
3287
|
+
),
|
|
3109
3288
|
)
|
|
3110
3289
|
})
|
|
3111
3290
|
|
|
3112
3291
|
test('fromRpc detects multisig by shape (no `type` field)', () => {
|
|
3113
3292
|
const rpc = SignatureEnvelope.toRpc(bootstrapEnvelope)
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3293
|
+
expect(SignatureEnvelope.fromRpc(rpc)).toEqual(bootstrapEnvelope)
|
|
3294
|
+
})
|
|
3295
|
+
|
|
3296
|
+
test('fromRpc accepts the opposite static property as undefined', () => {
|
|
3297
|
+
const rpc = SignatureEnvelope.toRpc(
|
|
3298
|
+
bootstrapEnvelope,
|
|
3299
|
+
) as SignatureEnvelope.MultisigRpc
|
|
3300
|
+
expect(
|
|
3301
|
+
SignatureEnvelope.fromRpc({ ...rpc, account: undefined } as never),
|
|
3302
|
+
).toEqual(bootstrapEnvelope)
|
|
3303
|
+
})
|
|
3304
|
+
|
|
3305
|
+
test('fromRpc rejects the legacy combined account and init shape', () => {
|
|
3306
|
+
const rpc = SignatureEnvelope.toRpc(
|
|
3117
3307
|
bootstrapEnvelope,
|
|
3308
|
+
) as SignatureEnvelope.MultisigRpc
|
|
3309
|
+
expect(() =>
|
|
3310
|
+
SignatureEnvelope.fromRpc({
|
|
3311
|
+
...rpc,
|
|
3312
|
+
account: bootstrapAccount,
|
|
3313
|
+
} as never),
|
|
3314
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
3315
|
+
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: RPC multisig must contain exactly one of \`account\` or \`init\`.]`,
|
|
3118
3316
|
)
|
|
3119
3317
|
})
|
|
3120
3318
|
|
|
@@ -3122,11 +3320,24 @@ describe('multisig', () => {
|
|
|
3122
3320
|
expect(() =>
|
|
3123
3321
|
SignatureEnvelope.assert({
|
|
3124
3322
|
type: 'multisig',
|
|
3125
|
-
account,
|
|
3126
|
-
signatures: [],
|
|
3323
|
+
account: bootstrapAccount,
|
|
3324
|
+
signatures: [innerP256],
|
|
3127
3325
|
init: { threshold: 1, owners: [] },
|
|
3128
3326
|
} as never),
|
|
3129
3327
|
).toThrowError()
|
|
3130
3328
|
})
|
|
3329
|
+
|
|
3330
|
+
test('assert: init must derive the supplied account', () => {
|
|
3331
|
+
expect(() =>
|
|
3332
|
+
SignatureEnvelope.assert({
|
|
3333
|
+
type: 'multisig',
|
|
3334
|
+
account,
|
|
3335
|
+
signatures: [innerP256],
|
|
3336
|
+
init,
|
|
3337
|
+
}),
|
|
3338
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
3339
|
+
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig init does not derive account.]`,
|
|
3340
|
+
)
|
|
3341
|
+
})
|
|
3131
3342
|
})
|
|
3132
3343
|
})
|