ox 0.14.29 → 0.14.30
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 +18 -0
- package/_cjs/tempo/MultisigConfig.js +16 -26
- package/_cjs/tempo/MultisigConfig.js.map +1 -1
- package/_cjs/tempo/SignatureEnvelope.js +46 -33
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/tempo/MultisigConfig.js +39 -79
- package/_esm/tempo/MultisigConfig.js.map +1 -1
- package/_esm/tempo/SignatureEnvelope.js +61 -51
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/tempo/index.js +2 -2
- package/_esm/version.js +1 -1
- package/_types/tempo/MultisigConfig.d.ts +37 -81
- package/_types/tempo/MultisigConfig.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +32 -30
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/tempo/index.d.ts +2 -2
- package/_types/version.d.ts +1 -1
- package/package.json +1 -1
- package/tempo/MultisigConfig.test.ts +54 -37
- package/tempo/MultisigConfig.ts +59 -130
- package/tempo/SignatureEnvelope.test.ts +118 -17
- package/tempo/SignatureEnvelope.ts +77 -72
- package/tempo/e2e.test.ts +243 -12
- package/tempo/index.ts +2 -2
- package/version.ts +1 -1
|
@@ -74,7 +74,6 @@ export type GetType<
|
|
|
74
74
|
? 'keychain'
|
|
75
75
|
: envelope extends {
|
|
76
76
|
account: Address.Address
|
|
77
|
-
genesisConfigId: `0x${string}`
|
|
78
77
|
signatures: any
|
|
79
78
|
}
|
|
80
79
|
? 'multisig'
|
|
@@ -150,9 +149,10 @@ export type KeychainRpc = {
|
|
|
150
149
|
/**
|
|
151
150
|
* Native multisig signature (type `0x05`).
|
|
152
151
|
*
|
|
153
|
-
* Wraps a set of
|
|
154
|
-
* multisig owner approval digest. The transaction sender is
|
|
155
|
-
* authorized once the recovered owner weights meet the
|
|
152
|
+
* Wraps a set of owner approvals (secp256k1, p256, webAuthn, or nested
|
|
153
|
+
* multisig) over the multisig owner approval digest. The transaction sender is
|
|
154
|
+
* the derived `account`, authorized once the recovered owner weights meet the
|
|
155
|
+
* configured threshold.
|
|
156
156
|
*
|
|
157
157
|
* [TIP-1061](https://tips.sh/1061)
|
|
158
158
|
*/
|
|
@@ -160,9 +160,11 @@ export type Multisig<bigintType = bigint, numberType = number> = {
|
|
|
160
160
|
type: 'multisig'
|
|
161
161
|
/** Native multisig account address. */
|
|
162
162
|
account: Address.Address
|
|
163
|
-
/**
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Owner approvals over the multisig owner approval digest. Each approval is
|
|
165
|
+
* either a primitive signature or a nested multisig signature (keychain
|
|
166
|
+
* approvals are invalid).
|
|
167
|
+
*/
|
|
166
168
|
signatures: readonly SignatureEnvelope<bigintType, numberType>[]
|
|
167
169
|
/**
|
|
168
170
|
* Initial native multisig config for bootstrapping this account. Present only on
|
|
@@ -176,14 +178,8 @@ export type MultisigRpc = {
|
|
|
176
178
|
type: 'multisig'
|
|
177
179
|
account: Address.Address
|
|
178
180
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* {@link ox#SignatureEnvelope.Multisig} envelope.
|
|
182
|
-
*/
|
|
183
|
-
configId: Hex.Hex
|
|
184
|
-
/**
|
|
185
|
-
* Encoded primitive owner approvals (raw serialized signatures), matching the
|
|
186
|
-
* node's `Vec<Bytes>` representation.
|
|
181
|
+
* Encoded owner approvals (raw serialized signatures), matching the node's
|
|
182
|
+
* `Vec<Bytes>` representation.
|
|
187
183
|
*/
|
|
188
184
|
signatures: readonly Serialized[]
|
|
189
185
|
init?: MultisigConfig.Config | undefined
|
|
@@ -333,11 +329,24 @@ export function assert(envelope: PartialBy<SignatureEnvelope, 'type'>): void {
|
|
|
333
329
|
const multisig = envelope as Multisig
|
|
334
330
|
const missing: string[] = []
|
|
335
331
|
if (!multisig.account) missing.push('account')
|
|
336
|
-
if (!multisig.genesisConfigId) missing.push('genesisConfigId')
|
|
337
332
|
if (!Array.isArray(multisig.signatures)) missing.push('signatures')
|
|
338
333
|
if (missing.length > 0)
|
|
339
334
|
throw new MissingPropertiesError({ envelope, missing, type: 'multisig' })
|
|
340
|
-
|
|
335
|
+
if (getNestingDepth(multisig) > MultisigConfig.maxNestingDepth)
|
|
336
|
+
throw new InvalidMultisigApprovalError({
|
|
337
|
+
reason: `multisig nesting depth exceeds ${MultisigConfig.maxNestingDepth}`,
|
|
338
|
+
})
|
|
339
|
+
for (const inner of multisig.signatures) {
|
|
340
|
+
if (inner.type === 'keychain')
|
|
341
|
+
throw new InvalidMultisigApprovalError({
|
|
342
|
+
reason: 'keychain owner approvals are not allowed',
|
|
343
|
+
})
|
|
344
|
+
if (inner.type === 'multisig' && inner.init)
|
|
345
|
+
throw new InvalidMultisigApprovalError({
|
|
346
|
+
reason: 'nested multisig owner approvals cannot carry `init`',
|
|
347
|
+
})
|
|
348
|
+
assert(inner)
|
|
349
|
+
}
|
|
341
350
|
if (multisig.init) MultisigConfig.assert(multisig.init)
|
|
342
351
|
return
|
|
343
352
|
}
|
|
@@ -346,12 +355,22 @@ export function assert(envelope: PartialBy<SignatureEnvelope, 'type'>): void {
|
|
|
346
355
|
export declare namespace assert {
|
|
347
356
|
type ErrorType =
|
|
348
357
|
| CoercionError
|
|
358
|
+
| InvalidMultisigApprovalError
|
|
349
359
|
| MissingPropertiesError
|
|
350
360
|
| MultisigConfig.assert.ErrorType
|
|
351
361
|
| Signature.assert.ErrorType
|
|
352
362
|
| Errors.GlobalErrorType
|
|
353
363
|
}
|
|
354
364
|
|
|
365
|
+
// Returns the deepest multisig path length within a multisig envelope.
|
|
366
|
+
function getNestingDepth(envelope: Multisig): number {
|
|
367
|
+
let deepest = 0
|
|
368
|
+
for (const inner of envelope.signatures)
|
|
369
|
+
if (inner.type === 'multisig')
|
|
370
|
+
deepest = Math.max(deepest, getNestingDepth(inner))
|
|
371
|
+
return 1 + deepest
|
|
372
|
+
}
|
|
373
|
+
|
|
355
374
|
/**
|
|
356
375
|
* Extracts the address of the signer from a {@link ox#SignatureEnvelope.SignatureEnvelope}.
|
|
357
376
|
*
|
|
@@ -618,24 +637,26 @@ export function deserialize(value: Serialized): SignatureEnvelope {
|
|
|
618
637
|
}
|
|
619
638
|
|
|
620
639
|
if (typeId === serializedMultisigType) {
|
|
621
|
-
// Wire format: `0x05 || rlp([account,
|
|
622
|
-
//
|
|
623
|
-
//
|
|
624
|
-
const [account,
|
|
625
|
-
Hex.Hex,
|
|
640
|
+
// Wire format: `0x05 || rlp([account, signatures, init?])`. `init` is a
|
|
641
|
+
// trailing optional: the bootstrap config list when present, otherwise
|
|
642
|
+
// omitted entirely (the empty `0x80` placeholder is rejected).
|
|
643
|
+
const [account, signatures, init] = Rlp.toHex(data) as [
|
|
626
644
|
Hex.Hex,
|
|
627
645
|
readonly Hex.Hex[],
|
|
628
646
|
(Hex.Hex | MultisigConfig.Tuple)?,
|
|
629
647
|
]
|
|
648
|
+
if (typeof init !== 'undefined' && !Array.isArray(init))
|
|
649
|
+
throw new InvalidSerializedError({
|
|
650
|
+
reason:
|
|
651
|
+
'Invalid multisig `init`: expected the bootstrap config list or an omitted trailing element',
|
|
652
|
+
serialized,
|
|
653
|
+
})
|
|
630
654
|
return {
|
|
631
655
|
type: 'multisig',
|
|
632
656
|
account,
|
|
633
|
-
genesisConfigId,
|
|
634
657
|
signatures: signatures.map((signature) => deserialize(signature)),
|
|
635
|
-
...(init
|
|
636
|
-
? {
|
|
637
|
-
init: MultisigConfig.fromTuple(init as MultisigConfig.Tuple),
|
|
638
|
-
}
|
|
658
|
+
...(init
|
|
659
|
+
? { init: MultisigConfig.fromTuple(init as MultisigConfig.Tuple) }
|
|
639
660
|
: {}),
|
|
640
661
|
} satisfies Multisig
|
|
641
662
|
}
|
|
@@ -761,9 +782,9 @@ export function deserialize(value: Serialized): SignatureEnvelope {
|
|
|
761
782
|
* @example
|
|
762
783
|
* ### Multisig (from genesis config)
|
|
763
784
|
*
|
|
764
|
-
* Pass `genesisConfig` to derive `account`
|
|
765
|
-
*
|
|
766
|
-
*
|
|
785
|
+
* Pass `genesisConfig` to derive `account` automatically. Set `init: true` to
|
|
786
|
+
* opt into bootstrap (uses `genesisConfig` as the bootstrap `init`); omit
|
|
787
|
+
* `init` for subsequent (non-bootstrap) transactions.
|
|
767
788
|
*
|
|
768
789
|
* ```ts twoslash
|
|
769
790
|
* import { Secp256k1 } from 'ox'
|
|
@@ -821,18 +842,12 @@ export function from<const value extends from.Value>(
|
|
|
821
842
|
init?: MultisigConfig.Config | boolean | undefined
|
|
822
843
|
}
|
|
823
844
|
const { genesisConfig, init, ...rest } = multisig
|
|
824
|
-
// Derive `account
|
|
825
|
-
// explicitly.
|
|
845
|
+
// Derive `account` from `genesisConfig` when not provided explicitly.
|
|
826
846
|
const account = (() => {
|
|
827
847
|
if (rest.account) return rest.account
|
|
828
848
|
if (genesisConfig) return MultisigConfig.getAddress(genesisConfig)
|
|
829
849
|
return rest.account
|
|
830
850
|
})()
|
|
831
|
-
const genesisConfigId = (() => {
|
|
832
|
-
if (rest.genesisConfigId) return rest.genesisConfigId
|
|
833
|
-
if (genesisConfig) return MultisigConfig.toId(genesisConfig)
|
|
834
|
-
return rest.genesisConfigId
|
|
835
|
-
})()
|
|
836
851
|
// `init: true` opts into bootstrap using the supplied `genesisConfig`.
|
|
837
852
|
// Otherwise, `init` is treated as the explicit bootstrap config (or
|
|
838
853
|
// omitted).
|
|
@@ -840,7 +855,6 @@ export function from<const value extends from.Value>(
|
|
|
840
855
|
return {
|
|
841
856
|
...rest,
|
|
842
857
|
account,
|
|
843
|
-
genesisConfigId,
|
|
844
858
|
signatures: rest.signatures.map((signature) => from(signature)),
|
|
845
859
|
// Normalize the bootstrap config (sorts owners, defaults the salt) so the
|
|
846
860
|
// in-memory envelope matches what `deserialize` reconstructs.
|
|
@@ -892,9 +906,9 @@ export declare namespace from {
|
|
|
892
906
|
}
|
|
893
907
|
|
|
894
908
|
/**
|
|
895
|
-
* Multisig envelope input variant where `account`
|
|
896
|
-
*
|
|
897
|
-
*
|
|
909
|
+
* Multisig envelope input variant where `account` is derived from the
|
|
910
|
+
* supplied `genesisConfig`. Pass `init: true` to opt into bootstrap (uses
|
|
911
|
+
* `genesisConfig` as the bootstrap `init`); omit `init` for subsequent
|
|
898
912
|
* (non-bootstrap) transactions.
|
|
899
913
|
*/
|
|
900
914
|
type MultisigFromGenesisConfig = {
|
|
@@ -1035,17 +1049,12 @@ export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope {
|
|
|
1035
1049
|
|
|
1036
1050
|
if (
|
|
1037
1051
|
envelope.type === 'multisig' ||
|
|
1038
|
-
('account' in envelope &&
|
|
1039
|
-
'configId' in envelope &&
|
|
1040
|
-
'signatures' in envelope)
|
|
1052
|
+
('account' in envelope && 'signatures' in envelope)
|
|
1041
1053
|
) {
|
|
1042
1054
|
const multisig = envelope as MultisigRpc
|
|
1043
1055
|
return {
|
|
1044
1056
|
type: 'multisig',
|
|
1045
1057
|
account: multisig.account,
|
|
1046
|
-
// Map RPC wire field `configId` (TIP-1061 spec name) to the typed
|
|
1047
|
-
// envelope's `genesisConfigId`.
|
|
1048
|
-
genesisConfigId: multisig.configId,
|
|
1049
1058
|
// Owner approvals are raw serialized signatures (node `Vec<Bytes>`).
|
|
1050
1059
|
signatures: multisig.signatures.map((signature) =>
|
|
1051
1060
|
deserialize(signature),
|
|
@@ -1132,8 +1141,7 @@ export function getType<
|
|
|
1132
1141
|
|
|
1133
1142
|
// Detect Multisig signature
|
|
1134
1143
|
if (
|
|
1135
|
-
(
|
|
1136
|
-
'genesisConfig' in envelope) &&
|
|
1144
|
+
('account' in envelope || 'genesisConfig' in envelope) &&
|
|
1137
1145
|
'signatures' in envelope
|
|
1138
1146
|
)
|
|
1139
1147
|
return 'multisig' as never
|
|
@@ -1233,17 +1241,15 @@ export function serialize(
|
|
|
1233
1241
|
|
|
1234
1242
|
if (type === 'multisig') {
|
|
1235
1243
|
const multisig = envelope as Multisig
|
|
1236
|
-
// Format: `0x05 || rlp([account,
|
|
1237
|
-
//
|
|
1238
|
-
// config
|
|
1239
|
-
// placeholder (`0x` → RLP `0x80`).
|
|
1244
|
+
// Format: `0x05 || rlp([account, signatures, init?])`, where each owner
|
|
1245
|
+
// approval is an encoded signature. `init` is a trailing optional: the
|
|
1246
|
+
// bootstrap config list when present, otherwise omitted entirely.
|
|
1240
1247
|
return Hex.concat(
|
|
1241
1248
|
serializedMultisigType,
|
|
1242
1249
|
Rlp.fromHex([
|
|
1243
1250
|
multisig.account,
|
|
1244
|
-
multisig.genesisConfigId,
|
|
1245
1251
|
multisig.signatures.map((signature) => serialize(signature)),
|
|
1246
|
-
multisig.init ? MultisigConfig.toTuple(multisig.init) :
|
|
1252
|
+
...(multisig.init ? [MultisigConfig.toTuple(multisig.init)] : []),
|
|
1247
1253
|
]),
|
|
1248
1254
|
options.magic ? magicBytes : '0x',
|
|
1249
1255
|
)
|
|
@@ -1271,10 +1277,10 @@ export declare namespace serialize {
|
|
|
1271
1277
|
* ({@link ox#MultisigConfig.(getSignPayload:function)}), so the signer of
|
|
1272
1278
|
* every approval is recovered against that digest and the list is sorted by the
|
|
1273
1279
|
* recovered owner address. Works for any owner key type (secp256k1, p256,
|
|
1274
|
-
* webAuthn
|
|
1280
|
+
* webAuthn).
|
|
1275
1281
|
*
|
|
1276
|
-
* Config updates never change `account
|
|
1277
|
-
*
|
|
1282
|
+
* Config updates never change `account`, so the genesis config is the correct
|
|
1283
|
+
* input even for post-update transactions.
|
|
1278
1284
|
*
|
|
1279
1285
|
* @example
|
|
1280
1286
|
* ```ts twoslash
|
|
@@ -1315,12 +1321,7 @@ export function sortMultisigApprovals(
|
|
|
1315
1321
|
const digest = MultisigConfig.getSignPayload(
|
|
1316
1322
|
'genesisConfig' in value && value.genesisConfig
|
|
1317
1323
|
? { payload, genesisConfig: value.genesisConfig }
|
|
1318
|
-
: {
|
|
1319
|
-
payload,
|
|
1320
|
-
account: (value as { account: Address.Address }).account,
|
|
1321
|
-
genesisConfigId: (value as { genesisConfigId: Hex.Hex })
|
|
1322
|
-
.genesisConfigId,
|
|
1323
|
-
},
|
|
1324
|
+
: { payload, account: (value as { account: Address.Address }).account },
|
|
1324
1325
|
)
|
|
1325
1326
|
// Recover each signer once (decorate–sort–undecorate) rather than inside the
|
|
1326
1327
|
// comparator.
|
|
@@ -1337,20 +1338,17 @@ export declare namespace sortMultisigApprovals {
|
|
|
1337
1338
|
type Value = {
|
|
1338
1339
|
/** The inner transaction sign payload (`tx.signature_hash()`). */
|
|
1339
1340
|
payload: Hex.Hex | Bytes.Bytes
|
|
1340
|
-
/** The
|
|
1341
|
+
/** The owner approvals to order. */
|
|
1341
1342
|
signatures: readonly SignatureEnvelope[]
|
|
1342
1343
|
} & OneOf<
|
|
1343
1344
|
| {
|
|
1344
1345
|
/** The native multisig account address. */
|
|
1345
1346
|
account: Address.Address
|
|
1346
|
-
/** The permanent config ID. */
|
|
1347
|
-
genesisConfigId: Hex.Hex
|
|
1348
1347
|
}
|
|
1349
1348
|
| {
|
|
1350
1349
|
/**
|
|
1351
1350
|
* The initial multisig config (the bootstrap config that derived the
|
|
1352
|
-
* permanent `account`
|
|
1353
|
-
* automatically.
|
|
1351
|
+
* permanent `account`). Used to derive the account automatically.
|
|
1354
1352
|
*/
|
|
1355
1353
|
genesisConfig: MultisigConfig.Config
|
|
1356
1354
|
}
|
|
@@ -1434,9 +1432,6 @@ export function toRpc(envelope: SignatureEnvelope): SignatureEnvelopeRpc {
|
|
|
1434
1432
|
return {
|
|
1435
1433
|
type: 'multisig',
|
|
1436
1434
|
account: multisig.account,
|
|
1437
|
-
// Map the typed envelope's `genesisConfigId` to the RPC wire field
|
|
1438
|
-
// `configId` (TIP-1061 spec name).
|
|
1439
|
-
configId: multisig.genesisConfigId,
|
|
1440
1435
|
// Owner approvals are raw serialized signatures (node `Vec<Bytes>`).
|
|
1441
1436
|
signatures: multisig.signatures.map((signature) => serialize(signature)),
|
|
1442
1437
|
...(multisig.init ? { init: multisig.init } : {}),
|
|
@@ -1706,6 +1701,16 @@ export class InvalidSerializedError extends Errors.BaseError {
|
|
|
1706
1701
|
}
|
|
1707
1702
|
}
|
|
1708
1703
|
|
|
1704
|
+
/**
|
|
1705
|
+
* Error thrown when a native multisig owner approval is invalid.
|
|
1706
|
+
*/
|
|
1707
|
+
export class InvalidMultisigApprovalError extends Errors.BaseError {
|
|
1708
|
+
override readonly name = 'SignatureEnvelope.InvalidMultisigApprovalError'
|
|
1709
|
+
constructor({ reason }: { reason: string }) {
|
|
1710
|
+
super(`Invalid native multisig owner approval: ${reason}.`)
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1709
1714
|
/**
|
|
1710
1715
|
* Error thrown when a signature envelope fails to verify.
|
|
1711
1716
|
*/
|