@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
|
@@ -5,23 +5,85 @@ import type {
|
|
|
5
5
|
WasmWebClient,
|
|
6
6
|
} from '@miden-sdk/miden-sdk';
|
|
7
7
|
import { AccountId, ChainAnchor, Word } from '@miden-sdk/miden-sdk';
|
|
8
|
+
import { BoundBlockNotDeclaredError } from '../multisig/authArgErrors.js';
|
|
8
9
|
import { getRawMidenClient } from '../raw-client.js';
|
|
9
|
-
import { base64ToUint8Array, uint8ArrayToBase64 } from '../utils/encoding.js';
|
|
10
|
+
import { base64ToUint8Array, normalizeHexWord, uint8ArrayToBase64 } from '../utils/encoding.js';
|
|
11
|
+
import { requestBoundBlockNum } from './authArgs.js';
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* Layout of the six user params a multisig auth component binds into the
|
|
15
|
+
* transaction summary since protocol 0.17: the approval expiration block (or
|
|
16
|
+
* zero for an approval that never expires), a zero, then the four salt felts.
|
|
15
17
|
*/
|
|
16
|
-
const
|
|
18
|
+
const APPROVAL_EXPIRATION_USER_PARAM_INDEX = 0;
|
|
19
|
+
const SALT_USER_PARAM_OFFSET = 2;
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
22
|
+
* The summary binds the block the request's auth args name, and the anchor
|
|
23
|
+
* the store's sync height at capture. A sync landing between the build and the
|
|
24
|
+
* capture leaves them one block apart, and every cosigner's anchor check would
|
|
25
|
+
* then fail on a proposal nothing else is wrong with. Caught here, before the
|
|
26
|
+
* proposal is pushed, so the proposer rebuilds instead.
|
|
27
|
+
*/
|
|
28
|
+
export class SummaryAnchorMismatchError extends Error {
|
|
29
|
+
readonly retryable = true;
|
|
30
|
+
|
|
31
|
+
constructor(details: { anchorCommitmentHex: string; summaryBlockCommitmentHex: string }) {
|
|
32
|
+
super(
|
|
33
|
+
`the transaction summary binds block commitment ${details.summaryBlockCommitmentHex} but ` +
|
|
34
|
+
`the captured chain anchor is ${details.anchorCommitmentHex}; a sync landed between ` +
|
|
35
|
+
'building the request and capturing its anchor, so rebuild the request and retry',
|
|
36
|
+
);
|
|
37
|
+
this.name = 'SummaryAnchorMismatchError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The Miden client synced and its node still has not produced the block a
|
|
43
|
+
* proposal binds, so the proposal cannot execute at this client's tip yet.
|
|
44
|
+
* Worth retrying once the node catches up.
|
|
45
|
+
*/
|
|
46
|
+
export class ChainBehindBoundBlockError extends Error {
|
|
47
|
+
readonly retryable = true;
|
|
48
|
+
readonly syncHeight: number;
|
|
49
|
+
readonly boundBlockNum: number;
|
|
50
|
+
|
|
51
|
+
constructor(details: { syncHeight: number; boundBlockNum: number }) {
|
|
52
|
+
super(
|
|
53
|
+
`the Miden client synced to block ${details.syncHeight}, below block ` +
|
|
54
|
+
`${details.boundBlockNum} the proposal binds; its node has not reached that block yet`,
|
|
55
|
+
);
|
|
56
|
+
this.name = 'ChainBehindBoundBlockError';
|
|
57
|
+
this.syncHeight = details.syncHeight;
|
|
58
|
+
this.boundBlockNum = details.boundBlockNum;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Whether a failed re-execution came from chain state this client can catch up
|
|
64
|
+
* with rather than from the proposal itself: a node that has not reached the
|
|
65
|
+
* bound block yet, or account state the node pruned because this client had
|
|
66
|
+
* not synced recently. Either clears on a later attempt, which syncs first.
|
|
67
|
+
*/
|
|
68
|
+
export function isStaleChainError(error: unknown): boolean {
|
|
69
|
+
if (error instanceof ChainBehindBoundBlockError) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
73
|
+
return message.includes('has been pruned');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Derives the summary awaiting authorization for a proposal the caller is
|
|
78
|
+
* creating now, and captures a `ChainAnchor` at the current sync height to ship
|
|
79
|
+
* with it.
|
|
80
|
+
*
|
|
81
|
+
* The summary is derived at the chain tip, like every other execution of a
|
|
82
|
+
* multisig proposal (see {@link executeForSummaryAtTip}). The anchor still
|
|
83
|
+
* travels in the proposal: it names the block the request's auth args bind,
|
|
84
|
+
* which is how a rebuild learns that block, and 0.18.0-rc.1 clients re-execute
|
|
85
|
+
* at it. A proposer builds at the sync height the anchor is captured at, and
|
|
86
|
+
* the check below is what makes that hold.
|
|
25
87
|
*/
|
|
26
88
|
export function executeForSummary(
|
|
27
89
|
client: MidenClient,
|
|
@@ -41,18 +103,143 @@ export async function executeForSummary(
|
|
|
41
103
|
txRequest: TransactionRequest,
|
|
42
104
|
midenRpcEndpoint?: string,
|
|
43
105
|
): Promise<{ summary: TransactionSummary; anchor: ChainAnchor }> {
|
|
44
|
-
const acc = AccountId.fromHex(accountId);
|
|
45
106
|
const rawClient = await getRawMidenClient(client, midenRpcEndpoint);
|
|
46
107
|
const anchor = await rawClient.chainAnchorForRequest(txRequest);
|
|
47
|
-
|
|
108
|
+
let summary: TransactionSummary;
|
|
109
|
+
try {
|
|
110
|
+
summary = await executeForSummaryAtTip(rawClient, accountId, txRequest);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
anchor.free();
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const anchorCommitment = anchor.commitment();
|
|
117
|
+
const summaryBlockCommitment = summary.blockCommitment();
|
|
118
|
+
const anchorCommitmentHex = normalizeHexWord(anchorCommitment.toHex());
|
|
119
|
+
const summaryBlockCommitmentHex = normalizeHexWord(summaryBlockCommitment.toHex());
|
|
120
|
+
anchorCommitment.free?.();
|
|
121
|
+
summaryBlockCommitment.free?.();
|
|
122
|
+
if (anchorCommitmentHex !== summaryBlockCommitmentHex) {
|
|
123
|
+
anchor.free();
|
|
124
|
+
throw new SummaryAnchorMismatchError({ anchorCommitmentHex, summaryBlockCommitmentHex });
|
|
125
|
+
}
|
|
48
126
|
return { summary, anchor };
|
|
49
127
|
}
|
|
50
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Executes a multisig request at the chain tip to obtain the summary awaiting
|
|
131
|
+
* authorization. This is how cosigners and the executor reproduce a proposal's
|
|
132
|
+
* summary, whatever block they have synced to.
|
|
133
|
+
*
|
|
134
|
+
* Since protocol 0.17 a multisig summary binds the block its auth args name
|
|
135
|
+
* (the bound block), not the block the transaction executes against, so it
|
|
136
|
+
* reproduces at any later tip once the bound block is in the transaction's
|
|
137
|
+
* partial blockchain. The request declares it through `withBlockNumbers`, and
|
|
138
|
+
* foreign accounts, the fee faucet among them, load at the tip. Re-executing at
|
|
139
|
+
* the proposal's anchor instead loads them at the bound block, which a node
|
|
140
|
+
* prunes about 50 blocks later (issue #462).
|
|
141
|
+
*
|
|
142
|
+
* The client has to have synced to at least the bound block. When it has not,
|
|
143
|
+
* this syncs once before executing.
|
|
144
|
+
*
|
|
145
|
+
* @throws BoundBlockNotDeclaredError when the request binds a block in its
|
|
146
|
+
* multisig auth args without declaring it.
|
|
147
|
+
*/
|
|
148
|
+
export function executeForSummaryAtTip(
|
|
149
|
+
client: MidenClient,
|
|
150
|
+
accountId: string,
|
|
151
|
+
txRequest: TransactionRequest,
|
|
152
|
+
midenRpcEndpoint: string,
|
|
153
|
+
): Promise<TransactionSummary>;
|
|
154
|
+
export function executeForSummaryAtTip(
|
|
155
|
+
client: WasmWebClient,
|
|
156
|
+
accountId: string,
|
|
157
|
+
txRequest: TransactionRequest,
|
|
158
|
+
midenRpcEndpoint?: string,
|
|
159
|
+
): Promise<TransactionSummary>;
|
|
160
|
+
export async function executeForSummaryAtTip(
|
|
161
|
+
client: MidenClient | WasmWebClient,
|
|
162
|
+
accountId: string,
|
|
163
|
+
txRequest: TransactionRequest,
|
|
164
|
+
midenRpcEndpoint?: string,
|
|
165
|
+
): Promise<TransactionSummary> {
|
|
166
|
+
const rawClient = await getRawMidenClient(client, midenRpcEndpoint);
|
|
167
|
+
await prepareTipExecution(rawClient, txRequest);
|
|
168
|
+
return rawClient.executeForSummary(AccountId.fromHex(accountId), txRequest);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Gets `client` ready to execute `request` at the chain tip: checks the
|
|
173
|
+
* request declares the block its multisig auth args bind, and syncs to that
|
|
174
|
+
* block (see {@link syncToBoundBlock}).
|
|
175
|
+
*
|
|
176
|
+
* @throws BoundBlockNotDeclaredError when the request binds a block in its
|
|
177
|
+
* multisig auth args without declaring it.
|
|
178
|
+
*/
|
|
179
|
+
export async function prepareTipExecution(
|
|
180
|
+
client: WasmWebClient,
|
|
181
|
+
request: TransactionRequest,
|
|
182
|
+
syncState?: () => Promise<unknown>,
|
|
183
|
+
): Promise<void> {
|
|
184
|
+
const boundBlockNum = requireDeclaredBoundBlock(request);
|
|
185
|
+
if (boundBlockNum !== undefined) {
|
|
186
|
+
await syncToBoundBlock(client, boundBlockNum, syncState);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The block `request`'s multisig auth args bind, after checking the request
|
|
192
|
+
* declares it. `undefined` for a request without multisig auth args, which
|
|
193
|
+
* has no bound block to declare.
|
|
194
|
+
*
|
|
195
|
+
* @throws BoundBlockNotDeclaredError when the block is bound but not declared.
|
|
196
|
+
*/
|
|
197
|
+
export function requireDeclaredBoundBlock(request: TransactionRequest): number | undefined {
|
|
198
|
+
const boundBlockNum = requestBoundBlockNum(request);
|
|
199
|
+
if (boundBlockNum !== undefined && !request.blockNumbers().includes(boundBlockNum)) {
|
|
200
|
+
throw new BoundBlockNotDeclaredError(boundBlockNum);
|
|
201
|
+
}
|
|
202
|
+
return boundBlockNum;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Syncs `client` once when its sync height is below `blockNum`, the block a
|
|
207
|
+
* proposal binds. Execution at a tip below it fails with "requested block N is
|
|
208
|
+
* after transaction reference block M", and a store that has never synced (a
|
|
209
|
+
* cosigner that has only just loaded the account) holds no header to rebuild
|
|
210
|
+
* the request from. `syncState` lets a caller wrap the sync in its own retry
|
|
211
|
+
* policy.
|
|
212
|
+
*
|
|
213
|
+
* This does not make a store that is already past the bound block current. An
|
|
214
|
+
* execution loads foreign accounts, the fee faucet among them, at the store's
|
|
215
|
+
* sync height, which a node prunes about 50 blocks later, so the multisig
|
|
216
|
+
* entry points that re-execute a proposal sync the chain first.
|
|
217
|
+
*
|
|
218
|
+
* @throws ChainBehindBoundBlockError when the node has not reached the block.
|
|
219
|
+
*/
|
|
220
|
+
export async function syncToBoundBlock(
|
|
221
|
+
client: WasmWebClient,
|
|
222
|
+
blockNum: number,
|
|
223
|
+
syncState: () => Promise<unknown> = () => client.syncState(),
|
|
224
|
+
): Promise<void> {
|
|
225
|
+
if ((await client.getSyncHeight()) >= blockNum) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
await syncState();
|
|
229
|
+
const syncHeight = await client.getSyncHeight();
|
|
230
|
+
if (syncHeight < blockNum) {
|
|
231
|
+
throw new ChainBehindBoundBlockError({ syncHeight, boundBlockNum: blockNum });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
51
235
|
/**
|
|
52
236
|
* Executes a transaction at the given `ChainAnchor`'s reference block to
|
|
53
|
-
* obtain the summary awaiting authorization
|
|
54
|
-
*
|
|
55
|
-
*
|
|
237
|
+
* obtain the summary awaiting authorization.
|
|
238
|
+
*
|
|
239
|
+
* For a summary that binds the reference block, such as a single-signature
|
|
240
|
+
* one. A multisig proposal's summary binds its bound block instead and is
|
|
241
|
+
* reproduced with {@link executeForSummaryAtTip}: re-executing it at an anchor
|
|
242
|
+
* fails once the node prunes the anchor block's account state.
|
|
56
243
|
*/
|
|
57
244
|
export function executeForSummaryAt(
|
|
58
245
|
client: MidenClient,
|
|
@@ -91,26 +278,48 @@ export function chainAnchorToBase64(anchor: ChainAnchor): string {
|
|
|
91
278
|
* Deserializes a `ChainAnchor` from its base64 wire form. `ChainAnchor`
|
|
92
279
|
* deserialization validates the header/chain consistency internally, so a
|
|
93
280
|
* decoded anchor only needs its block commitment checked against the signed
|
|
94
|
-
* transaction summary before it is
|
|
281
|
+
* transaction summary before the block it names is taken as the one the
|
|
282
|
+
* summary binds.
|
|
95
283
|
*/
|
|
96
284
|
export function chainAnchorFromBase64(anchorBase64: string): ChainAnchor {
|
|
97
285
|
return ChainAnchor.deserialize(base64ToUint8Array(anchorBase64));
|
|
98
286
|
}
|
|
99
287
|
|
|
100
288
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
289
|
+
* The block a proposal's `chainAnchor` names, which is the block its summary
|
|
290
|
+
* binds: a custom producer rebuilds its request at this block. Decodes the
|
|
291
|
+
* anchor for the one number and frees it.
|
|
292
|
+
*/
|
|
293
|
+
export function chainAnchorBlockNum(anchorBase64: string): number {
|
|
294
|
+
const anchor = chainAnchorFromBase64(anchorBase64);
|
|
295
|
+
try {
|
|
296
|
+
return anchor.blockNum();
|
|
297
|
+
} finally {
|
|
298
|
+
anchor.free();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Reads the salt a multisig transaction summary binds.
|
|
107
304
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
305
|
+
* Since protocol 0.17 the multisig auth components bind the salt itself into
|
|
306
|
+
* the summary's user params rather than a commitment derived from it, so the
|
|
307
|
+
* value cosigners signed over is readable again. A proposal still carries the
|
|
308
|
+
* salt in its metadata, because a request has to be rebuilt before any summary
|
|
309
|
+
* exists; this reader is the cross-check that the two agree.
|
|
310
|
+
*/
|
|
311
|
+
export function summarySalt(summary: TransactionSummary): Word {
|
|
312
|
+
return Word.newFromFelts(
|
|
313
|
+
summary.userParams().slice(SALT_USER_PARAM_OFFSET, SALT_USER_PARAM_OFFSET + 4),
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Reads the block at which the approvers' signatures stop authorizing the
|
|
319
|
+
* transaction, or `undefined` for an approval that never expires, which is
|
|
320
|
+
* what this package's builders produce.
|
|
113
321
|
*/
|
|
114
|
-
export function
|
|
115
|
-
|
|
322
|
+
export function summaryApprovalExpirationBlockNum(summary: TransactionSummary): number | undefined {
|
|
323
|
+
const value = summary.userParams()[APPROVAL_EXPIRATION_USER_PARAM_INDEX].asInt();
|
|
324
|
+
return value === 0n ? undefined : Number(value);
|
|
116
325
|
}
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type MidenClient,
|
|
3
3
|
TransactionRequest,
|
|
4
|
-
TransactionRequestBuilder,
|
|
5
4
|
TransactionScript,
|
|
6
5
|
type WasmWebClient,
|
|
7
6
|
Word,
|
|
8
|
-
Word as WordType,
|
|
9
7
|
} from '@miden-sdk/miden-sdk';
|
|
10
8
|
import { compileTxScript } from '../raw-client.js';
|
|
11
9
|
import { normalizeHexWord } from '../utils/encoding.js';
|
|
12
|
-
import { randomWord } from '../utils/random.js';
|
|
13
10
|
import { authSchemeId } from '../utils/signature.js';
|
|
14
|
-
import
|
|
11
|
+
import { buildMultisigRequest, multisigRequestBuilder } from './authArgs.js';
|
|
12
|
+
import type { MidenClientMultisigRequestOptions, MultisigRequestOptions } from './options.js';
|
|
15
13
|
import type { SignatureScheme } from '../types.js';
|
|
16
14
|
|
|
17
15
|
async function buildUpdateGuardianScript(
|
|
@@ -44,17 +42,17 @@ end
|
|
|
44
42
|
export function buildUpdateGuardianTransactionRequest(
|
|
45
43
|
client: MidenClient,
|
|
46
44
|
newGuardianPubkey: string,
|
|
47
|
-
options:
|
|
45
|
+
options: MidenClientMultisigRequestOptions,
|
|
48
46
|
): Promise<{ request: TransactionRequest; salt: Word }>;
|
|
49
47
|
export function buildUpdateGuardianTransactionRequest(
|
|
50
48
|
client: WasmWebClient,
|
|
51
49
|
newGuardianPubkey: string,
|
|
52
|
-
options
|
|
50
|
+
options: MultisigRequestOptions,
|
|
53
51
|
): Promise<{ request: TransactionRequest; salt: Word }>;
|
|
54
52
|
export async function buildUpdateGuardianTransactionRequest(
|
|
55
53
|
client: MidenClient | WasmWebClient,
|
|
56
54
|
newGuardianPubkey: string,
|
|
57
|
-
options:
|
|
55
|
+
options: MultisigRequestOptions,
|
|
58
56
|
): Promise<{ request: TransactionRequest; salt: Word }> {
|
|
59
57
|
const signatureScheme = options.signatureScheme ?? 'falcon';
|
|
60
58
|
const script = await buildUpdateGuardianScript(
|
|
@@ -64,24 +62,12 @@ export async function buildUpdateGuardianTransactionRequest(
|
|
|
64
62
|
options.midenRpcEndpoint,
|
|
65
63
|
);
|
|
66
64
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
let txBuilder = new TransactionRequestBuilder();
|
|
71
|
-
txBuilder = txBuilder.withCustomScript(script);
|
|
72
|
-
txBuilder = txBuilder.withFeeConversionSalt(authSaltForBuilder);
|
|
73
|
-
// Borrows rather than consumes: the glue passes `__wbg_ptr` without taking it,
|
|
74
|
-
// so the handle stays ours to release once the builder has read it.
|
|
75
|
-
authSaltForBuilder.free?.();
|
|
65
|
+
const { builder, saltHex } = await multisigRequestBuilder(client, options);
|
|
66
|
+
let txBuilder = builder.withCustomScript(script);
|
|
76
67
|
|
|
77
68
|
if (options.signatureAdviceMap) {
|
|
78
69
|
txBuilder = txBuilder.extendAdviceMap(options.signatureAdviceMap);
|
|
79
70
|
}
|
|
80
71
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
request: txBuilder.build(),
|
|
85
|
-
salt: authSaltForReturn,
|
|
86
|
-
};
|
|
72
|
+
return buildMultisigRequest(txBuilder, saltHex, options.accountId);
|
|
87
73
|
}
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
type MidenClient,
|
|
5
5
|
Poseidon2,
|
|
6
6
|
TransactionRequest,
|
|
7
|
-
TransactionRequestBuilder,
|
|
8
7
|
TransactionScript,
|
|
9
8
|
type WasmWebClient,
|
|
10
9
|
Word,
|
|
@@ -13,8 +12,8 @@ import {
|
|
|
13
12
|
import { getProcedureRoot, type ProcedureName } from '../procedures.js';
|
|
14
13
|
import { compileTxScript } from '../raw-client.js';
|
|
15
14
|
import { normalizeHexWord } from '../utils/encoding.js';
|
|
16
|
-
import {
|
|
17
|
-
import type {
|
|
15
|
+
import { buildMultisigRequest, multisigRequestBuilder } from './authArgs.js';
|
|
16
|
+
import type { MidenClientMultisigRequestOptions, MultisigRequestOptions } from './options.js';
|
|
18
17
|
|
|
19
18
|
function buildProcedureThresholdFelts(procedure: ProcedureName, threshold: number): Felt[] {
|
|
20
19
|
const procedureRoot = WordType.fromHex(normalizeHexWord(getProcedureRoot(procedure)));
|
|
@@ -66,19 +65,19 @@ export function buildUpdateProcedureThresholdTransactionRequest(
|
|
|
66
65
|
client: MidenClient,
|
|
67
66
|
procedure: ProcedureName,
|
|
68
67
|
threshold: number,
|
|
69
|
-
options:
|
|
68
|
+
options: MidenClientMultisigRequestOptions,
|
|
70
69
|
): Promise<{ request: TransactionRequest; salt: Word; configHash: Word }>;
|
|
71
70
|
export function buildUpdateProcedureThresholdTransactionRequest(
|
|
72
71
|
client: WasmWebClient,
|
|
73
72
|
procedure: ProcedureName,
|
|
74
73
|
threshold: number,
|
|
75
|
-
options
|
|
74
|
+
options: MultisigRequestOptions,
|
|
76
75
|
): Promise<{ request: TransactionRequest; salt: Word; configHash: Word }>;
|
|
77
76
|
export async function buildUpdateProcedureThresholdTransactionRequest(
|
|
78
77
|
client: MidenClient | WasmWebClient,
|
|
79
78
|
procedure: ProcedureName,
|
|
80
79
|
threshold: number,
|
|
81
|
-
options:
|
|
80
|
+
options: MultisigRequestOptions,
|
|
82
81
|
): Promise<{ request: TransactionRequest; salt: Word; configHash: Word }> {
|
|
83
82
|
const configHash = buildProcedureThresholdConfigHash(procedure, threshold);
|
|
84
83
|
|
|
@@ -88,23 +87,12 @@ export async function buildUpdateProcedureThresholdTransactionRequest(
|
|
|
88
87
|
threshold,
|
|
89
88
|
options.midenRpcEndpoint,
|
|
90
89
|
);
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
let txBuilder = new TransactionRequestBuilder();
|
|
95
|
-
txBuilder = txBuilder.withCustomScript(script);
|
|
96
|
-
txBuilder = txBuilder.withFeeConversionSalt(authSalt);
|
|
97
|
-
// Borrows rather than consumes: the glue passes `__wbg_ptr` without taking it,
|
|
98
|
-
// so the handle stays ours to release once the builder has read it.
|
|
99
|
-
authSalt.free?.();
|
|
90
|
+
const { builder, saltHex } = await multisigRequestBuilder(client, options);
|
|
91
|
+
let txBuilder = builder.withCustomScript(script);
|
|
100
92
|
|
|
101
93
|
if (options.signatureAdviceMap) {
|
|
102
94
|
txBuilder = txBuilder.extendAdviceMap(options.signatureAdviceMap);
|
|
103
95
|
}
|
|
104
96
|
|
|
105
|
-
return {
|
|
106
|
-
request: txBuilder.build(),
|
|
107
|
-
salt: WordType.fromHex(normalizeHexWord(authSaltHex)),
|
|
108
|
-
configHash,
|
|
109
|
-
};
|
|
97
|
+
return { ...buildMultisigRequest(txBuilder, saltHex, options.accountId), configHash };
|
|
110
98
|
}
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
type MidenClient,
|
|
6
6
|
Poseidon2,
|
|
7
7
|
TransactionRequest,
|
|
8
|
-
TransactionRequestBuilder,
|
|
9
8
|
TransactionScript,
|
|
10
9
|
type WasmWebClient,
|
|
11
10
|
Word,
|
|
@@ -13,9 +12,9 @@ import {
|
|
|
13
12
|
} from '@miden-sdk/miden-sdk';
|
|
14
13
|
import { compileTxScript } from '../raw-client.js';
|
|
15
14
|
import { normalizeHexWord } from '../utils/encoding.js';
|
|
16
|
-
import { randomWord } from '../utils/random.js';
|
|
17
15
|
import { authSchemeId } from '../utils/signature.js';
|
|
18
|
-
import
|
|
16
|
+
import { buildMultisigRequest, multisigRequestBuilder } from './authArgs.js';
|
|
17
|
+
import type { MidenClientMultisigRequestOptions, MultisigRequestOptions } from './options.js';
|
|
19
18
|
import type { SignatureScheme } from '../types.js';
|
|
20
19
|
|
|
21
20
|
function buildMultisigConfigFelts(
|
|
@@ -77,19 +76,19 @@ export function buildUpdateSignersTransactionRequest(
|
|
|
77
76
|
client: MidenClient,
|
|
78
77
|
threshold: number,
|
|
79
78
|
signerCommitments: string[],
|
|
80
|
-
options:
|
|
79
|
+
options: MidenClientMultisigRequestOptions,
|
|
81
80
|
): Promise<{ request: TransactionRequest; salt: Word; configHash: Word }>;
|
|
82
81
|
export function buildUpdateSignersTransactionRequest(
|
|
83
82
|
client: WasmWebClient,
|
|
84
83
|
threshold: number,
|
|
85
84
|
signerCommitments: string[],
|
|
86
|
-
options
|
|
85
|
+
options: MultisigRequestOptions,
|
|
87
86
|
): Promise<{ request: TransactionRequest; salt: Word; configHash: Word }>;
|
|
88
87
|
export async function buildUpdateSignersTransactionRequest(
|
|
89
88
|
client: MidenClient | WasmWebClient,
|
|
90
89
|
threshold: number,
|
|
91
90
|
signerCommitments: string[],
|
|
92
|
-
options:
|
|
91
|
+
options: MultisigRequestOptions,
|
|
93
92
|
): Promise<{ request: TransactionRequest; salt: Word; configHash: Word }> {
|
|
94
93
|
const signatureScheme = options.signatureScheme ?? 'falcon';
|
|
95
94
|
const { configHash: configHashForAdvice, payload } = buildMultisigConfigAdvice(
|
|
@@ -115,28 +114,18 @@ export async function buildUpdateSignersTransactionRequest(
|
|
|
115
114
|
|
|
116
115
|
const script = await buildUpdateSignersScript(client, options.midenRpcEndpoint);
|
|
117
116
|
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
txBuilder = txBuilder.withCustomScript(script);
|
|
124
|
-
txBuilder = txBuilder.withScriptArg(configHashForScript);
|
|
125
|
-
txBuilder = txBuilder.extendAdviceMap(advice);
|
|
126
|
-
txBuilder = txBuilder.withFeeConversionSalt(authSaltForBuilder);
|
|
127
|
-
// Borrows rather than consumes: the glue passes `__wbg_ptr` without taking it,
|
|
128
|
-
// so the handle stays ours to release once the builder has read it.
|
|
129
|
-
authSaltForBuilder.free?.();
|
|
117
|
+
const { builder, saltHex } = await multisigRequestBuilder(client, options);
|
|
118
|
+
let txBuilder = builder
|
|
119
|
+
.withCustomScript(script)
|
|
120
|
+
.withScriptArg(configHashForScript)
|
|
121
|
+
.extendAdviceMap(advice);
|
|
130
122
|
|
|
131
123
|
if (options.signatureAdviceMap) {
|
|
132
124
|
txBuilder = txBuilder.extendAdviceMap(options.signatureAdviceMap);
|
|
133
125
|
}
|
|
134
126
|
|
|
135
|
-
const authSaltForReturn = WordType.fromHex(normalizeHexWord(authSaltHex));
|
|
136
|
-
|
|
137
127
|
return {
|
|
138
|
-
|
|
139
|
-
salt: authSaltForReturn,
|
|
128
|
+
...buildMultisigRequest(txBuilder, saltHex, options.accountId),
|
|
140
129
|
configHash: configHashForReturn,
|
|
141
130
|
};
|
|
142
131
|
}
|
package/src/transaction.ts
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
|
+
export {
|
|
2
|
+
MAX_APPROVAL_EXPIRATION_DELTA,
|
|
3
|
+
buildMultisigRequest,
|
|
4
|
+
multisigRequestBuilder,
|
|
5
|
+
requestBoundBlockNum,
|
|
6
|
+
requestSaltHex,
|
|
7
|
+
} from './transaction/authArgs.js';
|
|
1
8
|
export {
|
|
2
9
|
buildConsumeNotesTransactionRequest,
|
|
10
|
+
buildConsumeNotesTransactionRequestFromNotes,
|
|
3
11
|
} from './transaction/consumeNotes.js';
|
|
4
12
|
export {
|
|
13
|
+
ChainBehindBoundBlockError,
|
|
14
|
+
chainAnchorBlockNum,
|
|
5
15
|
chainAnchorFromBase64,
|
|
6
16
|
chainAnchorToBase64,
|
|
7
17
|
executeForSummary,
|
|
8
18
|
executeForSummaryAt,
|
|
9
|
-
|
|
19
|
+
executeForSummaryAtTip,
|
|
20
|
+
prepareTipExecution,
|
|
21
|
+
isStaleChainError,
|
|
22
|
+
requireDeclaredBoundBlock,
|
|
23
|
+
syncToBoundBlock,
|
|
24
|
+
summaryApprovalExpirationBlockNum,
|
|
25
|
+
summarySalt,
|
|
26
|
+
SummaryAnchorMismatchError,
|
|
10
27
|
} from './transaction/summary.js';
|
|
11
28
|
export {
|
|
12
29
|
buildP2idNoteFromMetadata,
|
package/src/types/proposal.ts
CHANGED
|
@@ -38,11 +38,11 @@ interface BaseProposalMetadata {
|
|
|
38
38
|
saltHex?: string;
|
|
39
39
|
requiredSignatures?: number;
|
|
40
40
|
/**
|
|
41
|
-
* Base64-serialized Miden `ChainAnchor`
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
41
|
+
* Base64-serialized Miden `ChainAnchor` at the block the proposal's
|
|
42
|
+
* transaction summary binds, the proposer's sync height when it built the
|
|
43
|
+
* request. Required, and checked against the summary's block commitment: a
|
|
44
|
+
* rebuild binds the block it names. The proposal executes at the chain tip,
|
|
45
|
+
* not at the anchor; 0.18.0-rc.1 clients still re-execute at it.
|
|
46
46
|
*/
|
|
47
47
|
chainAnchor?: string;
|
|
48
48
|
}
|
|
@@ -162,6 +162,36 @@ export interface Proposal {
|
|
|
162
162
|
txSummary: string;
|
|
163
163
|
signatures: ProposalSignatureEntry[];
|
|
164
164
|
metadata: ProposalMetadata;
|
|
165
|
+
/**
|
|
166
|
+
* Result of the last summary-binding check on this value. Only the check
|
|
167
|
+
* itself writes `verified`; a freshly parsed or imported proposal is
|
|
168
|
+
* `unchecked`. `syncProposals` surfaces failed proposals instead of
|
|
169
|
+
* failing wholesale (issue #462); `signProposal` and `executeProposal`
|
|
170
|
+
* re-verify and refuse them.
|
|
171
|
+
*/
|
|
172
|
+
verification: ProposalVerification;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Outcome of checking a proposal's metadata against its signed summary.
|
|
177
|
+
* `failed.retryable` is true when the failure came from a transient node or
|
|
178
|
+
* RPC error, or from chain state this client had not caught up with, so the
|
|
179
|
+
* same proposal may verify on a later sync; false when the proposal itself
|
|
180
|
+
* cannot be reproduced (tampered metadata, for example) and it has to be
|
|
181
|
+
* re-proposed.
|
|
182
|
+
*/
|
|
183
|
+
export type ProposalVerification =
|
|
184
|
+
| { status: 'unchecked' }
|
|
185
|
+
| { status: 'verified' }
|
|
186
|
+
| { status: 'failed'; retryable: boolean; message: string };
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* True when the proposal is both verified and has met its signature
|
|
190
|
+
* threshold, i.e. it can be executed. `status` alone keeps meaning
|
|
191
|
+
* "threshold met": a fully signed proposal can still be dead.
|
|
192
|
+
*/
|
|
193
|
+
export function isProposalActionable(proposal: Proposal): boolean {
|
|
194
|
+
return proposal.verification.status === 'verified' && proposal.status === 'ready';
|
|
165
195
|
}
|
|
166
196
|
|
|
167
197
|
export interface TransactionProposal {
|
|
@@ -185,6 +215,7 @@ export interface ExportedProposal {
|
|
|
185
215
|
signatureHex: string;
|
|
186
216
|
scheme?: SignatureScheme;
|
|
187
217
|
publicKey?: string;
|
|
218
|
+
messageFormat?: 'eip712';
|
|
188
219
|
timestamp?: string;
|
|
189
220
|
}>;
|
|
190
221
|
metadata: ProposalMetadata;
|