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.
- package/CHANGELOG.md +18 -0
- package/dist/core/AesGcm.d.ts +67 -2
- package/dist/core/AesGcm.d.ts.map +1 -1
- package/dist/core/AesGcm.js +79 -10
- package/dist/core/AesGcm.js.map +1 -1
- package/dist/core/Ed25519.d.ts +83 -2
- package/dist/core/Ed25519.d.ts.map +1 -1
- package/dist/core/Ed25519.js +79 -4
- package/dist/core/Ed25519.js.map +1 -1
- package/dist/core/MlDsa44.d.ts +84 -5
- package/dist/core/MlDsa44.d.ts.map +1 -1
- package/dist/core/MlDsa44.js +79 -4
- package/dist/core/MlDsa44.js.map +1 -1
- package/dist/core/P256.d.ts +82 -1
- package/dist/core/P256.d.ts.map +1 -1
- package/dist/core/P256.js +78 -0
- package/dist/core/P256.js.map +1 -1
- package/dist/core/Secp256k1.d.ts +85 -1
- package/dist/core/Secp256k1.d.ts.map +1 -1
- package/dist/core/Secp256k1.js +74 -3
- package/dist/core/Secp256k1.js.map +1 -1
- package/dist/core/internal/keyDerivation.d.ts +13 -0
- package/dist/core/internal/keyDerivation.d.ts.map +1 -0
- package/dist/core/internal/keyDerivation.js +13 -0
- package/dist/core/internal/keyDerivation.js.map +1 -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/core/AesGcm.ts +137 -30
- package/src/core/Ed25519.ts +135 -4
- package/src/core/MlDsa44.ts +135 -4
- package/src/core/P256.ts +135 -1
- package/src/core/Secp256k1.ts +134 -3
- package/src/core/_test/AesGcm.test-d.ts +19 -0
- package/src/core/_test/AesGcm.test.ts +90 -1
- package/src/core/_test/Ed25519.test-d.ts +24 -0
- package/src/core/_test/Ed25519.test.ts +87 -1
- package/src/core/_test/MlDsa44.test-d.ts +24 -0
- package/src/core/_test/MlDsa44.test.ts +69 -1
- package/src/core/_test/P256.test-d.ts +26 -0
- package/src/core/_test/P256.test.ts +98 -1
- package/src/core/_test/Secp256k1.test-d.ts +27 -0
- package/src/core/_test/Secp256k1.test.ts +112 -1
- package/src/core/internal/keyDerivation.ts +34 -0
- 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
|
@@ -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
|
|
|
@@ -1116,7 +1116,7 @@ describe('from', () => {
|
|
|
1116
1116
|
})
|
|
1117
1117
|
|
|
1118
1118
|
describe('multisig', () => {
|
|
1119
|
-
const
|
|
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 `
|
|
1129
|
+
test('behavior: derives `account` from `initialConfig`', () => {
|
|
1130
1130
|
const envelope = SignatureEnvelope.from({
|
|
1131
|
-
|
|
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(
|
|
1137
|
+
account: MultisigConfig.getAddress(initialConfig),
|
|
1138
1138
|
})
|
|
1139
|
-
expect('
|
|
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 `
|
|
1143
|
+
test('behavior: `init: true` opts into bootstrap (uses `initialConfig` as `init`)', () => {
|
|
1144
1144
|
const envelope = SignatureEnvelope.from({
|
|
1145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: `
|
|
1861
|
-
const account = MultisigConfig.getAddress(
|
|
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
|
-
|
|
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',
|