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.
Files changed (66) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/_cjs/tempo/KeyAuthorization.js +43 -10
  3. package/_cjs/tempo/KeyAuthorization.js.map +1 -1
  4. package/_cjs/tempo/MultisigConfig.js +33 -11
  5. package/_cjs/tempo/MultisigConfig.js.map +1 -1
  6. package/_cjs/tempo/MultisigOperation.js +18 -94
  7. package/_cjs/tempo/MultisigOperation.js.map +1 -1
  8. package/_cjs/tempo/MultisigSimulation.js +18 -46
  9. package/_cjs/tempo/MultisigSimulation.js.map +1 -1
  10. package/_cjs/tempo/SignatureEnvelope.js +72 -109
  11. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  12. package/_cjs/tempo/TransactionRequest.js +5 -1
  13. package/_cjs/tempo/TransactionRequest.js.map +1 -1
  14. package/_cjs/tempo/index.js.map +1 -1
  15. package/_cjs/version.js +1 -1
  16. package/_esm/tempo/KeyAuthorization.js +42 -9
  17. package/_esm/tempo/KeyAuthorization.js.map +1 -1
  18. package/_esm/tempo/MultisigConfig.js +41 -22
  19. package/_esm/tempo/MultisigConfig.js.map +1 -1
  20. package/_esm/tempo/MultisigOperation.js +20 -100
  21. package/_esm/tempo/MultisigOperation.js.map +1 -1
  22. package/_esm/tempo/MultisigSimulation.js +18 -46
  23. package/_esm/tempo/MultisigSimulation.js.map +1 -1
  24. package/_esm/tempo/SignatureEnvelope.js +107 -169
  25. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  26. package/_esm/tempo/TransactionRequest.js +5 -1
  27. package/_esm/tempo/TransactionRequest.js.map +1 -1
  28. package/_esm/tempo/index.js +3 -1
  29. package/_esm/tempo/index.js.map +1 -1
  30. package/_esm/version.js +1 -1
  31. package/_types/tempo/KeyAuthorization.d.ts +48 -38
  32. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  33. package/_types/tempo/MultisigConfig.d.ts +15 -14
  34. package/_types/tempo/MultisigConfig.d.ts.map +1 -1
  35. package/_types/tempo/MultisigOperation.d.ts +3 -2
  36. package/_types/tempo/MultisigOperation.d.ts.map +1 -1
  37. package/_types/tempo/MultisigSimulation.d.ts +2 -49
  38. package/_types/tempo/MultisigSimulation.d.ts.map +1 -1
  39. package/_types/tempo/SignatureEnvelope.d.ts +69 -128
  40. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  41. package/_types/tempo/TransactionRequest.d.ts +5 -2
  42. package/_types/tempo/TransactionRequest.d.ts.map +1 -1
  43. package/_types/tempo/index.d.ts +3 -1
  44. package/_types/tempo/index.d.ts.map +1 -1
  45. package/_types/version.d.ts +1 -1
  46. package/package.json +1 -1
  47. package/tempo/AuthorizationTempo.test.ts +8 -4
  48. package/tempo/KeyAuthorization.test-d.ts +68 -33
  49. package/tempo/KeyAuthorization.test.ts +89 -8
  50. package/tempo/KeyAuthorization.ts +124 -61
  51. package/tempo/MultisigConfig.test.ts +34 -26
  52. package/tempo/MultisigConfig.ts +54 -24
  53. package/tempo/MultisigOperation.test.ts +79 -550
  54. package/tempo/MultisigOperation.ts +31 -146
  55. package/tempo/MultisigSimulation.test-d.ts +2 -2
  56. package/tempo/MultisigSimulation.test.ts +21 -110
  57. package/tempo/MultisigSimulation.ts +25 -99
  58. package/tempo/SignatureEnvelope.test-d.ts +29 -67
  59. package/tempo/SignatureEnvelope.test.ts +301 -568
  60. package/tempo/SignatureEnvelope.ts +156 -274
  61. package/tempo/TransactionRequest.test-d.ts +6 -5
  62. package/tempo/TransactionRequest.test.ts +17 -80
  63. package/tempo/TransactionRequest.ts +23 -3
  64. package/tempo/index.ts +3 -1
  65. package/tempo/multisig.e2e.test.ts +322 -861
  66. 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 { config: unknown; signatures: any }
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
- | MultisigOneOf<bigintType, numberType>
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
- * Wraps a set of owner approvals (secp256k1, p256, webAuthn, or nested
206
- * multisig) over the multisig owner approval digest. The transaction sender is
207
- * the derived `account`, authorized once the recovered owner weights meet the
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 interface Multisig<bigintType = bigint, numberType = number> {
213
- /** Native multisig account address. */
213
+ export type Multisig<bigintType = bigint, numberType = number> = {
214
+ /** Permanent native multisig account address. */
214
215
  account: Address.Address
215
- /** Complete applicable multisig configuration. */
216
+ /** Complete current configuration, included in every signature. */
216
217
  config: MultisigConfig.Config<bigintType, numberType>
217
- /**
218
- * Owner approvals over the multisig owner approval digest. Each approval is
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
- type MultisigOneOf<bigintType = bigint, numberType = number> = Multisig<
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, 1)
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 assertMultisig(envelope: Multisig, depth: number): void {
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: 'keychain owner approvals are not allowed',
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, 0)
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), multisigDepth)
708
+ const inner = deserialize_(Hex.slice(data, 20))
732
709
 
733
- return {
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
- config.length !== 4 ||
770
- Array.isArray(salt) ||
771
- Hex.size(salt) !== 32 ||
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
- account: account as Address.Address,
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
- deserialize_(signature as Hex.Hex, depth),
825
- ),
826
- type: 'multisig',
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, depth)
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
- * ```ts twoslash
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
- * const config = MultisigConfig.from({
958
- * owners: [
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 multisig = SignatureEnvelope.from({
970
- * config,
971
- * signatures: [signature],
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 from.MultisigFromConfig
997
- const config = MultisigConfig.from(multisig.config)
998
- const account = multisig.account ?? MultisigConfig.getAddress(config)
999
- const envelope = {
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
- } satisfies Multisig
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 signature input with an optional derived initial account. */
1052
- type MultisigFromConfig =
1053
- | {
1054
- /** Initial native multisig account, derived from config when omitted. */
1055
- account?: Address.Address | undefined
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
- ? MultisigOneOf
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 serialized = Hex.concat(serializedMultisigType, envelope)
1117
- const result = deserialize(serialized)
1118
- if (
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
- return {
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
- | serialize.ErrorType
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
- * Orders native multisig owner approvals into the strictly-ascending
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
- * Each approval is signed over the multisig owner approval digest
1432
- * ({@link ox#MultisigConfig.(getSignPayload:function)}), so the signer of
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 { Secp256k1 } from 'ox'
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({ // [!code focus]
1468
- * account, // [!code focus]
1469
- * config, // [!code focus]
1470
- * payload, // [!code focus]
1471
- * signatures, // [!code focus]
1472
- * }) // [!code focus]
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 - The approval ordering parameters.
1476
- * @returns The owner approvals ordered ascending by recovered owner address.
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 SignatureEnvelope[] {
1481
- const { account, config, payload, signatures } = value
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
- /** The native multisig account address. */
1501
- account: Address.Address
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
- const multisig = envelope as Multisig
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
  }