ox 1.7.1 → 1.7.2
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 +15 -0
- package/dist/core/Hash.js +1 -1
- package/dist/core/Hash.js.map +1 -1
- package/dist/tempo/KeyAuthorization.d.ts +1 -1
- package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/MultisigConfig.d.ts +151 -83
- package/dist/tempo/MultisigConfig.d.ts.map +1 -1
- package/dist/tempo/MultisigConfig.js +164 -86
- package/dist/tempo/MultisigConfig.js.map +1 -1
- package/dist/tempo/MultisigOperation.d.ts +324 -0
- package/dist/tempo/MultisigOperation.d.ts.map +1 -0
- package/dist/tempo/MultisigOperation.js +809 -0
- package/dist/tempo/MultisigOperation.js.map +1 -0
- package/dist/tempo/MultisigWitness.d.ts +106 -0
- package/dist/tempo/MultisigWitness.d.ts.map +1 -0
- package/dist/tempo/MultisigWitness.js +118 -0
- package/dist/tempo/MultisigWitness.js.map +1 -0
- package/dist/tempo/RpcSchemaTempo.d.ts +35 -0
- package/dist/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/tempo/SignatureEnvelope.d.ts +60 -80
- package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/tempo/SignatureEnvelope.js +87 -146
- package/dist/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/tempo/TransactionRequest.d.ts +9 -17
- package/dist/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/tempo/TransactionRequest.js +7 -7
- package/dist/tempo/TransactionRequest.js.map +1 -1
- package/dist/tempo/TxEnvelopeTempo.d.ts +3 -2
- package/dist/tempo/TxEnvelopeTempo.d.ts.map +1 -1
- package/dist/tempo/TxEnvelopeTempo.js +3 -2
- package/dist/tempo/TxEnvelopeTempo.js.map +1 -1
- package/dist/tempo/index.d.ts +22 -2
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +22 -2
- package/dist/tempo/index.js.map +1 -1
- package/dist/zod/tempo/AuthorizationTempo.d.ts +110 -110
- package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.d.ts +90 -83
- package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/zod/tempo/MultisigConfig.d.ts +31 -2
- package/dist/zod/tempo/MultisigConfig.d.ts.map +1 -1
- package/dist/zod/tempo/MultisigConfig.js +26 -3
- package/dist/zod/tempo/MultisigConfig.js.map +1 -1
- package/dist/zod/tempo/MultisigOperation.d.ts +168 -0
- package/dist/zod/tempo/MultisigOperation.d.ts.map +1 -0
- package/dist/zod/tempo/MultisigOperation.js +65 -0
- package/dist/zod/tempo/MultisigOperation.js.map +1 -0
- package/dist/zod/tempo/MultisigWitness.d.ts +152 -0
- package/dist/zod/tempo/MultisigWitness.d.ts.map +1 -0
- package/dist/zod/tempo/MultisigWitness.js +76 -0
- package/dist/zod/tempo/MultisigWitness.js.map +1 -0
- package/dist/zod/tempo/RpcSchemaTempo.d.ts +607 -102
- package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/zod/tempo/RpcSchemaTempo.js +38 -0
- package/dist/zod/tempo/RpcSchemaTempo.js.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.d.ts +33 -33
- package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.js +9 -16
- package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/zod/tempo/Transaction.d.ts +228 -222
- package/dist/zod/tempo/Transaction.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.d.ts +379 -176
- package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.js +11 -22
- package/dist/zod/tempo/TransactionRequest.js.map +1 -1
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts +60 -48
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
- package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +15 -12
- package/dist/zod/tempo/ZoneRpcAuthentication.d.ts.map +1 -1
- package/dist/zod/tempo/z.d.ts +2 -0
- package/dist/zod/tempo/z.d.ts.map +1 -1
- package/dist/zod/tempo/z.js +2 -0
- package/dist/zod/tempo/z.js.map +1 -1
- package/package.json +21 -1
- package/src/core/Hash.ts +1 -1
- package/src/core/_test/Hash.test.ts +15 -0
- package/src/tempo/KeyAuthorization.test-d.ts +13 -1
- package/src/tempo/KeyAuthorization.test.ts +65 -0
- package/src/tempo/KeyAuthorization.ts +1 -1
- package/src/tempo/MultisigConfig.test-d.ts +52 -0
- package/src/tempo/MultisigConfig.test.ts +288 -227
- package/src/tempo/MultisigConfig.ts +275 -104
- package/src/tempo/MultisigOperation.test-d.ts +77 -0
- package/src/tempo/MultisigOperation.test.ts +1883 -0
- package/src/tempo/MultisigOperation.ts +1254 -0
- package/src/tempo/MultisigWitness.test-d.ts +42 -0
- package/src/tempo/MultisigWitness.test.ts +288 -0
- package/src/tempo/MultisigWitness.ts +210 -0
- package/src/tempo/RpcSchemaTempo.test-d.ts +56 -0
- package/src/tempo/RpcSchemaTempo.ts +37 -0
- package/src/tempo/SignatureEnvelope.test-d.ts +63 -46
- package/src/tempo/SignatureEnvelope.test.ts +523 -500
- package/src/tempo/SignatureEnvelope.ts +172 -242
- package/src/tempo/TransactionRequest.test-d.ts +11 -0
- package/src/tempo/TransactionRequest.test.ts +285 -33
- package/src/tempo/TransactionRequest.ts +15 -20
- package/src/tempo/TxEnvelopeTempo.test-d.ts +7 -0
- package/src/tempo/TxEnvelopeTempo.test.ts +25 -0
- package/src/tempo/TxEnvelopeTempo.ts +5 -3
- package/src/tempo/index.ts +22 -2
- package/src/tempo/multisig.e2e.test.ts +113 -86
- package/src/version.ts +1 -1
- package/src/zod/tempo/MultisigConfig.ts +29 -3
- package/src/zod/tempo/MultisigOperation.ts +69 -0
- package/src/zod/tempo/MultisigWitness.ts +101 -0
- package/src/zod/tempo/RpcSchemaTempo.ts +43 -0
- package/src/zod/tempo/SignatureEnvelope.ts +20 -38
- package/src/zod/tempo/TransactionRequest.ts +17 -26
- package/src/zod/tempo/_test/KeyAuthorization.test.ts +11 -0
- package/src/zod/tempo/_test/MultisigConfig.test-d.ts +13 -0
- package/src/zod/tempo/_test/MultisigConfig.test.ts +13 -2
- package/src/zod/tempo/_test/MultisigOperation.test-d.ts +27 -0
- package/src/zod/tempo/_test/MultisigOperation.test.ts +68 -0
- package/src/zod/tempo/_test/MultisigWitness.test-d.ts +13 -0
- package/src/zod/tempo/_test/MultisigWitness.test.ts +149 -0
- package/src/zod/tempo/_test/RpcSchemaTempo.test-d.ts +31 -0
- package/src/zod/tempo/_test/RpcSchemaTempo.test.ts +41 -0
- package/src/zod/tempo/_test/SignatureEnvelope.test-d.ts +1 -1
- package/src/zod/tempo/_test/SignatureEnvelope.test.ts +25 -34
- package/src/zod/tempo/_test/TransactionRequest.test.ts +176 -8
- package/src/zod/tempo/z.ts +2 -0
|
@@ -3,16 +3,7 @@ import type * as Bytes from '../core/Bytes.js'
|
|
|
3
3
|
import * as Errors from '../core/Errors.js'
|
|
4
4
|
import * as Hash from '../core/Hash.js'
|
|
5
5
|
import * as Hex from '../core/Hex.js'
|
|
6
|
-
import type { Compute
|
|
7
|
-
|
|
8
|
-
/** Maximum number of owners allowed in a native multisig config. */
|
|
9
|
-
export const maxOwners = 50
|
|
10
|
-
|
|
11
|
-
/** Maximum threshold accepted by a native multisig config. */
|
|
12
|
-
export const maxThreshold = 8
|
|
13
|
-
|
|
14
|
-
/** Maximum number of owner approvals in a native multisig signature. */
|
|
15
|
-
export const maxSignatures = maxThreshold
|
|
6
|
+
import type { Compute } from '../core/internal/types.js'
|
|
16
7
|
|
|
17
8
|
/**
|
|
18
9
|
* Maximum number of native multisig signatures in one nested authorization
|
|
@@ -23,6 +14,18 @@ export const maxNestingDepth = 2
|
|
|
23
14
|
/** Maximum encoded byte length for one primitive owner approval. */
|
|
24
15
|
export const maxOwnerSignatureBytes = 2049
|
|
25
16
|
|
|
17
|
+
/** Maximum number of owners allowed in a native multisig config. */
|
|
18
|
+
export const maxOwners = 48
|
|
19
|
+
|
|
20
|
+
/** Maximum number of owner approvals in a native multisig signature. */
|
|
21
|
+
export const maxSignatures = 8
|
|
22
|
+
|
|
23
|
+
/** Maximum threshold accepted by a native multisig config. */
|
|
24
|
+
export const maxThreshold = 0xff
|
|
25
|
+
|
|
26
|
+
/** Maximum version accepted by a native multisig config. */
|
|
27
|
+
export const maxVersion = 2n ** 64n - 1n
|
|
28
|
+
|
|
26
29
|
/** Tempo signature type byte for native multisig signatures. */
|
|
27
30
|
export const signatureTypeByte = '0x05' as const
|
|
28
31
|
|
|
@@ -32,14 +35,33 @@ export const zeroSalt = `0x${'00'.repeat(32)}` as const
|
|
|
32
35
|
/** Domain prefix for the native multisig account address derivation. */
|
|
33
36
|
const accountDomain = 'tempo:multisig:account'
|
|
34
37
|
|
|
38
|
+
/** Domain prefix for native multisig configuration commitments. */
|
|
39
|
+
const configDomain = 'tempo:multisig:config'
|
|
40
|
+
|
|
35
41
|
/** Domain prefix for native multisig owner approvals. */
|
|
36
42
|
const signatureDomain = 'tempo:multisig:signature'
|
|
37
43
|
|
|
38
44
|
/**
|
|
39
|
-
*
|
|
40
|
-
* address.
|
|
45
|
+
* Complete native multisig configuration witness.
|
|
41
46
|
*/
|
|
42
|
-
export type Config<numberType = number> = Compute<{
|
|
47
|
+
export type Config<bigintType = bigint, numberType = number> = Compute<{
|
|
48
|
+
/** Weighted owner list, strictly ascending by owner address. */
|
|
49
|
+
owners: readonly Owner<numberType>[]
|
|
50
|
+
/** Caller-chosen 32-byte salt. */
|
|
51
|
+
salt: Hex.Hex
|
|
52
|
+
/** Minimum total owner weight required for authorization. */
|
|
53
|
+
threshold: numberType
|
|
54
|
+
/** Configuration version. Zero identifies the initial configuration. */
|
|
55
|
+
version: bigintType
|
|
56
|
+
}>
|
|
57
|
+
|
|
58
|
+
/** Input accepted when constructing a native multisig configuration. */
|
|
59
|
+
export type Input<
|
|
60
|
+
versionType extends bigint | number = bigint | number,
|
|
61
|
+
numberType = number,
|
|
62
|
+
> = Compute<{
|
|
63
|
+
/** Weighted owner list (strictly ascending by `owner` address). */
|
|
64
|
+
owners: readonly Owner<numberType>[]
|
|
43
65
|
/**
|
|
44
66
|
* Caller-chosen 32-byte salt mixed into the derived account address.
|
|
45
67
|
* Defaults to the zero salt (`MultisigConfig.zeroSalt`) when omitted.
|
|
@@ -47,8 +69,8 @@ export type Config<numberType = number> = Compute<{
|
|
|
47
69
|
salt?: Hex.Hex | undefined
|
|
48
70
|
/** Minimum total owner weight required to authorize a transaction. */
|
|
49
71
|
threshold: numberType
|
|
50
|
-
/**
|
|
51
|
-
|
|
72
|
+
/** Configuration version as a safe integer or bigint. Defaults to `0n`. */
|
|
73
|
+
version?: versionType | undefined
|
|
52
74
|
}>
|
|
53
75
|
|
|
54
76
|
/** Native multisig owner entry. */
|
|
@@ -59,9 +81,13 @@ export type Owner<numberType = number> = {
|
|
|
59
81
|
weight: numberType
|
|
60
82
|
}
|
|
61
83
|
|
|
84
|
+
/** JSON-RPC representation of a native multisig configuration. */
|
|
85
|
+
export type Rpc = Config<Hex.Hex, number>
|
|
86
|
+
|
|
62
87
|
/** RLP tuple representation of a {@link ox#MultisigConfig.Config}. */
|
|
63
88
|
export type Tuple = readonly [
|
|
64
89
|
salt: Hex.Hex,
|
|
90
|
+
version: Hex.Hex,
|
|
65
91
|
threshold: Hex.Hex,
|
|
66
92
|
owners: readonly Hex.Hex[][],
|
|
67
93
|
]
|
|
@@ -69,10 +95,11 @@ export type Tuple = readonly [
|
|
|
69
95
|
/**
|
|
70
96
|
* Asserts that a native multisig {@link ox#MultisigConfig.Config} is valid.
|
|
71
97
|
*
|
|
72
|
-
* Mirrors the Tempo
|
|
98
|
+
* Mirrors the Tempo configuration rules: owners non-empty and
|
|
73
99
|
* `<= maxOwners`, strictly ascending unique nonzero owner addresses, nonzero
|
|
74
100
|
* integer owner weights, integer `threshold` between `1` and `maxThreshold`,
|
|
75
|
-
* total weight `<= 255` (u8 max), and
|
|
101
|
+
* total weight `<= 255` (u8 max), and a threshold reachable by at most
|
|
102
|
+
* `maxSignatures` owners.
|
|
76
103
|
*
|
|
77
104
|
* @example
|
|
78
105
|
* ```ts twoslash
|
|
@@ -91,11 +118,15 @@ export type Tuple = readonly [
|
|
|
91
118
|
*
|
|
92
119
|
* @param config - The multisig config.
|
|
93
120
|
*/
|
|
94
|
-
export function assert<
|
|
95
|
-
|
|
121
|
+
export function assert<
|
|
122
|
+
versionType extends bigint | number = bigint | number,
|
|
123
|
+
numberType = number,
|
|
124
|
+
>(config: Input<versionType, numberType>): void {
|
|
125
|
+
const { owners, salt, threshold, version = 0n } = config
|
|
96
126
|
|
|
97
127
|
if (typeof salt !== 'undefined' && Hex.size(salt) !== 32)
|
|
98
128
|
throw new InvalidConfigError({ reason: 'salt must be 32 bytes' })
|
|
129
|
+
assertVersion(version)
|
|
99
130
|
if (owners.length === 0)
|
|
100
131
|
throw new InvalidConfigError({ reason: 'owners cannot be empty' })
|
|
101
132
|
if (owners.length > maxOwners)
|
|
@@ -108,6 +139,7 @@ export function assert<numberType = number>(config: Config<numberType>): void {
|
|
|
108
139
|
throw new InvalidConfigError({ reason: 'threshold exceeds max threshold' })
|
|
109
140
|
|
|
110
141
|
let totalWeight = 0
|
|
142
|
+
const weights: number[] = []
|
|
111
143
|
let previous: bigint | undefined
|
|
112
144
|
for (const owner of owners) {
|
|
113
145
|
if (!Address.validate(owner.owner) || Hex.toBigInt(owner.owner) === 0n)
|
|
@@ -126,7 +158,9 @@ export function assert<numberType = number>(config: Config<numberType>): void {
|
|
|
126
158
|
})
|
|
127
159
|
previous = current
|
|
128
160
|
|
|
129
|
-
|
|
161
|
+
const weight = Number(owner.weight)
|
|
162
|
+
totalWeight += weight
|
|
163
|
+
weights.push(weight)
|
|
130
164
|
}
|
|
131
165
|
|
|
132
166
|
if (totalWeight > 0xff)
|
|
@@ -137,6 +171,15 @@ export function assert<numberType = number>(config: Config<numberType>): void {
|
|
|
137
171
|
throw new InvalidConfigError({
|
|
138
172
|
reason: 'threshold exceeds total owner weight',
|
|
139
173
|
})
|
|
174
|
+
|
|
175
|
+
const reachableWeight = weights
|
|
176
|
+
.sort((a, b) => b - a)
|
|
177
|
+
.slice(0, maxSignatures)
|
|
178
|
+
.reduce((sum, weight) => sum + weight, 0)
|
|
179
|
+
if (Number(threshold) > reachableWeight)
|
|
180
|
+
throw new InvalidConfigError({
|
|
181
|
+
reason: `threshold exceeds weight reachable by ${maxSignatures} owner signatures`,
|
|
182
|
+
})
|
|
140
183
|
}
|
|
141
184
|
|
|
142
185
|
export declare namespace assert {
|
|
@@ -154,7 +197,6 @@ export declare namespace assert {
|
|
|
154
197
|
* import { MultisigConfig } from 'ox/tempo'
|
|
155
198
|
*
|
|
156
199
|
* const config = MultisigConfig.from({
|
|
157
|
-
* threshold: 2,
|
|
158
200
|
* owners: [
|
|
159
201
|
* {
|
|
160
202
|
* owner: '0x2222222222222222222222222222222222222222',
|
|
@@ -164,7 +206,8 @@ export declare namespace assert {
|
|
|
164
206
|
* owner: '0x1111111111111111111111111111111111111111',
|
|
165
207
|
* weight: 1
|
|
166
208
|
* }
|
|
167
|
-
* ]
|
|
209
|
+
* ],
|
|
210
|
+
* threshold: 2
|
|
168
211
|
* })
|
|
169
212
|
* // owners are now sorted ascending by address
|
|
170
213
|
* ```
|
|
@@ -173,20 +216,73 @@ export declare namespace assert {
|
|
|
173
216
|
* @returns The normalized multisig config.
|
|
174
217
|
*/
|
|
175
218
|
export function from<numberType = number>(
|
|
176
|
-
config:
|
|
177
|
-
): Config<numberType>
|
|
219
|
+
config: Input<0 | 0n, numberType> & { version?: 0 | 0n | undefined },
|
|
220
|
+
): Config<0n, numberType>
|
|
221
|
+
export function from<bigintType extends bigint, numberType = number>(
|
|
222
|
+
config: Input<bigintType, numberType> & { version: bigintType },
|
|
223
|
+
): Config<bigintType, numberType>
|
|
224
|
+
export function from<numberType = number>(
|
|
225
|
+
config: Input<number, numberType> & { version: number },
|
|
226
|
+
): Config<bigint, numberType>
|
|
227
|
+
export function from<numberType = number>(
|
|
228
|
+
config: Input<bigint | number, numberType>,
|
|
229
|
+
): Config<bigint, numberType>
|
|
230
|
+
// eslint-disable-next-line jsdoc-js/require-jsdoc
|
|
231
|
+
export function from<numberType = number>(
|
|
232
|
+
config: Input<bigint | number, numberType>,
|
|
233
|
+
): Config<bigint, numberType> {
|
|
234
|
+
const version = config.version ?? 0n
|
|
235
|
+
assertVersion(version)
|
|
178
236
|
const owners = [...config.owners].sort((a, b) =>
|
|
179
237
|
Hex.toBigInt(a.owner) < Hex.toBigInt(b.owner) ? -1 : 1,
|
|
180
238
|
)
|
|
181
239
|
const normalized = {
|
|
240
|
+
owners,
|
|
182
241
|
salt: config.salt ? Hex.padLeft(config.salt, 32) : zeroSalt,
|
|
183
242
|
threshold: config.threshold,
|
|
184
|
-
|
|
185
|
-
} as Config<numberType>
|
|
243
|
+
version: BigInt(version),
|
|
244
|
+
} as Config<bigint, numberType>
|
|
186
245
|
assert(normalized)
|
|
187
246
|
return normalized
|
|
188
247
|
}
|
|
189
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Converts a JSON-RPC multisig configuration to its domain representation.
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* ```ts twoslash
|
|
254
|
+
* import { MultisigConfig } from 'ox/tempo'
|
|
255
|
+
*
|
|
256
|
+
* const config = MultisigConfig.fromRpc({
|
|
257
|
+
* owners: [
|
|
258
|
+
* {
|
|
259
|
+
* owner: '0x1111111111111111111111111111111111111111',
|
|
260
|
+
* weight: 1
|
|
261
|
+
* }
|
|
262
|
+
* ],
|
|
263
|
+
* salt: `0x${'00'.repeat(32)}`,
|
|
264
|
+
* threshold: 1,
|
|
265
|
+
* version: '0x0'
|
|
266
|
+
* })
|
|
267
|
+
* ```
|
|
268
|
+
*
|
|
269
|
+
* @param config - The JSON-RPC multisig configuration.
|
|
270
|
+
* @returns The normalized multisig configuration.
|
|
271
|
+
*/
|
|
272
|
+
export function fromRpc(config: Rpc): Config {
|
|
273
|
+
return from({
|
|
274
|
+
...config,
|
|
275
|
+
version: Hex.toBigInt(config.version),
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export declare namespace fromRpc {
|
|
280
|
+
type ErrorType =
|
|
281
|
+
| assert.ErrorType
|
|
282
|
+
| Hex.toBigInt.ErrorType
|
|
283
|
+
| Errors.GlobalErrorType
|
|
284
|
+
}
|
|
285
|
+
|
|
190
286
|
/**
|
|
191
287
|
* Converts an RLP {@link ox#MultisigConfig.Tuple} back to a
|
|
192
288
|
* {@link ox#MultisigConfig.Config}.
|
|
@@ -197,6 +293,7 @@ export function from<numberType = number>(
|
|
|
197
293
|
*
|
|
198
294
|
* const config = MultisigConfig.fromTuple([
|
|
199
295
|
* `0x${'00'.repeat(32)}`,
|
|
296
|
+
* '0x',
|
|
200
297
|
* '0x01',
|
|
201
298
|
* [['0x1111111111111111111111111111111111111111', '0x01']]
|
|
202
299
|
* ])
|
|
@@ -206,10 +303,8 @@ export function from<numberType = number>(
|
|
|
206
303
|
* @returns The multisig config.
|
|
207
304
|
*/
|
|
208
305
|
export function fromTuple(tuple: Tuple): Config {
|
|
209
|
-
const [salt, threshold, owners] = tuple
|
|
306
|
+
const [salt, version, threshold, owners] = tuple
|
|
210
307
|
return {
|
|
211
|
-
salt: salt && salt !== '0x' ? Hex.padLeft(salt, 32) : zeroSalt,
|
|
212
|
-
threshold: threshold === '0x' ? 0 : Hex.toNumber(threshold),
|
|
213
308
|
owners: owners.map((owner) => {
|
|
214
309
|
const [ownerAddress, weight] = owner as readonly Hex.Hex[]
|
|
215
310
|
return {
|
|
@@ -217,6 +312,9 @@ export function fromTuple(tuple: Tuple): Config {
|
|
|
217
312
|
weight: !weight || weight === '0x' ? 0 : Hex.toNumber(weight),
|
|
218
313
|
}
|
|
219
314
|
}),
|
|
315
|
+
salt: salt && salt !== '0x' ? Hex.padLeft(salt, 32) : zeroSalt,
|
|
316
|
+
threshold: threshold === '0x' ? 0 : Hex.toNumber(threshold),
|
|
317
|
+
version: version === '0x' ? 0n : Hex.toBigInt(version),
|
|
220
318
|
}
|
|
221
319
|
}
|
|
222
320
|
|
|
@@ -226,31 +324,35 @@ export function fromTuple(tuple: Tuple): Config {
|
|
|
226
324
|
* Preimage (fixed-width big-endian, **not** RLP):
|
|
227
325
|
* `keccak256("tempo:multisig:account" || salt || u8(threshold) || u8(owners.length) || (owner || u8(weight)) for each owner)[12:32]`.
|
|
228
326
|
*
|
|
229
|
-
* The address is derived once from the initial
|
|
230
|
-
*
|
|
327
|
+
* The address is derived once from the initial version-0 config. Config
|
|
328
|
+
* updates do not change it.
|
|
231
329
|
*
|
|
232
330
|
* @example
|
|
233
331
|
* ```ts twoslash
|
|
234
332
|
* import { MultisigConfig } from 'ox/tempo'
|
|
235
333
|
*
|
|
236
334
|
* const initialConfig = MultisigConfig.from({
|
|
237
|
-
* threshold: 1,
|
|
238
335
|
* owners: [
|
|
239
336
|
* {
|
|
240
337
|
* owner: '0x1111111111111111111111111111111111111111',
|
|
241
338
|
* weight: 1
|
|
242
339
|
* }
|
|
243
|
-
* ]
|
|
340
|
+
* ],
|
|
341
|
+
* threshold: 1
|
|
244
342
|
* })
|
|
245
343
|
*
|
|
246
344
|
* const address = MultisigConfig.getAddress(initialConfig)
|
|
247
345
|
* ```
|
|
248
346
|
*
|
|
249
|
-
* @param config - The initial
|
|
347
|
+
* @param config - The initial multisig config.
|
|
250
348
|
* @returns The multisig account address.
|
|
251
349
|
*/
|
|
252
|
-
export function getAddress(config:
|
|
350
|
+
export function getAddress(config: Input): Address.Address {
|
|
253
351
|
assert(config)
|
|
352
|
+
if (BigInt(config.version ?? 0) !== 0n)
|
|
353
|
+
throw new InvalidConfigError({
|
|
354
|
+
reason: 'account address requires version zero',
|
|
355
|
+
})
|
|
254
356
|
const hash = Hash.keccak256(
|
|
255
357
|
Hex.concat(
|
|
256
358
|
Hex.fromString(accountDomain),
|
|
@@ -266,6 +368,10 @@ export function getAddress(config: Config): Address.Address {
|
|
|
266
368
|
const account = Address.from(Hex.slice(hash, 12, 32))
|
|
267
369
|
if (Hex.toBigInt(account) === 0n)
|
|
268
370
|
throw new InvalidConfigError({ reason: 'derived account cannot be zero' })
|
|
371
|
+
if (config.owners.some((owner) => Address.isEqual(owner.owner, account)))
|
|
372
|
+
throw new InvalidConfigError({
|
|
373
|
+
reason: 'derived account cannot be an owner',
|
|
374
|
+
})
|
|
269
375
|
return account
|
|
270
376
|
}
|
|
271
377
|
|
|
@@ -282,63 +388,84 @@ export declare namespace getAddress {
|
|
|
282
388
|
}
|
|
283
389
|
|
|
284
390
|
/**
|
|
285
|
-
* Computes the
|
|
286
|
-
*
|
|
287
|
-
* `keccak256("tempo:multisig:signature" || inner_digest || account || uint64be(version))`,
|
|
288
|
-
* where `inner_digest` is the transaction sign payload
|
|
289
|
-
* ({@link ox#TxEnvelopeTempo.(getSignPayload:function)}).
|
|
290
|
-
*
|
|
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.
|
|
391
|
+
* Computes the commitment for a native multisig configuration.
|
|
294
392
|
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
393
|
+
* The commitment uses raw fixed-width fields, not RLP or ABI encoding:
|
|
394
|
+
* `keccak256("tempo:multisig:config" || salt || uint64be(version) || uint8(threshold) || uint8(owners.length) || owners)`.
|
|
297
395
|
*
|
|
298
396
|
* @example
|
|
299
397
|
* ```ts twoslash
|
|
300
|
-
* import { MultisigConfig
|
|
398
|
+
* import { MultisigConfig } from 'ox/tempo'
|
|
301
399
|
*
|
|
302
|
-
* const
|
|
303
|
-
* threshold: 1,
|
|
400
|
+
* const commitment = MultisigConfig.getCommitment({
|
|
304
401
|
* owners: [
|
|
305
402
|
* {
|
|
306
403
|
* owner: '0x1111111111111111111111111111111111111111',
|
|
307
404
|
* weight: 1
|
|
308
405
|
* }
|
|
309
|
-
* ]
|
|
406
|
+
* ],
|
|
407
|
+
* threshold: 1,
|
|
408
|
+
* version: 1n
|
|
310
409
|
* })
|
|
410
|
+
* ```
|
|
311
411
|
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
|
|
315
|
-
|
|
412
|
+
* @param config - The complete multisig configuration.
|
|
413
|
+
* @returns The configuration commitment.
|
|
414
|
+
*/
|
|
415
|
+
export function getCommitment(config: Input): Hex.Hex {
|
|
416
|
+
assert(config)
|
|
417
|
+
return Hash.keccak256(
|
|
418
|
+
Hex.concat(
|
|
419
|
+
Hex.fromString(configDomain),
|
|
420
|
+
Hex.padLeft(config.salt ?? zeroSalt, 32),
|
|
421
|
+
Hex.fromNumber(config.version ?? 0n, { size: 8 }),
|
|
422
|
+
Hex.fromNumber(config.threshold, { size: 1 }),
|
|
423
|
+
Hex.fromNumber(config.owners.length, { size: 1 }),
|
|
424
|
+
...config.owners.flatMap((owner) => [
|
|
425
|
+
owner.owner,
|
|
426
|
+
Hex.fromNumber(owner.weight, { size: 1 }),
|
|
427
|
+
]),
|
|
428
|
+
),
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export declare namespace getCommitment {
|
|
433
|
+
type ErrorType =
|
|
434
|
+
| assert.ErrorType
|
|
435
|
+
| Hash.keccak256.ErrorType
|
|
436
|
+
| Hex.concat.ErrorType
|
|
437
|
+
| Hex.fromNumber.ErrorType
|
|
438
|
+
| Hex.fromString.ErrorType
|
|
439
|
+
| Errors.GlobalErrorType
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Computes the digest a native multisig owner approves (signs).
|
|
316
444
|
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
* })
|
|
321
|
-
* ```
|
|
445
|
+
* `keccak256("tempo:multisig:signature" || inner_digest || account || uint64be(version))`,
|
|
446
|
+
* where `inner_digest` is the transaction sign payload
|
|
447
|
+
* ({@link ox#TxEnvelopeTempo.(getSignPayload:function)}).
|
|
322
448
|
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
449
|
+
* The digest is keyed on the permanent `account` and the supplied config
|
|
450
|
+
* version. Initial approvals use version `0n`; each config update increments
|
|
451
|
+
* it.
|
|
325
452
|
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
453
|
+
* For a nested multisig owner approval, the parent digest becomes the nested
|
|
454
|
+
* approval's `payload`, with the nested multisig `account`.
|
|
328
455
|
*
|
|
456
|
+
* @example
|
|
329
457
|
* ```ts twoslash
|
|
330
458
|
* import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
|
|
331
459
|
*
|
|
332
|
-
* const
|
|
333
|
-
* threshold: 1,
|
|
460
|
+
* const config = MultisigConfig.from({
|
|
334
461
|
* owners: [
|
|
335
462
|
* {
|
|
336
463
|
* owner: '0x1111111111111111111111111111111111111111',
|
|
337
464
|
* weight: 1
|
|
338
465
|
* }
|
|
339
|
-
* ]
|
|
466
|
+
* ],
|
|
467
|
+
* threshold: 1
|
|
340
468
|
* })
|
|
341
|
-
* const account = MultisigConfig.getAddress(initialConfig)
|
|
342
469
|
*
|
|
343
470
|
* const envelope = TxEnvelopeTempo.from({
|
|
344
471
|
* chainId: 1,
|
|
@@ -346,9 +473,9 @@ export declare namespace getAddress {
|
|
|
346
473
|
* })
|
|
347
474
|
*
|
|
348
475
|
* const digest = MultisigConfig.getSignPayload({
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
476
|
+
* account: MultisigConfig.getAddress(config),
|
|
477
|
+
* config,
|
|
478
|
+
* payload: TxEnvelopeTempo.getSignPayload(envelope)
|
|
352
479
|
* })
|
|
353
480
|
* ```
|
|
354
481
|
*
|
|
@@ -356,45 +483,30 @@ export declare namespace getAddress {
|
|
|
356
483
|
* @returns The owner approval digest.
|
|
357
484
|
*/
|
|
358
485
|
export function getSignPayload(value: getSignPayload.Value): Hex.Hex {
|
|
359
|
-
const {
|
|
360
|
-
|
|
361
|
-
'account' in value && value.account
|
|
362
|
-
? value.account
|
|
363
|
-
: getAddress((value as { initialConfig: Config }).initialConfig)
|
|
486
|
+
const { account, config, payload } = value
|
|
487
|
+
assertVersion(config.version)
|
|
364
488
|
return Hash.keccak256(
|
|
365
489
|
Hex.concat(
|
|
366
490
|
Hex.fromString(signatureDomain),
|
|
367
491
|
Hex.from(payload),
|
|
368
492
|
account,
|
|
369
|
-
Hex.fromNumber(version, { size: 8 }),
|
|
493
|
+
Hex.fromNumber(config.version ?? 0n, { size: 8 }),
|
|
370
494
|
),
|
|
371
495
|
)
|
|
372
496
|
}
|
|
373
497
|
|
|
374
498
|
export declare namespace getSignPayload {
|
|
375
499
|
type Value = {
|
|
500
|
+
/** The native multisig account address. */
|
|
501
|
+
account: Address.Address
|
|
502
|
+
/** Configuration whose version applies to the approval. */
|
|
503
|
+
config: Pick<Config<bigint | number>, 'version'>
|
|
376
504
|
/** The inner transaction sign payload (`tx.signature_hash()`). */
|
|
377
505
|
payload: Hex.Hex | Bytes.Bytes
|
|
378
|
-
|
|
379
|
-
version?: bigint | undefined
|
|
380
|
-
} & OneOf<
|
|
381
|
-
| {
|
|
382
|
-
/** The native multisig account address. */
|
|
383
|
-
account: Address.Address
|
|
384
|
-
}
|
|
385
|
-
| {
|
|
386
|
-
/**
|
|
387
|
-
* The initial multisig config (the bootstrap config that derived the
|
|
388
|
-
* permanent `account`). Used to derive the account automatically.
|
|
389
|
-
* Config updates never change `account`, so the initial config can
|
|
390
|
-
* continue deriving the account for post-update transactions.
|
|
391
|
-
*/
|
|
392
|
-
initialConfig: Config
|
|
393
|
-
}
|
|
394
|
-
>
|
|
506
|
+
}
|
|
395
507
|
|
|
396
508
|
type ErrorType =
|
|
397
|
-
|
|
|
509
|
+
| assert.ErrorType
|
|
398
510
|
| Hash.keccak256.ErrorType
|
|
399
511
|
| Hex.concat.ErrorType
|
|
400
512
|
| Hex.from.ErrorType
|
|
@@ -403,10 +515,50 @@ export declare namespace getSignPayload {
|
|
|
403
515
|
}
|
|
404
516
|
|
|
405
517
|
/**
|
|
406
|
-
* Converts a
|
|
407
|
-
* by the multisig signature `init`).
|
|
518
|
+
* Converts a multisig configuration to its JSON-RPC representation.
|
|
408
519
|
*
|
|
409
|
-
*
|
|
520
|
+
* @example
|
|
521
|
+
* ```ts twoslash
|
|
522
|
+
* import { MultisigConfig } from 'ox/tempo'
|
|
523
|
+
*
|
|
524
|
+
* const config = MultisigConfig.toRpc({
|
|
525
|
+
* owners: [
|
|
526
|
+
* {
|
|
527
|
+
* owner: '0x1111111111111111111111111111111111111111',
|
|
528
|
+
* weight: 1
|
|
529
|
+
* }
|
|
530
|
+
* ],
|
|
531
|
+
* threshold: 1
|
|
532
|
+
* })
|
|
533
|
+
* ```
|
|
534
|
+
*
|
|
535
|
+
* @param config - The multisig configuration.
|
|
536
|
+
* @returns The JSON-RPC multisig configuration.
|
|
537
|
+
*/
|
|
538
|
+
export function toRpc(config: Input): Rpc {
|
|
539
|
+
const value = from(config)
|
|
540
|
+
return {
|
|
541
|
+
owners: value.owners.map((owner) => ({
|
|
542
|
+
owner: owner.owner,
|
|
543
|
+
weight: Number(owner.weight),
|
|
544
|
+
})),
|
|
545
|
+
salt: value.salt,
|
|
546
|
+
threshold: Number(value.threshold),
|
|
547
|
+
version: Hex.fromNumber(value.version),
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export declare namespace toRpc {
|
|
552
|
+
type ErrorType =
|
|
553
|
+
| assert.ErrorType
|
|
554
|
+
| Hex.fromNumber.ErrorType
|
|
555
|
+
| Errors.GlobalErrorType
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Converts a {@link ox#MultisigConfig.Config} to its RLP tuple form.
|
|
560
|
+
*
|
|
561
|
+
* Tuple shape: `[salt, version, threshold, [[owner, weight], ...]]`. The
|
|
410
562
|
* 32-byte `salt` encodes as a full fixed-width string; other integers use
|
|
411
563
|
* canonical RLP encoding (zero values encode as `0x`).
|
|
412
564
|
*
|
|
@@ -415,20 +567,20 @@ export declare namespace getSignPayload {
|
|
|
415
567
|
* import { MultisigConfig } from 'ox/tempo'
|
|
416
568
|
*
|
|
417
569
|
* const tuple = MultisigConfig.toTuple({
|
|
418
|
-
* threshold: 1,
|
|
419
570
|
* owners: [
|
|
420
571
|
* {
|
|
421
572
|
* owner: '0x1111111111111111111111111111111111111111',
|
|
422
573
|
* weight: 1
|
|
423
574
|
* }
|
|
424
|
-
* ]
|
|
575
|
+
* ],
|
|
576
|
+
* threshold: 1
|
|
425
577
|
* })
|
|
426
578
|
* ```
|
|
427
579
|
*
|
|
428
580
|
* @param config - The multisig config.
|
|
429
581
|
* @returns The RLP tuple.
|
|
430
582
|
*/
|
|
431
|
-
export function toTuple(config:
|
|
583
|
+
export function toTuple(config: Input): Tuple {
|
|
432
584
|
assert(config)
|
|
433
585
|
const owners = config.owners.map(
|
|
434
586
|
(owner) => [owner.owner, Hex.fromNumber(owner.weight)] as Hex.Hex[],
|
|
@@ -436,7 +588,13 @@ export function toTuple(config: Config): Tuple {
|
|
|
436
588
|
// `salt` is a fixed 32-byte value: it RLP-encodes as a full 32-byte string
|
|
437
589
|
// (including the zero salt), never trimmed like an integer.
|
|
438
590
|
const salt = config.salt ? Hex.padLeft(config.salt, 32) : zeroSalt
|
|
439
|
-
|
|
591
|
+
const version = BigInt(config.version ?? 0)
|
|
592
|
+
return [
|
|
593
|
+
salt,
|
|
594
|
+
version === 0n ? '0x' : Hex.fromNumber(version),
|
|
595
|
+
Hex.fromNumber(config.threshold),
|
|
596
|
+
owners,
|
|
597
|
+
] as const
|
|
440
598
|
}
|
|
441
599
|
|
|
442
600
|
/**
|
|
@@ -448,13 +606,13 @@ export function toTuple(config: Config): Tuple {
|
|
|
448
606
|
* import { MultisigConfig } from 'ox/tempo'
|
|
449
607
|
*
|
|
450
608
|
* const valid = MultisigConfig.validate({
|
|
451
|
-
* threshold: 1,
|
|
452
609
|
* owners: [
|
|
453
610
|
* {
|
|
454
611
|
* owner: '0x1111111111111111111111111111111111111111',
|
|
455
612
|
* weight: 1
|
|
456
613
|
* }
|
|
457
|
-
* ]
|
|
614
|
+
* ],
|
|
615
|
+
* threshold: 1
|
|
458
616
|
* })
|
|
459
617
|
* // @log: true
|
|
460
618
|
* ```
|
|
@@ -462,7 +620,7 @@ export function toTuple(config: Config): Tuple {
|
|
|
462
620
|
* @param config - The multisig config.
|
|
463
621
|
* @returns Whether the config is valid.
|
|
464
622
|
*/
|
|
465
|
-
export function validate(config:
|
|
623
|
+
export function validate(config: Input): boolean {
|
|
466
624
|
try {
|
|
467
625
|
assert(config)
|
|
468
626
|
return true
|
|
@@ -478,3 +636,16 @@ export class InvalidConfigError extends Errors.BaseError {
|
|
|
478
636
|
super(`Invalid native multisig config: ${reason}.`)
|
|
479
637
|
}
|
|
480
638
|
}
|
|
639
|
+
|
|
640
|
+
/** @internal */
|
|
641
|
+
function assertVersion(version: unknown): asserts version is bigint | number {
|
|
642
|
+
if (
|
|
643
|
+
(typeof version !== 'bigint' && typeof version !== 'number') ||
|
|
644
|
+
(typeof version === 'number' && !Number.isSafeInteger(version)) ||
|
|
645
|
+
BigInt(version) < 0n ||
|
|
646
|
+
BigInt(version) > maxVersion
|
|
647
|
+
)
|
|
648
|
+
throw new InvalidConfigError({
|
|
649
|
+
reason: 'version must be an unsigned 64-bit integer',
|
|
650
|
+
})
|
|
651
|
+
}
|