ox 1.7.4 → 1.7.5

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 (105) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/core/AbiEvent.d.ts.map +1 -1
  3. package/dist/core/AbiEvent.js +10 -9
  4. package/dist/core/AbiEvent.js.map +1 -1
  5. package/dist/core/Blobs.d.ts.map +1 -1
  6. package/dist/core/Blobs.js +6 -2
  7. package/dist/core/Blobs.js.map +1 -1
  8. package/dist/core/Signature.d.ts +6 -5
  9. package/dist/core/Signature.d.ts.map +1 -1
  10. package/dist/core/Signature.js +17 -7
  11. package/dist/core/Signature.js.map +1 -1
  12. package/dist/core/Siwe.d.ts.map +1 -1
  13. package/dist/core/Siwe.js +21 -1
  14. package/dist/core/Siwe.js.map +1 -1
  15. package/dist/tempo/KeyAuthorization.d.ts +42 -38
  16. package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
  17. package/dist/tempo/KeyAuthorization.js +24 -4
  18. package/dist/tempo/KeyAuthorization.js.map +1 -1
  19. package/dist/tempo/MultisigConfig.d.ts +18 -15
  20. package/dist/tempo/MultisigConfig.d.ts.map +1 -1
  21. package/dist/tempo/MultisigConfig.js +49 -20
  22. package/dist/tempo/MultisigConfig.js.map +1 -1
  23. package/dist/tempo/MultisigOperation.d.ts +3 -2
  24. package/dist/tempo/MultisigOperation.d.ts.map +1 -1
  25. package/dist/tempo/MultisigOperation.js +20 -100
  26. package/dist/tempo/MultisigOperation.js.map +1 -1
  27. package/dist/tempo/MultisigSimulation.d.ts +2 -49
  28. package/dist/tempo/MultisigSimulation.d.ts.map +1 -1
  29. package/dist/tempo/MultisigSimulation.js +18 -46
  30. package/dist/tempo/MultisigSimulation.js.map +1 -1
  31. package/dist/tempo/SignatureEnvelope.d.ts +72 -140
  32. package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
  33. package/dist/tempo/SignatureEnvelope.js +130 -181
  34. package/dist/tempo/SignatureEnvelope.js.map +1 -1
  35. package/dist/tempo/TransactionRequest.d.ts +4 -2
  36. package/dist/tempo/TransactionRequest.d.ts.map +1 -1
  37. package/dist/tempo/TransactionRequest.js +6 -1
  38. package/dist/tempo/TransactionRequest.js.map +1 -1
  39. package/dist/tempo/index.d.ts +3 -1
  40. package/dist/tempo/index.d.ts.map +1 -1
  41. package/dist/tempo/index.js +3 -1
  42. package/dist/tempo/index.js.map +1 -1
  43. package/dist/zod/tempo/AuthorizationTempo.d.ts +350 -10
  44. package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
  45. package/dist/zod/tempo/KeyAuthorization.d.ts +242 -422
  46. package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
  47. package/dist/zod/tempo/KeyAuthorization.js +29 -31
  48. package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
  49. package/dist/zod/tempo/MultisigSimulation.d.ts +8 -76
  50. package/dist/zod/tempo/MultisigSimulation.d.ts.map +1 -1
  51. package/dist/zod/tempo/MultisigSimulation.js +3 -37
  52. package/dist/zod/tempo/MultisigSimulation.js.map +1 -1
  53. package/dist/zod/tempo/RpcSchemaTempo.d.ts +284 -228
  54. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  55. package/dist/zod/tempo/SignatureEnvelope.d.ts +105 -3
  56. package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
  57. package/dist/zod/tempo/SignatureEnvelope.js +21 -9
  58. package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
  59. package/dist/zod/tempo/Transaction.d.ts +676 -448
  60. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  61. package/dist/zod/tempo/TransactionRequest.d.ts +590 -473
  62. package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
  63. package/dist/zod/tempo/TransactionRequest.js +8 -0
  64. package/dist/zod/tempo/TransactionRequest.js.map +1 -1
  65. package/dist/zod/tempo/TxEnvelopeTempo.d.ts +327 -213
  66. package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  67. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +105 -3
  68. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts.map +1 -1
  69. package/package.json +1 -1
  70. package/src/core/AbiEvent.ts +11 -9
  71. package/src/core/Blobs.ts +6 -2
  72. package/src/core/Signature.ts +41 -11
  73. package/src/core/Siwe.ts +20 -1
  74. package/src/core/_test/AbiEvent.test.ts +45 -0
  75. package/src/core/_test/Blobs.test.ts +22 -0
  76. package/src/core/_test/Signature.test.ts +166 -0
  77. package/src/core/_test/Siwe.test.ts +100 -1
  78. package/src/tempo/AuthorizationTempo.test.ts +8 -4
  79. package/src/tempo/KeyAuthorization.test-d.ts +16 -63
  80. package/src/tempo/KeyAuthorization.test.ts +69 -11
  81. package/src/tempo/KeyAuthorization.ts +107 -63
  82. package/src/tempo/MultisigConfig.test.ts +44 -20
  83. package/src/tempo/MultisigConfig.ts +65 -23
  84. package/src/tempo/MultisigOperation.test.ts +79 -550
  85. package/src/tempo/MultisigOperation.ts +31 -146
  86. package/src/tempo/MultisigSimulation.test-d.ts +2 -2
  87. package/src/tempo/MultisigSimulation.test.ts +21 -110
  88. package/src/tempo/MultisigSimulation.ts +25 -99
  89. package/src/tempo/SignatureEnvelope.test-d.ts +29 -47
  90. package/src/tempo/SignatureEnvelope.test.ts +328 -541
  91. package/src/tempo/SignatureEnvelope.ts +196 -291
  92. package/src/tempo/Transaction.test.ts +4 -4
  93. package/src/tempo/TransactionRequest.test.ts +17 -80
  94. package/src/tempo/TransactionRequest.ts +19 -2
  95. package/src/tempo/index.ts +3 -1
  96. package/src/tempo/multisig.e2e.test.ts +321 -860
  97. package/src/version.ts +1 -1
  98. package/src/zod/tempo/KeyAuthorization.ts +50 -39
  99. package/src/zod/tempo/MultisigSimulation.ts +3 -48
  100. package/src/zod/tempo/SignatureEnvelope.ts +46 -35
  101. package/src/zod/tempo/TransactionRequest.ts +14 -0
  102. package/src/zod/tempo/_test/KeyAuthorization.test.ts +69 -20
  103. package/src/zod/tempo/_test/MultisigSimulation.test.ts +6 -31
  104. package/src/zod/tempo/_test/SignatureEnvelope.test.ts +118 -85
  105. package/src/zod/tempo/_test/TransactionRequest.test.ts +9 -19
@@ -8,7 +8,6 @@ import type {
8
8
  IsNarrowable,
9
9
  OneOf,
10
10
  PartialBy,
11
- Undefined,
12
11
  UnionPartialBy,
13
12
  } from '../core/internal/types.js'
14
13
  import * as Json from '../core/Json.js'
@@ -28,15 +27,6 @@ const serializedKeychainType = '0x03'
28
27
  const serializedKeychainV2Type = '0x04'
29
28
  const serializedMultisigType = '0x05'
30
29
 
31
- type NonMultisig<numberType = number> = OneOf<
32
- | Secp256k1<numberType>
33
- | P256<numberType>
34
- | WebAuthn<numberType>
35
- | Keychain<numberType>
36
- >
37
-
38
- type NonMultisigRpc = OneOf<Secp256k1Rpc | P256Rpc | WebAuthnRpc | KeychainRpc>
39
-
40
30
  /** Serialized magic identifier for Tempo signature envelopes. */
41
31
  export const magicBytes =
42
32
  '0x7777777777777777777777777777777777777777777777777777777777777777' // 32 "T"s
@@ -65,15 +55,15 @@ export type GetType<
65
55
  : envelope extends { type: infer T extends Type }
66
56
  ? T
67
57
  : envelope extends {
68
- signature: { r: `0x${string}`; s: `0x${string}` }
69
58
  prehash: boolean
70
59
  publicKey: PublicKey.PublicKey
60
+ signature: { r: `0x${string}`; s: `0x${string}` }
71
61
  }
72
62
  ? 'p256'
73
63
  : envelope extends {
74
- signature: { r: `0x${string}`; s: `0x${string}` }
75
64
  metadata: any
76
65
  publicKey: PublicKey.PublicKey
66
+ signature: { r: `0x${string}`; s: `0x${string}` }
77
67
  }
78
68
  ? 'webAuthn'
79
69
  : envelope extends {
@@ -94,7 +84,11 @@ export type GetType<
94
84
  userAddress: Address.Address
95
85
  }
96
86
  ? 'keychain'
97
- : envelope extends { config: unknown; signatures: any }
87
+ : envelope extends {
88
+ account: Address.Address
89
+ config: MultisigConfig.Input
90
+ signatures: any
91
+ }
98
92
  ? 'multisig'
99
93
  : never
100
94
 
@@ -121,12 +115,20 @@ export type GetType<
121
115
  *
122
116
  * [Signature Types Specification](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types)
123
117
  */
124
- export type SignatureEnvelope<numberType = number> =
125
- | (NonMultisig<numberType> & Undefined<Omit<Multisig<numberType>, 'type'>>)
126
- | MultisigOneOf<numberType>
118
+ export type SignatureEnvelope<numberType = number> = OneOf<
119
+ | Secp256k1<numberType>
120
+ | P256<numberType>
121
+ | WebAuthn<numberType>
122
+ | Keychain<numberType>
123
+ | Multisig<numberType>
124
+ >
127
125
 
128
- /** RPC-formatted signature envelope. */
129
- export type SignatureEnvelopeRpc = NonMultisigRpc | MultisigRpc
126
+ /**
127
+ * RPC-formatted signature envelope.
128
+ */
129
+ export type SignatureEnvelopeRpc =
130
+ | OneOf<Secp256k1Rpc | P256Rpc | WebAuthnRpc | KeychainRpc>
131
+ | MultisigRpc
130
132
 
131
133
  /** Primitive signature envelope accepted by protocol sidecars. */
132
134
  export type Primitive<numberType = number> = OneOf<
@@ -146,55 +148,43 @@ export type PrimitiveRpc = OneOf<Secp256k1Rpc | P256Rpc | WebAuthnRpc>
146
148
  export type KeychainVersion = 'v1' | 'v2'
147
149
 
148
150
  export type Keychain<numberType = number> = {
149
- /** Root account address that this transaction is being executed for */
150
- userAddress: Address.Address
151
151
  /** The actual signature from the access key (can be Secp256k1, P256, or WebAuthn) */
152
152
  inner: SignatureEnvelope<numberType>
153
153
  /** The access key address (recovered address of the access key signer). */
154
154
  keyId?: Address.Address | undefined
155
155
  type: 'keychain'
156
+ /** Root account address that this transaction is being executed for */
157
+ userAddress: Address.Address
156
158
  /** Keychain signature version. @default 'v1' */
157
159
  version?: KeychainVersion | undefined
158
160
  }
159
161
 
160
162
  export type KeychainRpc = {
161
- type: 'keychain'
162
- userAddress: Address.Address
163
163
  keyId?: Address.Address | undefined
164
164
  signature: SignatureEnvelopeRpc
165
+ type: 'keychain'
166
+ userAddress: Address.Address
165
167
  version?: KeychainVersion | undefined
166
168
  }
167
169
 
168
170
  /**
169
171
  * Native multisig signature (type `0x05`).
170
172
  *
171
- * Wraps a set of owner approvals (secp256k1, p256, webAuthn, or nested
172
- * multisig) over the multisig owner approval digest. The transaction sender is
173
- * the derived `account`, authorized once the recovered owner weights meet the
174
- * configured threshold.
175
- *
176
- * [TIP-1061](https://tips.sh/1061)
173
+ * Carries the account's complete current configuration and primitive owner
174
+ * approvals over its version-bound digest. The node validates the account
175
+ * identity or stored commitment and the owner quorum against chain state.
177
176
  */
178
177
  export type Multisig<numberType = number> = {
179
- /** Native multisig account address. */
178
+ /** Permanent native multisig account address. */
180
179
  account: Address.Address
181
- /** Complete applicable multisig configuration. */
180
+ /** Complete current configuration, included in every signature. */
182
181
  config: MultisigConfig.Config<bigint, numberType>
183
- /**
184
- * Owner approvals over the multisig owner approval digest. Each approval is
185
- * either a primitive signature or a nested multisig signature (keychain
186
- * approvals are invalid).
187
- */
188
- signatures: readonly SignatureEnvelope<numberType>[]
182
+ /** Primitive owner approvals in ascending recovered-address order. */
183
+ signatures: readonly Primitive<numberType>[]
189
184
  type: 'multisig'
190
185
  }
191
186
 
192
- interface MultisigOneOf<numberType = number>
193
- extends
194
- Multisig<numberType>,
195
- Undefined<Omit<NonMultisig<numberType>, 'type'>> {}
196
-
197
- /** RPC-formatted native multisig signature without the `0x05` type byte. */
187
+ /** RLP-encoded `[account, config, signatures]`, without the `0x05` type byte. */
198
188
  export type MultisigRpc = Hex.Hex
199
189
 
200
190
  export type P256<numberType = number> = {
@@ -220,8 +210,8 @@ export type Secp256k1<numberType = number> = {
220
210
 
221
211
  export type Secp256k1Rpc = Compute<
222
212
  Signature.Rpc<true> & {
223
- v?: Hex.Hex | undefined
224
213
  type: 'secp256k1'
214
+ v?: Hex.Hex | undefined
225
215
  }
226
216
  >
227
217
 
@@ -237,8 +227,8 @@ export type WebAuthn<numberType = number> = {
237
227
  WebAuthnP256.SignMetadata,
238
228
  'authenticatorData' | 'clientDataJSON'
239
229
  >
240
- signature: Signature.Signature<false, numberType>
241
230
  publicKey: PublicKey.PublicKey
231
+ signature: Signature.Signature<false, numberType>
242
232
  type: 'webAuthn'
243
233
  }
244
234
 
@@ -333,13 +323,14 @@ export function assert(envelope: PartialBy<SignatureEnvelope, 'type'>): void {
333
323
 
334
324
  if (type === 'keychain') {
335
325
  const keychain = envelope as Keychain
326
+ assertKeychainVersion(keychain)
336
327
  assert(keychain.inner)
337
328
  return
338
329
  }
339
330
 
340
331
  if (type === 'multisig') {
341
332
  const multisig = envelope as Multisig
342
- assertMultisig(multisig, 1)
333
+ assertMultisig(multisig)
343
334
  return
344
335
  }
345
336
  }
@@ -355,7 +346,17 @@ export declare namespace assert {
355
346
  | Errors.GlobalErrorType
356
347
  }
357
348
 
358
- function assertMultisig(envelope: Multisig, depth: number): void {
349
+ function assertKeychainVersion(keychain: Keychain): void {
350
+ if (keychain.version !== 'v1') return
351
+ let inner = keychain.inner
352
+ while (getType(inner) === 'keychain') inner = (inner as Keychain).inner
353
+ if (getType(inner) === 'multisig')
354
+ throw new InvalidMultisigApprovalError({
355
+ reason: 'multisig access keys require keychain V2',
356
+ })
357
+ }
358
+
359
+ function assertMultisig(envelope: Multisig): void {
359
360
  const missing: string[] = []
360
361
  if (!envelope.account) missing.push('account')
361
362
  if (!envelope.config) missing.push('config')
@@ -366,10 +367,6 @@ function assertMultisig(envelope: Multisig, depth: number): void {
366
367
  missing,
367
368
  type: 'multisig',
368
369
  })
369
- if (depth > MultisigConfig.maxNestingDepth)
370
- throw new InvalidMultisigApprovalError({
371
- reason: `multisig nesting depth exceeds ${MultisigConfig.maxNestingDepth}`,
372
- })
373
370
  if (!Address.validate(envelope.account))
374
371
  throw new InvalidMultisigApprovalError({
375
372
  reason: 'multisig account is invalid',
@@ -378,25 +375,6 @@ function assertMultisig(envelope: Multisig, depth: number): void {
378
375
  throw new InvalidMultisigApprovalError({
379
376
  reason: 'multisig account cannot be zero',
380
377
  })
381
- MultisigConfig.assert(envelope.config)
382
- if (
383
- envelope.config.owners.some((owner) =>
384
- Address.isEqual(owner.owner, envelope.account),
385
- )
386
- )
387
- throw new InvalidMultisigApprovalError({
388
- reason: 'multisig account cannot be an owner',
389
- })
390
- if (
391
- envelope.config.version === 0n &&
392
- !Address.isEqual(
393
- MultisigConfig.getAddress(envelope.config),
394
- envelope.account,
395
- )
396
- )
397
- throw new InvalidMultisigApprovalError({
398
- reason: 'initial multisig config does not derive account',
399
- })
400
378
  if (envelope.signatures.length === 0)
401
379
  throw new InvalidMultisigApprovalError({
402
380
  reason: 'multisig signatures cannot be empty',
@@ -406,20 +384,29 @@ function assertMultisig(envelope: Multisig, depth: number): void {
406
384
  reason: `multisig signatures exceed ${MultisigConfig.maxSignatures}`,
407
385
  })
408
386
 
387
+ MultisigConfig.assert(envelope.config)
388
+ if (
389
+ (envelope.config.version ?? 0n) === 0n &&
390
+ envelope.config.owners.some((owner) =>
391
+ Address.isEqual(owner.owner, envelope.account),
392
+ )
393
+ )
394
+ throw new InvalidMultisigApprovalError({
395
+ reason: 'initial account cannot be an owner',
396
+ })
397
+
409
398
  for (const inner of envelope.signatures) {
410
- const type = getType(inner)
411
- if (type === 'keychain')
399
+ const type = getType(inner as SignatureEnvelope)
400
+ if (type === 'keychain' || type === 'multisig')
412
401
  throw new InvalidMultisigApprovalError({
413
- reason: 'keychain owner approvals are not allowed',
402
+ reason: 'owner approvals must be primitive signatures',
403
+ })
404
+ assert(inner)
405
+
406
+ if (Hex.size(serialize(inner)) > MultisigConfig.maxOwnerSignatureBytes)
407
+ throw new InvalidMultisigApprovalError({
408
+ reason: `multisig owner signature exceeds ${MultisigConfig.maxOwnerSignatureBytes} bytes`,
414
409
  })
415
- if (type === 'multisig') assertMultisig(inner as Multisig, depth + 1)
416
- else {
417
- assert(inner)
418
- if (Hex.size(serialize(inner)) > MultisigConfig.maxOwnerSignatureBytes)
419
- throw new InvalidMultisigApprovalError({
420
- reason: `multisig owner signature exceeds ${MultisigConfig.maxOwnerSignatureBytes} bytes`,
421
- })
422
- }
423
410
  }
424
411
  }
425
412
 
@@ -470,10 +457,10 @@ export declare namespace extractAddress {
470
457
  type Options = {
471
458
  /** The sign payload that was signed (only required for secp256k1 signatures). */
472
459
  payload: Hex.Hex | Bytes.Bytes
473
- /** The signature envelope. */
474
- signature: SignatureEnvelope
475
460
  /** Whether to return the root `userAddress` for keychain signatures instead of extracting from the inner signature. */
476
461
  root?: boolean | undefined
462
+ /** The signature envelope. */
463
+ signature: SignatureEnvelope
477
464
  }
478
465
 
479
466
  type ReturnType = Address.Address
@@ -576,13 +563,10 @@ export declare namespace extractPublicKey {
576
563
  * @throws `CoercionError` if the serialized value cannot be coerced to a valid signature envelope.
577
564
  */
578
565
  export function deserialize(value: Serialized): SignatureEnvelope {
579
- return deserialize_(value, 0)
566
+ return deserialize_(value)
580
567
  }
581
568
 
582
- function deserialize_(
583
- value: Serialized,
584
- multisigDepth: number,
585
- ): SignatureEnvelope {
569
+ function deserialize_(value: Serialized): SignatureEnvelope {
586
570
  const serialized = value.endsWith(magicBytes.slice(2))
587
571
  ? Hex.slice(value, 0, -Hex.size(magicBytes))
588
572
  : value
@@ -685,33 +669,27 @@ function deserialize_(
685
669
  typeId === serializedKeychainV2Type
686
670
  ) {
687
671
  const userAddress = Hex.slice(data, 0, 20)
688
- const inner = deserialize_(Hex.slice(data, 20), multisigDepth)
672
+ const inner = deserialize_(Hex.slice(data, 20))
689
673
 
690
- return {
674
+ const envelope = {
691
675
  userAddress,
692
676
  inner,
693
677
  type: 'keychain',
694
678
  version: typeId === serializedKeychainV2Type ? 'v2' : 'v1',
695
679
  } satisfies Keychain
680
+ assert(envelope)
681
+ return envelope
696
682
  }
697
683
 
698
684
  if (typeId === serializedMultisigType) {
699
- const depth = multisigDepth + 1
700
- if (depth > MultisigConfig.maxNestingDepth)
701
- throw new InvalidSerializedError({
702
- reason: `multisig nesting depth exceeds ${MultisigConfig.maxNestingDepth}`,
703
- serialized,
704
- })
705
-
706
685
  const decoded = Rlp.toHex(data)
707
686
  if (!Array.isArray(decoded) || decoded.length !== 3)
708
687
  throw new InvalidSerializedError({
709
688
  reason: 'invalid multisig wire shape: expected exactly three fields',
710
689
  serialized,
711
690
  })
712
-
713
691
  const [account, config, signatures] = decoded
714
- if (!Address.validate(account))
692
+ if (typeof account !== 'string' || !Address.validate(account))
715
693
  throw new InvalidSerializedError({
716
694
  reason: 'invalid multisig account',
717
695
  serialized,
@@ -721,68 +699,43 @@ function deserialize_(
721
699
  reason: 'invalid multisig config',
722
700
  serialized,
723
701
  })
724
- const [salt, version, threshold, owners] = config
725
702
  if (
726
- config.length !== 4 ||
727
- Array.isArray(salt) ||
728
- Hex.size(salt) !== 32 ||
729
- Array.isArray(version) ||
730
- Hex.size(version) > 8 ||
731
- (version !== '0x' && Hex.slice(version, 0, 1) === '0x00') ||
732
- Array.isArray(threshold) ||
733
- Hex.size(threshold) > 1 ||
734
- !Array.isArray(owners) ||
735
- owners.some(
736
- (owner) =>
737
- !Array.isArray(owner) ||
738
- owner.length !== 2 ||
739
- owner.some(Array.isArray) ||
740
- !Address.validate(owner[0]) ||
741
- Hex.size(owner[1] as Hex.Hex) > 1,
742
- )
703
+ !Array.isArray(signatures) ||
704
+ signatures.length === 0 ||
705
+ signatures.length > MultisigConfig.maxSignatures
743
706
  )
744
- throw new InvalidSerializedError({
745
- reason: 'invalid multisig config',
746
- serialized,
747
- })
748
- if (!Array.isArray(signatures) || signatures.some(Array.isArray))
749
707
  throw new InvalidSerializedError({
750
708
  reason: 'invalid multisig signatures list',
751
709
  serialized,
752
710
  })
753
- if (signatures.length === 0)
754
- throw new InvalidSerializedError({
755
- reason: 'multisig signatures cannot be empty',
756
- serialized,
757
- })
758
- if (signatures.length > MultisigConfig.maxSignatures)
759
- throw new InvalidSerializedError({
760
- reason: `multisig signatures exceed ${MultisigConfig.maxSignatures}`,
761
- serialized,
762
- })
763
- for (const signature of signatures)
764
- if (
765
- Hex.size(signature as Hex.Hex) >
766
- MultisigConfig.maxOwnerSignatureBytes &&
767
- Hex.slice(signature as Hex.Hex, 0, 1) !==
768
- MultisigConfig.signatureTypeByte
769
- )
770
- throw new InvalidSerializedError({
771
- reason: `multisig owner signature exceeds ${MultisigConfig.maxOwnerSignatureBytes} bytes`,
772
- serialized,
773
- })
774
-
775
711
  const envelope = {
776
- account: account as Address.Address,
712
+ type: 'multisig',
713
+ account,
777
714
  config: MultisigConfig.fromTuple(
778
715
  config as unknown as MultisigConfig.Tuple,
779
716
  ),
780
- signatures: signatures.map((signature) =>
781
- deserialize_(signature as Hex.Hex, depth),
782
- ),
783
- type: 'multisig',
717
+ signatures: signatures.map((signature) => {
718
+ if (
719
+ !Hex.validate(signature) ||
720
+ Hex.size(signature) > MultisigConfig.maxOwnerSignatureBytes
721
+ )
722
+ throw new InvalidSerializedError({
723
+ reason: 'invalid multisig owner signature',
724
+ serialized,
725
+ })
726
+ // Reject recursive envelopes before decoding their contents.
727
+ if (
728
+ Hex.size(signature) !== 65 &&
729
+ !['0x01', '0x02'].includes(Hex.slice(signature, 0, 1))
730
+ )
731
+ throw new InvalidSerializedError({
732
+ reason: 'owner approvals must be primitive signatures',
733
+ serialized,
734
+ })
735
+ return deserialize(signature) as Primitive
736
+ }),
784
737
  } satisfies Multisig
785
- assertMultisig(envelope, depth)
738
+ assertMultisig(envelope)
786
739
  return envelope
787
740
  }
788
741
 
@@ -920,28 +873,30 @@ function deserialize_(
920
873
  * @example
921
874
  * ### Multisig
922
875
  *
923
- * ```ts twoslash
924
- * import { Secp256k1 } from 'ox'
925
- * import { MultisigConfig, SignatureEnvelope } from 'ox/tempo'
926
- *
927
- * const config = MultisigConfig.from({
928
- * owners: [
929
- * {
930
- * owner: '0x1111111111111111111111111111111111111111',
931
- * weight: 1
932
- * }
933
- * ],
934
- * threshold: 1
935
- * })
876
+ * Include the permanent account and current configuration on every transaction.
936
877
  *
937
- * const privateKey = Secp256k1.randomPrivateKey()
938
- * const signature = SignatureEnvelope.from(
939
- * Secp256k1.sign({ payload: '0xdeadbeef', privateKey })
940
- * )
878
+ * ```ts twoslash
879
+ * import { SignatureEnvelope } from 'ox/tempo'
941
880
  *
942
- * const multisig = SignatureEnvelope.from({
943
- * config,
944
- * signatures: [signature]
881
+ * const envelope = SignatureEnvelope.from({
882
+ * account: '0x2222222222222222222222222222222222222222',
883
+ * config: {
884
+ * version: 0n,
885
+ * threshold: 1,
886
+ * owners: [
887
+ * {
888
+ * owner: '0x1111111111111111111111111111111111111111',
889
+ * weight: 1
890
+ * }
891
+ * ]
892
+ * },
893
+ * signatures: [
894
+ * SignatureEnvelope.from({
895
+ * r: '0x01',
896
+ * s: '0x02',
897
+ * yParity: 0
898
+ * })
899
+ * ]
945
900
  * })
946
901
  * ```
947
902
  *
@@ -966,17 +921,13 @@ export function from<const value extends from.Value>(
966
921
  const type = getType(value)
967
922
 
968
923
  if (type === 'multisig') {
969
- const multisig = value as from.MultisigFromConfig
970
- const config = MultisigConfig.from(multisig.config)
971
- const account = multisig.account ?? MultisigConfig.getAddress(config)
972
- const envelope = {
973
- account,
974
- config,
924
+ const multisig = value as Multisig
925
+ return {
926
+ ...multisig,
927
+ config: MultisigConfig.from(multisig.config),
975
928
  signatures: multisig.signatures.map((signature) => from(signature)),
976
929
  type,
977
- } satisfies Multisig
978
- assert(envelope)
979
- return envelope as never
930
+ } as never
980
931
  }
981
932
 
982
933
  return {
@@ -1021,32 +972,17 @@ export declare namespace from {
1021
972
  payload?: Hex.Hex | Bytes.Bytes | undefined
1022
973
  }
1023
974
 
1024
- /** Multisig signature input with an optional derived initial account. */
1025
- type MultisigFromConfig =
1026
- | {
1027
- /** Initial native multisig account, derived from config when omitted. */
1028
- account?: Address.Address | undefined
1029
- /** Initial version-0 multisig configuration. */
1030
- config: MultisigConfig.Input<0 | 0n>
1031
- /** Primitive or nested owner approvals. */
1032
- signatures: readonly SignatureEnvelope[]
1033
- type?: 'multisig' | undefined
1034
- }
1035
- | {
1036
- /** Native multisig account. */
1037
- account: Address.Address
1038
- /** Complete applicable multisig configuration. */
1039
- config: MultisigConfig.Input
1040
- /** Primitive or nested owner approvals. */
1041
- signatures: readonly SignatureEnvelope[]
1042
- type?: 'multisig' | undefined
1043
- }
975
+ /** Multisig input with an explicit account and a normalizable configuration. */
976
+ type MultisigFromConfig = Omit<Multisig, 'config' | 'type'> & {
977
+ config: MultisigConfig.Input
978
+ type?: 'multisig' | undefined
979
+ }
1044
980
 
1045
981
  type Value =
982
+ | MultisigFromConfig
1046
983
  | UnionPartialBy<SignatureEnvelope, 'prehash' | 'type'>
1047
984
  | Secp256k1Flat
1048
985
  | Serialized
1049
- | MultisigFromConfig
1050
986
 
1051
987
  type ReturnValue<value extends Value> = Compute<
1052
988
  OneOf<
@@ -1055,7 +991,7 @@ export declare namespace from {
1055
991
  : value extends Secp256k1Flat
1056
992
  ? Secp256k1
1057
993
  : value extends MultisigFromConfig
1058
- ? Multisig
994
+ ? Extract<SignatureEnvelope, { type: 'multisig' }>
1059
995
  : IsNarrowable<value, SignatureEnvelope> extends true
1060
996
  ? SignatureEnvelope
1061
997
  : Assign<
@@ -1090,19 +1026,10 @@ export declare namespace from {
1090
1026
  */
1091
1027
  export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope {
1092
1028
  if (typeof envelope === 'string') {
1093
- const serialized = Hex.concat(serializedMultisigType, envelope)
1094
- const result = deserialize(serialized)
1095
- if (
1096
- result.type !== 'multisig' ||
1097
- !Hex.isEqual(serialize(result), serialized)
1098
- )
1099
- throw new InvalidSerializedError({
1100
- reason: 'invalid multisig RPC signature',
1101
- serialized: envelope,
1102
- })
1103
- return result
1029
+ const signature = deserialize(Hex.concat(serializedMultisigType, envelope))
1030
+ if (signature.type !== 'multisig') throw new CoercionError({ envelope })
1031
+ return signature
1104
1032
  }
1105
-
1106
1033
  if (envelope.type === 'secp256k1')
1107
1034
  return {
1108
1035
  signature: Signature.fromRpc(envelope),
@@ -1176,13 +1103,15 @@ export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope {
1176
1103
  ('userAddress' in envelope && 'signature' in envelope)
1177
1104
  ) {
1178
1105
  const keychain = envelope as KeychainRpc
1179
- return {
1106
+ const result = {
1180
1107
  type: 'keychain',
1181
1108
  userAddress: keychain.userAddress,
1182
1109
  inner: fromRpc(keychain.signature),
1183
1110
  ...(keychain.keyId ? { keyId: keychain.keyId } : {}),
1184
1111
  ...(keychain.version ? { version: keychain.version } : {}),
1185
- }
1112
+ } satisfies Keychain
1113
+ assert(result)
1114
+ return result
1186
1115
  }
1187
1116
 
1188
1117
  throw new CoercionError({ envelope })
@@ -1192,10 +1121,8 @@ export declare namespace fromRpc {
1192
1121
  type ErrorType =
1193
1122
  | assert.ErrorType
1194
1123
  | CoercionError
1195
- | Hex.concat.ErrorType
1196
- | Hex.isEqual.ErrorType
1197
1124
  | InvalidSerializedError
1198
- | serialize.ErrorType
1125
+ | MultisigConfig.getAddress.ErrorType
1199
1126
  | Signature.fromRpc.ErrorType
1200
1127
  | Errors.GlobalErrorType
1201
1128
  }
@@ -1270,7 +1197,7 @@ export function getType<
1270
1197
  return 'keychain' as never
1271
1198
 
1272
1199
  // Detect Multisig signature
1273
- if ('config' in envelope && 'signatures' in envelope)
1200
+ if ('account' in envelope && 'config' in envelope && 'signatures' in envelope)
1274
1201
  return 'multisig' as never
1275
1202
 
1276
1203
  throw new CoercionError({
@@ -1359,6 +1286,7 @@ export function serialize(
1359
1286
 
1360
1287
  if (type === 'keychain') {
1361
1288
  const keychain = envelope as Keychain
1289
+ assert(keychain)
1362
1290
  const keychainTypeId =
1363
1291
  keychain.version === 'v1'
1364
1292
  ? serializedKeychainType
@@ -1409,79 +1337,31 @@ export declare namespace serialize {
1409
1337
  }
1410
1338
 
1411
1339
  /**
1412
- * Orders native multisig owner approvals into the strictly-ascending
1413
- * recovered-owner order the Tempo node requires for the multisig `signatures`
1414
- * array (the node enforces "recovered owners must be strictly ascending").
1340
+ * Sorts primitive owner approvals by recovered address.
1415
1341
  *
1416
- * Each approval is signed over the multisig owner approval digest
1417
- * ({@link ox#MultisigConfig.(getSignPayload:function)}), so the signer of
1418
- * every approval is recovered against that digest and the list is sorted by the
1419
- * recovered owner address. Works for any owner key type (secp256k1, p256,
1420
- * webAuthn).
1342
+ * Recovery uses the account and current config version's multisig digest.
1343
+ * Duplicate or non-owner approvals must still be rejected by the node.
1421
1344
  *
1422
1345
  * @example
1423
1346
  * ```ts twoslash
1424
- * import { Secp256k1 } from 'ox'
1425
- * import {
1426
- * MultisigConfig,
1427
- * SignatureEnvelope,
1428
- * TxEnvelopeTempo
1429
- * } from 'ox/tempo'
1430
- *
1431
- * const config = MultisigConfig.from({
1432
- * owners: [
1433
- * {
1434
- * owner: '0x1111111111111111111111111111111111111111',
1435
- * weight: 1
1436
- * },
1437
- * {
1438
- * owner: '0x2222222222222222222222222222222222222222',
1439
- * weight: 1
1440
- * }
1441
- * ],
1442
- * threshold: 2
1443
- * })
1444
- * const account = MultisigConfig.getAddress(config)
1445
- *
1446
- * const tx = TxEnvelopeTempo.from({ chainId: 1, calls: [] })
1447
- * const payload = TxEnvelopeTempo.getSignPayload(tx)
1448
- *
1449
- * const privateKeys = [
1450
- * Secp256k1.randomPrivateKey(),
1451
- * Secp256k1.randomPrivateKey()
1452
- * ]
1453
- * const digest = MultisigConfig.getSignPayload({
1454
- * account,
1455
- * config,
1456
- * payload
1457
- * })
1458
- * const signatures = privateKeys.map((privateKey) =>
1459
- * SignatureEnvelope.from(
1460
- * Secp256k1.sign({ payload: digest, privateKey })
1461
- * )
1462
- * )
1347
+ * import { SignatureEnvelope } from 'ox/tempo'
1463
1348
  *
1464
1349
  * const ordered = SignatureEnvelope.sortMultisigApprovals({
1465
- * // [!code focus]
1466
- * account, // [!code focus]
1467
- * config, // [!code focus]
1468
- * payload, // [!code focus]
1469
- * signatures // [!code focus]
1470
- * }) // [!code focus]
1350
+ * account: '0x2222222222222222222222222222222222222222',
1351
+ * config: { version: 0n },
1352
+ * payload: `0x${'ab'.repeat(32)}`,
1353
+ * signatures: []
1354
+ * })
1471
1355
  * ```
1472
1356
  *
1473
- * @param value - The approval ordering parameters.
1474
- * @returns The owner approvals ordered ascending by recovered owner address.
1357
+ * @param value - Account, version, payload, and primitive approvals.
1358
+ * @returns The approvals in ascending recovered-address order.
1475
1359
  */
1476
1360
  export function sortMultisigApprovals(
1477
1361
  value: sortMultisigApprovals.Value,
1478
- ): readonly SignatureEnvelope[] {
1479
- const { account, config, payload, signatures } = value
1480
- const digest = MultisigConfig.getSignPayload({
1481
- account,
1482
- config: MultisigConfig.from(config),
1483
- payload,
1484
- })
1362
+ ): readonly Primitive[] {
1363
+ const { signatures } = value
1364
+ const digest = MultisigConfig.getSignPayload(value)
1485
1365
  // Recover each signer once (decorate–sort–undecorate) rather than inside the
1486
1366
  // comparator.
1487
1367
  return signatures
@@ -1494,15 +1374,9 @@ export function sortMultisigApprovals(
1494
1374
  }
1495
1375
 
1496
1376
  export declare namespace sortMultisigApprovals {
1497
- type Value = {
1498
- /** The native multisig account address. */
1499
- account: Address.Address
1500
- /** Complete applicable multisig configuration. */
1501
- config: MultisigConfig.Input
1502
- /** The inner transaction sign payload (`tx.signature_hash()`). */
1503
- payload: Hex.Hex | Bytes.Bytes
1504
- /** The owner approvals to order. */
1505
- signatures: readonly SignatureEnvelope[]
1377
+ type Value = MultisigConfig.getSignPayload.Value & {
1378
+ /** Primitive owner approvals to order. */
1379
+ signatures: readonly Primitive[]
1506
1380
  }
1507
1381
 
1508
1382
  type ErrorType =
@@ -1575,6 +1449,7 @@ export function toRpc<const envelope extends toRpc.Input>(
1575
1449
 
1576
1450
  if (type === 'keychain') {
1577
1451
  const keychain = envelope as Keychain
1452
+ assertKeychainVersion(keychain)
1578
1453
  return {
1579
1454
  type: 'keychain',
1580
1455
  userAddress: keychain.userAddress,
@@ -1585,8 +1460,38 @@ export function toRpc<const envelope extends toRpc.Input>(
1585
1460
  }
1586
1461
 
1587
1462
  if (type === 'multisig') {
1588
- const multisig = envelope as Multisig
1589
- return Hex.slice(serialize(multisig), 1) as never
1463
+ const multisig = envelope as Multisig<Hex.Hex | number>
1464
+ return Hex.slice(
1465
+ serialize({
1466
+ ...multisig,
1467
+ config: {
1468
+ ...multisig.config,
1469
+ threshold: Number(multisig.config.threshold),
1470
+ owners: multisig.config.owners.map((owner) => ({
1471
+ ...owner,
1472
+ weight: Number(owner.weight),
1473
+ })),
1474
+ },
1475
+ signatures: multisig.signatures.map((signature) =>
1476
+ signature.type === 'secp256k1'
1477
+ ? {
1478
+ ...signature,
1479
+ signature: {
1480
+ ...signature.signature,
1481
+ yParity: Number(signature.signature.yParity),
1482
+ },
1483
+ }
1484
+ : {
1485
+ ...signature,
1486
+ signature: {
1487
+ r: signature.signature.r,
1488
+ s: signature.signature.s,
1489
+ },
1490
+ },
1491
+ ),
1492
+ }),
1493
+ 1,
1494
+ ) as never
1590
1495
  }
1591
1496
 
1592
1497
  throw new CoercionError({ envelope })