ox 0.14.29 → 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.
- package/CHANGELOG.md +30 -0
- package/_cjs/core/AbiParameters.js +2 -1
- package/_cjs/core/AbiParameters.js.map +1 -1
- package/_cjs/core/internal/abiParameters.js +10 -2
- package/_cjs/core/internal/abiParameters.js.map +1 -1
- package/_cjs/tempo/KeyAuthorization.js +39 -10
- package/_cjs/tempo/KeyAuthorization.js.map +1 -1
- package/_cjs/tempo/MultisigConfig.js +26 -26
- package/_cjs/tempo/MultisigConfig.js.map +1 -1
- package/_cjs/tempo/SignatureEnvelope.js +189 -55
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/core/AbiParameters.js +4 -1
- package/_esm/core/AbiParameters.js.map +1 -1
- package/_esm/core/internal/abiParameters.js +17 -2
- package/_esm/core/internal/abiParameters.js.map +1 -1
- package/_esm/tempo/KeyAuthorization.js +41 -9
- package/_esm/tempo/KeyAuthorization.js.map +1 -1
- package/_esm/tempo/MultisigConfig.js +53 -81
- package/_esm/tempo/MultisigConfig.js.map +1 -1
- package/_esm/tempo/SignatureEnvelope.js +202 -75
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/tempo/index.js +2 -2
- package/_esm/version.js +1 -1
- package/_types/core/AbiParameters.d.ts.map +1 -1
- package/_types/core/internal/abiParameters.d.ts.map +1 -1
- package/_types/core/internal/rpcSchemas/eth.d.ts +16 -0
- package/_types/core/internal/rpcSchemas/eth.d.ts.map +1 -1
- package/_types/tempo/KeyAuthorization.d.ts +22 -16
- package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
- package/_types/tempo/MultisigConfig.d.ts +43 -83
- package/_types/tempo/MultisigConfig.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +62 -40
- 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/core/AbiParameters.ts +3 -1
- package/core/internal/abiParameters.ts +17 -2
- package/core/internal/rpcSchemas/eth.ts +16 -0
- package/package.json +11 -1
- package/tempo/KeyAuthorization.test-d/package.json +6 -0
- package/tempo/KeyAuthorization.test-d.ts +62 -0
- package/tempo/KeyAuthorization.test.ts +132 -0
- package/tempo/KeyAuthorization.ts +76 -29
- package/tempo/MultisigConfig.test.ts +86 -37
- package/tempo/MultisigConfig.ts +75 -132
- package/tempo/SignatureEnvelope.test-d/package.json +6 -0
- package/tempo/SignatureEnvelope.test-d.ts +97 -0
- package/tempo/SignatureEnvelope.test.ts +322 -30
- package/tempo/SignatureEnvelope.ts +314 -118
- package/tempo/e2e.test.ts +22 -207
- package/tempo/index.ts +2 -2
- package/tempo/multisig.e2e.test.ts +582 -0
- package/version.ts +1 -1
|
@@ -72,11 +72,15 @@ export type GetType<
|
|
|
72
72
|
userAddress: Address.Address
|
|
73
73
|
}
|
|
74
74
|
? 'keychain'
|
|
75
|
-
: envelope extends
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
: envelope extends
|
|
76
|
+
| {
|
|
77
|
+
account: Address.Address
|
|
78
|
+
signatures: any
|
|
79
|
+
}
|
|
80
|
+
| {
|
|
81
|
+
init: MultisigConfig.Config
|
|
82
|
+
signatures: any
|
|
83
|
+
}
|
|
80
84
|
? 'multisig'
|
|
81
85
|
: never
|
|
82
86
|
|
|
@@ -118,6 +122,16 @@ export type SignatureEnvelopeRpc = OneOf<
|
|
|
118
122
|
Secp256k1Rpc | P256Rpc | WebAuthnRpc | KeychainRpc | MultisigRpc
|
|
119
123
|
>
|
|
120
124
|
|
|
125
|
+
/** Primitive signature envelope accepted by protocol sidecars. */
|
|
126
|
+
export type Primitive<bigintType = bigint, numberType = number> = OneOf<
|
|
127
|
+
| Secp256k1<bigintType, numberType>
|
|
128
|
+
| P256<bigintType, numberType>
|
|
129
|
+
| WebAuthn<bigintType, numberType>
|
|
130
|
+
>
|
|
131
|
+
|
|
132
|
+
/** RPC-formatted primitive signature envelope. */
|
|
133
|
+
export type PrimitiveRpc = OneOf<Secp256k1Rpc | P256Rpc | WebAuthnRpc>
|
|
134
|
+
|
|
121
135
|
/**
|
|
122
136
|
* Keychain signature version.
|
|
123
137
|
*
|
|
@@ -150,9 +164,10 @@ export type KeychainRpc = {
|
|
|
150
164
|
/**
|
|
151
165
|
* Native multisig signature (type `0x05`).
|
|
152
166
|
*
|
|
153
|
-
* Wraps a set of
|
|
154
|
-
* multisig owner approval digest. The transaction sender is
|
|
155
|
-
* authorized once the recovered owner weights meet the
|
|
167
|
+
* Wraps a set of owner approvals (secp256k1, p256, webAuthn, or nested
|
|
168
|
+
* multisig) over the multisig owner approval digest. The transaction sender is
|
|
169
|
+
* the derived `account`, authorized once the recovered owner weights meet the
|
|
170
|
+
* configured threshold.
|
|
156
171
|
*
|
|
157
172
|
* [TIP-1061](https://tips.sh/1061)
|
|
158
173
|
*/
|
|
@@ -160,9 +175,11 @@ export type Multisig<bigintType = bigint, numberType = number> = {
|
|
|
160
175
|
type: 'multisig'
|
|
161
176
|
/** Native multisig account address. */
|
|
162
177
|
account: Address.Address
|
|
163
|
-
/**
|
|
164
|
-
|
|
165
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Owner approvals over the multisig owner approval digest. Each approval is
|
|
180
|
+
* either a primitive signature or a nested multisig signature (keychain
|
|
181
|
+
* approvals are invalid).
|
|
182
|
+
*/
|
|
166
183
|
signatures: readonly SignatureEnvelope<bigintType, numberType>[]
|
|
167
184
|
/**
|
|
168
185
|
* Initial native multisig config for bootstrapping this account. Present only on
|
|
@@ -172,22 +189,25 @@ export type Multisig<bigintType = bigint, numberType = number> = {
|
|
|
172
189
|
init?: MultisigConfig.Config<numberType> | undefined
|
|
173
190
|
}
|
|
174
191
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
192
|
+
/** RPC-formatted native multisig signature. */
|
|
193
|
+
export type MultisigRpc = OneOf<
|
|
194
|
+
| {
|
|
195
|
+
/** Existing native multisig account. */
|
|
196
|
+
account: Address.Address
|
|
197
|
+
/** Structured owner approvals. */
|
|
198
|
+
signatures: readonly SignatureEnvelopeRpc[]
|
|
199
|
+
/** Multisig RPC signatures are untagged. */
|
|
200
|
+
type?: undefined
|
|
201
|
+
}
|
|
202
|
+
| {
|
|
203
|
+
/** Initial config for bootstrapping a native multisig account. */
|
|
204
|
+
init: MultisigConfig.Config
|
|
205
|
+
/** Structured owner approvals. */
|
|
206
|
+
signatures: readonly SignatureEnvelopeRpc[]
|
|
207
|
+
/** Multisig RPC signatures are untagged. */
|
|
208
|
+
type?: undefined
|
|
209
|
+
}
|
|
210
|
+
>
|
|
191
211
|
|
|
192
212
|
export type P256<bigintType = bigint, numberType = number> = {
|
|
193
213
|
prehash: boolean
|
|
@@ -331,14 +351,7 @@ export function assert(envelope: PartialBy<SignatureEnvelope, 'type'>): void {
|
|
|
331
351
|
|
|
332
352
|
if (type === 'multisig') {
|
|
333
353
|
const multisig = envelope as Multisig
|
|
334
|
-
|
|
335
|
-
if (!multisig.account) missing.push('account')
|
|
336
|
-
if (!multisig.genesisConfigId) missing.push('genesisConfigId')
|
|
337
|
-
if (!Array.isArray(multisig.signatures)) missing.push('signatures')
|
|
338
|
-
if (missing.length > 0)
|
|
339
|
-
throw new MissingPropertiesError({ envelope, missing, type: 'multisig' })
|
|
340
|
-
for (const inner of multisig.signatures) assert(inner)
|
|
341
|
-
if (multisig.init) MultisigConfig.assert(multisig.init)
|
|
354
|
+
assertMultisig(multisig, 1)
|
|
342
355
|
return
|
|
343
356
|
}
|
|
344
357
|
}
|
|
@@ -346,12 +359,80 @@ export function assert(envelope: PartialBy<SignatureEnvelope, 'type'>): void {
|
|
|
346
359
|
export declare namespace assert {
|
|
347
360
|
type ErrorType =
|
|
348
361
|
| CoercionError
|
|
362
|
+
| InvalidMultisigApprovalError
|
|
349
363
|
| MissingPropertiesError
|
|
350
364
|
| MultisigConfig.assert.ErrorType
|
|
365
|
+
| MultisigConfig.getAddress.ErrorType
|
|
351
366
|
| Signature.assert.ErrorType
|
|
352
367
|
| Errors.GlobalErrorType
|
|
353
368
|
}
|
|
354
369
|
|
|
370
|
+
function assertMultisig(envelope: Multisig, depth: number): void {
|
|
371
|
+
const missing: string[] = []
|
|
372
|
+
if (!envelope.account) missing.push('account')
|
|
373
|
+
if (!Array.isArray(envelope.signatures)) missing.push('signatures')
|
|
374
|
+
if (missing.length > 0)
|
|
375
|
+
throw new MissingPropertiesError({
|
|
376
|
+
envelope,
|
|
377
|
+
missing,
|
|
378
|
+
type: 'multisig',
|
|
379
|
+
})
|
|
380
|
+
if (depth > MultisigConfig.maxNestingDepth)
|
|
381
|
+
throw new InvalidMultisigApprovalError({
|
|
382
|
+
reason: `multisig nesting depth exceeds ${MultisigConfig.maxNestingDepth}`,
|
|
383
|
+
})
|
|
384
|
+
if (!Address.validate(envelope.account))
|
|
385
|
+
throw new InvalidMultisigApprovalError({
|
|
386
|
+
reason: 'multisig account is invalid',
|
|
387
|
+
})
|
|
388
|
+
if (Hex.toBigInt(envelope.account) === 0n)
|
|
389
|
+
throw new InvalidMultisigApprovalError({
|
|
390
|
+
reason: 'multisig account cannot be zero',
|
|
391
|
+
})
|
|
392
|
+
if (envelope.signatures.length === 0)
|
|
393
|
+
throw new InvalidMultisigApprovalError({
|
|
394
|
+
reason: 'multisig signatures cannot be empty',
|
|
395
|
+
})
|
|
396
|
+
if (envelope.signatures.length > MultisigConfig.maxSignatures)
|
|
397
|
+
throw new InvalidMultisigApprovalError({
|
|
398
|
+
reason: `multisig signatures exceed ${MultisigConfig.maxSignatures}`,
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
if (envelope.init) {
|
|
402
|
+
MultisigConfig.assert(envelope.init)
|
|
403
|
+
if (
|
|
404
|
+
!Address.isEqual(
|
|
405
|
+
MultisigConfig.getAddress(envelope.init),
|
|
406
|
+
envelope.account,
|
|
407
|
+
)
|
|
408
|
+
)
|
|
409
|
+
throw new InvalidMultisigApprovalError({
|
|
410
|
+
reason: 'multisig init does not derive account',
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
for (const inner of envelope.signatures) {
|
|
415
|
+
const type = getType(inner)
|
|
416
|
+
if (type === 'keychain')
|
|
417
|
+
throw new InvalidMultisigApprovalError({
|
|
418
|
+
reason: 'keychain owner approvals are not allowed',
|
|
419
|
+
})
|
|
420
|
+
if (type === 'multisig') {
|
|
421
|
+
const multisig = inner as Multisig
|
|
422
|
+
if (multisig.init)
|
|
423
|
+
throw new InvalidMultisigApprovalError({
|
|
424
|
+
reason: 'nested multisig owner approvals cannot carry `init`',
|
|
425
|
+
})
|
|
426
|
+
assertMultisig(multisig, depth + 1)
|
|
427
|
+
} else assert(inner)
|
|
428
|
+
|
|
429
|
+
if (Hex.size(serialize(inner)) > MultisigConfig.maxOwnerSignatureBytes)
|
|
430
|
+
throw new InvalidMultisigApprovalError({
|
|
431
|
+
reason: `multisig owner signature exceeds ${MultisigConfig.maxOwnerSignatureBytes} bytes`,
|
|
432
|
+
})
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
355
436
|
/**
|
|
356
437
|
* Extracts the address of the signer from a {@link ox#SignatureEnvelope.SignatureEnvelope}.
|
|
357
438
|
*
|
|
@@ -497,6 +578,13 @@ export declare namespace extractPublicKey {
|
|
|
497
578
|
* @throws `CoercionError` if the serialized value cannot be coerced to a valid signature envelope.
|
|
498
579
|
*/
|
|
499
580
|
export function deserialize(value: Serialized): SignatureEnvelope {
|
|
581
|
+
return deserialize_(value, 0)
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function deserialize_(
|
|
585
|
+
value: Serialized,
|
|
586
|
+
multisigDepth: number,
|
|
587
|
+
): SignatureEnvelope {
|
|
500
588
|
const serialized = value.endsWith(magicBytes.slice(2))
|
|
501
589
|
? Hex.slice(value, 0, -Hex.size(magicBytes))
|
|
502
590
|
: value
|
|
@@ -607,7 +695,7 @@ export function deserialize(value: Serialized): SignatureEnvelope {
|
|
|
607
695
|
typeId === serializedKeychainV2Type
|
|
608
696
|
) {
|
|
609
697
|
const userAddress = Hex.slice(data, 0, 20)
|
|
610
|
-
const inner =
|
|
698
|
+
const inner = deserialize_(Hex.slice(data, 20), multisigDepth)
|
|
611
699
|
|
|
612
700
|
return {
|
|
613
701
|
userAddress,
|
|
@@ -618,26 +706,91 @@ export function deserialize(value: Serialized): SignatureEnvelope {
|
|
|
618
706
|
}
|
|
619
707
|
|
|
620
708
|
if (typeId === serializedMultisigType) {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
709
|
+
const depth = multisigDepth + 1
|
|
710
|
+
if (depth > MultisigConfig.maxNestingDepth)
|
|
711
|
+
throw new InvalidSerializedError({
|
|
712
|
+
reason: `multisig nesting depth exceeds ${MultisigConfig.maxNestingDepth}`,
|
|
713
|
+
serialized,
|
|
714
|
+
})
|
|
715
|
+
|
|
716
|
+
// The first field distinguishes the static wire shapes: a bootstrap init
|
|
717
|
+
// config is an RLP list, while an initialized account is a 20-byte string.
|
|
718
|
+
const decoded = Rlp.toHex(data)
|
|
719
|
+
if (!Array.isArray(decoded) || decoded.length !== 2)
|
|
720
|
+
throw new InvalidSerializedError({
|
|
721
|
+
reason: 'invalid multisig wire shape: expected exactly two fields',
|
|
722
|
+
serialized,
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
const [address, signatures] = decoded
|
|
726
|
+
if (!Array.isArray(signatures) || signatures.some(Array.isArray))
|
|
727
|
+
throw new InvalidSerializedError({
|
|
728
|
+
reason: 'invalid multisig signatures list',
|
|
729
|
+
serialized,
|
|
730
|
+
})
|
|
731
|
+
if (signatures.length === 0)
|
|
732
|
+
throw new InvalidSerializedError({
|
|
733
|
+
reason: 'multisig signatures cannot be empty',
|
|
734
|
+
serialized,
|
|
735
|
+
})
|
|
736
|
+
if (signatures.length > MultisigConfig.maxSignatures)
|
|
737
|
+
throw new InvalidSerializedError({
|
|
738
|
+
reason: `multisig signatures exceed ${MultisigConfig.maxSignatures}`,
|
|
739
|
+
serialized,
|
|
740
|
+
})
|
|
741
|
+
for (const signature of signatures)
|
|
742
|
+
if (
|
|
743
|
+
Hex.size(signature as Hex.Hex) > MultisigConfig.maxOwnerSignatureBytes
|
|
744
|
+
)
|
|
745
|
+
throw new InvalidSerializedError({
|
|
746
|
+
reason: `multisig owner signature exceeds ${MultisigConfig.maxOwnerSignatureBytes} bytes`,
|
|
747
|
+
serialized,
|
|
748
|
+
})
|
|
749
|
+
|
|
750
|
+
if (!Array.isArray(address) && !Address.validate(address))
|
|
751
|
+
throw new InvalidSerializedError({
|
|
752
|
+
reason: 'invalid multisig account',
|
|
753
|
+
serialized,
|
|
754
|
+
})
|
|
755
|
+
if (Array.isArray(address)) {
|
|
756
|
+
const [salt, threshold, owners] = address
|
|
757
|
+
if (
|
|
758
|
+
address.length !== 3 ||
|
|
759
|
+
Array.isArray(salt) ||
|
|
760
|
+
Hex.size(salt) !== 32 ||
|
|
761
|
+
Array.isArray(threshold) ||
|
|
762
|
+
Hex.size(threshold) > 1 ||
|
|
763
|
+
!Array.isArray(owners) ||
|
|
764
|
+
owners.some(
|
|
765
|
+
(owner) =>
|
|
766
|
+
!Array.isArray(owner) ||
|
|
767
|
+
owner.length !== 2 ||
|
|
768
|
+
owner.some(Array.isArray) ||
|
|
769
|
+
Hex.size(owner[1] as Hex.Hex) > 1,
|
|
770
|
+
)
|
|
771
|
+
)
|
|
772
|
+
throw new InvalidSerializedError({
|
|
773
|
+
reason: 'invalid multisig init config',
|
|
774
|
+
serialized,
|
|
775
|
+
})
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const init = Array.isArray(address)
|
|
779
|
+
? MultisigConfig.fromTuple(address as unknown as MultisigConfig.Tuple)
|
|
780
|
+
: undefined
|
|
781
|
+
const account = init
|
|
782
|
+
? MultisigConfig.getAddress(init)
|
|
783
|
+
: (address as Address.Address)
|
|
784
|
+
const envelope = {
|
|
631
785
|
type: 'multisig',
|
|
632
786
|
account,
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
init: MultisigConfig.fromTuple(init as MultisigConfig.Tuple),
|
|
638
|
-
}
|
|
639
|
-
: {}),
|
|
787
|
+
signatures: signatures.map((signature) =>
|
|
788
|
+
deserialize_(signature as Hex.Hex, depth),
|
|
789
|
+
),
|
|
790
|
+
...(init ? { init } : {}),
|
|
640
791
|
} satisfies Multisig
|
|
792
|
+
assertMultisig(envelope, depth)
|
|
793
|
+
return envelope
|
|
641
794
|
}
|
|
642
795
|
|
|
643
796
|
throw new InvalidSerializedError({
|
|
@@ -761,9 +914,9 @@ export function deserialize(value: Serialized): SignatureEnvelope {
|
|
|
761
914
|
* @example
|
|
762
915
|
* ### Multisig (from genesis config)
|
|
763
916
|
*
|
|
764
|
-
* Pass `genesisConfig` to derive `account`
|
|
765
|
-
*
|
|
766
|
-
*
|
|
917
|
+
* Pass `genesisConfig` to derive `account` automatically. Set `init: true` to
|
|
918
|
+
* opt into bootstrap (uses `genesisConfig` as the bootstrap `init`); omit
|
|
919
|
+
* `init` for subsequent (non-bootstrap) transactions.
|
|
767
920
|
*
|
|
768
921
|
* ```ts twoslash
|
|
769
922
|
* import { Secp256k1 } from 'ox'
|
|
@@ -821,18 +974,12 @@ export function from<const value extends from.Value>(
|
|
|
821
974
|
init?: MultisigConfig.Config | boolean | undefined
|
|
822
975
|
}
|
|
823
976
|
const { genesisConfig, init, ...rest } = multisig
|
|
824
|
-
// Derive `account
|
|
825
|
-
// explicitly.
|
|
977
|
+
// Derive `account` from `genesisConfig` when not provided explicitly.
|
|
826
978
|
const account = (() => {
|
|
827
979
|
if (rest.account) return rest.account
|
|
828
980
|
if (genesisConfig) return MultisigConfig.getAddress(genesisConfig)
|
|
829
981
|
return rest.account
|
|
830
982
|
})()
|
|
831
|
-
const genesisConfigId = (() => {
|
|
832
|
-
if (rest.genesisConfigId) return rest.genesisConfigId
|
|
833
|
-
if (genesisConfig) return MultisigConfig.toId(genesisConfig)
|
|
834
|
-
return rest.genesisConfigId
|
|
835
|
-
})()
|
|
836
983
|
// `init: true` opts into bootstrap using the supplied `genesisConfig`.
|
|
837
984
|
// Otherwise, `init` is treated as the explicit bootstrap config (or
|
|
838
985
|
// omitted).
|
|
@@ -840,7 +987,6 @@ export function from<const value extends from.Value>(
|
|
|
840
987
|
return {
|
|
841
988
|
...rest,
|
|
842
989
|
account,
|
|
843
|
-
genesisConfigId,
|
|
844
990
|
signatures: rest.signatures.map((signature) => from(signature)),
|
|
845
991
|
// Normalize the bootstrap config (sorts owners, defaults the salt) so the
|
|
846
992
|
// in-memory envelope matches what `deserialize` reconstructs.
|
|
@@ -892,9 +1038,9 @@ export declare namespace from {
|
|
|
892
1038
|
}
|
|
893
1039
|
|
|
894
1040
|
/**
|
|
895
|
-
* Multisig envelope input variant where `account`
|
|
896
|
-
*
|
|
897
|
-
*
|
|
1041
|
+
* Multisig envelope input variant where `account` is derived from the
|
|
1042
|
+
* supplied `genesisConfig`. Pass `init: true` to opt into bootstrap (uses
|
|
1043
|
+
* `genesisConfig` as the bootstrap `init`); omit `init` for subsequent
|
|
898
1044
|
* (non-bootstrap) transactions.
|
|
899
1045
|
*/
|
|
900
1046
|
type MultisigFromGenesisConfig = {
|
|
@@ -1034,24 +1180,28 @@ export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope {
|
|
|
1034
1180
|
}
|
|
1035
1181
|
|
|
1036
1182
|
if (
|
|
1037
|
-
envelope.type === 'multisig' ||
|
|
1038
|
-
('account' in envelope
|
|
1039
|
-
'configId' in envelope &&
|
|
1040
|
-
'signatures' in envelope)
|
|
1183
|
+
(envelope as { type?: string | undefined }).type === 'multisig' ||
|
|
1184
|
+
('signatures' in envelope && ('account' in envelope || 'init' in envelope))
|
|
1041
1185
|
) {
|
|
1042
1186
|
const multisig = envelope as MultisigRpc
|
|
1043
|
-
|
|
1187
|
+
const hasAccount = typeof multisig.account !== 'undefined'
|
|
1188
|
+
const hasInit = typeof multisig.init !== 'undefined'
|
|
1189
|
+
if (hasAccount === hasInit)
|
|
1190
|
+
throw new InvalidMultisigApprovalError({
|
|
1191
|
+
reason: 'RPC multisig must contain exactly one of `account` or `init`',
|
|
1192
|
+
})
|
|
1193
|
+
const init = hasInit
|
|
1194
|
+
? MultisigConfig.from(multisig.init as MultisigConfig.Config)
|
|
1195
|
+
: undefined
|
|
1196
|
+
const account = init ? MultisigConfig.getAddress(init) : multisig.account
|
|
1197
|
+
const result = {
|
|
1044
1198
|
type: 'multisig',
|
|
1045
|
-
account:
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
deserialize(signature),
|
|
1052
|
-
),
|
|
1053
|
-
...(multisig.init ? { init: MultisigConfig.from(multisig.init) } : {}),
|
|
1054
|
-
}
|
|
1199
|
+
account: account as Address.Address,
|
|
1200
|
+
signatures: multisig.signatures.map((signature) => fromRpc(signature)),
|
|
1201
|
+
...(init ? { init } : {}),
|
|
1202
|
+
} satisfies Multisig
|
|
1203
|
+
assert(result)
|
|
1204
|
+
return result
|
|
1055
1205
|
}
|
|
1056
1206
|
|
|
1057
1207
|
throw new CoercionError({ envelope })
|
|
@@ -1059,8 +1209,10 @@ export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope {
|
|
|
1059
1209
|
|
|
1060
1210
|
export declare namespace fromRpc {
|
|
1061
1211
|
type ErrorType =
|
|
1212
|
+
| assert.ErrorType
|
|
1062
1213
|
| CoercionError
|
|
1063
1214
|
| InvalidSerializedError
|
|
1215
|
+
| MultisigConfig.getAddress.ErrorType
|
|
1064
1216
|
| Signature.fromRpc.ErrorType
|
|
1065
1217
|
| Errors.GlobalErrorType
|
|
1066
1218
|
}
|
|
@@ -1132,8 +1284,9 @@ export function getType<
|
|
|
1132
1284
|
|
|
1133
1285
|
// Detect Multisig signature
|
|
1134
1286
|
if (
|
|
1135
|
-
(
|
|
1136
|
-
'genesisConfig' in envelope
|
|
1287
|
+
('account' in envelope ||
|
|
1288
|
+
'genesisConfig' in envelope ||
|
|
1289
|
+
'init' in envelope) &&
|
|
1137
1290
|
'signatures' in envelope
|
|
1138
1291
|
)
|
|
1139
1292
|
return 'multisig' as never
|
|
@@ -1152,6 +1305,7 @@ export function getType<
|
|
|
1152
1305
|
* - WebAuthn: `0x02` + webauthnData (variable) + r (32) + s (32) + pubKeyX (32) + pubKeyY (32)
|
|
1153
1306
|
* - Keychain V1: `0x03` + userAddress (20) + inner signature (recursive)
|
|
1154
1307
|
* - Keychain V2: `0x04` + userAddress (20) + inner signature (recursive)
|
|
1308
|
+
* - Multisig: `0x05` + RLP `[account | init, signatures]`
|
|
1155
1309
|
*
|
|
1156
1310
|
* [Signature Types](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types)
|
|
1157
1311
|
*
|
|
@@ -1233,17 +1387,16 @@ export function serialize(
|
|
|
1233
1387
|
|
|
1234
1388
|
if (type === 'multisig') {
|
|
1235
1389
|
const multisig = envelope as Multisig
|
|
1236
|
-
|
|
1237
|
-
//
|
|
1238
|
-
// config
|
|
1239
|
-
// placeholder (`0x` → RLP `0x80`).
|
|
1390
|
+
assert(multisig)
|
|
1391
|
+
// The first field is either the initialized account or the bootstrap init
|
|
1392
|
+
// config. Each owner approval is an encoded signature.
|
|
1240
1393
|
return Hex.concat(
|
|
1241
1394
|
serializedMultisigType,
|
|
1242
1395
|
Rlp.fromHex([
|
|
1243
|
-
multisig.
|
|
1244
|
-
|
|
1396
|
+
multisig.init
|
|
1397
|
+
? MultisigConfig.toTuple(multisig.init)
|
|
1398
|
+
: multisig.account,
|
|
1245
1399
|
multisig.signatures.map((signature) => serialize(signature)),
|
|
1246
|
-
multisig.init ? MultisigConfig.toTuple(multisig.init) : '0x',
|
|
1247
1400
|
]),
|
|
1248
1401
|
options.magic ? magicBytes : '0x',
|
|
1249
1402
|
)
|
|
@@ -1260,6 +1413,16 @@ export declare namespace serialize {
|
|
|
1260
1413
|
*/
|
|
1261
1414
|
magic?: boolean | undefined
|
|
1262
1415
|
}
|
|
1416
|
+
|
|
1417
|
+
type ErrorType =
|
|
1418
|
+
| assert.ErrorType
|
|
1419
|
+
| CoercionError
|
|
1420
|
+
| Hex.concat.ErrorType
|
|
1421
|
+
| Hex.fromNumber.ErrorType
|
|
1422
|
+
| Hex.fromString.ErrorType
|
|
1423
|
+
| Rlp.fromHex.ErrorType
|
|
1424
|
+
| Signature.toHex.ErrorType
|
|
1425
|
+
| Errors.GlobalErrorType
|
|
1263
1426
|
}
|
|
1264
1427
|
|
|
1265
1428
|
/**
|
|
@@ -1271,10 +1434,10 @@ export declare namespace serialize {
|
|
|
1271
1434
|
* ({@link ox#MultisigConfig.(getSignPayload:function)}), so the signer of
|
|
1272
1435
|
* every approval is recovered against that digest and the list is sorted by the
|
|
1273
1436
|
* recovered owner address. Works for any owner key type (secp256k1, p256,
|
|
1274
|
-
* webAuthn
|
|
1437
|
+
* webAuthn).
|
|
1275
1438
|
*
|
|
1276
|
-
* Config updates never change `account
|
|
1277
|
-
*
|
|
1439
|
+
* Config updates never change `account`, so the genesis config is the correct
|
|
1440
|
+
* input even for post-update transactions.
|
|
1278
1441
|
*
|
|
1279
1442
|
* @example
|
|
1280
1443
|
* ```ts twoslash
|
|
@@ -1315,12 +1478,7 @@ export function sortMultisigApprovals(
|
|
|
1315
1478
|
const digest = MultisigConfig.getSignPayload(
|
|
1316
1479
|
'genesisConfig' in value && value.genesisConfig
|
|
1317
1480
|
? { 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
|
-
},
|
|
1481
|
+
: { payload, account: (value as { account: Address.Address }).account },
|
|
1324
1482
|
)
|
|
1325
1483
|
// Recover each signer once (decorate–sort–undecorate) rather than inside the
|
|
1326
1484
|
// comparator.
|
|
@@ -1337,20 +1495,17 @@ export declare namespace sortMultisigApprovals {
|
|
|
1337
1495
|
type Value = {
|
|
1338
1496
|
/** The inner transaction sign payload (`tx.signature_hash()`). */
|
|
1339
1497
|
payload: Hex.Hex | Bytes.Bytes
|
|
1340
|
-
/** The
|
|
1498
|
+
/** The owner approvals to order. */
|
|
1341
1499
|
signatures: readonly SignatureEnvelope[]
|
|
1342
1500
|
} & OneOf<
|
|
1343
1501
|
| {
|
|
1344
1502
|
/** The native multisig account address. */
|
|
1345
1503
|
account: Address.Address
|
|
1346
|
-
/** The permanent config ID. */
|
|
1347
|
-
genesisConfigId: Hex.Hex
|
|
1348
1504
|
}
|
|
1349
1505
|
| {
|
|
1350
1506
|
/**
|
|
1351
1507
|
* The initial multisig config (the bootstrap config that derived the
|
|
1352
|
-
* permanent `account`
|
|
1353
|
-
* automatically.
|
|
1508
|
+
* permanent `account`). Used to derive the account automatically.
|
|
1354
1509
|
*/
|
|
1355
1510
|
genesisConfig: MultisigConfig.Config
|
|
1356
1511
|
}
|
|
@@ -1378,7 +1533,9 @@ export declare namespace sortMultisigApprovals {
|
|
|
1378
1533
|
* @param envelope - The signature envelope to convert.
|
|
1379
1534
|
* @returns The RPC signature envelope with hex values.
|
|
1380
1535
|
*/
|
|
1381
|
-
export function toRpc
|
|
1536
|
+
export function toRpc<const envelope extends toRpc.Input>(
|
|
1537
|
+
envelope: envelope,
|
|
1538
|
+
): toRpc.ReturnType<envelope> {
|
|
1382
1539
|
const type = getType(envelope)
|
|
1383
1540
|
|
|
1384
1541
|
if (type === 'secp256k1') {
|
|
@@ -1386,7 +1543,7 @@ export function toRpc(envelope: SignatureEnvelope): SignatureEnvelopeRpc {
|
|
|
1386
1543
|
return {
|
|
1387
1544
|
...Signature.toRpc(secp256k1.signature),
|
|
1388
1545
|
type: 'secp256k1',
|
|
1389
|
-
}
|
|
1546
|
+
} as never
|
|
1390
1547
|
}
|
|
1391
1548
|
|
|
1392
1549
|
if (type === 'p256') {
|
|
@@ -1398,7 +1555,7 @@ export function toRpc(envelope: SignatureEnvelope): SignatureEnvelopeRpc {
|
|
|
1398
1555
|
r: Hex.fromNumber(p256.signature.r, { size: 32 }),
|
|
1399
1556
|
s: Hex.fromNumber(p256.signature.s, { size: 32 }),
|
|
1400
1557
|
type: 'p256',
|
|
1401
|
-
}
|
|
1558
|
+
} as never
|
|
1402
1559
|
}
|
|
1403
1560
|
|
|
1404
1561
|
if (type === 'webAuthn') {
|
|
@@ -1415,7 +1572,7 @@ export function toRpc(envelope: SignatureEnvelope): SignatureEnvelopeRpc {
|
|
|
1415
1572
|
s: Hex.fromNumber(webauthn.signature.s, { size: 32 }),
|
|
1416
1573
|
type: 'webAuthn',
|
|
1417
1574
|
webauthnData,
|
|
1418
|
-
}
|
|
1575
|
+
} as never
|
|
1419
1576
|
}
|
|
1420
1577
|
|
|
1421
1578
|
if (type === 'keychain') {
|
|
@@ -1426,28 +1583,57 @@ export function toRpc(envelope: SignatureEnvelope): SignatureEnvelopeRpc {
|
|
|
1426
1583
|
signature: toRpc(keychain.inner),
|
|
1427
1584
|
...(keychain.keyId ? { keyId: keychain.keyId } : {}),
|
|
1428
1585
|
...(keychain.version ? { version: keychain.version } : {}),
|
|
1429
|
-
}
|
|
1586
|
+
} as never
|
|
1430
1587
|
}
|
|
1431
1588
|
|
|
1432
1589
|
if (type === 'multisig') {
|
|
1433
1590
|
const multisig = envelope as Multisig
|
|
1591
|
+
assert(multisig)
|
|
1592
|
+
const signatures = multisig.signatures.map((signature) => toRpc(signature))
|
|
1593
|
+
if (multisig.init) {
|
|
1594
|
+
const init = {
|
|
1595
|
+
...multisig.init,
|
|
1596
|
+
salt: multisig.init.salt ?? MultisigConfig.zeroSalt,
|
|
1597
|
+
threshold: Number(multisig.init.threshold),
|
|
1598
|
+
owners: multisig.init.owners.map((owner) => ({
|
|
1599
|
+
...owner,
|
|
1600
|
+
weight: Number(owner.weight),
|
|
1601
|
+
})),
|
|
1602
|
+
}
|
|
1603
|
+
return {
|
|
1604
|
+
init,
|
|
1605
|
+
signatures,
|
|
1606
|
+
} as never
|
|
1607
|
+
}
|
|
1434
1608
|
return {
|
|
1435
|
-
type: 'multisig',
|
|
1436
1609
|
account: multisig.account,
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
configId: multisig.genesisConfigId,
|
|
1440
|
-
// Owner approvals are raw serialized signatures (node `Vec<Bytes>`).
|
|
1441
|
-
signatures: multisig.signatures.map((signature) => serialize(signature)),
|
|
1442
|
-
...(multisig.init ? { init: multisig.init } : {}),
|
|
1443
|
-
}
|
|
1610
|
+
signatures,
|
|
1611
|
+
} as never
|
|
1444
1612
|
}
|
|
1445
1613
|
|
|
1446
1614
|
throw new CoercionError({ envelope })
|
|
1447
1615
|
}
|
|
1448
1616
|
|
|
1449
1617
|
export declare namespace toRpc {
|
|
1618
|
+
/** Input accepted by {@link ox#SignatureEnvelope.(toRpc:function)}. */
|
|
1619
|
+
type Input = SignatureEnvelope
|
|
1620
|
+
|
|
1621
|
+
/** RPC signature envelope inferred from the input type. */
|
|
1622
|
+
type ReturnType<envelope extends Input = Input> =
|
|
1623
|
+
GetType<envelope> extends 'secp256k1'
|
|
1624
|
+
? Secp256k1Rpc
|
|
1625
|
+
: GetType<envelope> extends 'p256'
|
|
1626
|
+
? P256Rpc
|
|
1627
|
+
: GetType<envelope> extends 'webAuthn'
|
|
1628
|
+
? WebAuthnRpc
|
|
1629
|
+
: GetType<envelope> extends 'keychain'
|
|
1630
|
+
? KeychainRpc
|
|
1631
|
+
: GetType<envelope> extends 'multisig'
|
|
1632
|
+
? MultisigRpc
|
|
1633
|
+
: SignatureEnvelopeRpc
|
|
1634
|
+
|
|
1450
1635
|
type ErrorType =
|
|
1636
|
+
| assert.ErrorType
|
|
1451
1637
|
| CoercionError
|
|
1452
1638
|
| Signature.toRpc.ErrorType
|
|
1453
1639
|
| Errors.GlobalErrorType
|
|
@@ -1706,6 +1892,16 @@ export class InvalidSerializedError extends Errors.BaseError {
|
|
|
1706
1892
|
}
|
|
1707
1893
|
}
|
|
1708
1894
|
|
|
1895
|
+
/**
|
|
1896
|
+
* Error thrown when a native multisig owner approval is invalid.
|
|
1897
|
+
*/
|
|
1898
|
+
export class InvalidMultisigApprovalError extends Errors.BaseError {
|
|
1899
|
+
override readonly name = 'SignatureEnvelope.InvalidMultisigApprovalError'
|
|
1900
|
+
constructor({ reason }: { reason: string }) {
|
|
1901
|
+
super(`Invalid native multisig owner approval: ${reason}.`)
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1709
1905
|
/**
|
|
1710
1906
|
* Error thrown when a signature envelope fails to verify.
|
|
1711
1907
|
*/
|