@openzeppelin/miden-multisig-client 0.16.1 → 0.17.0-rc.1
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 +192 -11
- package/dist/account/builder.d.ts.map +1 -1
- package/dist/account/builder.js +32 -25
- package/dist/account/builder.js.map +1 -1
- package/dist/account/builder.test.js +73 -21
- package/dist/account/builder.test.js.map +1 -1
- package/dist/account/layout.d.ts +31 -0
- package/dist/account/layout.d.ts.map +1 -0
- package/dist/account/layout.js +31 -0
- package/dist/account/layout.js.map +1 -0
- package/dist/account/masm/account-components/auth.d.ts +1 -4
- package/dist/account/masm/account-components/auth.d.ts.map +1 -1
- package/dist/account/masm/account-components/auth.js +36 -53
- package/dist/account/masm/account-components/auth.js.map +1 -1
- package/dist/account/masm/index.d.ts +0 -1
- package/dist/account/masm/index.d.ts.map +1 -1
- package/dist/account/masm/index.js +0 -1
- package/dist/account/masm/index.js.map +1 -1
- package/dist/account/storage.d.ts +4 -0
- package/dist/account/storage.d.ts.map +1 -1
- package/dist/account/storage.js +9 -20
- package/dist/account/storage.js.map +1 -1
- package/dist/client.d.ts +4 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +10 -5
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +74 -16
- package/dist/client.test.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/inspector.d.ts +59 -1
- package/dist/inspector.d.ts.map +1 -1
- package/dist/inspector.js +157 -29
- package/dist/inspector.js.map +1 -1
- package/dist/inspector.test.js +246 -37
- package/dist/inspector.test.js.map +1 -1
- package/dist/multisig.d.ts +109 -33
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +307 -104
- package/dist/multisig.js.map +1 -1
- package/dist/multisig.test.js +582 -58
- package/dist/multisig.test.js.map +1 -1
- package/dist/procedures.d.ts +6 -7
- package/dist/procedures.d.ts.map +1 -1
- package/dist/procedures.js +6 -7
- package/dist/procedures.js.map +1 -1
- package/dist/proposal/metadata.d.ts.map +1 -1
- package/dist/proposal/metadata.js +13 -2
- package/dist/proposal/metadata.js.map +1 -1
- package/dist/proposal/metadata.test.js +57 -0
- package/dist/proposal/metadata.test.js.map +1 -1
- package/dist/prover/errors.d.ts +5 -0
- package/dist/prover/errors.d.ts.map +1 -1
- package/dist/prover/errors.js +7 -155
- package/dist/prover/errors.js.map +1 -1
- package/dist/prover/errors.test.js +5 -0
- package/dist/prover/errors.test.js.map +1 -1
- package/dist/prover/retry.d.ts +1 -6
- package/dist/prover/retry.d.ts.map +1 -1
- package/dist/prover/retry.js +5 -28
- package/dist/prover/retry.js.map +1 -1
- package/dist/prover/retry.test.js +1 -1
- package/dist/prover/retry.test.js.map +1 -1
- package/dist/prover/workflow.d.ts +4 -3
- package/dist/prover/workflow.d.ts.map +1 -1
- package/dist/prover/workflow.js +5 -2
- package/dist/prover/workflow.js.map +1 -1
- package/dist/prover/workflow.test.js +28 -3
- package/dist/prover/workflow.test.js.map +1 -1
- package/dist/retry/classify.d.ts +16 -0
- package/dist/retry/classify.d.ts.map +1 -0
- package/dist/retry/classify.js +187 -0
- package/dist/retry/classify.js.map +1 -0
- package/dist/retry/runtime.d.ts +13 -0
- package/dist/retry/runtime.d.ts.map +1 -0
- package/dist/retry/runtime.js +33 -0
- package/dist/retry/runtime.js.map +1 -0
- package/dist/rpc/config.d.ts +11 -0
- package/dist/rpc/config.d.ts.map +1 -0
- package/dist/rpc/config.js +17 -0
- package/dist/rpc/config.js.map +1 -0
- package/dist/rpc/config.test.d.ts +2 -0
- package/dist/rpc/config.test.d.ts.map +1 -0
- package/dist/rpc/config.test.js +24 -0
- package/dist/rpc/config.test.js.map +1 -0
- package/dist/rpc/errors.d.ts +2 -0
- package/dist/rpc/errors.d.ts.map +1 -0
- package/dist/rpc/errors.js +11 -0
- package/dist/rpc/errors.js.map +1 -0
- package/dist/rpc/errors.test.d.ts +2 -0
- package/dist/rpc/errors.test.d.ts.map +1 -0
- package/dist/rpc/errors.test.js +34 -0
- package/dist/rpc/errors.test.js.map +1 -0
- package/dist/rpc/retry.d.ts +4 -0
- package/dist/rpc/retry.d.ts.map +1 -0
- package/dist/rpc/retry.js +6 -0
- package/dist/rpc/retry.js.map +1 -0
- package/dist/rpc/retry.test.d.ts +2 -0
- package/dist/rpc/retry.test.d.ts.map +1 -0
- package/dist/rpc/retry.test.js +98 -0
- package/dist/rpc/retry.test.js.map +1 -0
- package/dist/transaction/index.d.ts +1 -1
- package/dist/transaction/index.d.ts.map +1 -1
- package/dist/transaction/index.js +1 -1
- package/dist/transaction/index.js.map +1 -1
- package/dist/transaction/p2id.d.ts +17 -6
- package/dist/transaction/p2id.d.ts.map +1 -1
- package/dist/transaction/p2id.js +22 -31
- package/dist/transaction/p2id.js.map +1 -1
- package/dist/transaction/p2id.test.js +62 -24
- package/dist/transaction/p2id.test.js.map +1 -1
- package/dist/transaction/summary.d.ts +45 -2
- package/dist/transaction/summary.d.ts.map +1 -1
- package/dist/transaction/summary.js +42 -2
- package/dist/transaction/summary.js.map +1 -1
- package/dist/transaction/summary.test.d.ts +2 -0
- package/dist/transaction/summary.test.d.ts.map +1 -0
- package/dist/transaction/summary.test.js +26 -0
- package/dist/transaction/summary.test.js.map +1 -0
- package/dist/transaction/updateGuardian.d.ts.map +1 -1
- package/dist/transaction/updateGuardian.js +16 -18
- package/dist/transaction/updateGuardian.js.map +1 -1
- package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
- package/dist/transaction/updateProcedureThreshold.js +15 -17
- package/dist/transaction/updateProcedureThreshold.js.map +1 -1
- package/dist/transaction/updateSigners.d.ts +6 -1
- package/dist/transaction/updateSigners.d.ts.map +1 -1
- package/dist/transaction/updateSigners.js +20 -17
- package/dist/transaction/updateSigners.js.map +1 -1
- package/dist/transaction.d.ts +2 -2
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +1 -1
- package/dist/transaction.js.map +1 -1
- package/dist/types/proposal.d.ts +28 -4
- package/dist/types/proposal.d.ts.map +1 -1
- package/dist/types/proposal.js +18 -0
- package/dist/types/proposal.js.map +1 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/signature.d.ts +11 -7
- package/dist/utils/signature.d.ts.map +1 -1
- package/dist/utils/signature.js +24 -58
- package/dist/utils/signature.js.map +1 -1
- package/dist/utils/word.d.ts +7 -0
- package/dist/utils/word.d.ts.map +1 -1
- package/dist/utils/word.js +15 -0
- package/dist/utils/word.js.map +1 -1
- package/masm/account_components/auth/guarded_multisig.masm +42 -0
- package/package.json +7 -4
- package/src/account/builder.test.ts +111 -45
- package/src/account/builder.ts +45 -33
- package/src/account/layout.ts +33 -0
- package/src/account/masm/account-components/auth.ts +36 -56
- package/src/account/masm/index.ts +0 -1
- package/src/account/storage.ts +9 -22
- package/src/client.test.ts +87 -16
- package/src/client.ts +16 -3
- package/src/index.ts +27 -1
- package/src/inspector.test.ts +330 -38
- package/src/inspector.ts +196 -33
- package/src/multisig.test.ts +680 -63
- package/src/multisig.ts +400 -115
- package/src/procedures.ts +6 -7
- package/src/proposal/metadata.test.ts +76 -0
- package/src/proposal/metadata.ts +13 -2
- package/src/prover/errors.test.ts +8 -0
- package/src/prover/errors.ts +7 -175
- package/src/prover/retry.test.ts +1 -1
- package/src/prover/retry.ts +10 -35
- package/src/prover/workflow.test.ts +40 -4
- package/src/prover/workflow.ts +16 -4
- package/src/retry/classify.ts +220 -0
- package/src/retry/runtime.ts +45 -0
- package/src/rpc/config.test.ts +45 -0
- package/src/rpc/config.ts +30 -0
- package/src/rpc/errors.test.ts +69 -0
- package/src/rpc/errors.ts +12 -0
- package/src/rpc/retry.test.ts +144 -0
- package/src/rpc/retry.ts +12 -0
- package/src/transaction/index.ts +7 -1
- package/src/transaction/p2id.test.ts +112 -31
- package/src/transaction/p2id.ts +39 -38
- package/src/transaction/summary.test.ts +32 -0
- package/src/transaction/summary.ts +83 -4
- package/src/transaction/updateGuardian.ts +15 -25
- package/src/transaction/updateProcedureThreshold.ts +13 -29
- package/src/transaction/updateSigners.ts +38 -30
- package/src/transaction.ts +8 -1
- package/src/types/proposal.ts +43 -4
- package/src/types.ts +0 -1
- package/src/utils/signature.ts +32 -65
- package/src/utils/word.ts +17 -0
- package/dist/account/masm/auth.d.ts +0 -5
- package/dist/account/masm/auth.d.ts.map +0 -1
- package/dist/account/masm/auth.js +0 -1509
- package/dist/account/masm/auth.js.map +0 -1
- package/masm/account_components/auth/multisig.masm +0 -12
- package/masm/account_components/auth/multisig_ecdsa.masm +0 -12
- package/masm/account_components/auth/multisig_guardian.masm +0 -16
- package/masm/account_components/auth/multisig_guardian_ecdsa.masm +0 -16
- package/masm/auth/guardian.masm +0 -199
- package/masm/auth/guardian_ecdsa.masm +0 -195
- package/masm/auth/multisig.masm +0 -554
- package/masm/auth/multisig_ecdsa.masm +0 -554
- package/src/account/masm/auth.ts +0 -1512
|
@@ -2,13 +2,14 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
2
2
|
import type { Word } from '@miden-sdk/miden-sdk';
|
|
3
3
|
|
|
4
4
|
const {
|
|
5
|
-
mockFromVaultKey,
|
|
6
5
|
mockFungibleAssetConstructor,
|
|
7
6
|
mockHashElements,
|
|
8
7
|
mockNormalizeHexWord,
|
|
9
8
|
mockRandomWord,
|
|
10
9
|
mockWordFromHex,
|
|
11
10
|
noteMetadataCalls,
|
|
11
|
+
noteRecipientCalls,
|
|
12
|
+
noteStorageCalls,
|
|
12
13
|
saltFelts,
|
|
13
14
|
} = vi.hoisted(() => {
|
|
14
15
|
const saltFelts = [
|
|
@@ -19,13 +20,10 @@ const {
|
|
|
19
20
|
];
|
|
20
21
|
|
|
21
22
|
return {
|
|
22
|
-
mockFromVaultKey: vi.fn((vaultKey: unknown, amount: bigint) => ({
|
|
23
|
-
kind: 'asset-from-vault-key',
|
|
24
|
-
vaultKey,
|
|
25
|
-
amount,
|
|
26
|
-
})),
|
|
27
23
|
mockFungibleAssetConstructor: vi.fn(),
|
|
28
24
|
noteMetadataCalls: [] as unknown[][],
|
|
25
|
+
noteRecipientCalls: [] as unknown[][],
|
|
26
|
+
noteStorageCalls: [] as unknown[][],
|
|
29
27
|
mockHashElements: vi.fn().mockReturnValue({ toString: () => 'serial' }),
|
|
30
28
|
mockNormalizeHexWord: vi.fn((hex: string) => hex),
|
|
31
29
|
mockRandomWord: vi.fn().mockReturnValue({
|
|
@@ -71,7 +69,9 @@ vi.mock('@miden-sdk/miden-sdk', () => {
|
|
|
71
69
|
}
|
|
72
70
|
|
|
73
71
|
class NoteStorage {
|
|
74
|
-
constructor(
|
|
72
|
+
constructor(inputs: FeltArray) {
|
|
73
|
+
noteStorageCalls.push([inputs]);
|
|
74
|
+
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
class NoteMetadata {
|
|
@@ -86,10 +86,12 @@ vi.mock('@miden-sdk/miden-sdk', () => {
|
|
|
86
86
|
|
|
87
87
|
class NoteRecipient {
|
|
88
88
|
constructor(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
) {
|
|
89
|
+
serialNum: unknown,
|
|
90
|
+
noteScript: unknown,
|
|
91
|
+
noteInputs: unknown,
|
|
92
|
+
) {
|
|
93
|
+
noteRecipientCalls.push([serialNum, noteScript, noteInputs]);
|
|
94
|
+
}
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
class Note {
|
|
@@ -104,8 +106,6 @@ vi.mock('@miden-sdk/miden-sdk', () => {
|
|
|
104
106
|
constructor(faucet: unknown, amount: bigint) {
|
|
105
107
|
mockFungibleAssetConstructor(faucet, amount);
|
|
106
108
|
}
|
|
107
|
-
|
|
108
|
-
static fromVaultKey = mockFromVaultKey;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
class NoteArray {
|
|
@@ -152,6 +152,7 @@ vi.mock('@miden-sdk/miden-sdk', () => {
|
|
|
152
152
|
NoteStorage,
|
|
153
153
|
NoteScript: {
|
|
154
154
|
p2id: vi.fn(() => ({ kind: 'p2id-script' })),
|
|
155
|
+
p2ide: vi.fn(() => ({ kind: 'p2ide-script' })),
|
|
155
156
|
},
|
|
156
157
|
NoteTag: {
|
|
157
158
|
withAccountTarget: vi.fn(() => ({ kind: 'tag' })),
|
|
@@ -182,31 +183,20 @@ vi.mock('../utils/random.js', () => ({
|
|
|
182
183
|
}));
|
|
183
184
|
|
|
184
185
|
import { buildP2idTransactionRequest, parseP2idNoteType, p2idNoteTypeToMetadata } from './p2id.js';
|
|
185
|
-
import
|
|
186
|
+
import { NoteType } from '@miden-sdk/miden-sdk';
|
|
186
187
|
|
|
187
188
|
const FAUCET_ID = '0x7bfb0f38b0fafa103f86a805594171';
|
|
188
189
|
|
|
189
|
-
const mockAccount = {
|
|
190
|
-
vault: () => ({
|
|
191
|
-
fungibleAssets: () => [
|
|
192
|
-
{
|
|
193
|
-
faucetId: () => ({ toString: () => FAUCET_ID }),
|
|
194
|
-
vaultKey: () => ({ kind: 'vault-key' }),
|
|
195
|
-
},
|
|
196
|
-
],
|
|
197
|
-
}),
|
|
198
|
-
} as unknown as Account;
|
|
199
|
-
import { NoteType } from '@miden-sdk/miden-sdk';
|
|
200
|
-
|
|
201
190
|
describe('buildP2idTransactionRequest', () => {
|
|
202
191
|
beforeEach(() => {
|
|
203
|
-
mockFromVaultKey.mockClear();
|
|
204
192
|
mockFungibleAssetConstructor.mockClear();
|
|
205
193
|
mockHashElements.mockClear();
|
|
206
194
|
mockNormalizeHexWord.mockClear();
|
|
207
195
|
mockRandomWord.mockClear();
|
|
208
196
|
mockWordFromHex.mockClear();
|
|
209
197
|
noteMetadataCalls.length = 0;
|
|
198
|
+
noteRecipientCalls.length = 0;
|
|
199
|
+
noteStorageCalls.length = 0;
|
|
210
200
|
});
|
|
211
201
|
|
|
212
202
|
it('derives serial number from salt felts plus four zero felts', () => {
|
|
@@ -217,7 +207,6 @@ describe('buildP2idTransactionRequest', () => {
|
|
|
217
207
|
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
218
208
|
FAUCET_ID,
|
|
219
209
|
10n,
|
|
220
|
-
mockAccount,
|
|
221
210
|
{ salt },
|
|
222
211
|
);
|
|
223
212
|
|
|
@@ -239,28 +228,120 @@ describe('buildP2idTransactionRequest', () => {
|
|
|
239
228
|
buildP2idTransactionRequest(
|
|
240
229
|
'0x7bfb0f38b0fafa103f86a805594170',
|
|
241
230
|
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
242
|
-
|
|
231
|
+
FAUCET_ID,
|
|
243
232
|
10n,
|
|
244
|
-
mockAccount,
|
|
245
233
|
);
|
|
246
234
|
|
|
247
235
|
expect(noteMetadataCalls).toHaveLength(1);
|
|
248
236
|
expect(noteMetadataCalls[0][1]).toBe(NoteType.Public);
|
|
249
237
|
});
|
|
250
238
|
|
|
239
|
+
it('builds the asset from the faucet id, whose callback flag it carries since Miden 0.16', () => {
|
|
240
|
+
buildP2idTransactionRequest(
|
|
241
|
+
'0x7bfb0f38b0fafa103f86a805594170',
|
|
242
|
+
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
243
|
+
FAUCET_ID,
|
|
244
|
+
10n,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
expect(mockFungibleAssetConstructor).toHaveBeenCalledTimes(1);
|
|
248
|
+
const [faucet, amount] = mockFungibleAssetConstructor.mock.calls[0] as [{ hex: string }, bigint];
|
|
249
|
+
expect(faucet.hex).toBe(FAUCET_ID);
|
|
250
|
+
expect(amount).toBe(10n);
|
|
251
|
+
});
|
|
252
|
+
|
|
251
253
|
it('threads the requested noteType into the note metadata (issue #322)', () => {
|
|
252
254
|
buildP2idTransactionRequest(
|
|
253
255
|
'0x7bfb0f38b0fafa103f86a805594170',
|
|
254
256
|
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
255
257
|
'0x7bfb0f38b0fafa103f86a805594171',
|
|
256
258
|
10n,
|
|
257
|
-
mockAccount,
|
|
258
259
|
{ noteType: NoteType.Private },
|
|
259
260
|
);
|
|
260
261
|
|
|
261
262
|
expect(noteMetadataCalls).toHaveLength(1);
|
|
262
263
|
expect(noteMetadataCalls[0][1]).toBe(NoteType.Private);
|
|
263
264
|
});
|
|
265
|
+
|
|
266
|
+
it('builds a plain P2ID note without heights: p2id script, 2 storage items', () => {
|
|
267
|
+
buildP2idTransactionRequest(
|
|
268
|
+
'0x7bfb0f38b0fafa103f86a805594170',
|
|
269
|
+
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
270
|
+
FAUCET_ID,
|
|
271
|
+
10n,
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
expect(noteRecipientCalls).toHaveLength(1);
|
|
275
|
+
expect(noteRecipientCalls[0][1]).toEqual({ kind: 'p2id-script' });
|
|
276
|
+
const [storageInputs] = noteStorageCalls[0] as [{ values: unknown[] }];
|
|
277
|
+
expect(storageInputs.values).toEqual([2, 1]);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('builds a P2IDE note when reclaimHeight is set (issue #366)', () => {
|
|
281
|
+
buildP2idTransactionRequest(
|
|
282
|
+
'0x7bfb0f38b0fafa103f86a805594170',
|
|
283
|
+
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
284
|
+
FAUCET_ID,
|
|
285
|
+
10n,
|
|
286
|
+
{ reclaimHeight: 12345 },
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
expect(noteRecipientCalls).toHaveLength(1);
|
|
290
|
+
expect(noteRecipientCalls[0][1]).toEqual({ kind: 'p2ide-script' });
|
|
291
|
+
|
|
292
|
+
// P2IDE storage layout: [suffix, prefix, reclaim, timelock], 0 = unset.
|
|
293
|
+
const [storageInputs] = noteStorageCalls[0] as [{ values: unknown[] }];
|
|
294
|
+
expect(storageInputs.values).toHaveLength(4);
|
|
295
|
+
expect(storageInputs.values.slice(0, 2)).toEqual([2, 1]);
|
|
296
|
+
expect((storageInputs.values[2] as { value: bigint }).value).toBe(12345n);
|
|
297
|
+
expect((storageInputs.values[3] as { value: bigint }).value).toBe(0n);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('builds a P2IDE note when only timelockHeight is set (issue #366)', () => {
|
|
301
|
+
buildP2idTransactionRequest(
|
|
302
|
+
'0x7bfb0f38b0fafa103f86a805594170',
|
|
303
|
+
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
304
|
+
FAUCET_ID,
|
|
305
|
+
10n,
|
|
306
|
+
{ timelockHeight: 777 },
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
expect(noteRecipientCalls[0][1]).toEqual({ kind: 'p2ide-script' });
|
|
310
|
+
const [storageInputs] = noteStorageCalls[0] as [{ values: unknown[] }];
|
|
311
|
+
expect((storageInputs.values[2] as { value: bigint }).value).toBe(0n);
|
|
312
|
+
expect((storageInputs.values[3] as { value: bigint }).value).toBe(777n);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('carries both heights into the P2IDE storage (issue #366)', () => {
|
|
316
|
+
buildP2idTransactionRequest(
|
|
317
|
+
'0x7bfb0f38b0fafa103f86a805594170',
|
|
318
|
+
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
319
|
+
FAUCET_ID,
|
|
320
|
+
10n,
|
|
321
|
+
{ reclaimHeight: 500, timelockHeight: 400 },
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
const [storageInputs] = noteStorageCalls[0] as [{ values: unknown[] }];
|
|
325
|
+
expect((storageInputs.values[2] as { value: bigint }).value).toBe(500n);
|
|
326
|
+
expect((storageInputs.values[3] as { value: bigint }).value).toBe(400n);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it.each([
|
|
330
|
+
['zero', 0],
|
|
331
|
+
['negative', -5],
|
|
332
|
+
['fractional', 1.5],
|
|
333
|
+
['above u32::MAX', 0x1_0000_0000],
|
|
334
|
+
])('rejects a %s height instead of building a divergent note (issue #366)', (_label, height) => {
|
|
335
|
+
expect(() =>
|
|
336
|
+
buildP2idTransactionRequest(
|
|
337
|
+
'0x7bfb0f38b0fafa103f86a805594170',
|
|
338
|
+
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
339
|
+
FAUCET_ID,
|
|
340
|
+
10n,
|
|
341
|
+
{ reclaimHeight: height },
|
|
342
|
+
),
|
|
343
|
+
).toThrow(/unsupported reclaimHeight/);
|
|
344
|
+
});
|
|
264
345
|
});
|
|
265
346
|
|
|
266
347
|
describe('parseP2idNoteType', () => {
|
package/src/transaction/p2id.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TransactionRequest, Word } from '@miden-sdk/miden-sdk';
|
|
2
2
|
import {
|
|
3
3
|
AccountId,
|
|
4
|
+
Felt,
|
|
4
5
|
FeltArray,
|
|
5
6
|
FungibleAsset,
|
|
6
7
|
MidenArrays,
|
|
@@ -20,8 +21,21 @@ import { randomWord } from '../utils/random.js';
|
|
|
20
21
|
import { normalizeHexWord } from '../utils/encoding.js';
|
|
21
22
|
import type { SignatureOptions } from './options.js';
|
|
22
23
|
import type { P2idNoteVisibility } from '../types/proposal.js';
|
|
24
|
+
import { parseP2ideHeight } from '../types/proposal.js';
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
/**
|
|
27
|
+
* P2IDE execution constraints (issue #366). Presence of either height builds
|
|
28
|
+
* a P2IDE note instead of a plain P2ID note, mirroring the Miden SDK's
|
|
29
|
+
* `reclaimAfter`/`timelockUntil` semantics on `SendOptions`.
|
|
30
|
+
*/
|
|
31
|
+
export interface P2ideHeightOptions {
|
|
32
|
+
/** Absolute block height at which the sender may reclaim the note. */
|
|
33
|
+
reclaimHeight?: number;
|
|
34
|
+
/** Absolute block height before which the note cannot be consumed. */
|
|
35
|
+
timelockHeight?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface P2idTransactionOptions extends SignatureOptions, P2ideHeightOptions {
|
|
25
39
|
/** Visibility of the created note. Defaults to `NoteType.Public`. */
|
|
26
40
|
noteType?: NoteType;
|
|
27
41
|
}
|
|
@@ -60,47 +74,35 @@ export function deriveP2idSerialNumber(salt: Word): Word {
|
|
|
60
74
|
]));
|
|
61
75
|
}
|
|
62
76
|
|
|
63
|
-
/**
|
|
64
|
-
* Builds the fungible asset to transfer, sourcing the callback flag from the held asset.
|
|
65
|
-
*
|
|
66
|
-
* In Miden 0.15 the callback flag is part of the vault key, so rebuilding the asset from
|
|
67
|
-
* `faucet`/`amount` with the default flag would not match the held asset and the transfer
|
|
68
|
-
* would abort. When the faucet is absent the default flag is used, surfacing the
|
|
69
|
-
* missing-asset error during execution.
|
|
70
|
-
*/
|
|
71
|
-
function resolveFungibleAssetFromVault(
|
|
72
|
-
account: Account,
|
|
73
|
-
faucet: AccountId,
|
|
74
|
-
amount: bigint,
|
|
75
|
-
): FungibleAsset {
|
|
76
|
-
const faucetHex = faucet.toString();
|
|
77
|
-
const assetFromVault = account
|
|
78
|
-
.vault()
|
|
79
|
-
.fungibleAssets()
|
|
80
|
-
.find(asset => asset.faucetId().toString() === faucetHex);
|
|
81
|
-
|
|
82
|
-
if (!assetFromVault) {
|
|
83
|
-
return new FungibleAsset(faucet, amount);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return FungibleAsset.fromVaultKey(assetFromVault.vaultKey(), amount);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
77
|
function buildP2idNote(
|
|
90
78
|
sender: AccountId,
|
|
91
79
|
recipient: AccountId,
|
|
92
80
|
noteAssets: NoteAssets,
|
|
93
81
|
noteType: NoteType,
|
|
94
82
|
saltHex: string,
|
|
83
|
+
heights: P2ideHeightOptions = {},
|
|
95
84
|
): Note {
|
|
96
85
|
const salt = WordType.fromHex(normalizeHexWord(saltHex));
|
|
97
86
|
const serialNum = deriveP2idSerialNumber(salt);
|
|
98
87
|
|
|
99
|
-
const
|
|
100
|
-
const
|
|
88
|
+
const reclaimHeight = parseP2ideHeight('reclaimHeight', heights.reclaimHeight);
|
|
89
|
+
const timelockHeight = parseP2ideHeight('timelockHeight', heights.timelockHeight);
|
|
90
|
+
const isP2ide = reclaimHeight !== undefined || timelockHeight !== undefined;
|
|
91
|
+
|
|
92
|
+
// P2IDE storage layout (miden-standards `P2ideNoteStorage`): the P2ID
|
|
93
|
+
// storage plus reclaim/timelock heights as felts, 0 encoding "unset".
|
|
94
|
+
const noteScript = isP2ide ? NoteScript.p2ide() : NoteScript.p2id();
|
|
95
|
+
const storageFelts = [
|
|
101
96
|
recipient.suffix(),
|
|
102
97
|
recipient.prefix(),
|
|
103
|
-
]
|
|
98
|
+
];
|
|
99
|
+
if (isP2ide) {
|
|
100
|
+
storageFelts.push(
|
|
101
|
+
new Felt(BigInt(reclaimHeight ?? 0)),
|
|
102
|
+
new Felt(BigInt(timelockHeight ?? 0)),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const noteStorage = new NoteStorage(new FeltArray(storageFelts));
|
|
104
106
|
|
|
105
107
|
const noteRecipient = new NoteRecipient(serialNum, noteScript, noteStorage);
|
|
106
108
|
const noteTag = NoteTag.withAccountTarget(recipient);
|
|
@@ -117,26 +119,26 @@ function buildP2idNote(
|
|
|
117
119
|
/**
|
|
118
120
|
* Rebuilds the P2ID note a proposal creates, from its metadata fields. The
|
|
119
121
|
* note is deterministic in the salt, so the resulting ID matches the note the
|
|
120
|
-
* proposal produces on execution
|
|
121
|
-
*
|
|
122
|
+
* proposal produces on execution. Since Miden 0.16, the asset
|
|
123
|
+
* callback flag is encoded in the faucet account ID.
|
|
122
124
|
*/
|
|
123
125
|
export function buildP2idNoteFromMetadata(
|
|
124
126
|
senderId: string,
|
|
125
127
|
recipientId: string,
|
|
126
128
|
faucetId: string,
|
|
127
129
|
amount: bigint,
|
|
128
|
-
account: Account,
|
|
129
130
|
noteType: NoteType,
|
|
130
131
|
saltHex: string,
|
|
132
|
+
heights: P2ideHeightOptions = {},
|
|
131
133
|
): Note {
|
|
132
134
|
const sender = AccountId.fromHex(senderId);
|
|
133
135
|
const recipient = AccountId.fromHex(recipientId);
|
|
134
136
|
const faucet = AccountId.fromHex(faucetId);
|
|
135
137
|
|
|
136
|
-
const asset =
|
|
138
|
+
const asset = new FungibleAsset(faucet, amount);
|
|
137
139
|
const noteAssets = new NoteAssets([asset]);
|
|
138
140
|
|
|
139
|
-
return buildP2idNote(sender, recipient, noteAssets, noteType, saltHex);
|
|
141
|
+
return buildP2idNote(sender, recipient, noteAssets, noteType, saltHex, heights);
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
export function buildP2idTransactionRequest(
|
|
@@ -144,7 +146,6 @@ export function buildP2idTransactionRequest(
|
|
|
144
146
|
recipientId: string,
|
|
145
147
|
faucetId: string,
|
|
146
148
|
amount: bigint,
|
|
147
|
-
account: Account,
|
|
148
149
|
options: P2idTransactionOptions = {},
|
|
149
150
|
): { request: TransactionRequest; salt: Word } {
|
|
150
151
|
const authSaltHex = options.salt ? options.salt.toHex() : randomWord().toHex();
|
|
@@ -154,9 +155,9 @@ export function buildP2idTransactionRequest(
|
|
|
154
155
|
recipientId,
|
|
155
156
|
faucetId,
|
|
156
157
|
amount,
|
|
157
|
-
account,
|
|
158
158
|
options.noteType ?? NoteType.Public,
|
|
159
159
|
authSaltHex,
|
|
160
|
+
{ reclaimHeight: options.reclaimHeight, timelockHeight: options.timelockHeight },
|
|
160
161
|
);
|
|
161
162
|
|
|
162
163
|
const outputNotes = new MidenArrays.NoteArray([note]);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
vi.mock('@miden-sdk/miden-sdk', () => ({
|
|
4
|
+
AccountId: { fromHex: vi.fn() },
|
|
5
|
+
Word: {
|
|
6
|
+
newFromFelts: vi.fn((felts: unknown[]) => ({ felts })),
|
|
7
|
+
},
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock('../raw-client.js', () => ({
|
|
11
|
+
getRawMidenClient: vi.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const { summarySalt } = await import('./summary.js');
|
|
15
|
+
|
|
16
|
+
describe('summarySalt', () => {
|
|
17
|
+
it('reads the salt from the trailing four user params', () => {
|
|
18
|
+
const summary = {
|
|
19
|
+
userParams: () => [0, 0, 0, 11, 22, 33, 44],
|
|
20
|
+
} as never;
|
|
21
|
+
|
|
22
|
+
expect(summarySalt(summary)).toEqual({ felts: [11, 22, 33, 44] });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('ignores the leading three user params the auth component zeroes', () => {
|
|
26
|
+
const summary = {
|
|
27
|
+
userParams: () => [7, 8, 9, 11, 22, 33, 44],
|
|
28
|
+
} as never;
|
|
29
|
+
|
|
30
|
+
expect(summarySalt(summary)).toEqual({ felts: [11, 22, 33, 44] });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -4,28 +4,107 @@ import type {
|
|
|
4
4
|
TransactionSummary,
|
|
5
5
|
WasmWebClient,
|
|
6
6
|
} from '@miden-sdk/miden-sdk';
|
|
7
|
-
import { AccountId } from '@miden-sdk/miden-sdk';
|
|
7
|
+
import { AccountId, ChainAnchor, Word } from '@miden-sdk/miden-sdk';
|
|
8
8
|
import { getRawMidenClient } from '../raw-client.js';
|
|
9
|
+
import { base64ToUint8Array, uint8ArrayToBase64 } from '../utils/encoding.js';
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Index of the first user param carrying the auth-arg salt. The guarded-multisig
|
|
13
|
+
* auth component zeroes user params 0-2 and fills 3-6 with the auth args, matching
|
|
14
|
+
* `push.0.0.0` ahead of `multisig::auth_tx` in `guarded_multisig.masm`.
|
|
15
|
+
*/
|
|
16
|
+
const SALT_USER_PARAM_OFFSET = 3;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Captures a `ChainAnchor` for the request at the current sync height and
|
|
20
|
+
* executes the transaction against it to obtain the summary awaiting
|
|
21
|
+
* authorization. The anchor is returned alongside the summary so the proposer
|
|
22
|
+
* can ship it with the signed data; cosigners and the executor then reproduce
|
|
23
|
+
* the summary — which binds the reference block commitment since protocol
|
|
24
|
+
* 0.16 — with {@link executeForSummaryAt} regardless of their own sync height.
|
|
25
|
+
*/
|
|
10
26
|
export function executeForSummary(
|
|
11
27
|
client: MidenClient,
|
|
12
28
|
accountId: string,
|
|
13
29
|
txRequest: TransactionRequest,
|
|
14
30
|
midenRpcEndpoint: string,
|
|
15
|
-
): Promise<TransactionSummary>;
|
|
31
|
+
): Promise<{ summary: TransactionSummary; anchor: ChainAnchor }>;
|
|
16
32
|
export function executeForSummary(
|
|
17
33
|
client: WasmWebClient,
|
|
18
34
|
accountId: string,
|
|
19
35
|
txRequest: TransactionRequest,
|
|
20
36
|
midenRpcEndpoint?: string,
|
|
21
|
-
): Promise<TransactionSummary>;
|
|
37
|
+
): Promise<{ summary: TransactionSummary; anchor: ChainAnchor }>;
|
|
22
38
|
export async function executeForSummary(
|
|
23
39
|
client: MidenClient | WasmWebClient,
|
|
24
40
|
accountId: string,
|
|
25
41
|
txRequest: TransactionRequest,
|
|
26
42
|
midenRpcEndpoint?: string,
|
|
43
|
+
): Promise<{ summary: TransactionSummary; anchor: ChainAnchor }> {
|
|
44
|
+
const acc = AccountId.fromHex(accountId);
|
|
45
|
+
const rawClient = await getRawMidenClient(client, midenRpcEndpoint);
|
|
46
|
+
const anchor = await rawClient.chainAnchorForRequest(txRequest);
|
|
47
|
+
const summary = await rawClient.executeForSummaryAt(acc, txRequest, anchor);
|
|
48
|
+
return { summary, anchor };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Executes a transaction at the given `ChainAnchor`'s reference block to
|
|
53
|
+
* obtain the summary awaiting authorization — the anchored counterpart of
|
|
54
|
+
* {@link executeForSummary} for cosigners and executors holding a proposal's
|
|
55
|
+
* anchor.
|
|
56
|
+
*/
|
|
57
|
+
export function executeForSummaryAt(
|
|
58
|
+
client: MidenClient,
|
|
59
|
+
accountId: string,
|
|
60
|
+
txRequest: TransactionRequest,
|
|
61
|
+
anchor: ChainAnchor,
|
|
62
|
+
midenRpcEndpoint: string,
|
|
63
|
+
): Promise<TransactionSummary>;
|
|
64
|
+
export function executeForSummaryAt(
|
|
65
|
+
client: WasmWebClient,
|
|
66
|
+
accountId: string,
|
|
67
|
+
txRequest: TransactionRequest,
|
|
68
|
+
anchor: ChainAnchor,
|
|
69
|
+
midenRpcEndpoint?: string,
|
|
70
|
+
): Promise<TransactionSummary>;
|
|
71
|
+
export async function executeForSummaryAt(
|
|
72
|
+
client: MidenClient | WasmWebClient,
|
|
73
|
+
accountId: string,
|
|
74
|
+
txRequest: TransactionRequest,
|
|
75
|
+
anchor: ChainAnchor,
|
|
76
|
+
midenRpcEndpoint?: string,
|
|
27
77
|
): Promise<TransactionSummary> {
|
|
28
78
|
const acc = AccountId.fromHex(accountId);
|
|
29
79
|
const rawClient = await getRawMidenClient(client, midenRpcEndpoint);
|
|
30
|
-
return rawClient.
|
|
80
|
+
return rawClient.executeForSummaryAt(acc, txRequest, anchor);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Serializes a `ChainAnchor` to base64 for the proposal wire payload.
|
|
85
|
+
*/
|
|
86
|
+
export function chainAnchorToBase64(anchor: ChainAnchor): string {
|
|
87
|
+
return uint8ArrayToBase64(anchor.serialize());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Deserializes a `ChainAnchor` from its base64 wire form. `ChainAnchor`
|
|
92
|
+
* deserialization validates the header/chain consistency internally, so a
|
|
93
|
+
* decoded anchor only needs its block commitment checked against the signed
|
|
94
|
+
* transaction summary before it is safe to execute against.
|
|
95
|
+
*/
|
|
96
|
+
export function chainAnchorFromBase64(anchorBase64: string): ChainAnchor {
|
|
97
|
+
return ChainAnchor.deserialize(base64ToUint8Array(anchorBase64));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Reads the auth-arg salt back out of a transaction summary.
|
|
102
|
+
*
|
|
103
|
+
* Since miden-protocol 0.16-rc the summary binds seven user-defined elements
|
|
104
|
+
* instead of a dedicated salt word. The guarded-multisig auth component zeroes
|
|
105
|
+
* the leading three and passes the auth args as the trailing four, so the salt
|
|
106
|
+
* is the tail of `userParams()`.
|
|
107
|
+
*/
|
|
108
|
+
export function summarySalt(summary: TransactionSummary): Word {
|
|
109
|
+
return Word.newFromFelts(summary.userParams().slice(SALT_USER_PARAM_OFFSET));
|
|
31
110
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AdviceMap,
|
|
3
|
-
FeltArray,
|
|
4
2
|
type MidenClient,
|
|
5
3
|
TransactionRequest,
|
|
6
4
|
TransactionRequestBuilder,
|
|
@@ -9,37 +7,38 @@ import {
|
|
|
9
7
|
Word,
|
|
10
8
|
Word as WordType,
|
|
11
9
|
} from '@miden-sdk/miden-sdk';
|
|
12
|
-
import { GUARDIAN_ECDSA_MASM, GUARDIAN_MASM } from '../account/masm/auth.js';
|
|
13
10
|
import { compileTxScript } from '../raw-client.js';
|
|
14
11
|
import { normalizeHexWord } from '../utils/encoding.js';
|
|
15
12
|
import { randomWord } from '../utils/random.js';
|
|
13
|
+
import { authSchemeId } from '../utils/signature.js';
|
|
16
14
|
import type { MidenClientSignatureOptions, SignatureOptions } from './options.js';
|
|
17
15
|
import type { SignatureScheme } from '../types.js';
|
|
18
16
|
|
|
19
17
|
async function buildUpdateGuardianScript(
|
|
20
18
|
client: MidenClient | WasmWebClient,
|
|
19
|
+
newGuardianPubkey: string,
|
|
21
20
|
signatureScheme: SignatureScheme,
|
|
22
21
|
midenRpcEndpoint?: string,
|
|
23
22
|
): Promise<TransactionScript> {
|
|
24
|
-
|
|
25
|
-
const
|
|
23
|
+
// A word literal preserves the key's element order on the operand stack.
|
|
24
|
+
const keyLiteral = normalizeHexWord(newGuardianPubkey);
|
|
25
|
+
const schemeId = authSchemeId(signatureScheme);
|
|
26
26
|
|
|
27
|
+
// Calling the origin procedure yields the same MAST root as its component re-export.
|
|
27
28
|
const scriptSource = `
|
|
28
|
-
use
|
|
29
|
+
use miden::standards::auth::guardian
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
@transaction_script
|
|
32
|
+
pub proc main
|
|
33
|
+
push.${keyLiteral}
|
|
34
|
+
push.${schemeId}
|
|
33
35
|
call.guardian::update_guardian_public_key
|
|
36
|
+
drop
|
|
37
|
+
dropw
|
|
34
38
|
end
|
|
35
39
|
`;
|
|
36
40
|
|
|
37
|
-
return compileTxScript(
|
|
38
|
-
client,
|
|
39
|
-
scriptSource,
|
|
40
|
-
[{ namespace: guardianLibraryPath, code: guardianMasm }],
|
|
41
|
-
midenRpcEndpoint,
|
|
42
|
-
);
|
|
41
|
+
return compileTxScript(client, scriptSource, [], midenRpcEndpoint);
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
export function buildUpdateGuardianTransactionRequest(
|
|
@@ -60,25 +59,16 @@ export async function buildUpdateGuardianTransactionRequest(
|
|
|
60
59
|
const signatureScheme = options.signatureScheme ?? 'falcon';
|
|
61
60
|
const script = await buildUpdateGuardianScript(
|
|
62
61
|
client,
|
|
62
|
+
newGuardianPubkey,
|
|
63
63
|
signatureScheme,
|
|
64
64
|
options.midenRpcEndpoint,
|
|
65
65
|
);
|
|
66
66
|
|
|
67
67
|
const authSaltHex = options.salt ? options.salt.toHex() : randomWord().toHex();
|
|
68
|
-
|
|
69
|
-
const pubkeyWordForAdvice = WordType.fromHex(normalizeHexWord(newGuardianPubkey));
|
|
70
|
-
const pubkeyWordForFelts = WordType.fromHex(normalizeHexWord(newGuardianPubkey));
|
|
71
|
-
const pubkeyWordForScript = WordType.fromHex(normalizeHexWord(newGuardianPubkey));
|
|
72
|
-
|
|
73
|
-
const advice = new AdviceMap();
|
|
74
|
-
advice.insert(pubkeyWordForAdvice, new FeltArray(pubkeyWordForFelts.toFelts()));
|
|
75
|
-
|
|
76
68
|
const authSaltForBuilder = WordType.fromHex(normalizeHexWord(authSaltHex));
|
|
77
69
|
|
|
78
70
|
let txBuilder = new TransactionRequestBuilder();
|
|
79
71
|
txBuilder = txBuilder.withCustomScript(script);
|
|
80
|
-
txBuilder = txBuilder.withScriptArg(pubkeyWordForScript);
|
|
81
|
-
txBuilder = txBuilder.extendAdviceMap(advice);
|
|
82
72
|
txBuilder = txBuilder.withAuthArg(authSaltForBuilder);
|
|
83
73
|
|
|
84
74
|
if (options.signatureAdviceMap) {
|