ox 1.6.2 → 1.6.3
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.
- package/CHANGELOG.md +12 -0
- package/dist/tempo/KeyAuthorization.d.ts +16 -12
- package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/KeyAuthorization.js +7 -13
- package/dist/tempo/KeyAuthorization.js.map +1 -1
- package/dist/tempo/MultisigConfig.d.ts +20 -17
- package/dist/tempo/MultisigConfig.d.ts.map +1 -1
- package/dist/tempo/MultisigConfig.js +17 -16
- package/dist/tempo/MultisigConfig.js.map +1 -1
- package/dist/tempo/SignatureEnvelope.d.ts +19 -17
- package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/tempo/SignatureEnvelope.js +35 -27
- package/dist/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/tempo/index.d.ts +2 -2
- package/dist/tempo/index.js +2 -2
- package/dist/zod/tempo/KeyAuthorization.d.ts +231 -22
- package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.js +10 -2
- package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
- package/dist/zod/tempo/RpcSchemaTempo.d.ts +82 -8
- package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/zod/tempo/Transaction.d.ts +246 -24
- package/dist/zod/tempo/Transaction.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.d.ts +149 -14
- package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts +78 -6
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/tempo/KeyAuthorization.test-d.ts +41 -6
- package/src/tempo/KeyAuthorization.test.ts +77 -18
- package/src/tempo/KeyAuthorization.ts +36 -36
- package/src/tempo/MultisigConfig.test.ts +28 -8
- package/src/tempo/MultisigConfig.ts +28 -19
- package/src/tempo/SignatureEnvelope.test.ts +64 -19
- package/src/tempo/SignatureEnvelope.ts +45 -35
- package/src/tempo/index.ts +2 -2
- package/src/tempo/multisig.e2e.test.ts +402 -145
- package/src/version.ts +1 -1
- package/src/zod/tempo/KeyAuthorization.ts +12 -2
- package/src/zod/tempo/_test/KeyAuthorization.test.ts +19 -5
|
@@ -53,11 +53,17 @@ const signature_webauthn = SignatureEnvelope.from({
|
|
|
53
53
|
},
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
-
const signature_multisig =
|
|
56
|
+
const signature_multisig = {
|
|
57
57
|
account: address,
|
|
58
58
|
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
59
59
|
type: 'multisig',
|
|
60
|
-
}
|
|
60
|
+
} as const satisfies SignatureEnvelope.Multisig
|
|
61
|
+
|
|
62
|
+
const signature_keychain = {
|
|
63
|
+
inner: SignatureEnvelope.from(signature_secp256k1),
|
|
64
|
+
type: 'keychain',
|
|
65
|
+
userAddress: address,
|
|
66
|
+
} as const satisfies SignatureEnvelope.Keychain
|
|
61
67
|
|
|
62
68
|
describe('from', () => {
|
|
63
69
|
test('default', () => {
|
|
@@ -423,7 +429,34 @@ describe('from', () => {
|
|
|
423
429
|
`)
|
|
424
430
|
})
|
|
425
431
|
|
|
426
|
-
test('
|
|
432
|
+
test('with signature (multisig)', () => {
|
|
433
|
+
const authorization = KeyAuthorization.from({
|
|
434
|
+
address,
|
|
435
|
+
chainId: 1n,
|
|
436
|
+
type: 'secp256k1',
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
const signed = KeyAuthorization.from(authorization, {
|
|
440
|
+
signature: signature_multisig,
|
|
441
|
+
})
|
|
442
|
+
expect(signed.signature).toEqual(signature_multisig)
|
|
443
|
+
expect(
|
|
444
|
+
KeyAuthorization.from(authorization, {
|
|
445
|
+
signature: SignatureEnvelope.serialize(signature_multisig),
|
|
446
|
+
}).signature,
|
|
447
|
+
).toEqual(signature_multisig)
|
|
448
|
+
expect(
|
|
449
|
+
KeyAuthorization.from({
|
|
450
|
+
...authorization,
|
|
451
|
+
signature: signature_multisig,
|
|
452
|
+
}).signature,
|
|
453
|
+
).toEqual(signature_multisig)
|
|
454
|
+
expect(
|
|
455
|
+
KeyAuthorization.deserialize(KeyAuthorization.serialize(signed)),
|
|
456
|
+
).toEqual(signed)
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
test('rejects a keychain signature', () => {
|
|
427
460
|
const authorization = KeyAuthorization.from({
|
|
428
461
|
address,
|
|
429
462
|
chainId: 1n,
|
|
@@ -432,43 +465,55 @@ describe('from', () => {
|
|
|
432
465
|
|
|
433
466
|
expect(() =>
|
|
434
467
|
KeyAuthorization.from(authorization, {
|
|
435
|
-
signature:
|
|
468
|
+
signature: signature_keychain as never,
|
|
436
469
|
}),
|
|
437
470
|
).toThrowErrorMatchingInlineSnapshot(
|
|
438
|
-
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`
|
|
471
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
|
|
439
472
|
)
|
|
440
473
|
|
|
441
474
|
expect(() =>
|
|
442
475
|
KeyAuthorization.from(authorization, {
|
|
443
|
-
signature: SignatureEnvelope.serialize(
|
|
476
|
+
signature: SignatureEnvelope.serialize(signature_keychain),
|
|
444
477
|
}),
|
|
445
478
|
).toThrowErrorMatchingInlineSnapshot(
|
|
446
|
-
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`
|
|
479
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
|
|
447
480
|
)
|
|
448
481
|
|
|
449
482
|
expect(() =>
|
|
450
483
|
KeyAuthorization.from({
|
|
451
484
|
...authorization,
|
|
452
|
-
signature:
|
|
485
|
+
signature: signature_keychain,
|
|
453
486
|
} as never),
|
|
454
487
|
).toThrowErrorMatchingInlineSnapshot(
|
|
455
|
-
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`
|
|
488
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
|
|
456
489
|
)
|
|
457
490
|
})
|
|
458
491
|
})
|
|
459
492
|
|
|
460
493
|
describe('fromRpc', () => {
|
|
461
|
-
test('
|
|
494
|
+
test('multisig', () => {
|
|
495
|
+
const authorization = KeyAuthorization.fromRpc({
|
|
496
|
+
chainId: '0x1',
|
|
497
|
+
expiry: null,
|
|
498
|
+
keyId: address,
|
|
499
|
+
keyType: 'secp256k1',
|
|
500
|
+
signature: SignatureEnvelope.toRpc(signature_multisig),
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
expect(authorization.signature).toEqual(signature_multisig)
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
test('rejects a keychain signature', () => {
|
|
462
507
|
expect(() =>
|
|
463
508
|
KeyAuthorization.fromRpc({
|
|
464
509
|
chainId: '0x1',
|
|
465
510
|
expiry: null,
|
|
466
511
|
keyId: address,
|
|
467
512
|
keyType: 'secp256k1',
|
|
468
|
-
signature: SignatureEnvelope.toRpc(
|
|
513
|
+
signature: SignatureEnvelope.toRpc(signature_keychain),
|
|
469
514
|
} as never),
|
|
470
515
|
).toThrowErrorMatchingInlineSnapshot(
|
|
471
|
-
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`
|
|
516
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
|
|
472
517
|
)
|
|
473
518
|
})
|
|
474
519
|
|
|
@@ -2401,12 +2446,12 @@ describe('admin keys (TIP-1049)', () => {
|
|
|
2401
2446
|
expect(restored.account).toBeUndefined()
|
|
2402
2447
|
})
|
|
2403
2448
|
|
|
2404
|
-
test('fromTuple:
|
|
2449
|
+
test('fromTuple: preserves a non-admin account binding', () => {
|
|
2405
2450
|
const restored = KeyAuthorization.fromTuple([
|
|
2406
2451
|
['0x01', '0x', address, '0x', [], [], '0x', '0x', account],
|
|
2407
2452
|
])
|
|
2408
|
-
expect(restored.isAdmin).
|
|
2409
|
-
expect(restored.account).
|
|
2453
|
+
expect(restored.isAdmin).toBe(false)
|
|
2454
|
+
expect(restored.account).toBe(account)
|
|
2410
2455
|
})
|
|
2411
2456
|
|
|
2412
2457
|
test('fromTuple: extracts isAdmin + account together', () => {
|
|
@@ -2441,6 +2486,20 @@ describe('admin keys (TIP-1049)', () => {
|
|
|
2441
2486
|
expect(restored.account).toBe(account)
|
|
2442
2487
|
})
|
|
2443
2488
|
|
|
2489
|
+
test('serialize/deserialize: roundtrip with non-admin account binding', () => {
|
|
2490
|
+
const authorization = KeyAuthorization.from({
|
|
2491
|
+
address,
|
|
2492
|
+
account,
|
|
2493
|
+
chainId: 1n,
|
|
2494
|
+
isAdmin: false,
|
|
2495
|
+
type: 'secp256k1',
|
|
2496
|
+
})
|
|
2497
|
+
const serialized = KeyAuthorization.serialize(authorization)
|
|
2498
|
+
const restored = KeyAuthorization.deserialize(serialized)
|
|
2499
|
+
expect(restored.isAdmin).toBe(false)
|
|
2500
|
+
expect(restored.account).toBe(account)
|
|
2501
|
+
})
|
|
2502
|
+
|
|
2444
2503
|
test('toRpc/fromRpc: roundtrip with isAdmin + account', () => {
|
|
2445
2504
|
const authorization = KeyAuthorization.from(
|
|
2446
2505
|
{
|
|
@@ -2471,15 +2530,15 @@ describe('admin keys (TIP-1049)', () => {
|
|
|
2471
2530
|
expect(restored.account).toBeUndefined()
|
|
2472
2531
|
})
|
|
2473
2532
|
|
|
2474
|
-
test('fromRpc:
|
|
2533
|
+
test('fromRpc: preserves a non-admin account binding', () => {
|
|
2475
2534
|
const authorization = KeyAuthorization.from(
|
|
2476
2535
|
{ address, chainId: 1n, type: 'secp256k1' },
|
|
2477
2536
|
{ signature: SignatureEnvelope.from(signature_secp256k1) },
|
|
2478
2537
|
)
|
|
2479
2538
|
const rpc = KeyAuthorization.toRpc(authorization)
|
|
2480
2539
|
const restored = KeyAuthorization.fromRpc({ ...rpc, account })
|
|
2481
|
-
expect(restored.isAdmin).
|
|
2482
|
-
expect(restored.account).
|
|
2540
|
+
expect(restored.isAdmin).toBe(false)
|
|
2541
|
+
expect(restored.account).toBe(account)
|
|
2483
2542
|
})
|
|
2484
2543
|
|
|
2485
2544
|
test('hash: changes when admin pair is added', () => {
|
|
@@ -78,11 +78,22 @@ export type KeyAuthorization<
|
|
|
78
78
|
| {}
|
|
79
79
|
> &
|
|
80
80
|
(signed extends true
|
|
81
|
-
? { signature:
|
|
81
|
+
? { signature: Signature<numberType> }
|
|
82
82
|
: {
|
|
83
|
-
signature?:
|
|
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
|
-
/**
|
|
108
|
-
signature:
|
|
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
|
|
140
|
-
| UnionPartialBy<
|
|
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
|
|
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
|
|
461
|
-
| PrimitiveSignatureValue
|
|
462
|
-
| undefined,
|
|
472
|
+
signature extends SignatureValue | undefined = SignatureValue | undefined,
|
|
463
473
|
> = {
|
|
464
|
-
/** The
|
|
465
|
-
signature?: signature |
|
|
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
|
|
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
|
|
485
|
+
(signature extends SignatureValue
|
|
478
486
|
? {
|
|
479
487
|
signature: Extract<
|
|
480
488
|
SignatureEnvelope.from.ReturnValue<signature>,
|
|
481
|
-
|
|
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
|
-
|
|
548
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
701
|
-
|
|
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
|
-
...
|
|
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
|
|
1190
|
-
if (signature.type === 'keychain'
|
|
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
|
|
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 \`
|
|
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${'
|
|
90
|
-
|
|
89
|
+
payload: `0x${'42'.repeat(32)}`,
|
|
90
|
+
initialConfig: singleOwnerConfig,
|
|
91
91
|
}),
|
|
92
92
|
).toMatchInlineSnapshot(
|
|
93
|
-
`"
|
|
93
|
+
`"0xbf944a7a752b2cfab0418d5fb4591c5a7ff62976488edce11794d7f35fb34f41"`,
|
|
94
94
|
)
|
|
95
95
|
})
|
|
96
96
|
|
|
97
|
-
test('behavior: `
|
|
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
|
-
|
|
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
|
|
153
|
-
const owners = Array.from({ length:
|
|
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:
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
292
|
-
* (bootstrap) config
|
|
293
|
-
* is the
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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(
|
|
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 {
|
|
363
|
+
: getAddress((value as { initialConfig: Config }).initialConfig)
|
|
363
364
|
return Hash.keccak256(
|
|
364
|
-
Hex.concat(
|
|
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
|
|
382
|
-
*
|
|
389
|
+
* Config updates never change `account`, so the initial config can
|
|
390
|
+
* continue deriving the account for post-update transactions.
|
|
383
391
|
*/
|
|
384
|
-
|
|
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
|
|