@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
@@ -1,18 +1,6 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { AccountInspector } from './inspector.js';
3
-
4
- // Storage slot names matching the MASM definitions
5
- const MULTISIG_SLOT_NAMES = {
6
- THRESHOLD_CONFIG: 'openzeppelin::multisig::threshold_config',
7
- SIGNER_PUBLIC_KEYS: 'openzeppelin::multisig::signer_public_keys',
8
- EXECUTED_TRANSACTIONS: 'openzeppelin::multisig::executed_transactions',
9
- PROCEDURE_THRESHOLDS: 'openzeppelin::multisig::procedure_thresholds',
10
- } as const;
11
-
12
- const GUARDIAN_SLOT_NAMES = {
13
- SELECTOR: 'openzeppelin::guardian::selector',
14
- PUBLIC_KEY: 'openzeppelin::guardian::public_key',
15
- } as const;
2
+ import { AccountInspector, assertCompleteDetectedConfig } from './inspector.js';
3
+ import type { DetectedMultisigConfig } from './inspector.js';
16
4
 
17
5
  // Mock the Miden SDK
18
6
  vi.mock('@miden-sdk/miden-sdk', () => {
@@ -21,15 +9,18 @@ vi.mock('@miden-sdk/miden-sdk', () => {
21
9
  toHex: () => '0x' + values.map(v => v.toString(16).padStart(16, '0')).join(''),
22
10
  });
23
11
 
12
+ // Faithful to the real SDK's read semantics (verified against the wasm
13
+ // glue and miden-protocol): getItem returns undefined for an absent slot;
14
+ // getMapItem returns undefined only when the slot is absent or not a map,
15
+ // and the EMPTY word for a missing key in an existing map
16
+ // (StorageMap::get is unwrap_or_default). Neither throws for "not found".
24
17
  const createMockStorage = (slots: Map<string, any>, maps: Map<string, Map<string, any>>) => ({
25
- getItem: (slotName: string) => slots.get(slotName) ?? createMockWord([0n, 0n, 0n, 0n]),
18
+ getItem: (slotName: string) => slots.get(slotName),
26
19
  getMapItem: (slotName: string, key: any) => {
27
20
  const map = maps.get(slotName);
28
- if (!map) throw new Error('Map not found');
21
+ if (!map) return undefined;
29
22
  const keyStr = key.toU64s?.()[0]?.toString() ?? '0';
30
- const value = map.get(keyStr);
31
- if (!value) throw new Error('Key not found');
32
- return value;
23
+ return map.get(keyStr) ?? createMockWord([0n, 0n, 0n, 0n]);
33
24
  },
34
25
  });
35
26
 
@@ -46,7 +37,6 @@ vi.mock('@miden-sdk/miden-sdk', () => {
46
37
  // Return different mocked accounts based on test scenario
47
38
  // Default: 2-of-3 multisig with GUARDIAN enabled
48
39
  const slot0 = createMockWord([2n, 3n, 0n, 0n]); // threshold=2, numSigners=3
49
- const slot4 = createMockWord([1n, 0n, 0n, 0n]); // GUARDIAN enabled
50
40
 
51
41
  const signerMap = new Map<string, any>();
52
42
  signerMap.set('0', createMockWord([BigInt('0x1111111111111111'), BigInt('0x2222222222222222'), BigInt('0x3333333333333333'), BigInt('0x4444444444444444')]));
@@ -57,15 +47,16 @@ vi.mock('@miden-sdk/miden-sdk', () => {
57
47
  guardianMap.set('0', createMockWord([BigInt('0xeeeeeeeeeeeeeeee'), BigInt('0xffffffffffffffff'), BigInt('0x0000000000000001'), BigInt('0x0000000000000002')]));
58
48
 
59
49
  const slots = new Map<string, any>();
60
- slots.set('openzeppelin::multisig::threshold_config', slot0);
61
- slots.set('openzeppelin::guardian::selector', slot4);
50
+ slots.set('miden::standards::auth::multisig::threshold_config', slot0);
62
51
 
63
52
  const maps = new Map<string, Map<string, any>>();
64
- maps.set('openzeppelin::multisig::signer_public_keys', signerMap);
65
- maps.set('openzeppelin::guardian::public_key', guardianMap);
53
+ maps.set('miden::standards::auth::multisig::approver_public_keys', signerMap);
54
+ maps.set('miden::standards::auth::guardian::pub_key', guardianMap);
66
55
 
67
56
  return {
68
57
  storage: () => createMockStorage(slots, maps),
58
+ // The contract-version guard checks for the pinned auth procedure.
59
+ code: () => ({ hasProcedure: () => true }),
69
60
  vault: () => createMockVault([
70
61
  { faucetId: '0xfaucet1', amount: 1000n },
71
62
  { faucetId: '0xfaucet2', amount: 500n },
@@ -73,10 +64,18 @@ vi.mock('@miden-sdk/miden-sdk', () => {
73
64
  };
74
65
  }),
75
66
  },
76
- Word: vi.fn().mockImplementation((arr: BigUint64Array) => ({
77
- toU64s: () => Array.from(arr),
78
- toHex: () => '0x' + Array.from(arr).map(v => v.toString(16).padStart(16, '0')).join(''),
79
- })),
67
+ Word: Object.assign(
68
+ vi.fn().mockImplementation((arr: BigUint64Array) => ({
69
+ toU64s: () => Array.from(arr),
70
+ toHex: () => '0x' + Array.from(arr).map(v => v.toString(16).padStart(16, '0')).join(''),
71
+ })),
72
+ {
73
+ fromHex: vi.fn((hex: string) => ({
74
+ toU64s: () => [0n, 0n, 0n, 0n],
75
+ toHex: () => hex,
76
+ })),
77
+ },
78
+ ),
80
79
  };
81
80
  });
82
81
 
@@ -94,11 +93,10 @@ describe('AccountInspector', () => {
94
93
  expect(config.numSigners).toBe(3);
95
94
  });
96
95
 
97
- it('extracts GUARDIAN status', () => {
96
+ it('extracts the GUARDIAN commitment', () => {
98
97
  const base64 = btoa(String.fromCharCode(...new Uint8Array([1, 2, 3])));
99
98
  const config = AccountInspector.fromBase64(base64);
100
99
 
101
- expect(config.guardianEnabled).toBe(true);
102
100
  expect(config.guardianCommitment).toMatch(/^0x[a-f0-9]+$/);
103
101
  });
104
102
 
@@ -138,6 +136,21 @@ describe('AccountInspector', () => {
138
136
 
139
137
  expect(config.numSigners).toBe(3);
140
138
  });
139
+
140
+ it('rejects accounts built from a different contract version', async () => {
141
+ const { Account } = await import('@miden-sdk/miden-sdk');
142
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
143
+ // Same account shape, but its code lacks the pinned auth procedure —
144
+ // root-keyed reads against it would silently miss its stored overrides.
145
+ const foreign = {
146
+ ...account,
147
+ code: () => ({ hasProcedure: () => false }),
148
+ };
149
+
150
+ expect(() => AccountInspector.fromAccount(foreign as never)).toThrow(
151
+ /unsupported contract version/,
152
+ );
153
+ });
141
154
  });
142
155
  });
143
156
 
@@ -146,19 +159,19 @@ describe('AccountInspector edge cases', () => {
146
159
  vi.clearAllMocks();
147
160
  });
148
161
 
149
- it('handles account with GUARDIAN disabled', async () => {
162
+ it('returns a null GUARDIAN commitment when the guardian pub_key slot is absent', async () => {
150
163
  const { Account } = await import('@miden-sdk/miden-sdk');
151
164
 
152
- // Override mock for this test
165
+ // Override mock for this test: no guardian pub_key entry present.
153
166
  vi.mocked(Account.deserialize).mockReturnValueOnce({
167
+ code: () => ({ hasProcedure: () => true }),
154
168
  storage: () => ({
155
169
  getItem: (slotName: string) => {
156
- if (slotName === 'openzeppelin::multisig::threshold_config') return { toU64s: () => [1n, 1n, 0n, 0n] };
157
- if (slotName === 'openzeppelin::guardian::selector') return { toU64s: () => [0n, 0n, 0n, 0n] }; // GUARDIAN disabled
170
+ if (slotName === 'miden::standards::auth::multisig::threshold_config') return { toU64s: () => [1n, 1n, 0n, 0n] };
158
171
  return { toU64s: () => [0n, 0n, 0n, 0n] };
159
172
  },
160
173
  getMapItem: (slotName: string, key: any) => {
161
- if (slotName === 'openzeppelin::multisig::signer_public_keys' && key.toU64s?.()[0] === 0n) {
174
+ if (slotName === 'miden::standards::auth::multisig::approver_public_keys' && key.toU64s?.()[0] === 0n) {
162
175
  return {
163
176
  toHex: () => '0x' + 'a'.repeat(64),
164
177
  toU64s: () => [1n, 2n, 3n, 4n],
@@ -175,7 +188,6 @@ describe('AccountInspector edge cases', () => {
175
188
  const account = Account.deserialize(new Uint8Array([1, 2, 3]));
176
189
  const config = AccountInspector.fromAccount(account);
177
190
 
178
- expect(config.guardianEnabled).toBe(false);
179
191
  expect(config.guardianCommitment).toBeNull();
180
192
  });
181
193
 
@@ -183,6 +195,7 @@ describe('AccountInspector edge cases', () => {
183
195
  const { Account } = await import('@miden-sdk/miden-sdk');
184
196
 
185
197
  vi.mocked(Account.deserialize).mockReturnValueOnce({
198
+ code: () => ({ hasProcedure: () => true }),
186
199
  storage: () => ({
187
200
  getItem: () => ({ toU64s: () => [1n, 1n, 0n, 0n] }),
188
201
  getMapItem: () => {
@@ -204,10 +217,10 @@ describe('AccountInspector edge cases', () => {
204
217
  const { Account } = await import('@miden-sdk/miden-sdk');
205
218
 
206
219
  vi.mocked(Account.deserialize).mockReturnValueOnce({
220
+ code: () => ({ hasProcedure: () => true }),
207
221
  storage: () => ({
208
222
  getItem: (slotName: string) => {
209
- if (slotName === 'openzeppelin::multisig::threshold_config') return { toU64s: () => [2n, 5n, 0n, 0n] }; // threshold=2, numSigners=5
210
- if (slotName === 'openzeppelin::guardian::selector') return { toU64s: () => [0n, 0n, 0n, 0n] };
223
+ if (slotName === 'miden::standards::auth::multisig::threshold_config') return { toU64s: () => [2n, 5n, 0n, 0n] }; // threshold=2, numSigners=5
211
224
  return { toU64s: () => [0n, 0n, 0n, 0n] };
212
225
  },
213
226
  getMapItem: () => {
@@ -231,6 +244,7 @@ describe('AccountInspector edge cases', () => {
231
244
  const { Account } = await import('@miden-sdk/miden-sdk');
232
245
 
233
246
  vi.mocked(Account.deserialize).mockReturnValueOnce({
247
+ code: () => ({ hasProcedure: () => true }),
234
248
  storage: () => ({
235
249
  getItem: () => ({ toU64s: () => [1n, 1n, 0n, 0n] }),
236
250
  getMapItem: () => {
@@ -248,3 +262,281 @@ describe('AccountInspector edge cases', () => {
248
262
  expect(config.vaultBalances).toEqual([]);
249
263
  });
250
264
  });
265
+
266
+ describe('AccountInspector.getSignerPublicKeyCommitments', () => {
267
+ beforeEach(() => {
268
+ vi.clearAllMocks();
269
+ });
270
+
271
+ it('returns all signer commitments ordered by signer index', async () => {
272
+ const { Account } = await import('@miden-sdk/miden-sdk');
273
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
274
+
275
+ const commitments = AccountInspector.getSignerPublicKeyCommitments(account);
276
+
277
+ expect(commitments).toEqual([
278
+ '0x1111111111111111222222222222222233333333333333334444444444444444',
279
+ '0x5555555555555555666666666666666677777777777777778888888888888888',
280
+ '0xaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbccccccccccccccccdddddddddddddddd',
281
+ ]);
282
+ });
283
+
284
+ it('rejects accounts built from a different contract version', async () => {
285
+ const { Account } = await import('@miden-sdk/miden-sdk');
286
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
287
+ const foreign = {
288
+ ...account,
289
+ code: () => ({ hasProcedure: () => false }),
290
+ };
291
+
292
+ expect(() => AccountInspector.getSignerPublicKeyCommitments(foreign as never)).toThrow(
293
+ /unsupported contract version/,
294
+ );
295
+ });
296
+
297
+ it('throws when a signer map entry is the empty word instead of truncating', async () => {
298
+ const { Account } = await import('@miden-sdk/miden-sdk');
299
+
300
+ // Real SDK semantics: entries 0 and 1 exist, entries 2..4 come back as
301
+ // the empty word (StorageMap::get is unwrap_or_default) — NOT undefined
302
+ // and NOT a throw.
303
+ const entries = new Map<string, any>([
304
+ ['0', { toU64s: () => [1n, 2n, 3n, 4n], toHex: () => '0x' + 'a'.repeat(64) }],
305
+ ['1', { toU64s: () => [5n, 6n, 7n, 8n], toHex: () => '0x' + 'b'.repeat(64) }],
306
+ ]);
307
+ vi.mocked(Account.deserialize).mockReturnValueOnce({
308
+ code: () => ({ hasProcedure: () => true }),
309
+ storage: () => ({
310
+ getItem: (slotName: string) => {
311
+ if (slotName === 'miden::standards::auth::multisig::threshold_config') return { toU64s: () => [2n, 5n, 0n, 0n] };
312
+ return undefined;
313
+ },
314
+ getMapItem: (slotName: string, key: any) => {
315
+ if (slotName !== 'miden::standards::auth::multisig::approver_public_keys') return undefined;
316
+ const keyStr = key.toU64s?.()[0]?.toString() ?? '0';
317
+ return entries.get(keyStr) ?? { toU64s: () => [0n, 0n, 0n, 0n] };
318
+ },
319
+ }),
320
+ } as any);
321
+
322
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
323
+
324
+ expect(() => AccountInspector.getSignerPublicKeyCommitments(account)).toThrow(
325
+ /missing signer public key at index 2/
326
+ );
327
+ });
328
+
329
+ it('throws when the signer map slot itself is absent', async () => {
330
+ const { Account } = await import('@miden-sdk/miden-sdk');
331
+
332
+ vi.mocked(Account.deserialize).mockReturnValueOnce({
333
+ code: () => ({ hasProcedure: () => true }),
334
+ storage: () => ({
335
+ getItem: (slotName: string) => {
336
+ if (slotName === 'miden::standards::auth::multisig::threshold_config') return { toU64s: () => [2n, 3n, 0n, 0n] };
337
+ return undefined;
338
+ },
339
+ getMapItem: () => undefined,
340
+ }),
341
+ } as any);
342
+
343
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
344
+
345
+ expect(() => AccountInspector.getSignerPublicKeyCommitments(account)).toThrow(
346
+ /missing signer public key at index 0/
347
+ );
348
+ });
349
+
350
+ it('throws when the threshold config slot is absent', async () => {
351
+ const { Account } = await import('@miden-sdk/miden-sdk');
352
+
353
+ vi.mocked(Account.deserialize).mockReturnValueOnce({
354
+ code: () => ({ hasProcedure: () => true }),
355
+ storage: () => ({
356
+ getItem: () => undefined,
357
+ getMapItem: () => undefined,
358
+ }),
359
+ } as any);
360
+
361
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
362
+
363
+ expect(() => AccountInspector.getSignerPublicKeyCommitments(account)).toThrow(
364
+ /not a guarded-multisig account/
365
+ );
366
+ });
367
+
368
+ it('throws when the threshold config reports zero signers', async () => {
369
+ const { Account } = await import('@miden-sdk/miden-sdk');
370
+
371
+ vi.mocked(Account.deserialize).mockReturnValueOnce({
372
+ code: () => ({ hasProcedure: () => true }),
373
+ storage: () => ({
374
+ getItem: () => ({ toU64s: () => [0n, 0n, 0n, 0n] }),
375
+ getMapItem: () => undefined,
376
+ }),
377
+ } as any);
378
+
379
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
380
+
381
+ expect(() => AccountInspector.getSignerPublicKeyCommitments(account)).toThrow(
382
+ /zero signers/
383
+ );
384
+ });
385
+
386
+ // 2n ** 53n is the smallest count whose Number() conversion is no longer a
387
+ // safe integer (i++ would stop advancing without the ceiling guard).
388
+ it.each([4294967295n, 2n ** 53n])(
389
+ 'throws on an absurd signer count (%s) instead of looping',
390
+ async (count) => {
391
+ const { Account } = await import('@miden-sdk/miden-sdk');
392
+
393
+ vi.mocked(Account.deserialize).mockReturnValueOnce({
394
+ code: () => ({ hasProcedure: () => true }),
395
+ storage: () => ({
396
+ getItem: () => ({ toU64s: () => [2n, count, 0n, 0n] }),
397
+ getMapItem: () => ({ toU64s: () => [1n, 2n, 3n, 4n], toHex: () => '0x' + 'a'.repeat(64) }),
398
+ }),
399
+ } as any);
400
+
401
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
402
+
403
+ expect(() => AccountInspector.getSignerPublicKeyCommitments(account)).toThrow(
404
+ /sanity limit/
405
+ );
406
+ },
407
+ );
408
+
409
+ it('surfaces a descriptive error for an account from a different SDK copy', async () => {
410
+ const { Account } = await import('@miden-sdk/miden-sdk');
411
+
412
+ // wasm-bindgen's _assertClass rejects Word instances from another
413
+ // bundled SDK copy with this exact message.
414
+ vi.mocked(Account.deserialize).mockReturnValueOnce({
415
+ code: () => ({ hasProcedure: () => true }),
416
+ storage: () => ({
417
+ getItem: (slotName: string) => {
418
+ if (slotName === 'miden::standards::auth::multisig::threshold_config') return { toU64s: () => [1n, 1n, 0n, 0n] };
419
+ return undefined;
420
+ },
421
+ getMapItem: () => {
422
+ throw new Error('expected instance of Word');
423
+ },
424
+ }),
425
+ } as any);
426
+
427
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
428
+
429
+ expect(() => AccountInspector.getSignerPublicKeyCommitments(account)).toThrow(
430
+ /different copy of @miden-sdk\/miden-sdk/
431
+ );
432
+ });
433
+ });
434
+
435
+ describe('AccountInspector.getGuardianPublicKeyCommitment', () => {
436
+ beforeEach(() => {
437
+ vi.clearAllMocks();
438
+ });
439
+
440
+ it('returns the guardian commitment', async () => {
441
+ const { Account } = await import('@miden-sdk/miden-sdk');
442
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
443
+
444
+ const commitment = AccountInspector.getGuardianPublicKeyCommitment(account);
445
+
446
+ expect(commitment).toBe('0xeeeeeeeeeeeeeeeeffffffffffffffff00000000000000010000000000000002');
447
+ });
448
+
449
+ it('rejects accounts built from a different contract version', async () => {
450
+ const { Account } = await import('@miden-sdk/miden-sdk');
451
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
452
+ const foreign = {
453
+ ...account,
454
+ code: () => ({ hasProcedure: () => false }),
455
+ };
456
+
457
+ expect(() => AccountInspector.getGuardianPublicKeyCommitment(foreign as never)).toThrow(
458
+ /unsupported contract version/,
459
+ );
460
+ });
461
+
462
+ it('throws when the guardian key entry is the empty word', async () => {
463
+ const { Account } = await import('@miden-sdk/miden-sdk');
464
+
465
+ vi.mocked(Account.deserialize).mockReturnValueOnce({
466
+ code: () => ({ hasProcedure: () => true }),
467
+ storage: () => ({
468
+ getItem: () => ({ toU64s: () => [1n, 1n, 0n, 0n] }),
469
+ getMapItem: () => ({ toU64s: () => [0n, 0n, 0n, 0n] }),
470
+ }),
471
+ } as any);
472
+
473
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
474
+
475
+ expect(() => AccountInspector.getGuardianPublicKeyCommitment(account)).toThrow(
476
+ /inconsistent account state/
477
+ );
478
+ });
479
+
480
+ it('propagates genuine storage read failures', async () => {
481
+ const { Account } = await import('@miden-sdk/miden-sdk');
482
+
483
+ vi.mocked(Account.deserialize).mockReturnValueOnce({
484
+ code: () => ({ hasProcedure: () => true }),
485
+ storage: () => ({
486
+ getItem: () => ({ toU64s: () => [1n, 1n, 0n, 0n] }),
487
+ getMapItem: () => {
488
+ throw new Error('storage backend exploded');
489
+ },
490
+ }),
491
+ } as any);
492
+
493
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
494
+
495
+ expect(() => AccountInspector.getGuardianPublicKeyCommitment(account)).toThrow(
496
+ 'storage backend exploded'
497
+ );
498
+ });
499
+ });
500
+
501
+ describe('assertCompleteDetectedConfig', () => {
502
+ const complete: DetectedMultisigConfig = {
503
+ threshold: 2,
504
+ numSigners: 2,
505
+ signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
506
+ guardianCommitment: '0x' + 'c'.repeat(64),
507
+ vaultBalances: [],
508
+ procedureThresholds: new Map(),
509
+ };
510
+
511
+ it('accepts a complete config', () => {
512
+ expect(() => assertCompleteDetectedConfig(complete)).not.toThrow();
513
+ });
514
+
515
+ it('rejects a signer set shorter than the reported count', () => {
516
+ expect(() =>
517
+ assertCompleteDetectedConfig({
518
+ ...complete,
519
+ numSigners: 3,
520
+ }),
521
+ ).toThrow(/incomplete signer set: storage reports 3 signers, read 2/);
522
+ });
523
+
524
+ it('rejects a zero signer count', () => {
525
+ expect(() =>
526
+ assertCompleteDetectedConfig({
527
+ ...complete,
528
+ numSigners: 0,
529
+ signerCommitments: [],
530
+ }),
531
+ ).toThrow(/incomplete signer set/);
532
+ });
533
+
534
+ it('rejects a missing guardian commitment', () => {
535
+ expect(() =>
536
+ assertCompleteDetectedConfig({
537
+ ...complete,
538
+ guardianCommitment: null,
539
+ }),
540
+ ).toThrow(/missing guardian commitment/);
541
+ });
542
+ });