ox 1.0.0-beta.11 → 1.0.0-beta.13
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/dist/core/internal/rpcSchemas/eth.d.ts +16 -0
- package/dist/core/internal/rpcSchemas/eth.d.ts.map +1 -1
- package/dist/tempo/MultisigConfig.d.ts +37 -85
- package/dist/tempo/MultisigConfig.d.ts.map +1 -1
- package/dist/tempo/MultisigConfig.js +39 -83
- package/dist/tempo/MultisigConfig.js.map +1 -1
- package/dist/tempo/SignatureEnvelope.d.ts +32 -30
- package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/tempo/SignatureEnvelope.js +56 -51
- package/dist/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/tempo/index.d.ts +2 -2
- package/dist/tempo/index.js +2 -2
- package/dist/zod/RpcSchema.d.ts +1 -0
- package/dist/zod/RpcSchema.d.ts.map +1 -1
- package/dist/zod/internal/rpcSchemas/Eth.d.ts +1 -0
- package/dist/zod/internal/rpcSchemas/Eth.d.ts.map +1 -1
- package/dist/zod/internal/rpcSchemas/Eth.js +5 -0
- package/dist/zod/internal/rpcSchemas/Eth.js.map +1 -1
- package/dist/zod/tempo/AuthorizationTempo.d.ts +0 -20
- package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.d.ts +0 -11
- package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/zod/tempo/RpcSchemaTempo.d.ts +0 -12
- package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.d.ts +0 -6
- package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.js +0 -2
- package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/zod/tempo/Transaction.d.ts +0 -24
- package/dist/zod/tempo/Transaction.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.d.ts +0 -21
- package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts +0 -9
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
- package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +0 -3
- package/dist/zod/tempo/ZoneRpcAuthentication.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/internal/rpcSchemas/eth.ts +16 -0
- package/src/tempo/MultisigConfig.test.ts +54 -37
- package/src/tempo/MultisigConfig.ts +59 -134
- package/src/tempo/SignatureEnvelope.test.ts +118 -17
- package/src/tempo/SignatureEnvelope.ts +77 -72
- package/src/tempo/e2e.test.ts +221 -1
- package/src/tempo/index.ts +2 -2
- package/src/version.ts +1 -1
- package/src/zod/internal/rpcSchemas/Eth.ts +6 -0
- package/src/zod/tempo/SignatureEnvelope.ts +0 -2
- package/src/zod/tempo/_test/SignatureEnvelope.test.ts +0 -2
|
@@ -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,
|
|
@@ -1125,7 +1126,7 @@ describe('from', () => {
|
|
|
1125
1126
|
],
|
|
1126
1127
|
})
|
|
1127
1128
|
|
|
1128
|
-
test('behavior: derives `account
|
|
1129
|
+
test('behavior: derives `account` from `genesisConfig`', () => {
|
|
1129
1130
|
const envelope = SignatureEnvelope.from({
|
|
1130
1131
|
genesisConfig,
|
|
1131
1132
|
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
@@ -1134,7 +1135,6 @@ describe('from', () => {
|
|
|
1134
1135
|
expect(envelope).toMatchObject({
|
|
1135
1136
|
type: 'multisig',
|
|
1136
1137
|
account: MultisigConfig.getAddress(genesisConfig),
|
|
1137
|
-
genesisConfigId: MultisigConfig.toId(genesisConfig),
|
|
1138
1138
|
})
|
|
1139
1139
|
expect('genesisConfig' in envelope).toBe(false)
|
|
1140
1140
|
expect((envelope as SignatureEnvelope.Multisig).init).toBeUndefined()
|
|
@@ -1171,19 +1171,16 @@ describe('from', () => {
|
|
|
1171
1171
|
expect((envelope as SignatureEnvelope.Multisig).init).toEqual(otherConfig)
|
|
1172
1172
|
})
|
|
1173
1173
|
|
|
1174
|
-
test('behavior: `{account
|
|
1174
|
+
test('behavior: `{ account }` form still works', () => {
|
|
1175
1175
|
const account = MultisigConfig.getAddress(genesisConfig)
|
|
1176
|
-
const genesisConfigId = MultisigConfig.toId(genesisConfig)
|
|
1177
1176
|
const envelope = SignatureEnvelope.from({
|
|
1178
1177
|
account,
|
|
1179
|
-
genesisConfigId,
|
|
1180
1178
|
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
1181
1179
|
})
|
|
1182
1180
|
|
|
1183
1181
|
expect(envelope).toMatchObject({
|
|
1184
1182
|
type: 'multisig',
|
|
1185
1183
|
account,
|
|
1186
|
-
genesisConfigId,
|
|
1187
1184
|
})
|
|
1188
1185
|
})
|
|
1189
1186
|
})
|
|
@@ -1860,9 +1857,8 @@ describe('sortMultisigApprovals', () => {
|
|
|
1860
1857
|
expect(recovered).toEqual(ascending.map((owner) => owner.address))
|
|
1861
1858
|
})
|
|
1862
1859
|
|
|
1863
|
-
test('behavior: `genesisConfig` and `{account
|
|
1860
|
+
test('behavior: `genesisConfig` and `{ account }` produce identical ordering', () => {
|
|
1864
1861
|
const account = MultisigConfig.getAddress(genesisConfig)
|
|
1865
|
-
const genesisConfigId = MultisigConfig.toId(genesisConfig)
|
|
1866
1862
|
const signatures = owners.map((owner) => owner.signature)
|
|
1867
1863
|
|
|
1868
1864
|
const fromConfig = SignatureEnvelope.sortMultisigApprovals({
|
|
@@ -1870,13 +1866,12 @@ describe('sortMultisigApprovals', () => {
|
|
|
1870
1866
|
payload,
|
|
1871
1867
|
signatures,
|
|
1872
1868
|
})
|
|
1873
|
-
const
|
|
1869
|
+
const fromAccount = SignatureEnvelope.sortMultisigApprovals({
|
|
1874
1870
|
account,
|
|
1875
|
-
genesisConfigId,
|
|
1876
1871
|
payload,
|
|
1877
1872
|
signatures,
|
|
1878
1873
|
})
|
|
1879
|
-
expect(fromConfig).toEqual(
|
|
1874
|
+
expect(fromConfig).toEqual(fromAccount)
|
|
1880
1875
|
})
|
|
1881
1876
|
})
|
|
1882
1877
|
|
|
@@ -2883,8 +2878,6 @@ describe('CoercionError', () => {
|
|
|
2883
2878
|
|
|
2884
2879
|
describe('multisig', () => {
|
|
2885
2880
|
const account = '0x8ba6d26ff5c4e82ba0c8caf8c8ca794e1489a7ae'
|
|
2886
|
-
const genesisConfigId =
|
|
2887
|
-
'0x01781fe551182476f2422c759e82d81c92e3263737afbbad57def6e8b69d21f5'
|
|
2888
2881
|
|
|
2889
2882
|
// P256 signatures do not carry `yParity` in the wire format, so use a clean
|
|
2890
2883
|
// inner signature for round-trip equality checks.
|
|
@@ -2897,7 +2890,6 @@ describe('multisig', () => {
|
|
|
2897
2890
|
const envelope = SignatureEnvelope.from({
|
|
2898
2891
|
type: 'multisig',
|
|
2899
2892
|
account,
|
|
2900
|
-
genesisConfigId,
|
|
2901
2893
|
signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
|
|
2902
2894
|
})
|
|
2903
2895
|
|
|
@@ -2906,11 +2898,32 @@ describe('multisig', () => {
|
|
|
2906
2898
|
expect(serialized.startsWith('0x05')).toBe(true)
|
|
2907
2899
|
})
|
|
2908
2900
|
|
|
2901
|
+
test('serialize: wire shape is `0x05 || rlp([account, signatures])`', () => {
|
|
2902
|
+
const deterministic = SignatureEnvelope.from({
|
|
2903
|
+
type: 'multisig',
|
|
2904
|
+
account,
|
|
2905
|
+
signatures: [innerP256],
|
|
2906
|
+
})
|
|
2907
|
+
expect(SignatureEnvelope.serialize(deterministic)).toMatchInlineSnapshot(
|
|
2908
|
+
`"0x05f89b948ba6d26ff5c4e82ba0c8caf8c8ca794e1489a7aef884b88201ccbb3485d4726235f13cb15ef394fb7158179fb7b1925eccec0147671090c52e77c3c53373cc1e3b05e7c23f609deb17cea8fe097300c45411237e9fe4166b35ad8ac16e167d6992c3e120d7f17d2376bc1cbcf30c46ba6dd00ce07303e742f511edf6ce1c32de66846f56afa7be1cbd729bc35750b6d0cdcf3ec9d75461aba001"`,
|
|
2909
|
+
)
|
|
2910
|
+
})
|
|
2911
|
+
|
|
2909
2912
|
test('serialize/deserialize round-trip', () => {
|
|
2910
2913
|
const serialized = SignatureEnvelope.serialize(envelope)
|
|
2911
2914
|
expect(SignatureEnvelope.deserialize(serialized)).toEqual(envelope)
|
|
2912
2915
|
})
|
|
2913
2916
|
|
|
2917
|
+
test('deserialize: rejects the empty `init` placeholder (`0x80`)', () => {
|
|
2918
|
+
const withPlaceholder = Hex.concat(
|
|
2919
|
+
'0x05',
|
|
2920
|
+
Rlp.fromHex([account, [SignatureEnvelope.serialize(innerP256)], '0x']),
|
|
2921
|
+
)
|
|
2922
|
+
expect(() => SignatureEnvelope.deserialize(withPlaceholder)).toThrowError(
|
|
2923
|
+
SignatureEnvelope.InvalidSerializedError,
|
|
2924
|
+
)
|
|
2925
|
+
})
|
|
2926
|
+
|
|
2914
2927
|
test('getType', () => {
|
|
2915
2928
|
expect(SignatureEnvelope.getType(envelope)).toBe('multisig')
|
|
2916
2929
|
})
|
|
@@ -2936,6 +2949,97 @@ describe('multisig', () => {
|
|
|
2936
2949
|
).toThrowError()
|
|
2937
2950
|
})
|
|
2938
2951
|
|
|
2952
|
+
test('assert: rejects keychain owner approvals', () => {
|
|
2953
|
+
expect(() =>
|
|
2954
|
+
SignatureEnvelope.assert({
|
|
2955
|
+
type: 'multisig',
|
|
2956
|
+
account,
|
|
2957
|
+
signatures: [signature_keychain_secp256k1],
|
|
2958
|
+
} as never),
|
|
2959
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
2960
|
+
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: keychain owner approvals are not allowed.]`,
|
|
2961
|
+
)
|
|
2962
|
+
})
|
|
2963
|
+
|
|
2964
|
+
describe('nested approvals', () => {
|
|
2965
|
+
const nestedAccount = '0x1111111111111111111111111111111111111111'
|
|
2966
|
+
const nested = SignatureEnvelope.from({
|
|
2967
|
+
type: 'multisig',
|
|
2968
|
+
account: nestedAccount,
|
|
2969
|
+
signatures: [innerP256],
|
|
2970
|
+
})
|
|
2971
|
+
|
|
2972
|
+
test('serialize/deserialize round-trip with a nested multisig approval', () => {
|
|
2973
|
+
const parent = SignatureEnvelope.from({
|
|
2974
|
+
type: 'multisig',
|
|
2975
|
+
account,
|
|
2976
|
+
signatures: [innerP256, nested],
|
|
2977
|
+
})
|
|
2978
|
+
const serialized = SignatureEnvelope.serialize(parent)
|
|
2979
|
+
expect(SignatureEnvelope.deserialize(serialized)).toEqual(parent)
|
|
2980
|
+
})
|
|
2981
|
+
|
|
2982
|
+
test('assert: accepts the maximum nesting depth', () => {
|
|
2983
|
+
const depth3 = SignatureEnvelope.from({
|
|
2984
|
+
type: 'multisig',
|
|
2985
|
+
account,
|
|
2986
|
+
signatures: [
|
|
2987
|
+
SignatureEnvelope.from({
|
|
2988
|
+
type: 'multisig',
|
|
2989
|
+
account: nestedAccount,
|
|
2990
|
+
signatures: [nested],
|
|
2991
|
+
}),
|
|
2992
|
+
],
|
|
2993
|
+
})
|
|
2994
|
+
expect(() => SignatureEnvelope.assert(depth3)).not.toThrowError()
|
|
2995
|
+
})
|
|
2996
|
+
|
|
2997
|
+
test('assert: rejects nesting deeper than the maximum', () => {
|
|
2998
|
+
const depth4 = SignatureEnvelope.from({
|
|
2999
|
+
type: 'multisig',
|
|
3000
|
+
account,
|
|
3001
|
+
signatures: [
|
|
3002
|
+
SignatureEnvelope.from({
|
|
3003
|
+
type: 'multisig',
|
|
3004
|
+
account: nestedAccount,
|
|
3005
|
+
signatures: [
|
|
3006
|
+
SignatureEnvelope.from({
|
|
3007
|
+
type: 'multisig',
|
|
3008
|
+
account: nestedAccount,
|
|
3009
|
+
signatures: [nested],
|
|
3010
|
+
}),
|
|
3011
|
+
],
|
|
3012
|
+
}),
|
|
3013
|
+
],
|
|
3014
|
+
})
|
|
3015
|
+
expect(() =>
|
|
3016
|
+
SignatureEnvelope.assert(depth4),
|
|
3017
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
3018
|
+
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig nesting depth exceeds 3.]`,
|
|
3019
|
+
)
|
|
3020
|
+
})
|
|
3021
|
+
|
|
3022
|
+
test('assert: rejects nested approvals carrying `init`', () => {
|
|
3023
|
+
expect(() =>
|
|
3024
|
+
SignatureEnvelope.assert({
|
|
3025
|
+
type: 'multisig',
|
|
3026
|
+
account,
|
|
3027
|
+
signatures: [
|
|
3028
|
+
{
|
|
3029
|
+
...nested,
|
|
3030
|
+
init: {
|
|
3031
|
+
threshold: 1,
|
|
3032
|
+
owners: [{ owner: nestedAccount, weight: 1 }],
|
|
3033
|
+
},
|
|
3034
|
+
},
|
|
3035
|
+
],
|
|
3036
|
+
} as never),
|
|
3037
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
3038
|
+
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: nested multisig owner approvals cannot carry \`init\`.]`,
|
|
3039
|
+
)
|
|
3040
|
+
})
|
|
3041
|
+
})
|
|
3042
|
+
|
|
2939
3043
|
describe('init (bootstrap)', () => {
|
|
2940
3044
|
const init = {
|
|
2941
3045
|
salt: `0x${'00'.repeat(32)}` as const,
|
|
@@ -2951,7 +3055,6 @@ describe('multisig', () => {
|
|
|
2951
3055
|
const bootstrapEnvelope = SignatureEnvelope.from({
|
|
2952
3056
|
type: 'multisig',
|
|
2953
3057
|
account,
|
|
2954
|
-
genesisConfigId,
|
|
2955
3058
|
signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
|
|
2956
3059
|
init,
|
|
2957
3060
|
})
|
|
@@ -2967,7 +3070,6 @@ describe('multisig', () => {
|
|
|
2967
3070
|
const salted = SignatureEnvelope.from({
|
|
2968
3071
|
type: 'multisig',
|
|
2969
3072
|
account,
|
|
2970
|
-
genesisConfigId,
|
|
2971
3073
|
signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
|
|
2972
3074
|
init: { ...init, salt: `0x${'42'.repeat(32)}` },
|
|
2973
3075
|
})
|
|
@@ -3021,7 +3123,6 @@ describe('multisig', () => {
|
|
|
3021
3123
|
SignatureEnvelope.assert({
|
|
3022
3124
|
type: 'multisig',
|
|
3023
3125
|
account,
|
|
3024
|
-
genesisConfigId,
|
|
3025
3126
|
signatures: [],
|
|
3026
3127
|
init: { threshold: 1, owners: [] },
|
|
3027
3128
|
} as never),
|
|
@@ -86,7 +86,6 @@ export type GetType<
|
|
|
86
86
|
? 'keychain'
|
|
87
87
|
: envelope extends {
|
|
88
88
|
account: Address.Address
|
|
89
|
-
genesisConfigId: `0x${string}`
|
|
90
89
|
signatures: any
|
|
91
90
|
}
|
|
92
91
|
? 'multisig'
|
|
@@ -162,9 +161,10 @@ export type KeychainRpc = {
|
|
|
162
161
|
/**
|
|
163
162
|
* Native multisig signature (type `0x05`).
|
|
164
163
|
*
|
|
165
|
-
* Wraps a set of
|
|
166
|
-
* multisig owner approval digest. The transaction sender is
|
|
167
|
-
* authorized once the recovered owner weights meet the
|
|
164
|
+
* Wraps a set of owner approvals (secp256k1, p256, webAuthn, or nested
|
|
165
|
+
* multisig) over the multisig owner approval digest. The transaction sender is
|
|
166
|
+
* the derived `account`, authorized once the recovered owner weights meet the
|
|
167
|
+
* configured threshold.
|
|
168
168
|
*
|
|
169
169
|
* [TIP-1061](https://tips.sh/1061)
|
|
170
170
|
*/
|
|
@@ -172,9 +172,11 @@ export type Multisig<numberType = number> = {
|
|
|
172
172
|
type: 'multisig'
|
|
173
173
|
/** Native multisig account address. */
|
|
174
174
|
account: Address.Address
|
|
175
|
-
/**
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Owner approvals over the multisig owner approval digest. Each approval is
|
|
177
|
+
* either a primitive signature or a nested multisig signature (keychain
|
|
178
|
+
* approvals are invalid).
|
|
179
|
+
*/
|
|
178
180
|
signatures: readonly SignatureEnvelope<numberType>[]
|
|
179
181
|
/**
|
|
180
182
|
* Initial native multisig config for bootstrapping this account. Present only on
|
|
@@ -188,14 +190,8 @@ export type MultisigRpc = {
|
|
|
188
190
|
type: 'multisig'
|
|
189
191
|
account: Address.Address
|
|
190
192
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* {@link ox#SignatureEnvelope.Multisig} envelope.
|
|
194
|
-
*/
|
|
195
|
-
configId: Hex.Hex
|
|
196
|
-
/**
|
|
197
|
-
* Encoded primitive owner approvals (raw serialized signatures), matching the
|
|
198
|
-
* node's `Vec<Bytes>` representation.
|
|
193
|
+
* Encoded owner approvals (raw serialized signatures), matching the node's
|
|
194
|
+
* `Vec<Bytes>` representation.
|
|
199
195
|
*/
|
|
200
196
|
signatures: readonly Serialized[]
|
|
201
197
|
init?: MultisigConfig.Config | undefined
|
|
@@ -345,11 +341,24 @@ export function assert(envelope: PartialBy<SignatureEnvelope, 'type'>): void {
|
|
|
345
341
|
const multisig = envelope as Multisig
|
|
346
342
|
const missing: string[] = []
|
|
347
343
|
if (!multisig.account) missing.push('account')
|
|
348
|
-
if (!multisig.genesisConfigId) missing.push('genesisConfigId')
|
|
349
344
|
if (!Array.isArray(multisig.signatures)) missing.push('signatures')
|
|
350
345
|
if (missing.length > 0)
|
|
351
346
|
throw new MissingPropertiesError({ envelope, missing, type: 'multisig' })
|
|
352
|
-
|
|
347
|
+
if (getNestingDepth(multisig) > MultisigConfig.maxNestingDepth)
|
|
348
|
+
throw new InvalidMultisigApprovalError({
|
|
349
|
+
reason: `multisig nesting depth exceeds ${MultisigConfig.maxNestingDepth}`,
|
|
350
|
+
})
|
|
351
|
+
for (const inner of multisig.signatures) {
|
|
352
|
+
if (inner.type === 'keychain')
|
|
353
|
+
throw new InvalidMultisigApprovalError({
|
|
354
|
+
reason: 'keychain owner approvals are not allowed',
|
|
355
|
+
})
|
|
356
|
+
if (inner.type === 'multisig' && inner.init)
|
|
357
|
+
throw new InvalidMultisigApprovalError({
|
|
358
|
+
reason: 'nested multisig owner approvals cannot carry `init`',
|
|
359
|
+
})
|
|
360
|
+
assert(inner)
|
|
361
|
+
}
|
|
353
362
|
if (multisig.init) MultisigConfig.assert(multisig.init)
|
|
354
363
|
return
|
|
355
364
|
}
|
|
@@ -358,12 +367,22 @@ export function assert(envelope: PartialBy<SignatureEnvelope, 'type'>): void {
|
|
|
358
367
|
export declare namespace assert {
|
|
359
368
|
type ErrorType =
|
|
360
369
|
| CoercionError
|
|
370
|
+
| InvalidMultisigApprovalError
|
|
361
371
|
| MissingPropertiesError
|
|
362
372
|
| MultisigConfig.assert.ErrorType
|
|
363
373
|
| Signature.assert.ErrorType
|
|
364
374
|
| Errors.GlobalErrorType
|
|
365
375
|
}
|
|
366
376
|
|
|
377
|
+
// Returns the deepest multisig path length within a multisig envelope.
|
|
378
|
+
function getNestingDepth(envelope: Multisig): number {
|
|
379
|
+
let deepest = 0
|
|
380
|
+
for (const inner of envelope.signatures)
|
|
381
|
+
if (inner.type === 'multisig')
|
|
382
|
+
deepest = Math.max(deepest, getNestingDepth(inner))
|
|
383
|
+
return 1 + deepest
|
|
384
|
+
}
|
|
385
|
+
|
|
367
386
|
/**
|
|
368
387
|
* Extracts the address of the signer from a {@link ox#SignatureEnvelope.SignatureEnvelope}.
|
|
369
388
|
*
|
|
@@ -630,24 +649,26 @@ export function deserialize(value: Serialized): SignatureEnvelope {
|
|
|
630
649
|
}
|
|
631
650
|
|
|
632
651
|
if (typeId === serializedMultisigType) {
|
|
633
|
-
// Wire format: `0x05 || rlp([account,
|
|
634
|
-
//
|
|
635
|
-
//
|
|
636
|
-
const [account,
|
|
637
|
-
Hex.Hex,
|
|
652
|
+
// Wire format: `0x05 || rlp([account, signatures, init?])`. `init` is a
|
|
653
|
+
// trailing optional: the bootstrap config list when present, otherwise
|
|
654
|
+
// omitted entirely (the empty `0x80` placeholder is rejected).
|
|
655
|
+
const [account, signatures, init] = Rlp.toHex(data) as [
|
|
638
656
|
Hex.Hex,
|
|
639
657
|
readonly Hex.Hex[],
|
|
640
658
|
(Hex.Hex | MultisigConfig.Tuple)?,
|
|
641
659
|
]
|
|
660
|
+
if (typeof init !== 'undefined' && !Array.isArray(init))
|
|
661
|
+
throw new InvalidSerializedError({
|
|
662
|
+
reason:
|
|
663
|
+
'Invalid multisig `init`: expected the bootstrap config list or an omitted trailing element',
|
|
664
|
+
serialized,
|
|
665
|
+
})
|
|
642
666
|
return {
|
|
643
667
|
type: 'multisig',
|
|
644
668
|
account,
|
|
645
|
-
genesisConfigId,
|
|
646
669
|
signatures: signatures.map((signature) => deserialize(signature)),
|
|
647
|
-
...(init
|
|
648
|
-
? {
|
|
649
|
-
init: MultisigConfig.fromTuple(init as MultisigConfig.Tuple),
|
|
650
|
-
}
|
|
670
|
+
...(init
|
|
671
|
+
? { init: MultisigConfig.fromTuple(init as MultisigConfig.Tuple) }
|
|
651
672
|
: {}),
|
|
652
673
|
} satisfies Multisig
|
|
653
674
|
}
|
|
@@ -786,9 +807,9 @@ export function deserialize(value: Serialized): SignatureEnvelope {
|
|
|
786
807
|
* @example
|
|
787
808
|
* ### Multisig (from genesis config)
|
|
788
809
|
*
|
|
789
|
-
* Pass `genesisConfig` to derive `account`
|
|
790
|
-
*
|
|
791
|
-
*
|
|
810
|
+
* Pass `genesisConfig` to derive `account` automatically. Set `init: true` to
|
|
811
|
+
* opt into bootstrap (uses `genesisConfig` as the bootstrap `init`); omit
|
|
812
|
+
* `init` for subsequent (non-bootstrap) transactions.
|
|
792
813
|
*
|
|
793
814
|
* ```ts twoslash
|
|
794
815
|
* import { Secp256k1 } from 'ox'
|
|
@@ -849,18 +870,12 @@ export function from<const value extends from.Value>(
|
|
|
849
870
|
init?: MultisigConfig.Config | boolean | undefined
|
|
850
871
|
}
|
|
851
872
|
const { genesisConfig, init, ...rest } = multisig
|
|
852
|
-
// Derive `account
|
|
853
|
-
// explicitly.
|
|
873
|
+
// Derive `account` from `genesisConfig` when not provided explicitly.
|
|
854
874
|
const account = (() => {
|
|
855
875
|
if (rest.account) return rest.account
|
|
856
876
|
if (genesisConfig) return MultisigConfig.getAddress(genesisConfig)
|
|
857
877
|
return rest.account
|
|
858
878
|
})()
|
|
859
|
-
const genesisConfigId = (() => {
|
|
860
|
-
if (rest.genesisConfigId) return rest.genesisConfigId
|
|
861
|
-
if (genesisConfig) return MultisigConfig.toId(genesisConfig)
|
|
862
|
-
return rest.genesisConfigId
|
|
863
|
-
})()
|
|
864
879
|
// `init: true` opts into bootstrap using the supplied `genesisConfig`.
|
|
865
880
|
// Otherwise, `init` is treated as the explicit bootstrap config (or
|
|
866
881
|
// omitted).
|
|
@@ -868,7 +883,6 @@ export function from<const value extends from.Value>(
|
|
|
868
883
|
return {
|
|
869
884
|
...rest,
|
|
870
885
|
account,
|
|
871
|
-
genesisConfigId,
|
|
872
886
|
signatures: rest.signatures.map((signature) => from(signature)),
|
|
873
887
|
// Normalize the bootstrap config (sorts owners, defaults the salt) so the
|
|
874
888
|
// in-memory envelope matches what `deserialize` reconstructs.
|
|
@@ -920,9 +934,9 @@ export declare namespace from {
|
|
|
920
934
|
}
|
|
921
935
|
|
|
922
936
|
/**
|
|
923
|
-
* Multisig envelope input variant where `account`
|
|
924
|
-
*
|
|
925
|
-
*
|
|
937
|
+
* Multisig envelope input variant where `account` is derived from the
|
|
938
|
+
* supplied `genesisConfig`. Pass `init: true` to opt into bootstrap (uses
|
|
939
|
+
* `genesisConfig` as the bootstrap `init`); omit `init` for subsequent
|
|
926
940
|
* (non-bootstrap) transactions.
|
|
927
941
|
*/
|
|
928
942
|
type MultisigFromGenesisConfig = {
|
|
@@ -1063,17 +1077,12 @@ export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope {
|
|
|
1063
1077
|
|
|
1064
1078
|
if (
|
|
1065
1079
|
envelope.type === 'multisig' ||
|
|
1066
|
-
('account' in envelope &&
|
|
1067
|
-
'configId' in envelope &&
|
|
1068
|
-
'signatures' in envelope)
|
|
1080
|
+
('account' in envelope && 'signatures' in envelope)
|
|
1069
1081
|
) {
|
|
1070
1082
|
const multisig = envelope as MultisigRpc
|
|
1071
1083
|
return {
|
|
1072
1084
|
type: 'multisig',
|
|
1073
1085
|
account: multisig.account,
|
|
1074
|
-
// Map RPC wire field `configId` (TIP-1061 spec name) to the typed
|
|
1075
|
-
// envelope's `genesisConfigId`.
|
|
1076
|
-
genesisConfigId: multisig.configId,
|
|
1077
1086
|
// Owner approvals are raw serialized signatures (node `Vec<Bytes>`).
|
|
1078
1087
|
signatures: multisig.signatures.map((signature) =>
|
|
1079
1088
|
deserialize(signature),
|
|
@@ -1164,8 +1173,7 @@ export function getType<
|
|
|
1164
1173
|
|
|
1165
1174
|
// Detect Multisig signature
|
|
1166
1175
|
if (
|
|
1167
|
-
(
|
|
1168
|
-
'genesisConfig' in envelope) &&
|
|
1176
|
+
('account' in envelope || 'genesisConfig' in envelope) &&
|
|
1169
1177
|
'signatures' in envelope
|
|
1170
1178
|
)
|
|
1171
1179
|
return 'multisig' as never
|
|
@@ -1269,17 +1277,15 @@ export function serialize(
|
|
|
1269
1277
|
|
|
1270
1278
|
if (type === 'multisig') {
|
|
1271
1279
|
const multisig = envelope as Multisig
|
|
1272
|
-
// Format: `0x05 || rlp([account,
|
|
1273
|
-
//
|
|
1274
|
-
// config
|
|
1275
|
-
// placeholder (`0x` → RLP `0x80`).
|
|
1280
|
+
// Format: `0x05 || rlp([account, signatures, init?])`, where each owner
|
|
1281
|
+
// approval is an encoded signature. `init` is a trailing optional: the
|
|
1282
|
+
// bootstrap config list when present, otherwise omitted entirely.
|
|
1276
1283
|
return Hex.concat(
|
|
1277
1284
|
serializedMultisigType,
|
|
1278
1285
|
Rlp.fromHex([
|
|
1279
1286
|
multisig.account,
|
|
1280
|
-
multisig.genesisConfigId,
|
|
1281
1287
|
multisig.signatures.map((signature) => serialize(signature)),
|
|
1282
|
-
multisig.init ? MultisigConfig.toTuple(multisig.init) :
|
|
1288
|
+
...(multisig.init ? [MultisigConfig.toTuple(multisig.init)] : []),
|
|
1283
1289
|
]),
|
|
1284
1290
|
options.magic ? magicBytes : '0x',
|
|
1285
1291
|
)
|
|
@@ -1307,10 +1313,10 @@ export declare namespace serialize {
|
|
|
1307
1313
|
* ({@link ox#MultisigConfig.(getSignPayload:function)}), so the signer of
|
|
1308
1314
|
* every approval is recovered against that digest and the list is sorted by the
|
|
1309
1315
|
* recovered owner address. Works for any owner key type (secp256k1, p256,
|
|
1310
|
-
* webAuthn
|
|
1316
|
+
* webAuthn).
|
|
1311
1317
|
*
|
|
1312
|
-
* Config updates never change `account
|
|
1313
|
-
*
|
|
1318
|
+
* Config updates never change `account`, so the genesis config is the correct
|
|
1319
|
+
* input even for post-update transactions.
|
|
1314
1320
|
*
|
|
1315
1321
|
* @example
|
|
1316
1322
|
* ```ts twoslash
|
|
@@ -1370,12 +1376,7 @@ export function sortMultisigApprovals(
|
|
|
1370
1376
|
const digest = MultisigConfig.getSignPayload(
|
|
1371
1377
|
'genesisConfig' in value && value.genesisConfig
|
|
1372
1378
|
? { payload, genesisConfig: value.genesisConfig }
|
|
1373
|
-
: {
|
|
1374
|
-
payload,
|
|
1375
|
-
account: (value as { account: Address.Address }).account,
|
|
1376
|
-
genesisConfigId: (value as { genesisConfigId: Hex.Hex })
|
|
1377
|
-
.genesisConfigId,
|
|
1378
|
-
},
|
|
1379
|
+
: { payload, account: (value as { account: Address.Address }).account },
|
|
1379
1380
|
)
|
|
1380
1381
|
// Recover each signer once (decorate–sort–undecorate) rather than inside the
|
|
1381
1382
|
// comparator.
|
|
@@ -1392,20 +1393,17 @@ export declare namespace sortMultisigApprovals {
|
|
|
1392
1393
|
type Value = {
|
|
1393
1394
|
/** The inner transaction sign payload (`tx.signature_hash()`). */
|
|
1394
1395
|
payload: Hex.Hex | Bytes.Bytes
|
|
1395
|
-
/** The
|
|
1396
|
+
/** The owner approvals to order. */
|
|
1396
1397
|
signatures: readonly SignatureEnvelope[]
|
|
1397
1398
|
} & OneOf<
|
|
1398
1399
|
| {
|
|
1399
1400
|
/** The native multisig account address. */
|
|
1400
1401
|
account: Address.Address
|
|
1401
|
-
/** The permanent config ID. */
|
|
1402
|
-
genesisConfigId: Hex.Hex
|
|
1403
1402
|
}
|
|
1404
1403
|
| {
|
|
1405
1404
|
/**
|
|
1406
1405
|
* The initial multisig config (the bootstrap config that derived the
|
|
1407
|
-
* permanent `account`
|
|
1408
|
-
* automatically.
|
|
1406
|
+
* permanent `account`). Used to derive the account automatically.
|
|
1409
1407
|
*/
|
|
1410
1408
|
genesisConfig: MultisigConfig.Config
|
|
1411
1409
|
}
|
|
@@ -1493,9 +1491,6 @@ export function toRpc(envelope: toRpc.Input): SignatureEnvelopeRpc {
|
|
|
1493
1491
|
return {
|
|
1494
1492
|
type: 'multisig',
|
|
1495
1493
|
account: multisig.account,
|
|
1496
|
-
// Map the typed envelope's `genesisConfigId` to the RPC wire field
|
|
1497
|
-
// `configId` (TIP-1061 spec name).
|
|
1498
|
-
configId: multisig.genesisConfigId,
|
|
1499
1494
|
// Owner approvals are raw serialized signatures (node `Vec<Bytes>`).
|
|
1500
1495
|
signatures: multisig.signatures.map((signature) => serialize(signature)),
|
|
1501
1496
|
...(multisig.init ? { init: multisig.init } : {}),
|
|
@@ -1780,6 +1775,16 @@ export class InvalidSerializedError extends Errors.BaseError {
|
|
|
1780
1775
|
}
|
|
1781
1776
|
}
|
|
1782
1777
|
|
|
1778
|
+
/**
|
|
1779
|
+
* Error thrown when a native multisig owner approval is invalid.
|
|
1780
|
+
*/
|
|
1781
|
+
export class InvalidMultisigApprovalError extends Errors.BaseError {
|
|
1782
|
+
override readonly name = 'SignatureEnvelope.InvalidMultisigApprovalError'
|
|
1783
|
+
constructor({ reason }: { reason: string }) {
|
|
1784
|
+
super(`Invalid native multisig owner approval: ${reason}.`)
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1783
1788
|
/**
|
|
1784
1789
|
* Error thrown when a signature envelope fails to verify.
|
|
1785
1790
|
*/
|