ox 0.14.43 → 0.14.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/_cjs/tempo/KeyAuthorization.js +43 -10
  3. package/_cjs/tempo/KeyAuthorization.js.map +1 -1
  4. package/_cjs/tempo/MultisigConfig.js +39 -10
  5. package/_cjs/tempo/MultisigConfig.js.map +1 -1
  6. package/_cjs/tempo/MultisigOperation.js +18 -94
  7. package/_cjs/tempo/MultisigOperation.js.map +1 -1
  8. package/_cjs/tempo/MultisigSimulation.js +18 -46
  9. package/_cjs/tempo/MultisigSimulation.js.map +1 -1
  10. package/_cjs/tempo/SignatureEnvelope.js +72 -109
  11. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  12. package/_cjs/tempo/TransactionRequest.js +5 -1
  13. package/_cjs/tempo/TransactionRequest.js.map +1 -1
  14. package/_cjs/tempo/index.js.map +1 -1
  15. package/_cjs/version.js +1 -1
  16. package/_esm/tempo/KeyAuthorization.js +42 -9
  17. package/_esm/tempo/KeyAuthorization.js.map +1 -1
  18. package/_esm/tempo/MultisigConfig.js +49 -20
  19. package/_esm/tempo/MultisigConfig.js.map +1 -1
  20. package/_esm/tempo/MultisigOperation.js +20 -100
  21. package/_esm/tempo/MultisigOperation.js.map +1 -1
  22. package/_esm/tempo/MultisigSimulation.js +18 -46
  23. package/_esm/tempo/MultisigSimulation.js.map +1 -1
  24. package/_esm/tempo/SignatureEnvelope.js +107 -169
  25. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  26. package/_esm/tempo/TransactionRequest.js +5 -1
  27. package/_esm/tempo/TransactionRequest.js.map +1 -1
  28. package/_esm/tempo/index.js +3 -1
  29. package/_esm/tempo/index.js.map +1 -1
  30. package/_esm/version.js +1 -1
  31. package/_types/tempo/KeyAuthorization.d.ts +48 -38
  32. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  33. package/_types/tempo/MultisigConfig.d.ts +18 -15
  34. package/_types/tempo/MultisigConfig.d.ts.map +1 -1
  35. package/_types/tempo/MultisigOperation.d.ts +3 -2
  36. package/_types/tempo/MultisigOperation.d.ts.map +1 -1
  37. package/_types/tempo/MultisigSimulation.d.ts +2 -49
  38. package/_types/tempo/MultisigSimulation.d.ts.map +1 -1
  39. package/_types/tempo/SignatureEnvelope.d.ts +69 -128
  40. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  41. package/_types/tempo/TransactionRequest.d.ts +5 -2
  42. package/_types/tempo/TransactionRequest.d.ts.map +1 -1
  43. package/_types/tempo/index.d.ts +3 -1
  44. package/_types/tempo/index.d.ts.map +1 -1
  45. package/_types/version.d.ts +1 -1
  46. package/package.json +1 -1
  47. package/tempo/AuthorizationTempo.test.ts +8 -4
  48. package/tempo/KeyAuthorization.test-d.ts +68 -33
  49. package/tempo/KeyAuthorization.test.ts +92 -11
  50. package/tempo/KeyAuthorization.ts +124 -61
  51. package/tempo/MultisigConfig.test.ts +44 -20
  52. package/tempo/MultisigConfig.ts +65 -23
  53. package/tempo/MultisigOperation.test.ts +79 -550
  54. package/tempo/MultisigOperation.ts +31 -146
  55. package/tempo/MultisigSimulation.test-d.ts +2 -2
  56. package/tempo/MultisigSimulation.test.ts +21 -110
  57. package/tempo/MultisigSimulation.ts +25 -99
  58. package/tempo/SignatureEnvelope.test-d.ts +29 -67
  59. package/tempo/SignatureEnvelope.test.ts +305 -572
  60. package/tempo/SignatureEnvelope.ts +156 -274
  61. package/tempo/Transaction.test.ts +4 -4
  62. package/tempo/TransactionRequest.test-d.ts +6 -5
  63. package/tempo/TransactionRequest.test.ts +17 -80
  64. package/tempo/TransactionRequest.ts +23 -3
  65. package/tempo/index.ts +3 -1
  66. package/tempo/multisig.e2e.test.ts +321 -860
  67. package/version.ts +1 -1
@@ -1,6 +1,7 @@
1
1
  import { Hash, Hex, Rlp } from 'ox'
2
2
  import { MultisigConfig } from 'ox/tempo'
3
3
  import { describe, expect, test } from 'vitest'
4
+ import { factory } from '../../test/tempo/multisig.js'
4
5
 
5
6
  const owner_1 = '0x1111111111111111111111111111111111111111'
6
7
  const owner_2 = '0x2222222222222222222222222222222222222222'
@@ -9,7 +10,7 @@ const config = MultisigConfig.from({
9
10
  owners: [{ owner: owner_1, weight: 1 }],
10
11
  threshold: 1,
11
12
  })
12
- const account = MultisigConfig.getAddress(config)
13
+ const account = MultisigConfig.getAddress(config, { factory })
13
14
 
14
15
  describe('from', () => {
15
16
  test('behavior: normalizes an initial configuration', () => {
@@ -99,35 +100,59 @@ describe('fromRpc/toRpc', () => {
99
100
  })
100
101
 
101
102
  describe('getAddress', () => {
102
- test('example: matches the frozen version-0 vector', () => {
103
+ test('example: matches the frozen CREATE2 vector', () => {
103
104
  expect(account).toMatchInlineSnapshot(
104
- `"0x8820d1497eeaf4f68e00b2cfc00a2f3b1dbb00da"`,
105
+ `"0x90ba71cb7534da990a1182cf28c9bf415055e03d"`,
105
106
  )
107
+ expect(account).not.toBe('0x8820d1497eeaf4f68e00b2cfc00a2f3b1dbb00da')
106
108
  })
107
109
 
108
110
  test('behavior: includes salt, threshold, and owners', () => {
109
111
  expect(
110
- MultisigConfig.getAddress({
111
- owners: [
112
- { owner: owner_1, weight: 1 },
113
- { owner: owner_2, weight: 2 },
114
- ],
115
- salt: `0x${'42'.repeat(32)}`,
116
- threshold: 2,
117
- }),
118
- ).toMatchInlineSnapshot(`"0x0773e28146400643e42cb28f6659b74e7c0b451d"`)
112
+ MultisigConfig.getAddress(
113
+ {
114
+ owners: [
115
+ { owner: owner_1, weight: 1 },
116
+ { owner: owner_2, weight: 2 },
117
+ ],
118
+ salt: `0x${'42'.repeat(32)}`,
119
+ threshold: 2,
120
+ },
121
+ { factory },
122
+ ),
123
+ ).toMatchInlineSnapshot(`"0x7898a7d1dd96946d2a968de1540ca884a244320c"`)
124
+ })
125
+
126
+ test('behavior: is stable and binds the salt', () => {
127
+ expect({
128
+ repeated: MultisigConfig.getAddress(config, { factory }),
129
+ salted: MultisigConfig.getAddress(
130
+ {
131
+ ...config,
132
+ salt: `0x${'42'.repeat(32)}`,
133
+ },
134
+ { factory },
135
+ ),
136
+ }).toMatchInlineSnapshot(`
137
+ {
138
+ "repeated": "0x90ba71cb7534da990a1182cf28c9bf415055e03d",
139
+ "salted": "0xa1a8472f4a01772db729d8ebc73fe46120750b83",
140
+ }
141
+ `)
119
142
  })
120
143
 
121
144
  test('error: rejects a current configuration', () => {
122
145
  expect(() =>
123
- MultisigConfig.getAddress({ ...config, version: 1n }),
146
+ MultisigConfig.getAddress({ ...config, version: 1n }, { factory }),
124
147
  ).toThrowErrorMatchingInlineSnapshot(
125
148
  `[MultisigConfig.InvalidConfigError: Invalid native multisig config: account address requires version zero.]`,
126
149
  )
127
150
  })
128
151
 
129
152
  test('behavior: accepts numeric zero for an initial configuration', () => {
130
- expect(MultisigConfig.getAddress({ ...config, version: 0 })).toBe(account)
153
+ expect(
154
+ MultisigConfig.getAddress({ ...config, version: 0 }, { factory }),
155
+ ).toBe(account)
131
156
  })
132
157
  })
133
158
 
@@ -152,7 +177,7 @@ describe('getSignPayload', () => {
152
177
  expect(
153
178
  MultisigConfig.getSignPayload({ account, config, payload }),
154
179
  ).toMatchInlineSnapshot(
155
- `"0xbf944a7a752b2cfab0418d5fb4591c5a7ff62976488edce11794d7f35fb34f41"`,
180
+ `"0x728efe90611b01c91c2be0da56e7eb8e0d2c274c84f97182e274187dde0b8a20"`,
156
181
  )
157
182
  })
158
183
 
@@ -207,14 +232,14 @@ describe('assert/validate', () => {
207
232
  })
208
233
  const rlp = Rlp.fromHex(MultisigConfig.toTuple(boundary))
209
234
  expect({
210
- account: MultisigConfig.getAddress(boundary),
235
+ account: MultisigConfig.getAddress(boundary, { factory }),
211
236
  commitment: MultisigConfig.getCommitment(boundary),
212
237
  rlpHash: Hash.keccak256(rlp),
213
238
  rlpLength: Hex.size(rlp),
214
239
  valid: MultisigConfig.validate(boundary),
215
240
  }).toMatchInlineSnapshot(`
216
241
  {
217
- "account": "0x6c67c57e0eed05341137dbf88e4e7a90dc46ef50",
242
+ "account": "0x6acb25715a2b0cf05c098f753a87d9b38471e99f",
218
243
  "commitment": "0x0dc47a7ab45ffa21a01bfd115427e26617b5a57d7ccbea57db2fd4537ba96f56",
219
244
  "rlpHash": "0xbaf0d030add91caaa10815d2e99c942f1e39b0d199216973781adb4fc1af6955",
220
245
  "rlpLength": 1145,
@@ -230,7 +255,7 @@ describe('assert/validate', () => {
230
255
  { owner: owner_1, weight: 128 },
231
256
  { owner: owner_2, weight: 127 },
232
257
  ],
233
- threshold: 255,
258
+ threshold: 8,
234
259
  }),
235
260
  ).toBe(true)
236
261
  })
@@ -296,7 +321,7 @@ describe('assert/validate', () => {
296
321
  { owner: owner_1, weight: 128 },
297
322
  { owner: owner_2, weight: 128 },
298
323
  ],
299
- threshold: 255,
324
+ threshold: 8,
300
325
  },
301
326
  name: 'weight overflow',
302
327
  },
@@ -343,7 +368,6 @@ describe('assert/validate', () => {
343
368
  test('exports', () => {
344
369
  expect(Object.keys(MultisigConfig)).toMatchInlineSnapshot(`
345
370
  [
346
- "maxNestingDepth",
347
371
  "maxOwnerSignatureBytes",
348
372
  "maxOwners",
349
373
  "maxSignatures",
@@ -1,16 +1,11 @@
1
1
  import * as Address from '../core/Address.js'
2
2
  import type * as Bytes from '../core/Bytes.js'
3
+ import * as ContractAddress from '../core/ContractAddress.js'
3
4
  import * as Errors from '../core/Errors.js'
4
5
  import * as Hash from '../core/Hash.js'
5
6
  import * as Hex from '../core/Hex.js'
6
7
  import type { Compute } from '../core/internal/types.js'
7
8
 
8
- /**
9
- * Maximum number of native multisig signatures in one nested authorization
10
- * path, including the top-level transaction signature.
11
- */
12
- export const maxNestingDepth = 2
13
-
14
9
  /** Maximum encoded byte length for one primitive owner approval. */
15
10
  export const maxOwnerSignatureBytes = 2049
16
11
 
@@ -21,7 +16,7 @@ export const maxOwners = 48
21
16
  export const maxSignatures = 8
22
17
 
23
18
  /** Maximum threshold accepted by a native multisig config. */
24
- export const maxThreshold = 0xff
19
+ export const maxThreshold = 8
25
20
 
26
21
  /** Maximum version accepted by a native multisig config. */
27
22
  export const maxVersion = 2n ** 64n - 1n
@@ -38,6 +33,10 @@ const accountDomain = 'tempo:multisig:account'
38
33
  /** Domain prefix for native multisig configuration commitments. */
39
34
  const configDomain = 'tempo:multisig:config'
40
35
 
36
+ /** Keccak-256 of the canonical recovery wallet creation code. */
37
+ const recoveryWalletInitCodeHash =
38
+ '0x583cc63a2e37f645b43eac911b1a6d6de08b83abdc308c61364edda8cfc3bd37'
39
+
41
40
  /** Domain prefix for native multisig owner approvals. */
42
41
  const signatureDomain = 'tempo:multisig:signature'
43
42
 
@@ -292,7 +291,33 @@ export declare namespace fromRpc {
292
291
  */
293
292
  export function fromTuple(tuple: Tuple): Config {
294
293
  const [salt, version, threshold, owners] = tuple
295
- return {
294
+ if (
295
+ tuple.length !== 4 ||
296
+ typeof salt !== 'string' ||
297
+ Hex.size(salt) !== 32 ||
298
+ typeof version !== 'string' ||
299
+ Hex.size(version) > 8 ||
300
+ version.startsWith('0x00') ||
301
+ typeof threshold !== 'string' ||
302
+ Hex.size(threshold) > 1 ||
303
+ threshold.startsWith('0x00') ||
304
+ !Array.isArray(owners) ||
305
+ owners.some(
306
+ (owner) =>
307
+ !Array.isArray(owner) ||
308
+ owner.length !== 2 ||
309
+ typeof owner[0] !== 'string' ||
310
+ !Address.validate(owner[0]) ||
311
+ typeof owner[1] !== 'string' ||
312
+ Hex.size(owner[1] as Hex.Hex) > 1 ||
313
+ owner[1].startsWith('0x00'),
314
+ )
315
+ )
316
+ throw new InvalidConfigError({ reason: 'invalid config RLP tuple' })
317
+ const config = {
318
+ salt: salt && salt !== '0x' ? Hex.padLeft(salt, 32) : zeroSalt,
319
+ version: version === '0x' ? 0n : Hex.toBigInt(version),
320
+ threshold: threshold === '0x' ? 0 : Hex.toNumber(threshold),
296
321
  owners: owners.map((owner) => {
297
322
  const [ownerAddress, weight] = owner as readonly Hex.Hex[]
298
323
  return {
@@ -300,17 +325,17 @@ export function fromTuple(tuple: Tuple): Config {
300
325
  weight: !weight || weight === '0x' ? 0 : Hex.toNumber(weight),
301
326
  }
302
327
  }),
303
- salt: salt && salt !== '0x' ? Hex.padLeft(salt, 32) : zeroSalt,
304
- threshold: threshold === '0x' ? 0 : Hex.toNumber(threshold),
305
- version: version === '0x' ? 0n : Hex.toBigInt(version),
306
328
  }
329
+ assert(config)
330
+ return config
307
331
  }
308
332
 
309
333
  /**
310
334
  * Derives the stable native multisig account address.
311
335
  *
312
- * Preimage (fixed-width big-endian, **not** RLP):
313
- * `keccak256("tempo:multisig:account" || salt || u8(threshold) || u8(owners.length) || (owner || u8(weight)) for each owner)[12:32]`.
336
+ * The initial config is hashed into a CREATE2 salt using fixed-width
337
+ * big-endian fields, not RLP. The account uses the chain-configured recovery
338
+ * factory and wallet init-code hash.
314
339
  *
315
340
  * The address is derived once from the initial version-0 config. Config
316
341
  * updates do not change it.
@@ -326,19 +351,26 @@ export function fromTuple(tuple: Tuple): Config {
326
351
  * threshold: 1,
327
352
  * })
328
353
  *
329
- * const address = MultisigConfig.getAddress(initialConfig)
354
+ * const address = MultisigConfig.getAddress(initialConfig, {
355
+ * factory: '0x7171717171717171717171717171717171717171'
356
+ * })
330
357
  * ```
331
358
  *
332
359
  * @param config - The initial multisig config.
360
+ * @param options - The recovery factory configured by the chain.
333
361
  * @returns The multisig account address.
334
362
  */
335
- export function getAddress(config: Input): Address.Address {
363
+ export function getAddress(
364
+ config: Input,
365
+ options: getAddress.Options,
366
+ ): Address.Address {
367
+ Address.assert(options.factory)
336
368
  assert(config)
337
369
  if (BigInt(config.version ?? 0) !== 0n)
338
370
  throw new InvalidConfigError({
339
371
  reason: 'account address requires version zero',
340
372
  })
341
- const hash = Hash.keccak256(
373
+ const accountSalt = Hash.keccak256(
342
374
  Hex.concat(
343
375
  Hex.fromString(accountDomain),
344
376
  Hex.padLeft(config.salt ?? zeroSalt, 32),
@@ -350,7 +382,11 @@ export function getAddress(config: Input): Address.Address {
350
382
  ]),
351
383
  ),
352
384
  )
353
- const account = Address.from(Hex.slice(hash, 12, 32))
385
+ const account = ContractAddress.fromCreate2({
386
+ bytecodeHash: recoveryWalletInitCodeHash,
387
+ from: options.factory,
388
+ salt: accountSalt,
389
+ })
354
390
  if (Hex.toBigInt(account) === 0n)
355
391
  throw new InvalidConfigError({ reason: 'derived account cannot be zero' })
356
392
  if (config.owners.some((owner) => Address.isEqual(owner.owner, account)))
@@ -361,14 +397,18 @@ export function getAddress(config: Input): Address.Address {
361
397
  }
362
398
 
363
399
  export declare namespace getAddress {
400
+ type Options = {
401
+ /** Recovery factory configured by the chain. */
402
+ factory: Address.Address
403
+ }
404
+
364
405
  type ErrorType =
365
406
  | assert.ErrorType
366
- | Address.from.ErrorType
407
+ | ContractAddress.fromCreate2.ErrorType
367
408
  | Hash.keccak256.ErrorType
368
409
  | Hex.concat.ErrorType
369
410
  | Hex.fromNumber.ErrorType
370
411
  | Hex.fromString.ErrorType
371
- | Hex.slice.ErrorType
372
412
  | Errors.GlobalErrorType
373
413
  }
374
414
 
@@ -432,9 +472,6 @@ export declare namespace getCommitment {
432
472
  * version. Initial approvals use version `0n`; each config update increments
433
473
  * it.
434
474
  *
435
- * For a nested multisig owner approval, the parent digest becomes the nested
436
- * approval's `payload`, with the nested multisig `account`.
437
- *
438
475
  * @example
439
476
  * ```ts twoslash
440
477
  * import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
@@ -452,7 +489,9 @@ export declare namespace getCommitment {
452
489
  * })
453
490
  *
454
491
  * const digest = MultisigConfig.getSignPayload({
455
- * account: MultisigConfig.getAddress(config),
492
+ * account: MultisigConfig.getAddress(config, {
493
+ * factory: '0x7171717171717171717171717171717171717171'
494
+ * }),
456
495
  * config,
457
496
  * payload: TxEnvelopeTempo.getSignPayload(envelope),
458
497
  * })
@@ -464,6 +503,9 @@ export declare namespace getCommitment {
464
503
  export function getSignPayload(value: getSignPayload.Value): Hex.Hex {
465
504
  const { account, config, payload } = value
466
505
  assertVersion(config.version)
506
+ Address.assert(account)
507
+ if (Hex.size(Hex.from(payload)) !== 32)
508
+ throw new InvalidConfigError({ reason: 'payload must be 32 bytes' })
467
509
  return Hash.keccak256(
468
510
  Hex.concat(
469
511
  Hex.fromString(signatureDomain),