ox 0.14.38 → 0.14.39
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 +14 -0
- package/_cjs/tempo/MultisigConfig.js +71 -15
- package/_cjs/tempo/MultisigConfig.js.map +1 -1
- package/_cjs/tempo/MultisigOperation.js +42 -36
- package/_cjs/tempo/MultisigOperation.js.map +1 -1
- package/_cjs/tempo/SignatureEnvelope.js +71 -108
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/tempo/MultisigConfig.js +140 -71
- package/_esm/tempo/MultisigConfig.js.map +1 -1
- package/_esm/tempo/MultisigOperation.js +42 -37
- package/_esm/tempo/MultisigOperation.js.map +1 -1
- package/_esm/tempo/SignatureEnvelope.js +84 -143
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/version.js +1 -1
- package/_types/tempo/KeyAuthorization.d.ts +1 -1
- package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
- package/_types/tempo/MultisigConfig.d.ts +137 -73
- package/_types/tempo/MultisigConfig.d.ts.map +1 -1
- package/_types/tempo/MultisigOperation.d.ts +1 -1
- package/_types/tempo/MultisigOperation.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +81 -75
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/version.d.ts +1 -1
- package/package.json +6 -1
- package/tempo/KeyAuthorization.test-d.ts +12 -0
- package/tempo/KeyAuthorization.test.ts +54 -0
- package/tempo/KeyAuthorization.ts +1 -1
- package/tempo/MultisigConfig.test-d/package.json +6 -0
- package/tempo/MultisigConfig.test-d.ts +52 -0
- package/tempo/MultisigConfig.test.ts +291 -263
- package/tempo/MultisigConfig.ts +250 -95
- package/tempo/MultisigOperation.test-d.ts +6 -0
- package/tempo/MultisigOperation.test.ts +113 -72
- package/tempo/MultisigOperation.ts +41 -35
- package/tempo/SignatureEnvelope.test-d.ts +57 -40
- package/tempo/SignatureEnvelope.test.ts +523 -480
- package/tempo/SignatureEnvelope.ts +250 -257
- package/tempo/multisig.e2e.test.ts +113 -86
- package/version.ts +1 -1
|
@@ -14,7 +14,7 @@ export type Base<quantity = bigint> = {
|
|
|
14
14
|
/** Every retained serialized owner approval. */
|
|
15
15
|
approvals: readonly Hex.Hex[]
|
|
16
16
|
/** Root configuration used to verify approvals. */
|
|
17
|
-
config: MultisigConfig.Config
|
|
17
|
+
config: MultisigConfig.Config<quantity>
|
|
18
18
|
/** Root configuration version. */
|
|
19
19
|
configVersion: quantity
|
|
20
20
|
/** Unix creation time in milliseconds. */
|
|
@@ -109,8 +109,8 @@ export function getHash(options: getHash.Options): Hex.Hex {
|
|
|
109
109
|
)
|
|
110
110
|
return MultisigConfig.getSignPayload({
|
|
111
111
|
account,
|
|
112
|
+
config: { version: configVersion },
|
|
112
113
|
payload,
|
|
113
|
-
version: configVersion,
|
|
114
114
|
})
|
|
115
115
|
}
|
|
116
116
|
|
|
@@ -281,20 +281,15 @@ export function serializeTransaction(
|
|
|
281
281
|
assertRetainedApprovals(value, approvals)
|
|
282
282
|
const signatures = SignatureEnvelope.sortMultisigApprovals({
|
|
283
283
|
account: value.account,
|
|
284
|
+
config: value.config,
|
|
284
285
|
payload: TxEnvelopeTempo.getSignPayload(envelope),
|
|
285
286
|
signatures: approvals,
|
|
286
|
-
version: value.configVersion,
|
|
287
287
|
})
|
|
288
|
-
const signature =
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
})
|
|
294
|
-
: SignatureEnvelope.from({
|
|
295
|
-
account: value.account,
|
|
296
|
-
signatures,
|
|
297
|
-
})
|
|
288
|
+
const signature = SignatureEnvelope.from({
|
|
289
|
+
account: value.account,
|
|
290
|
+
config: value.config,
|
|
291
|
+
signatures,
|
|
292
|
+
})
|
|
298
293
|
return TxEnvelopeTempo.serialize(
|
|
299
294
|
envelope,
|
|
300
295
|
value.transaction.startsWith(TxEnvelopeTempo.feePayerMagic)
|
|
@@ -343,7 +338,10 @@ export function from<const operation extends Operation>(
|
|
|
343
338
|
operation: operation,
|
|
344
339
|
): from.ReturnValue<operation> {
|
|
345
340
|
try {
|
|
346
|
-
const config = MultisigConfig.from(
|
|
341
|
+
const config = MultisigConfig.from({
|
|
342
|
+
...operation.config,
|
|
343
|
+
version: operation.configVersion,
|
|
344
|
+
})
|
|
347
345
|
if (
|
|
348
346
|
typeof config.threshold !== 'number' ||
|
|
349
347
|
config.owners.some((owner) => typeof owner.weight !== 'number')
|
|
@@ -351,12 +349,13 @@ export function from<const operation extends Operation>(
|
|
|
351
349
|
throw new InvalidOperationError({
|
|
352
350
|
reason: 'config threshold and owner weights must be numbers',
|
|
353
351
|
})
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
352
|
+
const value = { ...operation, config } as Operation
|
|
353
|
+
assertBase(value, config)
|
|
354
|
+
if (value.type === 'transaction') assertTransaction(value)
|
|
355
|
+
else if (value.type === 'keyAuthorization')
|
|
356
|
+
assertKeyAuthorization(value, config)
|
|
358
357
|
else throw new InvalidOperationError({ reason: 'unknown operation type' })
|
|
359
|
-
return
|
|
358
|
+
return value as never
|
|
360
359
|
} catch (cause) {
|
|
361
360
|
if (cause instanceof InvalidOperationError) throw cause
|
|
362
361
|
throw new InvalidOperationError({ cause })
|
|
@@ -404,7 +403,11 @@ export function fromRpc<const operation extends Rpc>(
|
|
|
404
403
|
throw new InvalidOperationError({
|
|
405
404
|
reason: 'configVersion must use canonical quantity encoding',
|
|
406
405
|
})
|
|
407
|
-
return from({
|
|
406
|
+
return from({
|
|
407
|
+
...operation,
|
|
408
|
+
config: MultisigConfig.fromRpc(operation.config),
|
|
409
|
+
configVersion,
|
|
410
|
+
} as Operation) as never
|
|
408
411
|
} catch (cause) {
|
|
409
412
|
if (cause instanceof InvalidOperationError) throw cause
|
|
410
413
|
throw new InvalidOperationError({ cause })
|
|
@@ -442,6 +445,7 @@ export function toRpc<const operation extends Operation>(
|
|
|
442
445
|
const value = from(operation)
|
|
443
446
|
return {
|
|
444
447
|
...value,
|
|
448
|
+
config: MultisigConfig.toRpc(value.config),
|
|
445
449
|
configVersion: Hex.fromNumber(value.configVersion),
|
|
446
450
|
} as never
|
|
447
451
|
}
|
|
@@ -513,10 +517,6 @@ async function selectApprovals_internal(
|
|
|
513
517
|
throw new InvalidApprovalError({
|
|
514
518
|
reason: `owner ${group.address} has conflicting signature types`,
|
|
515
519
|
})
|
|
516
|
-
if (nested.some((signature) => signature.init))
|
|
517
|
-
throw new InvalidApprovalError({
|
|
518
|
-
reason: `nested multisig owner ${group.address} cannot carry init`,
|
|
519
|
-
})
|
|
520
520
|
if (
|
|
521
521
|
path.length >= MultisigConfig.maxNestingDepth ||
|
|
522
522
|
path.includes(group.address.toLowerCase())
|
|
@@ -529,6 +529,10 @@ async function selectApprovals_internal(
|
|
|
529
529
|
reason: `nested multisig owner ${group.address} requires a config resolver`,
|
|
530
530
|
})
|
|
531
531
|
const resolved = await options.resolveConfig({ account: group.address })
|
|
532
|
+
const config = MultisigConfig.from({
|
|
533
|
+
...resolved.config,
|
|
534
|
+
version: resolved.version,
|
|
535
|
+
})
|
|
532
536
|
const selected = await selectApprovals_internal(
|
|
533
537
|
{
|
|
534
538
|
account: group.address,
|
|
@@ -537,11 +541,11 @@ async function selectApprovals_internal(
|
|
|
537
541
|
SignatureEnvelope.serialize(approval),
|
|
538
542
|
),
|
|
539
543
|
),
|
|
540
|
-
config
|
|
544
|
+
config,
|
|
541
545
|
hash: MultisigConfig.getSignPayload({
|
|
542
546
|
account: group.address,
|
|
547
|
+
config,
|
|
543
548
|
payload: options.hash,
|
|
544
|
-
version: resolved.version,
|
|
545
549
|
}),
|
|
546
550
|
resolveConfig: options.resolveConfig,
|
|
547
551
|
},
|
|
@@ -552,6 +556,7 @@ async function selectApprovals_internal(
|
|
|
552
556
|
signature: SignatureEnvelope.serialize(
|
|
553
557
|
SignatureEnvelope.from({
|
|
554
558
|
account: group.address,
|
|
559
|
+
config,
|
|
555
560
|
signatures: selected.approvals.map((approval) =>
|
|
556
561
|
SignatureEnvelope.from(approval),
|
|
557
562
|
),
|
|
@@ -564,6 +569,7 @@ async function selectApprovals_internal(
|
|
|
564
569
|
signature: SignatureEnvelope.serialize(
|
|
565
570
|
SignatureEnvelope.from({
|
|
566
571
|
account: group.address,
|
|
572
|
+
config,
|
|
567
573
|
signatures: selected.selectedApprovals.map((approval) =>
|
|
568
574
|
SignatureEnvelope.from(approval),
|
|
569
575
|
),
|
|
@@ -739,6 +745,7 @@ function assertBase(operation: Operation, config: MultisigConfig.Config): void {
|
|
|
739
745
|
const signature = assertApproval(
|
|
740
746
|
operation.account,
|
|
741
747
|
approval as SignatureEnvelope.Serialized,
|
|
748
|
+
config,
|
|
742
749
|
)
|
|
743
750
|
const address = SignatureEnvelope.extractAddress({
|
|
744
751
|
payload: operation.hash,
|
|
@@ -944,13 +951,9 @@ function assertKeyAuthorization(
|
|
|
944
951
|
reason: 'key authorization signatureCount does not match its signature',
|
|
945
952
|
})
|
|
946
953
|
assertSelectedApprovals(operation, signature.signatures, authorization)
|
|
947
|
-
if (
|
|
954
|
+
if (!sameConfig(signature.config, config))
|
|
948
955
|
throw new InvalidOperationError({
|
|
949
|
-
reason: 'key authorization
|
|
950
|
-
})
|
|
951
|
-
if (signature.init && !sameConfig(signature.init, config))
|
|
952
|
-
throw new InvalidOperationError({
|
|
953
|
-
reason: 'key authorization bootstrap config does not match',
|
|
956
|
+
reason: 'key authorization config does not match',
|
|
954
957
|
})
|
|
955
958
|
}
|
|
956
959
|
assertOperationHash(
|
|
@@ -974,10 +977,12 @@ function assertKeyAuthorization(
|
|
|
974
977
|
function assertApproval(
|
|
975
978
|
account: Address.Address,
|
|
976
979
|
serialized: SignatureEnvelope.Serialized,
|
|
980
|
+
config: MultisigConfig.Config,
|
|
977
981
|
): SignatureEnvelope.SignatureEnvelope {
|
|
978
982
|
const approval = SignatureEnvelope.deserialize(serialized)
|
|
979
983
|
SignatureEnvelope.assert({
|
|
980
984
|
account,
|
|
985
|
+
config,
|
|
981
986
|
signatures: [approval],
|
|
982
987
|
type: 'multisig',
|
|
983
988
|
})
|
|
@@ -1039,8 +1044,8 @@ function assertSelectedApprovals(
|
|
|
1039
1044
|
|
|
1040
1045
|
const digest = MultisigConfig.getSignPayload({
|
|
1041
1046
|
account: operation.account,
|
|
1047
|
+
config: operation.config,
|
|
1042
1048
|
payload: KeyAuthorization_.getSignPayload(authorization),
|
|
1043
|
-
version: operation.configVersion,
|
|
1044
1049
|
})
|
|
1045
1050
|
const addresses = selected.map((signature) =>
|
|
1046
1051
|
SignatureEnvelope.extractAddress({ payload: digest, signature }),
|
|
@@ -1075,7 +1080,7 @@ function includesApproval(
|
|
|
1075
1080
|
)
|
|
1076
1081
|
if (retained.account.toLowerCase() !== selected.account.toLowerCase())
|
|
1077
1082
|
return false
|
|
1078
|
-
|
|
1083
|
+
if (!sameConfig(retained.config, selected.config)) return false
|
|
1079
1084
|
let index = 0
|
|
1080
1085
|
for (const approval of selected.signatures) {
|
|
1081
1086
|
while (
|
|
@@ -1119,8 +1124,8 @@ function isWeightReachable(
|
|
|
1119
1124
|
function assertOperationHash(operation: Operation, payload: Hex.Hex): void {
|
|
1120
1125
|
const hash = MultisigConfig.getSignPayload({
|
|
1121
1126
|
account: operation.account,
|
|
1127
|
+
config: operation.config,
|
|
1122
1128
|
payload,
|
|
1123
|
-
version: operation.configVersion,
|
|
1124
1129
|
})
|
|
1125
1130
|
if (hash.toLowerCase() !== operation.hash.toLowerCase())
|
|
1126
1131
|
throw new InvalidOperationError({
|
|
@@ -1157,6 +1162,7 @@ function sameConfig(
|
|
|
1157
1162
|
configB.salt ?? MultisigConfig.zeroSalt,
|
|
1158
1163
|
) &&
|
|
1159
1164
|
configA.threshold === configB.threshold &&
|
|
1165
|
+
configA.version === configB.version &&
|
|
1160
1166
|
configA.owners.length === configB.owners.length &&
|
|
1161
1167
|
configA.owners.every((owner, index) => {
|
|
1162
1168
|
const other = configB.owners[index]!
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { expectTypeOf, test } from 'vitest'
|
|
2
|
+
import * as MultisigConfig from './MultisigConfig.js'
|
|
2
3
|
import * as SignatureEnvelope from './SignatureEnvelope.js'
|
|
3
4
|
|
|
4
5
|
const signatureRpc = {
|
|
@@ -17,67 +18,81 @@ const signature = {
|
|
|
17
18
|
type: 'secp256k1',
|
|
18
19
|
} as const satisfies SignatureEnvelope.Secp256k1
|
|
19
20
|
|
|
21
|
+
const config = MultisigConfig.from({
|
|
22
|
+
owners: [
|
|
23
|
+
{
|
|
24
|
+
owner: '0x1111111111111111111111111111111111111111',
|
|
25
|
+
weight: 1,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
threshold: 1,
|
|
29
|
+
})
|
|
30
|
+
|
|
20
31
|
test('toRpc preserves the signature type', () => {
|
|
21
32
|
expectTypeOf(
|
|
22
33
|
SignatureEnvelope.toRpc(signature),
|
|
23
34
|
).toEqualTypeOf<SignatureEnvelope.Secp256k1Rpc>()
|
|
24
35
|
})
|
|
25
36
|
|
|
26
|
-
test('MultisigRpc
|
|
27
|
-
const
|
|
37
|
+
test('MultisigRpc carries one complete witness shape', () => {
|
|
38
|
+
const rpc = {
|
|
28
39
|
account: '0x1111111111111111111111111111111111111111',
|
|
40
|
+
config: MultisigConfig.toRpc(config),
|
|
29
41
|
signatures: [signatureRpc],
|
|
30
42
|
} 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: [signatureRpc],
|
|
42
|
-
} as const satisfies SignatureEnvelope.MultisigRpc
|
|
43
|
-
const bootstrapWithAccountUndefined: SignatureEnvelope.MultisigRpc = {
|
|
44
|
-
...bootstrap,
|
|
45
|
-
account: undefined,
|
|
46
|
-
}
|
|
47
43
|
|
|
48
|
-
expectTypeOf(
|
|
49
|
-
|
|
50
|
-
>()
|
|
51
|
-
expectTypeOf(bootstrap.signatures).toMatchTypeOf<
|
|
44
|
+
expectTypeOf(rpc.config).toMatchTypeOf<MultisigConfig.Rpc>()
|
|
45
|
+
expectTypeOf(rpc.signatures).toMatchTypeOf<
|
|
52
46
|
readonly SignatureEnvelope.SignatureEnvelopeRpc[]
|
|
53
47
|
>()
|
|
54
|
-
expectTypeOf(
|
|
55
|
-
bootstrapWithAccountUndefined,
|
|
56
|
-
).toMatchTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
57
48
|
expectTypeOf<
|
|
58
|
-
SignatureEnvelope.GetType<typeof
|
|
49
|
+
SignatureEnvelope.GetType<typeof rpc>
|
|
59
50
|
>().toEqualTypeOf<'multisig'>()
|
|
60
51
|
})
|
|
61
52
|
|
|
62
|
-
test('
|
|
63
|
-
const
|
|
53
|
+
test('from derives an initial account', () => {
|
|
54
|
+
const multisig = SignatureEnvelope.from({
|
|
55
|
+
config,
|
|
56
|
+
signatures: [signature],
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
expectTypeOf(multisig).toMatchTypeOf<SignatureEnvelope.Multisig>()
|
|
60
|
+
expectTypeOf(multisig.config.version).toEqualTypeOf<bigint>()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('from requires an account for a current config', () => {
|
|
64
|
+
// @ts-expect-error Current configurations require an explicit account.
|
|
65
|
+
SignatureEnvelope.from({
|
|
66
|
+
config: { ...config, version: 1 },
|
|
67
|
+
signatures: [signature],
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const multisig = SignatureEnvelope.from({
|
|
71
|
+
account: '0x2222222222222222222222222222222222222222',
|
|
72
|
+
config: { ...config, version: 1 },
|
|
73
|
+
signatures: [signature],
|
|
74
|
+
})
|
|
75
|
+
expectTypeOf(multisig).toMatchTypeOf<SignatureEnvelope.Multisig>()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('MultisigRpc rejects old witness shapes', () => {
|
|
79
|
+
const accountOnly = {
|
|
64
80
|
account: '0x1111111111111111111111111111111111111111',
|
|
65
|
-
init: {
|
|
66
|
-
owners: [
|
|
67
|
-
{
|
|
68
|
-
owner: '0x1111111111111111111111111111111111111111',
|
|
69
|
-
weight: 1,
|
|
70
|
-
},
|
|
71
|
-
],
|
|
72
|
-
threshold: 1,
|
|
73
|
-
},
|
|
74
81
|
signatures: [signatureRpc],
|
|
75
82
|
} as const
|
|
76
|
-
// @ts-expect-error
|
|
77
|
-
const
|
|
83
|
+
// @ts-expect-error Multisig RPC signatures require config.
|
|
84
|
+
const accountOnlyRpc: SignatureEnvelope.MultisigRpc = accountOnly
|
|
85
|
+
|
|
86
|
+
const init = {
|
|
87
|
+
init: config,
|
|
88
|
+
signatures: [signatureRpc],
|
|
89
|
+
} as const
|
|
90
|
+
// @ts-expect-error Multisig RPC signatures no longer accept init.
|
|
91
|
+
const initRpc: SignatureEnvelope.MultisigRpc = init
|
|
78
92
|
|
|
79
93
|
const serialized = {
|
|
80
94
|
account: '0x1111111111111111111111111111111111111111',
|
|
95
|
+
config: MultisigConfig.toRpc(config),
|
|
81
96
|
signatures: ['0x1234'],
|
|
82
97
|
} as const
|
|
83
98
|
// @ts-expect-error Owner approvals use structured RPC envelopes.
|
|
@@ -85,13 +100,15 @@ test('MultisigRpc rejects legacy shapes', () => {
|
|
|
85
100
|
|
|
86
101
|
const tagged = {
|
|
87
102
|
account: '0x1111111111111111111111111111111111111111',
|
|
103
|
+
config: MultisigConfig.toRpc(config),
|
|
88
104
|
signatures: [signatureRpc],
|
|
89
105
|
type: 'multisig',
|
|
90
106
|
} as const
|
|
91
107
|
// @ts-expect-error Multisig RPC signatures are untagged.
|
|
92
108
|
const taggedRpc: SignatureEnvelope.MultisigRpc = tagged
|
|
93
109
|
|
|
94
|
-
expectTypeOf(
|
|
110
|
+
expectTypeOf(accountOnlyRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
111
|
+
expectTypeOf(initRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
95
112
|
expectTypeOf(serializedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
96
113
|
expectTypeOf(taggedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
97
114
|
})
|