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
|
@@ -8,38 +8,58 @@ import * as TransactionReceipt from './TransactionReceipt.js'
|
|
|
8
8
|
import * as TxEnvelopeTempo from './TxEnvelopeTempo.js'
|
|
9
9
|
|
|
10
10
|
const chainId = chain.id
|
|
11
|
+
const updateConfig = AbiFunction.from(
|
|
12
|
+
'function updateConfig(uint8 threshold, (address owner, uint8 weight)[] owners)',
|
|
13
|
+
)
|
|
11
14
|
|
|
12
15
|
describe('behavior: multisig (TIP-1061)', () => {
|
|
13
|
-
function
|
|
14
|
-
const
|
|
16
|
+
function createKey() {
|
|
17
|
+
const privateKey = Secp256k1.randomPrivateKey()
|
|
18
|
+
const address = Address.fromPublicKey(
|
|
19
|
+
Secp256k1.getPublicKey({ privateKey }),
|
|
20
|
+
)
|
|
21
|
+
return { address, privateKey } as const
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function setup(parameters: {
|
|
25
|
+
count: number
|
|
26
|
+
threshold: number
|
|
27
|
+
weights?: readonly number[] | undefined
|
|
28
|
+
}) {
|
|
29
|
+
const { count, threshold, weights = Array(count).fill(1) } = parameters
|
|
15
30
|
const ownerKeys = Array.from({ length: count }, () => {
|
|
16
31
|
const privateKey = Secp256k1.randomPrivateKey()
|
|
17
32
|
const address = Address.fromPublicKey(
|
|
18
33
|
Secp256k1.getPublicKey({ privateKey }),
|
|
19
34
|
)
|
|
20
35
|
return { address, privateKey } as const
|
|
21
|
-
})
|
|
36
|
+
}).sort((a, b) => a.address.localeCompare(b.address))
|
|
22
37
|
|
|
23
|
-
const
|
|
38
|
+
const initialConfig = MultisigConfig.from({
|
|
24
39
|
salt: Hex.random(32),
|
|
25
40
|
threshold,
|
|
26
|
-
owners: ownerKeys.map((key) => ({
|
|
41
|
+
owners: ownerKeys.map((key, index) => ({
|
|
27
42
|
owner: key.address,
|
|
28
|
-
weight:
|
|
43
|
+
weight: weights[index]!,
|
|
29
44
|
})),
|
|
30
45
|
})
|
|
31
|
-
const account = MultisigConfig.getAddress(
|
|
46
|
+
const account = MultisigConfig.getAddress(initialConfig)
|
|
32
47
|
|
|
33
|
-
return { account,
|
|
48
|
+
return { account, initialConfig, ownerKeys } as const
|
|
34
49
|
}
|
|
35
50
|
|
|
36
51
|
function approve(parameters: {
|
|
37
|
-
|
|
52
|
+
initialConfig: MultisigConfig.Config
|
|
38
53
|
payload: Hex.Hex
|
|
39
54
|
signers: readonly { privateKey: Hex.Hex }[]
|
|
55
|
+
version?: bigint | undefined
|
|
40
56
|
}) {
|
|
41
|
-
const {
|
|
42
|
-
const digest = MultisigConfig.getSignPayload({
|
|
57
|
+
const { initialConfig, payload, signers, version = 0n } = parameters
|
|
58
|
+
const digest = MultisigConfig.getSignPayload({
|
|
59
|
+
payload,
|
|
60
|
+
initialConfig,
|
|
61
|
+
version,
|
|
62
|
+
})
|
|
43
63
|
const signatures = signers.map((signer) =>
|
|
44
64
|
SignatureEnvelope.from(
|
|
45
65
|
Secp256k1.sign({ payload: digest, privateKey: signer.privateKey }),
|
|
@@ -47,17 +67,92 @@ describe('behavior: multisig (TIP-1061)', () => {
|
|
|
47
67
|
)
|
|
48
68
|
// The node requires approvals ordered by recovered owner address.
|
|
49
69
|
return SignatureEnvelope.sortMultisigApprovals({
|
|
50
|
-
|
|
70
|
+
initialConfig,
|
|
51
71
|
payload,
|
|
52
72
|
signatures,
|
|
73
|
+
version,
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function multisigSignature(parameters: {
|
|
78
|
+
initialConfig: MultisigConfig.Config
|
|
79
|
+
payload: Hex.Hex
|
|
80
|
+
signers: readonly { privateKey: Hex.Hex }[]
|
|
81
|
+
init?: boolean | undefined
|
|
82
|
+
version?: bigint | undefined
|
|
83
|
+
}) {
|
|
84
|
+
const { init = false, ...approval } = parameters
|
|
85
|
+
return SignatureEnvelope.from({
|
|
86
|
+
initialConfig: parameters.initialConfig,
|
|
87
|
+
...(init ? { init: true } : {}),
|
|
88
|
+
signatures: approve(approval),
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function send(serialized: Hex.Hex) {
|
|
93
|
+
return (await client
|
|
94
|
+
.request({
|
|
95
|
+
method: 'eth_sendRawTransactionSync',
|
|
96
|
+
params: [serialized],
|
|
97
|
+
})
|
|
98
|
+
.then((transaction) => TransactionReceipt.fromRpc(transaction as any)))!
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function bootstrap(multisig: ReturnType<typeof setup>) {
|
|
102
|
+
await fundAddress(client, { address: multisig.account })
|
|
103
|
+
const transaction = TxEnvelopeTempo.from({
|
|
104
|
+
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
105
|
+
chainId,
|
|
106
|
+
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
107
|
+
nonce: 0n,
|
|
108
|
+
gas: 5_000_000n,
|
|
109
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
110
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
111
|
+
})
|
|
112
|
+
const receipt = await send(
|
|
113
|
+
TxEnvelopeTempo.serialize(transaction, {
|
|
114
|
+
signature: multisigSignature({
|
|
115
|
+
initialConfig: multisig.initialConfig,
|
|
116
|
+
init: true,
|
|
117
|
+
payload: TxEnvelopeTempo.getSignPayload(transaction),
|
|
118
|
+
signers: multisig.ownerKeys,
|
|
119
|
+
}),
|
|
120
|
+
}),
|
|
121
|
+
)
|
|
122
|
+
expect(receipt.status).toBe('success')
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function accessKeySignature(parameters: {
|
|
126
|
+
accessKey: { privateKey: Hex.Hex }
|
|
127
|
+
account: Address.Address
|
|
128
|
+
transaction: TxEnvelopeTempo.TxEnvelopeTempo
|
|
129
|
+
}) {
|
|
130
|
+
const { accessKey, account, transaction } = parameters
|
|
131
|
+
return SignatureEnvelope.from({
|
|
132
|
+
inner: SignatureEnvelope.from(
|
|
133
|
+
Secp256k1.sign({
|
|
134
|
+
payload: TxEnvelopeTempo.getSignPayload(transaction, {
|
|
135
|
+
from: account,
|
|
136
|
+
}),
|
|
137
|
+
privateKey: accessKey.privateKey,
|
|
138
|
+
}),
|
|
139
|
+
),
|
|
140
|
+
type: 'keychain',
|
|
141
|
+
userAddress: account,
|
|
53
142
|
})
|
|
54
143
|
}
|
|
55
144
|
|
|
56
|
-
test('
|
|
57
|
-
const { account,
|
|
145
|
+
test('examples: bootstrap, initialized, and configuration rotation', async () => {
|
|
146
|
+
const { account, initialConfig, ownerKeys } = setup({
|
|
58
147
|
count: 3,
|
|
59
148
|
threshold: 2,
|
|
60
149
|
})
|
|
150
|
+
const replacement = createKey()
|
|
151
|
+
const rotatedConfig = MultisigConfig.from({
|
|
152
|
+
salt: initialConfig.salt,
|
|
153
|
+
threshold: 1,
|
|
154
|
+
owners: [{ owner: replacement.address, weight: 1 }],
|
|
155
|
+
})
|
|
61
156
|
|
|
62
157
|
await fundAddress(client, { address: account })
|
|
63
158
|
|
|
@@ -73,10 +168,10 @@ describe('behavior: multisig (TIP-1061)', () => {
|
|
|
73
168
|
|
|
74
169
|
const bootstrap_signed = TxEnvelopeTempo.serialize(bootstrap, {
|
|
75
170
|
signature: SignatureEnvelope.from({
|
|
76
|
-
|
|
171
|
+
initialConfig,
|
|
77
172
|
init: true,
|
|
78
173
|
signatures: approve({
|
|
79
|
-
|
|
174
|
+
initialConfig,
|
|
80
175
|
payload: TxEnvelopeTempo.getSignPayload(bootstrap),
|
|
81
176
|
signers: [ownerKeys[0]!, ownerKeys[1]!],
|
|
82
177
|
}),
|
|
@@ -105,9 +200,48 @@ describe('behavior: multisig (TIP-1061)', () => {
|
|
|
105
200
|
expect(response.signature?.type).toBe('multisig')
|
|
106
201
|
expect(
|
|
107
202
|
(response.signature as SignatureEnvelope.Multisig | undefined)?.init,
|
|
108
|
-
).toEqual(
|
|
203
|
+
).toEqual(initialConfig)
|
|
109
204
|
}
|
|
110
205
|
|
|
206
|
+
const updateNonce = await getTransactionCount(client, {
|
|
207
|
+
address: account,
|
|
208
|
+
blockTag: 'pending',
|
|
209
|
+
})
|
|
210
|
+
const update = TxEnvelopeTempo.from({
|
|
211
|
+
calls: [
|
|
212
|
+
{
|
|
213
|
+
to: '0xaacc000000000000000000000000000000000000',
|
|
214
|
+
data: AbiFunction.encodeData(updateConfig, [
|
|
215
|
+
rotatedConfig.threshold,
|
|
216
|
+
rotatedConfig.owners,
|
|
217
|
+
]),
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
chainId,
|
|
221
|
+
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
222
|
+
nonce: BigInt(updateNonce),
|
|
223
|
+
gas: 5_000_000n,
|
|
224
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
225
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
226
|
+
})
|
|
227
|
+
const updateSigned = TxEnvelopeTempo.serialize(update, {
|
|
228
|
+
signature: SignatureEnvelope.from({
|
|
229
|
+
initialConfig,
|
|
230
|
+
signatures: approve({
|
|
231
|
+
initialConfig,
|
|
232
|
+
payload: TxEnvelopeTempo.getSignPayload(update),
|
|
233
|
+
signers: [ownerKeys[0]!, ownerKeys[1]!],
|
|
234
|
+
}),
|
|
235
|
+
}),
|
|
236
|
+
})
|
|
237
|
+
const updateReceipt = (await client
|
|
238
|
+
.request({
|
|
239
|
+
method: 'eth_sendRawTransactionSync',
|
|
240
|
+
params: [updateSigned],
|
|
241
|
+
})
|
|
242
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
243
|
+
expect(updateReceipt.status).toBe('success')
|
|
244
|
+
|
|
111
245
|
const nonce = await getTransactionCount(client, {
|
|
112
246
|
address: account,
|
|
113
247
|
blockTag: 'pending',
|
|
@@ -125,11 +259,12 @@ describe('behavior: multisig (TIP-1061)', () => {
|
|
|
125
259
|
|
|
126
260
|
const spend_signed = TxEnvelopeTempo.serialize(spend, {
|
|
127
261
|
signature: SignatureEnvelope.from({
|
|
128
|
-
|
|
262
|
+
initialConfig,
|
|
129
263
|
signatures: approve({
|
|
130
|
-
|
|
264
|
+
initialConfig,
|
|
131
265
|
payload: TxEnvelopeTempo.getSignPayload(spend),
|
|
132
|
-
signers: [
|
|
266
|
+
signers: [replacement],
|
|
267
|
+
version: 1n,
|
|
133
268
|
}),
|
|
134
269
|
}),
|
|
135
270
|
})
|
|
@@ -145,7 +280,7 @@ describe('behavior: multisig (TIP-1061)', () => {
|
|
|
145
280
|
expect(spend_receipt.from).toBe(account)
|
|
146
281
|
})
|
|
147
282
|
|
|
148
|
-
test('
|
|
283
|
+
test('example: nested ownership', async () => {
|
|
149
284
|
const child = setup({ count: 1, threshold: 1 })
|
|
150
285
|
await fundAddress(client, { address: child.account })
|
|
151
286
|
|
|
@@ -160,10 +295,10 @@ describe('behavior: multisig (TIP-1061)', () => {
|
|
|
160
295
|
})
|
|
161
296
|
const childBootstrapSigned = TxEnvelopeTempo.serialize(childBootstrap, {
|
|
162
297
|
signature: SignatureEnvelope.from({
|
|
163
|
-
|
|
298
|
+
initialConfig: child.initialConfig,
|
|
164
299
|
init: true,
|
|
165
300
|
signatures: approve({
|
|
166
|
-
|
|
301
|
+
initialConfig: child.initialConfig,
|
|
167
302
|
payload: TxEnvelopeTempo.getSignPayload(childBootstrap),
|
|
168
303
|
signers: child.ownerKeys,
|
|
169
304
|
}),
|
|
@@ -177,12 +312,12 @@ describe('behavior: multisig (TIP-1061)', () => {
|
|
|
177
312
|
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
178
313
|
expect(childReceipt.status).toBe('success')
|
|
179
314
|
|
|
180
|
-
const
|
|
315
|
+
const initialConfig = MultisigConfig.from({
|
|
181
316
|
salt: Hex.random(32),
|
|
182
317
|
threshold: 1,
|
|
183
318
|
owners: [{ owner: child.account, weight: 1 }],
|
|
184
319
|
})
|
|
185
|
-
const account = MultisigConfig.getAddress(
|
|
320
|
+
const account = MultisigConfig.getAddress(initialConfig)
|
|
186
321
|
await fundAddress(client, { address: account })
|
|
187
322
|
|
|
188
323
|
const bootstrap = TxEnvelopeTempo.from({
|
|
@@ -195,20 +330,20 @@ describe('behavior: multisig (TIP-1061)', () => {
|
|
|
195
330
|
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
196
331
|
})
|
|
197
332
|
const digest = MultisigConfig.getSignPayload({
|
|
198
|
-
|
|
333
|
+
initialConfig,
|
|
199
334
|
payload: TxEnvelopeTempo.getSignPayload(bootstrap),
|
|
200
335
|
})
|
|
201
336
|
const nested = SignatureEnvelope.from({
|
|
202
|
-
|
|
337
|
+
initialConfig: child.initialConfig,
|
|
203
338
|
signatures: approve({
|
|
204
|
-
|
|
339
|
+
initialConfig: child.initialConfig,
|
|
205
340
|
payload: digest,
|
|
206
341
|
signers: child.ownerKeys,
|
|
207
342
|
}),
|
|
208
343
|
})
|
|
209
344
|
const bootstrapSigned = TxEnvelopeTempo.serialize(bootstrap, {
|
|
210
345
|
signature: SignatureEnvelope.from({
|
|
211
|
-
|
|
346
|
+
initialConfig,
|
|
212
347
|
init: true,
|
|
213
348
|
signatures: [nested],
|
|
214
349
|
}),
|
|
@@ -222,48 +357,191 @@ describe('behavior: multisig (TIP-1061)', () => {
|
|
|
222
357
|
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
223
358
|
expect(receipt.status).toBe('success')
|
|
224
359
|
expect(receipt.from).toBe(account)
|
|
360
|
+
|
|
361
|
+
const transaction = TxEnvelopeTempo.from({
|
|
362
|
+
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
363
|
+
chainId,
|
|
364
|
+
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
365
|
+
nonce: 1n,
|
|
366
|
+
gas: 5_000_000n,
|
|
367
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
368
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
369
|
+
})
|
|
370
|
+
const parentDigest = MultisigConfig.getSignPayload({
|
|
371
|
+
initialConfig,
|
|
372
|
+
payload: TxEnvelopeTempo.getSignPayload(transaction),
|
|
373
|
+
})
|
|
374
|
+
const childSignature = multisigSignature({
|
|
375
|
+
initialConfig: child.initialConfig,
|
|
376
|
+
payload: parentDigest,
|
|
377
|
+
signers: child.ownerKeys,
|
|
378
|
+
})
|
|
379
|
+
const serialized = TxEnvelopeTempo.serialize(transaction, {
|
|
380
|
+
signature: SignatureEnvelope.from({
|
|
381
|
+
initialConfig,
|
|
382
|
+
signatures: [childSignature],
|
|
383
|
+
}),
|
|
384
|
+
})
|
|
385
|
+
const nestedReceipt = (await client
|
|
386
|
+
.request({
|
|
387
|
+
method: 'eth_sendRawTransactionSync',
|
|
388
|
+
params: [serialized],
|
|
389
|
+
})
|
|
390
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
391
|
+
expect(nestedReceipt.status).toBe('success')
|
|
392
|
+
expect(nestedReceipt.from).toBe(account)
|
|
225
393
|
})
|
|
226
394
|
|
|
227
|
-
test('
|
|
228
|
-
const {
|
|
229
|
-
|
|
230
|
-
|
|
395
|
+
test('example: fee sponsorship (owners sign first)', async () => {
|
|
396
|
+
const multisig = setup({ count: 2, threshold: 2 })
|
|
397
|
+
const feePayer = createKey()
|
|
398
|
+
await Promise.all([
|
|
399
|
+
bootstrap(multisig),
|
|
400
|
+
fundAddress(client, { address: feePayer.address }),
|
|
401
|
+
])
|
|
402
|
+
|
|
403
|
+
const transaction = TxEnvelopeTempo.from({
|
|
404
|
+
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
405
|
+
chainId,
|
|
406
|
+
feePayerSignature: null,
|
|
407
|
+
nonce: 1n,
|
|
408
|
+
gas: 5_000_000n,
|
|
409
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
410
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
411
|
+
})
|
|
412
|
+
const signedByOwners = TxEnvelopeTempo.from(transaction, {
|
|
413
|
+
signature: multisigSignature({
|
|
414
|
+
initialConfig: multisig.initialConfig,
|
|
415
|
+
payload: TxEnvelopeTempo.getSignPayload(transaction),
|
|
416
|
+
signers: multisig.ownerKeys,
|
|
417
|
+
}),
|
|
418
|
+
})
|
|
419
|
+
const sponsored = TxEnvelopeTempo.from({
|
|
420
|
+
...signedByOwners,
|
|
421
|
+
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
231
422
|
})
|
|
423
|
+
const feePayerSignature = Secp256k1.sign({
|
|
424
|
+
payload: TxEnvelopeTempo.getFeePayerSignPayload(sponsored, {
|
|
425
|
+
sender: multisig.account,
|
|
426
|
+
}),
|
|
427
|
+
privateKey: feePayer.privateKey,
|
|
428
|
+
})
|
|
429
|
+
const receipt = await send(
|
|
430
|
+
TxEnvelopeTempo.serialize(sponsored, { feePayerSignature }),
|
|
431
|
+
)
|
|
432
|
+
expect(receipt.status).toBe('success')
|
|
433
|
+
expect(receipt.from).toBe(multisig.account)
|
|
434
|
+
expect(receipt.feePayer).toBe(feePayer.address)
|
|
435
|
+
})
|
|
232
436
|
|
|
233
|
-
|
|
437
|
+
test('example: fee sponsorship (fee payer signs first)', async () => {
|
|
438
|
+
const multisig = setup({ count: 2, threshold: 2 })
|
|
439
|
+
const feePayer = createKey()
|
|
440
|
+
await Promise.all([
|
|
441
|
+
bootstrap(multisig),
|
|
442
|
+
fundAddress(client, { address: feePayer.address }),
|
|
443
|
+
])
|
|
234
444
|
|
|
235
|
-
const
|
|
445
|
+
const transaction = TxEnvelopeTempo.from({
|
|
236
446
|
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
237
447
|
chainId,
|
|
448
|
+
feePayerSignature: null,
|
|
238
449
|
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
239
|
-
nonce:
|
|
450
|
+
nonce: 1n,
|
|
240
451
|
gas: 5_000_000n,
|
|
241
452
|
maxFeePerGas: Value.fromGwei('20'),
|
|
242
453
|
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
243
454
|
})
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
455
|
+
const feePayerSignature = Secp256k1.sign({
|
|
456
|
+
payload: TxEnvelopeTempo.getFeePayerSignPayload(transaction, {
|
|
457
|
+
sender: multisig.account,
|
|
458
|
+
}),
|
|
459
|
+
privateKey: feePayer.privateKey,
|
|
460
|
+
})
|
|
461
|
+
const sponsored = TxEnvelopeTempo.from(transaction, { feePayerSignature })
|
|
462
|
+
const receipt = await send(
|
|
463
|
+
TxEnvelopeTempo.serialize(sponsored, {
|
|
464
|
+
signature: multisigSignature({
|
|
465
|
+
initialConfig: multisig.initialConfig,
|
|
466
|
+
payload: TxEnvelopeTempo.getSignPayload(sponsored),
|
|
467
|
+
signers: multisig.ownerKeys,
|
|
253
468
|
}),
|
|
254
469
|
}),
|
|
470
|
+
)
|
|
471
|
+
expect(receipt.status).toBe('success')
|
|
472
|
+
expect(receipt.from).toBe(multisig.account)
|
|
473
|
+
expect(receipt.feePayer).toBe(feePayer.address)
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
test('example: weighted quorum', async () => {
|
|
477
|
+
const { account, initialConfig, ownerKeys } = setup({
|
|
478
|
+
count: 3,
|
|
479
|
+
threshold: 3,
|
|
480
|
+
weights: [2, 1, 1],
|
|
255
481
|
})
|
|
256
482
|
|
|
483
|
+
await fundAddress(client, { address: account })
|
|
484
|
+
|
|
485
|
+
const transaction = (nonce: bigint) =>
|
|
486
|
+
TxEnvelopeTempo.from({
|
|
487
|
+
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
488
|
+
chainId,
|
|
489
|
+
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
490
|
+
nonce,
|
|
491
|
+
gas: 5_000_000n,
|
|
492
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
493
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
494
|
+
})
|
|
495
|
+
const serialize = (
|
|
496
|
+
value: ReturnType<typeof transaction>,
|
|
497
|
+
signers: readonly { privateKey: Hex.Hex }[],
|
|
498
|
+
init = false,
|
|
499
|
+
) =>
|
|
500
|
+
TxEnvelopeTempo.serialize(value, {
|
|
501
|
+
signature: multisigSignature({
|
|
502
|
+
initialConfig,
|
|
503
|
+
init,
|
|
504
|
+
payload: TxEnvelopeTempo.getSignPayload(value),
|
|
505
|
+
signers,
|
|
506
|
+
}),
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
const bootstrap = transaction(0n)
|
|
510
|
+
const bootstrapReceipt = (await client
|
|
511
|
+
.request({
|
|
512
|
+
method: 'eth_sendRawTransactionSync',
|
|
513
|
+
params: [serialize(bootstrap, [ownerKeys[0]!, ownerKeys[1]!], true)],
|
|
514
|
+
})
|
|
515
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
516
|
+
expect(bootstrapReceipt.status).toBe('success')
|
|
517
|
+
|
|
518
|
+
const valid = transaction(1n)
|
|
519
|
+
const validReceipt = (await client
|
|
520
|
+
.request({
|
|
521
|
+
method: 'eth_sendRawTransactionSync',
|
|
522
|
+
params: [serialize(valid, [ownerKeys[0]!, ownerKeys[2]!])],
|
|
523
|
+
})
|
|
524
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
525
|
+
expect(validReceipt.status).toBe('success')
|
|
526
|
+
|
|
527
|
+
const belowThreshold = transaction(2n)
|
|
528
|
+
await expect(
|
|
529
|
+
client.request({
|
|
530
|
+
method: 'eth_sendRawTransactionSync',
|
|
531
|
+
params: [serialize(belowThreshold, [ownerKeys[1]!, ownerKeys[2]!])],
|
|
532
|
+
}),
|
|
533
|
+
).rejects.toThrow()
|
|
534
|
+
|
|
257
535
|
await expect(
|
|
258
536
|
client.request({
|
|
259
537
|
method: 'eth_sendRawTransactionSync',
|
|
260
|
-
params: [
|
|
538
|
+
params: [serialize(belowThreshold, ownerKeys)],
|
|
261
539
|
}),
|
|
262
540
|
).rejects.toThrow()
|
|
263
541
|
})
|
|
264
542
|
|
|
265
543
|
test('behavior: keychain access key (authorize, spend, reject config update)', async () => {
|
|
266
|
-
const { account,
|
|
544
|
+
const { account, initialConfig, ownerKeys } = setup({
|
|
267
545
|
count: 2,
|
|
268
546
|
threshold: 2,
|
|
269
547
|
})
|
|
@@ -290,10 +568,10 @@ describe('behavior: multisig (TIP-1061)', () => {
|
|
|
290
568
|
|
|
291
569
|
const bootstrap_signed = TxEnvelopeTempo.serialize(bootstrap, {
|
|
292
570
|
signature: SignatureEnvelope.from({
|
|
293
|
-
|
|
571
|
+
initialConfig,
|
|
294
572
|
init: true,
|
|
295
573
|
signatures: approve({
|
|
296
|
-
|
|
574
|
+
initialConfig,
|
|
297
575
|
payload: TxEnvelopeTempo.getSignPayload(bootstrap),
|
|
298
576
|
signers: ownerKeys,
|
|
299
577
|
}),
|
|
@@ -340,9 +618,9 @@ describe('behavior: multisig (TIP-1061)', () => {
|
|
|
340
618
|
|
|
341
619
|
const authorize_signed = TxEnvelopeTempo.serialize(authorize, {
|
|
342
620
|
signature: SignatureEnvelope.from({
|
|
343
|
-
|
|
621
|
+
initialConfig,
|
|
344
622
|
signatures: approve({
|
|
345
|
-
|
|
623
|
+
initialConfig,
|
|
346
624
|
payload: TxEnvelopeTempo.getSignPayload(authorize),
|
|
347
625
|
signers: ownerKeys,
|
|
348
626
|
}),
|
|
@@ -411,9 +689,6 @@ describe('behavior: multisig (TIP-1061)', () => {
|
|
|
411
689
|
).toBe(account)
|
|
412
690
|
}
|
|
413
691
|
|
|
414
|
-
const updateMultisigConfig = AbiFunction.from(
|
|
415
|
-
'function updateMultisigConfig(uint8 threshold, (address owner, uint8 weight)[] owners)',
|
|
416
|
-
)
|
|
417
692
|
const updateNonce = await getTransactionCount(client, {
|
|
418
693
|
address: account,
|
|
419
694
|
blockTag: 'pending',
|
|
@@ -422,9 +697,9 @@ describe('behavior: multisig (TIP-1061)', () => {
|
|
|
422
697
|
calls: [
|
|
423
698
|
{
|
|
424
699
|
to: '0xaacc000000000000000000000000000000000000',
|
|
425
|
-
data: AbiFunction.encodeData(
|
|
426
|
-
|
|
427
|
-
|
|
700
|
+
data: AbiFunction.encodeData(updateConfig, [
|
|
701
|
+
initialConfig.threshold,
|
|
702
|
+
initialConfig.owners,
|
|
428
703
|
]),
|
|
429
704
|
},
|
|
430
705
|
],
|
|
@@ -456,127 +731,109 @@ describe('behavior: multisig (TIP-1061)', () => {
|
|
|
456
731
|
expect(updateReceipt.status).toBe('reverted')
|
|
457
732
|
})
|
|
458
733
|
|
|
459
|
-
test('
|
|
460
|
-
const {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
})
|
|
734
|
+
test('example: bootstrap and immediate access key use', async () => {
|
|
735
|
+
const multisig = setup({ count: 2, threshold: 2 })
|
|
736
|
+
const accessKey = createKey()
|
|
737
|
+
await fundAddress(client, { address: multisig.account })
|
|
464
738
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const privateKey = Secp256k1.randomPrivateKey()
|
|
469
|
-
const address = Address.fromPublicKey(
|
|
470
|
-
Secp256k1.getPublicKey({ privateKey }),
|
|
471
|
-
)
|
|
472
|
-
return { address, privateKey } as const
|
|
473
|
-
})()
|
|
474
|
-
|
|
475
|
-
const keyAuth = KeyAuthorization.from({
|
|
476
|
-
address: access.address,
|
|
739
|
+
const keyAuthorization = KeyAuthorization.from({
|
|
740
|
+
account: multisig.account,
|
|
741
|
+
address: accessKey.address,
|
|
477
742
|
chainId: BigInt(chainId),
|
|
743
|
+
isAdmin: false,
|
|
478
744
|
type: 'secp256k1',
|
|
479
745
|
})
|
|
480
|
-
const
|
|
481
|
-
signature:
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
),
|
|
746
|
+
const keyAuthorizationSigned = KeyAuthorization.from(keyAuthorization, {
|
|
747
|
+
signature: multisigSignature({
|
|
748
|
+
initialConfig: multisig.initialConfig,
|
|
749
|
+
init: true,
|
|
750
|
+
payload: KeyAuthorization.getSignPayload(keyAuthorization),
|
|
751
|
+
signers: multisig.ownerKeys,
|
|
752
|
+
}),
|
|
487
753
|
})
|
|
488
|
-
|
|
489
|
-
const bootstrap = TxEnvelopeTempo.from({
|
|
754
|
+
const transaction = TxEnvelopeTempo.from({
|
|
490
755
|
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
491
756
|
chainId,
|
|
492
757
|
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
493
|
-
keyAuthorization:
|
|
758
|
+
keyAuthorization: keyAuthorizationSigned,
|
|
494
759
|
nonce: 0n,
|
|
495
760
|
gas: 5_000_000n,
|
|
496
761
|
maxFeePerGas: Value.fromGwei('20'),
|
|
497
762
|
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
498
763
|
})
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
genesisConfig,
|
|
506
|
-
payload: TxEnvelopeTempo.getSignPayload(bootstrap),
|
|
507
|
-
signers: ownerKeys,
|
|
764
|
+
const receipt = await send(
|
|
765
|
+
TxEnvelopeTempo.serialize(transaction, {
|
|
766
|
+
signature: accessKeySignature({
|
|
767
|
+
accessKey,
|
|
768
|
+
account: multisig.account,
|
|
769
|
+
transaction,
|
|
508
770
|
}),
|
|
509
771
|
}),
|
|
510
|
-
})
|
|
511
|
-
|
|
512
|
-
await expect(
|
|
513
|
-
client.request({
|
|
514
|
-
method: 'eth_sendRawTransactionSync',
|
|
515
|
-
params: [serialized_signed],
|
|
516
|
-
}),
|
|
517
|
-
).rejects.toThrow(
|
|
518
|
-
'native multisig transactions cannot carry key_authorization',
|
|
519
772
|
)
|
|
773
|
+
expect(receipt.status).toBe('success')
|
|
774
|
+
expect(receipt.from).toBe(multisig.account)
|
|
520
775
|
})
|
|
521
776
|
|
|
522
|
-
test('
|
|
523
|
-
const {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
})
|
|
527
|
-
|
|
528
|
-
await fundAddress(client, { address: account })
|
|
529
|
-
|
|
530
|
-
const access = (() => {
|
|
531
|
-
const privateKey = Secp256k1.randomPrivateKey()
|
|
532
|
-
const address = Address.fromPublicKey(
|
|
533
|
-
Secp256k1.getPublicKey({ privateKey }),
|
|
534
|
-
)
|
|
535
|
-
return { address, privateKey } as const
|
|
536
|
-
})()
|
|
777
|
+
test('example: bootstrap and subsequent access key use', async () => {
|
|
778
|
+
const multisig = setup({ count: 2, threshold: 2 })
|
|
779
|
+
const accessKey = createKey()
|
|
780
|
+
await fundAddress(client, { address: multisig.account })
|
|
537
781
|
|
|
538
|
-
const
|
|
539
|
-
|
|
782
|
+
const keyAuthorization = KeyAuthorization.from({
|
|
783
|
+
account: multisig.account,
|
|
784
|
+
address: accessKey.address,
|
|
540
785
|
chainId: BigInt(chainId),
|
|
786
|
+
isAdmin: false,
|
|
541
787
|
type: 'secp256k1',
|
|
542
788
|
})
|
|
543
|
-
const
|
|
544
|
-
signature:
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
),
|
|
789
|
+
const keyAuthorizationSigned = KeyAuthorization.from(keyAuthorization, {
|
|
790
|
+
signature: multisigSignature({
|
|
791
|
+
initialConfig: multisig.initialConfig,
|
|
792
|
+
payload: KeyAuthorization.getSignPayload(keyAuthorization),
|
|
793
|
+
signers: multisig.ownerKeys,
|
|
794
|
+
}),
|
|
550
795
|
})
|
|
551
|
-
|
|
552
|
-
const bootstrap = TxEnvelopeTempo.from({
|
|
796
|
+
const bootstrapTransaction = TxEnvelopeTempo.from({
|
|
553
797
|
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
554
798
|
chainId,
|
|
555
799
|
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
556
|
-
keyAuthorization:
|
|
800
|
+
keyAuthorization: keyAuthorizationSigned,
|
|
557
801
|
nonce: 0n,
|
|
558
802
|
gas: 5_000_000n,
|
|
559
803
|
maxFeePerGas: Value.fromGwei('20'),
|
|
560
804
|
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
561
805
|
})
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
inner: SignatureEnvelope.from(signature),
|
|
571
|
-
type: 'keychain',
|
|
806
|
+
const bootstrapReceipt = await send(
|
|
807
|
+
TxEnvelopeTempo.serialize(bootstrapTransaction, {
|
|
808
|
+
signature: multisigSignature({
|
|
809
|
+
initialConfig: multisig.initialConfig,
|
|
810
|
+
init: true,
|
|
811
|
+
payload: TxEnvelopeTempo.getSignPayload(bootstrapTransaction),
|
|
812
|
+
signers: multisig.ownerKeys,
|
|
813
|
+
}),
|
|
572
814
|
}),
|
|
573
|
-
|
|
815
|
+
)
|
|
816
|
+
expect(bootstrapReceipt.status).toBe('success')
|
|
574
817
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
818
|
+
const transaction = TxEnvelopeTempo.from({
|
|
819
|
+
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
820
|
+
chainId,
|
|
821
|
+
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
822
|
+
nonce: 1n,
|
|
823
|
+
gas: 5_000_000n,
|
|
824
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
825
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
826
|
+
})
|
|
827
|
+
const receipt = await send(
|
|
828
|
+
TxEnvelopeTempo.serialize(transaction, {
|
|
829
|
+
signature: accessKeySignature({
|
|
830
|
+
accessKey,
|
|
831
|
+
account: multisig.account,
|
|
832
|
+
transaction,
|
|
833
|
+
}),
|
|
579
834
|
}),
|
|
580
|
-
)
|
|
835
|
+
)
|
|
836
|
+
expect(receipt.status).toBe('success')
|
|
837
|
+
expect(receipt.from).toBe(multisig.account)
|
|
581
838
|
})
|
|
582
839
|
})
|