@openzeppelin/miden-multisig-client 0.12.6

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 (215) hide show
  1. package/README.md +189 -0
  2. package/dist/account/builder.d.ts +23 -0
  3. package/dist/account/builder.d.ts.map +1 -0
  4. package/dist/account/builder.js +85 -0
  5. package/dist/account/builder.js.map +1 -0
  6. package/dist/account/index.d.ts +6 -0
  7. package/dist/account/index.d.ts.map +1 -0
  8. package/dist/account/index.js +6 -0
  9. package/dist/account/index.js.map +1 -0
  10. package/dist/account/masm.d.ts +3 -0
  11. package/dist/account/masm.d.ts.map +1 -0
  12. package/dist/account/masm.js +605 -0
  13. package/dist/account/masm.js.map +1 -0
  14. package/dist/account/storage.d.ts +10 -0
  15. package/dist/account/storage.d.ts.map +1 -0
  16. package/dist/account/storage.js +56 -0
  17. package/dist/account/storage.js.map +1 -0
  18. package/dist/account.d.ts +104 -0
  19. package/dist/account.d.ts.map +1 -0
  20. package/dist/account.js +136 -0
  21. package/dist/account.js.map +1 -0
  22. package/dist/builder.d.ts +54 -0
  23. package/dist/builder.d.ts.map +1 -0
  24. package/dist/builder.js +78 -0
  25. package/dist/builder.js.map +1 -0
  26. package/dist/client.d.ts +73 -0
  27. package/dist/client.d.ts.map +1 -0
  28. package/dist/client.js +104 -0
  29. package/dist/client.js.map +1 -0
  30. package/dist/client.test.d.ts +2 -0
  31. package/dist/client.test.d.ts.map +1 -0
  32. package/dist/client.test.js +150 -0
  33. package/dist/client.test.js.map +1 -0
  34. package/dist/http.d.ts +64 -0
  35. package/dist/http.d.ts.map +1 -0
  36. package/dist/http.js +162 -0
  37. package/dist/http.js.map +1 -0
  38. package/dist/index.d.ts +53 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +52 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/inspector.d.ts +49 -0
  43. package/dist/inspector.d.ts.map +1 -0
  44. package/dist/inspector.js +120 -0
  45. package/dist/inspector.js.map +1 -0
  46. package/dist/inspector.test.d.ts +2 -0
  47. package/dist/inspector.test.d.ts.map +1 -0
  48. package/dist/inspector.test.js +205 -0
  49. package/dist/inspector.test.js.map +1 -0
  50. package/dist/keystore.d.ts +65 -0
  51. package/dist/keystore.d.ts.map +1 -0
  52. package/dist/keystore.js +121 -0
  53. package/dist/keystore.js.map +1 -0
  54. package/dist/miden.d.ts +9 -0
  55. package/dist/miden.d.ts.map +1 -0
  56. package/dist/miden.js +26 -0
  57. package/dist/miden.js.map +1 -0
  58. package/dist/multisig/helpers.d.ts +4 -0
  59. package/dist/multisig/helpers.d.ts.map +1 -0
  60. package/dist/multisig/helpers.js +21 -0
  61. package/dist/multisig/helpers.js.map +1 -0
  62. package/dist/multisig/helpers.test.d.ts +2 -0
  63. package/dist/multisig/helpers.test.d.ts.map +1 -0
  64. package/dist/multisig/helpers.test.js +94 -0
  65. package/dist/multisig/helpers.test.js.map +1 -0
  66. package/dist/multisig/metadata.d.ts +18 -0
  67. package/dist/multisig/metadata.d.ts.map +1 -0
  68. package/dist/multisig/metadata.js +100 -0
  69. package/dist/multisig/metadata.js.map +1 -0
  70. package/dist/multisig/metadata.test.d.ts +2 -0
  71. package/dist/multisig/metadata.test.d.ts.map +1 -0
  72. package/dist/multisig/metadata.test.js +323 -0
  73. package/dist/multisig/metadata.test.js.map +1 -0
  74. package/dist/multisig.d.ts +199 -0
  75. package/dist/multisig.d.ts.map +1 -0
  76. package/dist/multisig.js +821 -0
  77. package/dist/multisig.js.map +1 -0
  78. package/dist/multisig.test.d.ts +2 -0
  79. package/dist/multisig.test.d.ts.map +1 -0
  80. package/dist/multisig.test.js +1121 -0
  81. package/dist/multisig.test.js.map +1 -0
  82. package/dist/procedures.d.ts +57 -0
  83. package/dist/procedures.d.ts.map +1 -0
  84. package/dist/procedures.js +62 -0
  85. package/dist/procedures.js.map +1 -0
  86. package/dist/signer.d.ts +36 -0
  87. package/dist/signer.d.ts.map +1 -0
  88. package/dist/signer.js +68 -0
  89. package/dist/signer.js.map +1 -0
  90. package/dist/signer.test.d.ts +2 -0
  91. package/dist/signer.test.d.ts.map +1 -0
  92. package/dist/signer.test.js +117 -0
  93. package/dist/signer.test.js.map +1 -0
  94. package/dist/transaction/consumeNotes.d.ts +7 -0
  95. package/dist/transaction/consumeNotes.d.ts.map +1 -0
  96. package/dist/transaction/consumeNotes.js +28 -0
  97. package/dist/transaction/consumeNotes.js.map +1 -0
  98. package/dist/transaction/index.d.ts +7 -0
  99. package/dist/transaction/index.d.ts.map +1 -0
  100. package/dist/transaction/index.js +6 -0
  101. package/dist/transaction/index.js.map +1 -0
  102. package/dist/transaction/options.d.ts +6 -0
  103. package/dist/transaction/options.d.ts.map +1 -0
  104. package/dist/transaction/options.js +2 -0
  105. package/dist/transaction/options.js.map +1 -0
  106. package/dist/transaction/p2id.d.ts +7 -0
  107. package/dist/transaction/p2id.d.ts.map +1 -0
  108. package/dist/transaction/p2id.js +43 -0
  109. package/dist/transaction/p2id.js.map +1 -0
  110. package/dist/transaction/summary.d.ts +3 -0
  111. package/dist/transaction/summary.d.ts.map +1 -0
  112. package/dist/transaction/summary.js +6 -0
  113. package/dist/transaction/summary.js.map +1 -0
  114. package/dist/transaction/updatePsm.d.ts +7 -0
  115. package/dist/transaction/updatePsm.d.ts.map +1 -0
  116. package/dist/transaction/updatePsm.js +43 -0
  117. package/dist/transaction/updatePsm.js.map +1 -0
  118. package/dist/transaction/updateSigners.d.ts +8 -0
  119. package/dist/transaction/updateSigners.d.ts.map +1 -0
  120. package/dist/transaction/updateSigners.js +60 -0
  121. package/dist/transaction/updateSigners.js.map +1 -0
  122. package/dist/transaction.d.ts +6 -0
  123. package/dist/transaction.d.ts.map +1 -0
  124. package/dist/transaction.js +8 -0
  125. package/dist/transaction.js.map +1 -0
  126. package/dist/transaction.test.d.ts +2 -0
  127. package/dist/transaction.test.d.ts.map +1 -0
  128. package/dist/transaction.test.js +117 -0
  129. package/dist/transaction.test.js.map +1 -0
  130. package/dist/transport/http.d.ts +64 -0
  131. package/dist/transport/http.d.ts.map +1 -0
  132. package/dist/transport/http.js +162 -0
  133. package/dist/transport/http.js.map +1 -0
  134. package/dist/transport/index.d.ts +5 -0
  135. package/dist/transport/index.d.ts.map +1 -0
  136. package/dist/transport/index.js +5 -0
  137. package/dist/transport/index.js.map +1 -0
  138. package/dist/types/proposal.d.ts +74 -0
  139. package/dist/types/proposal.d.ts.map +1 -0
  140. package/dist/types/proposal.js +2 -0
  141. package/dist/types/proposal.js.map +1 -0
  142. package/dist/types.d.ts +60 -0
  143. package/dist/types.d.ts.map +1 -0
  144. package/dist/types.js +2 -0
  145. package/dist/types.js.map +1 -0
  146. package/dist/utils/encoding.d.ts +7 -0
  147. package/dist/utils/encoding.d.ts.map +1 -0
  148. package/dist/utils/encoding.js +54 -0
  149. package/dist/utils/encoding.js.map +1 -0
  150. package/dist/utils/encoding.test.d.ts +2 -0
  151. package/dist/utils/encoding.test.d.ts.map +1 -0
  152. package/dist/utils/encoding.test.js +185 -0
  153. package/dist/utils/encoding.test.js.map +1 -0
  154. package/dist/utils/index.d.ts +5 -0
  155. package/dist/utils/index.d.ts.map +1 -0
  156. package/dist/utils/index.js +5 -0
  157. package/dist/utils/index.js.map +1 -0
  158. package/dist/utils/random.d.ts +3 -0
  159. package/dist/utils/random.d.ts.map +1 -0
  160. package/dist/utils/random.js +14 -0
  161. package/dist/utils/random.js.map +1 -0
  162. package/dist/utils/signature.d.ts +12 -0
  163. package/dist/utils/signature.d.ts.map +1 -0
  164. package/dist/utils/signature.js +28 -0
  165. package/dist/utils/signature.js.map +1 -0
  166. package/dist/utils/signature.test.d.ts +2 -0
  167. package/dist/utils/signature.test.d.ts.map +1 -0
  168. package/dist/utils/signature.test.js +149 -0
  169. package/dist/utils/signature.test.js.map +1 -0
  170. package/dist/utils/word.d.ts +4 -0
  171. package/dist/utils/word.d.ts.map +1 -0
  172. package/dist/utils/word.js +8 -0
  173. package/dist/utils/word.js.map +1 -0
  174. package/dist/utils/word.test.d.ts +2 -0
  175. package/dist/utils/word.test.d.ts.map +1 -0
  176. package/dist/utils/word.test.js +54 -0
  177. package/dist/utils/word.test.js.map +1 -0
  178. package/masm/multisig.masm +423 -0
  179. package/masm/psm.masm +178 -0
  180. package/package.json +42 -0
  181. package/src/account/builder.ts +112 -0
  182. package/src/account/index.ts +15 -0
  183. package/src/account/masm.ts +606 -0
  184. package/src/account/storage.ts +70 -0
  185. package/src/client.test.ts +177 -0
  186. package/src/client.ts +129 -0
  187. package/src/index.ts +118 -0
  188. package/src/inspector.test.ts +237 -0
  189. package/src/inspector.ts +142 -0
  190. package/src/multisig/helpers.test.ts +114 -0
  191. package/src/multisig/helpers.ts +23 -0
  192. package/src/multisig.test.ts +1294 -0
  193. package/src/multisig.ts +1097 -0
  194. package/src/procedures.ts +72 -0
  195. package/src/signer.test.ts +135 -0
  196. package/src/signer.ts +75 -0
  197. package/src/transaction/consumeNotes.ts +41 -0
  198. package/src/transaction/index.ts +7 -0
  199. package/src/transaction/options.ts +7 -0
  200. package/src/transaction/p2id.ts +105 -0
  201. package/src/transaction/summary.ts +12 -0
  202. package/src/transaction/updatePsm.ts +69 -0
  203. package/src/transaction/updateSigners.ts +96 -0
  204. package/src/transaction.test.ts +136 -0
  205. package/src/transaction.ts +16 -0
  206. package/src/types/proposal.ts +80 -0
  207. package/src/types.ts +81 -0
  208. package/src/utils/encoding.test.ts +233 -0
  209. package/src/utils/encoding.ts +60 -0
  210. package/src/utils/index.ts +5 -0
  211. package/src/utils/random.ts +15 -0
  212. package/src/utils/signature.test.ts +187 -0
  213. package/src/utils/signature.ts +36 -0
  214. package/src/utils/word.test.ts +64 -0
  215. package/src/utils/word.ts +11 -0
@@ -0,0 +1,606 @@
1
+ // Embedded MASM constants - content from masm/*.masm files
2
+
3
+ export const MULTISIG_MASM = `# Multi-Signature RPO Falcon 512 Authentication Component
4
+ #
5
+ # This component provides multi-signature authentication for accounts.
6
+ # It integrates with the PSM component for optional PSM signature verification.
7
+
8
+ use miden::active_account
9
+ use miden::native_account
10
+ use miden::auth
11
+ use openzeppelin::psm
12
+
13
+ # Type definitions for v0.12 syntax
14
+ type BeWord = struct @bigendian { a: felt, b: felt, c: felt, d: felt }
15
+
16
+ # CONSTANTS
17
+ # =================================================================================================
18
+
19
+ # Auth Request Constants
20
+
21
+ # The event emitted when a signature is not found for a required signer.
22
+ const AUTH_UNAUTHORIZED_EVENT = event("miden::auth::unauthorized")
23
+
24
+ # Storage Layout Constants
25
+ #
26
+ # +-------------------------------+----------+--------------+-------------------+
27
+ # | THRESHOLD & APPROVERS CONFIG | PUB KEYS | EXECUTED TXS | PROC THRESHOLDS |
28
+ # | (slot) | (map) | (map) | (map) |
29
+ # +-------------------------------+----------+--------------+-------------------+
30
+ # | 0 | 1 | 2 | 3 |
31
+ # +-------------------------------+----------+--------------+-------------------+
32
+
33
+ # The slot in this component's storage layout where the default signature threshold and
34
+ # number of approvers are stored as:
35
+ # [default_threshold, num_approvers, 0, 0].
36
+ # The threshold is guaranteed to be less than or equal to num_approvers.
37
+ const THRESHOLD_CONFIG_SLOT = 0
38
+
39
+ # The slot in this component's storage layout where the public keys map is stored.
40
+ # Map entries: [key_index, 0, 0, 0] => APPROVER_PUBLIC_KEY
41
+ const PUBLIC_KEYS_MAP_SLOT = 1
42
+
43
+ # The slot in this component's storage layout where executed transactions are stored.
44
+ # Map entries: transaction_message => [is_executed, 0, 0, 0]
45
+ const EXECUTED_TXS_SLOT = 2
46
+
47
+ # The slot in this component's storage layout where procedure thresholds are stored.
48
+ # Map entries: PROC_ROOT => [proc_threshold, 0, 0, 0]
49
+ const PROC_THRESHOLD_ROOTS_SLOT = 3
50
+
51
+ # Executed Transaction Flag Constant
52
+ const IS_EXECUTED_FLAG = [1, 0, 0, 0]
53
+
54
+ # ERRORS
55
+ const ERR_TX_ALREADY_EXECUTED = "failed to approve multisig transaction as it was already executed"
56
+
57
+ const ERR_MALFORMED_MULTISIG_CONFIG = "number of approvers must be equal to or greater than threshold"
58
+
59
+ const ERR_ZERO_IN_MULTISIG_CONFIG = "number of approvers or threshold must not be zero"
60
+
61
+ # MULTISIG PROCEDURES
62
+ # =================================================================================================
63
+
64
+ #! Check if transaction has already been executed and add it to executed transactions for replay protection.
65
+ #!
66
+ #! Inputs: [MSG]
67
+ #! Outputs: []
68
+ #!
69
+ #! Panics if:
70
+ #! - the same transaction has already been executed
71
+ proc assert_new_tx(msg: BeWord)
72
+ push.IS_EXECUTED_FLAG
73
+ # => [[0, 0, 0, is_executed], MSG]
74
+
75
+ swapw
76
+ # => [MSG, IS_EXECUTED_FLAG]
77
+
78
+ push.EXECUTED_TXS_SLOT
79
+ # => [index, MSG, IS_EXECUTED_FLAG]
80
+
81
+ # Set the key value pair in the map to mark transaction as executed
82
+ exec.native_account::set_map_item
83
+ # => [OLD_MAP_ROOT, [0, 0, 0, is_executed]]
84
+
85
+ dropw drop drop drop
86
+ # => [is_executed]
87
+
88
+ assertz.err=ERR_TX_ALREADY_EXECUTED
89
+ # => []
90
+ end
91
+
92
+ #! Remove old approver public keys from the approver public key mapping.
93
+ #!
94
+ #! This procedure cleans up the storage by removing public keys of approvers that are no longer
95
+ #! part of the multisig configuration. This procedure assumes that init_num_of_approvers and
96
+ #! new_num_of_approvers are u32 values.
97
+ #!
98
+ #! Inputs: [init_num_of_approvers, new_num_of_approvers]
99
+ #! Outputs: []
100
+ #!
101
+ #! Where:
102
+ #! - init_num_of_approvers is the original number of approvers before the update
103
+ #! - new_num_of_approvers is the new number of approvers after the update
104
+ proc cleanup_pubkey_mapping(init_num_of_approvers: u32, new_num_of_approvers: u32)
105
+ dup.1 dup.1
106
+ u32assert2 u32lt
107
+ # => [should_loop, i = init_num_of_approvers, new_num_of_approvers]
108
+
109
+ while.true
110
+ # => [i, new_num_of_approvers]
111
+
112
+ sub.1
113
+ # => [i-1, new_num_of_approvers]
114
+
115
+ dup
116
+ # => [i-1, i-1, new_num_of_approvers]
117
+
118
+ push.0.0.0
119
+ # => [[0, 0, 0, i-1], i-1, new_num_of_approvers]
120
+
121
+ padw swapw
122
+ # => [[0, 0, 0, i-1], EMPTY_WORD, i-1, new_num_of_approvers]
123
+
124
+ push.PUBLIC_KEYS_MAP_SLOT
125
+ # => [pub_key_slot_idx, [0, 0, 0, i-1], EMPTY_WORD, i-1, new_num_of_approvers]
126
+
127
+ exec.native_account::set_map_item
128
+ # => [OLD_MAP_ROOT, OLD_MAP_VALUE, i-1, new_num_of_approvers]
129
+
130
+ dropw dropw
131
+ # => [i-1, new_num_of_approvers]
132
+
133
+ dup.1 dup.1
134
+ u32lt
135
+ # => [should_loop, i-1, new_num_of_approvers]
136
+ end
137
+
138
+ drop drop
139
+ # => []
140
+ end
141
+
142
+ #! Update threshold config and add / remove approvers
143
+ #!
144
+ #! Inputs:
145
+ #! Operand stack: [MULTISIG_CONFIG_HASH, pad(12)]
146
+ #! Advice map: {
147
+ #! MULTISIG_CONFIG_HASH => [CONFIG, PUB_KEY_N, PUB_KEY_N-1, ..., PUB_KEY_0]
148
+ #! }
149
+ #! Outputs:
150
+ #! Operand stack: []
151
+ #!
152
+ #! Where:
153
+ #! - MULTISIG_CONFIG_HASH is the hash of the threshold and new public key vector
154
+ #! - MULTISIG_CONFIG is [threshold, num_approvers, 0, 0]
155
+ #! - PUB_KEY_i is the public key of the i-th signer
156
+ #!
157
+ #! Locals:
158
+ #! 0: new_num_of_approvers
159
+ #! 1: init_num_of_approvers
160
+ pub proc update_signers_and_threshold.2(multisig_config_hash: BeWord)
161
+ adv.push_mapval
162
+ # => [MULTISIG_CONFIG_HASH, pad(12)]
163
+
164
+ adv_loadw
165
+ # => [MULTISIG_CONFIG, pad(12)]
166
+
167
+ # store new_num_of_approvers for later
168
+ dup.2 loc_store.0
169
+ # => [MULTISIG_CONFIG, pad(12)]
170
+
171
+ dup.3 dup.3
172
+ # => [num_approvers, threshold, MULTISIG_CONFIG, pad(12)]
173
+
174
+ # make sure that the threshold is smaller than the number of approvers
175
+ u32assert2.err=ERR_MALFORMED_MULTISIG_CONFIG
176
+ u32gt assertz.err=ERR_MALFORMED_MULTISIG_CONFIG
177
+ # => [MULTISIG_CONFIG, pad(12)]
178
+
179
+ dup.3 dup.3
180
+ # => [num_approvers, threshold, MULTISIG_CONFIG, pad(12)]
181
+
182
+ # make sure that threshold or num_approvers are not zero
183
+ eq.0 assertz.err=ERR_ZERO_IN_MULTISIG_CONFIG
184
+ eq.0 assertz.err=ERR_ZERO_IN_MULTISIG_CONFIG
185
+ # => [MULTISIG_CONFIG, pad(12)]
186
+
187
+ push.THRESHOLD_CONFIG_SLOT
188
+ # => [slot, MULTISIG_CONFIG, pad(12)]
189
+
190
+ exec.native_account::set_item
191
+ # => [OLD_THRESHOLD_CONFIG, pad(12)]
192
+
193
+ # store init_num_of_approvers for later
194
+ drop drop loc_store.1 drop
195
+ # => [pad(12)]
196
+
197
+ loc_load.0
198
+ # => [num_approvers]
199
+
200
+ dup neq.0
201
+ while.true
202
+ sub.1
203
+ # => [i-1, pad(12)]
204
+
205
+ dup push.0.0.0
206
+ # => [[0, 0, 0, i-1], i-1, pad(12)]
207
+
208
+ padw adv_loadw
209
+ # => [PUB_KEY, [0, 0, 0, i-1], i-1, pad(12)]
210
+
211
+ swapw
212
+ # => [[0, 0, 0, i-1], PUB_KEY, i-1, pad(12)]
213
+
214
+ push.PUBLIC_KEYS_MAP_SLOT
215
+ # => [pub_key_slot_idx, [0, 0, 0, i-1], PUB_KEY, i-1, pad(12)]
216
+
217
+ exec.native_account::set_map_item
218
+ # => [OLD_MAP_ROOT, OLD_MAP_VALUE, i-1, pad(12)]
219
+
220
+ dropw dropw
221
+ # => [i-1, pad(12)]
222
+
223
+ dup neq.0
224
+ # => [is_non_zero, i-1, pad(12)]
225
+ end
226
+ # => [pad(13)]
227
+
228
+ drop
229
+ # => [pad(12)]
230
+
231
+ # compare initial vs current multisig config
232
+
233
+ # load init_num_of_approvers & new_num_of_approvers
234
+ loc_load.0 loc_load.1
235
+ # => [init_num_of_approvers, new_num_of_approvers, pad(12)]
236
+
237
+ exec.cleanup_pubkey_mapping
238
+ # => [pad(12)]
239
+ end
240
+
241
+ # Computes the effective transaction threshold based on called procedures and per-procedure
242
+ # overrides stored in PROC_THRESHOLD_ROOTS_SLOT. Falls back to default_threshold if no
243
+ # overrides apply.
244
+ #
245
+ #! Inputs: [default_threshold]
246
+ #! Outputs: [transaction_threshold]
247
+ proc compute_transaction_threshold.1(default_threshold: u32) -> u32
248
+ # 1. initialize transaction_threshold = 0
249
+ # 2. iterate through all account procedures
250
+ # a. check if the procedure was called during the transaction
251
+ # b. if called, get the override threshold of that procedure from the config map
252
+ # c. if proc_threshold > transaction_threshold, set transaction_threshold = proc_threshold
253
+ # 3. if transaction_threshold == 0 at the end, revert to using default_threshold
254
+
255
+ # store default_threshold for later
256
+ loc_store.0
257
+ # => []
258
+
259
+ # 1. initialize transaction_threshold = 0
260
+ push.0
261
+ # => [transaction_threshold]
262
+
263
+ # get the number of account procedures
264
+ exec.active_account::get_num_procedures
265
+ # => [num_procedures, transaction_threshold]
266
+
267
+ # 2. iterate through all account procedures
268
+ dup neq.0
269
+ # => [should_continue, num_procedures, transaction_threshold]
270
+ while.true
271
+ sub.1 dup
272
+ # => [num_procedures-1, num_procedures-1, transaction_threshold]
273
+
274
+ # get procedure root of the procedure with index i
275
+ exec.active_account::get_procedure_root dupw
276
+ # => [PROC_ROOT, PROC_ROOT, num_procedures-1, transaction_threshold]
277
+
278
+ # 2a. check if this procedure has been called in the transaction
279
+ exec.native_account::was_procedure_called
280
+ # => [was_called, PROC_ROOT, num_procedures-1, transaction_threshold]
281
+
282
+ # if it has been called, get the override threshold of that procedure
283
+ if.true
284
+ # => [PROC_ROOT, num_procedures-1, transaction_threshold]
285
+
286
+ push.PROC_THRESHOLD_ROOTS_SLOT
287
+ # => [PROC_THRESHOLD_ROOTS_SLOT, PROC_ROOT, num_procedures-1, transaction_threshold]
288
+
289
+ # 2b. get the override proc_threshold of that procedure
290
+ # if the procedure has no override threshold, the returned map item will be [0, 0, 0, 0]
291
+ exec.active_account::get_initial_map_item
292
+ # => [[0, 0, 0, proc_threshold], num_procedures-1, transaction_threshold]
293
+
294
+ drop drop drop dup dup.3
295
+ # => [transaction_threshold, proc_threshold, proc_threshold, num_procedures-1, transaction_threshold]
296
+
297
+ u32assert2.err="transaction threshold or procedure threshold are not u32"
298
+ u32gt
299
+ # => [is_gt, proc_threshold, num_procedures-1, transaction_threshold]
300
+ # 2c. if proc_threshold > transaction_threshold, update transaction_threshold
301
+ movup.2 movdn.3
302
+ # => [is_gt, proc_threshold, transaction_threshold, num_procedures-1]
303
+ cdrop
304
+ # => [updated_transaction_threshold, num_procedures-1]
305
+ swap
306
+ # => [num_procedures-1, updated_transaction_threshold]
307
+ # if it has not been called during this transaction, nothing to do, move to the next procedure
308
+ else
309
+ dropw
310
+ # => [num_procedures-1, transaction_threshold]
311
+ end
312
+
313
+ dup neq.0
314
+ # => [should_continue, num_procedures-1, transaction_threshold]
315
+ end
316
+
317
+ drop
318
+ # => [transaction_threshold]
319
+
320
+ loc_load.0
321
+ # => [default_threshold, transaction_threshold]
322
+
323
+ # 3. if transaction_threshold == 0 at the end, revert to using default_threshold
324
+ dup.1 eq.0
325
+ # => [is_zero, default_threshold, transaction_threshold]
326
+
327
+ cdrop
328
+ # => [effective_transaction_threshold]
329
+ end
330
+
331
+ #! Authenticate a transaction using the Falcon signature scheme with multi-signature support.
332
+ #!
333
+ #! This procedure implements multi-signature authentication by:
334
+ #! 1. Computing the transaction summary message that needs to be signed
335
+ #! 2. Verifying signatures from multiple required signers against their public keys
336
+ #! 3. Ensuring the minimum threshold of valid signatures is met
337
+ #! 4. Implementing replay protection by tracking executed transactions
338
+ #! 5. Verifying PSM signature if PSM selector is enabled (via PSM component)
339
+ #!
340
+ #! Inputs:
341
+ #! Operand stack: [SALT]
342
+ #! Advice map: {
343
+ #! h(SIG_0, MSG): SIG_0,
344
+ #! h(SIG_1, MSG): SIG_1,
345
+ #! h(SIG_n, MSG): SIG_n
346
+ #! }
347
+ #! Outputs:
348
+ #! Operand stack: []
349
+ #!
350
+ #! Where:
351
+ #! - SALT is a cryptographically random nonce that enables multiple concurrent
352
+ #! multisig transactions while maintaining replay protection. Each transaction
353
+ #! must use a unique SALT value to ensure transaction uniqueness.
354
+ #! - SIG_i is the signature from the i-th signer.
355
+ #! - MSG is the transaction message being signed.
356
+ #! - h(SIG_i, MSG) is the hash of the signature and message used as the advice map key.
357
+ #!
358
+ #! Panics if:
359
+ #! - insufficient number of valid signatures (below threshold).
360
+ #! - the same transaction has already been executed (replay protection).
361
+ #! - PSM signature verification fails (if PSM is enabled).
362
+ #!
363
+ #! Invocation: call
364
+ pub proc auth_tx_rpo_falcon512_multisig.1(salt: BeWord)
365
+ exec.native_account::incr_nonce drop
366
+ # => [SALT]
367
+
368
+ # ------ Computing transaction summary ------
369
+
370
+ exec.auth::create_tx_summary
371
+ # => [SALT, OUTPUT_NOTES_COMMITMENT, INPUT_NOTES_COMMITMENT, ACCOUNT_DELTA_COMMITMENT]
372
+
373
+ # to build a tx_summary in the host, we need these four words in the advice provider
374
+ exec.auth::adv_insert_hqword
375
+ # => [SALT, OUTPUT_NOTES_COMMITMENT, INPUT_NOTES_COMMITMENT, ACCOUNT_DELTA_COMMITMENT]
376
+
377
+ # the commitment to the tx summary is the message that is signed
378
+ exec.auth::hash_tx_summary
379
+ # => [TX_SUMMARY_COMMITMENT]
380
+
381
+ # ------ Verifying approver signatures ------
382
+
383
+ push.THRESHOLD_CONFIG_SLOT
384
+ # => [index, TX_SUMMARY_COMMITMENT]
385
+
386
+ exec.active_account::get_initial_item
387
+ # => [0, 0, num_of_approvers, default_threshold, TX_SUMMARY_COMMITMENT]
388
+
389
+ drop drop
390
+ # => [num_of_approvers, default_threshold, TX_SUMMARY_COMMITMENT]
391
+
392
+ swap movdn.5
393
+ # => [num_of_approvers, TX_SUMMARY_COMMITMENT, default_threshold]
394
+
395
+ push.PUBLIC_KEYS_MAP_SLOT
396
+ # => [pub_key_slot_idx, num_of_approvers, TX_SUMMARY_COMMITMENT, default_threshold]
397
+
398
+ exec.::miden::auth::rpo_falcon512::verify_signatures
399
+ # => [num_verified_signatures, TX_SUMMARY_COMMITMENT, default_threshold]
400
+
401
+ # ------ Checking threshold is >= num_verified_signatures ------
402
+
403
+ movup.5
404
+ # => [default_threshold, num_verified_signatures, TX_SUMMARY_COMMITMENT]
405
+
406
+ exec.compute_transaction_threshold
407
+ # => [transaction_threshold, num_verified_signatures, TX_SUMMARY_COMMITMENT]
408
+
409
+ u32assert2 u32lt
410
+ # => [is_unauthorized, TX_SUMMARY_COMMITMENT]
411
+
412
+ # If signatures are non-existent the tx will fail here.
413
+ if.true
414
+ emit.AUTH_UNAUTHORIZED_EVENT
415
+ push.0 assert.err="insufficient number of signatures"
416
+ end
417
+
418
+ # ------ Verifying PSM Signature ------
419
+ # => [TX_SUMMARY_COMMITMENT]
420
+ call.psm::verify_psm_signature
421
+
422
+ # ------ Writing executed transaction MSG to map ------
423
+ # => [TX_SUMMARY_COMMITMENT]
424
+ exec.assert_new_tx
425
+ end
426
+ `;
427
+
428
+ export const PSM_MASM = `# Private State Manager (PSM) Authentication Component
429
+ #
430
+ # This component provides PSM signature verification for accounts.
431
+ # It can be used standalone or in conjunction with other auth components like multisig.
432
+
433
+ use miden::active_account
434
+ use miden::native_account
435
+
436
+ # Type definitions for v0.12 syntax
437
+ type BeWord = struct @bigendian { a: felt, b: felt, c: felt, d: felt }
438
+
439
+ # IMPORTANT SECURITY NOTES
440
+ # --------------------------------------------------------------------------------
441
+ # - The selector in \`PSM_SELECTOR_SLOT\` controls whether the extra PSM signature
442
+ # is enforced:
443
+ # * PSM_ON => exactly one valid PSM signature is required.
444
+ # * PSM_OFF => PSM signature is skipped for that call.
445
+ #
446
+ # - \`verify_psm_signature\` reads the selector from initial storage state.
447
+ # This means changes made during the same transaction won't affect the check.
448
+ #
449
+ # - \`enable_psm\` / \`disable_psm\` procedures allow explicit control over PSM state.
450
+ #
451
+ # - \`update_psm_public_key\`:
452
+ # * Installs a new PSM public key in the map at \`PSM_PUBLIC_KEY_MAP_SLOT\`.
453
+ # * Does not itself perform any signature checks.
454
+ # * To update the key without requiring PSM signature, ensure selector is OFF.
455
+ #
456
+ # Storage Layout
457
+ # --------------------------------------------------------------------------------
458
+ #
459
+ # +---------------------+---------------+
460
+ # | DESCRIPTION | SLOT |
461
+ # +---------------------+---------------+
462
+ # | PSM SELECTOR (word) | 0 |
463
+ # | PSM PUBLIC KEY MAP | 1 |
464
+ # +---------------------+---------------+
465
+ #
466
+ # - PSM_SELECTOR_SLOT (0):
467
+ # * Stores a word that is compared against [1, 0, 0, 0] (PSM_ON).
468
+ # * Any value != PSM_ON is treated as PSM_OFF.
469
+ #
470
+ # - PSM_PUBLIC_KEY_MAP_SLOT (1):
471
+ # * A map from a fixed key [0, 0, 0, 0] to the single PSM public key:
472
+ # [0, 0, 0, 0] => PSM_PUBLIC_KEY
473
+ # * PSM_PUBLIC_KEY is a RPO Falcon 512 public key represented as a word.
474
+
475
+ # CONSTANTS
476
+ # =================================================================================================
477
+
478
+ # Slot where the PSM selector flag is stored:
479
+ # - PSM_ON => PSM signature required
480
+ # - PSM_OFF => PSM signature skipped
481
+ const PSM_SELECTOR_SLOT = 0
482
+
483
+ # Map slot for PSM public key
484
+ # Uses exactly one PSM public key at index [0, 0, 0, 0]
485
+ # [0, 0, 0, 0] => PSM_PUBLIC_KEY
486
+ const PSM_PUBLIC_KEY_MAP_SLOT = 1
487
+
488
+ # Selector flag values
489
+ const PSM_ON = [1, 0, 0, 0]
490
+ const PSM_OFF = [0, 0, 0, 0]
491
+
492
+ # The event emitted when a signature is not found for a required signer.
493
+ const AUTH_UNAUTHORIZED_EVENT = event("miden::auth::unauthorized")
494
+
495
+ # PSM PROCEDURES
496
+ # =================================================================================================
497
+
498
+ #! Enable PSM verification by setting the selector to ON.
499
+ #!
500
+ #! Operand stack inputs: []
501
+ #! Outputs: []
502
+ #!
503
+ #! Notes:
504
+ #! - Sets PSM_SELECTOR_SLOT to PSM_ON (1)
505
+ #! - After this, transactions will require PSM signature verification
506
+ proc.enable_psm
507
+ push.PSM_ON
508
+ # => [PSM_ON]
509
+
510
+ push.PSM_SELECTOR_SLOT
511
+ # => [PSM_SELECTOR_SLOT, PSM_ON]
512
+
513
+ exec.native_account::set_item
514
+ # => [OLD_ROOT]
515
+
516
+ dropw
517
+ # => []
518
+ end
519
+
520
+ #! Disable PSM verification by setting the selector to OFF.
521
+ #!
522
+ #! Operand stack inputs: []
523
+ #! Outputs: []
524
+ #!
525
+ #! Notes:
526
+ #! - Sets PSM_SELECTOR_SLOT to PSM_OFF (0)
527
+ #! - After this, transactions will NOT require PSM signature verification
528
+ proc.disable_psm
529
+ push.PSM_OFF
530
+ # => [PSM_OFF]
531
+
532
+ push.PSM_SELECTOR_SLOT
533
+ # => [PSM_SELECTOR_SLOT, PSM_OFF]
534
+
535
+ exec.native_account::set_item
536
+ # => [OLD_ROOT]
537
+
538
+ dropw
539
+ # => []
540
+ end
541
+
542
+ #! Update the PSM public key.
543
+ #!
544
+ #! Operand stack inputs: []
545
+ #! Advice stack inputs: [PUB_KEY]
546
+ #! - PUB_KEY is the new PSM RPO Falcon 512 public key
547
+ #!
548
+ #! Notes:
549
+ #! - Stores PUB_KEY into PSM_PUBLIC_KEY_MAP_SLOT:
550
+ #! [0, 0, 0, 0] => PSM_PUBLIC_KEY
551
+ #! - To update the key without requiring PSM signature, ensure
552
+ #! PSM_SELECTOR_SLOT = 0 (OFF) before calling this.
553
+ pub proc update_psm_public_key
554
+ exec.disable_psm
555
+ # ------ Update the PSM public key ------
556
+ adv_loadw
557
+ # => [PUB_KEY]
558
+
559
+ push.0.0.0.0
560
+ # => [MAP_KEY, PUB_KEY]
561
+ # Note that MAP_KEY is [0, 0, 0, 0] for a single PSM_KEY
562
+
563
+ push.PSM_PUBLIC_KEY_MAP_SLOT
564
+ # => [index, MAP_KEY, PUB_KEY]
565
+
566
+ exec.native_account::set_map_item
567
+ # => [OLD_MAP_ROOT, OLD_MAP_VALUE]
568
+
569
+ dropw dropw
570
+ # => []
571
+ end
572
+
573
+ #! Conditionally verify a "PSM" signature against a stored public key hash.
574
+ #! The condition is controlled by the selector at PSM_SELECTOR_SLOT.
575
+ #!
576
+ #! Inputs: [MSG]
577
+ #! Outputs: [MSG]
578
+ #!
579
+ #! Panics if:
580
+ #! - Selector is ON but the provided PSM signature is invalid or missing.
581
+ #!
582
+ #! Notes:
583
+ #! - MSG is TX_SUMMARY_COMMITMENT provided by auth procedure
584
+ #! - If selector is OFF (0), PSM verification is skipped
585
+ #! - Selector value is read from initial storage state
586
+ pub proc verify_psm_signature(msg: BeWord)
587
+ push.PSM_SELECTOR_SLOT
588
+ exec.active_account::get_item
589
+ drop drop drop
590
+ # => [selector, MSG]
591
+
592
+ push.1 eq
593
+ if.true
594
+ push.1
595
+ push.PSM_PUBLIC_KEY_MAP_SLOT
596
+ exec.::miden::auth::rpo_falcon512::verify_signatures
597
+ push.1 neq
598
+ if.true
599
+ emit.AUTH_UNAUTHORIZED_EVENT
600
+ push.0 assert.err="invalid PSM signature"
601
+ end
602
+ end
603
+ # => [MSG]
604
+ exec.enable_psm
605
+ end
606
+ `;
@@ -0,0 +1,70 @@
1
+ import type { MultisigConfig } from '../types.js';
2
+ import { StorageSlot, StorageMap, Word } from '@demox-labs/miden-sdk';
3
+ import { ensureHexPrefix } from '../utils/encoding.js';
4
+ import { getProcedureRoot } from '../procedures.js';
5
+
6
+ export class StorageLayoutBuilder {
7
+ buildMultisigSlots(config: MultisigConfig): StorageSlot[] {
8
+ const numSigners = config.signerCommitments.length;
9
+ const slot0Word = new Word(
10
+ new BigUint64Array([
11
+ BigInt(config.threshold),
12
+ BigInt(numSigners),
13
+ 0n,
14
+ 0n,
15
+ ])
16
+ );
17
+ const slot0 = StorageSlot.fromValue(slot0Word);
18
+
19
+ const signersMap = new StorageMap();
20
+ config.signerCommitments.forEach((commitment, index) => {
21
+ const key = new Word(new BigUint64Array([BigInt(index), 0n, 0n, 0n]));
22
+ const value = Word.fromHex(ensureHexPrefix(commitment));
23
+ signersMap.insert(key, value);
24
+ });
25
+ const slot1 = StorageSlot.map(signersMap);
26
+
27
+ const slot2 = StorageSlot.map(new StorageMap());
28
+
29
+ // Map entries: PROC_ROOT => [proc_threshold, 0, 0, 0]
30
+ // Use SDK's Word.fromHex to match how account code procedure roots are represented
31
+ const procThresholdMap = new StorageMap();
32
+ if (config.procedureThresholds) {
33
+ for (const pt of config.procedureThresholds) {
34
+ const rootHex = getProcedureRoot(pt.procedure);
35
+ const key = Word.fromHex(rootHex);
36
+ const value = new Word(new BigUint64Array([BigInt(pt.threshold), 0n, 0n, 0n]));
37
+ procThresholdMap.insert(key, value);
38
+ }
39
+ }
40
+ const slot3 = StorageSlot.map(procThresholdMap);
41
+
42
+ return [slot0, slot1, slot2, slot3];
43
+ }
44
+
45
+ buildPsmSlots(config: MultisigConfig): StorageSlot[] {
46
+ const selector = config.psmEnabled !== false ? 1n : 0n;
47
+ const selectorWord = new Word(new BigUint64Array([selector, 0n, 0n, 0n]));
48
+ const slot0 = StorageSlot.fromValue(selectorWord);
49
+
50
+ const psmKeyMap = new StorageMap();
51
+ const zeroKey = new Word(new BigUint64Array([0n, 0n, 0n, 0n]));
52
+ const psmKey = Word.fromHex(ensureHexPrefix(config.psmCommitment));
53
+ psmKeyMap.insert(zeroKey, psmKey);
54
+ const slot1 = StorageSlot.map(psmKeyMap);
55
+
56
+ return [slot0, slot1];
57
+ }
58
+ }
59
+
60
+ const defaultStorageBuilder = new StorageLayoutBuilder();
61
+
62
+ export function buildMultisigStorageSlots(config: MultisigConfig): StorageSlot[] {
63
+ return defaultStorageBuilder.buildMultisigSlots(config);
64
+ }
65
+
66
+ export function buildPsmStorageSlots(config: MultisigConfig): StorageSlot[] {
67
+ return defaultStorageBuilder.buildPsmSlots(config);
68
+ }
69
+
70
+ export const storageLayoutBuilder = defaultStorageBuilder;