@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.
Files changed (207) hide show
  1. package/README.md +192 -11
  2. package/dist/account/builder.d.ts.map +1 -1
  3. package/dist/account/builder.js +32 -25
  4. package/dist/account/builder.js.map +1 -1
  5. package/dist/account/builder.test.js +73 -21
  6. package/dist/account/builder.test.js.map +1 -1
  7. package/dist/account/layout.d.ts +31 -0
  8. package/dist/account/layout.d.ts.map +1 -0
  9. package/dist/account/layout.js +31 -0
  10. package/dist/account/layout.js.map +1 -0
  11. package/dist/account/masm/account-components/auth.d.ts +1 -4
  12. package/dist/account/masm/account-components/auth.d.ts.map +1 -1
  13. package/dist/account/masm/account-components/auth.js +36 -53
  14. package/dist/account/masm/account-components/auth.js.map +1 -1
  15. package/dist/account/masm/index.d.ts +0 -1
  16. package/dist/account/masm/index.d.ts.map +1 -1
  17. package/dist/account/masm/index.js +0 -1
  18. package/dist/account/masm/index.js.map +1 -1
  19. package/dist/account/storage.d.ts +4 -0
  20. package/dist/account/storage.d.ts.map +1 -1
  21. package/dist/account/storage.js +9 -20
  22. package/dist/account/storage.js.map +1 -1
  23. package/dist/client.d.ts +4 -0
  24. package/dist/client.d.ts.map +1 -1
  25. package/dist/client.js +10 -5
  26. package/dist/client.js.map +1 -1
  27. package/dist/client.test.js +74 -16
  28. package/dist/client.test.js.map +1 -1
  29. package/dist/index.d.ts +5 -3
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +3 -3
  32. package/dist/index.js.map +1 -1
  33. package/dist/inspector.d.ts +59 -1
  34. package/dist/inspector.d.ts.map +1 -1
  35. package/dist/inspector.js +157 -29
  36. package/dist/inspector.js.map +1 -1
  37. package/dist/inspector.test.js +246 -37
  38. package/dist/inspector.test.js.map +1 -1
  39. package/dist/multisig.d.ts +109 -33
  40. package/dist/multisig.d.ts.map +1 -1
  41. package/dist/multisig.js +307 -104
  42. package/dist/multisig.js.map +1 -1
  43. package/dist/multisig.test.js +582 -58
  44. package/dist/multisig.test.js.map +1 -1
  45. package/dist/procedures.d.ts +6 -7
  46. package/dist/procedures.d.ts.map +1 -1
  47. package/dist/procedures.js +6 -7
  48. package/dist/procedures.js.map +1 -1
  49. package/dist/proposal/metadata.d.ts.map +1 -1
  50. package/dist/proposal/metadata.js +13 -2
  51. package/dist/proposal/metadata.js.map +1 -1
  52. package/dist/proposal/metadata.test.js +57 -0
  53. package/dist/proposal/metadata.test.js.map +1 -1
  54. package/dist/prover/errors.d.ts +5 -0
  55. package/dist/prover/errors.d.ts.map +1 -1
  56. package/dist/prover/errors.js +7 -155
  57. package/dist/prover/errors.js.map +1 -1
  58. package/dist/prover/errors.test.js +5 -0
  59. package/dist/prover/errors.test.js.map +1 -1
  60. package/dist/prover/retry.d.ts +1 -6
  61. package/dist/prover/retry.d.ts.map +1 -1
  62. package/dist/prover/retry.js +5 -28
  63. package/dist/prover/retry.js.map +1 -1
  64. package/dist/prover/retry.test.js +1 -1
  65. package/dist/prover/retry.test.js.map +1 -1
  66. package/dist/prover/workflow.d.ts +4 -3
  67. package/dist/prover/workflow.d.ts.map +1 -1
  68. package/dist/prover/workflow.js +5 -2
  69. package/dist/prover/workflow.js.map +1 -1
  70. package/dist/prover/workflow.test.js +28 -3
  71. package/dist/prover/workflow.test.js.map +1 -1
  72. package/dist/retry/classify.d.ts +16 -0
  73. package/dist/retry/classify.d.ts.map +1 -0
  74. package/dist/retry/classify.js +187 -0
  75. package/dist/retry/classify.js.map +1 -0
  76. package/dist/retry/runtime.d.ts +13 -0
  77. package/dist/retry/runtime.d.ts.map +1 -0
  78. package/dist/retry/runtime.js +33 -0
  79. package/dist/retry/runtime.js.map +1 -0
  80. package/dist/rpc/config.d.ts +11 -0
  81. package/dist/rpc/config.d.ts.map +1 -0
  82. package/dist/rpc/config.js +17 -0
  83. package/dist/rpc/config.js.map +1 -0
  84. package/dist/rpc/config.test.d.ts +2 -0
  85. package/dist/rpc/config.test.d.ts.map +1 -0
  86. package/dist/rpc/config.test.js +24 -0
  87. package/dist/rpc/config.test.js.map +1 -0
  88. package/dist/rpc/errors.d.ts +2 -0
  89. package/dist/rpc/errors.d.ts.map +1 -0
  90. package/dist/rpc/errors.js +11 -0
  91. package/dist/rpc/errors.js.map +1 -0
  92. package/dist/rpc/errors.test.d.ts +2 -0
  93. package/dist/rpc/errors.test.d.ts.map +1 -0
  94. package/dist/rpc/errors.test.js +34 -0
  95. package/dist/rpc/errors.test.js.map +1 -0
  96. package/dist/rpc/retry.d.ts +4 -0
  97. package/dist/rpc/retry.d.ts.map +1 -0
  98. package/dist/rpc/retry.js +6 -0
  99. package/dist/rpc/retry.js.map +1 -0
  100. package/dist/rpc/retry.test.d.ts +2 -0
  101. package/dist/rpc/retry.test.d.ts.map +1 -0
  102. package/dist/rpc/retry.test.js +98 -0
  103. package/dist/rpc/retry.test.js.map +1 -0
  104. package/dist/transaction/index.d.ts +1 -1
  105. package/dist/transaction/index.d.ts.map +1 -1
  106. package/dist/transaction/index.js +1 -1
  107. package/dist/transaction/index.js.map +1 -1
  108. package/dist/transaction/p2id.d.ts +17 -6
  109. package/dist/transaction/p2id.d.ts.map +1 -1
  110. package/dist/transaction/p2id.js +22 -31
  111. package/dist/transaction/p2id.js.map +1 -1
  112. package/dist/transaction/p2id.test.js +62 -24
  113. package/dist/transaction/p2id.test.js.map +1 -1
  114. package/dist/transaction/summary.d.ts +45 -2
  115. package/dist/transaction/summary.d.ts.map +1 -1
  116. package/dist/transaction/summary.js +42 -2
  117. package/dist/transaction/summary.js.map +1 -1
  118. package/dist/transaction/summary.test.d.ts +2 -0
  119. package/dist/transaction/summary.test.d.ts.map +1 -0
  120. package/dist/transaction/summary.test.js +26 -0
  121. package/dist/transaction/summary.test.js.map +1 -0
  122. package/dist/transaction/updateGuardian.d.ts.map +1 -1
  123. package/dist/transaction/updateGuardian.js +16 -18
  124. package/dist/transaction/updateGuardian.js.map +1 -1
  125. package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
  126. package/dist/transaction/updateProcedureThreshold.js +15 -17
  127. package/dist/transaction/updateProcedureThreshold.js.map +1 -1
  128. package/dist/transaction/updateSigners.d.ts +6 -1
  129. package/dist/transaction/updateSigners.d.ts.map +1 -1
  130. package/dist/transaction/updateSigners.js +20 -17
  131. package/dist/transaction/updateSigners.js.map +1 -1
  132. package/dist/transaction.d.ts +2 -2
  133. package/dist/transaction.d.ts.map +1 -1
  134. package/dist/transaction.js +1 -1
  135. package/dist/transaction.js.map +1 -1
  136. package/dist/types/proposal.d.ts +28 -4
  137. package/dist/types/proposal.d.ts.map +1 -1
  138. package/dist/types/proposal.js +18 -0
  139. package/dist/types/proposal.js.map +1 -1
  140. package/dist/types.d.ts +0 -1
  141. package/dist/types.d.ts.map +1 -1
  142. package/dist/utils/signature.d.ts +11 -7
  143. package/dist/utils/signature.d.ts.map +1 -1
  144. package/dist/utils/signature.js +24 -58
  145. package/dist/utils/signature.js.map +1 -1
  146. package/dist/utils/word.d.ts +7 -0
  147. package/dist/utils/word.d.ts.map +1 -1
  148. package/dist/utils/word.js +15 -0
  149. package/dist/utils/word.js.map +1 -1
  150. package/masm/account_components/auth/guarded_multisig.masm +42 -0
  151. package/package.json +7 -4
  152. package/src/account/builder.test.ts +111 -45
  153. package/src/account/builder.ts +45 -33
  154. package/src/account/layout.ts +33 -0
  155. package/src/account/masm/account-components/auth.ts +36 -56
  156. package/src/account/masm/index.ts +0 -1
  157. package/src/account/storage.ts +9 -22
  158. package/src/client.test.ts +87 -16
  159. package/src/client.ts +16 -3
  160. package/src/index.ts +27 -1
  161. package/src/inspector.test.ts +330 -38
  162. package/src/inspector.ts +196 -33
  163. package/src/multisig.test.ts +680 -63
  164. package/src/multisig.ts +400 -115
  165. package/src/procedures.ts +6 -7
  166. package/src/proposal/metadata.test.ts +76 -0
  167. package/src/proposal/metadata.ts +13 -2
  168. package/src/prover/errors.test.ts +8 -0
  169. package/src/prover/errors.ts +7 -175
  170. package/src/prover/retry.test.ts +1 -1
  171. package/src/prover/retry.ts +10 -35
  172. package/src/prover/workflow.test.ts +40 -4
  173. package/src/prover/workflow.ts +16 -4
  174. package/src/retry/classify.ts +220 -0
  175. package/src/retry/runtime.ts +45 -0
  176. package/src/rpc/config.test.ts +45 -0
  177. package/src/rpc/config.ts +30 -0
  178. package/src/rpc/errors.test.ts +69 -0
  179. package/src/rpc/errors.ts +12 -0
  180. package/src/rpc/retry.test.ts +144 -0
  181. package/src/rpc/retry.ts +12 -0
  182. package/src/transaction/index.ts +7 -1
  183. package/src/transaction/p2id.test.ts +112 -31
  184. package/src/transaction/p2id.ts +39 -38
  185. package/src/transaction/summary.test.ts +32 -0
  186. package/src/transaction/summary.ts +83 -4
  187. package/src/transaction/updateGuardian.ts +15 -25
  188. package/src/transaction/updateProcedureThreshold.ts +13 -29
  189. package/src/transaction/updateSigners.ts +38 -30
  190. package/src/transaction.ts +8 -1
  191. package/src/types/proposal.ts +43 -4
  192. package/src/types.ts +0 -1
  193. package/src/utils/signature.ts +32 -65
  194. package/src/utils/word.ts +17 -0
  195. package/dist/account/masm/auth.d.ts +0 -5
  196. package/dist/account/masm/auth.d.ts.map +0 -1
  197. package/dist/account/masm/auth.js +0 -1509
  198. package/dist/account/masm/auth.js.map +0 -1
  199. package/masm/account_components/auth/multisig.masm +0 -12
  200. package/masm/account_components/auth/multisig_ecdsa.masm +0 -12
  201. package/masm/account_components/auth/multisig_guardian.masm +0 -16
  202. package/masm/account_components/auth/multisig_guardian_ecdsa.masm +0 -16
  203. package/masm/auth/guardian.masm +0 -199
  204. package/masm/auth/guardian_ecdsa.masm +0 -195
  205. package/masm/auth/multisig.masm +0 -554
  206. package/masm/auth/multisig_ecdsa.masm +0 -554
  207. 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(_inputs: FeltArray) {}
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
- _serialNum: unknown,
90
- _noteScript: unknown,
91
- _noteInputs: unknown,
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 type { Account } from '@miden-sdk/miden-sdk';
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
- '0x7bfb0f38b0fafa103f86a805594171',
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', () => {
@@ -1,6 +1,7 @@
1
- import type { Account, TransactionRequest, Word } from '@miden-sdk/miden-sdk';
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
- export interface P2idTransactionOptions extends SignatureOptions {
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 noteScript = NoteScript.p2id();
100
- const noteStorage = new NoteStorage(new FeltArray([
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 (issue #356). The asset is derived from the
121
- * account's current vault state, so call this before the transfer executes.
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 = resolveFungibleAssetFromVault(account, faucet, amount);
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.executeForSummary(acc, txRequest);
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
- const guardianLibraryPath = 'oz_guardian::guardian';
25
- const guardianMasm = signatureScheme === 'ecdsa' ? GUARDIAN_ECDSA_MASM : GUARDIAN_MASM;
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 oz_guardian::guardian
29
+ use miden::standards::auth::guardian
29
30
 
30
- begin
31
- adv.push_mapval
32
- dropw
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) {