ox 0.14.28 → 0.14.30
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 +24 -0
- package/_cjs/tempo/MultisigConfig.js +19 -21
- package/_cjs/tempo/MultisigConfig.js.map +1 -1
- package/_cjs/tempo/SignatureEnvelope.js +62 -28
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/tempo/MultisigConfig.js +60 -49
- package/_esm/tempo/MultisigConfig.js.map +1 -1
- package/_esm/tempo/SignatureEnvelope.js +118 -41
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/tempo/index.js +4 -4
- package/_esm/version.js +1 -1
- package/_types/tempo/MultisigConfig.d.ts +68 -56
- package/_types/tempo/MultisigConfig.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +90 -26
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/tempo/index.d.ts +4 -4
- package/_types/version.d.ts +1 -1
- package/package.json +1 -1
- package/tempo/MultisigConfig.test.ts +62 -36
- package/tempo/MultisigConfig.ts +102 -95
- package/tempo/SignatureEnvelope.test.ts +226 -27
- package/tempo/SignatureEnvelope.ts +174 -69
- package/tempo/e2e.test.ts +263 -48
- package/tempo/index.ts +4 -4
- package/version.ts +1 -1
package/tempo/e2e.test.ts
CHANGED
|
@@ -87,6 +87,7 @@ test('behavior: default (secp256k1)', async () => {
|
|
|
87
87
|
from,
|
|
88
88
|
keyAuthorization: __,
|
|
89
89
|
nonce,
|
|
90
|
+
gasPrice,
|
|
90
91
|
maxFeePerGas,
|
|
91
92
|
maxPriorityFeePerGas,
|
|
92
93
|
signature,
|
|
@@ -100,6 +101,7 @@ test('behavior: default (secp256k1)', async () => {
|
|
|
100
101
|
expect(chainId).toBe(chainId)
|
|
101
102
|
expect(hash).toBe(receipt.transactionHash)
|
|
102
103
|
expect(from).toBe(address)
|
|
104
|
+
expect(gasPrice).toBeDefined()
|
|
103
105
|
expect(maxFeePerGas).toBeDefined()
|
|
104
106
|
expect(maxPriorityFeePerGas).toBeDefined()
|
|
105
107
|
expect(nonce).toBeDefined()
|
|
@@ -119,7 +121,6 @@ test('behavior: default (secp256k1)', async () => {
|
|
|
119
121
|
"data": undefined,
|
|
120
122
|
"feePayerSignature": null,
|
|
121
123
|
"gas": 500000n,
|
|
122
|
-
"gasPrice": 20000000000n,
|
|
123
124
|
"nonceKey": 0n,
|
|
124
125
|
"type": "tempo",
|
|
125
126
|
"validAfter": null,
|
|
@@ -133,6 +134,7 @@ test('behavior: default (secp256k1)', async () => {
|
|
|
133
134
|
blockNumber,
|
|
134
135
|
blockHash,
|
|
135
136
|
cumulativeGasUsed,
|
|
137
|
+
effectiveGasPrice,
|
|
136
138
|
feePayer,
|
|
137
139
|
feeToken: _,
|
|
138
140
|
from,
|
|
@@ -147,6 +149,7 @@ test('behavior: default (secp256k1)', async () => {
|
|
|
147
149
|
expect(blockNumber).toBeDefined()
|
|
148
150
|
expect(blockHash).toBeDefined()
|
|
149
151
|
expect(cumulativeGasUsed).toBeDefined()
|
|
152
|
+
expect(effectiveGasPrice).toBeDefined()
|
|
150
153
|
expect(feePayer).toBeDefined()
|
|
151
154
|
expect(from).toBe(address)
|
|
152
155
|
expect(gasUsed).toBeDefined()
|
|
@@ -159,7 +162,6 @@ test('behavior: default (secp256k1)', async () => {
|
|
|
159
162
|
"blobGasPrice": undefined,
|
|
160
163
|
"blobGasUsed": undefined,
|
|
161
164
|
"contractAddress": null,
|
|
162
|
-
"effectiveGasPrice": 20000000000n,
|
|
163
165
|
"status": "success",
|
|
164
166
|
"to": "0x0000000000000000000000000000000000000000",
|
|
165
167
|
"type": "0x76",
|
|
@@ -298,6 +300,7 @@ test('behavior: default (p256)', async () => {
|
|
|
298
300
|
keyAuthorization: __,
|
|
299
301
|
hash,
|
|
300
302
|
nonce,
|
|
303
|
+
gasPrice,
|
|
301
304
|
maxFeePerGas,
|
|
302
305
|
maxPriorityFeePerGas,
|
|
303
306
|
signature,
|
|
@@ -312,6 +315,7 @@ test('behavior: default (p256)', async () => {
|
|
|
312
315
|
expect(from).toBe(address)
|
|
313
316
|
expect(hash).toBe(receipt.transactionHash)
|
|
314
317
|
expect(nonce).toBeDefined()
|
|
318
|
+
expect(gasPrice).toBeDefined()
|
|
315
319
|
expect(maxFeePerGas).toBeDefined()
|
|
316
320
|
expect(maxPriorityFeePerGas).toBeDefined()
|
|
317
321
|
expect(signature).toBeDefined()
|
|
@@ -330,7 +334,6 @@ test('behavior: default (p256)', async () => {
|
|
|
330
334
|
"data": undefined,
|
|
331
335
|
"feePayerSignature": null,
|
|
332
336
|
"gas": 500000n,
|
|
333
|
-
"gasPrice": 20000000000n,
|
|
334
337
|
"nonceKey": 0n,
|
|
335
338
|
"type": "tempo",
|
|
336
339
|
"validAfter": null,
|
|
@@ -344,6 +347,7 @@ test('behavior: default (p256)', async () => {
|
|
|
344
347
|
blockNumber,
|
|
345
348
|
blockHash,
|
|
346
349
|
cumulativeGasUsed,
|
|
350
|
+
effectiveGasPrice,
|
|
347
351
|
feePayer,
|
|
348
352
|
feeToken: _,
|
|
349
353
|
from,
|
|
@@ -358,6 +362,7 @@ test('behavior: default (p256)', async () => {
|
|
|
358
362
|
expect(blockNumber).toBeDefined()
|
|
359
363
|
expect(blockHash).toBeDefined()
|
|
360
364
|
expect(cumulativeGasUsed).toBeDefined()
|
|
365
|
+
expect(effectiveGasPrice).toBeDefined()
|
|
361
366
|
expect(feePayer).toBeDefined()
|
|
362
367
|
expect(from).toBe(address)
|
|
363
368
|
expect(gasUsed).toBeDefined()
|
|
@@ -370,7 +375,6 @@ test('behavior: default (p256)', async () => {
|
|
|
370
375
|
"blobGasPrice": undefined,
|
|
371
376
|
"blobGasUsed": undefined,
|
|
372
377
|
"contractAddress": null,
|
|
373
|
-
"effectiveGasPrice": 20000000000n,
|
|
374
378
|
"status": "success",
|
|
375
379
|
"to": "0x0000000000000000000000000000000000000000",
|
|
376
380
|
"type": "0x76",
|
|
@@ -441,6 +445,7 @@ test('behavior: default (p256 - webcrypto)', async () => {
|
|
|
441
445
|
keyAuthorization: __,
|
|
442
446
|
hash,
|
|
443
447
|
nonce,
|
|
448
|
+
gasPrice,
|
|
444
449
|
maxFeePerGas,
|
|
445
450
|
maxPriorityFeePerGas,
|
|
446
451
|
signature,
|
|
@@ -455,6 +460,7 @@ test('behavior: default (p256 - webcrypto)', async () => {
|
|
|
455
460
|
expect(from).toBeDefined()
|
|
456
461
|
expect(hash).toBe(receipt.transactionHash)
|
|
457
462
|
expect(nonce).toBeDefined()
|
|
463
|
+
expect(gasPrice).toBeDefined()
|
|
458
464
|
expect(maxFeePerGas).toBeDefined()
|
|
459
465
|
expect(maxPriorityFeePerGas).toBeDefined()
|
|
460
466
|
expect(signature).toBeDefined()
|
|
@@ -473,7 +479,6 @@ test('behavior: default (p256 - webcrypto)', async () => {
|
|
|
473
479
|
"data": undefined,
|
|
474
480
|
"feePayerSignature": null,
|
|
475
481
|
"gas": 500000n,
|
|
476
|
-
"gasPrice": 20000000000n,
|
|
477
482
|
"nonceKey": 0n,
|
|
478
483
|
"type": "tempo",
|
|
479
484
|
"validAfter": null,
|
|
@@ -487,6 +492,7 @@ test('behavior: default (p256 - webcrypto)', async () => {
|
|
|
487
492
|
blockNumber,
|
|
488
493
|
blockHash,
|
|
489
494
|
cumulativeGasUsed,
|
|
495
|
+
effectiveGasPrice,
|
|
490
496
|
feePayer,
|
|
491
497
|
feeToken: _,
|
|
492
498
|
from,
|
|
@@ -501,6 +507,7 @@ test('behavior: default (p256 - webcrypto)', async () => {
|
|
|
501
507
|
expect(blockNumber).toBeDefined()
|
|
502
508
|
expect(blockHash).toBeDefined()
|
|
503
509
|
expect(cumulativeGasUsed).toBeDefined()
|
|
510
|
+
expect(effectiveGasPrice).toBeDefined()
|
|
504
511
|
expect(feePayer).toBeDefined()
|
|
505
512
|
expect(from).toBeDefined()
|
|
506
513
|
expect(gasUsed).toBeDefined()
|
|
@@ -513,7 +520,6 @@ test('behavior: default (p256 - webcrypto)', async () => {
|
|
|
513
520
|
"blobGasPrice": undefined,
|
|
514
521
|
"blobGasUsed": undefined,
|
|
515
522
|
"contractAddress": null,
|
|
516
|
-
"effectiveGasPrice": 20000000000n,
|
|
517
523
|
"status": "success",
|
|
518
524
|
"to": "0x0000000000000000000000000000000000000000",
|
|
519
525
|
"type": "0x76",
|
|
@@ -591,6 +597,7 @@ test('behavior: default (webauthn)', async () => {
|
|
|
591
597
|
keyAuthorization: __,
|
|
592
598
|
hash,
|
|
593
599
|
nonce,
|
|
600
|
+
gasPrice,
|
|
594
601
|
maxFeePerGas,
|
|
595
602
|
maxPriorityFeePerGas,
|
|
596
603
|
transactionIndex,
|
|
@@ -605,6 +612,7 @@ test('behavior: default (webauthn)', async () => {
|
|
|
605
612
|
expect(from).toBeDefined()
|
|
606
613
|
expect(hash).toBe(receipt.transactionHash)
|
|
607
614
|
expect(nonce).toBeDefined()
|
|
615
|
+
expect(gasPrice).toBeDefined()
|
|
608
616
|
expect(maxFeePerGas).toBeDefined()
|
|
609
617
|
expect(maxPriorityFeePerGas).toBeDefined()
|
|
610
618
|
expect(signature).toBeDefined()
|
|
@@ -623,7 +631,6 @@ test('behavior: default (webauthn)', async () => {
|
|
|
623
631
|
"data": undefined,
|
|
624
632
|
"feePayerSignature": null,
|
|
625
633
|
"gas": 500000n,
|
|
626
|
-
"gasPrice": 20000000000n,
|
|
627
634
|
"nonceKey": 0n,
|
|
628
635
|
"type": "tempo",
|
|
629
636
|
"validAfter": null,
|
|
@@ -637,6 +644,7 @@ test('behavior: default (webauthn)', async () => {
|
|
|
637
644
|
blockNumber,
|
|
638
645
|
blockHash,
|
|
639
646
|
cumulativeGasUsed,
|
|
647
|
+
effectiveGasPrice,
|
|
640
648
|
feePayer,
|
|
641
649
|
feeToken: _,
|
|
642
650
|
from,
|
|
@@ -651,6 +659,7 @@ test('behavior: default (webauthn)', async () => {
|
|
|
651
659
|
expect(blockNumber).toBeDefined()
|
|
652
660
|
expect(blockHash).toBeDefined()
|
|
653
661
|
expect(cumulativeGasUsed).toBeDefined()
|
|
662
|
+
expect(effectiveGasPrice).toBeDefined()
|
|
654
663
|
expect(feePayer).toBeDefined()
|
|
655
664
|
expect(from).toBe(address)
|
|
656
665
|
expect(gasUsed).toBeDefined()
|
|
@@ -663,7 +672,6 @@ test('behavior: default (webauthn)', async () => {
|
|
|
663
672
|
"blobGasPrice": undefined,
|
|
664
673
|
"blobGasUsed": undefined,
|
|
665
674
|
"contractAddress": null,
|
|
666
|
-
"effectiveGasPrice": 20000000000n,
|
|
667
675
|
"status": "success",
|
|
668
676
|
"to": "0x0000000000000000000000000000000000000000",
|
|
669
677
|
"type": "0x76",
|
|
@@ -745,6 +753,7 @@ test('behavior: feePayerSignature (user → feePayer)', async () => {
|
|
|
745
753
|
blockNumber,
|
|
746
754
|
blockHash,
|
|
747
755
|
cumulativeGasUsed,
|
|
756
|
+
effectiveGasPrice,
|
|
748
757
|
feePayer,
|
|
749
758
|
feeToken: _,
|
|
750
759
|
from,
|
|
@@ -759,6 +768,7 @@ test('behavior: feePayerSignature (user → feePayer)', async () => {
|
|
|
759
768
|
expect(blockNumber).toBeDefined()
|
|
760
769
|
expect(blockHash).toBeDefined()
|
|
761
770
|
expect(cumulativeGasUsed).toBeDefined()
|
|
771
|
+
expect(effectiveGasPrice).toBeDefined()
|
|
762
772
|
expect(feePayer).toBe(feePayerAddress)
|
|
763
773
|
expect(from).toBe(senderAddress)
|
|
764
774
|
expect(gasUsed).toBeDefined()
|
|
@@ -771,7 +781,6 @@ test('behavior: feePayerSignature (user → feePayer)', async () => {
|
|
|
771
781
|
"blobGasPrice": undefined,
|
|
772
782
|
"blobGasUsed": undefined,
|
|
773
783
|
"contractAddress": null,
|
|
774
|
-
"effectiveGasPrice": 20000000000n,
|
|
775
784
|
"status": "success",
|
|
776
785
|
"to": "0x0000000000000000000000000000000000000000",
|
|
777
786
|
"type": "0x76",
|
|
@@ -1019,6 +1028,7 @@ describe('behavior: keyAuthorization', () => {
|
|
|
1019
1028
|
blockNumber,
|
|
1020
1029
|
blockHash,
|
|
1021
1030
|
cumulativeGasUsed,
|
|
1031
|
+
effectiveGasPrice,
|
|
1022
1032
|
feePayer,
|
|
1023
1033
|
feeToken,
|
|
1024
1034
|
from,
|
|
@@ -1033,6 +1043,7 @@ describe('behavior: keyAuthorization', () => {
|
|
|
1033
1043
|
expect(blockNumber).toBeDefined()
|
|
1034
1044
|
expect(blockHash).toBeDefined()
|
|
1035
1045
|
expect(cumulativeGasUsed).toBeDefined()
|
|
1046
|
+
expect(effectiveGasPrice).toBeDefined()
|
|
1036
1047
|
expect(feeToken).toBeDefined()
|
|
1037
1048
|
expect(feePayer).toBeDefined()
|
|
1038
1049
|
expect(gasUsed).toBeDefined()
|
|
@@ -1046,7 +1057,6 @@ describe('behavior: keyAuthorization', () => {
|
|
|
1046
1057
|
"blobGasPrice": undefined,
|
|
1047
1058
|
"blobGasUsed": undefined,
|
|
1048
1059
|
"contractAddress": null,
|
|
1049
|
-
"effectiveGasPrice": 20000000000n,
|
|
1050
1060
|
"status": "success",
|
|
1051
1061
|
"to": "0x0000000000000000000000000000000000000000",
|
|
1052
1062
|
"type": "0x76",
|
|
@@ -1238,6 +1248,7 @@ describe('behavior: keyAuthorization', () => {
|
|
|
1238
1248
|
blockNumber,
|
|
1239
1249
|
blockHash,
|
|
1240
1250
|
cumulativeGasUsed,
|
|
1251
|
+
effectiveGasPrice,
|
|
1241
1252
|
feePayer,
|
|
1242
1253
|
feeToken,
|
|
1243
1254
|
from,
|
|
@@ -1252,6 +1263,7 @@ describe('behavior: keyAuthorization', () => {
|
|
|
1252
1263
|
expect(blockNumber).toBeDefined()
|
|
1253
1264
|
expect(blockHash).toBeDefined()
|
|
1254
1265
|
expect(cumulativeGasUsed).toBeDefined()
|
|
1266
|
+
expect(effectiveGasPrice).toBeDefined()
|
|
1255
1267
|
expect(feePayer).toBeDefined()
|
|
1256
1268
|
expect(feeToken).toBeDefined()
|
|
1257
1269
|
expect(from).toBeDefined()
|
|
@@ -1265,7 +1277,6 @@ describe('behavior: keyAuthorization', () => {
|
|
|
1265
1277
|
"blobGasPrice": undefined,
|
|
1266
1278
|
"blobGasUsed": undefined,
|
|
1267
1279
|
"contractAddress": null,
|
|
1268
|
-
"effectiveGasPrice": 20000000000n,
|
|
1269
1280
|
"status": "success",
|
|
1270
1281
|
"to": "0x0000000000000000000000000000000000000000",
|
|
1271
1282
|
"type": "0x76",
|
|
@@ -3015,9 +3026,9 @@ describe.skip('behavior: multisig (TIP-1061)', () => {
|
|
|
3015
3026
|
return { address, privateKey } as const
|
|
3016
3027
|
})
|
|
3017
3028
|
|
|
3018
|
-
const
|
|
3029
|
+
const genesisConfig = MultisigConfig.from({
|
|
3019
3030
|
// A fresh random salt yields a distinct account each run, exercising the
|
|
3020
|
-
// salt-inclusive
|
|
3031
|
+
// salt-inclusive account derivation against the node.
|
|
3021
3032
|
salt: Hex.random(32),
|
|
3022
3033
|
threshold,
|
|
3023
3034
|
owners: ownerKeys.map((key) => ({
|
|
@@ -3025,10 +3036,9 @@ describe.skip('behavior: multisig (TIP-1061)', () => {
|
|
|
3025
3036
|
weight: 1,
|
|
3026
3037
|
})),
|
|
3027
3038
|
})
|
|
3028
|
-
const
|
|
3029
|
-
const account = MultisigConfig.getAddress({ configId })
|
|
3039
|
+
const account = MultisigConfig.getAddress(genesisConfig)
|
|
3030
3040
|
|
|
3031
|
-
return { account,
|
|
3041
|
+
return { account, genesisConfig, ownerKeys } as const
|
|
3032
3042
|
}
|
|
3033
3043
|
|
|
3034
3044
|
// Signs the multisig owner digest with the provided owner keys, returning
|
|
@@ -3036,32 +3046,26 @@ describe.skip('behavior: multisig (TIP-1061)', () => {
|
|
|
3036
3046
|
// address (required by the node: "recovered owners must be strictly
|
|
3037
3047
|
// ascending").
|
|
3038
3048
|
function approve(parameters: {
|
|
3039
|
-
|
|
3040
|
-
configId: Hex.Hex
|
|
3049
|
+
genesisConfig: MultisigConfig.Config
|
|
3041
3050
|
payload: Hex.Hex
|
|
3042
3051
|
signers: readonly { privateKey: Hex.Hex }[]
|
|
3043
3052
|
}) {
|
|
3044
|
-
const {
|
|
3045
|
-
const digest = MultisigConfig.getSignPayload({
|
|
3046
|
-
account,
|
|
3047
|
-
configId,
|
|
3048
|
-
payload,
|
|
3049
|
-
})
|
|
3053
|
+
const { genesisConfig, payload, signers } = parameters
|
|
3054
|
+
const digest = MultisigConfig.getSignPayload({ payload, genesisConfig })
|
|
3050
3055
|
const signatures = signers.map((signer) =>
|
|
3051
3056
|
SignatureEnvelope.from(
|
|
3052
3057
|
Secp256k1.sign({ payload: digest, privateKey: signer.privateKey }),
|
|
3053
3058
|
),
|
|
3054
3059
|
)
|
|
3055
3060
|
return SignatureEnvelope.sortMultisigApprovals({
|
|
3056
|
-
|
|
3057
|
-
configId,
|
|
3061
|
+
genesisConfig,
|
|
3058
3062
|
payload,
|
|
3059
3063
|
signatures,
|
|
3060
3064
|
})
|
|
3061
3065
|
}
|
|
3062
3066
|
|
|
3063
3067
|
test('behavior: bootstrap + spend (2-of-3 secp256k1)', async () => {
|
|
3064
|
-
const { account,
|
|
3068
|
+
const { account, genesisConfig, ownerKeys } = setup({
|
|
3065
3069
|
count: 3,
|
|
3066
3070
|
threshold: 2,
|
|
3067
3071
|
})
|
|
@@ -3083,15 +3087,12 @@ describe.skip('behavior: multisig (TIP-1061)', () => {
|
|
|
3083
3087
|
|
|
3084
3088
|
const bootstrap_signed = TxEnvelopeTempo.serialize(bootstrap, {
|
|
3085
3089
|
signature: SignatureEnvelope.from({
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
// The bootstrap config is carried by the signature `init`.
|
|
3090
|
-
init: config,
|
|
3090
|
+
genesisConfig,
|
|
3091
|
+
// Initialize multisig.
|
|
3092
|
+
init: true,
|
|
3091
3093
|
// Approve with 2 of the 3 owners to satisfy the threshold.
|
|
3092
3094
|
signatures: approve({
|
|
3093
|
-
|
|
3094
|
-
configId,
|
|
3095
|
+
genesisConfig,
|
|
3095
3096
|
payload: TxEnvelopeTempo.getSignPayload(bootstrap),
|
|
3096
3097
|
signers: [ownerKeys[0]!, ownerKeys[1]!],
|
|
3097
3098
|
}),
|
|
@@ -3121,7 +3122,7 @@ describe.skip('behavior: multisig (TIP-1061)', () => {
|
|
|
3121
3122
|
// The bootstrap config is carried by the multisig signature `init`.
|
|
3122
3123
|
expect(
|
|
3123
3124
|
(response.signature as SignatureEnvelope.Multisig | undefined)?.init,
|
|
3124
|
-
).toEqual(
|
|
3125
|
+
).toEqual(genesisConfig)
|
|
3125
3126
|
}
|
|
3126
3127
|
|
|
3127
3128
|
// Spend (subsequent transaction): no signature `init`, nonce 1, uses the
|
|
@@ -3143,13 +3144,10 @@ describe.skip('behavior: multisig (TIP-1061)', () => {
|
|
|
3143
3144
|
|
|
3144
3145
|
const spend_signed = TxEnvelopeTempo.serialize(spend, {
|
|
3145
3146
|
signature: SignatureEnvelope.from({
|
|
3146
|
-
|
|
3147
|
-
account,
|
|
3148
|
-
configId,
|
|
3147
|
+
genesisConfig,
|
|
3149
3148
|
// A different 2-of-3 subset still authorizes the transaction.
|
|
3150
3149
|
signatures: approve({
|
|
3151
|
-
|
|
3152
|
-
configId,
|
|
3150
|
+
genesisConfig,
|
|
3153
3151
|
payload: TxEnvelopeTempo.getSignPayload(spend),
|
|
3154
3152
|
signers: [ownerKeys[1]!, ownerKeys[2]!],
|
|
3155
3153
|
}),
|
|
@@ -3168,7 +3166,7 @@ describe.skip('behavior: multisig (TIP-1061)', () => {
|
|
|
3168
3166
|
})
|
|
3169
3167
|
|
|
3170
3168
|
test('behavior: rejects below-threshold approvals', async () => {
|
|
3171
|
-
const { account,
|
|
3169
|
+
const { account, genesisConfig, ownerKeys } = setup({
|
|
3172
3170
|
count: 3,
|
|
3173
3171
|
threshold: 2,
|
|
3174
3172
|
})
|
|
@@ -3187,15 +3185,12 @@ describe.skip('behavior: multisig (TIP-1061)', () => {
|
|
|
3187
3185
|
|
|
3188
3186
|
const serialized_signed = TxEnvelopeTempo.serialize(bootstrap, {
|
|
3189
3187
|
signature: SignatureEnvelope.from({
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
// The bootstrap config is carried by the signature `init`.
|
|
3194
|
-
init: config,
|
|
3188
|
+
genesisConfig,
|
|
3189
|
+
// Opt into bootstrap: writes `genesisConfig` into the signature `init`.
|
|
3190
|
+
init: true,
|
|
3195
3191
|
// Only one approval — below the threshold of 2.
|
|
3196
3192
|
signatures: approve({
|
|
3197
|
-
|
|
3198
|
-
configId,
|
|
3193
|
+
genesisConfig,
|
|
3199
3194
|
payload: TxEnvelopeTempo.getSignPayload(bootstrap),
|
|
3200
3195
|
signers: [ownerKeys[0]!],
|
|
3201
3196
|
}),
|
|
@@ -3209,4 +3204,224 @@ describe.skip('behavior: multisig (TIP-1061)', () => {
|
|
|
3209
3204
|
}),
|
|
3210
3205
|
).rejects.toThrow()
|
|
3211
3206
|
})
|
|
3207
|
+
|
|
3208
|
+
test('behavior: keychain access key (authorize via AccountKeychain + spend)', async () => {
|
|
3209
|
+
const { account, genesisConfig, ownerKeys } = setup({
|
|
3210
|
+
count: 2,
|
|
3211
|
+
threshold: 2,
|
|
3212
|
+
})
|
|
3213
|
+
|
|
3214
|
+
await fundAddress(client, { address: account })
|
|
3215
|
+
|
|
3216
|
+
// Fresh secp256k1 access key for the multisig account.
|
|
3217
|
+
const access = (() => {
|
|
3218
|
+
const privateKey = Secp256k1.randomPrivateKey()
|
|
3219
|
+
const address = Address.fromPublicKey(
|
|
3220
|
+
Secp256k1.getPublicKey({ privateKey }),
|
|
3221
|
+
)
|
|
3222
|
+
return { address, privateKey } as const
|
|
3223
|
+
})()
|
|
3224
|
+
|
|
3225
|
+
// Bootstrap the multisig (nonce 0).
|
|
3226
|
+
const bootstrap = TxEnvelopeTempo.from({
|
|
3227
|
+
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
3228
|
+
chainId,
|
|
3229
|
+
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
3230
|
+
nonce: 0n,
|
|
3231
|
+
gas: 2_000_000n,
|
|
3232
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
3233
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
3234
|
+
})
|
|
3235
|
+
|
|
3236
|
+
const bootstrap_signed = TxEnvelopeTempo.serialize(bootstrap, {
|
|
3237
|
+
signature: SignatureEnvelope.from({
|
|
3238
|
+
genesisConfig,
|
|
3239
|
+
init: true,
|
|
3240
|
+
signatures: approve({
|
|
3241
|
+
genesisConfig,
|
|
3242
|
+
payload: TxEnvelopeTempo.getSignPayload(bootstrap),
|
|
3243
|
+
signers: ownerKeys,
|
|
3244
|
+
}),
|
|
3245
|
+
}),
|
|
3246
|
+
})
|
|
3247
|
+
|
|
3248
|
+
const bootstrap_receipt = (await client
|
|
3249
|
+
.request({
|
|
3250
|
+
method: 'eth_sendRawTransactionSync',
|
|
3251
|
+
params: [bootstrap_signed],
|
|
3252
|
+
})
|
|
3253
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
3254
|
+
expect(bootstrap_receipt.status).toBe('success')
|
|
3255
|
+
|
|
3256
|
+
// Authorize the access key (nonce 1): multisig transactions cannot carry
|
|
3257
|
+
// a TIP-1049 `keyAuthorization`, so the multisig account calls the
|
|
3258
|
+
// AccountKeychain precompile's `authorizeKey` directly.
|
|
3259
|
+
const authorizeKey = AbiFunction.from(
|
|
3260
|
+
'function authorizeKey(address keyId, uint8 signatureType, (uint64 expiry, bool enforceLimits, (address token, uint256 amount, uint64 period)[] limits, bool allowAnyCalls, (address target, (bytes4 selector, address[] recipients)[] selectorRules)[] allowedCalls) config)',
|
|
3261
|
+
)
|
|
3262
|
+
|
|
3263
|
+
const authorize = TxEnvelopeTempo.from({
|
|
3264
|
+
calls: [
|
|
3265
|
+
{
|
|
3266
|
+
to: '0xaaaaaaaa00000000000000000000000000000000',
|
|
3267
|
+
data: AbiFunction.encodeData(authorizeKey, [
|
|
3268
|
+
access.address,
|
|
3269
|
+
0, // secp256k1
|
|
3270
|
+
{
|
|
3271
|
+
expiry: 18446744073709551615n, // u64 max (never expires)
|
|
3272
|
+
enforceLimits: false,
|
|
3273
|
+
limits: [],
|
|
3274
|
+
allowAnyCalls: true,
|
|
3275
|
+
allowedCalls: [],
|
|
3276
|
+
},
|
|
3277
|
+
]),
|
|
3278
|
+
},
|
|
3279
|
+
],
|
|
3280
|
+
chainId,
|
|
3281
|
+
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
3282
|
+
nonce: 1n,
|
|
3283
|
+
gas: 2_000_000n,
|
|
3284
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
3285
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
3286
|
+
})
|
|
3287
|
+
|
|
3288
|
+
const authorize_signed = TxEnvelopeTempo.serialize(authorize, {
|
|
3289
|
+
signature: SignatureEnvelope.from({
|
|
3290
|
+
genesisConfig,
|
|
3291
|
+
signatures: approve({
|
|
3292
|
+
genesisConfig,
|
|
3293
|
+
payload: TxEnvelopeTempo.getSignPayload(authorize),
|
|
3294
|
+
signers: ownerKeys,
|
|
3295
|
+
}),
|
|
3296
|
+
}),
|
|
3297
|
+
})
|
|
3298
|
+
|
|
3299
|
+
const authorize_receipt = (await client
|
|
3300
|
+
.request({
|
|
3301
|
+
method: 'eth_sendRawTransactionSync',
|
|
3302
|
+
params: [authorize_signed],
|
|
3303
|
+
})
|
|
3304
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
3305
|
+
expect(authorize_receipt.status).toBe('success')
|
|
3306
|
+
expect(authorize_receipt.from).toBe(account)
|
|
3307
|
+
|
|
3308
|
+
// Spend with the access key (nonce 2): outer keychain signature with the
|
|
3309
|
+
// multisig account as `userAddress` — no owner approvals involved.
|
|
3310
|
+
const nonce = await getTransactionCount(client, {
|
|
3311
|
+
address: account,
|
|
3312
|
+
blockTag: 'pending',
|
|
3313
|
+
})
|
|
3314
|
+
|
|
3315
|
+
const spend = TxEnvelopeTempo.from({
|
|
3316
|
+
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
3317
|
+
chainId,
|
|
3318
|
+
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
3319
|
+
nonce: BigInt(nonce),
|
|
3320
|
+
gas: 2_000_000n,
|
|
3321
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
3322
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
3323
|
+
})
|
|
3324
|
+
|
|
3325
|
+
const spend_signature = Secp256k1.sign({
|
|
3326
|
+
payload: TxEnvelopeTempo.getSignPayload(spend, { from: account }),
|
|
3327
|
+
privateKey: access.privateKey,
|
|
3328
|
+
})
|
|
3329
|
+
|
|
3330
|
+
const spend_signed = TxEnvelopeTempo.serialize(spend, {
|
|
3331
|
+
signature: SignatureEnvelope.from({
|
|
3332
|
+
userAddress: account,
|
|
3333
|
+
inner: SignatureEnvelope.from(spend_signature),
|
|
3334
|
+
type: 'keychain',
|
|
3335
|
+
}),
|
|
3336
|
+
})
|
|
3337
|
+
|
|
3338
|
+
const spend_receipt = (await client
|
|
3339
|
+
.request({
|
|
3340
|
+
method: 'eth_sendRawTransactionSync',
|
|
3341
|
+
params: [spend_signed],
|
|
3342
|
+
})
|
|
3343
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
3344
|
+
expect(spend_receipt.status).toBe('success')
|
|
3345
|
+
expect(spend_receipt.from).toBe(account)
|
|
3346
|
+
|
|
3347
|
+
{
|
|
3348
|
+
const response = await client
|
|
3349
|
+
.request({
|
|
3350
|
+
method: 'eth_getTransactionByHash',
|
|
3351
|
+
params: [spend_receipt.transactionHash],
|
|
3352
|
+
})
|
|
3353
|
+
.then((tx) => Transaction.fromRpc(tx as any))
|
|
3354
|
+
if (!response) throw new Error()
|
|
3355
|
+
expect(response.from).toBe(account)
|
|
3356
|
+
expect(response.signature?.type).toBe('keychain')
|
|
3357
|
+
expect(
|
|
3358
|
+
(response.signature as SignatureEnvelope.Keychain | undefined)
|
|
3359
|
+
?.userAddress,
|
|
3360
|
+
).toBe(account)
|
|
3361
|
+
}
|
|
3362
|
+
})
|
|
3363
|
+
|
|
3364
|
+
test('behavior: rejects `keyAuthorization` on multisig transactions', async () => {
|
|
3365
|
+
const { account, genesisConfig, ownerKeys } = setup({
|
|
3366
|
+
count: 1,
|
|
3367
|
+
threshold: 1,
|
|
3368
|
+
})
|
|
3369
|
+
|
|
3370
|
+
await fundAddress(client, { address: account })
|
|
3371
|
+
|
|
3372
|
+
const access = (() => {
|
|
3373
|
+
const privateKey = Secp256k1.randomPrivateKey()
|
|
3374
|
+
const address = Address.fromPublicKey(
|
|
3375
|
+
Secp256k1.getPublicKey({ privateKey }),
|
|
3376
|
+
)
|
|
3377
|
+
return { address, privateKey } as const
|
|
3378
|
+
})()
|
|
3379
|
+
|
|
3380
|
+
// TIP-1049 key authorizations are only valid on primitive/keychain
|
|
3381
|
+
// transactions; multisig accounts must call the AccountKeychain
|
|
3382
|
+
// precompile instead.
|
|
3383
|
+
const keyAuth = KeyAuthorization.from({
|
|
3384
|
+
address: access.address,
|
|
3385
|
+
chainId: BigInt(chainId),
|
|
3386
|
+
type: 'secp256k1',
|
|
3387
|
+
})
|
|
3388
|
+
const keyAuth_signed = KeyAuthorization.from(keyAuth, {
|
|
3389
|
+
signature: SignatureEnvelope.from(
|
|
3390
|
+
Secp256k1.sign({
|
|
3391
|
+
payload: KeyAuthorization.getSignPayload(keyAuth),
|
|
3392
|
+
privateKey: ownerKeys[0]!.privateKey,
|
|
3393
|
+
}),
|
|
3394
|
+
),
|
|
3395
|
+
})
|
|
3396
|
+
|
|
3397
|
+
const bootstrap = TxEnvelopeTempo.from({
|
|
3398
|
+
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
3399
|
+
chainId,
|
|
3400
|
+
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
3401
|
+
keyAuthorization: keyAuth_signed,
|
|
3402
|
+
nonce: 0n,
|
|
3403
|
+
gas: 2_000_000n,
|
|
3404
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
3405
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
3406
|
+
})
|
|
3407
|
+
|
|
3408
|
+
const serialized_signed = TxEnvelopeTempo.serialize(bootstrap, {
|
|
3409
|
+
signature: SignatureEnvelope.from({
|
|
3410
|
+
genesisConfig,
|
|
3411
|
+
init: true,
|
|
3412
|
+
signatures: approve({
|
|
3413
|
+
genesisConfig,
|
|
3414
|
+
payload: TxEnvelopeTempo.getSignPayload(bootstrap),
|
|
3415
|
+
signers: ownerKeys,
|
|
3416
|
+
}),
|
|
3417
|
+
}),
|
|
3418
|
+
})
|
|
3419
|
+
|
|
3420
|
+
await expect(
|
|
3421
|
+
client.request({
|
|
3422
|
+
method: 'eth_sendRawTransactionSync',
|
|
3423
|
+
params: [serialized_signed],
|
|
3424
|
+
}),
|
|
3425
|
+
).rejects.toThrow()
|
|
3426
|
+
})
|
|
3212
3427
|
})
|
package/tempo/index.ts
CHANGED
|
@@ -105,8 +105,8 @@ export * as KeyAuthorization from './KeyAuthorization.js'
|
|
|
105
105
|
/**
|
|
106
106
|
* Native multisig account utilities (TIP-1061).
|
|
107
107
|
*
|
|
108
|
-
* Derives stable multisig account addresses
|
|
109
|
-
*
|
|
108
|
+
* Derives stable multisig account addresses from a weighted owner
|
|
109
|
+
* configuration, and computes the owner approval digest that owners sign.
|
|
110
110
|
*
|
|
111
111
|
* [TIP-1061](https://tips.sh/1061)
|
|
112
112
|
*
|
|
@@ -114,7 +114,7 @@ export * as KeyAuthorization from './KeyAuthorization.js'
|
|
|
114
114
|
* ```ts twoslash
|
|
115
115
|
* import { MultisigConfig } from 'ox/tempo'
|
|
116
116
|
*
|
|
117
|
-
* const
|
|
117
|
+
* const genesisConfig = MultisigConfig.from({
|
|
118
118
|
* threshold: 2,
|
|
119
119
|
* owners: [
|
|
120
120
|
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
@@ -122,7 +122,7 @@ export * as KeyAuthorization from './KeyAuthorization.js'
|
|
|
122
122
|
* ],
|
|
123
123
|
* })
|
|
124
124
|
*
|
|
125
|
-
* const account = MultisigConfig.getAddress(
|
|
125
|
+
* const account = MultisigConfig.getAddress(genesisConfig)
|
|
126
126
|
* ```
|
|
127
127
|
*
|
|
128
128
|
* @category Reference
|
package/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** @internal */
|
|
2
|
-
export const version = '0.14.
|
|
2
|
+
export const version = '0.14.30'
|