@openzeppelin/miden-multisig-client 0.17.0 → 0.18.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +171 -46
- package/dist/account/builder.d.ts +4 -4
- package/dist/account/builder.d.ts.map +1 -1
- package/dist/account/builder.js +17 -7
- package/dist/account/builder.js.map +1 -1
- package/dist/account/layout.d.ts +5 -5
- package/dist/account/layout.d.ts.map +1 -1
- package/dist/account/layout.js +5 -5
- package/dist/account/layout.js.map +1 -1
- package/dist/client.d.ts +18 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +79 -6
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/multisig/authArgErrors.d.ts +28 -31
- package/dist/multisig/authArgErrors.d.ts.map +1 -1
- package/dist/multisig/authArgErrors.js +42 -46
- package/dist/multisig/authArgErrors.js.map +1 -1
- package/dist/multisig/consumeNotesErrors.d.ts +13 -1
- package/dist/multisig/consumeNotesErrors.d.ts.map +1 -1
- package/dist/multisig/consumeNotesErrors.js +17 -0
- package/dist/multisig/consumeNotesErrors.js.map +1 -1
- package/dist/multisig/signing.d.ts +1 -1
- package/dist/multisig/signing.d.ts.map +1 -1
- package/dist/multisig/signing.js +8 -3
- package/dist/multisig/signing.js.map +1 -1
- package/dist/multisig.d.ts +145 -18
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +493 -162
- package/dist/multisig.js.map +1 -1
- package/dist/procedures.d.ts +7 -7
- package/dist/procedures.js +7 -7
- package/dist/proposal/factory.d.ts.map +1 -1
- package/dist/proposal/factory.js +7 -0
- package/dist/proposal/factory.js.map +1 -1
- package/dist/prover/workflow.d.ts +7 -3
- package/dist/prover/workflow.d.ts.map +1 -1
- package/dist/prover/workflow.js +7 -5
- package/dist/prover/workflow.js.map +1 -1
- package/dist/raw-client.d.ts +1 -0
- package/dist/raw-client.d.ts.map +1 -1
- package/dist/raw-client.js +10 -2
- package/dist/raw-client.js.map +1 -1
- package/dist/recovery/publicNoteBackfill.js +1 -1
- package/dist/recovery/publicNoteBackfill.js.map +1 -1
- package/dist/retry/classify.d.ts +3 -0
- package/dist/retry/classify.d.ts.map +1 -1
- package/dist/retry/classify.js +2 -2
- package/dist/retry/classify.js.map +1 -1
- package/dist/signer.d.ts +1 -0
- package/dist/signer.d.ts.map +1 -1
- package/dist/signer.js +1 -0
- package/dist/signer.js.map +1 -1
- package/dist/signers/index.d.ts +1 -0
- package/dist/signers/index.d.ts.map +1 -1
- package/dist/signers/index.js +1 -0
- package/dist/signers/index.js.map +1 -1
- package/dist/signers/ledger.d.ts +25 -0
- package/dist/signers/ledger.d.ts.map +1 -0
- package/dist/signers/ledger.js +96 -0
- package/dist/signers/ledger.js.map +1 -0
- package/dist/state/adopt.d.ts +45 -0
- package/dist/state/adopt.d.ts.map +1 -0
- package/dist/state/adopt.js +101 -0
- package/dist/state/adopt.js.map +1 -0
- package/dist/transaction/authArgs.d.ts +57 -0
- package/dist/transaction/authArgs.d.ts.map +1 -0
- package/dist/transaction/authArgs.js +108 -0
- package/dist/transaction/authArgs.js.map +1 -0
- package/dist/transaction/consumeNotes.d.ts +9 -5
- package/dist/transaction/consumeNotes.d.ts.map +1 -1
- package/dist/transaction/consumeNotes.js +8 -23
- package/dist/transaction/consumeNotes.js.map +1 -1
- package/dist/transaction/noteAuthentication.d.ts +39 -0
- package/dist/transaction/noteAuthentication.d.ts.map +1 -0
- package/dist/transaction/noteAuthentication.js +94 -0
- package/dist/transaction/noteAuthentication.js.map +1 -0
- package/dist/transaction/options.d.ts +25 -0
- package/dist/transaction/options.d.ts.map +1 -1
- package/dist/transaction/p2id.d.ts +3 -2
- package/dist/transaction/p2id.d.ts.map +1 -1
- package/dist/transaction/p2id.js +36 -27
- package/dist/transaction/p2id.js.map +1 -1
- package/dist/transaction/summary.d.ts +126 -22
- package/dist/transaction/summary.d.ts.map +1 -1
- package/dist/transaction/summary.js +164 -22
- package/dist/transaction/summary.js.map +1 -1
- package/dist/transaction/updateGuardian.d.ts +3 -3
- package/dist/transaction/updateGuardian.d.ts.map +1 -1
- package/dist/transaction/updateGuardian.js +5 -16
- package/dist/transaction/updateGuardian.js.map +1 -1
- package/dist/transaction/updateProcedureThreshold.d.ts +3 -3
- package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
- package/dist/transaction/updateProcedureThreshold.js +6 -16
- package/dist/transaction/updateProcedureThreshold.js.map +1 -1
- package/dist/transaction/updateSigners.d.ts +3 -3
- package/dist/transaction/updateSigners.d.ts.map +1 -1
- package/dist/transaction/updateSigners.js +9 -16
- package/dist/transaction/updateSigners.js.map +1 -1
- package/dist/transaction.d.ts +3 -2
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +3 -2
- package/dist/transaction.js.map +1 -1
- package/dist/types/proposal.d.ts +37 -5
- package/dist/types/proposal.d.ts.map +1 -1
- package/dist/types/proposal.js +8 -0
- package/dist/types/proposal.js.map +1 -1
- package/dist/utils/eip712.d.ts +80 -0
- package/dist/utils/eip712.d.ts.map +1 -0
- package/dist/utils/eip712.js +49 -0
- package/dist/utils/eip712.js.map +1 -0
- package/dist/utils/signature.d.ts +4 -0
- package/dist/utils/signature.d.ts.map +1 -1
- package/dist/utils/signature.js +49 -1
- package/dist/utils/signature.js.map +1 -1
- package/package.json +11 -6
- package/src/account/builder.ts +18 -7
- package/src/account/layout.ts +5 -5
- package/src/client.ts +94 -6
- package/src/index.ts +24 -3
- package/src/multisig/authArgErrors.ts +47 -53
- package/src/multisig/consumeNotesErrors.ts +20 -1
- package/src/multisig/signing.ts +8 -2
- package/src/multisig.ts +614 -205
- package/src/procedures.ts +7 -7
- package/src/proposal/factory.ts +7 -0
- package/src/prover/workflow.ts +7 -10
- package/src/raw-client.ts +11 -7
- package/src/recovery/publicNoteBackfill.ts +1 -1
- package/src/retry/classify.ts +3 -3
- package/src/signer.ts +1 -0
- package/src/signers/index.ts +1 -0
- package/src/signers/ledger.ts +122 -0
- package/src/state/adopt.ts +132 -0
- package/src/transaction/authArgs.ts +142 -0
- package/src/transaction/consumeNotes.ts +23 -30
- package/src/transaction/noteAuthentication.ts +136 -0
- package/src/transaction/options.ts +27 -0
- package/src/transaction/p2id.ts +45 -34
- package/src/transaction/summary.ts +239 -30
- package/src/transaction/updateGuardian.ts +8 -22
- package/src/transaction/updateProcedureThreshold.ts +8 -20
- package/src/transaction/updateSigners.ts +11 -22
- package/src/transaction.ts +18 -1
- package/src/types/proposal.ts +36 -5
- package/src/utils/eip712.ts +57 -0
- package/src/utils/signature.ts +57 -0
- package/src/prover/test-node.d.ts +0 -6
package/README.md
CHANGED
|
@@ -17,9 +17,14 @@ Miden multisig accounts store their authentication logic on-chain, but **their s
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npm install @openzeppelin/miden-multisig-client @miden-sdk/miden-sdk@0.
|
|
20
|
+
npm install @openzeppelin/miden-multisig-client@0.18.0-rc.2 @miden-sdk/miden-sdk@0.17.0-rc.4
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
Miden 0.17 requires a new client database: a store created under 0.16 does
|
|
24
|
+
not open. Pass a fresh `storeName` when creating the `MidenClient`, or delete
|
|
25
|
+
the existing IndexedDB database first. Accounts and pending proposals from the
|
|
26
|
+
0.16 line do not carry over; see the compatibility document below.
|
|
27
|
+
|
|
23
28
|
> **Why the peer version is exact**: the transaction-summary layout and the
|
|
24
29
|
> guarded-multisig procedure roots are only byte-compatible between one
|
|
25
30
|
> `@miden-sdk/miden-sdk` build and the `miden-standards` version its WASM
|
|
@@ -32,6 +37,7 @@ matches your Miden node:
|
|
|
32
37
|
|
|
33
38
|
| This package | Miden protocol |
|
|
34
39
|
|---|---|
|
|
40
|
+
| 0.18.x | 0.17.x (pre-release) |
|
|
35
41
|
| 0.17.x | 0.16.x |
|
|
36
42
|
| 0.16.x | 0.15.x |
|
|
37
43
|
| 0.15.x | 0.15.x |
|
|
@@ -69,6 +75,42 @@ const client = new MultisigClient(midenClient, {
|
|
|
69
75
|
});
|
|
70
76
|
```
|
|
71
77
|
|
|
78
|
+
For an ECDSA cosigner using an EIP-1193 wallet (including Ledger), call
|
|
79
|
+
`Eip712Signer.connect(provider)` once to select the Ethereum address and
|
|
80
|
+
recover its secp256k1 public key from a dedicated key-discovery signature.
|
|
81
|
+
Alternatively, pass an already-enrolled public key and its matching address
|
|
82
|
+
to `new Eip712Signer(provider, publicKeyHex, address)`. `LedgerSigner` remains
|
|
83
|
+
an alias. Use the resulting signer with the same `client.load(accountId, signer)`,
|
|
84
|
+
proposal-creation methods, and
|
|
85
|
+
`multisig.signProposal(id)` flow as a raw signer. The Ledger user can create
|
|
86
|
+
the proposal, then approve it. The approval and Guardian submission each
|
|
87
|
+
require a separate `eth_signTypedData_v4` signature; authenticated reads in
|
|
88
|
+
the flow also prompt the device. All signatures use the enrolled key; no
|
|
89
|
+
separate Guardian signing endpoint is needed. The device displays hashes,
|
|
90
|
+
not human-readable transferred assets. `recoverByKey` uses a separate
|
|
91
|
+
`GuardianLookup(bytes32 lookupHash)` typed-data signature to discover accounts.
|
|
92
|
+
EIP-712 execution requires an account compiled with the Miden 0.17 multisig
|
|
93
|
+
authentication component; changing Guardian's signature format does not upgrade
|
|
94
|
+
an older account's code root.
|
|
95
|
+
|
|
96
|
+
For example, a Ledger-backed proposer follows the same proposal lifecycle as a
|
|
97
|
+
raw signer:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { Eip712Signer } from '@openzeppelin/miden-multisig-client';
|
|
101
|
+
|
|
102
|
+
const signer = await Eip712Signer.connect(provider); // key-discovery prompt
|
|
103
|
+
const multisig = await client.load(accountId, signer); // authenticated reads may prompt
|
|
104
|
+
const proposal = await multisig.createAddSignerProposal(newSignerCommitment); // request-auth prompt
|
|
105
|
+
await multisig.signProposal(proposal.id); // EIP-712 approval and request-auth prompts
|
|
106
|
+
await multisig.executeProposal(proposal.id); // authenticated calls may prompt again
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The proposal ID is the transaction-summary commitment. The wallet signs an
|
|
110
|
+
EIP-712 digest derived from that commitment, so the digest displayed by the
|
|
111
|
+
wallet need not equal the proposal ID. The approval signature and the
|
|
112
|
+
Guardian request-authentication signature are separate.
|
|
113
|
+
|
|
72
114
|
The nested `prover` configuration is optional. Without it, the injected Miden
|
|
73
115
|
client's prover is preserved. By default, cloneable remote provers get two total
|
|
74
116
|
attempts; endpoint-less injected provers, including local and callback provers,
|
|
@@ -232,6 +274,13 @@ the min of the current threshold and the remaining signer count on remove.
|
|
|
232
274
|
The option shapes are exported as `CreateProposalOptions`,
|
|
233
275
|
`CreateSignerProposalOptions`, and `CreateP2idProposalOptions`.
|
|
234
276
|
|
|
277
|
+
All methods also accept `approvalExpirationDelta` (1 to 65535): the number of blocks after
|
|
278
|
+
the block the proposal binds by which the transaction must be included. Past
|
|
279
|
+
that block the approvers' signatures no longer authorize it, the SDK refuses to
|
|
280
|
+
execute it, and the node rejects it as expired. The summary binds the value, so
|
|
281
|
+
the executing party cannot change it. Omitted, the approval never expires,
|
|
282
|
+
which is the Miden default.
|
|
283
|
+
|
|
235
284
|
> **Breaking change (issue #387):** these methods previously took `nonce` (and
|
|
236
285
|
> `newThreshold`) as positional parameters. Passing the old positional form
|
|
237
286
|
> now throws at runtime instead of silently applying defaults.
|
|
@@ -264,7 +313,7 @@ console.log('Signatures:', signedProposal.signatures.length);
|
|
|
264
313
|
|
|
265
314
|
### Sync Proposals
|
|
266
315
|
|
|
267
|
-
Fetches proposals from the GUARDIAN server and
|
|
316
|
+
Fetches proposals from the GUARDIAN server and reconciles local state. A proposal GUARDIAN reported on an earlier sync but no longer reports (executed, canonicalized, or abandoned) is pruned from the cache. A proposal GUARDIAN holds but has not listed yet (a freshly pushed create) survives the first listing that omits it, absorbing a read-your-writes lag, and is pruned once a second consecutive listing omits it. Proposals GUARDIAN never received (offline switch-guardian creations and imports) are not pruned by listings. The response is parsed in full before the cache changes (a malformed payload rejects and leaves the cache untouched), a proposal that fails metadata-binding verification is still cached and returned with `verification.status === 'failed'` (see [Proposal Verification Status](#proposal-verification-status)), a proposal executed or signed offline while a sync is in flight keeps that local outcome (it is neither reverted to the listed state nor pruned by that sync), and overlapping calls share a single in-flight sync:
|
|
268
317
|
|
|
269
318
|
```typescript
|
|
270
319
|
const proposals = await multisig.syncProposals();
|
|
@@ -325,6 +374,55 @@ for (const p of proposals) {
|
|
|
325
374
|
}
|
|
326
375
|
```
|
|
327
376
|
|
|
377
|
+
### Proposal Verification Status
|
|
378
|
+
|
|
379
|
+
`syncProposals()` checks every proposal's metadata against its signed
|
|
380
|
+
summary and records the outcome in `proposal.verification`:
|
|
381
|
+
`{ status: 'unchecked' }`, `{ status: 'verified' }`, or
|
|
382
|
+
`{ status: 'failed', retryable, message }`. A proposal that fails the
|
|
383
|
+
check is still returned, so one stale or corrupt proposal cannot hide the
|
|
384
|
+
others; only the check itself writes `verified`, and a freshly parsed or
|
|
385
|
+
imported proposal is `unchecked`. `retryable: true` means the
|
|
386
|
+
re-execution hit a transient node error and the proposal may verify on
|
|
387
|
+
the next sync; `retryable: false` means it cannot be reproduced (tampered
|
|
388
|
+
metadata, for example) and has to be re-proposed. Verification is deliberately not part of `status`: a fully
|
|
389
|
+
signed proposal can be dead, so `'ready'` keeps meaning "threshold met"
|
|
390
|
+
and `isProposalActionable(proposal)` answers "verified and ready".
|
|
391
|
+
`signProposal` and `executeProposal` re-verify the one proposal they act
|
|
392
|
+
on and throw the real error. A payload that does not parse at all still
|
|
393
|
+
rejects, so malformed GUARDIAN data is never silently dropped.
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
import { isProposalActionable } from '@openzeppelin/miden-multisig-client';
|
|
397
|
+
|
|
398
|
+
for (const p of await multisig.syncProposals()) {
|
|
399
|
+
if (p.verification.status === 'failed') {
|
|
400
|
+
const { retryable, message } = p.verification;
|
|
401
|
+
console.log(`${p.id}: ${retryable ? 'retry later' : 're-propose'} — ${message}`);
|
|
402
|
+
} else if (isProposalActionable(p)) {
|
|
403
|
+
await multisig.executeProposal(p.id);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Verification and execution run at the chain tip. Since Miden 0.17 a
|
|
409
|
+
multisig summary binds the block its auth args name (the bound block), and
|
|
410
|
+
every request the SDK builds declares that block (`withBlockNumbers`), so the
|
|
411
|
+
summary reproduces at any later tip. An execution runs at the Miden client's
|
|
412
|
+
sync height, so `syncProposals`, `signProposal`, `executeProposal`,
|
|
413
|
+
`createTransactionProposalRequest` and the custom and offline paths sync the
|
|
414
|
+
client first. Foreign accounts, the fee faucet among them, load at the tip, so
|
|
415
|
+
a proposal stays verifiable however long it waits for signatures, even after
|
|
416
|
+
the node has pruned the bound block's account state (devnet keeps about 50
|
|
417
|
+
blocks). The proposal's `chainAnchor` still names the bound block, and
|
|
418
|
+
0.18.0-rc.1 clients still re-execute at it.
|
|
419
|
+
|
|
420
|
+
`createTransactionProposalRequest(proposalId)` returns the final, fully
|
|
421
|
+
signed request for an integration that proves and submits with its own
|
|
422
|
+
pipeline. Execute it at the chain tip, without an anchor, on a client that has
|
|
423
|
+
synced recently: this call syncs first, and an execution loads the fee faucet
|
|
424
|
+
at the client's sync height, which a node serves for only about 50 blocks.
|
|
425
|
+
|
|
328
426
|
### Execute a Proposal
|
|
329
427
|
|
|
330
428
|
When a proposal has enough signatures:
|
|
@@ -401,7 +499,7 @@ exported/imported through the normal flow, but the SDK cannot build its on-chain
|
|
|
401
499
|
transaction — the integration owns that recipe and submits it itself.
|
|
402
500
|
|
|
403
501
|
```typescript
|
|
404
|
-
import { buildP2idTransactionRequest } from '@openzeppelin/miden-multisig-client';
|
|
502
|
+
import { buildP2idTransactionRequest, chainAnchorBlockNum } from '@openzeppelin/miden-multisig-client';
|
|
405
503
|
|
|
406
504
|
// Producer: build a transaction and propose it under a custom label.
|
|
407
505
|
// The options object accepts `noteType` (`NoteType.Public` (default) or
|
|
@@ -412,7 +510,12 @@ import { buildP2idTransactionRequest } from '@openzeppelin/miden-multisig-client
|
|
|
412
510
|
// The typed path is `createP2idProposal(recipient, faucet, amount,
|
|
413
511
|
// { nonce, noteType, reclaimHeight, timelockHeight })`, which persists the
|
|
414
512
|
// choices in signed metadata.
|
|
415
|
-
|
|
513
|
+
//
|
|
514
|
+
// The builder takes the Miden client because the executing account decides
|
|
515
|
+
// the auth args the request has to carry (see below).
|
|
516
|
+
const { request, salt } = await buildP2idTransactionRequest(
|
|
517
|
+
midenClient, senderId, recipientId, faucetId, amount, { midenRpcEndpoint },
|
|
518
|
+
);
|
|
416
519
|
const proposal = await multisig.createCustomProposal(request.serialize(), 'b2agg');
|
|
417
520
|
|
|
418
521
|
// Cosigners review and sign through the usual signProposal flow.
|
|
@@ -423,51 +526,56 @@ const proposal = await multisig.createCustomProposal(request.serialize(), 'b2agg
|
|
|
423
526
|
const advice = await multisig.prepareCustomExecution(proposal.id, request.serialize());
|
|
424
527
|
|
|
425
528
|
// The browser TransactionRequest is immutable, so rebuild from the same recipe
|
|
426
|
-
// (inputs + salt) with the advice, then submit. `submitTransaction`
|
|
427
|
-
//
|
|
428
|
-
|
|
429
|
-
const { request: finalRequest } = buildP2idTransactionRequest(
|
|
430
|
-
senderId, recipientId, faucetId, amount,
|
|
529
|
+
// (inputs + salt + bound block) with the advice, then submit. `submitTransaction`
|
|
530
|
+
// executes at the chain tip: the rebuilt request declares the block it binds.
|
|
531
|
+
const boundBlockNum = chainAnchorBlockNum(proposal.metadata.chainAnchor);
|
|
532
|
+
const { request: finalRequest } = await buildP2idTransactionRequest(
|
|
533
|
+
midenClient, senderId, recipientId, faucetId, amount,
|
|
534
|
+
{ salt, boundBlockNum, signatureAdviceMap: advice, midenRpcEndpoint },
|
|
431
535
|
);
|
|
432
536
|
await multisig.submitTransaction(proposal.id, finalRequest);
|
|
433
537
|
```
|
|
434
538
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
the
|
|
452
|
-
|
|
453
|
-
The
|
|
539
|
+
Since Miden 0.17 a multisig auth procedure reads three words out of the
|
|
540
|
+
transaction's auth arg: the block the summary binds together with the approval
|
|
541
|
+
expiration, the salt, and the fee conversion info. The exported builders get
|
|
542
|
+
them from `client.feeAwareTransactionRequestBuilder(account, { feeConversionSalt,
|
|
543
|
+
boundBlockNum })`, which sets the commitment as the request's auth arg, puts
|
|
544
|
+
the preimage in its advice map, and declares the bound block with
|
|
545
|
+
`withBlockNumbers` so the request executes at the chain tip. A request whose
|
|
546
|
+
auth args bind a block it does not declare is refused with
|
|
547
|
+
`BoundBlockNotDeclaredError`. An integration that assembles a request itself
|
|
548
|
+
must start from that builder for a multisig account, and must not call
|
|
549
|
+
`withFeeConversionSalt` or `withAuthArg` on it: the two setters clear each other
|
|
550
|
+
and either one discards the auth args. The approval never expires unless the
|
|
551
|
+
integration asks for one through `approvalExpirationDelta`, which the
|
|
552
|
+
`create*Proposal` methods forward from their options.
|
|
553
|
+
|
|
554
|
+
The integration keeps its own recipe (build inputs + salt) and reads the bound
|
|
555
|
+
block from the proposal's chain anchor, so it can reproduce the exact
|
|
556
|
+
transaction at execute time — the SDK does not store the serialized request.
|
|
557
|
+
The binding check guarantees the rebuilt transaction matches the commitment the
|
|
558
|
+
cosigners signed. A request built at one sync height and anchored at another is
|
|
559
|
+
refused by `executeForSummary` with `SummaryAnchorMismatchError`; rebuild and
|
|
560
|
+
retry.
|
|
561
|
+
|
|
562
|
+
The summary binds the salt itself, so the value the cosigners signed over is
|
|
563
|
+
readable back out of it:
|
|
454
564
|
|
|
455
565
|
```typescript
|
|
456
|
-
import {
|
|
566
|
+
import { summarySalt, summaryApprovalExpirationBlockNum } from '@openzeppelin/miden-multisig-client';
|
|
457
567
|
import { TransactionSummary } from '@miden-sdk/miden-sdk';
|
|
458
568
|
|
|
459
|
-
const
|
|
569
|
+
const summary = TransactionSummary.deserialize(bytes);
|
|
570
|
+
const salt = summarySalt(summary);
|
|
571
|
+
const expiresAt = summaryApprovalExpirationBlockNum(summary); // undefined: never
|
|
460
572
|
```
|
|
461
573
|
|
|
462
|
-
|
|
463
|
-
`
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
and the guarded-multisig auth component zeroes the leading three and passes the
|
|
468
|
-
auth arg as the trailing four. `summaryAuthArg` reads that convention, so prefer
|
|
469
|
-
it over indexing `userParams()` by hand. It replaced `summarySalt`, whose name
|
|
470
|
-
claimed an inversion that no longer exists.
|
|
574
|
+
The SDK's own verification path compares `summarySalt(summary)` with the
|
|
575
|
+
proposal's `saltHex` metadata before rebuilding, so a proposal GUARDIAN serves
|
|
576
|
+
with a summary and metadata that disagree fails by name rather than as a
|
|
577
|
+
generic summary mismatch. A request carries the same two values in its auth
|
|
578
|
+
args; `requestSaltHex(request)` and `requestBoundBlockNum(request)` read them.
|
|
471
579
|
|
|
472
580
|
> **Rust ↔ TS parity:** both SDKs expose the same producer surface —
|
|
473
581
|
> `createCustomProposal` / `propose_custom_transaction`, `prepareCustomExecution` /
|
|
@@ -497,8 +605,8 @@ if (recovered.length === 0) {
|
|
|
497
605
|
```
|
|
498
606
|
|
|
499
607
|
The `Signer` passed to `recoverByKey` MUST implement `signLookupMessage`
|
|
500
|
-
(the bundled `FalconSigner` and `
|
|
501
|
-
authenticates by proof-of-possession of the queried commitment — same key
|
|
608
|
+
(the bundled `FalconSigner`, `EcdsaSigner`, and `Eip712Signer` do). The lookup
|
|
609
|
+
endpoint authenticates by proof-of-possession of the queried commitment — same key
|
|
502
610
|
that already authenticates per-account requests, so revealing the account ID
|
|
503
611
|
does not grant any new capability. See the design doc for the security
|
|
504
612
|
analysis.
|
|
@@ -628,11 +736,26 @@ discriminator.
|
|
|
628
736
|
[issue #229](https://github.com/OpenZeppelin/guardian/issues/229).
|
|
629
737
|
- **v2 (self-contained)** — `metadataVersion: 2` plus a `notes` array
|
|
630
738
|
of base64-encoded `Note.serialize()` bytes, aligned by index with
|
|
631
|
-
`noteIds`. Verification rebuilds the request from the embedded notes
|
|
632
|
-
|
|
633
|
-
"rebuild from signed metadata" invariant every other
|
|
634
|
-
already satisfied (and that audit finding **M-08**
|
|
635
|
-
`p2id`).
|
|
739
|
+
`noteIds`. Verification rebuilds the request from the embedded notes,
|
|
740
|
+
never from whatever notes the verifier's store happens to hold.
|
|
741
|
+
Restores the same "rebuild from signed metadata" invariant every other
|
|
742
|
+
proposal type already satisfied (and that audit finding **M-08**
|
|
743
|
+
remediated for `p2id`).
|
|
744
|
+
|
|
745
|
+
The rebuild is not store-independent by itself, though: miden-client
|
|
746
|
+
consumes each input note as *authenticated* when the local store holds
|
|
747
|
+
its inclusion proof and as *unauthenticated* otherwise, and the two
|
|
748
|
+
commit differently into the signed summary (issue #409). Authenticated
|
|
749
|
+
is the canonical mode, so before every rebuild (`syncProposals`,
|
|
750
|
+
`signProposal`, `executeProposal`) the verifier authenticates the
|
|
751
|
+
embedded notes: notes already authenticated locally are left alone,
|
|
752
|
+
the rest get their inclusion proofs from the Miden node in one round
|
|
753
|
+
trip and are imported into the local store as committed (with one
|
|
754
|
+
`syncState` if the store is behind the note's block). Verification
|
|
755
|
+
therefore reads and writes the local store and contacts the node.
|
|
756
|
+
`createConsumeNotesProposal` does the same before the summary and its
|
|
757
|
+
chain anchor are captured, and refuses a note that is not yet
|
|
758
|
+
committed on chain.
|
|
636
759
|
|
|
637
760
|
`createConsumeNotesProposal` always emits v2 starting with this
|
|
638
761
|
release; the proposer is the one party guaranteed to hold the notes
|
|
@@ -645,6 +768,7 @@ signature collection begins.
|
|
|
645
768
|
import {
|
|
646
769
|
MAX_CONSUME_NOTES_METADATA_BYTES,
|
|
647
770
|
CONSUME_NOTES_METADATA_VERSION_V2,
|
|
771
|
+
ConsumeNoteNotAuthenticatedError,
|
|
648
772
|
ConsumeNotesMetadataOversizeError,
|
|
649
773
|
LegacyConsumeNotesNoteMissingError,
|
|
650
774
|
NoteBindingMismatchError,
|
|
@@ -665,6 +789,7 @@ dashboards can branch on one taxonomy.
|
|
|
665
789
|
| `UnsupportedMetadataVersionError` | `consume_notes_unsupported_metadata_version` | Unrecognized version (including v1 on a cut-over build) |
|
|
666
790
|
| `ConsumeNotesMetadataOversizeError` | `consume_notes_metadata_oversize` | v2 metadata serialization exceeds 256 KiB at creation |
|
|
667
791
|
| `LegacyConsumeNotesNoteMissingError` | `consume_notes_legacy_note_missing` | v1 path: local store does not contain the referenced note |
|
|
792
|
+
| `ConsumeNoteNotAuthenticatedError` | `consume_notes_note_not_authenticated` | A note could not be authenticated: not committed on chain yet, the node served no proof, the import failed, or the store could not verify it after a sync |
|
|
668
793
|
|
|
669
794
|
### Cut-over policy
|
|
670
795
|
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This module provides functionality to create multisig accounts.
|
|
5
5
|
*/
|
|
6
|
-
import { type
|
|
6
|
+
import { type RawClientSource } from "../raw-client.js";
|
|
7
7
|
import type { MultisigConfig, CreateAccountResult } from "../types.js";
|
|
8
8
|
/**
|
|
9
9
|
* Creates a multisig account with GUARDIAN authentication.
|
|
10
10
|
*
|
|
11
|
-
* @param
|
|
11
|
+
* @param client - Initialized MidenClient, or the WASM client behind one
|
|
12
12
|
* @param config - Multisig configuration
|
|
13
|
-
* @param midenRpcEndpoint - RPC endpoint for the
|
|
13
|
+
* @param midenRpcEndpoint - RPC endpoint for the client's network
|
|
14
14
|
* @returns The created account and seed
|
|
15
15
|
*/
|
|
16
|
-
export declare function createMultisigAccount(
|
|
16
|
+
export declare function createMultisigAccount(client: RawClientSource, config: MultisigConfig, midenRpcEndpoint: string): Promise<CreateAccountResult>;
|
|
17
17
|
/**
|
|
18
18
|
* Validates a multisig configuration.
|
|
19
19
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../src/account/builder.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../src/account/builder.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAE7E,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAgEvE;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,cAAc,EACtB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,mBAAmB,CAAC,CAqC9B;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAgFnE"}
|
package/dist/account/builder.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { AccountBuilder, AccountStorageMode, AuthGuardedMultisigConfig, ProcedureThreshold, Word, createAuthGuardedMultisig, } from "@miden-sdk/miden-sdk";
|
|
7
7
|
import { getProcedureRoot } from "../procedures.js";
|
|
8
|
+
import { isPublicMidenClient } from "../raw-client.js";
|
|
9
|
+
import { MAX_SIGNERS } from "./layout.js";
|
|
8
10
|
import { normalizeSignerCommitment } from "../utils/signature.js";
|
|
9
11
|
/**
|
|
10
12
|
* Discriminants of the SDK's wasm `AuthScheme` enum, which `AuthGuardedMultisigConfig` takes.
|
|
@@ -44,12 +46,12 @@ function buildGuardedMultisigComponent(config) {
|
|
|
44
46
|
/**
|
|
45
47
|
* Creates a multisig account with GUARDIAN authentication.
|
|
46
48
|
*
|
|
47
|
-
* @param
|
|
49
|
+
* @param client - Initialized MidenClient, or the WASM client behind one
|
|
48
50
|
* @param config - Multisig configuration
|
|
49
|
-
* @param midenRpcEndpoint - RPC endpoint for the
|
|
51
|
+
* @param midenRpcEndpoint - RPC endpoint for the client's network
|
|
50
52
|
* @returns The created account and seed
|
|
51
53
|
*/
|
|
52
|
-
export async function createMultisigAccount(
|
|
54
|
+
export async function createMultisigAccount(client, config, midenRpcEndpoint) {
|
|
53
55
|
validateMultisigConfig(config);
|
|
54
56
|
const authComponent = buildGuardedMultisigComponent(config);
|
|
55
57
|
let seed = config.seed;
|
|
@@ -67,10 +69,15 @@ export async function createMultisigAccount(midenClient, config, midenRpcEndpoin
|
|
|
67
69
|
.withAuthComponent(authComponent)
|
|
68
70
|
.withBasicWalletComponent();
|
|
69
71
|
const result = accountBuilder.buildWithoutSchemaCommitment();
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
if (isPublicMidenClient(client)) {
|
|
73
|
+
await client.accounts.insert({
|
|
74
|
+
account: result.account,
|
|
75
|
+
overwrite: false,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
await client.newAccount(result.account, false);
|
|
80
|
+
}
|
|
74
81
|
return {
|
|
75
82
|
account: result.account,
|
|
76
83
|
seed,
|
|
@@ -97,6 +104,9 @@ export function validateMultisigConfig(config) {
|
|
|
97
104
|
}
|
|
98
105
|
signerCommitments.add(normalizedCommitment);
|
|
99
106
|
}
|
|
107
|
+
if (config.signerCommitments.length > MAX_SIGNERS) {
|
|
108
|
+
throw new Error(`too many signers (${config.signerCommitments.length}): a multisig account holds at most ${MAX_SIGNERS}`);
|
|
109
|
+
}
|
|
100
110
|
if (config.threshold > config.signerCommitments.length) {
|
|
101
111
|
throw new Error(`threshold (${config.threshold}) cannot exceed number of signers (${config.signerCommitments.length})`);
|
|
102
112
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.js","sourceRoot":"","sources":["../../src/account/builder.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,cAAc,EAEd,kBAAkB,EAClB,yBAAyB,EACzB,kBAAkB,EAClB,IAAI,EACJ,yBAAyB,GAE1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"builder.js","sourceRoot":"","sources":["../../src/account/builder.ts"],"names":[],"mappings":"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"}
|
package/dist/account/layout.d.ts
CHANGED
|
@@ -22,10 +22,10 @@ export declare const GUARDIAN_SLOT_NAMES: {
|
|
|
22
22
|
readonly SCHEME_ID: "miden::standards::auth::guardian::scheme";
|
|
23
23
|
};
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* The most approvers a multisig account can hold: `ApproverSet::MAX_APPROVERS`
|
|
26
|
+
* in miden-standards, enforced at account creation and again on-chain by
|
|
27
|
+
* `update_signers_and_threshold`. Readers bound their loops with it, so a
|
|
28
|
+
* corrupt `threshold_config` cannot drive an unbounded storage walk.
|
|
29
29
|
*/
|
|
30
|
-
export declare const MAX_SIGNERS =
|
|
30
|
+
export declare const MAX_SIGNERS = 64;
|
|
31
31
|
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/account/layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,mBAAmB;;;;;;CAMtB,CAAC;AAEX,eAAO,MAAM,mBAAmB;;;CAGtB,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/account/layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,mBAAmB;;;;;;CAMtB,CAAC;AAEX,eAAO,MAAM,mBAAmB;;;CAGtB,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,WAAW,KAAK,CAAC"}
|
package/dist/account/layout.js
CHANGED
|
@@ -22,10 +22,10 @@ export const GUARDIAN_SLOT_NAMES = {
|
|
|
22
22
|
SCHEME_ID: 'miden::standards::auth::guardian::scheme',
|
|
23
23
|
};
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* The most approvers a multisig account can hold: `ApproverSet::MAX_APPROVERS`
|
|
26
|
+
* in miden-standards, enforced at account creation and again on-chain by
|
|
27
|
+
* `update_signers_and_threshold`. Readers bound their loops with it, so a
|
|
28
|
+
* corrupt `threshold_config` cannot drive an unbounded storage walk.
|
|
29
29
|
*/
|
|
30
|
-
export const MAX_SIGNERS =
|
|
30
|
+
export const MAX_SIGNERS = 64;
|
|
31
31
|
//# sourceMappingURL=layout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/account/layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,gBAAgB,EAAE,oDAAoD;IACtE,kBAAkB,EAAE,wDAAwD;IAC5E,iBAAiB,EAAE,oDAAoD;IACvE,qBAAqB,EAAE,yDAAyD;IAChF,oBAAoB,EAAE,wDAAwD;CACtE,CAAC;AAEX,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,UAAU,EAAE,2CAA2C;IACvD,SAAS,EAAE,0CAA0C;CAC7C,CAAC;AAEX;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/account/layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,gBAAgB,EAAE,oDAAoD;IACtE,kBAAkB,EAAE,wDAAwD;IAC5E,iBAAiB,EAAE,oDAAoD;IACvE,qBAAqB,EAAE,yDAAyD;IAChF,oBAAoB,EAAE,wDAAwD;CACtE,CAAC;AAEX,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,UAAU,EAAE,2CAA2C;IACvD,SAAS,EAAE,0CAA0C;CAC7C,CAAC;AAEX;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC"}
|
package/dist/client.d.ts
CHANGED
|
@@ -93,7 +93,7 @@ export declare class MultisigClient {
|
|
|
93
93
|
* which would fail authentication first).
|
|
94
94
|
*
|
|
95
95
|
* @throws if `signer` does not implement `signLookupMessage`. The bundled
|
|
96
|
-
* `FalconSigner` and `
|
|
96
|
+
* `FalconSigner`, `EcdsaSigner`, and `LedgerSigner` do.
|
|
97
97
|
*/
|
|
98
98
|
recoverByKey(signer: Signer): Promise<RecoveredAccount[]>;
|
|
99
99
|
/**
|
|
@@ -112,5 +112,22 @@ export declare class MultisigClient {
|
|
|
112
112
|
* @returns A Multisig instance for the loaded account
|
|
113
113
|
*/
|
|
114
114
|
load(accountId: string, signer: Signer): Promise<Multisig>;
|
|
115
|
+
/**
|
|
116
|
+
* Reconcile the account GUARDIAN returned with the local store, and answer
|
|
117
|
+
* with the one that is actually current.
|
|
118
|
+
*
|
|
119
|
+
* Writing GUARDIAN's account only when the store held nothing meant a caller
|
|
120
|
+
* that already had the account kept its own copy while the returned
|
|
121
|
+
* `Multisig` carried a config derived from GUARDIAN's. One object, two
|
|
122
|
+
* sources, silently disagreeing: after a membership change the config said
|
|
123
|
+
* one thing and every account read said another, with no error.
|
|
124
|
+
*
|
|
125
|
+
* Overwriting unconditionally is not the fix either. Between pushing a delta
|
|
126
|
+
* and GUARDIAN canonicalizing it, local is legitimately ahead, and clobbering
|
|
127
|
+
* it would build the next transaction on a stale nonce. So this applies the
|
|
128
|
+
* same rule `syncState` does, and returns whichever account wins so the
|
|
129
|
+
* caller describes the state it will actually read.
|
|
130
|
+
*/
|
|
131
|
+
private adoptGuardianAccount;
|
|
115
132
|
}
|
|
116
133
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,WAAW,EAAsB,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,WAAW,EAAsB,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGzD,OAAO,EAEL,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEL,KAAK,SAAS,EAEf,MAAM,iBAAiB,CAAC;AAiBzB;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,iFAAiF;IACjF,GAAG,CAAC,EAAE,SAAS,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,WAAW,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAuB;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;IAC9C,OAAO,CAAC,eAAe,CAAqB;gBAEhC,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,oBAAoB;IAUlE;;;;OAIG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAM3C;;OAEG;IACH,IAAI,cAAc,IAAI,kBAAkB,CAEvC;IAED;;;;;;;;;OASG;IACG,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAe/D;;;;;;OAMG;IACG,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAwBvE;;;;;;OAMG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IA+ChE;;;;;;;;;;;;;;;OAeG;YACW,oBAAoB;CAuEnC"}
|
package/dist/client.js
CHANGED
|
@@ -10,6 +10,8 @@ import { Multisig } from './multisig.js';
|
|
|
10
10
|
import { createMultisigAccount } from './account/index.js';
|
|
11
11
|
import { AccountInspector, assertCompleteDetectedConfig } from './inspector.js';
|
|
12
12
|
import { requireConfigValue, requireMidenRpcEndpoint } from './raw-client.js';
|
|
13
|
+
import { isSafeToAdoptGuardianState, readOnChainCommitment } from './state/adopt.js';
|
|
14
|
+
import { normalizeHexWord } from './utils/encoding.js';
|
|
13
15
|
import { resolveProverConfig, } from './prover/config.js';
|
|
14
16
|
import { resolveRpcConfig, } from './rpc/config.js';
|
|
15
17
|
async function bindSignerAccountKey(signer, midenClient, accountId) {
|
|
@@ -84,7 +86,7 @@ export class MultisigClient {
|
|
|
84
86
|
* which would fail authentication first).
|
|
85
87
|
*
|
|
86
88
|
* @throws if `signer` does not implement `signLookupMessage`. The bundled
|
|
87
|
-
* `FalconSigner` and `
|
|
89
|
+
* `FalconSigner`, `EcdsaSigner`, and `LedgerSigner` do.
|
|
88
90
|
*/
|
|
89
91
|
async recoverByKey(signer) {
|
|
90
92
|
this._guardianClient.setSigner(signer);
|
|
@@ -129,7 +131,8 @@ export class MultisigClient {
|
|
|
129
131
|
for (let i = 0; i < binaryString.length; i++) {
|
|
130
132
|
accountBytes[i] = binaryString.charCodeAt(i);
|
|
131
133
|
}
|
|
132
|
-
const
|
|
134
|
+
const incomingAccount = Account.deserialize(accountBytes);
|
|
135
|
+
const account = await this.adoptGuardianAccount(accountId, incomingAccount);
|
|
133
136
|
const detected = AccountInspector.fromAccount(account);
|
|
134
137
|
// Fail closed on a partial read: the detected signer set becomes the
|
|
135
138
|
// authoritative config that membership proposals rewrite on-chain.
|
|
@@ -140,12 +143,82 @@ export class MultisigClient {
|
|
|
140
143
|
guardianCommitment: detected.guardianCommitment,
|
|
141
144
|
procedureThresholds: Array.from(detected.procedureThresholds.entries()).map(([procedure, threshold]) => ({ procedure, threshold })),
|
|
142
145
|
};
|
|
143
|
-
const existingAccount = await this.midenClient.accounts.get(AccountId.fromHex(accountId));
|
|
144
|
-
if (!existingAccount) {
|
|
145
|
-
await this.midenClient.accounts.insert({ account, overwrite: true });
|
|
146
|
-
}
|
|
147
146
|
await bindSignerAccountKey(signer, this.midenClient, accountId);
|
|
148
147
|
return new Multisig(account, config, this._guardianClient, signer, this.midenClient, accountId, this.midenRpcEndpoint, this.proverConfig, this.rpcConfig);
|
|
149
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Reconcile the account GUARDIAN returned with the local store, and answer
|
|
151
|
+
* with the one that is actually current.
|
|
152
|
+
*
|
|
153
|
+
* Writing GUARDIAN's account only when the store held nothing meant a caller
|
|
154
|
+
* that already had the account kept its own copy while the returned
|
|
155
|
+
* `Multisig` carried a config derived from GUARDIAN's. One object, two
|
|
156
|
+
* sources, silently disagreeing: after a membership change the config said
|
|
157
|
+
* one thing and every account read said another, with no error.
|
|
158
|
+
*
|
|
159
|
+
* Overwriting unconditionally is not the fix either. Between pushing a delta
|
|
160
|
+
* and GUARDIAN canonicalizing it, local is legitimately ahead, and clobbering
|
|
161
|
+
* it would build the next transaction on a stale nonce. So this applies the
|
|
162
|
+
* same rule `syncState` does, and returns whichever account wins so the
|
|
163
|
+
* caller describes the state it will actually read.
|
|
164
|
+
*/
|
|
165
|
+
async adoptGuardianAccount(accountId, incomingAccount) {
|
|
166
|
+
const localAccount = await this.midenClient.accounts.get(AccountId.fromHex(accountId));
|
|
167
|
+
if (!localAccount) {
|
|
168
|
+
// Still checked against chain, and through the same rule the other path
|
|
169
|
+
// uses. An empty store is the ordinary shape for loading an account this
|
|
170
|
+
// client has never held, so inserting without the check would make it the
|
|
171
|
+
// one path where GUARDIAN's word is taken on its own. Checking only that
|
|
172
|
+
// *some* commitment exists is not the check: it admits state that
|
|
173
|
+
// disagrees with chain, which is the thing being guarded against.
|
|
174
|
+
// Only for an account that has transacted. One that has not is not
|
|
175
|
+
// deployed, so there is no on-chain commitment to agree or disagree with,
|
|
176
|
+
// and reading the node for one would make loading a fresh account depend
|
|
177
|
+
// on the node as well as on GUARDIAN.
|
|
178
|
+
const transacted = incomingAccount.nonce().asInt() > BigInt(0);
|
|
179
|
+
const onChain = transacted
|
|
180
|
+
? await readOnChainCommitment(this.midenRpcEndpoint, AccountId.fromHex(accountId), this.rpcConfig)
|
|
181
|
+
: null;
|
|
182
|
+
// The one rule the shared predicate cannot apply here. It keys its
|
|
183
|
+
// null-commitment guard on the *local* nonce, because `syncState`
|
|
184
|
+
// legitimately meets a higher incoming nonce with no local history. With
|
|
185
|
+
// no local record at all, the incoming nonce is the only evidence there
|
|
186
|
+
// is: an account GUARDIAN reports as having transacted is deployed, so a
|
|
187
|
+
// node reporting nothing for it is an RPC or gateway failure rather than
|
|
188
|
+
// an undeployed account. `readOnChainCommitment` reports a missing
|
|
189
|
+
// account by matching `not found`, which a 404 also says.
|
|
190
|
+
if (transacted && !onChain) {
|
|
191
|
+
throw new Error(`Refusing to adopt GUARDIAN state for ${accountId}: it has transacted (nonce ${incomingAccount.nonce().asInt().toString()}) but the node reported no on-chain commitment, so it could not be checked against chain`);
|
|
192
|
+
}
|
|
193
|
+
// Handed the commitment already read, rather than letting the predicate
|
|
194
|
+
// read it again: two reads can disagree, and the guard above would then
|
|
195
|
+
// have passed on a different answer than the comparison rejects.
|
|
196
|
+
await isSafeToAdoptGuardianState({
|
|
197
|
+
accountId,
|
|
198
|
+
incomingAccount,
|
|
199
|
+
readCommitment: () => Promise.resolve(onChain),
|
|
200
|
+
});
|
|
201
|
+
await this.midenClient.accounts.insert({ account: incomingAccount, overwrite: true });
|
|
202
|
+
return incomingAccount;
|
|
203
|
+
}
|
|
204
|
+
const localCommitment = normalizeHexWord(localAccount.to_commitment().toHex());
|
|
205
|
+
const incomingCommitment = normalizeHexWord(incomingAccount.to_commitment().toHex());
|
|
206
|
+
// Equal commitments are the same state, and the safety rule reads an equal
|
|
207
|
+
// nonce as divergence, so it must not be asked about a pair that agrees.
|
|
208
|
+
if (localCommitment === incomingCommitment) {
|
|
209
|
+
return localAccount;
|
|
210
|
+
}
|
|
211
|
+
const adopt = await isSafeToAdoptGuardianState({
|
|
212
|
+
accountId,
|
|
213
|
+
incomingAccount,
|
|
214
|
+
localAccount,
|
|
215
|
+
readCommitment: () => readOnChainCommitment(this.midenRpcEndpoint, AccountId.fromHex(accountId), this.rpcConfig),
|
|
216
|
+
});
|
|
217
|
+
if (!adopt) {
|
|
218
|
+
return localAccount;
|
|
219
|
+
}
|
|
220
|
+
await this.midenClient.accounts.insert({ account: incomingAccount, overwrite: true });
|
|
221
|
+
return incomingAccount;
|
|
222
|
+
}
|
|
150
223
|
}
|
|
151
224
|
//# sourceMappingURL=client.js.map
|