ox 1.7.2 → 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 (142) hide show
  1. package/CHANGELOG.md +43 -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 +43 -39
  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 +45 -28
  24. package/dist/tempo/MultisigOperation.d.ts.map +1 -1
  25. package/dist/tempo/MultisigOperation.js +81 -131
  26. package/dist/tempo/MultisigOperation.js.map +1 -1
  27. package/dist/tempo/MultisigSimulation.d.ts +85 -0
  28. package/dist/tempo/MultisigSimulation.d.ts.map +1 -0
  29. package/dist/tempo/MultisigSimulation.js +112 -0
  30. package/dist/tempo/MultisigSimulation.js.map +1 -0
  31. package/dist/tempo/RpcSchemaTempo.d.ts +20 -10
  32. package/dist/tempo/RpcSchemaTempo.d.ts.map +1 -1
  33. package/dist/tempo/SignatureEnvelope.d.ts +74 -153
  34. package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
  35. package/dist/tempo/SignatureEnvelope.js +130 -187
  36. package/dist/tempo/SignatureEnvelope.js.map +1 -1
  37. package/dist/tempo/TransactionRequest.d.ts +9 -7
  38. package/dist/tempo/TransactionRequest.d.ts.map +1 -1
  39. package/dist/tempo/TransactionRequest.js +12 -7
  40. package/dist/tempo/TransactionRequest.js.map +1 -1
  41. package/dist/tempo/index.d.ts +7 -5
  42. package/dist/tempo/index.d.ts.map +1 -1
  43. package/dist/tempo/index.js +7 -5
  44. package/dist/tempo/index.js.map +1 -1
  45. package/dist/zod/tempo/AuthorizationTempo.d.ts +360 -140
  46. package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
  47. package/dist/zod/tempo/KeyAuthorization.d.ts +247 -487
  48. package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
  49. package/dist/zod/tempo/KeyAuthorization.js +29 -31
  50. package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
  51. package/dist/zod/tempo/MultisigOperation.d.ts +0 -10
  52. package/dist/zod/tempo/MultisigOperation.d.ts.map +1 -1
  53. package/dist/zod/tempo/MultisigOperation.js +0 -3
  54. package/dist/zod/tempo/MultisigOperation.js.map +1 -1
  55. package/dist/zod/tempo/MultisigSimulation.d.ts +52 -0
  56. package/dist/zod/tempo/MultisigSimulation.d.ts.map +1 -0
  57. package/dist/zod/tempo/MultisigSimulation.js +44 -0
  58. package/dist/zod/tempo/MultisigSimulation.js.map +1 -0
  59. package/dist/zod/tempo/RpcSchemaTempo.d.ts +357 -422
  60. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  61. package/dist/zod/tempo/RpcSchemaTempo.js +19 -10
  62. package/dist/zod/tempo/RpcSchemaTempo.js.map +1 -1
  63. package/dist/zod/tempo/SignatureEnvelope.d.ts +108 -42
  64. package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
  65. package/dist/zod/tempo/SignatureEnvelope.js +21 -19
  66. package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
  67. package/dist/zod/tempo/Transaction.d.ts +678 -666
  68. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  69. package/dist/zod/tempo/TransactionRequest.d.ts +580 -619
  70. package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
  71. package/dist/zod/tempo/TransactionRequest.js +18 -10
  72. package/dist/zod/tempo/TransactionRequest.js.map +1 -1
  73. package/dist/zod/tempo/TxEnvelopeTempo.d.ts +327 -213
  74. package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  75. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +105 -3
  76. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts.map +1 -1
  77. package/dist/zod/tempo/z.d.ts +1 -1
  78. package/dist/zod/tempo/z.d.ts.map +1 -1
  79. package/dist/zod/tempo/z.js +1 -1
  80. package/dist/zod/tempo/z.js.map +1 -1
  81. package/package.json +9 -9
  82. package/src/core/AbiEvent.ts +11 -9
  83. package/src/core/Blobs.ts +6 -2
  84. package/src/core/Signature.ts +41 -11
  85. package/src/core/Siwe.ts +20 -1
  86. package/src/core/_test/AbiEvent.test.ts +45 -0
  87. package/src/core/_test/Blobs.test.ts +22 -0
  88. package/src/core/_test/Signature.test.ts +166 -0
  89. package/src/core/_test/Siwe.test.ts +100 -1
  90. package/src/tempo/AuthorizationTempo.test.ts +8 -0
  91. package/src/tempo/KeyAuthorization.test-d.ts +16 -74
  92. package/src/tempo/KeyAuthorization.test.ts +113 -14
  93. package/src/tempo/KeyAuthorization.ts +110 -66
  94. package/src/tempo/MultisigConfig.test.ts +44 -20
  95. package/src/tempo/MultisigConfig.ts +65 -23
  96. package/src/tempo/MultisigOperation.test-d.ts +8 -6
  97. package/src/tempo/MultisigOperation.test.ts +187 -591
  98. package/src/tempo/MultisigOperation.ts +129 -205
  99. package/src/tempo/MultisigSimulation.test-d.ts +33 -0
  100. package/src/tempo/MultisigSimulation.test.ts +194 -0
  101. package/src/tempo/MultisigSimulation.ts +192 -0
  102. package/src/tempo/RpcSchemaTempo.test-d.ts +45 -10
  103. package/src/tempo/RpcSchemaTempo.ts +15 -6
  104. package/src/tempo/SignatureEnvelope.test-d.ts +27 -101
  105. package/src/tempo/SignatureEnvelope.test.ts +330 -533
  106. package/src/tempo/SignatureEnvelope.ts +198 -310
  107. package/src/tempo/Transaction.test.ts +61 -0
  108. package/src/tempo/TransactionRequest.test-d.ts +7 -7
  109. package/src/tempo/TransactionRequest.test.ts +81 -223
  110. package/src/tempo/TransactionRequest.ts +34 -13
  111. package/src/tempo/index.ts +7 -5
  112. package/src/tempo/multisig.e2e.test.ts +329 -818
  113. package/src/version.ts +1 -1
  114. package/src/zod/tempo/KeyAuthorization.ts +50 -39
  115. package/src/zod/tempo/MultisigOperation.ts +0 -3
  116. package/src/zod/tempo/MultisigSimulation.ts +53 -0
  117. package/src/zod/tempo/RpcSchemaTempo.ts +20 -10
  118. package/src/zod/tempo/SignatureEnvelope.ts +46 -59
  119. package/src/zod/tempo/TransactionRequest.ts +28 -14
  120. package/src/zod/tempo/_test/KeyAuthorization.test.ts +66 -30
  121. package/src/zod/tempo/_test/MultisigOperation.test.ts +0 -2
  122. package/src/zod/tempo/_test/MultisigSimulation.test-d.ts +13 -0
  123. package/src/zod/tempo/_test/MultisigSimulation.test.ts +53 -0
  124. package/src/zod/tempo/_test/RpcSchemaTempo.test-d.ts +19 -2
  125. package/src/zod/tempo/_test/RpcSchemaTempo.test.ts +46 -4
  126. package/src/zod/tempo/_test/SignatureEnvelope.test.ts +108 -129
  127. package/src/zod/tempo/_test/TransactionRequest.test.ts +17 -168
  128. package/src/zod/tempo/z.ts +1 -1
  129. package/dist/tempo/MultisigWitness.d.ts +0 -106
  130. package/dist/tempo/MultisigWitness.d.ts.map +0 -1
  131. package/dist/tempo/MultisigWitness.js +0 -118
  132. package/dist/tempo/MultisigWitness.js.map +0 -1
  133. package/dist/zod/tempo/MultisigWitness.d.ts +0 -152
  134. package/dist/zod/tempo/MultisigWitness.d.ts.map +0 -1
  135. package/dist/zod/tempo/MultisigWitness.js +0 -76
  136. package/dist/zod/tempo/MultisigWitness.js.map +0 -1
  137. package/src/tempo/MultisigWitness.test-d.ts +0 -42
  138. package/src/tempo/MultisigWitness.test.ts +0 -288
  139. package/src/tempo/MultisigWitness.ts +0 -210
  140. package/src/zod/tempo/MultisigWitness.ts +0 -101
  141. package/src/zod/tempo/_test/MultisigWitness.test-d.ts +0 -13
  142. package/src/zod/tempo/_test/MultisigWitness.test.ts +0 -149
@@ -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,14 +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. */
126
+ /**
127
+ * RPC-formatted signature envelope.
128
+ */
129
129
  export type SignatureEnvelopeRpc =
130
- | (NonMultisigRpc & Undefined<Omit<MultisigRpc, 'type'>>)
131
- | MultisigRpcOneOf
130
+ | OneOf<Secp256k1Rpc | P256Rpc | WebAuthnRpc | KeychainRpc>
131
+ | MultisigRpc
132
132
 
133
133
  /** Primitive signature envelope accepted by protocol sidecars. */
134
134
  export type Primitive<numberType = number> = OneOf<
@@ -148,68 +148,44 @@ export type PrimitiveRpc = OneOf<Secp256k1Rpc | P256Rpc | WebAuthnRpc>
148
148
  export type KeychainVersion = 'v1' | 'v2'
149
149
 
150
150
  export type Keychain<numberType = number> = {
151
- /** Root account address that this transaction is being executed for */
152
- userAddress: Address.Address
153
151
  /** The actual signature from the access key (can be Secp256k1, P256, or WebAuthn) */
154
152
  inner: SignatureEnvelope<numberType>
155
153
  /** The access key address (recovered address of the access key signer). */
156
154
  keyId?: Address.Address | undefined
157
155
  type: 'keychain'
156
+ /** Root account address that this transaction is being executed for */
157
+ userAddress: Address.Address
158
158
  /** Keychain signature version. @default 'v1' */
159
159
  version?: KeychainVersion | undefined
160
160
  }
161
161
 
162
162
  export type KeychainRpc = {
163
- type: 'keychain'
164
- userAddress: Address.Address
165
163
  keyId?: Address.Address | undefined
166
164
  signature: SignatureEnvelopeRpc
165
+ type: 'keychain'
166
+ userAddress: Address.Address
167
167
  version?: KeychainVersion | undefined
168
168
  }
169
169
 
170
170
  /**
171
171
  * Native multisig signature (type `0x05`).
172
172
  *
173
- * Wraps a set of owner approvals (secp256k1, p256, webAuthn, or nested
174
- * multisig) over the multisig owner approval digest. The transaction sender is
175
- * the derived `account`, authorized once the recovered owner weights meet the
176
- * configured threshold.
177
- *
178
- * [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.
179
176
  */
180
177
  export type Multisig<numberType = number> = {
181
- /** Native multisig account address. */
178
+ /** Permanent native multisig account address. */
182
179
  account: Address.Address
183
- /** Complete applicable multisig configuration witness. */
180
+ /** Complete current configuration, included in every signature. */
184
181
  config: MultisigConfig.Config<bigint, numberType>
185
- /**
186
- * Owner approvals over the multisig owner approval digest. Each approval is
187
- * either a primitive signature or a nested multisig signature (keychain
188
- * approvals are invalid).
189
- */
190
- signatures: readonly SignatureEnvelope<numberType>[]
182
+ /** Primitive owner approvals in ascending recovered-address order. */
183
+ signatures: readonly Primitive<numberType>[]
191
184
  type: 'multisig'
192
185
  }
193
186
 
194
- interface MultisigOneOf<numberType = number>
195
- extends
196
- Multisig<numberType>,
197
- Undefined<Omit<NonMultisig<numberType>, 'type'>> {}
198
-
199
- /** RPC-formatted native multisig signature. */
200
- export type MultisigRpc = {
201
- /** Native multisig account address. */
202
- account: Address.Address
203
- /** Complete applicable multisig configuration witness. */
204
- config: MultisigConfig.Rpc
205
- /** Structured owner approvals. */
206
- signatures: readonly SignatureEnvelopeRpc[]
207
- /** Multisig RPC signatures are untagged. */
208
- type?: undefined
209
- }
210
-
211
- interface MultisigRpcOneOf
212
- extends MultisigRpc, Undefined<Omit<NonMultisigRpc, 'type'>> {}
187
+ /** RLP-encoded `[account, config, signatures]`, without the `0x05` type byte. */
188
+ export type MultisigRpc = Hex.Hex
213
189
 
214
190
  export type P256<numberType = number> = {
215
191
  prehash: boolean
@@ -234,8 +210,8 @@ export type Secp256k1<numberType = number> = {
234
210
 
235
211
  export type Secp256k1Rpc = Compute<
236
212
  Signature.Rpc<true> & {
237
- v?: Hex.Hex | undefined
238
213
  type: 'secp256k1'
214
+ v?: Hex.Hex | undefined
239
215
  }
240
216
  >
241
217
 
@@ -251,8 +227,8 @@ export type WebAuthn<numberType = number> = {
251
227
  WebAuthnP256.SignMetadata,
252
228
  'authenticatorData' | 'clientDataJSON'
253
229
  >
254
- signature: Signature.Signature<false, numberType>
255
230
  publicKey: PublicKey.PublicKey
231
+ signature: Signature.Signature<false, numberType>
256
232
  type: 'webAuthn'
257
233
  }
258
234
 
@@ -347,13 +323,14 @@ export function assert(envelope: PartialBy<SignatureEnvelope, 'type'>): void {
347
323
 
348
324
  if (type === 'keychain') {
349
325
  const keychain = envelope as Keychain
326
+ assertKeychainVersion(keychain)
350
327
  assert(keychain.inner)
351
328
  return
352
329
  }
353
330
 
354
331
  if (type === 'multisig') {
355
332
  const multisig = envelope as Multisig
356
- assertMultisig(multisig, 1)
333
+ assertMultisig(multisig)
357
334
  return
358
335
  }
359
336
  }
@@ -369,7 +346,17 @@ export declare namespace assert {
369
346
  | Errors.GlobalErrorType
370
347
  }
371
348
 
372
- 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 {
373
360
  const missing: string[] = []
374
361
  if (!envelope.account) missing.push('account')
375
362
  if (!envelope.config) missing.push('config')
@@ -380,10 +367,6 @@ function assertMultisig(envelope: Multisig, depth: number): void {
380
367
  missing,
381
368
  type: 'multisig',
382
369
  })
383
- if (depth > MultisigConfig.maxNestingDepth)
384
- throw new InvalidMultisigApprovalError({
385
- reason: `multisig nesting depth exceeds ${MultisigConfig.maxNestingDepth}`,
386
- })
387
370
  if (!Address.validate(envelope.account))
388
371
  throw new InvalidMultisigApprovalError({
389
372
  reason: 'multisig account is invalid',
@@ -392,25 +375,6 @@ function assertMultisig(envelope: Multisig, depth: number): void {
392
375
  throw new InvalidMultisigApprovalError({
393
376
  reason: 'multisig account cannot be zero',
394
377
  })
395
- MultisigConfig.assert(envelope.config)
396
- if (
397
- envelope.config.owners.some((owner) =>
398
- Address.isEqual(owner.owner, envelope.account),
399
- )
400
- )
401
- throw new InvalidMultisigApprovalError({
402
- reason: 'multisig account cannot be an owner',
403
- })
404
- if (
405
- envelope.config.version === 0n &&
406
- !Address.isEqual(
407
- MultisigConfig.getAddress(envelope.config),
408
- envelope.account,
409
- )
410
- )
411
- throw new InvalidMultisigApprovalError({
412
- reason: 'initial multisig config does not derive account',
413
- })
414
378
  if (envelope.signatures.length === 0)
415
379
  throw new InvalidMultisigApprovalError({
416
380
  reason: 'multisig signatures cannot be empty',
@@ -420,20 +384,29 @@ function assertMultisig(envelope: Multisig, depth: number): void {
420
384
  reason: `multisig signatures exceed ${MultisigConfig.maxSignatures}`,
421
385
  })
422
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
+
423
398
  for (const inner of envelope.signatures) {
424
- const type = getType(inner)
425
- if (type === 'keychain')
399
+ const type = getType(inner as SignatureEnvelope)
400
+ if (type === 'keychain' || type === 'multisig')
426
401
  throw new InvalidMultisigApprovalError({
427
- 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`,
428
409
  })
429
- if (type === 'multisig') assertMultisig(inner as Multisig, depth + 1)
430
- else {
431
- assert(inner)
432
- if (Hex.size(serialize(inner)) > MultisigConfig.maxOwnerSignatureBytes)
433
- throw new InvalidMultisigApprovalError({
434
- reason: `multisig owner signature exceeds ${MultisigConfig.maxOwnerSignatureBytes} bytes`,
435
- })
436
- }
437
410
  }
438
411
  }
439
412
 
@@ -484,10 +457,10 @@ export declare namespace extractAddress {
484
457
  type Options = {
485
458
  /** The sign payload that was signed (only required for secp256k1 signatures). */
486
459
  payload: Hex.Hex | Bytes.Bytes
487
- /** The signature envelope. */
488
- signature: SignatureEnvelope
489
460
  /** Whether to return the root `userAddress` for keychain signatures instead of extracting from the inner signature. */
490
461
  root?: boolean | undefined
462
+ /** The signature envelope. */
463
+ signature: SignatureEnvelope
491
464
  }
492
465
 
493
466
  type ReturnType = Address.Address
@@ -590,13 +563,10 @@ export declare namespace extractPublicKey {
590
563
  * @throws `CoercionError` if the serialized value cannot be coerced to a valid signature envelope.
591
564
  */
592
565
  export function deserialize(value: Serialized): SignatureEnvelope {
593
- return deserialize_(value, 0)
566
+ return deserialize_(value)
594
567
  }
595
568
 
596
- function deserialize_(
597
- value: Serialized,
598
- multisigDepth: number,
599
- ): SignatureEnvelope {
569
+ function deserialize_(value: Serialized): SignatureEnvelope {
600
570
  const serialized = value.endsWith(magicBytes.slice(2))
601
571
  ? Hex.slice(value, 0, -Hex.size(magicBytes))
602
572
  : value
@@ -699,33 +669,27 @@ function deserialize_(
699
669
  typeId === serializedKeychainV2Type
700
670
  ) {
701
671
  const userAddress = Hex.slice(data, 0, 20)
702
- const inner = deserialize_(Hex.slice(data, 20), multisigDepth)
672
+ const inner = deserialize_(Hex.slice(data, 20))
703
673
 
704
- return {
674
+ const envelope = {
705
675
  userAddress,
706
676
  inner,
707
677
  type: 'keychain',
708
678
  version: typeId === serializedKeychainV2Type ? 'v2' : 'v1',
709
679
  } satisfies Keychain
680
+ assert(envelope)
681
+ return envelope
710
682
  }
711
683
 
712
684
  if (typeId === serializedMultisigType) {
713
- const depth = multisigDepth + 1
714
- if (depth > MultisigConfig.maxNestingDepth)
715
- throw new InvalidSerializedError({
716
- reason: `multisig nesting depth exceeds ${MultisigConfig.maxNestingDepth}`,
717
- serialized,
718
- })
719
-
720
685
  const decoded = Rlp.toHex(data)
721
686
  if (!Array.isArray(decoded) || decoded.length !== 3)
722
687
  throw new InvalidSerializedError({
723
688
  reason: 'invalid multisig wire shape: expected exactly three fields',
724
689
  serialized,
725
690
  })
726
-
727
691
  const [account, config, signatures] = decoded
728
- if (!Address.validate(account))
692
+ if (typeof account !== 'string' || !Address.validate(account))
729
693
  throw new InvalidSerializedError({
730
694
  reason: 'invalid multisig account',
731
695
  serialized,
@@ -735,68 +699,43 @@ function deserialize_(
735
699
  reason: 'invalid multisig config',
736
700
  serialized,
737
701
  })
738
- const [salt, version, threshold, owners] = config
739
702
  if (
740
- config.length !== 4 ||
741
- Array.isArray(salt) ||
742
- Hex.size(salt) !== 32 ||
743
- Array.isArray(version) ||
744
- Hex.size(version) > 8 ||
745
- (version !== '0x' && Hex.slice(version, 0, 1) === '0x00') ||
746
- Array.isArray(threshold) ||
747
- Hex.size(threshold) > 1 ||
748
- !Array.isArray(owners) ||
749
- owners.some(
750
- (owner) =>
751
- !Array.isArray(owner) ||
752
- owner.length !== 2 ||
753
- owner.some(Array.isArray) ||
754
- !Address.validate(owner[0]) ||
755
- Hex.size(owner[1] as Hex.Hex) > 1,
756
- )
703
+ !Array.isArray(signatures) ||
704
+ signatures.length === 0 ||
705
+ signatures.length > MultisigConfig.maxSignatures
757
706
  )
758
- throw new InvalidSerializedError({
759
- reason: 'invalid multisig config',
760
- serialized,
761
- })
762
- if (!Array.isArray(signatures) || signatures.some(Array.isArray))
763
707
  throw new InvalidSerializedError({
764
708
  reason: 'invalid multisig signatures list',
765
709
  serialized,
766
710
  })
767
- if (signatures.length === 0)
768
- throw new InvalidSerializedError({
769
- reason: 'multisig signatures cannot be empty',
770
- serialized,
771
- })
772
- if (signatures.length > MultisigConfig.maxSignatures)
773
- throw new InvalidSerializedError({
774
- reason: `multisig signatures exceed ${MultisigConfig.maxSignatures}`,
775
- serialized,
776
- })
777
- for (const signature of signatures)
778
- if (
779
- Hex.size(signature as Hex.Hex) >
780
- MultisigConfig.maxOwnerSignatureBytes &&
781
- Hex.slice(signature as Hex.Hex, 0, 1) !==
782
- MultisigConfig.signatureTypeByte
783
- )
784
- throw new InvalidSerializedError({
785
- reason: `multisig owner signature exceeds ${MultisigConfig.maxOwnerSignatureBytes} bytes`,
786
- serialized,
787
- })
788
-
789
711
  const envelope = {
790
- account: account as Address.Address,
712
+ type: 'multisig',
713
+ account,
791
714
  config: MultisigConfig.fromTuple(
792
715
  config as unknown as MultisigConfig.Tuple,
793
716
  ),
794
- signatures: signatures.map((signature) =>
795
- deserialize_(signature as Hex.Hex, depth),
796
- ),
797
- 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
+ }),
798
737
  } satisfies Multisig
799
- assertMultisig(envelope, depth)
738
+ assertMultisig(envelope)
800
739
  return envelope
801
740
  }
802
741
 
@@ -934,28 +873,30 @@ function deserialize_(
934
873
  * @example
935
874
  * ### Multisig
936
875
  *
937
- * ```ts twoslash
938
- * import { Secp256k1 } from 'ox'
939
- * import { MultisigConfig, SignatureEnvelope } from 'ox/tempo'
940
- *
941
- * const config = MultisigConfig.from({
942
- * owners: [
943
- * {
944
- * owner: '0x1111111111111111111111111111111111111111',
945
- * weight: 1
946
- * }
947
- * ],
948
- * threshold: 1
949
- * })
876
+ * Include the permanent account and current configuration on every transaction.
950
877
  *
951
- * const privateKey = Secp256k1.randomPrivateKey()
952
- * const signature = SignatureEnvelope.from(
953
- * Secp256k1.sign({ payload: '0xdeadbeef', privateKey })
954
- * )
878
+ * ```ts twoslash
879
+ * import { SignatureEnvelope } from 'ox/tempo'
955
880
  *
956
- * const multisig = SignatureEnvelope.from({
957
- * config,
958
- * 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
+ * ]
959
900
  * })
960
901
  * ```
961
902
  *
@@ -980,17 +921,13 @@ export function from<const value extends from.Value>(
980
921
  const type = getType(value)
981
922
 
982
923
  if (type === 'multisig') {
983
- const multisig = value as from.MultisigFromConfig
984
- const config = MultisigConfig.from(multisig.config)
985
- const account = multisig.account ?? MultisigConfig.getAddress(config)
986
- const envelope = {
987
- account,
988
- config,
924
+ const multisig = value as Multisig
925
+ return {
926
+ ...multisig,
927
+ config: MultisigConfig.from(multisig.config),
989
928
  signatures: multisig.signatures.map((signature) => from(signature)),
990
929
  type,
991
- } satisfies Multisig
992
- assert(envelope)
993
- return envelope as never
930
+ } as never
994
931
  }
995
932
 
996
933
  return {
@@ -1035,32 +972,17 @@ export declare namespace from {
1035
972
  payload?: Hex.Hex | Bytes.Bytes | undefined
1036
973
  }
1037
974
 
1038
- /** Multisig signature input with an optional derived initial account. */
1039
- type MultisigFromConfig =
1040
- | {
1041
- /** Initial native multisig account, derived from config when omitted. */
1042
- account?: Address.Address | undefined
1043
- /** Initial version-0 multisig configuration witness. */
1044
- config: MultisigConfig.Input<0 | 0n>
1045
- /** Primitive or nested owner approvals. */
1046
- signatures: readonly SignatureEnvelope[]
1047
- type?: 'multisig' | undefined
1048
- }
1049
- | {
1050
- /** Native multisig account. */
1051
- account: Address.Address
1052
- /** Complete applicable multisig configuration witness. */
1053
- config: MultisigConfig.Input
1054
- /** Primitive or nested owner approvals. */
1055
- signatures: readonly SignatureEnvelope[]
1056
- type?: 'multisig' | undefined
1057
- }
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
+ }
1058
980
 
1059
981
  type Value =
982
+ | MultisigFromConfig
1060
983
  | UnionPartialBy<SignatureEnvelope, 'prehash' | 'type'>
1061
984
  | Secp256k1Flat
1062
985
  | Serialized
1063
- | MultisigFromConfig
1064
986
 
1065
987
  type ReturnValue<value extends Value> = Compute<
1066
988
  OneOf<
@@ -1069,7 +991,7 @@ export declare namespace from {
1069
991
  : value extends Secp256k1Flat
1070
992
  ? Secp256k1
1071
993
  : value extends MultisigFromConfig
1072
- ? Multisig
994
+ ? Extract<SignatureEnvelope, { type: 'multisig' }>
1073
995
  : IsNarrowable<value, SignatureEnvelope> extends true
1074
996
  ? SignatureEnvelope
1075
997
  : Assign<
@@ -1103,6 +1025,11 @@ export declare namespace from {
1103
1025
  * @returns The signature envelope with bigint values.
1104
1026
  */
1105
1027
  export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope {
1028
+ if (typeof envelope === 'string') {
1029
+ const signature = deserialize(Hex.concat(serializedMultisigType, envelope))
1030
+ if (signature.type !== 'multisig') throw new CoercionError({ envelope })
1031
+ return signature
1032
+ }
1106
1033
  if (envelope.type === 'secp256k1')
1107
1034
  return {
1108
1035
  signature: Signature.fromRpc(envelope),
@@ -1176,26 +1103,13 @@ 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
- }
1186
- }
1187
-
1188
- if (
1189
- (envelope as { type?: string | undefined }).type === 'multisig' ||
1190
- ('config' in envelope && 'signatures' in envelope)
1191
- ) {
1192
- const multisig = envelope as MultisigRpc
1193
- const result = {
1194
- account: multisig.account,
1195
- config: MultisigConfig.fromRpc(multisig.config),
1196
- signatures: multisig.signatures.map((signature) => fromRpc(signature)),
1197
- type: 'multisig',
1198
- } satisfies Multisig
1112
+ } satisfies Keychain
1199
1113
  assert(result)
1200
1114
  return result
1201
1115
  }
@@ -1208,7 +1122,7 @@ export declare namespace fromRpc {
1208
1122
  | assert.ErrorType
1209
1123
  | CoercionError
1210
1124
  | InvalidSerializedError
1211
- | MultisigConfig.fromRpc.ErrorType
1125
+ | MultisigConfig.getAddress.ErrorType
1212
1126
  | Signature.fromRpc.ErrorType
1213
1127
  | Errors.GlobalErrorType
1214
1128
  }
@@ -1283,7 +1197,7 @@ export function getType<
1283
1197
  return 'keychain' as never
1284
1198
 
1285
1199
  // Detect Multisig signature
1286
- if ('config' in envelope && 'signatures' in envelope)
1200
+ if ('account' in envelope && 'config' in envelope && 'signatures' in envelope)
1287
1201
  return 'multisig' as never
1288
1202
 
1289
1203
  throw new CoercionError({
@@ -1372,6 +1286,7 @@ export function serialize(
1372
1286
 
1373
1287
  if (type === 'keychain') {
1374
1288
  const keychain = envelope as Keychain
1289
+ assert(keychain)
1375
1290
  const keychainTypeId =
1376
1291
  keychain.version === 'v1'
1377
1292
  ? serializedKeychainType
@@ -1422,79 +1337,31 @@ export declare namespace serialize {
1422
1337
  }
1423
1338
 
1424
1339
  /**
1425
- * Orders native multisig owner approvals into the strictly-ascending
1426
- * recovered-owner order the Tempo node requires for the multisig `signatures`
1427
- * array (the node enforces "recovered owners must be strictly ascending").
1340
+ * Sorts primitive owner approvals by recovered address.
1428
1341
  *
1429
- * Each approval is signed over the multisig owner approval digest
1430
- * ({@link ox#MultisigConfig.(getSignPayload:function)}), so the signer of
1431
- * every approval is recovered against that digest and the list is sorted by the
1432
- * recovered owner address. Works for any owner key type (secp256k1, p256,
1433
- * 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.
1434
1344
  *
1435
1345
  * @example
1436
1346
  * ```ts twoslash
1437
- * import { Secp256k1 } from 'ox'
1438
- * import {
1439
- * MultisigConfig,
1440
- * SignatureEnvelope,
1441
- * TxEnvelopeTempo
1442
- * } from 'ox/tempo'
1443
- *
1444
- * const config = MultisigConfig.from({
1445
- * owners: [
1446
- * {
1447
- * owner: '0x1111111111111111111111111111111111111111',
1448
- * weight: 1
1449
- * },
1450
- * {
1451
- * owner: '0x2222222222222222222222222222222222222222',
1452
- * weight: 1
1453
- * }
1454
- * ],
1455
- * threshold: 2
1456
- * })
1457
- * const account = MultisigConfig.getAddress(config)
1458
- *
1459
- * const tx = TxEnvelopeTempo.from({ chainId: 1, calls: [] })
1460
- * const payload = TxEnvelopeTempo.getSignPayload(tx)
1461
- *
1462
- * const privateKeys = [
1463
- * Secp256k1.randomPrivateKey(),
1464
- * Secp256k1.randomPrivateKey()
1465
- * ]
1466
- * const digest = MultisigConfig.getSignPayload({
1467
- * account,
1468
- * config,
1469
- * payload
1470
- * })
1471
- * const signatures = privateKeys.map((privateKey) =>
1472
- * SignatureEnvelope.from(
1473
- * Secp256k1.sign({ payload: digest, privateKey })
1474
- * )
1475
- * )
1347
+ * import { SignatureEnvelope } from 'ox/tempo'
1476
1348
  *
1477
1349
  * const ordered = SignatureEnvelope.sortMultisigApprovals({
1478
- * // [!code focus]
1479
- * account, // [!code focus]
1480
- * config, // [!code focus]
1481
- * payload, // [!code focus]
1482
- * signatures // [!code focus]
1483
- * }) // [!code focus]
1350
+ * account: '0x2222222222222222222222222222222222222222',
1351
+ * config: { version: 0n },
1352
+ * payload: `0x${'ab'.repeat(32)}`,
1353
+ * signatures: []
1354
+ * })
1484
1355
  * ```
1485
1356
  *
1486
- * @param value - The approval ordering parameters.
1487
- * @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.
1488
1359
  */
1489
1360
  export function sortMultisigApprovals(
1490
1361
  value: sortMultisigApprovals.Value,
1491
- ): readonly SignatureEnvelope[] {
1492
- const { account, config, payload, signatures } = value
1493
- const digest = MultisigConfig.getSignPayload({
1494
- account,
1495
- config: MultisigConfig.from(config),
1496
- payload,
1497
- })
1362
+ ): readonly Primitive[] {
1363
+ const { signatures } = value
1364
+ const digest = MultisigConfig.getSignPayload(value)
1498
1365
  // Recover each signer once (decorate–sort–undecorate) rather than inside the
1499
1366
  // comparator.
1500
1367
  return signatures
@@ -1507,15 +1374,9 @@ export function sortMultisigApprovals(
1507
1374
  }
1508
1375
 
1509
1376
  export declare namespace sortMultisigApprovals {
1510
- type Value = {
1511
- /** The native multisig account address. */
1512
- account: Address.Address
1513
- /** Complete applicable multisig configuration witness. */
1514
- config: MultisigConfig.Input
1515
- /** The inner transaction sign payload (`tx.signature_hash()`). */
1516
- payload: Hex.Hex | Bytes.Bytes
1517
- /** The owner approvals to order. */
1518
- signatures: readonly SignatureEnvelope[]
1377
+ type Value = MultisigConfig.getSignPayload.Value & {
1378
+ /** Primitive owner approvals to order. */
1379
+ signatures: readonly Primitive[]
1519
1380
  }
1520
1381
 
1521
1382
  type ErrorType =
@@ -1588,6 +1449,7 @@ export function toRpc<const envelope extends toRpc.Input>(
1588
1449
 
1589
1450
  if (type === 'keychain') {
1590
1451
  const keychain = envelope as Keychain
1452
+ assertKeychainVersion(keychain)
1591
1453
  return {
1592
1454
  type: 'keychain',
1593
1455
  userAddress: keychain.userAddress,
@@ -1598,13 +1460,38 @@ export function toRpc<const envelope extends toRpc.Input>(
1598
1460
  }
1599
1461
 
1600
1462
  if (type === 'multisig') {
1601
- const multisig = envelope as Multisig
1602
- assert(multisig)
1603
- return {
1604
- account: multisig.account,
1605
- config: MultisigConfig.toRpc(multisig.config),
1606
- signatures: multisig.signatures.map((signature) => toRpc(signature)),
1607
- } 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
1608
1495
  }
1609
1496
 
1610
1497
  throw new CoercionError({ envelope })
@@ -1631,7 +1518,8 @@ export declare namespace toRpc {
1631
1518
  type ErrorType =
1632
1519
  | assert.ErrorType
1633
1520
  | CoercionError
1634
- | MultisigConfig.toRpc.ErrorType
1521
+ | Hex.slice.ErrorType
1522
+ | serialize.ErrorType
1635
1523
  | Signature.toRpc.ErrorType
1636
1524
  | Errors.GlobalErrorType
1637
1525
  }