ox 1.6.2 → 1.7.0

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 (80) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/core/AesGcm.d.ts +67 -2
  3. package/dist/core/AesGcm.d.ts.map +1 -1
  4. package/dist/core/AesGcm.js +79 -10
  5. package/dist/core/AesGcm.js.map +1 -1
  6. package/dist/core/Ed25519.d.ts +83 -2
  7. package/dist/core/Ed25519.d.ts.map +1 -1
  8. package/dist/core/Ed25519.js +79 -4
  9. package/dist/core/Ed25519.js.map +1 -1
  10. package/dist/core/MlDsa44.d.ts +84 -5
  11. package/dist/core/MlDsa44.d.ts.map +1 -1
  12. package/dist/core/MlDsa44.js +79 -4
  13. package/dist/core/MlDsa44.js.map +1 -1
  14. package/dist/core/P256.d.ts +82 -1
  15. package/dist/core/P256.d.ts.map +1 -1
  16. package/dist/core/P256.js +78 -0
  17. package/dist/core/P256.js.map +1 -1
  18. package/dist/core/Secp256k1.d.ts +85 -1
  19. package/dist/core/Secp256k1.d.ts.map +1 -1
  20. package/dist/core/Secp256k1.js +74 -3
  21. package/dist/core/Secp256k1.js.map +1 -1
  22. package/dist/core/internal/keyDerivation.d.ts +13 -0
  23. package/dist/core/internal/keyDerivation.d.ts.map +1 -0
  24. package/dist/core/internal/keyDerivation.js +13 -0
  25. package/dist/core/internal/keyDerivation.js.map +1 -0
  26. package/dist/tempo/KeyAuthorization.d.ts +16 -12
  27. package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
  28. package/dist/tempo/KeyAuthorization.js +7 -13
  29. package/dist/tempo/KeyAuthorization.js.map +1 -1
  30. package/dist/tempo/MultisigConfig.d.ts +20 -17
  31. package/dist/tempo/MultisigConfig.d.ts.map +1 -1
  32. package/dist/tempo/MultisigConfig.js +17 -16
  33. package/dist/tempo/MultisigConfig.js.map +1 -1
  34. package/dist/tempo/SignatureEnvelope.d.ts +19 -17
  35. package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
  36. package/dist/tempo/SignatureEnvelope.js +35 -27
  37. package/dist/tempo/SignatureEnvelope.js.map +1 -1
  38. package/dist/tempo/index.d.ts +2 -2
  39. package/dist/tempo/index.js +2 -2
  40. package/dist/zod/tempo/KeyAuthorization.d.ts +231 -22
  41. package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
  42. package/dist/zod/tempo/KeyAuthorization.js +10 -2
  43. package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
  44. package/dist/zod/tempo/RpcSchemaTempo.d.ts +82 -8
  45. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  46. package/dist/zod/tempo/Transaction.d.ts +246 -24
  47. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  48. package/dist/zod/tempo/TransactionRequest.d.ts +149 -14
  49. package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
  50. package/dist/zod/tempo/TxEnvelopeTempo.d.ts +78 -6
  51. package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  52. package/package.json +1 -1
  53. package/src/core/AesGcm.ts +137 -30
  54. package/src/core/Ed25519.ts +135 -4
  55. package/src/core/MlDsa44.ts +135 -4
  56. package/src/core/P256.ts +135 -1
  57. package/src/core/Secp256k1.ts +134 -3
  58. package/src/core/_test/AesGcm.test-d.ts +19 -0
  59. package/src/core/_test/AesGcm.test.ts +90 -1
  60. package/src/core/_test/Ed25519.test-d.ts +24 -0
  61. package/src/core/_test/Ed25519.test.ts +87 -1
  62. package/src/core/_test/MlDsa44.test-d.ts +24 -0
  63. package/src/core/_test/MlDsa44.test.ts +69 -1
  64. package/src/core/_test/P256.test-d.ts +26 -0
  65. package/src/core/_test/P256.test.ts +98 -1
  66. package/src/core/_test/Secp256k1.test-d.ts +27 -0
  67. package/src/core/_test/Secp256k1.test.ts +112 -1
  68. package/src/core/internal/keyDerivation.ts +34 -0
  69. package/src/tempo/KeyAuthorization.test-d.ts +41 -6
  70. package/src/tempo/KeyAuthorization.test.ts +77 -18
  71. package/src/tempo/KeyAuthorization.ts +36 -36
  72. package/src/tempo/MultisigConfig.test.ts +28 -8
  73. package/src/tempo/MultisigConfig.ts +28 -19
  74. package/src/tempo/SignatureEnvelope.test.ts +64 -19
  75. package/src/tempo/SignatureEnvelope.ts +45 -35
  76. package/src/tempo/index.ts +2 -2
  77. package/src/tempo/multisig.e2e.test.ts +402 -145
  78. package/src/version.ts +1 -1
  79. package/src/zod/tempo/KeyAuthorization.ts +12 -2
  80. package/src/zod/tempo/_test/KeyAuthorization.test.ts +19 -5
@@ -78,11 +78,22 @@ export type KeyAuthorization<
78
78
  | {}
79
79
  > &
80
80
  (signed extends true
81
- ? { signature: SignatureEnvelope.Primitive<numberType> }
81
+ ? { signature: Signature<numberType> }
82
82
  : {
83
- signature?: SignatureEnvelope.Primitive<numberType> | undefined
83
+ signature?: Signature<numberType> | undefined
84
84
  })
85
85
 
86
+ /** Signature that can authorize an access key. */
87
+ export type Signature<numberType = number> = OneOf<
88
+ | SignatureEnvelope.Primitive<numberType>
89
+ | SignatureEnvelope.Multisig<numberType>
90
+ >
91
+
92
+ /** RPC-formatted signature that can authorize an access key. */
93
+ export type SignatureRpc = OneOf<
94
+ SignatureEnvelope.PrimitiveRpc | SignatureEnvelope.MultisigRpc
95
+ >
96
+
86
97
  /** Input type for a Key Authorization. */
87
98
  export type Input = KeyAuthorization<false, bigint, number>
88
99
 
@@ -104,8 +115,8 @@ export type Rpc = {
104
115
  keyType: SignatureEnvelope.Type
105
116
  /** Token spending limits. */
106
117
  limits?: readonly RpcTokenLimit[] | null | undefined
107
- /** Primitive signature authorizing this key. */
108
- signature: SignatureEnvelope.PrimitiveRpc
118
+ /** Signature authorizing this key. */
119
+ signature: SignatureRpc
109
120
  /** Optional 32-byte witness (hex). */
110
121
  witness?: Hex.Hex | null | undefined
111
122
  }
@@ -136,10 +147,11 @@ export type Signed<bigintType = bigint, numberType = number> = KeyAuthorization<
136
147
  numberType
137
148
  >
138
149
 
139
- type PrimitiveSignatureValue =
140
- | UnionPartialBy<SignatureEnvelope.Primitive, 'prehash' | 'type'>
150
+ type SignatureValue =
151
+ | UnionPartialBy<Signature, 'prehash' | 'type'>
141
152
  | SignatureEnvelope.Secp256k1Flat
142
153
  | SignatureEnvelope.Serialized
154
+ | SignatureEnvelope.from.MultisigFromInitialConfig
143
155
 
144
156
  type BaseTuple = readonly [
145
157
  chainId: Hex.Hex,
@@ -415,7 +427,7 @@ export type TokenLimit<bigintType = bigint, numberType = number> = {
415
427
  */
416
428
  export function from<
417
429
  const authorization extends Input | Rpc,
418
- const signature extends PrimitiveSignatureValue | undefined = undefined,
430
+ const signature extends SignatureValue | undefined = undefined,
419
431
  >(
420
432
  authorization: authorization | KeyAuthorization,
421
433
  options: from.Options<signature> = {},
@@ -457,28 +469,24 @@ export function from<
457
469
 
458
470
  export declare namespace from {
459
471
  type Options<
460
- signature extends PrimitiveSignatureValue | undefined =
461
- | PrimitiveSignatureValue
462
- | undefined,
472
+ signature extends SignatureValue | undefined = SignatureValue | undefined,
463
473
  > = {
464
- /** The primitive signature to attach to the Key Authorization. */
465
- signature?: signature | SignatureEnvelope.Primitive | undefined
474
+ /** The signature to attach to the Key Authorization. */
475
+ signature?: signature | Signature | undefined
466
476
  }
467
477
 
468
478
  type ReturnType<
469
479
  authorization extends KeyAuthorization | Input | Rpc = KeyAuthorization,
470
- signature extends PrimitiveSignatureValue | undefined =
471
- | PrimitiveSignatureValue
472
- | undefined,
480
+ signature extends SignatureValue | undefined = SignatureValue | undefined,
473
481
  > = Compute<
474
482
  authorization extends Rpc
475
483
  ? Signed
476
484
  : authorization &
477
- (signature extends PrimitiveSignatureValue
485
+ (signature extends SignatureValue
478
486
  ? {
479
487
  signature: Extract<
480
488
  SignatureEnvelope.from.ReturnValue<signature>,
481
- SignatureEnvelope.Primitive
489
+ Signature
482
490
  >
483
491
  }
484
492
  : {})
@@ -544,11 +552,8 @@ export function fromRpc(authorization: Rpc): Signed {
544
552
  })
545
553
  : undefined
546
554
 
547
- // TIP-1049 admin fields are paired: emit both or neither; orphan wire
548
- // fields are dropped. Separate conditional spreads break `Signed`
549
- // assignability without `exactOptionalPropertyTypes` (#290).
550
- const adminPair =
551
- account !== undefined && isAdmin ? { account, isAdmin: true as const } : {}
555
+ const accountBinding =
556
+ account !== undefined ? { account, isAdmin: isAdmin ?? false } : {}
552
557
 
553
558
  return {
554
559
  address: keyId,
@@ -565,7 +570,7 @@ export function fromRpc(authorization: Rpc): Signed {
565
570
  signature,
566
571
  type: keyType,
567
572
  ...(witness !== undefined ? { witness } : {}),
568
- ...adminPair,
573
+ ...accountBinding,
569
574
  }
570
575
  }
571
576
 
@@ -697,11 +702,8 @@ export function fromTuple<const tuple extends Tuple>(
697
702
  const account = isAbsent(rawAccount)
698
703
  ? undefined
699
704
  : (rawAccount as Address.Address)
700
- // TIP-1049 admin fields are paired: only emit both when both are present on
701
- // the wire. Wire shapes carrying only one are tolerated for forward-compat
702
- // but the orphan field is dropped (since the public API requires both).
703
- const adminPair =
704
- account !== undefined && isAdmin ? { account, isAdmin: true as const } : {}
705
+ const accountBinding =
706
+ account !== undefined ? { account, isAdmin: isAdmin ?? false } : {}
705
707
  const args: KeyAuthorization = {
706
708
  address: keyId,
707
709
  chainId: chainId === '0x' ? 0n : Hex.toBigInt(chainId),
@@ -710,7 +712,7 @@ export function fromTuple<const tuple extends Tuple>(
710
712
  ...(limits !== undefined ? { limits } : {}),
711
713
  ...(scopes !== undefined ? { scopes } : {}),
712
714
  ...(witness !== undefined ? { witness } : {}),
713
- ...adminPair,
715
+ ...accountBinding,
714
716
  }
715
717
  if (signatureSerialized) {
716
718
  const signature = SignatureEnvelope.deserialize(signatureSerialized)
@@ -982,9 +984,7 @@ export function toRpc(authorization: toRpc.Input): Rpc {
982
984
  limit: Quantity.fromNumberish(limit),
983
985
  ...(period ? { period: Quantity.fromNumberish(period) } : {}),
984
986
  })),
985
- signature: SignatureEnvelope.toRpc(
986
- signature,
987
- ) as SignatureEnvelope.PrimitiveRpc,
987
+ signature: SignatureEnvelope.toRpc(signature) as SignatureRpc,
988
988
  ...(allowedCalls ? { allowedCalls } : {}),
989
989
  ...(witness !== undefined ? { witness } : {}),
990
990
  ...(isAdmin ? { isAdmin: true } : {}),
@@ -1186,8 +1186,8 @@ function assertWitness(witness: Hex.Hex): void {
1186
1186
 
1187
1187
  function assertSignature<numberType>(
1188
1188
  signature: SignatureEnvelope.SignatureEnvelope<numberType>,
1189
- ): asserts signature is SignatureEnvelope.Primitive<numberType> {
1190
- if (signature.type === 'keychain' || signature.type === 'multisig')
1189
+ ): asserts signature is Signature<numberType> {
1190
+ if (signature.type === 'keychain')
1191
1191
  throw new InvalidSignatureTypeError(signature.type)
1192
1192
  }
1193
1193
 
@@ -1215,12 +1215,12 @@ export class InvalidAdminMarkerError extends Error {
1215
1215
  }
1216
1216
  }
1217
1217
 
1218
- /** Thrown when a key authorization contains a non-primitive signature. */
1218
+ /** Thrown when a key authorization contains a keychain signature. */
1219
1219
  export class InvalidSignatureTypeError extends Error {
1220
1220
  override readonly name = 'KeyAuthorization.InvalidSignatureTypeError'
1221
1221
  constructor(type: SignatureEnvelope.SignatureEnvelope['type']) {
1222
1222
  super(
1223
- `Signature type \`${type}\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.`,
1223
+ `Signature type \`${type}\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.`,
1224
1224
  )
1225
1225
  }
1226
1226
  }
@@ -86,24 +86,44 @@ describe('getSignPayload', () => {
86
86
  test('matches independent ground truth', () => {
87
87
  expect(
88
88
  MultisigConfig.getSignPayload({
89
- payload: `0x${'de'.repeat(32)}`,
90
- genesisConfig: singleOwnerConfig,
89
+ payload: `0x${'42'.repeat(32)}`,
90
+ initialConfig: singleOwnerConfig,
91
91
  }),
92
92
  ).toMatchInlineSnapshot(
93
- `"0x7df8cb9fef4fc2aeb271b617d1a4c6178b720ae1d7564f48a363069b7d77a079"`,
93
+ `"0xbf944a7a752b2cfab0418d5fb4591c5a7ff62976488edce11794d7f35fb34f41"`,
94
94
  )
95
95
  })
96
96
 
97
- test('behavior: `genesisConfig` and `{ account }` produce identical digests', () => {
97
+ test('behavior: `initialConfig` and `{ account }` produce identical digests', () => {
98
98
  const account = MultisigConfig.getAddress(singleOwnerConfig)
99
99
  const payload = `0x${'42'.repeat(32)}` as const
100
100
  expect(
101
101
  MultisigConfig.getSignPayload({
102
102
  payload,
103
- genesisConfig: singleOwnerConfig,
103
+ initialConfig: singleOwnerConfig,
104
104
  }),
105
105
  ).toBe(MultisigConfig.getSignPayload({ payload, account }))
106
106
  })
107
+
108
+ test('differs for a different config version', () => {
109
+ const value = {
110
+ payload: `0x${'42'.repeat(32)}` as const,
111
+ initialConfig: singleOwnerConfig,
112
+ }
113
+ expect(MultisigConfig.getSignPayload(value)).not.toBe(
114
+ MultisigConfig.getSignPayload({ ...value, version: 1n }),
115
+ )
116
+ })
117
+
118
+ test('defaults the config version to zero', () => {
119
+ const value = {
120
+ payload: `0x${'42'.repeat(32)}` as const,
121
+ initialConfig: singleOwnerConfig,
122
+ }
123
+ expect(MultisigConfig.getSignPayload(value)).toBe(
124
+ MultisigConfig.getSignPayload({ ...value, version: 0n }),
125
+ )
126
+ })
107
127
  })
108
128
 
109
129
  describe('toTuple / fromTuple', () => {
@@ -149,8 +169,8 @@ describe('assert / validate', () => {
149
169
  expect(MultisigConfig.validate({ threshold: 1, owners: [] })).toBe(false)
150
170
  })
151
171
 
152
- test('accepts 255 owners', () => {
153
- const owners = Array.from({ length: 255 }, (_, i) => ({
172
+ test('accepts 50 owners', () => {
173
+ const owners = Array.from({ length: 50 }, (_, i) => ({
154
174
  owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
155
175
  weight: 1,
156
176
  }))
@@ -163,7 +183,7 @@ describe('assert / validate', () => {
163
183
  })
164
184
 
165
185
  test('too many owners', () => {
166
- const owners = Array.from({ length: 256 }, (_, i) => ({
186
+ const owners = Array.from({ length: 51 }, (_, i) => ({
167
187
  owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
168
188
  weight: 1,
169
189
  }))
@@ -6,7 +6,7 @@ import * as Hex from '../core/Hex.js'
6
6
  import type { Compute, OneOf } from '../core/internal/types.js'
7
7
 
8
8
  /** Maximum number of owners allowed in a native multisig config. */
9
- export const maxOwners = 255
9
+ export const maxOwners = 50
10
10
 
11
11
  /** Maximum threshold accepted by a native multisig config. */
12
12
  export const maxThreshold = 8
@@ -20,7 +20,7 @@ export const maxSignatures = maxThreshold
20
20
  */
21
21
  export const maxNestingDepth = 2
22
22
 
23
- /** Maximum encoded byte length for one owner approval. */
23
+ /** Maximum encoded byte length for one primitive owner approval. */
24
24
  export const maxOwnerSignatureBytes = 2049
25
25
 
26
26
  /** Tempo signature type byte for native multisig signatures. */
@@ -233,7 +233,7 @@ export function fromTuple(tuple: Tuple): Config {
233
233
  * ```ts twoslash
234
234
  * import { MultisigConfig } from 'ox/tempo'
235
235
  *
236
- * const genesisConfig = MultisigConfig.from({
236
+ * const initialConfig = MultisigConfig.from({
237
237
  * threshold: 1,
238
238
  * owners: [
239
239
  * {
@@ -243,7 +243,7 @@ export function fromTuple(tuple: Tuple): Config {
243
243
  * ]
244
244
  * })
245
245
  *
246
- * const address = MultisigConfig.getAddress(genesisConfig)
246
+ * const address = MultisigConfig.getAddress(initialConfig)
247
247
  * ```
248
248
  *
249
249
  * @param config - The initial (bootstrap) multisig config.
@@ -284,13 +284,13 @@ export declare namespace getAddress {
284
284
  /**
285
285
  * Computes the digest a native multisig owner approves (signs).
286
286
  *
287
- * `keccak256("tempo:multisig:signature" || inner_digest || account)`,
287
+ * `keccak256("tempo:multisig:signature" || inner_digest || account || uint64be(version))`,
288
288
  * where `inner_digest` is the transaction sign payload
289
289
  * ({@link ox#TxEnvelopeTempo.(getSignPayload:function)}).
290
290
  *
291
- * The digest is keyed on the permanent `account` derived from the genesis
292
- * (bootstrap) config config updates never change it, so the genesis config
293
- * is the correct input even for post-update transactions.
291
+ * The digest is keyed on the permanent `account` derived from the initial
292
+ * (bootstrap) config and the current config `version`. Bootstrap approvals use
293
+ * version `0n`, which is also the default; each config update increments it.
294
294
  *
295
295
  * For a nested multisig owner approval, the parent digest becomes the nested
296
296
  * approval's `payload`, with the nested multisig `account`.
@@ -299,7 +299,7 @@ export declare namespace getAddress {
299
299
  * ```ts twoslash
300
300
  * import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
301
301
  *
302
- * const genesisConfig = MultisigConfig.from({
302
+ * const initialConfig = MultisigConfig.from({
303
303
  * threshold: 1,
304
304
  * owners: [
305
305
  * {
@@ -316,7 +316,7 @@ export declare namespace getAddress {
316
316
  *
317
317
  * const digest = MultisigConfig.getSignPayload({
318
318
  * payload: TxEnvelopeTempo.getSignPayload(envelope),
319
- * genesisConfig
319
+ * initialConfig
320
320
  * })
321
321
  * ```
322
322
  *
@@ -329,7 +329,7 @@ export declare namespace getAddress {
329
329
  * ```ts twoslash
330
330
  * import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
331
331
  *
332
- * const genesisConfig = MultisigConfig.from({
332
+ * const initialConfig = MultisigConfig.from({
333
333
  * threshold: 1,
334
334
  * owners: [
335
335
  * {
@@ -338,7 +338,7 @@ export declare namespace getAddress {
338
338
  * }
339
339
  * ]
340
340
  * })
341
- * const account = MultisigConfig.getAddress(genesisConfig)
341
+ * const account = MultisigConfig.getAddress(initialConfig)
342
342
  *
343
343
  * const envelope = TxEnvelopeTempo.from({
344
344
  * chainId: 1,
@@ -347,7 +347,8 @@ export declare namespace getAddress {
347
347
  *
348
348
  * const digest = MultisigConfig.getSignPayload({
349
349
  * payload: TxEnvelopeTempo.getSignPayload(envelope),
350
- * account
350
+ * account,
351
+ * version: 1n
351
352
  * })
352
353
  * ```
353
354
  *
@@ -355,13 +356,18 @@ export declare namespace getAddress {
355
356
  * @returns The owner approval digest.
356
357
  */
357
358
  export function getSignPayload(value: getSignPayload.Value): Hex.Hex {
358
- const { payload } = value
359
+ const { payload, version = 0n } = value
359
360
  const account =
360
361
  'account' in value && value.account
361
362
  ? value.account
362
- : getAddress((value as { genesisConfig: Config }).genesisConfig)
363
+ : getAddress((value as { initialConfig: Config }).initialConfig)
363
364
  return Hash.keccak256(
364
- Hex.concat(Hex.fromString(signatureDomain), Hex.from(payload), account),
365
+ Hex.concat(
366
+ Hex.fromString(signatureDomain),
367
+ Hex.from(payload),
368
+ account,
369
+ Hex.fromNumber(version, { size: 8 }),
370
+ ),
365
371
  )
366
372
  }
367
373
 
@@ -369,6 +375,8 @@ export declare namespace getSignPayload {
369
375
  type Value = {
370
376
  /** The inner transaction sign payload (`tx.signature_hash()`). */
371
377
  payload: Hex.Hex | Bytes.Bytes
378
+ /** Current multisig config version. Defaults to `0n`. */
379
+ version?: bigint | undefined
372
380
  } & OneOf<
373
381
  | {
374
382
  /** The native multisig account address. */
@@ -378,10 +386,10 @@ export declare namespace getSignPayload {
378
386
  /**
379
387
  * The initial multisig config (the bootstrap config that derived the
380
388
  * permanent `account`). Used to derive the account automatically.
381
- * Config updates never change `account`, so the genesis config is
382
- * also the correct input for post-update transactions.
389
+ * Config updates never change `account`, so the initial config can
390
+ * continue deriving the account for post-update transactions.
383
391
  */
384
- genesisConfig: Config
392
+ initialConfig: Config
385
393
  }
386
394
  >
387
395
 
@@ -390,6 +398,7 @@ export declare namespace getSignPayload {
390
398
  | Hash.keccak256.ErrorType
391
399
  | Hex.concat.ErrorType
392
400
  | Hex.from.ErrorType
401
+ | Hex.fromNumber.ErrorType
393
402
  | Errors.GlobalErrorType
394
403
  }
395
404
 
@@ -1116,7 +1116,7 @@ describe('from', () => {
1116
1116
  })
1117
1117
 
1118
1118
  describe('multisig', () => {
1119
- const genesisConfig = MultisigConfig.from({
1119
+ const initialConfig = MultisigConfig.from({
1120
1120
  threshold: 1,
1121
1121
  owners: [
1122
1122
  {
@@ -1126,29 +1126,29 @@ describe('from', () => {
1126
1126
  ],
1127
1127
  })
1128
1128
 
1129
- test('behavior: derives `account` from `genesisConfig`', () => {
1129
+ test('behavior: derives `account` from `initialConfig`', () => {
1130
1130
  const envelope = SignatureEnvelope.from({
1131
- genesisConfig,
1131
+ initialConfig,
1132
1132
  signatures: [SignatureEnvelope.from(signature_secp256k1)],
1133
1133
  })
1134
1134
 
1135
1135
  expect(envelope).toMatchObject({
1136
1136
  type: 'multisig',
1137
- account: MultisigConfig.getAddress(genesisConfig),
1137
+ account: MultisigConfig.getAddress(initialConfig),
1138
1138
  })
1139
- expect('genesisConfig' in envelope).toBe(false)
1139
+ expect('initialConfig' in envelope).toBe(false)
1140
1140
  expect((envelope as SignatureEnvelope.Multisig).init).toBeUndefined()
1141
1141
  })
1142
1142
 
1143
- test('behavior: `init: true` opts into bootstrap (uses `genesisConfig` as `init`)', () => {
1143
+ test('behavior: `init: true` opts into bootstrap (uses `initialConfig` as `init`)', () => {
1144
1144
  const envelope = SignatureEnvelope.from({
1145
- genesisConfig,
1145
+ initialConfig,
1146
1146
  signatures: [SignatureEnvelope.from(signature_secp256k1)],
1147
1147
  init: true,
1148
1148
  })
1149
1149
 
1150
1150
  expect((envelope as SignatureEnvelope.Multisig).init).toEqual(
1151
- genesisConfig,
1151
+ initialConfig,
1152
1152
  )
1153
1153
  })
1154
1154
 
@@ -1163,7 +1163,7 @@ describe('from', () => {
1163
1163
  ],
1164
1164
  })
1165
1165
  const envelope = SignatureEnvelope.from({
1166
- genesisConfig,
1166
+ initialConfig,
1167
1167
  signatures: [SignatureEnvelope.from(signature_secp256k1)],
1168
1168
  init: otherConfig,
1169
1169
  })
@@ -1172,7 +1172,7 @@ describe('from', () => {
1172
1172
  })
1173
1173
 
1174
1174
  test('behavior: `{ account }` form still works', () => {
1175
- const account = MultisigConfig.getAddress(genesisConfig)
1175
+ const account = MultisigConfig.getAddress(initialConfig)
1176
1176
  const envelope = SignatureEnvelope.from({
1177
1177
  account,
1178
1178
  signatures: [SignatureEnvelope.from(signature_secp256k1)],
@@ -1793,7 +1793,7 @@ describe('serialize', () => {
1793
1793
  })
1794
1794
 
1795
1795
  describe('sortMultisigApprovals', () => {
1796
- // Build owner key pairs first so we can construct a real genesis config
1796
+ // Build owner key pairs first so we can construct a real initial config
1797
1797
  // whose owner set matches the keys that produce the approvals below.
1798
1798
  const ownerKeys = Array.from({ length: 3 }, () => {
1799
1799
  const privateKey = Secp256k1.randomPrivateKey()
@@ -1806,12 +1806,15 @@ describe('sortMultisigApprovals', () => {
1806
1806
  Hex.toBigInt(a.address) < Hex.toBigInt(b.address) ? -1 : 1,
1807
1807
  )
1808
1808
 
1809
- const genesisConfig = MultisigConfig.from({
1809
+ const initialConfig = MultisigConfig.from({
1810
1810
  threshold: 2,
1811
1811
  owners: ascendingOwners.map((o) => ({ owner: o.address, weight: 1 })),
1812
1812
  })
1813
1813
  const payload = `0x${'42'.repeat(32)}` as const
1814
- const digest = MultisigConfig.getSignPayload({ payload, genesisConfig })
1814
+ const digest = MultisigConfig.getSignPayload({
1815
+ payload,
1816
+ initialConfig,
1817
+ })
1815
1818
 
1816
1819
  const owners = ownerKeys.map((owner) => ({
1817
1820
  address: owner.address,
@@ -1826,7 +1829,7 @@ describe('sortMultisigApprovals', () => {
1826
1829
 
1827
1830
  test('behavior: orders approvals ascending by recovered owner address', () => {
1828
1831
  const ordered = SignatureEnvelope.sortMultisigApprovals({
1829
- genesisConfig,
1832
+ initialConfig,
1830
1833
  payload,
1831
1834
  // Provide approvals in reverse of the canonical order.
1832
1835
  signatures: [...ascending].reverse().map((owner) => owner.signature),
@@ -1838,7 +1841,7 @@ describe('sortMultisigApprovals', () => {
1838
1841
  const signatures = ascending.map((owner) => owner.signature)
1839
1842
  expect(
1840
1843
  SignatureEnvelope.sortMultisigApprovals({
1841
- genesisConfig,
1844
+ initialConfig,
1842
1845
  payload,
1843
1846
  signatures,
1844
1847
  }),
@@ -1847,7 +1850,7 @@ describe('sortMultisigApprovals', () => {
1847
1850
 
1848
1851
  test('behavior: recovered order matches the config owner order', () => {
1849
1852
  const ordered = SignatureEnvelope.sortMultisigApprovals({
1850
- genesisConfig,
1853
+ initialConfig,
1851
1854
  payload,
1852
1855
  signatures: owners.map((owner) => owner.signature),
1853
1856
  })
@@ -1857,12 +1860,12 @@ describe('sortMultisigApprovals', () => {
1857
1860
  expect(recovered).toEqual(ascending.map((owner) => owner.address))
1858
1861
  })
1859
1862
 
1860
- test('behavior: `genesisConfig` and `{ account }` produce identical ordering', () => {
1861
- const account = MultisigConfig.getAddress(genesisConfig)
1863
+ test('behavior: `initialConfig` and `{ account }` produce identical ordering', () => {
1864
+ const account = MultisigConfig.getAddress(initialConfig)
1862
1865
  const signatures = owners.map((owner) => owner.signature)
1863
1866
 
1864
1867
  const fromConfig = SignatureEnvelope.sortMultisigApprovals({
1865
- genesisConfig,
1868
+ initialConfig,
1866
1869
  payload,
1867
1870
  signatures,
1868
1871
  })
@@ -3041,6 +3044,14 @@ describe('multisig', () => {
3041
3044
  ).toThrowErrorMatchingInlineSnapshot(
3042
3045
  `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig owner signature exceeds 2049 bytes.]`,
3043
3046
  )
3047
+
3048
+ const serialized = Hex.concat(
3049
+ '0x05',
3050
+ Rlp.fromHex([account, [SignatureEnvelope.serialize(oversized)]]),
3051
+ )
3052
+ expect(() => SignatureEnvelope.deserialize(serialized)).toThrowError(
3053
+ SignatureEnvelope.InvalidSerializedError,
3054
+ )
3044
3055
  })
3045
3056
 
3046
3057
  test('assert: rejects keychain owner approvals', () => {
@@ -3082,6 +3093,40 @@ describe('multisig', () => {
3082
3093
  expect(() => SignatureEnvelope.assert(depth2)).not.toThrowError()
3083
3094
  })
3084
3095
 
3096
+ test('accepts a nested approval above the primitive byte limit', () => {
3097
+ const primitive = SignatureEnvelope.from({
3098
+ ...signature_webauthn,
3099
+ metadata: {
3100
+ ...signature_webauthn.metadata,
3101
+ clientDataJSON: JSON.stringify({ value: 'x'.repeat(1_050) }),
3102
+ },
3103
+ signature: {
3104
+ r: signature_webauthn.signature.r,
3105
+ s: signature_webauthn.signature.s,
3106
+ },
3107
+ })
3108
+ const largeNested = SignatureEnvelope.from({
3109
+ type: 'multisig',
3110
+ account: nestedAccount,
3111
+ signatures: [primitive, primitive],
3112
+ })
3113
+ expect(Hex.size(SignatureEnvelope.serialize(primitive))).toBeLessThan(
3114
+ MultisigConfig.maxOwnerSignatureBytes,
3115
+ )
3116
+ expect(
3117
+ Hex.size(SignatureEnvelope.serialize(largeNested)),
3118
+ ).toBeGreaterThan(MultisigConfig.maxOwnerSignatureBytes)
3119
+
3120
+ const parent = SignatureEnvelope.from({
3121
+ type: 'multisig',
3122
+ account,
3123
+ signatures: [largeNested],
3124
+ })
3125
+ expect(
3126
+ SignatureEnvelope.deserialize(SignatureEnvelope.serialize(parent)),
3127
+ ).toEqual(parent)
3128
+ })
3129
+
3085
3130
  test('assert: rejects nesting deeper than the maximum', () => {
3086
3131
  const depth3 = SignatureEnvelope.from({
3087
3132
  type: 'multisig',