ox 0.14.44 → 0.14.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/_cjs/tempo/KeyAuthorization.js +43 -10
- package/_cjs/tempo/KeyAuthorization.js.map +1 -1
- package/_cjs/tempo/MultisigConfig.js +33 -11
- package/_cjs/tempo/MultisigConfig.js.map +1 -1
- package/_cjs/tempo/MultisigOperation.js +18 -94
- package/_cjs/tempo/MultisigOperation.js.map +1 -1
- package/_cjs/tempo/MultisigSimulation.js +18 -46
- package/_cjs/tempo/MultisigSimulation.js.map +1 -1
- package/_cjs/tempo/SignatureEnvelope.js +72 -109
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/tempo/TransactionRequest.js +5 -1
- package/_cjs/tempo/TransactionRequest.js.map +1 -1
- package/_cjs/tempo/index.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/tempo/KeyAuthorization.js +42 -9
- package/_esm/tempo/KeyAuthorization.js.map +1 -1
- package/_esm/tempo/MultisigConfig.js +41 -22
- package/_esm/tempo/MultisigConfig.js.map +1 -1
- package/_esm/tempo/MultisigOperation.js +20 -100
- package/_esm/tempo/MultisigOperation.js.map +1 -1
- package/_esm/tempo/MultisigSimulation.js +18 -46
- package/_esm/tempo/MultisigSimulation.js.map +1 -1
- package/_esm/tempo/SignatureEnvelope.js +107 -169
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/tempo/TransactionRequest.js +5 -1
- package/_esm/tempo/TransactionRequest.js.map +1 -1
- package/_esm/tempo/index.js +3 -1
- package/_esm/tempo/index.js.map +1 -1
- package/_esm/version.js +1 -1
- package/_types/tempo/KeyAuthorization.d.ts +48 -38
- package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
- package/_types/tempo/MultisigConfig.d.ts +15 -14
- package/_types/tempo/MultisigConfig.d.ts.map +1 -1
- package/_types/tempo/MultisigOperation.d.ts +3 -2
- package/_types/tempo/MultisigOperation.d.ts.map +1 -1
- package/_types/tempo/MultisigSimulation.d.ts +2 -49
- package/_types/tempo/MultisigSimulation.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +69 -128
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/tempo/TransactionRequest.d.ts +5 -2
- package/_types/tempo/TransactionRequest.d.ts.map +1 -1
- package/_types/tempo/index.d.ts +3 -1
- package/_types/tempo/index.d.ts.map +1 -1
- package/_types/version.d.ts +1 -1
- package/package.json +1 -1
- package/tempo/AuthorizationTempo.test.ts +8 -4
- package/tempo/KeyAuthorization.test-d.ts +68 -33
- package/tempo/KeyAuthorization.test.ts +89 -8
- package/tempo/KeyAuthorization.ts +124 -61
- package/tempo/MultisigConfig.test.ts +34 -26
- package/tempo/MultisigConfig.ts +54 -24
- package/tempo/MultisigOperation.test.ts +79 -550
- package/tempo/MultisigOperation.ts +31 -146
- package/tempo/MultisigSimulation.test-d.ts +2 -2
- package/tempo/MultisigSimulation.test.ts +21 -110
- package/tempo/MultisigSimulation.ts +25 -99
- package/tempo/SignatureEnvelope.test-d.ts +29 -67
- package/tempo/SignatureEnvelope.test.ts +301 -568
- package/tempo/SignatureEnvelope.ts +156 -274
- package/tempo/TransactionRequest.test-d.ts +6 -5
- package/tempo/TransactionRequest.test.ts +17 -80
- package/tempo/TransactionRequest.ts +23 -3
- package/tempo/index.ts +3 -1
- package/tempo/multisig.e2e.test.ts +322 -861
- package/version.ts +1 -1
|
@@ -92,15 +92,15 @@ export type GetType<
|
|
|
92
92
|
: envelope extends { type: infer T extends Type }
|
|
93
93
|
? T
|
|
94
94
|
: envelope extends {
|
|
95
|
-
signature: { r: bigint; s: bigint }
|
|
96
95
|
prehash: boolean
|
|
97
96
|
publicKey: PublicKey.PublicKey
|
|
97
|
+
signature: { r: bigint; s: bigint }
|
|
98
98
|
}
|
|
99
99
|
? 'p256'
|
|
100
100
|
: envelope extends {
|
|
101
|
-
signature: { r: bigint; s: bigint }
|
|
102
101
|
metadata: any
|
|
103
102
|
publicKey: PublicKey.PublicKey
|
|
103
|
+
signature: { r: bigint; s: bigint }
|
|
104
104
|
}
|
|
105
105
|
? 'webAuthn'
|
|
106
106
|
: envelope extends { r: bigint; s: bigint; yParity: number }
|
|
@@ -113,7 +113,11 @@ export type GetType<
|
|
|
113
113
|
userAddress: Address.Address
|
|
114
114
|
}
|
|
115
115
|
? 'keychain'
|
|
116
|
-
: envelope extends {
|
|
116
|
+
: envelope extends {
|
|
117
|
+
account: Address.Address
|
|
118
|
+
config: MultisigConfig.Input
|
|
119
|
+
signatures: any
|
|
120
|
+
}
|
|
117
121
|
? 'multisig'
|
|
118
122
|
: never
|
|
119
123
|
|
|
@@ -148,7 +152,7 @@ export type SignatureEnvelope<bigintType = bigint, numberType = number> =
|
|
|
148
152
|
| Keychain<bigintType, numberType>,
|
|
149
153
|
SignatureEnvelopeKey
|
|
150
154
|
>
|
|
151
|
-
|
|
|
155
|
+
| EnvelopeOneOf<Multisig<bigintType, numberType>, SignatureEnvelopeKey>
|
|
152
156
|
|
|
153
157
|
/**
|
|
154
158
|
* RPC-formatted signature envelope.
|
|
@@ -180,64 +184,43 @@ export type PrimitiveRpc = OneOf<Secp256k1Rpc | P256Rpc | WebAuthnRpc>
|
|
|
180
184
|
export type KeychainVersion = 'v1' | 'v2'
|
|
181
185
|
|
|
182
186
|
export type Keychain<bigintType = bigint, numberType = number> = {
|
|
183
|
-
/** Root account address that this transaction is being executed for */
|
|
184
|
-
userAddress: Address.Address
|
|
185
187
|
/** The actual signature from the access key (can be Secp256k1, P256, or WebAuthn) */
|
|
186
188
|
inner: SignatureEnvelope<bigintType, numberType>
|
|
187
189
|
/** The access key address (recovered address of the access key signer). */
|
|
188
190
|
keyId?: Address.Address | undefined
|
|
189
191
|
type: 'keychain'
|
|
192
|
+
/** Root account address that this transaction is being executed for */
|
|
193
|
+
userAddress: Address.Address
|
|
190
194
|
/** Keychain signature version. @default 'v1' */
|
|
191
195
|
version?: KeychainVersion | undefined
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
export type KeychainRpc = {
|
|
195
|
-
type: 'keychain'
|
|
196
|
-
userAddress: Address.Address
|
|
197
199
|
keyId?: Address.Address | undefined
|
|
198
200
|
signature: SignatureEnvelopeRpc
|
|
201
|
+
type: 'keychain'
|
|
202
|
+
userAddress: Address.Address
|
|
199
203
|
version?: KeychainVersion | undefined
|
|
200
204
|
}
|
|
201
205
|
|
|
202
206
|
/**
|
|
203
207
|
* Native multisig signature (type `0x05`).
|
|
204
208
|
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* configured threshold.
|
|
209
|
-
*
|
|
210
|
-
* [TIP-1061](https://tips.sh/1061)
|
|
209
|
+
* Carries the account's complete current configuration and primitive owner
|
|
210
|
+
* approvals over its version-bound digest. The node validates the account
|
|
211
|
+
* identity or stored commitment and the owner quorum against chain state.
|
|
211
212
|
*/
|
|
212
|
-
export
|
|
213
|
-
/**
|
|
213
|
+
export type Multisig<bigintType = bigint, numberType = number> = {
|
|
214
|
+
/** Permanent native multisig account address. */
|
|
214
215
|
account: Address.Address
|
|
215
|
-
/** Complete
|
|
216
|
+
/** Complete current configuration, included in every signature. */
|
|
216
217
|
config: MultisigConfig.Config<bigintType, numberType>
|
|
217
|
-
/**
|
|
218
|
-
|
|
219
|
-
* either a primitive signature or a nested multisig signature (keychain
|
|
220
|
-
* approvals are invalid).
|
|
221
|
-
*/
|
|
222
|
-
signatures: readonly SignatureEnvelope<bigintType, numberType>[]
|
|
218
|
+
/** Primitive owner approvals in ascending recovered-address order. */
|
|
219
|
+
signatures: readonly Primitive<bigintType, numberType>[]
|
|
223
220
|
type: 'multisig'
|
|
224
221
|
}
|
|
225
222
|
|
|
226
|
-
|
|
227
|
-
bigintType,
|
|
228
|
-
numberType
|
|
229
|
-
> & {
|
|
230
|
-
inner?: undefined
|
|
231
|
-
keyId?: undefined
|
|
232
|
-
metadata?: undefined
|
|
233
|
-
prehash?: undefined
|
|
234
|
-
publicKey?: undefined
|
|
235
|
-
signature?: undefined
|
|
236
|
-
userAddress?: undefined
|
|
237
|
-
version?: undefined
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/** Canonical RLP-encoded native multisig signature without the `0x05` type byte. */
|
|
223
|
+
/** RLP-encoded `[account, config, signatures]`, without the `0x05` type byte. */
|
|
241
224
|
export type MultisigRpc = Hex.Hex
|
|
242
225
|
|
|
243
226
|
export type P256<bigintType = bigint, numberType = number> = {
|
|
@@ -263,8 +246,8 @@ export type Secp256k1<bigintType = bigint, numberType = number> = {
|
|
|
263
246
|
|
|
264
247
|
export type Secp256k1Rpc = Compute<
|
|
265
248
|
Signature.Rpc<true> & {
|
|
266
|
-
v?: Hex.Hex | undefined
|
|
267
249
|
type: 'secp256k1'
|
|
250
|
+
v?: Hex.Hex | undefined
|
|
268
251
|
}
|
|
269
252
|
>
|
|
270
253
|
|
|
@@ -280,8 +263,8 @@ export type WebAuthn<bigintType = bigint, numberType = number> = {
|
|
|
280
263
|
WebAuthnP256.SignMetadata,
|
|
281
264
|
'authenticatorData' | 'clientDataJSON'
|
|
282
265
|
>
|
|
283
|
-
signature: Signature.Signature<false, bigintType, numberType>
|
|
284
266
|
publicKey: PublicKey.PublicKey
|
|
267
|
+
signature: Signature.Signature<false, bigintType, numberType>
|
|
285
268
|
type: 'webAuthn'
|
|
286
269
|
}
|
|
287
270
|
|
|
@@ -376,13 +359,14 @@ export function assert(envelope: PartialBy<SignatureEnvelope, 'type'>): void {
|
|
|
376
359
|
|
|
377
360
|
if (type === 'keychain') {
|
|
378
361
|
const keychain = envelope as Keychain
|
|
362
|
+
assertKeychainVersion(keychain)
|
|
379
363
|
assert(keychain.inner)
|
|
380
364
|
return
|
|
381
365
|
}
|
|
382
366
|
|
|
383
367
|
if (type === 'multisig') {
|
|
384
368
|
const multisig = envelope as Multisig
|
|
385
|
-
assertMultisig(multisig
|
|
369
|
+
assertMultisig(multisig)
|
|
386
370
|
return
|
|
387
371
|
}
|
|
388
372
|
}
|
|
@@ -398,7 +382,17 @@ export declare namespace assert {
|
|
|
398
382
|
| Errors.GlobalErrorType
|
|
399
383
|
}
|
|
400
384
|
|
|
401
|
-
function
|
|
385
|
+
function assertKeychainVersion(keychain: Keychain): void {
|
|
386
|
+
if (keychain.version !== 'v1') return
|
|
387
|
+
let inner = keychain.inner
|
|
388
|
+
while (getType(inner) === 'keychain') inner = (inner as Keychain).inner
|
|
389
|
+
if (getType(inner) === 'multisig')
|
|
390
|
+
throw new InvalidMultisigApprovalError({
|
|
391
|
+
reason: 'multisig access keys require keychain V2',
|
|
392
|
+
})
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function assertMultisig(envelope: Multisig): void {
|
|
402
396
|
const missing: string[] = []
|
|
403
397
|
if (!envelope.account) missing.push('account')
|
|
404
398
|
if (!envelope.config) missing.push('config')
|
|
@@ -409,10 +403,6 @@ function assertMultisig(envelope: Multisig, depth: number): void {
|
|
|
409
403
|
missing,
|
|
410
404
|
type: 'multisig',
|
|
411
405
|
})
|
|
412
|
-
if (depth > MultisigConfig.maxNestingDepth)
|
|
413
|
-
throw new InvalidMultisigApprovalError({
|
|
414
|
-
reason: `multisig nesting depth exceeds ${MultisigConfig.maxNestingDepth}`,
|
|
415
|
-
})
|
|
416
406
|
if (!Address.validate(envelope.account))
|
|
417
407
|
throw new InvalidMultisigApprovalError({
|
|
418
408
|
reason: 'multisig account is invalid',
|
|
@@ -421,25 +411,6 @@ function assertMultisig(envelope: Multisig, depth: number): void {
|
|
|
421
411
|
throw new InvalidMultisigApprovalError({
|
|
422
412
|
reason: 'multisig account cannot be zero',
|
|
423
413
|
})
|
|
424
|
-
MultisigConfig.assert(envelope.config)
|
|
425
|
-
if (
|
|
426
|
-
envelope.config.owners.some((owner) =>
|
|
427
|
-
Address.isEqual(owner.owner, envelope.account),
|
|
428
|
-
)
|
|
429
|
-
)
|
|
430
|
-
throw new InvalidMultisigApprovalError({
|
|
431
|
-
reason: 'multisig account cannot be an owner',
|
|
432
|
-
})
|
|
433
|
-
if (
|
|
434
|
-
envelope.config.version === 0n &&
|
|
435
|
-
!Address.isEqual(
|
|
436
|
-
MultisigConfig.getAddress(envelope.config),
|
|
437
|
-
envelope.account,
|
|
438
|
-
)
|
|
439
|
-
)
|
|
440
|
-
throw new InvalidMultisigApprovalError({
|
|
441
|
-
reason: 'initial multisig config does not derive account',
|
|
442
|
-
})
|
|
443
414
|
if (envelope.signatures.length === 0)
|
|
444
415
|
throw new InvalidMultisigApprovalError({
|
|
445
416
|
reason: 'multisig signatures cannot be empty',
|
|
@@ -449,20 +420,29 @@ function assertMultisig(envelope: Multisig, depth: number): void {
|
|
|
449
420
|
reason: `multisig signatures exceed ${MultisigConfig.maxSignatures}`,
|
|
450
421
|
})
|
|
451
422
|
|
|
423
|
+
MultisigConfig.assert(envelope.config)
|
|
424
|
+
if (
|
|
425
|
+
(envelope.config.version ?? 0n) === 0n &&
|
|
426
|
+
envelope.config.owners.some((owner) =>
|
|
427
|
+
Address.isEqual(owner.owner, envelope.account),
|
|
428
|
+
)
|
|
429
|
+
)
|
|
430
|
+
throw new InvalidMultisigApprovalError({
|
|
431
|
+
reason: 'initial account cannot be an owner',
|
|
432
|
+
})
|
|
433
|
+
|
|
452
434
|
for (const inner of envelope.signatures) {
|
|
453
|
-
const type = getType(inner)
|
|
454
|
-
if (type === 'keychain')
|
|
435
|
+
const type = getType(inner as SignatureEnvelope)
|
|
436
|
+
if (type === 'keychain' || type === 'multisig')
|
|
455
437
|
throw new InvalidMultisigApprovalError({
|
|
456
|
-
reason: '
|
|
438
|
+
reason: 'owner approvals must be primitive signatures',
|
|
439
|
+
})
|
|
440
|
+
assert(inner)
|
|
441
|
+
|
|
442
|
+
if (Hex.size(serialize(inner)) > MultisigConfig.maxOwnerSignatureBytes)
|
|
443
|
+
throw new InvalidMultisigApprovalError({
|
|
444
|
+
reason: `multisig owner signature exceeds ${MultisigConfig.maxOwnerSignatureBytes} bytes`,
|
|
457
445
|
})
|
|
458
|
-
if (type === 'multisig') assertMultisig(inner as Multisig, depth + 1)
|
|
459
|
-
else {
|
|
460
|
-
assert(inner)
|
|
461
|
-
if (Hex.size(serialize(inner)) > MultisigConfig.maxOwnerSignatureBytes)
|
|
462
|
-
throw new InvalidMultisigApprovalError({
|
|
463
|
-
reason: `multisig owner signature exceeds ${MultisigConfig.maxOwnerSignatureBytes} bytes`,
|
|
464
|
-
})
|
|
465
|
-
}
|
|
466
446
|
}
|
|
467
447
|
}
|
|
468
448
|
|
|
@@ -509,10 +489,10 @@ export declare namespace extractAddress {
|
|
|
509
489
|
type Options = {
|
|
510
490
|
/** The sign payload that was signed (only required for secp256k1 signatures). */
|
|
511
491
|
payload: Hex.Hex | Bytes.Bytes
|
|
512
|
-
/** The signature envelope. */
|
|
513
|
-
signature: SignatureEnvelope
|
|
514
492
|
/** Whether to return the root `userAddress` for keychain signatures instead of extracting from the inner signature. */
|
|
515
493
|
root?: boolean | undefined
|
|
494
|
+
/** The signature envelope. */
|
|
495
|
+
signature: SignatureEnvelope
|
|
516
496
|
}
|
|
517
497
|
|
|
518
498
|
type ReturnType = Address.Address
|
|
@@ -611,13 +591,10 @@ export declare namespace extractPublicKey {
|
|
|
611
591
|
* @throws `CoercionError` if the serialized value cannot be coerced to a valid signature envelope.
|
|
612
592
|
*/
|
|
613
593
|
export function deserialize(value: Serialized): SignatureEnvelope {
|
|
614
|
-
return deserialize_(value
|
|
594
|
+
return deserialize_(value)
|
|
615
595
|
}
|
|
616
596
|
|
|
617
|
-
function deserialize_(
|
|
618
|
-
value: Serialized,
|
|
619
|
-
multisigDepth: number,
|
|
620
|
-
): SignatureEnvelope {
|
|
597
|
+
function deserialize_(value: Serialized): SignatureEnvelope {
|
|
621
598
|
const serialized = value.endsWith(magicBytes.slice(2))
|
|
622
599
|
? Hex.slice(value, 0, -Hex.size(magicBytes))
|
|
623
600
|
: value
|
|
@@ -728,33 +705,27 @@ function deserialize_(
|
|
|
728
705
|
typeId === serializedKeychainV2Type
|
|
729
706
|
) {
|
|
730
707
|
const userAddress = Hex.slice(data, 0, 20)
|
|
731
|
-
const inner = deserialize_(Hex.slice(data, 20)
|
|
708
|
+
const inner = deserialize_(Hex.slice(data, 20))
|
|
732
709
|
|
|
733
|
-
|
|
710
|
+
const envelope = {
|
|
734
711
|
userAddress,
|
|
735
712
|
inner,
|
|
736
713
|
type: 'keychain',
|
|
737
714
|
version: typeId === serializedKeychainV2Type ? 'v2' : 'v1',
|
|
738
715
|
} satisfies Keychain
|
|
716
|
+
assert(envelope)
|
|
717
|
+
return envelope
|
|
739
718
|
}
|
|
740
719
|
|
|
741
720
|
if (typeId === serializedMultisigType) {
|
|
742
|
-
const depth = multisigDepth + 1
|
|
743
|
-
if (depth > MultisigConfig.maxNestingDepth)
|
|
744
|
-
throw new InvalidSerializedError({
|
|
745
|
-
reason: `multisig nesting depth exceeds ${MultisigConfig.maxNestingDepth}`,
|
|
746
|
-
serialized,
|
|
747
|
-
})
|
|
748
|
-
|
|
749
721
|
const decoded = Rlp.toHex(data)
|
|
750
722
|
if (!Array.isArray(decoded) || decoded.length !== 3)
|
|
751
723
|
throw new InvalidSerializedError({
|
|
752
724
|
reason: 'invalid multisig wire shape: expected exactly three fields',
|
|
753
725
|
serialized,
|
|
754
726
|
})
|
|
755
|
-
|
|
756
727
|
const [account, config, signatures] = decoded
|
|
757
|
-
if (!Address.validate(account))
|
|
728
|
+
if (typeof account !== 'string' || !Address.validate(account))
|
|
758
729
|
throw new InvalidSerializedError({
|
|
759
730
|
reason: 'invalid multisig account',
|
|
760
731
|
serialized,
|
|
@@ -764,68 +735,43 @@ function deserialize_(
|
|
|
764
735
|
reason: 'invalid multisig config',
|
|
765
736
|
serialized,
|
|
766
737
|
})
|
|
767
|
-
const [salt, version, threshold, owners] = config
|
|
768
738
|
if (
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
Array.isArray(version) ||
|
|
773
|
-
Hex.size(version) > 8 ||
|
|
774
|
-
(version !== '0x' && Hex.slice(version, 0, 1) === '0x00') ||
|
|
775
|
-
Array.isArray(threshold) ||
|
|
776
|
-
Hex.size(threshold) > 1 ||
|
|
777
|
-
!Array.isArray(owners) ||
|
|
778
|
-
owners.some(
|
|
779
|
-
(owner) =>
|
|
780
|
-
!Array.isArray(owner) ||
|
|
781
|
-
owner.length !== 2 ||
|
|
782
|
-
owner.some(Array.isArray) ||
|
|
783
|
-
!Address.validate(owner[0]) ||
|
|
784
|
-
Hex.size(owner[1] as Hex.Hex) > 1,
|
|
785
|
-
)
|
|
739
|
+
!Array.isArray(signatures) ||
|
|
740
|
+
signatures.length === 0 ||
|
|
741
|
+
signatures.length > MultisigConfig.maxSignatures
|
|
786
742
|
)
|
|
787
|
-
throw new InvalidSerializedError({
|
|
788
|
-
reason: 'invalid multisig config',
|
|
789
|
-
serialized,
|
|
790
|
-
})
|
|
791
|
-
if (!Array.isArray(signatures) || signatures.some(Array.isArray))
|
|
792
743
|
throw new InvalidSerializedError({
|
|
793
744
|
reason: 'invalid multisig signatures list',
|
|
794
745
|
serialized,
|
|
795
746
|
})
|
|
796
|
-
if (signatures.length === 0)
|
|
797
|
-
throw new InvalidSerializedError({
|
|
798
|
-
reason: 'multisig signatures cannot be empty',
|
|
799
|
-
serialized,
|
|
800
|
-
})
|
|
801
|
-
if (signatures.length > MultisigConfig.maxSignatures)
|
|
802
|
-
throw new InvalidSerializedError({
|
|
803
|
-
reason: `multisig signatures exceed ${MultisigConfig.maxSignatures}`,
|
|
804
|
-
serialized,
|
|
805
|
-
})
|
|
806
|
-
for (const signature of signatures)
|
|
807
|
-
if (
|
|
808
|
-
Hex.size(signature as Hex.Hex) >
|
|
809
|
-
MultisigConfig.maxOwnerSignatureBytes &&
|
|
810
|
-
Hex.slice(signature as Hex.Hex, 0, 1) !==
|
|
811
|
-
MultisigConfig.signatureTypeByte
|
|
812
|
-
)
|
|
813
|
-
throw new InvalidSerializedError({
|
|
814
|
-
reason: `multisig owner signature exceeds ${MultisigConfig.maxOwnerSignatureBytes} bytes`,
|
|
815
|
-
serialized,
|
|
816
|
-
})
|
|
817
|
-
|
|
818
747
|
const envelope = {
|
|
819
|
-
|
|
748
|
+
type: 'multisig',
|
|
749
|
+
account,
|
|
820
750
|
config: MultisigConfig.fromTuple(
|
|
821
751
|
config as unknown as MultisigConfig.Tuple,
|
|
822
752
|
),
|
|
823
|
-
signatures: signatures.map((signature) =>
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
753
|
+
signatures: signatures.map((signature) => {
|
|
754
|
+
if (
|
|
755
|
+
!Hex.validate(signature) ||
|
|
756
|
+
Hex.size(signature) > MultisigConfig.maxOwnerSignatureBytes
|
|
757
|
+
)
|
|
758
|
+
throw new InvalidSerializedError({
|
|
759
|
+
reason: 'invalid multisig owner signature',
|
|
760
|
+
serialized,
|
|
761
|
+
})
|
|
762
|
+
// Reject recursive envelopes before decoding their contents.
|
|
763
|
+
if (
|
|
764
|
+
Hex.size(signature) !== 65 &&
|
|
765
|
+
!['0x01', '0x02'].includes(Hex.slice(signature, 0, 1))
|
|
766
|
+
)
|
|
767
|
+
throw new InvalidSerializedError({
|
|
768
|
+
reason: 'owner approvals must be primitive signatures',
|
|
769
|
+
serialized,
|
|
770
|
+
})
|
|
771
|
+
return deserialize(signature) as Primitive
|
|
772
|
+
}),
|
|
827
773
|
} satisfies Multisig
|
|
828
|
-
assertMultisig(envelope
|
|
774
|
+
assertMultisig(envelope)
|
|
829
775
|
return envelope
|
|
830
776
|
}
|
|
831
777
|
|
|
@@ -950,25 +896,30 @@ function deserialize_(
|
|
|
950
896
|
* @example
|
|
951
897
|
* ### Multisig
|
|
952
898
|
*
|
|
953
|
-
*
|
|
954
|
-
* import { Secp256k1 } from 'ox'
|
|
955
|
-
* import { MultisigConfig, SignatureEnvelope } from 'ox/tempo'
|
|
899
|
+
* Include the permanent account and current configuration on every transaction.
|
|
956
900
|
*
|
|
957
|
-
*
|
|
958
|
-
*
|
|
959
|
-
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
960
|
-
* ],
|
|
961
|
-
* threshold: 1,
|
|
962
|
-
* })
|
|
963
|
-
*
|
|
964
|
-
* const privateKey = Secp256k1.randomPrivateKey()
|
|
965
|
-
* const signature = SignatureEnvelope.from(
|
|
966
|
-
* Secp256k1.sign({ payload: '0xdeadbeef', privateKey }),
|
|
967
|
-
* )
|
|
901
|
+
* ```ts twoslash
|
|
902
|
+
* import { SignatureEnvelope } from 'ox/tempo'
|
|
968
903
|
*
|
|
969
|
-
* const
|
|
970
|
-
*
|
|
971
|
-
*
|
|
904
|
+
* const envelope = SignatureEnvelope.from({
|
|
905
|
+
* account: '0x2222222222222222222222222222222222222222',
|
|
906
|
+
* config: {
|
|
907
|
+
* version: 0n,
|
|
908
|
+
* threshold: 1,
|
|
909
|
+
* owners: [
|
|
910
|
+
* {
|
|
911
|
+
* owner: '0x1111111111111111111111111111111111111111',
|
|
912
|
+
* weight: 1
|
|
913
|
+
* }
|
|
914
|
+
* ]
|
|
915
|
+
* },
|
|
916
|
+
* signatures: [
|
|
917
|
+
* SignatureEnvelope.from({
|
|
918
|
+
* r: 1n,
|
|
919
|
+
* s: 2n,
|
|
920
|
+
* yParity: 0
|
|
921
|
+
* })
|
|
922
|
+
* ]
|
|
972
923
|
* })
|
|
973
924
|
* ```
|
|
974
925
|
*
|
|
@@ -993,17 +944,13 @@ export function from<const value extends from.Value>(
|
|
|
993
944
|
const type = getType(value)
|
|
994
945
|
|
|
995
946
|
if (type === 'multisig') {
|
|
996
|
-
const multisig = value as
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
account,
|
|
1001
|
-
config,
|
|
947
|
+
const multisig = value as Multisig
|
|
948
|
+
return {
|
|
949
|
+
...multisig,
|
|
950
|
+
config: MultisigConfig.from(multisig.config),
|
|
1002
951
|
signatures: multisig.signatures.map((signature) => from(signature)),
|
|
1003
952
|
type,
|
|
1004
|
-
}
|
|
1005
|
-
assert(envelope)
|
|
1006
|
-
return envelope as never
|
|
953
|
+
} as never
|
|
1007
954
|
}
|
|
1008
955
|
|
|
1009
956
|
return {
|
|
@@ -1048,39 +995,24 @@ export declare namespace from {
|
|
|
1048
995
|
payload?: Hex.Hex | Bytes.Bytes | undefined
|
|
1049
996
|
}
|
|
1050
997
|
|
|
1051
|
-
/** Multisig
|
|
1052
|
-
type MultisigFromConfig =
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
/** Initial version-0 multisig configuration. */
|
|
1057
|
-
config: MultisigConfig.Input<0 | 0n>
|
|
1058
|
-
/** Primitive or nested owner approvals. */
|
|
1059
|
-
signatures: readonly SignatureEnvelope[]
|
|
1060
|
-
type?: 'multisig' | undefined
|
|
1061
|
-
}
|
|
1062
|
-
| {
|
|
1063
|
-
/** Native multisig account. */
|
|
1064
|
-
account: Address.Address
|
|
1065
|
-
/** Complete applicable multisig configuration. */
|
|
1066
|
-
config: MultisigConfig.Input
|
|
1067
|
-
/** Primitive or nested owner approvals. */
|
|
1068
|
-
signatures: readonly SignatureEnvelope[]
|
|
1069
|
-
type?: 'multisig' | undefined
|
|
1070
|
-
}
|
|
998
|
+
/** Multisig input with an explicit account and a normalizable configuration. */
|
|
999
|
+
type MultisigFromConfig = Omit<Multisig, 'config' | 'type'> & {
|
|
1000
|
+
config: MultisigConfig.Input
|
|
1001
|
+
type?: 'multisig' | undefined
|
|
1002
|
+
}
|
|
1071
1003
|
|
|
1072
1004
|
type Value =
|
|
1005
|
+
| MultisigFromConfig
|
|
1073
1006
|
| UnionPartialBy<SignatureEnvelope, 'prehash' | 'type'>
|
|
1074
1007
|
| Secp256k1Flat
|
|
1075
1008
|
| Serialized
|
|
1076
|
-
| MultisigFromConfig
|
|
1077
1009
|
|
|
1078
1010
|
type ReturnValue<value extends Value> = value extends Serialized
|
|
1079
1011
|
? SignatureEnvelope
|
|
1080
1012
|
: value extends Secp256k1Flat
|
|
1081
1013
|
? Secp256k1
|
|
1082
1014
|
: value extends MultisigFromConfig
|
|
1083
|
-
?
|
|
1015
|
+
? Extract<SignatureEnvelope, { type: 'multisig' }>
|
|
1084
1016
|
: IsNarrowable<value, SignatureEnvelope> extends true
|
|
1085
1017
|
? SignatureEnvelope
|
|
1086
1018
|
: Assign<
|
|
@@ -1113,19 +1045,10 @@ export declare namespace from {
|
|
|
1113
1045
|
*/
|
|
1114
1046
|
export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope {
|
|
1115
1047
|
if (typeof envelope === 'string') {
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
result.type !== 'multisig' ||
|
|
1120
|
-
!Hex.isEqual(serialize(result), serialized)
|
|
1121
|
-
)
|
|
1122
|
-
throw new InvalidSerializedError({
|
|
1123
|
-
reason: 'invalid multisig RPC signature',
|
|
1124
|
-
serialized: envelope,
|
|
1125
|
-
})
|
|
1126
|
-
return result
|
|
1048
|
+
const signature = deserialize(Hex.concat(serializedMultisigType, envelope))
|
|
1049
|
+
if (signature.type !== 'multisig') throw new CoercionError({ envelope })
|
|
1050
|
+
return signature
|
|
1127
1051
|
}
|
|
1128
|
-
|
|
1129
1052
|
if (envelope.type === 'secp256k1')
|
|
1130
1053
|
return {
|
|
1131
1054
|
signature: Signature.fromRpc(envelope),
|
|
@@ -1199,13 +1122,15 @@ export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope {
|
|
|
1199
1122
|
('userAddress' in envelope && 'signature' in envelope)
|
|
1200
1123
|
) {
|
|
1201
1124
|
const keychain = envelope as KeychainRpc
|
|
1202
|
-
|
|
1125
|
+
const result = {
|
|
1203
1126
|
type: 'keychain',
|
|
1204
1127
|
userAddress: keychain.userAddress,
|
|
1205
1128
|
inner: fromRpc(keychain.signature),
|
|
1206
1129
|
...(keychain.keyId ? { keyId: keychain.keyId } : {}),
|
|
1207
1130
|
...(keychain.version ? { version: keychain.version } : {}),
|
|
1208
|
-
}
|
|
1131
|
+
} satisfies Keychain
|
|
1132
|
+
assert(result)
|
|
1133
|
+
return result
|
|
1209
1134
|
}
|
|
1210
1135
|
|
|
1211
1136
|
throw new CoercionError({ envelope })
|
|
@@ -1215,10 +1140,8 @@ export declare namespace fromRpc {
|
|
|
1215
1140
|
type ErrorType =
|
|
1216
1141
|
| assert.ErrorType
|
|
1217
1142
|
| CoercionError
|
|
1218
|
-
| Hex.concat.ErrorType
|
|
1219
|
-
| Hex.isEqual.ErrorType
|
|
1220
1143
|
| InvalidSerializedError
|
|
1221
|
-
|
|
|
1144
|
+
| MultisigConfig.getAddress.ErrorType
|
|
1222
1145
|
| Signature.fromRpc.ErrorType
|
|
1223
1146
|
| Errors.GlobalErrorType
|
|
1224
1147
|
}
|
|
@@ -1289,7 +1212,7 @@ export function getType<
|
|
|
1289
1212
|
return 'keychain' as never
|
|
1290
1213
|
|
|
1291
1214
|
// Detect Multisig signature
|
|
1292
|
-
if ('config' in envelope && 'signatures' in envelope)
|
|
1215
|
+
if ('account' in envelope && 'config' in envelope && 'signatures' in envelope)
|
|
1293
1216
|
return 'multisig' as never
|
|
1294
1217
|
|
|
1295
1218
|
throw new CoercionError({
|
|
@@ -1374,6 +1297,7 @@ export function serialize(
|
|
|
1374
1297
|
|
|
1375
1298
|
if (type === 'keychain') {
|
|
1376
1299
|
const keychain = envelope as Keychain
|
|
1300
|
+
assert(keychain)
|
|
1377
1301
|
const keychainTypeId =
|
|
1378
1302
|
keychain.version === 'v1'
|
|
1379
1303
|
? serializedKeychainType
|
|
@@ -1424,66 +1348,31 @@ export declare namespace serialize {
|
|
|
1424
1348
|
}
|
|
1425
1349
|
|
|
1426
1350
|
/**
|
|
1427
|
-
*
|
|
1428
|
-
* recovered-owner order the Tempo node requires for the multisig `signatures`
|
|
1429
|
-
* array (the node enforces "recovered owners must be strictly ascending").
|
|
1351
|
+
* Sorts primitive owner approvals by recovered address.
|
|
1430
1352
|
*
|
|
1431
|
-
*
|
|
1432
|
-
*
|
|
1433
|
-
* every approval is recovered against that digest and the list is sorted by the
|
|
1434
|
-
* recovered owner address. Works for any owner key type (secp256k1, p256,
|
|
1435
|
-
* webAuthn).
|
|
1353
|
+
* Recovery uses the account and current config version's multisig digest.
|
|
1354
|
+
* Duplicate or non-owner approvals must still be rejected by the node.
|
|
1436
1355
|
*
|
|
1437
1356
|
* @example
|
|
1438
1357
|
* ```ts twoslash
|
|
1439
|
-
* import {
|
|
1440
|
-
* import { MultisigConfig, SignatureEnvelope, TxEnvelopeTempo } from 'ox/tempo'
|
|
1441
|
-
*
|
|
1442
|
-
* const config = MultisigConfig.from({
|
|
1443
|
-
* owners: [
|
|
1444
|
-
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
1445
|
-
* { owner: '0x2222222222222222222222222222222222222222', weight: 1 },
|
|
1446
|
-
* ],
|
|
1447
|
-
* threshold: 2,
|
|
1448
|
-
* })
|
|
1449
|
-
* const account = MultisigConfig.getAddress(config)
|
|
1450
|
-
*
|
|
1451
|
-
* const tx = TxEnvelopeTempo.from({ chainId: 1, calls: [] })
|
|
1452
|
-
* const payload = TxEnvelopeTempo.getSignPayload(tx)
|
|
1453
|
-
*
|
|
1454
|
-
* const privateKeys = [
|
|
1455
|
-
* Secp256k1.randomPrivateKey(),
|
|
1456
|
-
* Secp256k1.randomPrivateKey(),
|
|
1457
|
-
* ]
|
|
1458
|
-
* const digest = MultisigConfig.getSignPayload({
|
|
1459
|
-
* account,
|
|
1460
|
-
* config,
|
|
1461
|
-
* payload,
|
|
1462
|
-
* })
|
|
1463
|
-
* const signatures = privateKeys.map((privateKey) =>
|
|
1464
|
-
* SignatureEnvelope.from(Secp256k1.sign({ payload: digest, privateKey })),
|
|
1465
|
-
* )
|
|
1358
|
+
* import { SignatureEnvelope } from 'ox/tempo'
|
|
1466
1359
|
*
|
|
1467
|
-
* const ordered = SignatureEnvelope.sortMultisigApprovals({
|
|
1468
|
-
* account,
|
|
1469
|
-
* config
|
|
1470
|
-
* payload
|
|
1471
|
-
* signatures
|
|
1472
|
-
* })
|
|
1360
|
+
* const ordered = SignatureEnvelope.sortMultisigApprovals({
|
|
1361
|
+
* account: '0x2222222222222222222222222222222222222222',
|
|
1362
|
+
* config: { version: 0n },
|
|
1363
|
+
* payload: `0x${'ab'.repeat(32)}`,
|
|
1364
|
+
* signatures: []
|
|
1365
|
+
* })
|
|
1473
1366
|
* ```
|
|
1474
1367
|
*
|
|
1475
|
-
* @param value -
|
|
1476
|
-
* @returns The
|
|
1368
|
+
* @param value - Account, version, payload, and primitive approvals.
|
|
1369
|
+
* @returns The approvals in ascending recovered-address order.
|
|
1477
1370
|
*/
|
|
1478
1371
|
export function sortMultisigApprovals(
|
|
1479
1372
|
value: sortMultisigApprovals.Value,
|
|
1480
|
-
): readonly
|
|
1481
|
-
const {
|
|
1482
|
-
const digest = MultisigConfig.getSignPayload(
|
|
1483
|
-
account,
|
|
1484
|
-
config: MultisigConfig.from(config),
|
|
1485
|
-
payload,
|
|
1486
|
-
})
|
|
1373
|
+
): readonly Primitive[] {
|
|
1374
|
+
const { signatures } = value
|
|
1375
|
+
const digest = MultisigConfig.getSignPayload(value)
|
|
1487
1376
|
// Recover each signer once (decorate–sort–undecorate) rather than inside the
|
|
1488
1377
|
// comparator.
|
|
1489
1378
|
return signatures
|
|
@@ -1496,15 +1385,9 @@ export function sortMultisigApprovals(
|
|
|
1496
1385
|
}
|
|
1497
1386
|
|
|
1498
1387
|
export declare namespace sortMultisigApprovals {
|
|
1499
|
-
type Value = {
|
|
1500
|
-
/**
|
|
1501
|
-
|
|
1502
|
-
/** Complete applicable multisig configuration. */
|
|
1503
|
-
config: MultisigConfig.Input
|
|
1504
|
-
/** The inner transaction sign payload (`tx.signature_hash()`). */
|
|
1505
|
-
payload: Hex.Hex | Bytes.Bytes
|
|
1506
|
-
/** The owner approvals to order. */
|
|
1507
|
-
signatures: readonly SignatureEnvelope[]
|
|
1388
|
+
type Value = MultisigConfig.getSignPayload.Value & {
|
|
1389
|
+
/** Primitive owner approvals to order. */
|
|
1390
|
+
signatures: readonly Primitive[]
|
|
1508
1391
|
}
|
|
1509
1392
|
|
|
1510
1393
|
type ErrorType =
|
|
@@ -1573,6 +1456,7 @@ export function toRpc<const envelope extends toRpc.Input>(
|
|
|
1573
1456
|
|
|
1574
1457
|
if (type === 'keychain') {
|
|
1575
1458
|
const keychain = envelope as Keychain
|
|
1459
|
+
assertKeychainVersion(keychain)
|
|
1576
1460
|
return {
|
|
1577
1461
|
type: 'keychain',
|
|
1578
1462
|
userAddress: keychain.userAddress,
|
|
@@ -1582,10 +1466,8 @@ export function toRpc<const envelope extends toRpc.Input>(
|
|
|
1582
1466
|
} as never
|
|
1583
1467
|
}
|
|
1584
1468
|
|
|
1585
|
-
if (type === 'multisig')
|
|
1586
|
-
|
|
1587
|
-
return Hex.slice(serialize(multisig), 1) as never
|
|
1588
|
-
}
|
|
1469
|
+
if (type === 'multisig')
|
|
1470
|
+
return Hex.slice(serialize(envelope as Multisig), 1) as never
|
|
1589
1471
|
|
|
1590
1472
|
throw new CoercionError({ envelope })
|
|
1591
1473
|
}
|