@openzeppelin/miden-multisig-client 0.12.8 → 0.13.0

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 (201) hide show
  1. package/README.md +42 -4
  2. package/dist/account/builder.d.ts +1 -1
  3. package/dist/account/builder.d.ts.map +1 -1
  4. package/dist/account/builder.js +7 -5
  5. package/dist/account/builder.js.map +1 -1
  6. package/dist/account/masm.d.ts +3 -3
  7. package/dist/account/masm.d.ts.map +1 -1
  8. package/dist/account/masm.js +101 -98
  9. package/dist/account/masm.js.map +1 -1
  10. package/dist/account/storage.d.ts +1 -1
  11. package/dist/account/storage.d.ts.map +1 -1
  12. package/dist/account/storage.js +17 -7
  13. package/dist/account/storage.js.map +1 -1
  14. package/dist/client.d.ts +1 -1
  15. package/dist/client.d.ts.map +1 -1
  16. package/dist/client.js +2 -2
  17. package/dist/client.js.map +1 -1
  18. package/dist/client.test.js +4 -4
  19. package/dist/client.test.js.map +1 -1
  20. package/dist/index.d.ts +8 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +6 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/inspector.d.ts +1 -1
  25. package/dist/inspector.d.ts.map +1 -1
  26. package/dist/inspector.js +16 -10
  27. package/dist/inspector.js.map +1 -1
  28. package/dist/inspector.test.js +33 -22
  29. package/dist/inspector.test.js.map +1 -1
  30. package/dist/multisig/helpers.d.ts +1 -1
  31. package/dist/multisig/helpers.d.ts.map +1 -1
  32. package/dist/multisig/helpers.js +1 -1
  33. package/dist/multisig/helpers.js.map +1 -1
  34. package/dist/multisig/helpers.test.js +2 -2
  35. package/dist/multisig/helpers.test.js.map +1 -1
  36. package/dist/multisig/proposal/execution.d.ts +18 -0
  37. package/dist/multisig/proposal/execution.d.ts.map +1 -0
  38. package/dist/multisig/proposal/execution.js +147 -0
  39. package/dist/multisig/proposal/execution.js.map +1 -0
  40. package/dist/multisig/proposal/parser.d.ts +17 -0
  41. package/dist/multisig/proposal/parser.d.ts.map +1 -0
  42. package/dist/multisig/proposal/parser.js +147 -0
  43. package/dist/multisig/proposal/parser.js.map +1 -0
  44. package/dist/multisig/signing.d.ts +5 -0
  45. package/dist/multisig/signing.d.ts.map +1 -0
  46. package/dist/multisig/signing.js +14 -0
  47. package/dist/multisig/signing.js.map +1 -0
  48. package/dist/multisig.d.ts +9 -13
  49. package/dist/multisig.d.ts.map +1 -1
  50. package/dist/multisig.js +109 -337
  51. package/dist/multisig.js.map +1 -1
  52. package/dist/multisig.test.js +8 -4
  53. package/dist/multisig.test.js.map +1 -1
  54. package/dist/signer.d.ts +2 -20
  55. package/dist/signer.d.ts.map +1 -1
  56. package/dist/signer.js +2 -82
  57. package/dist/signer.js.map +1 -1
  58. package/dist/signer.test.js +24 -24
  59. package/dist/signer.test.js.map +1 -1
  60. package/dist/signers/ecdsa.d.ts +12 -0
  61. package/dist/signers/ecdsa.d.ts.map +1 -0
  62. package/dist/signers/ecdsa.js +30 -0
  63. package/dist/signers/ecdsa.js.map +1 -0
  64. package/dist/signers/falcon.d.ts +12 -0
  65. package/dist/signers/falcon.d.ts.map +1 -0
  66. package/dist/signers/falcon.js +31 -0
  67. package/dist/signers/falcon.js.map +1 -0
  68. package/dist/signers/falcon.test.d.ts +2 -0
  69. package/dist/signers/falcon.test.d.ts.map +1 -0
  70. package/dist/signers/falcon.test.js +113 -0
  71. package/dist/signers/falcon.test.js.map +1 -0
  72. package/dist/signers/index.d.ts +5 -0
  73. package/dist/signers/index.d.ts.map +1 -0
  74. package/dist/signers/index.js +5 -0
  75. package/dist/signers/index.js.map +1 -0
  76. package/dist/signers/miden-wallet.d.ts +16 -0
  77. package/dist/signers/miden-wallet.d.ts.map +1 -0
  78. package/dist/signers/miden-wallet.js +35 -0
  79. package/dist/signers/miden-wallet.js.map +1 -0
  80. package/dist/signers/miden-wallet.test.d.ts +2 -0
  81. package/dist/signers/miden-wallet.test.d.ts.map +1 -0
  82. package/dist/signers/miden-wallet.test.js +107 -0
  83. package/dist/signers/miden-wallet.test.js.map +1 -0
  84. package/dist/signers/para.d.ts +21 -0
  85. package/dist/signers/para.d.ts.map +1 -0
  86. package/dist/signers/para.js +43 -0
  87. package/dist/signers/para.js.map +1 -0
  88. package/dist/signers/para.test.d.ts +2 -0
  89. package/dist/signers/para.test.d.ts.map +1 -0
  90. package/dist/signers/para.test.js +88 -0
  91. package/dist/signers/para.test.js.map +1 -0
  92. package/dist/transaction/consumeNotes.d.ts +3 -3
  93. package/dist/transaction/consumeNotes.d.ts.map +1 -1
  94. package/dist/transaction/consumeNotes.js +11 -7
  95. package/dist/transaction/consumeNotes.js.map +1 -1
  96. package/dist/transaction/options.d.ts +2 -2
  97. package/dist/transaction/options.d.ts.map +1 -1
  98. package/dist/transaction/p2id.d.ts +1 -1
  99. package/dist/transaction/p2id.d.ts.map +1 -1
  100. package/dist/transaction/p2id.js +5 -5
  101. package/dist/transaction/p2id.js.map +1 -1
  102. package/dist/transaction/summary.d.ts +1 -1
  103. package/dist/transaction/summary.d.ts.map +1 -1
  104. package/dist/transaction/summary.js +1 -1
  105. package/dist/transaction/summary.js.map +1 -1
  106. package/dist/transaction/updatePsm.d.ts +1 -1
  107. package/dist/transaction/updatePsm.d.ts.map +1 -1
  108. package/dist/transaction/updatePsm.js +5 -5
  109. package/dist/transaction/updatePsm.js.map +1 -1
  110. package/dist/transaction/updateSigners.d.ts +1 -1
  111. package/dist/transaction/updateSigners.d.ts.map +1 -1
  112. package/dist/transaction/updateSigners.js +5 -5
  113. package/dist/transaction/updateSigners.js.map +1 -1
  114. package/dist/transaction.test.js +1 -1
  115. package/dist/transaction.test.js.map +1 -1
  116. package/dist/types.d.ts +1 -1
  117. package/dist/types.d.ts.map +1 -1
  118. package/dist/utils/digest.d.ts +6 -0
  119. package/dist/utils/digest.d.ts.map +1 -0
  120. package/dist/utils/digest.js +22 -0
  121. package/dist/utils/digest.js.map +1 -0
  122. package/dist/utils/ecdsa.d.ts +8 -0
  123. package/dist/utils/ecdsa.d.ts.map +1 -0
  124. package/dist/utils/ecdsa.js +60 -0
  125. package/dist/utils/ecdsa.js.map +1 -0
  126. package/dist/utils/ecdsa.test.d.ts +2 -0
  127. package/dist/utils/ecdsa.test.d.ts.map +1 -0
  128. package/dist/utils/ecdsa.test.js +85 -0
  129. package/dist/utils/ecdsa.test.js.map +1 -0
  130. package/dist/utils/index.d.ts +1 -0
  131. package/dist/utils/index.d.ts.map +1 -1
  132. package/dist/utils/index.js +1 -0
  133. package/dist/utils/index.js.map +1 -1
  134. package/dist/utils/key.d.ts +10 -0
  135. package/dist/utils/key.d.ts.map +1 -0
  136. package/dist/utils/key.js +38 -0
  137. package/dist/utils/key.js.map +1 -0
  138. package/dist/utils/key.test.d.ts +2 -0
  139. package/dist/utils/key.test.d.ts.map +1 -0
  140. package/dist/utils/key.test.js +80 -0
  141. package/dist/utils/key.test.js.map +1 -0
  142. package/dist/utils/random.d.ts +1 -1
  143. package/dist/utils/random.d.ts.map +1 -1
  144. package/dist/utils/random.js +1 -1
  145. package/dist/utils/random.js.map +1 -1
  146. package/dist/utils/signature.d.ts +2 -1
  147. package/dist/utils/signature.d.ts.map +1 -1
  148. package/dist/utils/signature.js +6 -3
  149. package/dist/utils/signature.js.map +1 -1
  150. package/dist/utils/signature.test.js +11 -11
  151. package/dist/utils/signature.test.js.map +1 -1
  152. package/dist/utils/word.d.ts +6 -1
  153. package/dist/utils/word.d.ts.map +1 -1
  154. package/dist/utils/word.js +11 -0
  155. package/dist/utils/word.js.map +1 -1
  156. package/dist/utils/word.test.js +54 -3
  157. package/dist/utils/word.test.js.map +1 -1
  158. package/package.json +4 -5
  159. package/src/account/builder.ts +7 -5
  160. package/src/account/masm.ts +103 -99
  161. package/src/account/storage.ts +19 -7
  162. package/src/client.test.ts +4 -4
  163. package/src/client.ts +3 -3
  164. package/src/index.ts +8 -1
  165. package/src/inspector.test.ts +38 -25
  166. package/src/inspector.ts +18 -10
  167. package/src/multisig/helpers.test.ts +2 -2
  168. package/src/multisig/helpers.ts +2 -1
  169. package/src/multisig/proposal/execution.ts +320 -0
  170. package/src/multisig/proposal/parser.ts +191 -0
  171. package/src/multisig/signing.ts +24 -0
  172. package/src/multisig.test.ts +8 -4
  173. package/src/multisig.ts +153 -434
  174. package/src/signer.ts +2 -95
  175. package/src/signers/ecdsa.ts +35 -0
  176. package/src/{signer.test.ts → signers/falcon.test.ts} +29 -33
  177. package/src/signers/falcon.ts +36 -0
  178. package/src/signers/index.ts +4 -0
  179. package/src/signers/miden-wallet.test.ts +119 -0
  180. package/src/signers/miden-wallet.ts +49 -0
  181. package/src/signers/para.test.ts +103 -0
  182. package/src/signers/para.ts +60 -0
  183. package/src/transaction/consumeNotes.ts +14 -10
  184. package/src/transaction/options.ts +2 -2
  185. package/src/transaction/p2id.ts +3 -8
  186. package/src/transaction/summary.ts +2 -2
  187. package/src/transaction/updatePsm.ts +5 -6
  188. package/src/transaction/updateSigners.ts +5 -6
  189. package/src/transaction.test.ts +1 -1
  190. package/src/types.ts +1 -1
  191. package/src/utils/digest.ts +25 -0
  192. package/src/utils/ecdsa.test.ts +104 -0
  193. package/src/utils/ecdsa.ts +77 -0
  194. package/src/utils/index.ts +1 -0
  195. package/src/utils/key.test.ts +89 -0
  196. package/src/utils/key.ts +49 -0
  197. package/src/utils/random.ts +1 -1
  198. package/src/utils/signature.test.ts +11 -11
  199. package/src/utils/signature.ts +10 -3
  200. package/src/utils/word.test.ts +57 -3
  201. package/src/utils/word.ts +13 -1
@@ -1,17 +1,30 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { AccountInspector } from './inspector.js';
3
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 PSM_SLOT_NAMES = {
13
+ SELECTOR: 'openzeppelin::psm::selector',
14
+ PUBLIC_KEY: 'openzeppelin::psm::public_key',
15
+ } as const;
16
+
4
17
  // Mock the Miden SDK
5
- vi.mock('@demox-labs/miden-sdk', () => {
18
+ vi.mock('@miden-sdk/miden-sdk', () => {
6
19
  const createMockWord = (values: bigint[]) => ({
7
20
  toU64s: () => values,
8
21
  toHex: () => '0x' + values.map(v => v.toString(16).padStart(16, '0')).join(''),
9
22
  });
10
23
 
11
- const createMockStorage = (slots: Map<number, any>, maps: Map<number, Map<string, any>>) => ({
12
- getItem: (index: number) => slots.get(index) ?? createMockWord([0n, 0n, 0n, 0n]),
13
- getMapItem: (slotIndex: number, key: any) => {
14
- const map = maps.get(slotIndex);
24
+ const createMockStorage = (slots: Map<string, any>, maps: Map<string, Map<string, any>>) => ({
25
+ getItem: (slotName: string) => slots.get(slotName) ?? createMockWord([0n, 0n, 0n, 0n]),
26
+ getMapItem: (slotName: string, key: any) => {
27
+ const map = maps.get(slotName);
15
28
  if (!map) throw new Error('Map not found');
16
29
  const keyStr = key.toU64s?.()[0]?.toString() ?? '0';
17
30
  const value = map.get(keyStr);
@@ -43,13 +56,13 @@ vi.mock('@demox-labs/miden-sdk', () => {
43
56
  const psmMap = new Map<string, any>();
44
57
  psmMap.set('0', createMockWord([BigInt('0xeeeeeeeeeeeeeeee'), BigInt('0xffffffffffffffff'), BigInt('0x0000000000000001'), BigInt('0x0000000000000002')]));
45
58
 
46
- const slots = new Map<number, any>();
47
- slots.set(0, slot0);
48
- slots.set(4, slot4);
59
+ const slots = new Map<string, any>();
60
+ slots.set('openzeppelin::multisig::threshold_config', slot0);
61
+ slots.set('openzeppelin::psm::selector', slot4);
49
62
 
50
- const maps = new Map<number, Map<string, any>>();
51
- maps.set(1, signerMap);
52
- maps.set(5, psmMap);
63
+ const maps = new Map<string, Map<string, any>>();
64
+ maps.set('openzeppelin::multisig::signer_public_keys', signerMap);
65
+ maps.set('openzeppelin::psm::public_key', psmMap);
53
66
 
54
67
  return {
55
68
  storage: () => createMockStorage(slots, maps),
@@ -111,7 +124,7 @@ describe('AccountInspector', () => {
111
124
 
112
125
  describe('fromAccount', () => {
113
126
  it('extracts threshold from slot 0', async () => {
114
- const { Account } = await import('@demox-labs/miden-sdk');
127
+ const { Account } = await import('@miden-sdk/miden-sdk');
115
128
  const account = Account.deserialize(new Uint8Array([1, 2, 3]));
116
129
  const config = AccountInspector.fromAccount(account);
117
130
 
@@ -119,7 +132,7 @@ describe('AccountInspector', () => {
119
132
  });
120
133
 
121
134
  it('extracts numSigners from slot 0', async () => {
122
- const { Account } = await import('@demox-labs/miden-sdk');
135
+ const { Account } = await import('@miden-sdk/miden-sdk');
123
136
  const account = Account.deserialize(new Uint8Array([1, 2, 3]));
124
137
  const config = AccountInspector.fromAccount(account);
125
138
 
@@ -134,18 +147,18 @@ describe('AccountInspector edge cases', () => {
134
147
  });
135
148
 
136
149
  it('handles account with PSM disabled', async () => {
137
- const { Account } = await import('@demox-labs/miden-sdk');
150
+ const { Account } = await import('@miden-sdk/miden-sdk');
138
151
 
139
152
  // Override mock for this test
140
153
  vi.mocked(Account.deserialize).mockReturnValueOnce({
141
154
  storage: () => ({
142
- getItem: (index: number) => {
143
- if (index === 0) return { toU64s: () => [1n, 1n, 0n, 0n] };
144
- if (index === 4) return { toU64s: () => [0n, 0n, 0n, 0n] }; // PSM disabled
155
+ getItem: (slotName: string) => {
156
+ if (slotName === 'openzeppelin::multisig::threshold_config') return { toU64s: () => [1n, 1n, 0n, 0n] };
157
+ if (slotName === 'openzeppelin::psm::selector') return { toU64s: () => [0n, 0n, 0n, 0n] }; // PSM disabled
145
158
  return { toU64s: () => [0n, 0n, 0n, 0n] };
146
159
  },
147
- getMapItem: (slotIndex: number, key: any) => {
148
- if (slotIndex === 1 && key.toU64s?.()[0] === 0n) {
160
+ getMapItem: (slotName: string, key: any) => {
161
+ if (slotName === 'openzeppelin::multisig::signer_public_keys' && key.toU64s?.()[0] === 0n) {
149
162
  return {
150
163
  toHex: () => '0x' + 'a'.repeat(64),
151
164
  toU64s: () => [1n, 2n, 3n, 4n],
@@ -167,7 +180,7 @@ describe('AccountInspector edge cases', () => {
167
180
  });
168
181
 
169
182
  it('handles account with empty vault', async () => {
170
- const { Account } = await import('@demox-labs/miden-sdk');
183
+ const { Account } = await import('@miden-sdk/miden-sdk');
171
184
 
172
185
  vi.mocked(Account.deserialize).mockReturnValueOnce({
173
186
  storage: () => ({
@@ -188,13 +201,13 @@ describe('AccountInspector edge cases', () => {
188
201
  });
189
202
 
190
203
  it('handles missing signer map entries gracefully', async () => {
191
- const { Account } = await import('@demox-labs/miden-sdk');
204
+ const { Account } = await import('@miden-sdk/miden-sdk');
192
205
 
193
206
  vi.mocked(Account.deserialize).mockReturnValueOnce({
194
207
  storage: () => ({
195
- getItem: (index: number) => {
196
- if (index === 0) return { toU64s: () => [2n, 5n, 0n, 0n] }; // threshold=2, numSigners=5
197
- if (index === 4) return { toU64s: () => [0n, 0n, 0n, 0n] };
208
+ getItem: (slotName: string) => {
209
+ if (slotName === 'openzeppelin::multisig::threshold_config') return { toU64s: () => [2n, 5n, 0n, 0n] }; // threshold=2, numSigners=5
210
+ if (slotName === 'openzeppelin::psm::selector') return { toU64s: () => [0n, 0n, 0n, 0n] };
198
211
  return { toU64s: () => [0n, 0n, 0n, 0n] };
199
212
  },
200
213
  getMapItem: () => {
@@ -215,7 +228,7 @@ describe('AccountInspector edge cases', () => {
215
228
  });
216
229
 
217
230
  it('handles vault access error gracefully', async () => {
218
- const { Account } = await import('@demox-labs/miden-sdk');
231
+ const { Account } = await import('@miden-sdk/miden-sdk');
219
232
 
220
233
  vi.mocked(Account.deserialize).mockReturnValueOnce({
221
234
  storage: () => ({
package/src/inspector.ts CHANGED
@@ -1,9 +1,21 @@
1
- import { Account, Word } from '@demox-labs/miden-sdk';
1
+ import { Account, Word } from '@miden-sdk/miden-sdk';
2
2
  import { base64ToUint8Array } from './utils/encoding.js';
3
3
  import { wordElementToBigInt, wordToHex } from './utils/word.js';
4
4
  import { getProcedureRoot, getProcedureNames, type ProcedureName } from './procedures.js';
5
5
  import type { SignatureScheme } from './types.js';
6
6
 
7
+ const MULTISIG_SLOT_NAMES = {
8
+ THRESHOLD_CONFIG: 'openzeppelin::multisig::threshold_config',
9
+ SIGNER_PUBLIC_KEYS: 'openzeppelin::multisig::signer_public_keys',
10
+ EXECUTED_TRANSACTIONS: 'openzeppelin::multisig::executed_transactions',
11
+ PROCEDURE_THRESHOLDS: 'openzeppelin::multisig::procedure_thresholds',
12
+ } as const;
13
+
14
+ const PSM_SLOT_NAMES = {
15
+ SELECTOR: 'openzeppelin::psm::selector',
16
+ PUBLIC_KEY: 'openzeppelin::psm::public_key',
17
+ } as const;
18
+
7
19
  export interface VaultBalance {
8
20
  faucetId: string;
9
21
  amount: bigint;
@@ -32,7 +44,7 @@ export class AccountInspector {
32
44
  static fromAccount(account: Account, signatureScheme: SignatureScheme = 'falcon'): DetectedMultisigConfig {
33
45
  const storage = account.storage();
34
46
 
35
- const slot0 = storage.getItem(0) as Word;
47
+ const slot0 = storage.getItem(MULTISIG_SLOT_NAMES.THRESHOLD_CONFIG) as Word;
36
48
  const threshold = Number(wordElementToBigInt(slot0, 0));
37
49
  const numSigners = Number(wordElementToBigInt(slot0, 1));
38
50
 
@@ -40,12 +52,11 @@ export class AccountInspector {
40
52
  for (let i = 0; i < numSigners; i++) {
41
53
  try {
42
54
  const key = new Word(new BigUint64Array([BigInt(i), 0n, 0n, 0n]));
43
- const commitment = storage.getMapItem(1, key) as Word;
55
+ const commitment = storage.getMapItem(MULTISIG_SLOT_NAMES.SIGNER_PUBLIC_KEYS, key) as Word;
44
56
  if (commitment) {
45
57
  signerCommitments.push(wordToHex(commitment));
46
58
  }
47
59
  } catch {
48
- // Signer not found in storage map
49
60
  }
50
61
  }
51
62
 
@@ -53,19 +64,18 @@ export class AccountInspector {
53
64
  let psmCommitment: string | null = null;
54
65
 
55
66
  try {
56
- const psmSlot0 = storage.getItem(4) as Word;
67
+ const psmSlot0 = storage.getItem(PSM_SLOT_NAMES.SELECTOR) as Word;
57
68
  const selector = Number(wordElementToBigInt(psmSlot0, 0));
58
69
  psmEnabled = selector === 1;
59
70
 
60
71
  if (psmEnabled) {
61
72
  const zeroKey = new Word(new BigUint64Array([0n, 0n, 0n, 0n]));
62
- const psmKey = storage.getMapItem(5, zeroKey) as Word;
73
+ const psmKey = storage.getMapItem(PSM_SLOT_NAMES.PUBLIC_KEY, zeroKey) as Word;
63
74
  if (psmKey) {
64
75
  psmCommitment = wordToHex(psmKey);
65
76
  }
66
77
  }
67
78
  } catch {
68
- // PSM not configured
69
79
  }
70
80
 
71
81
  const vaultBalances: VaultBalance[] = [];
@@ -79,7 +89,6 @@ export class AccountInspector {
79
89
  });
80
90
  }
81
91
  } catch {
82
- // Vault access failed
83
92
  }
84
93
 
85
94
  const procedureThresholds = new Map<ProcedureName, number>();
@@ -87,7 +96,7 @@ export class AccountInspector {
87
96
  try {
88
97
  const rootHex = getProcedureRoot(procName, signatureScheme);
89
98
  const rootWord = Word.fromHex(rootHex);
90
- const value = storage.getMapItem(3, rootWord) as Word;
99
+ const value = storage.getMapItem(MULTISIG_SLOT_NAMES.PROCEDURE_THRESHOLDS, rootWord) as Word;
91
100
  if (value) {
92
101
  const procThreshold = Number(wordElementToBigInt(value, 0));
93
102
  if (procThreshold > 0) {
@@ -95,7 +104,6 @@ export class AccountInspector {
95
104
  }
96
105
  }
97
106
  } catch {
98
- // Not set
99
107
  }
100
108
  }
101
109
 
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { computeCommitmentFromTxSummary, accountIdToHex } from './helpers.js';
3
3
 
4
- vi.mock('@demox-labs/miden-sdk', () => ({
4
+ vi.mock('@miden-sdk/miden-sdk', () => ({
5
5
  Account: {},
6
6
  TransactionSummary: {
7
7
  deserialize: vi.fn((bytes: Uint8Array) => ({
@@ -27,7 +27,7 @@ describe('multisig helpers', () => {
27
27
  });
28
28
 
29
29
  it('deserializes the tx summary bytes', async () => {
30
- const { TransactionSummary } = await import('@demox-labs/miden-sdk');
30
+ const { TransactionSummary } = await import('@miden-sdk/miden-sdk');
31
31
  const base64 = btoa(String.fromCharCode(...new Uint8Array([4, 5, 6])));
32
32
 
33
33
  computeCommitmentFromTxSummary(base64);
@@ -1,4 +1,4 @@
1
- import { Account, TransactionSummary } from '@demox-labs/miden-sdk';
1
+ import { Account, TransactionSummary, Word } from '@miden-sdk/miden-sdk';
2
2
  import { base64ToUint8Array } from '../utils/encoding.js';
3
3
 
4
4
  export function computeCommitmentFromTxSummary(txSummaryBase64: string): string {
@@ -7,6 +7,7 @@ export function computeCommitmentFromTxSummary(txSummaryBase64: string): string
7
7
  const commitment = summary.toCommitment();
8
8
  return commitment.toHex();
9
9
  }
10
+
10
11
  export function accountIdToHex(account: Account): string {
11
12
  const accountId = account.id();
12
13
  const str = accountId.toString();
@@ -0,0 +1,320 @@
1
+ import { type DeltaObject, type PsmHttpClient } from '@openzeppelin/psm-client';
2
+ import type { WebClient, TransactionRequest } from '@miden-sdk/miden-sdk';
3
+ import {
4
+ AccountId,
5
+ AdviceMap,
6
+ FeltArray,
7
+ Signature,
8
+ TransactionSummary,
9
+ Word,
10
+ } from '@miden-sdk/miden-sdk';
11
+ import type { ProposalType, TransactionProposal } from '../../types.js';
12
+ import {
13
+ buildConsumeNotesTransactionRequest,
14
+ buildP2idTransactionRequest,
15
+ buildUpdatePsmTransactionRequest,
16
+ buildUpdateSignersTransactionRequest,
17
+ } from '../../transaction.js';
18
+ import { base64ToUint8Array, normalizeHexWord } from '../../utils/encoding.js';
19
+ import {
20
+ buildSignatureAdviceEntry,
21
+ signatureHexToBytes,
22
+ tryComputeEcdsaCommitmentHex,
23
+ } from '../../utils/signature.js';
24
+ import { computeCommitmentFromTxSummary } from '../helpers.js';
25
+
26
+ interface ResolveExecutionSourceResult {
27
+ delta?: DeltaObject;
28
+ txSummaryBase64: string;
29
+ }
30
+
31
+ interface PrepareExecutionResult {
32
+ txSummary: TransactionSummary;
33
+ saltHex: string;
34
+ txCommitmentHex: string;
35
+ }
36
+
37
+ interface ExecuteProposalWorkflowParams {
38
+ proposal: TransactionProposal;
39
+ accountId: string;
40
+ threshold: number;
41
+ signerCommitments: string[];
42
+ psmCommitment: string;
43
+ psmPublicKey?: string;
44
+ signatureScheme: 'falcon' | 'ecdsa';
45
+ getEffectiveThreshold: (proposalType: ProposalType) => number;
46
+ psm: PsmHttpClient;
47
+ webClient: WebClient;
48
+ }
49
+
50
+ export async function executeProposalWorkflow(
51
+ params: ExecuteProposalWorkflowParams,
52
+ ): Promise<void> {
53
+ ensureProposalReady(
54
+ params.proposal,
55
+ params.threshold,
56
+ params.getEffectiveThreshold,
57
+ );
58
+
59
+ const isSwitchPsm = params.proposal.metadata.proposalType === 'switch_psm';
60
+ const executionSource = await resolveExecutionSource(
61
+ params.psm,
62
+ params.accountId,
63
+ params.proposal,
64
+ isSwitchPsm,
65
+ );
66
+ const executionData = prepareExecutionData(executionSource.txSummaryBase64);
67
+
68
+ const adviceMap = buildCosignerAdviceMap(
69
+ params.proposal,
70
+ params.signerCommitments,
71
+ executionData.txCommitmentHex,
72
+ );
73
+
74
+ if (!isSwitchPsm && executionSource.delta) {
75
+ await appendPsmAckAdvice(
76
+ params.psm,
77
+ executionSource.delta,
78
+ params.psmCommitment,
79
+ params.psmPublicKey,
80
+ params.signatureScheme,
81
+ executionData.txCommitmentHex,
82
+ adviceMap,
83
+ );
84
+ }
85
+
86
+ const finalRequest = await buildFinalRequest(
87
+ params,
88
+ executionData.saltHex,
89
+ adviceMap,
90
+ );
91
+ await submitTransaction(params.webClient, params.accountId, finalRequest);
92
+ }
93
+
94
+ function ensureProposalReady(
95
+ proposal: TransactionProposal,
96
+ defaultThreshold: number,
97
+ getEffectiveThreshold: (proposalType: ProposalType) => number,
98
+ ): void {
99
+ const proposalType = proposal.metadata?.proposalType;
100
+ const effectiveThreshold = proposalType
101
+ ? getEffectiveThreshold(proposalType)
102
+ : defaultThreshold;
103
+
104
+ if (proposal.signatures.length < effectiveThreshold) {
105
+ throw new Error('Proposal is not ready for execution. Still pending signatures.');
106
+ }
107
+ }
108
+
109
+ async function resolveExecutionSource(
110
+ psm: PsmHttpClient,
111
+ accountId: string,
112
+ proposal: TransactionProposal,
113
+ isSwitchPsm: boolean,
114
+ ): Promise<ResolveExecutionSourceResult> {
115
+ if (isSwitchPsm) {
116
+ return { txSummaryBase64: proposal.txSummary };
117
+ }
118
+
119
+ const deltas = await psm.getDeltaProposals(accountId);
120
+ const delta = deltas.find(
121
+ (d) => computeCommitmentFromTxSummary(d.deltaPayload.txSummary.data) === proposal.commitment,
122
+ );
123
+
124
+ if (!delta) {
125
+ throw new Error(`Proposal not found on server: ${proposal.commitment}`);
126
+ }
127
+
128
+ return {
129
+ delta,
130
+ txSummaryBase64: delta.deltaPayload.txSummary.data,
131
+ };
132
+ }
133
+
134
+ function prepareExecutionData(txSummaryBase64: string): PrepareExecutionResult {
135
+ const txSummaryBytes = base64ToUint8Array(txSummaryBase64);
136
+ const txSummary = TransactionSummary.deserialize(txSummaryBytes);
137
+ const saltHex = txSummary.salt().toHex();
138
+ const txCommitmentHex = txSummary.toCommitment().toHex();
139
+ return { txSummary, saltHex, txCommitmentHex };
140
+ }
141
+
142
+ function buildCosignerAdviceMap(
143
+ proposal: TransactionProposal,
144
+ signerCommitments: string[],
145
+ txCommitmentHex: string,
146
+ ): AdviceMap {
147
+ const adviceMap = new AdviceMap();
148
+ const normalizedSignerCommitments = new Set(
149
+ signerCommitments.map((c) => normalizeHexWord(c)),
150
+ );
151
+
152
+ for (const cosignerSig of proposal.signatures) {
153
+ let signerCommitmentHex = normalizeHexWord(cosignerSig.signerId);
154
+ if (cosignerSig.signature.scheme === 'ecdsa' && cosignerSig.signature.publicKey) {
155
+ const derived = tryComputeEcdsaCommitmentHex(cosignerSig.signature.publicKey);
156
+ if (derived && derived !== signerCommitmentHex) {
157
+ if (!normalizedSignerCommitments.has(derived)) {
158
+ throw new Error(
159
+ `ECDSA public key commitment mismatch: derived commitment ${derived} is not in signerCommitments.`,
160
+ );
161
+ }
162
+ signerCommitmentHex = derived;
163
+ }
164
+ }
165
+
166
+ const signerCommitment = Word.fromHex(signerCommitmentHex);
167
+ const sigBytes = signatureHexToBytes(
168
+ cosignerSig.signature.signature,
169
+ cosignerSig.signature.scheme,
170
+ );
171
+ const signature = Signature.deserialize(sigBytes);
172
+ const txCommitment = Word.fromHex(normalizeHexWord(txCommitmentHex));
173
+ const isEcdsa =
174
+ cosignerSig.signature.scheme === 'ecdsa' && cosignerSig.signature.publicKey;
175
+
176
+ const { key, values } = buildSignatureAdviceEntry(
177
+ signerCommitment,
178
+ txCommitment,
179
+ signature,
180
+ isEcdsa ? cosignerSig.signature.publicKey : undefined,
181
+ isEcdsa ? cosignerSig.signature.signature : undefined,
182
+ );
183
+ adviceMap.insert(key, new FeltArray(values));
184
+ }
185
+
186
+ return adviceMap;
187
+ }
188
+
189
+ async function appendPsmAckAdvice(
190
+ psm: PsmHttpClient,
191
+ delta: DeltaObject,
192
+ psmCommitmentHex: string,
193
+ psmPublicKey: string | undefined,
194
+ defaultAckScheme: 'falcon' | 'ecdsa',
195
+ txCommitmentHex: string,
196
+ adviceMap: AdviceMap,
197
+ ): Promise<void> {
198
+ const executionDelta = {
199
+ ...delta,
200
+ deltaPayload: delta.deltaPayload.txSummary,
201
+ };
202
+
203
+ const pushResult = await psm.pushDelta(executionDelta);
204
+ const ackSigHex = pushResult.ackSig;
205
+ if (!ackSigHex) {
206
+ throw new Error('PSM did not return acknowledgment signature');
207
+ }
208
+
209
+ const psmAckScheme: 'ecdsa' | 'falcon' =
210
+ (pushResult.ackScheme as 'ecdsa' | 'falcon') || defaultAckScheme;
211
+ const ackPubkey = pushResult.ackPubkey || psmPublicKey;
212
+ const normalizedPsmCommitment = normalizeHexWord(psmCommitmentHex);
213
+
214
+ if (psmAckScheme === 'ecdsa' && ackPubkey) {
215
+ const derived = tryComputeEcdsaCommitmentHex(ackPubkey);
216
+ if (derived && derived !== normalizedPsmCommitment) {
217
+ throw new Error('PSM public key commitment mismatch');
218
+ }
219
+ }
220
+
221
+ const psmCommitment = Word.fromHex(normalizedPsmCommitment);
222
+ const ackSigBytes = signatureHexToBytes(ackSigHex, psmAckScheme);
223
+ const ackSignature = Signature.deserialize(ackSigBytes);
224
+ const txCommitment = Word.fromHex(normalizeHexWord(txCommitmentHex));
225
+ const isAckEcdsa = psmAckScheme === 'ecdsa' && ackPubkey;
226
+ const { key, values } = buildSignatureAdviceEntry(
227
+ psmCommitment,
228
+ txCommitment,
229
+ ackSignature,
230
+ isAckEcdsa ? ackPubkey : undefined,
231
+ isAckEcdsa ? ackSigHex : undefined,
232
+ );
233
+ adviceMap.insert(key, new FeltArray(values));
234
+ }
235
+
236
+ async function buildFinalRequest(
237
+ params: ExecuteProposalWorkflowParams,
238
+ saltHex: string,
239
+ adviceMap: AdviceMap,
240
+ ): Promise<TransactionRequest> {
241
+ const metadata = params.proposal.metadata;
242
+ const normalizedSalt = Word.fromHex(normalizeHexWord(saltHex));
243
+
244
+ switch (metadata.proposalType) {
245
+ case 'consume_notes': {
246
+ if (!metadata.noteIds || metadata.noteIds.length === 0) {
247
+ throw new Error(
248
+ 'Proposal missing noteIds. Was it created with createConsumeNotesProposal?',
249
+ );
250
+ }
251
+ const { request } = await buildConsumeNotesTransactionRequest(
252
+ params.webClient,
253
+ metadata.noteIds,
254
+ { salt: normalizedSalt, signatureAdviceMap: adviceMap },
255
+ );
256
+ return request;
257
+ }
258
+ case 'switch_psm': {
259
+ if (!metadata.newPsmPubkey) {
260
+ throw new Error(
261
+ 'Proposal missing newPsmPubkey. Was it created with createSwitchPsmProposal?',
262
+ );
263
+ }
264
+ const { request } = await buildUpdatePsmTransactionRequest(
265
+ params.webClient,
266
+ metadata.newPsmPubkey,
267
+ {
268
+ salt: normalizedSalt,
269
+ signatureAdviceMap: adviceMap,
270
+ signatureScheme: params.signatureScheme,
271
+ },
272
+ );
273
+ return request;
274
+ }
275
+ case 'p2id': {
276
+ if (!metadata.recipientId || !metadata.faucetId || !metadata.amount) {
277
+ throw new Error(
278
+ 'Proposal missing P2ID metadata (recipientId, faucetId, amount). Was it created with createP2idProposal?',
279
+ );
280
+ }
281
+ const { request } = buildP2idTransactionRequest(
282
+ params.accountId,
283
+ metadata.recipientId,
284
+ metadata.faucetId,
285
+ BigInt(metadata.amount),
286
+ { salt: normalizedSalt, signatureAdviceMap: adviceMap },
287
+ );
288
+ return request;
289
+ }
290
+ case 'unknown':
291
+ throw new Error(
292
+ 'Cannot execute proposal with unknown type. The proposal must have been imported without proper metadata.',
293
+ );
294
+ default: {
295
+ const { request } = await buildUpdateSignersTransactionRequest(
296
+ params.webClient,
297
+ metadata.targetThreshold,
298
+ metadata.targetSignerCommitments,
299
+ {
300
+ salt: normalizedSalt,
301
+ signatureAdviceMap: adviceMap,
302
+ signatureScheme: params.signatureScheme,
303
+ },
304
+ );
305
+ return request;
306
+ }
307
+ }
308
+ }
309
+
310
+ async function submitTransaction(
311
+ webClient: WebClient,
312
+ accountIdHex: string,
313
+ request: TransactionRequest,
314
+ ): Promise<void> {
315
+ const accountId = AccountId.fromHex(accountIdHex);
316
+ const result = await webClient.executeTransaction(accountId, request);
317
+ const proven = await webClient.proveTransaction(result, null);
318
+ const submissionHeight = await webClient.submitProvenTransaction(proven, result);
319
+ await webClient.applyTransaction(result, submissionHeight);
320
+ }