@openzeppelin/miden-multisig-client 0.13.3 → 0.14.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -13
- package/dist/account/builder.d.ts +3 -3
- package/dist/account/builder.d.ts.map +1 -1
- package/dist/account/builder.js +10 -10
- package/dist/account/builder.js.map +1 -1
- package/dist/account/builder.test.js +16 -14
- package/dist/account/builder.test.js.map +1 -1
- package/dist/account/masm/account-components/auth.d.ts +4 -4
- package/dist/account/masm/account-components/auth.d.ts.map +1 -1
- package/dist/account/masm/account-components/auth.js +8 -12
- package/dist/account/masm/account-components/auth.js.map +1 -1
- package/dist/account/masm/auth.d.ts +4 -4
- package/dist/account/masm/auth.d.ts.map +1 -1
- package/dist/account/masm/auth.js +226 -124
- package/dist/account/masm/auth.js.map +1 -1
- package/dist/account/storage.d.ts.map +1 -1
- package/dist/account/storage.js +24 -6
- package/dist/account/storage.js.map +1 -1
- package/dist/account/storage.test.d.ts +2 -0
- package/dist/account/storage.test.d.ts.map +1 -0
- package/dist/account/storage.test.js +73 -0
- package/dist/account/storage.test.js.map +1 -0
- package/dist/client.d.ts +9 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +24 -14
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +45 -8
- package/dist/client.test.js.map +1 -1
- package/dist/index.d.ts +6 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -12
- package/dist/index.js.map +1 -1
- package/dist/multisig/proposal/execution.d.ts +3 -2
- package/dist/multisig/proposal/execution.d.ts.map +1 -1
- package/dist/multisig/proposal/execution.js +8 -12
- package/dist/multisig/proposal/execution.js.map +1 -1
- package/dist/multisig.d.ts +10 -4
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +97 -71
- package/dist/multisig.js.map +1 -1
- package/dist/multisig.test.js +481 -29
- package/dist/multisig.test.js.map +1 -1
- package/dist/procedures.d.ts +8 -8
- package/dist/procedures.js +8 -8
- package/dist/raw-client.d.ts +13 -0
- package/dist/raw-client.d.ts.map +1 -0
- package/dist/raw-client.js +42 -0
- package/dist/raw-client.js.map +1 -0
- package/dist/raw-client.test.d.ts +2 -0
- package/dist/raw-client.test.d.ts.map +1 -0
- package/dist/raw-client.test.js +89 -0
- package/dist/raw-client.test.js.map +1 -0
- package/dist/signers/ecdsa.d.ts +3 -1
- package/dist/signers/ecdsa.d.ts.map +1 -1
- package/dist/signers/ecdsa.js +15 -1
- package/dist/signers/ecdsa.js.map +1 -1
- package/dist/signers/ecdsa.test.js +27 -4
- package/dist/signers/ecdsa.test.js.map +1 -1
- package/dist/signers/falcon.d.ts +3 -1
- package/dist/signers/falcon.d.ts.map +1 -1
- package/dist/signers/falcon.js +15 -1
- package/dist/signers/falcon.js.map +1 -1
- package/dist/signers/falcon.test.js +39 -3
- package/dist/signers/falcon.test.js.map +1 -1
- package/dist/transaction/consumeNotes.d.ts +2 -2
- package/dist/transaction/consumeNotes.d.ts.map +1 -1
- package/dist/transaction/consumeNotes.js +5 -4
- package/dist/transaction/consumeNotes.js.map +1 -1
- package/dist/transaction/options.d.ts +1 -0
- package/dist/transaction/options.d.ts.map +1 -1
- package/dist/transaction/p2id.d.ts.map +1 -1
- package/dist/transaction/p2id.js +7 -10
- package/dist/transaction/p2id.js.map +1 -1
- package/dist/transaction/p2id.test.js +19 -9
- package/dist/transaction/p2id.test.js.map +1 -1
- package/dist/transaction/summary.d.ts +2 -2
- package/dist/transaction/summary.d.ts.map +1 -1
- package/dist/transaction/summary.js +4 -2
- package/dist/transaction/summary.js.map +1 -1
- package/dist/transaction/updateGuardian.d.ts +2 -2
- package/dist/transaction/updateGuardian.d.ts.map +1 -1
- package/dist/transaction/updateGuardian.js +8 -11
- package/dist/transaction/updateGuardian.js.map +1 -1
- package/dist/transaction/updateProcedureThreshold.d.ts +2 -2
- package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
- package/dist/transaction/updateProcedureThreshold.js +8 -10
- package/dist/transaction/updateProcedureThreshold.js.map +1 -1
- package/dist/transaction/updateSigners.d.ts +2 -2
- package/dist/transaction/updateSigners.d.ts.map +1 -1
- package/dist/transaction/updateSigners.js +8 -10
- package/dist/transaction/updateSigners.js.map +1 -1
- package/dist/transaction.test.js +6 -6
- package/dist/transaction.test.js.map +1 -1
- package/dist/utils/key.d.ts.map +1 -1
- package/dist/utils/key.js +3 -3
- package/dist/utils/key.js.map +1 -1
- package/dist/utils/key.test.js +4 -2
- package/dist/utils/key.test.js.map +1 -1
- package/dist/utils/signature.d.ts +2 -0
- package/dist/utils/signature.d.ts.map +1 -1
- package/dist/utils/signature.js +10 -5
- package/dist/utils/signature.js.map +1 -1
- package/dist/utils/signature.test.js +9 -9
- package/dist/utils/signature.test.js.map +1 -1
- package/masm/account_components/auth/multisig.masm +2 -3
- package/masm/account_components/auth/multisig_ecdsa.masm +2 -3
- package/masm/account_components/auth/multisig_guardian.masm +2 -3
- package/masm/account_components/auth/multisig_guardian_ecdsa.masm +2 -3
- package/masm/auth/guardian.masm +30 -8
- package/masm/auth/guardian_ecdsa.masm +24 -7
- package/masm/auth/multisig.masm +86 -55
- package/masm/auth/multisig_ecdsa.masm +86 -54
- package/package.json +3 -3
- package/src/account/builder.test.ts +16 -14
- package/src/account/builder.ts +13 -12
- package/src/account/masm/account-components/auth.ts +8 -12
- package/src/account/masm/auth.ts +226 -124
- package/src/account/storage.test.ts +90 -0
- package/src/account/storage.ts +27 -6
- package/src/client.test.ts +52 -8
- package/src/client.ts +39 -16
- package/src/index.ts +6 -12
- package/src/multisig/proposal/execution.ts +18 -14
- package/src/multisig.test.ts +572 -29
- package/src/multisig.ts +121 -83
- package/src/procedures.ts +8 -8
- package/src/raw-client.test.ts +133 -0
- package/src/raw-client.ts +75 -0
- package/src/signers/ecdsa.test.ts +35 -4
- package/src/signers/ecdsa.ts +18 -2
- package/src/signers/falcon.test.ts +50 -3
- package/src/signers/falcon.ts +18 -2
- package/src/transaction/consumeNotes.ts +16 -5
- package/src/transaction/options.ts +1 -0
- package/src/transaction/p2id.test.ts +19 -9
- package/src/transaction/p2id.ts +8 -13
- package/src/transaction/summary.ts +11 -4
- package/src/transaction/updateGuardian.ts +22 -14
- package/src/transaction/updateProcedureThreshold.ts +20 -15
- package/src/transaction/updateSigners.ts +22 -14
- package/src/transaction.test.ts +6 -6
- package/src/utils/key.test.ts +4 -2
- package/src/utils/key.ts +7 -3
- package/src/utils/signature.test.ts +10 -10
- package/src/utils/signature.ts +12 -5
package/src/multisig.ts
CHANGED
|
@@ -16,7 +16,12 @@ import type {
|
|
|
16
16
|
ProposalType,
|
|
17
17
|
} from './types.js';
|
|
18
18
|
import type { ProcedureName } from './procedures.js';
|
|
19
|
-
import type {
|
|
19
|
+
import type {
|
|
20
|
+
MidenClient,
|
|
21
|
+
TransactionProver,
|
|
22
|
+
TransactionRequest,
|
|
23
|
+
WasmWebClient,
|
|
24
|
+
} from '@miden-sdk/miden-sdk';
|
|
20
25
|
import {
|
|
21
26
|
Account,
|
|
22
27
|
AccountId,
|
|
@@ -53,6 +58,7 @@ import { AccountInspector } from './inspector.js';
|
|
|
53
58
|
import { ProposalFactory } from './proposal/factory.js';
|
|
54
59
|
import { ProposalMetadataCodec } from './proposal/metadata.js';
|
|
55
60
|
import { ProposalSignatures } from './proposal/signatures.js';
|
|
61
|
+
import { getRawMidenClient, getTransactionProver } from './raw-client.js';
|
|
56
62
|
|
|
57
63
|
/**
|
|
58
64
|
* Result of fetching account state from GUARDIAN.
|
|
@@ -87,7 +93,9 @@ export class Multisig {
|
|
|
87
93
|
|
|
88
94
|
private guardian: GuardianHttpClient;
|
|
89
95
|
private readonly signer: Signer;
|
|
90
|
-
private readonly
|
|
96
|
+
private readonly midenClient: MidenClient;
|
|
97
|
+
private readonly rawClientPromise: Promise<WasmWebClient>;
|
|
98
|
+
private readonly transactionProver: TransactionProver | null;
|
|
91
99
|
private readonly _accountId: string;
|
|
92
100
|
private readonly midenRpcEndpoint?: string;
|
|
93
101
|
private proposals: Map<string, Proposal> = new Map();
|
|
@@ -97,7 +105,7 @@ export class Multisig {
|
|
|
97
105
|
config: MultisigConfig,
|
|
98
106
|
guardian: GuardianHttpClient,
|
|
99
107
|
signer: Signer,
|
|
100
|
-
|
|
108
|
+
midenClient: MidenClient,
|
|
101
109
|
accountId?: string,
|
|
102
110
|
midenRpcEndpoint?: string
|
|
103
111
|
) {
|
|
@@ -111,9 +119,11 @@ export class Multisig {
|
|
|
111
119
|
);
|
|
112
120
|
this.guardian = guardian;
|
|
113
121
|
this.signer = signer;
|
|
114
|
-
this.
|
|
122
|
+
this.midenClient = midenClient;
|
|
115
123
|
this._accountId = accountId ?? (account ? accountIdToHex(account) : '');
|
|
116
124
|
this.midenRpcEndpoint = midenRpcEndpoint;
|
|
125
|
+
this.rawClientPromise = getRawMidenClient(midenClient, midenRpcEndpoint);
|
|
126
|
+
this.transactionProver = getTransactionProver(midenClient);
|
|
117
127
|
}
|
|
118
128
|
|
|
119
129
|
private getMidenRpcEndpoint(): string {
|
|
@@ -123,6 +133,10 @@ export class Multisig {
|
|
|
123
133
|
return this.midenRpcEndpoint;
|
|
124
134
|
}
|
|
125
135
|
|
|
136
|
+
private async getRawClient(): Promise<WasmWebClient> {
|
|
137
|
+
return this.rawClientPromise;
|
|
138
|
+
}
|
|
139
|
+
|
|
126
140
|
private proposalFactory(): ProposalFactory {
|
|
127
141
|
return new ProposalFactory({
|
|
128
142
|
accountId: this._accountId,
|
|
@@ -228,7 +242,7 @@ export class Multisig {
|
|
|
228
242
|
}
|
|
229
243
|
|
|
230
244
|
/**
|
|
231
|
-
* Sync account state from GUARDIAN into the local
|
|
245
|
+
* Sync account state from GUARDIAN into the local Miden client store.
|
|
232
246
|
*
|
|
233
247
|
* If the GUARDIAN commitment differs from the local commitment (or the account
|
|
234
248
|
* is missing locally), the local store is overwritten with the GUARDIAN state.
|
|
@@ -236,19 +250,20 @@ export class Multisig {
|
|
|
236
250
|
async syncState(): Promise<AccountState> {
|
|
237
251
|
const state = await this.fetchState();
|
|
238
252
|
const accountId = AccountId.fromHex(this._accountId);
|
|
239
|
-
const
|
|
253
|
+
const webClient = await this.getRawClient();
|
|
254
|
+
const localAccount = await webClient.getAccount(accountId);
|
|
240
255
|
let accountForConfigRefresh: Account | null = localAccount ?? null;
|
|
241
256
|
|
|
242
257
|
const guardianCommitment = normalizeHexWord(state.commitment);
|
|
243
258
|
const localCommitment = localAccount
|
|
244
|
-
? normalizeHexWord(localAccount.
|
|
259
|
+
? normalizeHexWord(localAccount.to_commitment().toHex())
|
|
245
260
|
: null;
|
|
246
261
|
|
|
247
262
|
if (!localAccount || localCommitment !== guardianCommitment) {
|
|
248
263
|
const accountBytes = base64ToUint8Array(state.stateDataBase64);
|
|
249
264
|
const incomingAccount = Account.deserialize(accountBytes);
|
|
250
265
|
await this.ensureSafeToOverwriteLocalState(incomingAccount, localAccount);
|
|
251
|
-
await
|
|
266
|
+
await webClient.newAccount(incomingAccount, true);
|
|
252
267
|
accountForConfigRefresh = incomingAccount;
|
|
253
268
|
}
|
|
254
269
|
|
|
@@ -259,7 +274,8 @@ export class Multisig {
|
|
|
259
274
|
|
|
260
275
|
async verifyStateCommitment(): Promise<AccountStateVerificationResult> {
|
|
261
276
|
const accountId = AccountId.fromHex(this._accountId);
|
|
262
|
-
const
|
|
277
|
+
const webClient = await this.getRawClient();
|
|
278
|
+
const localAccount = await webClient.getAccount(accountId);
|
|
263
279
|
|
|
264
280
|
if (!localAccount) {
|
|
265
281
|
throw new Error(
|
|
@@ -267,7 +283,7 @@ export class Multisig {
|
|
|
267
283
|
);
|
|
268
284
|
}
|
|
269
285
|
|
|
270
|
-
const localCommitment = normalizeHexWord(localAccount.
|
|
286
|
+
const localCommitment = normalizeHexWord(localAccount.to_commitment().toHex());
|
|
271
287
|
const onChainCommitment = await this.getOnChainCommitment(accountId);
|
|
272
288
|
|
|
273
289
|
if (!onChainCommitment) {
|
|
@@ -308,7 +324,7 @@ export class Multisig {
|
|
|
308
324
|
return;
|
|
309
325
|
}
|
|
310
326
|
|
|
311
|
-
const incomingCommitment = normalizeHexWord(incomingAccount.
|
|
327
|
+
const incomingCommitment = normalizeHexWord(incomingAccount.to_commitment().toHex());
|
|
312
328
|
if (incomingCommitment !== onChainCommitment) {
|
|
313
329
|
throw new Error(
|
|
314
330
|
`Refusing to overwrite local state: incoming commitment does not match on-chain commitment for account ${this._accountId}`
|
|
@@ -472,17 +488,18 @@ export class Multisig {
|
|
|
472
488
|
nonce?: number,
|
|
473
489
|
newThreshold?: number,
|
|
474
490
|
): Promise<Proposal> {
|
|
491
|
+
const webClient = await this.getRawClient();
|
|
475
492
|
const targetThreshold = newThreshold ?? this.threshold;
|
|
476
493
|
const targetSignerCommitments = [...this.signerCommitments, newCommitment];
|
|
477
494
|
|
|
478
495
|
const { request, salt } = await buildUpdateSignersTransactionRequest(
|
|
479
|
-
|
|
496
|
+
webClient,
|
|
480
497
|
targetThreshold,
|
|
481
498
|
targetSignerCommitments,
|
|
482
499
|
{ signatureScheme: this.signer.scheme },
|
|
483
500
|
);
|
|
484
501
|
|
|
485
|
-
const summary = await executeForSummary(
|
|
502
|
+
const summary = await executeForSummary(webClient, this._accountId, request);
|
|
486
503
|
const summaryBase64 = uint8ArrayToBase64(summary.serialize());
|
|
487
504
|
const proposalNonce = nonce ?? Date.now();
|
|
488
505
|
|
|
@@ -510,6 +527,7 @@ export class Multisig {
|
|
|
510
527
|
nonce?: number,
|
|
511
528
|
newThreshold?: number,
|
|
512
529
|
): Promise<Proposal> {
|
|
530
|
+
const webClient = await this.getRawClient();
|
|
513
531
|
const normalizedRemove = signerToRemove.toLowerCase();
|
|
514
532
|
const targetSignerCommitments = this.signerCommitments.filter(
|
|
515
533
|
(c) => c.toLowerCase() !== normalizedRemove
|
|
@@ -531,13 +549,13 @@ export class Multisig {
|
|
|
531
549
|
}
|
|
532
550
|
|
|
533
551
|
const { request, salt } = await buildUpdateSignersTransactionRequest(
|
|
534
|
-
|
|
552
|
+
webClient,
|
|
535
553
|
targetThreshold,
|
|
536
554
|
targetSignerCommitments,
|
|
537
555
|
{ signatureScheme: this.signer.scheme },
|
|
538
556
|
);
|
|
539
557
|
|
|
540
|
-
const summary = await executeForSummary(
|
|
558
|
+
const summary = await executeForSummary(webClient, this._accountId, request);
|
|
541
559
|
const summaryBase64 = uint8ArrayToBase64(summary.serialize());
|
|
542
560
|
const proposalNonce = nonce ?? Date.now();
|
|
543
561
|
|
|
@@ -563,6 +581,7 @@ export class Multisig {
|
|
|
563
581
|
newThreshold: number,
|
|
564
582
|
nonce?: number,
|
|
565
583
|
): Promise<Proposal> {
|
|
584
|
+
const webClient = await this.getRawClient();
|
|
566
585
|
if (newThreshold < 1 || newThreshold > this.signerCommitments.length) {
|
|
567
586
|
throw new Error(
|
|
568
587
|
`Invalid threshold ${newThreshold}. Must be between 1 and ${this.signerCommitments.length}`
|
|
@@ -574,13 +593,13 @@ export class Multisig {
|
|
|
574
593
|
}
|
|
575
594
|
|
|
576
595
|
const { request, salt } = await buildUpdateSignersTransactionRequest(
|
|
577
|
-
|
|
596
|
+
webClient,
|
|
578
597
|
newThreshold,
|
|
579
598
|
this.signerCommitments,
|
|
580
599
|
{ signatureScheme: this.signer.scheme },
|
|
581
600
|
);
|
|
582
601
|
|
|
583
|
-
const summary = await executeForSummary(
|
|
602
|
+
const summary = await executeForSummary(webClient, this._accountId, request);
|
|
584
603
|
const summaryBase64 = uint8ArrayToBase64(summary.serialize());
|
|
585
604
|
const proposalNonce = nonce ?? Date.now();
|
|
586
605
|
|
|
@@ -601,6 +620,7 @@ export class Multisig {
|
|
|
601
620
|
targetThreshold: number,
|
|
602
621
|
nonce?: number,
|
|
603
622
|
): Promise<Proposal> {
|
|
623
|
+
const webClient = await this.getRawClient();
|
|
604
624
|
if (targetThreshold < 0 || targetThreshold > this.signerCommitments.length) {
|
|
605
625
|
throw new Error(
|
|
606
626
|
`Invalid threshold ${targetThreshold}. Must be between 0 and ${this.signerCommitments.length}`
|
|
@@ -619,13 +639,13 @@ export class Multisig {
|
|
|
619
639
|
}
|
|
620
640
|
|
|
621
641
|
const { request, salt } = await buildUpdateProcedureThresholdTransactionRequest(
|
|
622
|
-
|
|
642
|
+
webClient,
|
|
623
643
|
targetProcedure,
|
|
624
644
|
targetThreshold,
|
|
625
645
|
{ signatureScheme: this.signer.scheme },
|
|
626
646
|
);
|
|
627
647
|
|
|
628
|
-
const summary = await executeForSummary(
|
|
648
|
+
const summary = await executeForSummary(webClient, this._accountId, request);
|
|
629
649
|
const summaryBase64 = uint8ArrayToBase64(summary.serialize());
|
|
630
650
|
const proposalNonce = nonce ?? Date.now();
|
|
631
651
|
const action = targetThreshold === 0
|
|
@@ -656,15 +676,16 @@ export class Multisig {
|
|
|
656
676
|
newGuardianPubkey: string,
|
|
657
677
|
nonce?: number,
|
|
658
678
|
): Promise<Proposal> {
|
|
679
|
+
const webClient = await this.getRawClient();
|
|
659
680
|
await this.verifyGuardianEndpointCommitment(newGuardianEndpoint, newGuardianPubkey);
|
|
660
681
|
|
|
661
682
|
const { request, salt } = await buildUpdateGuardianTransactionRequest(
|
|
662
|
-
|
|
683
|
+
webClient,
|
|
663
684
|
newGuardianPubkey,
|
|
664
685
|
{ signatureScheme: this.signer.scheme },
|
|
665
686
|
);
|
|
666
687
|
|
|
667
|
-
const summary = await executeForSummary(
|
|
688
|
+
const summary = await executeForSummary(webClient, this._accountId, request);
|
|
668
689
|
const summaryBase64 = uint8ArrayToBase64(summary.serialize());
|
|
669
690
|
const proposalNonce = nonce ?? Date.now();
|
|
670
691
|
|
|
@@ -702,13 +723,14 @@ export class Multisig {
|
|
|
702
723
|
noteIds: string[],
|
|
703
724
|
nonce?: number,
|
|
704
725
|
): Promise<Proposal> {
|
|
726
|
+
const webClient = await this.getRawClient();
|
|
705
727
|
if (noteIds.length === 0) {
|
|
706
728
|
throw new Error('At least one note ID is required');
|
|
707
729
|
}
|
|
708
730
|
|
|
709
|
-
const { request, salt } = await buildConsumeNotesTransactionRequest(
|
|
731
|
+
const { request, salt } = await buildConsumeNotesTransactionRequest(webClient, noteIds);
|
|
710
732
|
|
|
711
|
-
const summary = await executeForSummary(
|
|
733
|
+
const summary = await executeForSummary(webClient, this._accountId, request);
|
|
712
734
|
const summaryBase64 = uint8ArrayToBase64(summary.serialize());
|
|
713
735
|
const proposalNonce = nonce ?? Date.now();
|
|
714
736
|
|
|
@@ -737,6 +759,7 @@ export class Multisig {
|
|
|
737
759
|
amount: bigint,
|
|
738
760
|
nonce?: number,
|
|
739
761
|
): Promise<Proposal> {
|
|
762
|
+
const webClient = await this.getRawClient();
|
|
740
763
|
if (amount <= 0n) {
|
|
741
764
|
throw new Error('Amount must be greater than 0');
|
|
742
765
|
}
|
|
@@ -748,7 +771,7 @@ export class Multisig {
|
|
|
748
771
|
amount,
|
|
749
772
|
);
|
|
750
773
|
|
|
751
|
-
const summary = await executeForSummary(
|
|
774
|
+
const summary = await executeForSummary(webClient, this._accountId, request);
|
|
752
775
|
const summaryBase64 = uint8ArrayToBase64(summary.serialize());
|
|
753
776
|
const proposalNonce = nonce ?? Date.now();
|
|
754
777
|
|
|
@@ -773,9 +796,10 @@ export class Multisig {
|
|
|
773
796
|
*/
|
|
774
797
|
async getConsumableNotes(): Promise<ConsumableNote[]> {
|
|
775
798
|
const accountId = AccountId.fromHex(this._accountId);
|
|
799
|
+
const webClient = await this.getRawClient();
|
|
776
800
|
|
|
777
801
|
// Get consumable notes for this account
|
|
778
|
-
const consumableRecords = await
|
|
802
|
+
const consumableRecords = await webClient.getConsumableNotes(accountId);
|
|
779
803
|
|
|
780
804
|
// Convert to our simplified ConsumableNote type
|
|
781
805
|
const notes: ConsumableNote[] = [];
|
|
@@ -866,24 +890,73 @@ export class Multisig {
|
|
|
866
890
|
return this.proposals.get(proposalId) ?? this.proposals.get(normalizedProposalId);
|
|
867
891
|
}
|
|
868
892
|
|
|
893
|
+
async createTransactionProposalRequest(proposalId: string): Promise<TransactionRequest> {
|
|
894
|
+
const { finalRequest } = await this.prepareProposalExecution(proposalId);
|
|
895
|
+
return finalRequest;
|
|
896
|
+
}
|
|
897
|
+
|
|
869
898
|
/**
|
|
870
899
|
* Execute a proposal that has enough signatures.
|
|
871
900
|
*
|
|
872
901
|
* @param proposalId - The proposal commitment/ID
|
|
873
902
|
*/
|
|
874
903
|
async executeProposal(proposalId: string): Promise<void> {
|
|
875
|
-
const proposal = this.
|
|
904
|
+
const { metadata, finalRequest, proposal } = await this.prepareProposalExecution(proposalId);
|
|
905
|
+
|
|
906
|
+
const accountId = AccountId.fromHex(this._accountId);
|
|
907
|
+
await this.midenClient.transactions.submit(accountId, finalRequest);
|
|
908
|
+
|
|
909
|
+
if (metadata.proposalType === 'switch_guardian') {
|
|
910
|
+
if (!metadata.newGuardianEndpoint || !metadata.newGuardianPubkey) {
|
|
911
|
+
throw new Error('Switch GUARDIAN proposal metadata is incomplete after execution');
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
try {
|
|
915
|
+
const webClient = await this.getRawClient();
|
|
916
|
+
await webClient.syncState();
|
|
917
|
+
|
|
918
|
+
const updatedAccount = await webClient.getAccount(accountId);
|
|
919
|
+
if (!updatedAccount) {
|
|
920
|
+
throw new Error(
|
|
921
|
+
`Updated account ${this._accountId} is missing from local client`
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const updatedStateBase64 = uint8ArrayToBase64(updatedAccount.serialize());
|
|
926
|
+
const nextGuardian = new GuardianHttpClient(metadata.newGuardianEndpoint);
|
|
927
|
+
this.setGuardianClient(nextGuardian);
|
|
928
|
+
this.guardianPublicKey = metadata.newGuardianPubkey;
|
|
929
|
+
|
|
930
|
+
await this.registerOnGuardian(updatedStateBase64);
|
|
931
|
+
} catch (error) {
|
|
932
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
933
|
+
throw new Error(
|
|
934
|
+
`Transaction executed successfully but failed to register on new GUARDIAN: ${message}`
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
proposal.status = 'finalized';
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
private getLocalProposal(proposalId: string): Proposal | undefined {
|
|
943
|
+
const normalizedProposalId = normalizeHexWord(proposalId);
|
|
944
|
+
return this.proposals.get(proposalId) ?? this.proposals.get(normalizedProposalId);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
private async prepareProposalExecution(
|
|
948
|
+
proposalId: string,
|
|
949
|
+
): Promise<{ finalRequest: TransactionRequest; metadata: ProposalMetadata; proposal: Proposal }> {
|
|
950
|
+
const proposal = this.getLocalProposal(proposalId);
|
|
876
951
|
if (!proposal) {
|
|
877
952
|
throw new Error(`Proposal not found: ${proposalId}`);
|
|
878
953
|
}
|
|
879
954
|
|
|
955
|
+
this.proposalFactory().assertAccountId(proposal.accountId);
|
|
880
956
|
await this.verifyProposalMetadataBinding(proposal);
|
|
881
957
|
|
|
882
|
-
const
|
|
883
|
-
const effectiveThreshold = proposalType
|
|
884
|
-
? this.getEffectiveThreshold(proposalType)
|
|
885
|
-
: this.threshold;
|
|
886
|
-
|
|
958
|
+
const metadata = proposal.metadata;
|
|
959
|
+
const effectiveThreshold = this.getEffectiveThreshold(metadata.proposalType);
|
|
887
960
|
const signatureContext = `Invalid proposal signatures for ${proposalId}`;
|
|
888
961
|
const signaturesForExecution = new ProposalSignatures(
|
|
889
962
|
proposal.signatures,
|
|
@@ -895,7 +968,8 @@ export class Multisig {
|
|
|
895
968
|
throw new Error('Proposal is not ready for execution. Still pending signatures.');
|
|
896
969
|
}
|
|
897
970
|
|
|
898
|
-
const isSwitchGuardian = proposalType === 'switch_guardian';
|
|
971
|
+
const isSwitchGuardian = metadata.proposalType === 'switch_guardian';
|
|
972
|
+
const normalizedProposalId = normalizeHexWord(proposal.id);
|
|
899
973
|
|
|
900
974
|
let txSummaryBase64: string;
|
|
901
975
|
let delta: DeltaObject | undefined;
|
|
@@ -903,7 +977,7 @@ export class Multisig {
|
|
|
903
977
|
if (isSwitchGuardian) {
|
|
904
978
|
txSummaryBase64 = proposal.txSummary;
|
|
905
979
|
} else {
|
|
906
|
-
delta = await this.guardian.getDeltaProposal(this._accountId,
|
|
980
|
+
delta = await this.guardian.getDeltaProposal(this._accountId, normalizedProposalId);
|
|
907
981
|
txSummaryBase64 = delta.deltaPayload.txSummary.data;
|
|
908
982
|
}
|
|
909
983
|
|
|
@@ -911,12 +985,13 @@ export class Multisig {
|
|
|
911
985
|
const txSummary = TransactionSummary.deserialize(txSummaryBytes);
|
|
912
986
|
const saltHex = txSummary.salt().toHex();
|
|
913
987
|
const txCommitmentHex = txSummary.toCommitment().toHex();
|
|
988
|
+
const normalizedTxCommitmentHex = normalizeHexWord(txCommitmentHex);
|
|
914
989
|
const normalizedSignerCommitments = new Set(
|
|
915
990
|
this.signerCommitments.map((commitment) => normalizeHexWord(commitment)),
|
|
916
991
|
);
|
|
917
|
-
|
|
918
992
|
const adviceMap = new AdviceMap();
|
|
919
993
|
const adviceMapKeys = new Set<string>();
|
|
994
|
+
const createTxCommitmentWord = (): Word => Word.fromHex(normalizedTxCommitmentHex);
|
|
920
995
|
|
|
921
996
|
for (const cosignerSig of signaturesForExecution) {
|
|
922
997
|
let signerCommitmentHex = normalizeHexWord(cosignerSig.signerId);
|
|
@@ -949,10 +1024,9 @@ export class Multisig {
|
|
|
949
1024
|
cosignerSig.signature.scheme,
|
|
950
1025
|
);
|
|
951
1026
|
const signature = Signature.deserialize(sigBytes);
|
|
952
|
-
const txCommitment = Word.fromHex(normalizeHexWord(txCommitmentHex));
|
|
953
1027
|
const { key, values } = buildSignatureAdviceEntry(
|
|
954
1028
|
signerCommitment,
|
|
955
|
-
|
|
1029
|
+
createTxCommitmentWord(),
|
|
956
1030
|
signature,
|
|
957
1031
|
ecdsaPublicKey,
|
|
958
1032
|
cosignerSig.signature.scheme === 'ecdsa'
|
|
@@ -993,10 +1067,9 @@ export class Multisig {
|
|
|
993
1067
|
}
|
|
994
1068
|
const ackSigBytes = signatureHexToBytes(ackSigHex, ackScheme);
|
|
995
1069
|
const ackSignature = Signature.deserialize(ackSigBytes);
|
|
996
|
-
const txCommitmentForAck = Word.fromHex(normalizeHexWord(txCommitmentHex));
|
|
997
1070
|
const { key: ackKey, values: ackValues } = buildSignatureAdviceEntry(
|
|
998
1071
|
guardianCommitment,
|
|
999
|
-
|
|
1072
|
+
createTxCommitmentWord(),
|
|
1000
1073
|
ackSignature,
|
|
1001
1074
|
ackScheme === 'ecdsa' ? ackPubkey : undefined,
|
|
1002
1075
|
ackScheme === 'ecdsa' ? ackSigHex : undefined,
|
|
@@ -1009,13 +1082,10 @@ export class Multisig {
|
|
|
1009
1082
|
adviceMap.insert(ackKey, new FeltArray(ackValues));
|
|
1010
1083
|
}
|
|
1011
1084
|
|
|
1012
|
-
const metadata = proposal.metadata;
|
|
1013
|
-
if (!metadata) {
|
|
1014
|
-
throw new Error('Proposal missing metadata');
|
|
1015
|
-
}
|
|
1016
1085
|
if (metadata.proposalType === 'switch_guardian') {
|
|
1017
1086
|
await this.verifyGuardianEndpointCommitment(metadata.newGuardianEndpoint, metadata.newGuardianPubkey);
|
|
1018
1087
|
}
|
|
1088
|
+
|
|
1019
1089
|
const executionSalt = Word.fromHex(normalizeHexWord(saltHex));
|
|
1020
1090
|
const finalRequest = await this.buildTransactionRequestFromMetadata(
|
|
1021
1091
|
metadata,
|
|
@@ -1023,42 +1093,7 @@ export class Multisig {
|
|
|
1023
1093
|
adviceMap,
|
|
1024
1094
|
);
|
|
1025
1095
|
|
|
1026
|
-
|
|
1027
|
-
const result = await this.webClient.executeTransaction(accountId, finalRequest);
|
|
1028
|
-
const proven = await this.webClient.proveTransaction(result, null);
|
|
1029
|
-
const submissionHeight = await this.webClient.submitProvenTransaction(proven, result);
|
|
1030
|
-
await this.webClient.applyTransaction(result, submissionHeight);
|
|
1031
|
-
|
|
1032
|
-
if (metadata.proposalType === 'switch_guardian') {
|
|
1033
|
-
if (!metadata.newGuardianEndpoint || !metadata.newGuardianPubkey) {
|
|
1034
|
-
throw new Error('Switch GUARDIAN proposal metadata is incomplete after execution');
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
try {
|
|
1038
|
-
await this.webClient.syncState();
|
|
1039
|
-
|
|
1040
|
-
const updatedAccount = await this.webClient.getAccount(accountId);
|
|
1041
|
-
if (!updatedAccount) {
|
|
1042
|
-
throw new Error(
|
|
1043
|
-
`Updated account ${this._accountId} is missing from local client`
|
|
1044
|
-
);
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
const updatedStateBase64 = uint8ArrayToBase64(updatedAccount.serialize());
|
|
1048
|
-
const nextGuardian = new GuardianHttpClient(metadata.newGuardianEndpoint);
|
|
1049
|
-
this.setGuardianClient(nextGuardian);
|
|
1050
|
-
this.guardianPublicKey = metadata.newGuardianPubkey;
|
|
1051
|
-
|
|
1052
|
-
await this.registerOnGuardian(updatedStateBase64);
|
|
1053
|
-
} catch (error) {
|
|
1054
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1055
|
-
throw new Error(
|
|
1056
|
-
`Transaction executed successfully but failed to register on new GUARDIAN: ${message}`
|
|
1057
|
-
);
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
proposal.status = 'finalized';
|
|
1096
|
+
return { finalRequest, metadata, proposal };
|
|
1062
1097
|
}
|
|
1063
1098
|
|
|
1064
1099
|
/**
|
|
@@ -1236,7 +1271,8 @@ export class Multisig {
|
|
|
1236
1271
|
: summary.salt();
|
|
1237
1272
|
|
|
1238
1273
|
const request = await this.buildTransactionRequestFromMetadata(proposal.metadata, salt);
|
|
1239
|
-
const
|
|
1274
|
+
const webClient = await this.getRawClient();
|
|
1275
|
+
const reconstructed = await executeForSummary(webClient, this._accountId, request);
|
|
1240
1276
|
const reconstructedCommitment = normalizeHexWord(reconstructed.toCommitment().toHex());
|
|
1241
1277
|
|
|
1242
1278
|
if (reconstructedCommitment !== txSummaryCommitment) {
|
|
@@ -1251,12 +1287,14 @@ export class Multisig {
|
|
|
1251
1287
|
salt: Word,
|
|
1252
1288
|
signatureAdviceMap?: AdviceMap,
|
|
1253
1289
|
): Promise<TransactionRequest> {
|
|
1290
|
+
const webClient = await this.getRawClient();
|
|
1291
|
+
|
|
1254
1292
|
switch (metadata.proposalType) {
|
|
1255
1293
|
case 'add_signer':
|
|
1256
1294
|
case 'remove_signer':
|
|
1257
1295
|
case 'change_threshold': {
|
|
1258
1296
|
const { request } = await buildUpdateSignersTransactionRequest(
|
|
1259
|
-
|
|
1297
|
+
webClient,
|
|
1260
1298
|
metadata.targetThreshold,
|
|
1261
1299
|
metadata.targetSignerCommitments,
|
|
1262
1300
|
{ salt, signatureAdviceMap, signatureScheme: this.signer.scheme }
|
|
@@ -1265,7 +1303,7 @@ export class Multisig {
|
|
|
1265
1303
|
}
|
|
1266
1304
|
case 'switch_guardian': {
|
|
1267
1305
|
const { request } = await buildUpdateGuardianTransactionRequest(
|
|
1268
|
-
|
|
1306
|
+
webClient,
|
|
1269
1307
|
metadata.newGuardianPubkey,
|
|
1270
1308
|
{ salt, signatureAdviceMap, signatureScheme: this.signer.scheme }
|
|
1271
1309
|
);
|
|
@@ -1273,7 +1311,7 @@ export class Multisig {
|
|
|
1273
1311
|
}
|
|
1274
1312
|
case 'update_procedure_threshold': {
|
|
1275
1313
|
const { request } = await buildUpdateProcedureThresholdTransactionRequest(
|
|
1276
|
-
|
|
1314
|
+
webClient,
|
|
1277
1315
|
metadata.targetProcedure,
|
|
1278
1316
|
metadata.targetThreshold,
|
|
1279
1317
|
{ salt, signatureAdviceMap, signatureScheme: this.signer.scheme }
|
|
@@ -1282,7 +1320,7 @@ export class Multisig {
|
|
|
1282
1320
|
}
|
|
1283
1321
|
case 'consume_notes': {
|
|
1284
1322
|
const { request } = await buildConsumeNotesTransactionRequest(
|
|
1285
|
-
|
|
1323
|
+
webClient,
|
|
1286
1324
|
metadata.noteIds,
|
|
1287
1325
|
{ salt, signatureAdviceMap }
|
|
1288
1326
|
);
|
package/src/procedures.ts
CHANGED
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
* human-readable encoding and should not be copied into this table.
|
|
12
12
|
*/
|
|
13
13
|
export const PROCEDURE_ROOTS = {
|
|
14
|
-
update_signers: '
|
|
15
|
-
update_procedure_threshold: '
|
|
16
|
-
auth_tx: '
|
|
17
|
-
update_guardian: '
|
|
18
|
-
verify_guardian: '
|
|
19
|
-
send_asset: '
|
|
20
|
-
receive_asset: '
|
|
14
|
+
update_signers: '0x3d382ad461f9914c487c6fe908991d088eb54ecbd4aa8560ef79c66c3746bf19',
|
|
15
|
+
update_procedure_threshold: '0x1f43e9d56ceff5d547ffdcb89896fb38cae0be1b74d9235ed2b4aa525df85f8d',
|
|
16
|
+
auth_tx: '0x415530d7169f849d7219e810065f9119bba9af2c55070de0bf4f082a1c0aea5c',
|
|
17
|
+
update_guardian: '0xc8ea876f1837e5cd1d6031becdbd40ce262ecd55930d65400f6890a37149d80c',
|
|
18
|
+
verify_guardian: '0x9bc6e7b25c8dbaa29d6ad41e354a545dd0a4bac7f3a521bb5195ba101f0213cc',
|
|
19
|
+
send_asset: '0x6d30df4312a2c44ec842db1bee227cc045396ca91e2c47d756dcb607f2bf5f89',
|
|
20
|
+
receive_asset: '0x75f638c65584d058542bcf4674b066ae394183021bc9b44dc2fdd97d52f9bcfb',
|
|
21
21
|
} as const;
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -34,7 +34,7 @@ export type ProcedureName = keyof typeof PROCEDURE_ROOTS;
|
|
|
34
34
|
* @example
|
|
35
35
|
* ```typescript
|
|
36
36
|
* const root = getProcedureRoot('send_asset');
|
|
37
|
-
* // '
|
|
37
|
+
* // '0x6d30df4312a2c44ec842db1bee227cc045396ca91e2c47d756dcb607f2bf5f89'
|
|
38
38
|
* ```
|
|
39
39
|
*/
|
|
40
40
|
export function getProcedureRoot(name: ProcedureName): string {
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const { mockCreateClient } = vi.hoisted(() => ({
|
|
4
|
+
mockCreateClient: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
vi.mock('@miden-sdk/miden-sdk', () => ({
|
|
8
|
+
WasmWebClient: {
|
|
9
|
+
createClient: mockCreateClient,
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
compileTxScript,
|
|
15
|
+
getRawMidenClient,
|
|
16
|
+
getTransactionProver,
|
|
17
|
+
resolveMidenRpcEndpoint,
|
|
18
|
+
} from './raw-client.js';
|
|
19
|
+
|
|
20
|
+
describe('raw-client', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
mockCreateClient.mockReset();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('defaults RPC endpoint resolution to devnet', () => {
|
|
26
|
+
expect(resolveMidenRpcEndpoint()).toBe('https://rpc.devnet.miden.io');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('returns the default prover from a public MidenClient', () => {
|
|
30
|
+
const prover = { kind: 'devnet-prover' };
|
|
31
|
+
const client = {
|
|
32
|
+
accounts: {},
|
|
33
|
+
sync: vi.fn(),
|
|
34
|
+
defaultProver: prover,
|
|
35
|
+
storeIdentifier: vi.fn(() => 'browser-db'),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
expect(getTransactionProver(client as any)).toBe(prover);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('returns null for raw web clients', () => {
|
|
42
|
+
const rawClient = {
|
|
43
|
+
executeTransaction: vi.fn(),
|
|
44
|
+
proveTransaction: vi.fn(),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
expect(getTransactionProver(rawClient as any)).toBeNull();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('creates and caches a raw client for a public MidenClient', async () => {
|
|
51
|
+
const rawClient = { kind: 'raw-client' };
|
|
52
|
+
const client = {
|
|
53
|
+
accounts: {},
|
|
54
|
+
sync: vi.fn(),
|
|
55
|
+
defaultProver: null,
|
|
56
|
+
storeIdentifier: vi.fn(() => 'browser-db'),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
mockCreateClient.mockResolvedValue(rawClient);
|
|
60
|
+
|
|
61
|
+
await expect(
|
|
62
|
+
getRawMidenClient(client as any, 'https://rpc.devnet.miden.io'),
|
|
63
|
+
).resolves.toBe(rawClient);
|
|
64
|
+
await expect(
|
|
65
|
+
getRawMidenClient(client as any, 'https://rpc.devnet.miden.io'),
|
|
66
|
+
).resolves.toBe(rawClient);
|
|
67
|
+
|
|
68
|
+
expect(mockCreateClient).toHaveBeenCalledTimes(1);
|
|
69
|
+
expect(mockCreateClient).toHaveBeenCalledWith(
|
|
70
|
+
'https://rpc.devnet.miden.io',
|
|
71
|
+
undefined,
|
|
72
|
+
undefined,
|
|
73
|
+
'browser-db',
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('uses the public compile resource when available', async () => {
|
|
78
|
+
const script = { kind: 'compiled-script' };
|
|
79
|
+
const client = {
|
|
80
|
+
accounts: {},
|
|
81
|
+
sync: vi.fn(),
|
|
82
|
+
compile: {
|
|
83
|
+
txScript: vi.fn().mockResolvedValue(script),
|
|
84
|
+
},
|
|
85
|
+
storeIdentifier: vi.fn(() => 'browser-db'),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
await expect(
|
|
89
|
+
compileTxScript(
|
|
90
|
+
client as any,
|
|
91
|
+
'begin end',
|
|
92
|
+
[{ namespace: 'auth::multisig', code: 'export.foo' }],
|
|
93
|
+
),
|
|
94
|
+
).resolves.toBe(script);
|
|
95
|
+
|
|
96
|
+
expect(client.compile.txScript).toHaveBeenCalledWith({
|
|
97
|
+
code: 'begin end',
|
|
98
|
+
libraries: [{ namespace: 'auth::multisig', code: 'export.foo' }],
|
|
99
|
+
});
|
|
100
|
+
expect(mockCreateClient).not.toHaveBeenCalled();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('falls back to raw client compilation for low-level callers', async () => {
|
|
104
|
+
const compiledScript = { kind: 'compiled-script' };
|
|
105
|
+
const builtLibrary = { kind: 'built-library' };
|
|
106
|
+
const builder = {
|
|
107
|
+
buildLibrary: vi.fn().mockReturnValue(builtLibrary),
|
|
108
|
+
linkDynamicLibrary: vi.fn(),
|
|
109
|
+
linkStaticLibrary: vi.fn(),
|
|
110
|
+
compileTxScript: vi.fn().mockReturnValue(compiledScript),
|
|
111
|
+
};
|
|
112
|
+
const rawClient = {
|
|
113
|
+
createCodeBuilder: vi.fn().mockReturnValue(builder),
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
await expect(
|
|
117
|
+
compileTxScript(
|
|
118
|
+
rawClient as any,
|
|
119
|
+
'begin end',
|
|
120
|
+
[
|
|
121
|
+
{ namespace: 'auth::multisig', code: 'export.foo' },
|
|
122
|
+
{ namespace: 'auth::guardian', code: 'export.bar', linking: 'static' },
|
|
123
|
+
],
|
|
124
|
+
),
|
|
125
|
+
).resolves.toBe(compiledScript);
|
|
126
|
+
|
|
127
|
+
expect(builder.buildLibrary).toHaveBeenNthCalledWith(1, 'auth::multisig', 'export.foo');
|
|
128
|
+
expect(builder.buildLibrary).toHaveBeenNthCalledWith(2, 'auth::guardian', 'export.bar');
|
|
129
|
+
expect(builder.linkDynamicLibrary).toHaveBeenCalledWith(builtLibrary);
|
|
130
|
+
expect(builder.linkStaticLibrary).toHaveBeenCalledWith(builtLibrary);
|
|
131
|
+
expect(builder.compileTxScript).toHaveBeenCalledWith('begin end');
|
|
132
|
+
});
|
|
133
|
+
});
|