@openzeppelin/miden-multisig-client 0.13.1 → 0.13.3

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 (275) hide show
  1. package/README.md +98 -119
  2. package/dist/account/builder.d.ts +18 -0
  3. package/dist/account/builder.d.ts.map +1 -1
  4. package/dist/account/builder.js +59 -29
  5. package/dist/account/builder.js.map +1 -1
  6. package/dist/account/builder.test.js +108 -32
  7. package/dist/account/builder.test.js.map +1 -1
  8. package/dist/account/index.d.ts +1 -1
  9. package/dist/account/index.d.ts.map +1 -1
  10. package/dist/account/index.js +1 -1
  11. package/dist/account/index.js.map +1 -1
  12. package/dist/account/masm/account-components/auth.d.ts +5 -0
  13. package/dist/account/masm/account-components/auth.d.ts.map +1 -0
  14. package/dist/account/masm/account-components/auth.js +67 -0
  15. package/dist/account/masm/account-components/auth.js.map +1 -0
  16. package/dist/account/masm/auth.d.ts +5 -0
  17. package/dist/account/masm/auth.d.ts.map +1 -0
  18. package/dist/account/masm/auth.js +1395 -0
  19. package/dist/account/masm/auth.js.map +1 -0
  20. package/dist/account/masm/index.d.ts +3 -0
  21. package/dist/account/masm/index.d.ts.map +1 -0
  22. package/dist/account/masm/index.js +5 -0
  23. package/dist/account/masm/index.js.map +1 -0
  24. package/dist/account/masm.d.ts +1 -4
  25. package/dist/account/masm.d.ts.map +1 -1
  26. package/dist/account/masm.js +3 -1034
  27. package/dist/account/masm.js.map +1 -1
  28. package/dist/account/storage.d.ts +2 -2
  29. package/dist/account/storage.d.ts.map +1 -1
  30. package/dist/account/storage.js +18 -16
  31. package/dist/account/storage.js.map +1 -1
  32. package/dist/client.d.ts +67 -9
  33. package/dist/client.d.ts.map +1 -1
  34. package/dist/client.js +73 -22
  35. package/dist/client.js.map +1 -1
  36. package/dist/client.test.js +31 -32
  37. package/dist/client.test.js.map +1 -1
  38. package/dist/index.d.ts +54 -12
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +53 -9
  41. package/dist/index.js.map +1 -1
  42. package/dist/inspector.d.ts +32 -6
  43. package/dist/inspector.d.ts.map +1 -1
  44. package/dist/inspector.js +58 -23
  45. package/dist/inspector.js.map +1 -1
  46. package/dist/inspector.test.js +18 -18
  47. package/dist/inspector.test.js.map +1 -1
  48. package/dist/multisig/proposal/execution.d.ts +4 -4
  49. package/dist/multisig/proposal/execution.d.ts.map +1 -1
  50. package/dist/multisig/proposal/execution.js +42 -31
  51. package/dist/multisig/proposal/execution.js.map +1 -1
  52. package/dist/multisig/proposal/parser.d.ts +3 -3
  53. package/dist/multisig/proposal/parser.d.ts.map +1 -1
  54. package/dist/multisig/proposal/parser.js +23 -23
  55. package/dist/multisig/proposal/parser.js.map +1 -1
  56. package/dist/multisig/signing.d.ts +3 -3
  57. package/dist/multisig/signing.d.ts.map +1 -1
  58. package/dist/multisig/signing.js +3 -3
  59. package/dist/multisig/signing.js.map +1 -1
  60. package/dist/multisig.d.ts +188 -48
  61. package/dist/multisig.d.ts.map +1 -1
  62. package/dist/multisig.js +650 -308
  63. package/dist/multisig.js.map +1 -1
  64. package/dist/multisig.test.js +1680 -241
  65. package/dist/multisig.test.js.map +1 -1
  66. package/dist/procedures.d.ts +46 -24
  67. package/dist/procedures.d.ts.map +1 -1
  68. package/dist/procedures.js +46 -21
  69. package/dist/procedures.js.map +1 -1
  70. package/dist/proposal/factory.d.ts +18 -0
  71. package/dist/proposal/factory.d.ts.map +1 -0
  72. package/dist/proposal/factory.js +99 -0
  73. package/dist/proposal/factory.js.map +1 -0
  74. package/dist/proposal/metadata.d.ts +5 -12
  75. package/dist/proposal/metadata.d.ts.map +1 -1
  76. package/dist/proposal/metadata.js +118 -174
  77. package/dist/proposal/metadata.js.map +1 -1
  78. package/dist/proposal/signatures.d.ts +10 -0
  79. package/dist/proposal/signatures.d.ts.map +1 -0
  80. package/dist/proposal/signatures.js +54 -0
  81. package/dist/proposal/signatures.js.map +1 -0
  82. package/dist/signer.d.ts +2 -0
  83. package/dist/signer.d.ts.map +1 -1
  84. package/dist/signer.js +2 -0
  85. package/dist/signer.js.map +1 -1
  86. package/dist/signers/ecdsa.d.ts +3 -0
  87. package/dist/signers/ecdsa.d.ts.map +1 -1
  88. package/dist/signers/ecdsa.js +8 -4
  89. package/dist/signers/ecdsa.js.map +1 -1
  90. package/dist/signers/ecdsa.test.d.ts +2 -0
  91. package/dist/signers/ecdsa.test.d.ts.map +1 -0
  92. package/dist/signers/ecdsa.test.js +65 -0
  93. package/dist/signers/ecdsa.test.js.map +1 -0
  94. package/dist/signers/falcon.d.ts +3 -0
  95. package/dist/signers/falcon.d.ts.map +1 -1
  96. package/dist/signers/falcon.js +8 -4
  97. package/dist/signers/falcon.js.map +1 -1
  98. package/dist/signers/falcon.test.js +12 -0
  99. package/dist/signers/falcon.test.js.map +1 -1
  100. package/dist/signers/miden-wallet.d.ts +3 -1
  101. package/dist/signers/miden-wallet.d.ts.map +1 -1
  102. package/dist/signers/miden-wallet.js +11 -2
  103. package/dist/signers/miden-wallet.js.map +1 -1
  104. package/dist/signers/miden-wallet.test.js +26 -0
  105. package/dist/signers/miden-wallet.test.js.map +1 -1
  106. package/dist/signers/para.d.ts +3 -3
  107. package/dist/signers/para.d.ts.map +1 -1
  108. package/dist/signers/para.js +15 -6
  109. package/dist/signers/para.js.map +1 -1
  110. package/dist/signers/para.test.js +39 -0
  111. package/dist/signers/para.test.js.map +1 -1
  112. package/dist/transaction/consumeNotes.d.ts.map +1 -1
  113. package/dist/transaction/consumeNotes.js +1 -0
  114. package/dist/transaction/consumeNotes.js.map +1 -1
  115. package/dist/transaction/index.d.ts +2 -1
  116. package/dist/transaction/index.d.ts.map +1 -1
  117. package/dist/transaction/index.js +2 -1
  118. package/dist/transaction/index.js.map +1 -1
  119. package/dist/transaction/options.d.ts +1 -1
  120. package/dist/transaction/options.d.ts.map +1 -1
  121. package/dist/transaction/p2id.d.ts +1 -0
  122. package/dist/transaction/p2id.d.ts.map +1 -1
  123. package/dist/transaction/p2id.js +9 -3
  124. package/dist/transaction/p2id.js.map +1 -1
  125. package/dist/transaction/updateGuardian.d.ts +7 -0
  126. package/dist/transaction/updateGuardian.d.ts.map +1 -0
  127. package/dist/transaction/{updatePsm.js → updateGuardian.js} +15 -29
  128. package/dist/transaction/updateGuardian.js.map +1 -0
  129. package/dist/transaction/updateProcedureThreshold.d.ts +9 -0
  130. package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -0
  131. package/dist/transaction/updateProcedureThreshold.js +55 -0
  132. package/dist/transaction/updateProcedureThreshold.js.map +1 -0
  133. package/dist/transaction/updateSigners.d.ts.map +1 -1
  134. package/dist/transaction/updateSigners.js +5 -23
  135. package/dist/transaction/updateSigners.js.map +1 -1
  136. package/dist/transaction.d.ts +2 -1
  137. package/dist/transaction.d.ts.map +1 -1
  138. package/dist/transaction.js +2 -1
  139. package/dist/transaction.js.map +1 -1
  140. package/dist/types/proposal.d.ts +32 -15
  141. package/dist/types/proposal.d.ts.map +1 -1
  142. package/dist/types.d.ts +24 -7
  143. package/dist/types.d.ts.map +1 -1
  144. package/dist/utils/digest.d.ts +5 -0
  145. package/dist/utils/digest.d.ts.map +1 -1
  146. package/dist/utils/digest.js +34 -1
  147. package/dist/utils/digest.js.map +1 -1
  148. package/dist/utils/digest.test.d.ts +2 -0
  149. package/dist/utils/digest.test.d.ts.map +1 -0
  150. package/dist/utils/digest.test.js +48 -0
  151. package/dist/utils/digest.test.js.map +1 -0
  152. package/dist/utils/signature.d.ts +3 -1
  153. package/dist/utils/signature.d.ts.map +1 -1
  154. package/dist/utils/signature.js +31 -6
  155. package/dist/utils/signature.js.map +1 -1
  156. package/dist/utils/word.test.js +1 -52
  157. package/dist/utils/word.test.js.map +1 -1
  158. package/masm/account_components/auth/multisig.masm +13 -0
  159. package/masm/account_components/auth/multisig_ecdsa.masm +13 -0
  160. package/masm/account_components/auth/multisig_guardian.masm +17 -0
  161. package/masm/account_components/auth/multisig_guardian_ecdsa.masm +17 -0
  162. package/masm/auth/guardian.masm +177 -0
  163. package/masm/auth/guardian_ecdsa.masm +178 -0
  164. package/masm/{multisig.masm → auth/multisig.masm} +149 -55
  165. package/masm/{multisig_ecdsa.masm → auth/multisig_ecdsa.masm} +148 -56
  166. package/package.json +11 -9
  167. package/src/account/builder.test.ts +165 -0
  168. package/src/account/builder.ts +71 -34
  169. package/src/account/index.ts +1 -1
  170. package/src/account/masm/account-components/auth.ts +70 -0
  171. package/src/account/masm/auth.ts +1398 -0
  172. package/src/account/masm/index.ts +5 -0
  173. package/src/account/masm.ts +3 -1040
  174. package/src/account/storage.ts +18 -16
  175. package/src/client.test.ts +31 -35
  176. package/src/client.ts +98 -26
  177. package/src/index.ts +84 -22
  178. package/src/inspector.test.ts +17 -17
  179. package/src/inspector.ts +63 -29
  180. package/src/multisig/proposal/execution.ts +66 -49
  181. package/src/multisig/proposal/parser.ts +26 -26
  182. package/src/multisig/signing.ts +4 -4
  183. package/src/multisig.test.ts +2097 -248
  184. package/src/multisig.ts +882 -388
  185. package/src/procedures.ts +50 -26
  186. package/src/proposal/factory.ts +171 -0
  187. package/src/proposal/metadata.ts +165 -0
  188. package/src/proposal/signatures.ts +68 -0
  189. package/src/signer.ts +2 -0
  190. package/src/signers/ecdsa.test.ts +80 -0
  191. package/src/signers/ecdsa.ts +15 -4
  192. package/src/signers/falcon.test.ts +19 -0
  193. package/src/signers/falcon.ts +16 -5
  194. package/src/signers/miden-wallet.test.ts +47 -0
  195. package/src/signers/miden-wallet.ts +18 -1
  196. package/src/signers/para.test.ts +57 -0
  197. package/src/signers/para.ts +26 -7
  198. package/src/transaction/consumeNotes.ts +1 -0
  199. package/src/transaction/index.ts +2 -2
  200. package/src/transaction/options.ts +1 -2
  201. package/src/transaction/p2id.test.ts +190 -0
  202. package/src/transaction/p2id.ts +11 -5
  203. package/src/transaction/rpoRandomCoin.test.ts +64 -0
  204. package/src/transaction/rpoRandomCoin.ts +24 -0
  205. package/src/transaction/{updatePsm.ts → updateGuardian.ts} +19 -32
  206. package/src/transaction/updateProcedureThreshold.ts +97 -0
  207. package/src/transaction/updateSigners.ts +12 -27
  208. package/src/transaction.ts +5 -3
  209. package/src/types/proposal.ts +43 -11
  210. package/src/types.ts +29 -11
  211. package/src/utils/digest.test.ts +55 -0
  212. package/src/utils/digest.ts +51 -1
  213. package/src/utils/signature.ts +47 -9
  214. package/src/utils/word.test.ts +1 -55
  215. package/dist/account.d.ts +0 -104
  216. package/dist/account.d.ts.map +0 -1
  217. package/dist/account.js +0 -136
  218. package/dist/account.js.map +0 -1
  219. package/dist/builder.d.ts +0 -54
  220. package/dist/builder.d.ts.map +0 -1
  221. package/dist/builder.js +0 -78
  222. package/dist/builder.js.map +0 -1
  223. package/dist/http.d.ts +0 -64
  224. package/dist/http.d.ts.map +0 -1
  225. package/dist/http.js +0 -162
  226. package/dist/http.js.map +0 -1
  227. package/dist/keystore.d.ts +0 -65
  228. package/dist/keystore.d.ts.map +0 -1
  229. package/dist/keystore.js +0 -121
  230. package/dist/keystore.js.map +0 -1
  231. package/dist/miden.d.ts +0 -9
  232. package/dist/miden.d.ts.map +0 -1
  233. package/dist/miden.js +0 -26
  234. package/dist/miden.js.map +0 -1
  235. package/dist/multisig/metadata.d.ts +0 -18
  236. package/dist/multisig/metadata.d.ts.map +0 -1
  237. package/dist/multisig/metadata.js +0 -100
  238. package/dist/multisig/metadata.js.map +0 -1
  239. package/dist/multisig/metadata.test.d.ts +0 -2
  240. package/dist/multisig/metadata.test.d.ts.map +0 -1
  241. package/dist/multisig/metadata.test.js +0 -323
  242. package/dist/multisig/metadata.test.js.map +0 -1
  243. package/dist/proposal/exported.d.ts +0 -12
  244. package/dist/proposal/exported.d.ts.map +0 -1
  245. package/dist/proposal/exported.js +0 -88
  246. package/dist/proposal/exported.js.map +0 -1
  247. package/dist/signer.test.d.ts +0 -2
  248. package/dist/signer.test.d.ts.map +0 -1
  249. package/dist/signer.test.js +0 -123
  250. package/dist/signer.test.js.map +0 -1
  251. package/dist/transaction/p2id.interop.test.d.ts +0 -2
  252. package/dist/transaction/p2id.interop.test.d.ts.map +0 -1
  253. package/dist/transaction/p2id.interop.test.js +0 -36
  254. package/dist/transaction/p2id.interop.test.js.map +0 -1
  255. package/dist/transaction/updatePsm.d.ts +0 -7
  256. package/dist/transaction/updatePsm.d.ts.map +0 -1
  257. package/dist/transaction/updatePsm.js.map +0 -1
  258. package/dist/transport/http.d.ts +0 -64
  259. package/dist/transport/http.d.ts.map +0 -1
  260. package/dist/transport/http.js +0 -162
  261. package/dist/transport/http.js.map +0 -1
  262. package/dist/transport/index.d.ts +0 -5
  263. package/dist/transport/index.d.ts.map +0 -1
  264. package/dist/transport/index.js +0 -5
  265. package/dist/transport/index.js.map +0 -1
  266. package/dist/utils/state-validation.d.ts +0 -5
  267. package/dist/utils/state-validation.d.ts.map +0 -1
  268. package/dist/utils/state-validation.js +0 -30
  269. package/dist/utils/state-validation.js.map +0 -1
  270. package/dist/utils/validation.d.ts +0 -6
  271. package/dist/utils/validation.d.ts.map +0 -1
  272. package/dist/utils/validation.js +0 -52
  273. package/dist/utils/validation.js.map +0 -1
  274. package/masm/psm.masm +0 -178
  275. package/masm/psm_ecdsa.masm +0 -179
@@ -0,0 +1,1398 @@
1
+ // This file is generated by scripts/generate-masm.mjs from the MASM contract sources.
2
+ // Do not edit it by hand.
3
+
4
+ export const MULTISIG_MASM = `# Multi-Signature RPO Falcon 512 Authentication Core Library
5
+ #
6
+ # This library provides the reusable multisig procedures used by account wrappers.
7
+
8
+ use miden::protocol::active_account
9
+ use miden::protocol::native_account
10
+ use miden::standards::auth
11
+
12
+ type BeWord = struct @bigendian { a: felt, b: felt, c: felt, d: felt }
13
+
14
+ # CONSTANTS
15
+ # =================================================================================================
16
+
17
+ # Auth Request Constants
18
+
19
+ # The event emitted when a signature is not found for a required signer.
20
+ const AUTH_UNAUTHORIZED_EVENT=event("miden::auth::unauthorized")
21
+
22
+ # Storage Layout Constants
23
+ #
24
+ # +-------------------------------+----------+--------------+-------------------+
25
+ # | THRESHOLD & APPROVERS CONFIG | PUB KEYS | EXECUTED TXS | PROC THRESHOLDS |
26
+ # | (slot) | (map) | (map) | (map) |
27
+ # +-------------------------------+----------+--------------+-------------------+
28
+ # | 0 | 1 | 2 | 3 |
29
+ # +-------------------------------+----------+--------------+-------------------+
30
+
31
+ # The slot in this component's storage layout where the default signature threshold and
32
+ # number of approvers are stored as:
33
+ # [default_threshold, num_approvers, 0, 0].
34
+ # The threshold is guaranteed to be less than or equal to num_approvers.
35
+ const THRESHOLD_CONFIG_SLOT=word("openzeppelin::multisig::threshold_config")
36
+
37
+ # The slot in this component's storage layout where the public keys map is stored.
38
+ # Map entries: [key_index, 0, 0, 0] => APPROVER_PUBLIC_KEY
39
+ const PUBLIC_KEYS_MAP_SLOT=word("openzeppelin::multisig::signer_public_keys")
40
+
41
+ # The slot in this component's storage layout where executed transactions are stored.
42
+ # Map entries: transaction_message => [is_executed, 0, 0, 0]
43
+ const EXECUTED_TXS_SLOT=word("openzeppelin::multisig::executed_transactions")
44
+
45
+ # The slot in this component's storage layout where procedure thresholds are stored.
46
+ # Map entries: PROC_ROOT => [proc_threshold, 0, 0, 0]
47
+ const PROC_THRESHOLD_ROOTS_SLOT=word("openzeppelin::multisig::procedure_thresholds")
48
+
49
+ # Executed Transaction Flag Constant
50
+ const IS_EXECUTED_FLAG=[1, 0, 0, 0]
51
+
52
+ # ERRORS
53
+ const ERR_TX_ALREADY_EXECUTED="failed to approve multisig transaction as it was already executed"
54
+
55
+ const ERR_MALFORMED_MULTISIG_CONFIG="number of approvers must be equal to or greater than threshold"
56
+
57
+ const ERR_ZERO_IN_MULTISIG_CONFIG="number of approvers or threshold must not be zero"
58
+
59
+ const ERR_PROC_THRESHOLD_NOT_U32="procedure threshold must be a u32 value"
60
+
61
+ const ERR_NUM_APPROVERS_OR_PROC_THRESHOLD_NOT_U32="number of approvers and procedure threshold must be u32"
62
+
63
+ const ERR_PROC_THRESHOLD_EXCEEDS_NUM_APPROVERS="procedure threshold exceeds number of approvers"
64
+
65
+ # MULTISIG PROCEDURES
66
+ # =================================================================================================
67
+
68
+ #! Check if transaction has already been executed and add it to executed transactions for replay protection.
69
+ #!
70
+ #! Inputs: [MSG]
71
+ #! Outputs: []
72
+ #!
73
+ #! Panics if:
74
+ #! - the same transaction has already been executed
75
+ pub proc assert_new_tx(msg: BeWord)
76
+ push.IS_EXECUTED_FLAG
77
+ # => [[0, 0, 0, is_executed], MSG]
78
+
79
+ swapw
80
+ # => [MSG, IS_EXECUTED_FLAG]
81
+
82
+ push.EXECUTED_TXS_SLOT[0..2]
83
+ # => [txs_slot_prefix, txs_slot_suffix, MSG, IS_EXECUTED_FLAG]
84
+
85
+ # Set the key value pair in the map to mark transaction as executed
86
+ exec.native_account::set_map_item
87
+ # => [[0, 0, 0, is_executed]]
88
+
89
+ drop drop drop
90
+ # => [is_executed]
91
+
92
+ assertz.err=ERR_TX_ALREADY_EXECUTED
93
+ # => []
94
+ end
95
+
96
+ #! Remove old approver public keys from the approver public key mapping.
97
+ #!
98
+ #! This procedure cleans up the storage by removing public keys of approvers that are no longer
99
+ #! part of the multisig configuration. This procedure assumes that init_num_of_approvers and
100
+ #! new_num_of_approvers are u32 values.
101
+ #!
102
+ #! Inputs: [init_num_of_approvers, new_num_of_approvers]
103
+ #! Outputs: []
104
+ #!
105
+ #! Where:
106
+ #! - init_num_of_approvers is the original number of approvers before the update
107
+ #! - new_num_of_approvers is the new number of approvers after the update
108
+ proc cleanup_pubkey_mapping(init_num_of_approvers: u32, new_num_of_approvers: u32)
109
+ dup.1 dup.1
110
+ u32assert2 u32lt
111
+ # => [should_loop, i = init_num_of_approvers, new_num_of_approvers]
112
+
113
+ while.true
114
+ # => [i, new_num_of_approvers]
115
+
116
+ sub.1
117
+ # => [i-1, new_num_of_approvers]
118
+
119
+ dup
120
+ # => [i-1, i-1, new_num_of_approvers]
121
+
122
+ push.0.0.0
123
+ # => [[0, 0, 0, i-1], i-1, new_num_of_approvers]
124
+
125
+ padw swapw
126
+ # => [[0, 0, 0, i-1], EMPTY_WORD, i-1, new_num_of_approvers]
127
+
128
+ push.PUBLIC_KEYS_MAP_SLOT[0..2]
129
+ # => [pub_key_slot_prefix, pub_key_slot_suffix, [0, 0, 0, i-1], EMPTY_WORD, i-1, new_num_of_approvers]
130
+
131
+ exec.native_account::set_map_item
132
+ # => [OLD_MAP_VALUE, i-1, new_num_of_approvers]
133
+
134
+ dropw
135
+ # => [i-1, new_num_of_approvers]
136
+
137
+ dup.1 dup.1
138
+ u32lt
139
+ # => [should_loop, i-1, new_num_of_approvers]
140
+ end
141
+
142
+ drop drop
143
+ # => []
144
+ end
145
+
146
+ proc get_threshold_and_num_approvers
147
+ push.THRESHOLD_CONFIG_SLOT[0..2]
148
+ exec.active_account::get_initial_item
149
+ # => [0, 0, num_approvers, threshold]
150
+
151
+ drop drop
152
+ # => [num_approvers, threshold]
153
+
154
+ swap
155
+ # => [threshold, num_approvers]
156
+ end
157
+
158
+ proc assert_proc_thresholds_lte_num_approvers(num_approvers: u32)
159
+ exec.active_account::get_num_procedures
160
+ # => [num_procedures, num_approvers]
161
+
162
+ dup neq.0
163
+ # => [should_continue, num_procedures, num_approvers]
164
+ while.true
165
+ sub.1 dup
166
+ # => [proc_index, proc_index, num_approvers]
167
+
168
+ exec.active_account::get_procedure_root
169
+ # => [PROC_ROOT, proc_index, num_approvers]
170
+
171
+ push.PROC_THRESHOLD_ROOTS_SLOT[0..2]
172
+ # => [proc_roots_slot_prefix, proc_roots_slot_suffix, PROC_ROOT, proc_index, num_approvers]
173
+
174
+ exec.active_account::get_initial_map_item
175
+ # => [[0, 0, 0, proc_threshold], proc_index, num_approvers]
176
+
177
+ drop drop drop
178
+ # => [proc_threshold, proc_index, num_approvers]
179
+
180
+ dup.2
181
+ # => [num_approvers, proc_threshold, proc_index, num_approvers]
182
+
183
+ u32assert2.err=ERR_PROC_THRESHOLD_NOT_U32
184
+ u32gt assertz.err=ERR_PROC_THRESHOLD_EXCEEDS_NUM_APPROVERS
185
+ # => [proc_index, num_approvers]
186
+
187
+ dup neq.0
188
+ # => [should_continue, proc_index, num_approvers]
189
+ end
190
+
191
+ drop drop
192
+ # => []
193
+ end
194
+
195
+ # Internal storage helper used by the public \`update_procedure_threshold\`
196
+ # entrypoint after it receives direct stack arguments.
197
+ proc set_procedure_threshold(proc_threshold: u32, proc_root: BeWord)
198
+ exec.get_threshold_and_num_approvers
199
+ # => [threshold, num_approvers, proc_threshold, PROC_ROOT]
200
+
201
+ drop
202
+ # => [num_approvers, proc_threshold, PROC_ROOT]
203
+
204
+ dup.1 swap
205
+ # => [num_approvers, proc_threshold, proc_threshold, PROC_ROOT]
206
+
207
+ u32assert2.err=ERR_NUM_APPROVERS_OR_PROC_THRESHOLD_NOT_U32
208
+ u32gt assertz.err=ERR_PROC_THRESHOLD_EXCEEDS_NUM_APPROVERS
209
+ # => [proc_threshold, PROC_ROOT]
210
+
211
+ push.0.0.0
212
+ swapw
213
+ # => [PROC_ROOT, PROC_THRESHOLD_WORD]
214
+
215
+ push.PROC_THRESHOLD_ROOTS_SLOT[0..2]
216
+ # => [proc_roots_slot_prefix, proc_roots_slot_suffix, PROC_ROOT, PROC_THRESHOLD_WORD]
217
+
218
+ exec.native_account::set_map_item
219
+ # => [OLD_VALUE]
220
+
221
+ dropw
222
+ # => []
223
+ end
224
+
225
+ #! Update threshold config and add / remove approvers
226
+ #!
227
+ #! Inputs:
228
+ #! Operand stack: [MULTISIG_CONFIG_HASH, pad(12)]
229
+ #! Advice map: {
230
+ #! MULTISIG_CONFIG_HASH => [CONFIG, PUB_KEY_N, PUB_KEY_N-1, ..., PUB_KEY_0]
231
+ #! }
232
+ #! Outputs:
233
+ #! Operand stack: [TX_SUMMARY_COMMITMENT]
234
+ #!
235
+ #! Where:
236
+ #! - MULTISIG_CONFIG_HASH is the hash of the threshold and new public key vector
237
+ #! - MULTISIG_CONFIG is [threshold, num_approvers, 0, 0]
238
+ #! - PUB_KEY_i is the public key of the i-th signer
239
+ #!
240
+ #! Locals:
241
+ #! 0: new_num_of_approvers
242
+ #! 1: init_num_of_approvers
243
+ @locals(2)
244
+ pub proc update_signers_and_threshold(multisig_config_hash: BeWord)
245
+ adv.push_mapval
246
+ # => [MULTISIG_CONFIG_HASH, pad(12)]
247
+
248
+ adv_loadw
249
+ # => [MULTISIG_CONFIG, pad(12)]
250
+
251
+ # store new_num_of_approvers for later
252
+ dup.2 loc_store.0
253
+ # => [MULTISIG_CONFIG, pad(12)]
254
+
255
+ dup.3 dup.3
256
+ # => [num_approvers, threshold, MULTISIG_CONFIG, pad(12)]
257
+
258
+ # make sure that the threshold is smaller than the number of approvers
259
+ u32assert2.err=ERR_MALFORMED_MULTISIG_CONFIG
260
+ u32gt assertz.err=ERR_MALFORMED_MULTISIG_CONFIG
261
+ # => [MULTISIG_CONFIG, pad(12)]
262
+
263
+ dup.3 dup.3
264
+ # => [num_approvers, threshold, MULTISIG_CONFIG, pad(12)]
265
+
266
+ # make sure that threshold or num_approvers are not zero
267
+ eq.0 assertz.err=ERR_ZERO_IN_MULTISIG_CONFIG
268
+ eq.0 assertz.err=ERR_ZERO_IN_MULTISIG_CONFIG
269
+ # => [MULTISIG_CONFIG, pad(12)]
270
+
271
+ loc_load.0
272
+ # => [num_approvers, MULTISIG_CONFIG, pad(12)]
273
+
274
+ exec.assert_proc_thresholds_lte_num_approvers
275
+ # => [MULTISIG_CONFIG, pad(12)]
276
+
277
+ push.THRESHOLD_CONFIG_SLOT[0..2]
278
+ # => [config_slot_prefix, config_slot_suffix, MULTISIG_CONFIG, pad(12)]
279
+
280
+ exec.native_account::set_item
281
+ # => [OLD_THRESHOLD_CONFIG, pad(12)]
282
+
283
+ # store init_num_of_approvers for later
284
+ drop drop loc_store.1 drop
285
+ # => [pad(12)]
286
+
287
+ loc_load.0
288
+ # => [num_approvers]
289
+
290
+ dup neq.0
291
+ while.true
292
+ sub.1
293
+ # => [i-1, pad(12)]
294
+
295
+ dup push.0.0.0
296
+ # => [[0, 0, 0, i-1], i-1, pad(12)]
297
+
298
+ padw adv_loadw
299
+ # => [PUB_KEY, [0, 0, 0, i-1], i-1, pad(12)]
300
+
301
+ swapw
302
+ # => [[0, 0, 0, i-1], PUB_KEY, i-1, pad(12)]
303
+
304
+ push.PUBLIC_KEYS_MAP_SLOT[0..2]
305
+ # => [pub_key_slot_prefix, pub_key_slot_suffix, [0, 0, 0, i-1], PUB_KEY, i-1, pad(12)]
306
+
307
+ exec.native_account::set_map_item
308
+ # => [OLD_VALUE, i-1, pad(12)]
309
+
310
+ dropw
311
+ # => [i-1, pad(12)]
312
+
313
+ dup neq.0
314
+ # => [is_non_zero, i-1, pad(12)]
315
+ end
316
+ # => [pad(13)]
317
+
318
+ drop
319
+ # => [pad(12)]
320
+
321
+ # compare initial vs current multisig config
322
+
323
+ # load init_num_of_approvers & new_num_of_approvers
324
+ loc_load.0 loc_load.1
325
+ # => [init_num_of_approvers, new_num_of_approvers, pad(12)]
326
+
327
+ exec.cleanup_pubkey_mapping
328
+ # => [pad(12)]
329
+ end
330
+
331
+ #! Update or clear the threshold override for a specific procedure.
332
+ #!
333
+ #! Inputs:
334
+ #! Operand stack: [proc_threshold, PROC_ROOT]
335
+ #! Outputs:
336
+ #! Operand stack: []
337
+ #! Invocation: call
338
+ pub proc update_procedure_threshold(proc_threshold: u32, proc_root: BeWord)
339
+ exec.set_procedure_threshold
340
+ end
341
+
342
+ # Computes the effective transaction threshold based on called procedures and per-procedure
343
+ # overrides stored in PROC_THRESHOLD_ROOTS_SLOT. Falls back to default_threshold if no
344
+ # overrides apply.
345
+ #
346
+ #! Inputs: [default_threshold]
347
+ #! Outputs: [transaction_threshold]
348
+ @locals(1)
349
+ proc compute_transaction_threshold(default_threshold: u32) -> u32
350
+ # 1. initialize transaction_threshold = 0
351
+ # 2. iterate through all account procedures
352
+ # a. check if the procedure was called during the transaction
353
+ # b. if called, get the override threshold of that procedure from the config map
354
+ # c. if proc_threshold > transaction_threshold, set transaction_threshold = proc_threshold
355
+ # 3. if transaction_threshold == 0 at the end, revert to using default_threshold
356
+
357
+ # store default_threshold for later
358
+ loc_store.0
359
+ # => []
360
+
361
+ # 1. initialize transaction_threshold = 0
362
+ push.0
363
+ # => [transaction_threshold]
364
+
365
+ # get the number of account procedures
366
+ exec.active_account::get_num_procedures
367
+ # => [num_procedures, transaction_threshold]
368
+
369
+ # 2. iterate through all account procedures
370
+ dup neq.0
371
+ # => [should_continue, num_procedures, transaction_threshold]
372
+ while.true
373
+ sub.1 dup
374
+ # => [num_procedures-1, num_procedures-1, transaction_threshold]
375
+
376
+ # get procedure root of the procedure with index i
377
+ exec.active_account::get_procedure_root dupw
378
+ # => [PROC_ROOT, PROC_ROOT, num_procedures-1, transaction_threshold]
379
+
380
+ # 2a. check if this procedure has been called in the transaction
381
+ exec.native_account::was_procedure_called
382
+ # => [was_called, PROC_ROOT, num_procedures-1, transaction_threshold]
383
+
384
+ # if it has been called, get the override threshold of that procedure
385
+ if.true
386
+ # => [PROC_ROOT, num_procedures-1, transaction_threshold]
387
+
388
+ push.PROC_THRESHOLD_ROOTS_SLOT[0..2]
389
+ # => [PROC_THRESHOLD_ROOTS_SLOT_prefix, PROC_THRESHOLD_ROOTS_SLOT_suffix, PROC_ROOT, num_procedures-1, transaction_threshold]
390
+
391
+ # 2b. get the override proc_threshold of that procedure
392
+ # if the procedure has no override threshold, the returned map item will be [0, 0, 0, 0]
393
+ exec.active_account::get_initial_map_item
394
+ # => [[0, 0, 0, proc_threshold], num_procedures-1, transaction_threshold]
395
+
396
+ drop drop drop dup dup.3
397
+ # => [transaction_threshold, proc_threshold, proc_threshold, num_procedures-1, transaction_threshold]
398
+
399
+ u32assert2.err="transaction threshold or procedure threshold are not u32"
400
+ u32gt
401
+ # => [is_gt, proc_threshold, num_procedures-1, transaction_threshold]
402
+ # 2c. if proc_threshold > transaction_threshold, update transaction_threshold
403
+ movup.2 movdn.3
404
+ # => [is_gt, proc_threshold, transaction_threshold, num_procedures-1]
405
+ cdrop
406
+ # => [updated_transaction_threshold, num_procedures-1]
407
+ swap
408
+ # => [num_procedures-1, updated_transaction_threshold]
409
+ # if it has not been called during this transaction, nothing to do, move to the next procedure
410
+ else
411
+ dropw
412
+ # => [num_procedures-1, transaction_threshold]
413
+ end
414
+
415
+ dup neq.0
416
+ # => [should_continue, num_procedures-1, transaction_threshold]
417
+ end
418
+
419
+ drop
420
+ # => [transaction_threshold]
421
+
422
+ loc_load.0
423
+ # => [default_threshold, transaction_threshold]
424
+
425
+ # 3. if transaction_threshold == 0 at the end, revert to using default_threshold
426
+ dup.1 eq.0
427
+ # => [is_zero, default_threshold, transaction_threshold]
428
+
429
+ cdrop
430
+ # => [effective_transaction_threshold]
431
+ end
432
+
433
+ #! Authenticate a transaction using the Falcon signature scheme with multi-signature support.
434
+ #!
435
+ #! This procedure implements multi-signature authentication by:
436
+ #! 1. Computing the transaction summary message that needs to be signed
437
+ #! 2. Verifying signatures from multiple required signers against their public keys
438
+ #! 3. Ensuring the minimum threshold of valid signatures is met
439
+ #! 4. Returning the transaction summary commitment for higher-level wrappers.
440
+ #!
441
+ #! Inputs:
442
+ #! Operand stack: [SALT]
443
+ #! Advice map: {
444
+ #! h(SIG_0, MSG): SIG_0,
445
+ #! h(SIG_1, MSG): SIG_1,
446
+ #! h(SIG_n, MSG): SIG_n
447
+ #! }
448
+ #! Outputs:
449
+ #! Operand stack: []
450
+ #!
451
+ #! Where:
452
+ #! - SALT is a cryptographically random nonce that enables multiple concurrent
453
+ #! multisig transactions while maintaining replay protection. Each transaction
454
+ #! must use a unique SALT value to ensure transaction uniqueness.
455
+ #! - SIG_i is the signature from the i-th signer.
456
+ #! - MSG is the transaction message being signed.
457
+ #! - h(SIG_i, MSG) is the hash of the signature and message used as the advice map key.
458
+ #!
459
+ #! Panics if:
460
+ #! - insufficient number of valid signatures (below threshold).
461
+ #! - insufficient number of valid signatures (below threshold).
462
+ #!
463
+ #! Invocation: exec
464
+ @locals(1)
465
+ pub proc auth_tx(salt: BeWord) -> BeWord
466
+ exec.native_account::incr_nonce drop
467
+ # => [SALT]
468
+
469
+ # ------ Computing transaction summary ------
470
+
471
+ exec.auth::create_tx_summary
472
+ # => [SALT, OUTPUT_NOTES_COMMITMENT, INPUT_NOTES_COMMITMENT, ACCOUNT_DELTA_COMMITMENT]
473
+
474
+ # to build a tx_summary in the host, we need these four words in the advice provider
475
+ exec.auth::adv_insert_hqword
476
+ # => [SALT, OUTPUT_NOTES_COMMITMENT, INPUT_NOTES_COMMITMENT, ACCOUNT_DELTA_COMMITMENT]
477
+
478
+ # the commitment to the tx summary is the message that is signed
479
+ exec.auth::hash_tx_summary
480
+ # => [TX_SUMMARY_COMMITMENT]
481
+
482
+ # ------ Verifying approver signatures ------
483
+
484
+ push.THRESHOLD_CONFIG_SLOT[0..2]
485
+ # => [config_slot_prefix, config_slot_suffix, TX_SUMMARY_COMMITMENT]
486
+
487
+ exec.active_account::get_initial_item
488
+ # => [0, 0, num_of_approvers, default_threshold, TX_SUMMARY_COMMITMENT]
489
+
490
+ drop drop
491
+ # => [num_of_approvers, default_threshold, TX_SUMMARY_COMMITMENT]
492
+
493
+ swap movdn.5
494
+ # => [num_of_approvers, TX_SUMMARY_COMMITMENT, default_threshold]
495
+
496
+ push.PUBLIC_KEYS_MAP_SLOT[0..2]
497
+ # => [pub_key_slot_prefix, pub_key_slot_suffix, num_of_approvers, TX_SUMMARY_COMMITMENT, default_threshold]
498
+
499
+ exec.::miden::standards::auth::falcon512_rpo::verify_signatures
500
+ # => [num_verified_signatures, TX_SUMMARY_COMMITMENT, default_threshold]
501
+
502
+ # ------ Checking threshold is >= num_verified_signatures ------
503
+
504
+ movup.5
505
+ # => [default_threshold, num_verified_signatures, TX_SUMMARY_COMMITMENT]
506
+
507
+ exec.compute_transaction_threshold
508
+ # => [transaction_threshold, num_verified_signatures, TX_SUMMARY_COMMITMENT]
509
+
510
+ u32assert2 u32lt
511
+ # => [is_unauthorized, TX_SUMMARY_COMMITMENT]
512
+
513
+ # If signatures are non-existent the tx will fail here.
514
+ if.true
515
+ emit.AUTH_UNAUTHORIZED_EVENT
516
+ push.0 assert.err="insufficient number of signatures"
517
+ end
518
+
519
+ # => [TX_SUMMARY_COMMITMENT]
520
+ end
521
+ `;
522
+
523
+ export const MULTISIG_ECDSA_MASM = `# Multi-Signature ECDSA secp256k1 Authentication Core Library
524
+ #
525
+ # This library provides the reusable multisig procedures used by account wrappers.
526
+
527
+ use miden::protocol::active_account
528
+ use miden::protocol::native_account
529
+ use miden::standards::auth
530
+ type BeWord = struct @bigendian { a: felt, b: felt, c: felt, d: felt }
531
+
532
+ # CONSTANTS
533
+ # =================================================================================================
534
+
535
+ # Auth Request Constants
536
+
537
+ # The event emitted when a signature is not found for a required signer.
538
+ const AUTH_UNAUTHORIZED_EVENT=event("miden::auth::unauthorized")
539
+
540
+ # Storage Layout Constants
541
+ #
542
+ # +-------------------------------+----------+--------------+-------------------+
543
+ # | THRESHOLD & APPROVERS CONFIG | PUB KEYS | EXECUTED TXS | PROC THRESHOLDS |
544
+ # | (slot) | (map) | (map) | (map) |
545
+ # +-------------------------------+----------+--------------+-------------------+
546
+ # | 0 | 1 | 2 | 3 |
547
+ # +-------------------------------+----------+--------------+-------------------+
548
+
549
+ # The slot in this component's storage layout where the default signature threshold and
550
+ # number of approvers are stored as:
551
+ # [default_threshold, num_approvers, 0, 0].
552
+ # The threshold is guaranteed to be less than or equal to num_approvers.
553
+ const THRESHOLD_CONFIG_SLOT=word("openzeppelin::multisig::threshold_config")
554
+
555
+ # The slot in this component's storage layout where the public keys map is stored.
556
+ # Map entries: [key_index, 0, 0, 0] => APPROVER_PUBLIC_KEY
557
+ const PUBLIC_KEYS_MAP_SLOT=word("openzeppelin::multisig::signer_public_keys")
558
+
559
+ # The slot in this component's storage layout where executed transactions are stored.
560
+ # Map entries: transaction_message => [is_executed, 0, 0, 0]
561
+ const EXECUTED_TXS_SLOT=word("openzeppelin::multisig::executed_transactions")
562
+
563
+ # The slot in this component's storage layout where procedure thresholds are stored.
564
+ # Map entries: PROC_ROOT => [proc_threshold, 0, 0, 0]
565
+ const PROC_THRESHOLD_ROOTS_SLOT=word("openzeppelin::multisig::procedure_thresholds")
566
+
567
+ # Executed Transaction Flag Constant
568
+ const IS_EXECUTED_FLAG=[1, 0, 0, 0]
569
+
570
+ # ERRORS
571
+ const ERR_TX_ALREADY_EXECUTED="failed to approve multisig transaction as it was already executed"
572
+
573
+ const ERR_MALFORMED_MULTISIG_CONFIG="number of approvers must be equal to or greater than threshold"
574
+
575
+ const ERR_ZERO_IN_MULTISIG_CONFIG="number of approvers or threshold must not be zero"
576
+
577
+ const ERR_PROC_THRESHOLD_NOT_U32="procedure threshold must be a u32 value"
578
+
579
+ const ERR_NUM_APPROVERS_OR_PROC_THRESHOLD_NOT_U32="number of approvers and procedure threshold must be u32"
580
+
581
+ const ERR_PROC_THRESHOLD_EXCEEDS_NUM_APPROVERS="procedure threshold exceeds number of approvers"
582
+
583
+ # MULTISIG PROCEDURES
584
+ # =================================================================================================
585
+
586
+ #! Check if transaction has already been executed and add it to executed transactions for replay protection.
587
+ #!
588
+ #! Inputs: [MSG]
589
+ #! Outputs: []
590
+ #!
591
+ #! Panics if:
592
+ #! - the same transaction has already been executed
593
+ pub proc assert_new_tx(msg: BeWord)
594
+ push.IS_EXECUTED_FLAG
595
+ # => [[0, 0, 0, is_executed], MSG]
596
+
597
+ swapw
598
+ # => [MSG, IS_EXECUTED_FLAG]
599
+
600
+ push.EXECUTED_TXS_SLOT[0..2]
601
+ # => [txs_slot_prefix, txs_slot_suffix, MSG, IS_EXECUTED_FLAG]
602
+
603
+ # Set the key value pair in the map to mark transaction as executed
604
+ exec.native_account::set_map_item
605
+ # => [[0, 0, 0, is_executed]]
606
+
607
+ drop drop drop
608
+ # => [is_executed]
609
+
610
+ assertz.err=ERR_TX_ALREADY_EXECUTED
611
+ # => []
612
+ end
613
+
614
+ #! Remove old approver public keys from the approver public key mapping.
615
+ #!
616
+ #! This procedure cleans up the storage by removing public keys of approvers that are no longer
617
+ #! part of the multisig configuration. This procedure assumes that init_num_of_approvers and
618
+ #! new_num_of_approvers are u32 values.
619
+ #!
620
+ #! Inputs: [init_num_of_approvers, new_num_of_approvers]
621
+ #! Outputs: []
622
+ #!
623
+ #! Where:
624
+ #! - init_num_of_approvers is the original number of approvers before the update
625
+ #! - new_num_of_approvers is the new number of approvers after the update
626
+ proc cleanup_pubkey_mapping(init_num_of_approvers: u32, new_num_of_approvers: u32)
627
+ dup.1 dup.1
628
+ u32assert2 u32lt
629
+ # => [should_loop, i = init_num_of_approvers, new_num_of_approvers]
630
+
631
+ while.true
632
+ # => [i, new_num_of_approvers]
633
+
634
+ sub.1
635
+ # => [i-1, new_num_of_approvers]
636
+
637
+ dup
638
+ # => [i-1, i-1, new_num_of_approvers]
639
+
640
+ push.0.0.0
641
+ # => [[0, 0, 0, i-1], i-1, new_num_of_approvers]
642
+
643
+ padw swapw
644
+ # => [[0, 0, 0, i-1], EMPTY_WORD, i-1, new_num_of_approvers]
645
+
646
+ push.PUBLIC_KEYS_MAP_SLOT[0..2]
647
+ # => [pub_key_slot_prefix, pub_key_slot_suffix, [0, 0, 0, i-1], EMPTY_WORD, i-1, new_num_of_approvers]
648
+
649
+ exec.native_account::set_map_item
650
+ # => [OLD_MAP_VALUE, i-1, new_num_of_approvers]
651
+
652
+ dropw
653
+ # => [i-1, new_num_of_approvers]
654
+
655
+ dup.1 dup.1
656
+ u32lt
657
+ # => [should_loop, i-1, new_num_of_approvers]
658
+ end
659
+
660
+ drop drop
661
+ # => []
662
+ end
663
+
664
+ proc get_threshold_and_num_approvers
665
+ push.THRESHOLD_CONFIG_SLOT[0..2]
666
+ exec.active_account::get_initial_item
667
+ # => [0, 0, num_approvers, threshold]
668
+
669
+ drop drop
670
+ # => [num_approvers, threshold]
671
+
672
+ swap
673
+ # => [threshold, num_approvers]
674
+ end
675
+
676
+ proc assert_proc_thresholds_lte_num_approvers(num_approvers: u32)
677
+ exec.active_account::get_num_procedures
678
+ # => [num_procedures, num_approvers]
679
+
680
+ dup neq.0
681
+ # => [should_continue, num_procedures, num_approvers]
682
+ while.true
683
+ sub.1 dup
684
+ # => [proc_index, proc_index, num_approvers]
685
+
686
+ exec.active_account::get_procedure_root
687
+ # => [PROC_ROOT, proc_index, num_approvers]
688
+
689
+ push.PROC_THRESHOLD_ROOTS_SLOT[0..2]
690
+ # => [proc_roots_slot_prefix, proc_roots_slot_suffix, PROC_ROOT, proc_index, num_approvers]
691
+
692
+ exec.active_account::get_initial_map_item
693
+ # => [[0, 0, 0, proc_threshold], proc_index, num_approvers]
694
+
695
+ drop drop drop
696
+ # => [proc_threshold, proc_index, num_approvers]
697
+
698
+ dup.2
699
+ # => [num_approvers, proc_threshold, proc_index, num_approvers]
700
+
701
+ u32assert2.err=ERR_PROC_THRESHOLD_NOT_U32
702
+ u32gt assertz.err=ERR_PROC_THRESHOLD_EXCEEDS_NUM_APPROVERS
703
+ # => [proc_index, num_approvers]
704
+
705
+ dup neq.0
706
+ # => [should_continue, proc_index, num_approvers]
707
+ end
708
+
709
+ drop drop
710
+ # => []
711
+ end
712
+
713
+ # Internal storage helper used by the public \`update_procedure_threshold\`
714
+ # entrypoint after it receives direct stack arguments.
715
+ proc set_procedure_threshold(proc_threshold: u32, proc_root: BeWord)
716
+ exec.get_threshold_and_num_approvers
717
+ # => [threshold, num_approvers, proc_threshold, PROC_ROOT]
718
+
719
+ drop
720
+ # => [num_approvers, proc_threshold, PROC_ROOT]
721
+
722
+ dup.1 swap
723
+ # => [num_approvers, proc_threshold, proc_threshold, PROC_ROOT]
724
+
725
+ u32assert2.err=ERR_NUM_APPROVERS_OR_PROC_THRESHOLD_NOT_U32
726
+ u32gt assertz.err=ERR_PROC_THRESHOLD_EXCEEDS_NUM_APPROVERS
727
+ # => [proc_threshold, PROC_ROOT]
728
+
729
+ push.0.0.0
730
+ swapw
731
+ # => [PROC_ROOT, PROC_THRESHOLD_WORD]
732
+
733
+ push.PROC_THRESHOLD_ROOTS_SLOT[0..2]
734
+ # => [proc_roots_slot_prefix, proc_roots_slot_suffix, PROC_ROOT, PROC_THRESHOLD_WORD]
735
+
736
+ exec.native_account::set_map_item
737
+ # => [OLD_VALUE]
738
+
739
+ dropw
740
+ # => []
741
+ end
742
+
743
+ #! Update threshold config and add / remove approvers
744
+ #!
745
+ #! Inputs:
746
+ #! Operand stack: [MULTISIG_CONFIG_HASH, pad(12)]
747
+ #! Advice map: {
748
+ #! MULTISIG_CONFIG_HASH => [CONFIG, PUB_KEY_N, PUB_KEY_N-1, ..., PUB_KEY_0]
749
+ #! }
750
+ #! Outputs:
751
+ #! Operand stack: []
752
+ #!
753
+ #! Where:
754
+ #! - MULTISIG_CONFIG_HASH is the hash of the threshold and new public key vector
755
+ #! - MULTISIG_CONFIG is [threshold, num_approvers, 0, 0]
756
+ #! - PUB_KEY_i is the public key of the i-th signer
757
+ #!
758
+ #! Locals:
759
+ #! 0: new_num_of_approvers
760
+ #! 1: init_num_of_approvers
761
+ @locals(2)
762
+ pub proc update_signers_and_threshold(multisig_config_hash: BeWord)
763
+ adv.push_mapval
764
+ # => [MULTISIG_CONFIG_HASH, pad(12)]
765
+
766
+ adv_loadw
767
+ # => [MULTISIG_CONFIG, pad(12)]
768
+
769
+ # store new_num_of_approvers for later
770
+ dup.2 loc_store.0
771
+ # => [MULTISIG_CONFIG, pad(12)]
772
+
773
+ dup.3 dup.3
774
+ # => [num_approvers, threshold, MULTISIG_CONFIG, pad(12)]
775
+
776
+ # make sure that the threshold is smaller than the number of approvers
777
+ u32assert2.err=ERR_MALFORMED_MULTISIG_CONFIG
778
+ u32gt assertz.err=ERR_MALFORMED_MULTISIG_CONFIG
779
+ # => [MULTISIG_CONFIG, pad(12)]
780
+
781
+ dup.3 dup.3
782
+ # => [num_approvers, threshold, MULTISIG_CONFIG, pad(12)]
783
+
784
+ # make sure that threshold or num_approvers are not zero
785
+ eq.0 assertz.err=ERR_ZERO_IN_MULTISIG_CONFIG
786
+ eq.0 assertz.err=ERR_ZERO_IN_MULTISIG_CONFIG
787
+ # => [MULTISIG_CONFIG, pad(12)]
788
+
789
+ loc_load.0
790
+ # => [num_approvers, MULTISIG_CONFIG, pad(12)]
791
+
792
+ exec.assert_proc_thresholds_lte_num_approvers
793
+ # => [MULTISIG_CONFIG, pad(12)]
794
+
795
+ push.THRESHOLD_CONFIG_SLOT[0..2]
796
+ # => [config_slot_prefix, config_slot_suffix, MULTISIG_CONFIG, pad(12)]
797
+
798
+ exec.native_account::set_item
799
+ # => [OLD_THRESHOLD_CONFIG, pad(12)]
800
+
801
+ # store init_num_of_approvers for later
802
+ drop drop loc_store.1 drop
803
+ # => [pad(12)]
804
+
805
+ loc_load.0
806
+ # => [num_approvers]
807
+
808
+ dup neq.0
809
+ while.true
810
+ sub.1
811
+ # => [i-1, pad(12)]
812
+
813
+ dup push.0.0.0
814
+ # => [[0, 0, 0, i-1], i-1, pad(12)]
815
+
816
+ padw adv_loadw
817
+ # => [PUB_KEY, [0, 0, 0, i-1], i-1, pad(12)]
818
+
819
+ swapw
820
+ # => [[0, 0, 0, i-1], PUB_KEY, i-1, pad(12)]
821
+
822
+ push.PUBLIC_KEYS_MAP_SLOT[0..2]
823
+ # => [pub_key_slot_prefix, pub_key_slot_suffix, [0, 0, 0, i-1], PUB_KEY, i-1, pad(12)]
824
+
825
+ exec.native_account::set_map_item
826
+ # => [OLD_VALUE, i-1, pad(12)]
827
+
828
+ dropw
829
+ # => [i-1, pad(12)]
830
+
831
+ dup neq.0
832
+ # => [is_non_zero, i-1, pad(12)]
833
+ end
834
+ # => [pad(13)]
835
+
836
+ drop
837
+ # => [pad(12)]
838
+
839
+ # compare initial vs current multisig config
840
+
841
+ # load init_num_of_approvers & new_num_of_approvers
842
+ loc_load.0 loc_load.1
843
+ # => [init_num_of_approvers, new_num_of_approvers, pad(12)]
844
+
845
+ exec.cleanup_pubkey_mapping
846
+ # => [pad(12)]
847
+ end
848
+
849
+ #! Update or clear the threshold override for a specific procedure.
850
+ #!
851
+ #! Inputs:
852
+ #! Operand stack: [proc_threshold, PROC_ROOT]
853
+ #! Outputs:
854
+ #! Operand stack: []
855
+ #! Invocation: call
856
+ pub proc update_procedure_threshold(proc_threshold: u32, proc_root: BeWord)
857
+ exec.set_procedure_threshold
858
+ end
859
+
860
+ # Computes the effective transaction threshold based on called procedures and per-procedure
861
+ # overrides stored in PROC_THRESHOLD_ROOTS_SLOT. Falls back to default_threshold if no
862
+ # overrides apply.
863
+ #
864
+ #! Inputs: [default_threshold]
865
+ #! Outputs: [transaction_threshold]
866
+ @locals(1)
867
+ proc compute_transaction_threshold(default_threshold: u32) -> u32
868
+ # 1. initialize transaction_threshold = 0
869
+ # 2. iterate through all account procedures
870
+ # a. check if the procedure was called during the transaction
871
+ # b. if called, get the override threshold of that procedure from the config map
872
+ # c. if proc_threshold > transaction_threshold, set transaction_threshold = proc_threshold
873
+ # 3. if transaction_threshold == 0 at the end, revert to using default_threshold
874
+
875
+ # store default_threshold for later
876
+ loc_store.0
877
+ # => []
878
+
879
+ # 1. initialize transaction_threshold = 0
880
+ push.0
881
+ # => [transaction_threshold]
882
+
883
+ # get the number of account procedures
884
+ exec.active_account::get_num_procedures
885
+ # => [num_procedures, transaction_threshold]
886
+
887
+ # 2. iterate through all account procedures
888
+ dup neq.0
889
+ # => [should_continue, num_procedures, transaction_threshold]
890
+ while.true
891
+ sub.1 dup
892
+ # => [num_procedures-1, num_procedures-1, transaction_threshold]
893
+
894
+ # get procedure root of the procedure with index i
895
+ exec.active_account::get_procedure_root dupw
896
+ # => [PROC_ROOT, PROC_ROOT, num_procedures-1, transaction_threshold]
897
+
898
+ # 2a. check if this procedure has been called in the transaction
899
+ exec.native_account::was_procedure_called
900
+ # => [was_called, PROC_ROOT, num_procedures-1, transaction_threshold]
901
+
902
+ # if it has been called, get the override threshold of that procedure
903
+ if.true
904
+ # => [PROC_ROOT, num_procedures-1, transaction_threshold]
905
+
906
+ push.PROC_THRESHOLD_ROOTS_SLOT[0..2]
907
+ # => [PROC_THRESHOLD_ROOTS_SLOT_prefix, PROC_THRESHOLD_ROOTS_SLOT_suffix, PROC_ROOT, num_procedures-1, transaction_threshold]
908
+
909
+ # 2b. get the override proc_threshold of that procedure
910
+ # if the procedure has no override threshold, the returned map item will be [0, 0, 0, 0]
911
+ exec.active_account::get_initial_map_item
912
+ # => [[0, 0, 0, proc_threshold], num_procedures-1, transaction_threshold]
913
+
914
+ drop drop drop dup dup.3
915
+ # => [transaction_threshold, proc_threshold, proc_threshold, num_procedures-1, transaction_threshold]
916
+
917
+ u32assert2.err="transaction threshold or procedure threshold are not u32"
918
+ u32gt
919
+ # => [is_gt, proc_threshold, num_procedures-1, transaction_threshold]
920
+ # 2c. if proc_threshold > transaction_threshold, update transaction_threshold
921
+ movup.2 movdn.3
922
+ # => [is_gt, proc_threshold, transaction_threshold, num_procedures-1]
923
+ cdrop
924
+ # => [updated_transaction_threshold, num_procedures-1]
925
+ swap
926
+ # => [num_procedures-1, updated_transaction_threshold]
927
+ # if it has not been called during this transaction, nothing to do, move to the next procedure
928
+ else
929
+ dropw
930
+ # => [num_procedures-1, transaction_threshold]
931
+ end
932
+
933
+ dup neq.0
934
+ # => [should_continue, num_procedures-1, transaction_threshold]
935
+ end
936
+
937
+ drop
938
+ # => [transaction_threshold]
939
+
940
+ loc_load.0
941
+ # => [default_threshold, transaction_threshold]
942
+
943
+ # 3. if transaction_threshold == 0 at the end, revert to using default_threshold
944
+ dup.1 eq.0
945
+ # => [is_zero, default_threshold, transaction_threshold]
946
+
947
+ cdrop
948
+ # => [effective_transaction_threshold]
949
+ end
950
+
951
+ #! Authenticate a transaction using ECDSA secp256k1 with multi-signature support.
952
+ #!
953
+ #! This procedure implements multi-signature authentication by:
954
+ #! 1. Computing the transaction summary message that needs to be signed
955
+ #! 2. Verifying ECDSA signatures from multiple required signers against their public keys
956
+ #! 3. Ensuring the minimum threshold of valid signatures is met
957
+ #! 4. Returning the transaction summary commitment for higher-level wrappers.
958
+ #!
959
+ #! Inputs:
960
+ #! Operand stack: [SALT]
961
+ #! Advice map: {
962
+ #! h(PUB_KEY_0, MSG): SIG_0,
963
+ #! h(PUB_KEY_1, MSG): SIG_1,
964
+ #! h(PUB_KEY_n, MSG): SIG_n
965
+ #! }
966
+ #! Outputs:
967
+ #! Operand stack: [TX_SUMMARY_COMMITMENT]
968
+ #!
969
+ #! Where:
970
+ #! - SALT is a cryptographically random nonce that enables multiple concurrent
971
+ #! multisig transactions while maintaining replay protection. Each transaction
972
+ #! must use a unique SALT value to ensure transaction uniqueness.
973
+ #! - SIG_i is the ECDSA signature from the i-th signer.
974
+ #! - MSG is the transaction message being signed.
975
+ #! - h(PUB_KEY_i, MSG) is the hash of the public key and message used as the advice map key.
976
+ #!
977
+ #! Panics if:
978
+ #! - insufficient number of valid signatures (below threshold).
979
+ #! - insufficient number of valid signatures (below threshold).
980
+ #!
981
+ #! Invocation: exec
982
+ @locals(1)
983
+ pub proc auth_tx(salt: BeWord) -> BeWord
984
+ exec.native_account::incr_nonce drop
985
+ # => [SALT]
986
+
987
+ # ------ Computing transaction summary ------
988
+
989
+ exec.auth::create_tx_summary
990
+ # => [SALT, OUTPUT_NOTES_COMMITMENT, INPUT_NOTES_COMMITMENT, ACCOUNT_DELTA_COMMITMENT]
991
+
992
+ # to build a tx_summary in the host, we need these four words in the advice provider
993
+ exec.auth::adv_insert_hqword
994
+ # => [SALT, OUTPUT_NOTES_COMMITMENT, INPUT_NOTES_COMMITMENT, ACCOUNT_DELTA_COMMITMENT]
995
+
996
+ # the commitment to the tx summary is the message that is signed
997
+ exec.auth::hash_tx_summary
998
+ # => [TX_SUMMARY_COMMITMENT]
999
+
1000
+ # ------ Verifying approver signatures ------
1001
+
1002
+ push.THRESHOLD_CONFIG_SLOT[0..2]
1003
+ # => [config_slot_prefix, config_slot_suffix, TX_SUMMARY_COMMITMENT]
1004
+
1005
+ exec.active_account::get_initial_item
1006
+ # => [0, 0, num_of_approvers, default_threshold, TX_SUMMARY_COMMITMENT]
1007
+
1008
+ drop drop
1009
+ # => [num_of_approvers, default_threshold, TX_SUMMARY_COMMITMENT]
1010
+
1011
+ swap movdn.5
1012
+ # => [num_of_approvers, TX_SUMMARY_COMMITMENT, default_threshold]
1013
+
1014
+ push.PUBLIC_KEYS_MAP_SLOT[0..2]
1015
+ # => [pub_key_slot_prefix, pub_key_slot_suffix, num_of_approvers, TX_SUMMARY_COMMITMENT, default_threshold]
1016
+
1017
+ exec.::miden::standards::auth::ecdsa_k256_keccak::verify_signatures
1018
+ # => [num_verified_signatures, TX_SUMMARY_COMMITMENT, default_threshold]
1019
+
1020
+ # ------ Checking threshold is >= num_verified_signatures ------
1021
+
1022
+ movup.5
1023
+ # => [default_threshold, num_verified_signatures, TX_SUMMARY_COMMITMENT]
1024
+
1025
+ exec.compute_transaction_threshold
1026
+ # => [transaction_threshold, num_verified_signatures, TX_SUMMARY_COMMITMENT]
1027
+
1028
+ u32assert2 u32lt
1029
+ # => [is_unauthorized, TX_SUMMARY_COMMITMENT]
1030
+
1031
+ # If signatures are non-existent the tx will fail here.
1032
+ if.true
1033
+ emit.AUTH_UNAUTHORIZED_EVENT
1034
+ push.0 assert.err="insufficient number of signatures"
1035
+ end
1036
+
1037
+ # => [TX_SUMMARY_COMMITMENT]
1038
+ end
1039
+ `;
1040
+
1041
+ export const GUARDIAN_MASM = `# Guardian Authentication Component
1042
+ #
1043
+ # This component provides GUARDIAN signature verification for accounts.
1044
+ # It can be used standalone or in conjunction with other auth components like multisig.
1045
+
1046
+ use miden::protocol::active_account
1047
+ use miden::protocol::native_account
1048
+
1049
+ type BeWord = struct @bigendian { a: felt, b: felt, c: felt, d: felt }
1050
+
1051
+ # IMPORTANT SECURITY NOTES
1052
+ # --------------------------------------------------------------------------------
1053
+ # - The selector in \`GUARDIAN_SELECTOR_SLOT\` controls whether the extra GUARDIAN signature
1054
+ # is enforced:
1055
+ # * GUARDIAN_ON => exactly one valid GUARDIAN signature is required.
1056
+ # * GUARDIAN_OFF => GUARDIAN signature is skipped for that call.
1057
+ #
1058
+ # - \`verify_guardian_signature\` reads the selector from initial storage state.
1059
+ # This means changes made during the same transaction won't affect the check.
1060
+ #
1061
+ # - \`enable_guardian\` / \`disable_guardian\` procedures allow explicit control over GUARDIAN state.
1062
+ #
1063
+ # - \`update_guardian_public_key\`:
1064
+ # * Installs a new GUARDIAN public key in the map at \`GUARDIAN_PUBLIC_KEY_MAP_SLOT\`.
1065
+ # * Does not itself perform any signature checks.
1066
+ # * To update the key without requiring GUARDIAN signature, ensure selector is OFF.
1067
+ #
1068
+ # Storage Layout
1069
+ # --------------------------------------------------------------------------------
1070
+ #
1071
+ # +---------------------+---------------+
1072
+ # | DESCRIPTION | SLOT |
1073
+ # +---------------------+---------------+
1074
+ # | GUARDIAN SELECTOR (word) | 0 |
1075
+ # | GUARDIAN PUBLIC KEY MAP | 1 |
1076
+ # +---------------------+---------------+
1077
+ #
1078
+ # - GUARDIAN_SELECTOR_SLOT (0):
1079
+ # * Stores a word that is compared against [1, 0, 0, 0] (GUARDIAN_ON).
1080
+ # * Any value != GUARDIAN_ON is treated as GUARDIAN_OFF.
1081
+ #
1082
+ # - GUARDIAN_PUBLIC_KEY_MAP_SLOT (1):
1083
+ # * A map from a fixed key [0, 0, 0, 0] to the single GUARDIAN public key:
1084
+ # [0, 0, 0, 0] => GUARDIAN_PUBLIC_KEY
1085
+ # * GUARDIAN_PUBLIC_KEY is a RPO Falcon 512 public key represented as a word.
1086
+
1087
+ # CONSTANTS
1088
+ # =================================================================================================
1089
+
1090
+ # Slot where the GUARDIAN selector flag is stored (using word() for named slot access):
1091
+ # - GUARDIAN_ON => GUARDIAN signature required
1092
+ # - GUARDIAN_OFF => GUARDIAN signature skipped
1093
+ const GUARDIAN_SELECTOR_SLOT=word("openzeppelin::guardian::selector")
1094
+
1095
+ # Map slot for GUARDIAN public key
1096
+ # Uses exactly one GUARDIAN public key at index [0, 0, 0, 0]
1097
+ # [0, 0, 0, 0] => GUARDIAN_PUBLIC_KEY
1098
+ const GUARDIAN_PUBLIC_KEY_MAP_SLOT=word("openzeppelin::guardian::public_key")
1099
+
1100
+ # Selector flag values
1101
+ const GUARDIAN_ON=[1, 0, 0, 0]
1102
+ const GUARDIAN_OFF=[0, 0, 0, 0]
1103
+
1104
+ # The event emitted when a signature is not found for a required signer.
1105
+ const AUTH_UNAUTHORIZED_EVENT=event("miden::auth::unauthorized")
1106
+
1107
+ # GUARDIAN PROCEDURES
1108
+ # =================================================================================================
1109
+
1110
+ #! Enable GUARDIAN verification by setting the selector to ON.
1111
+ #!
1112
+ #! Operand stack inputs: []
1113
+ #! Outputs: []
1114
+ #!
1115
+ #! Notes:
1116
+ #! - Sets GUARDIAN_SELECTOR_SLOT to GUARDIAN_ON (1)
1117
+ #! - After this, transactions will require GUARDIAN signature verification
1118
+ proc enable_guardian
1119
+ push.GUARDIAN_ON
1120
+ # => [GUARDIAN_ON]
1121
+
1122
+ push.GUARDIAN_SELECTOR_SLOT[0..2]
1123
+ # => [selector_slot_prefix, selector_slot_suffix, GUARDIAN_ON]
1124
+
1125
+ exec.native_account::set_item
1126
+ # => [OLD_ROOT]
1127
+
1128
+ dropw
1129
+ # => []
1130
+ end
1131
+
1132
+ #! Disable GUARDIAN verification by setting the selector to OFF.
1133
+ #!
1134
+ #! Operand stack inputs: []
1135
+ #! Outputs: []
1136
+ #!
1137
+ #! Notes:
1138
+ #! - Sets GUARDIAN_SELECTOR_SLOT to GUARDIAN_OFF (0)
1139
+ #! - After this, transactions will NOT require GUARDIAN signature verification
1140
+ proc disable_guardian
1141
+ push.GUARDIAN_OFF
1142
+ # => [GUARDIAN_OFF]
1143
+
1144
+ push.GUARDIAN_SELECTOR_SLOT[0..2]
1145
+ # => [selector_slot_prefix, selector_slot_suffix, GUARDIAN_OFF]
1146
+
1147
+ exec.native_account::set_item
1148
+ # => [OLD_ROOT]
1149
+
1150
+ dropw
1151
+ # => []
1152
+ end
1153
+
1154
+ #! Update the GUARDIAN public key.
1155
+ #!
1156
+ #! Operand stack inputs: []
1157
+ #! Advice stack inputs: [PUB_KEY]
1158
+ #! - PUB_KEY is the new GUARDIAN RPO Falcon 512 public key
1159
+ #!
1160
+ #! Notes:
1161
+ #! - Stores PUB_KEY into GUARDIAN_PUBLIC_KEY_MAP_SLOT:
1162
+ #! [0, 0, 0, 0] => GUARDIAN_PUBLIC_KEY
1163
+ #! - To update the key without requiring GUARDIAN signature, ensure
1164
+ #! GUARDIAN_SELECTOR_SLOT = 0 (OFF) before calling this.
1165
+ pub proc update_guardian_public_key
1166
+ exec.disable_guardian
1167
+ # ------ Update the GUARDIAN public key ------
1168
+ adv_loadw
1169
+ # => [PUB_KEY]
1170
+
1171
+ push.0.0.0.0
1172
+ # => [MAP_KEY, PUB_KEY]
1173
+ # Note that MAP_KEY is [0, 0, 0, 0] for a single GUARDIAN_KEY
1174
+
1175
+ push.GUARDIAN_PUBLIC_KEY_MAP_SLOT[0..2]
1176
+ # => [pub_key_slot_prefix, pub_key_slot_suffix, MAP_KEY, PUB_KEY]
1177
+
1178
+ exec.native_account::set_map_item
1179
+ # => [OLD_MAP_VALUE]
1180
+
1181
+ dropw
1182
+ # => []
1183
+ end
1184
+
1185
+ #! Conditionally verify a "GUARDIAN" signature against a stored public key hash.
1186
+ #! The condition is controlled by the selector at GUARDIAN_SELECTOR_SLOT.
1187
+ #!
1188
+ #! Inputs: [MSG]
1189
+ #! Outputs: [MSG]
1190
+ #!
1191
+ #! Panics if:
1192
+ #! - Selector is ON but the provided GUARDIAN signature is invalid or missing.
1193
+ #!
1194
+ #! Notes:
1195
+ #! - MSG is TX_SUMMARY_COMMITMENT provided by auth procedure
1196
+ #! - If selector is OFF (0), GUARDIAN verification is skipped
1197
+ #! - Selector value is read from initial storage state
1198
+ pub proc verify_guardian_signature(msg: BeWord) -> BeWord
1199
+ push.GUARDIAN_SELECTOR_SLOT[0..2]
1200
+ exec.active_account::get_item
1201
+ drop drop drop
1202
+ # => [selector, MSG]
1203
+
1204
+ push.1 eq
1205
+ if.true
1206
+ push.1
1207
+ push.GUARDIAN_PUBLIC_KEY_MAP_SLOT[0..2]
1208
+ exec.::miden::standards::auth::falcon512_rpo::verify_signatures
1209
+ push.1 neq
1210
+ if.true
1211
+ emit.AUTH_UNAUTHORIZED_EVENT
1212
+ push.0 assert.err="invalid GUARDIAN signature"
1213
+ end
1214
+ end
1215
+ # => [MSG]
1216
+ exec.enable_guardian
1217
+ end
1218
+ `;
1219
+
1220
+ export const GUARDIAN_ECDSA_MASM = `# Guardian Authentication Component (ECDSA)
1221
+ #
1222
+ # This component provides GUARDIAN signature verification for accounts using
1223
+ # ECDSA secp256k1 (k256/keccak) signatures.
1224
+ # It can be used standalone or in conjunction with other auth components like multisig.
1225
+
1226
+ use miden::protocol::active_account
1227
+ use miden::protocol::native_account
1228
+
1229
+ type BeWord = struct @bigendian { a: felt, b: felt, c: felt, d: felt }
1230
+
1231
+ # IMPORTANT SECURITY NOTES
1232
+ # --------------------------------------------------------------------------------
1233
+ # - The selector in \`GUARDIAN_SELECTOR_SLOT\` controls whether the extra GUARDIAN signature
1234
+ # is enforced:
1235
+ # * GUARDIAN_ON => exactly one valid GUARDIAN signature is required.
1236
+ # * GUARDIAN_OFF => GUARDIAN signature is skipped for that call.
1237
+ #
1238
+ # - \`verify_guardian_signature\` reads the selector from initial storage state.
1239
+ # This means changes made during the same transaction won't affect the check.
1240
+ #
1241
+ # - \`enable_guardian\` / \`disable_guardian\` procedures allow explicit control over GUARDIAN state.
1242
+ #
1243
+ # - \`update_guardian_public_key\`:
1244
+ # * Installs a new GUARDIAN public key in the map at \`GUARDIAN_PUBLIC_KEY_MAP_SLOT\`.
1245
+ # * Does not itself perform any signature checks.
1246
+ # * To update the key without requiring GUARDIAN signature, ensure selector is OFF.
1247
+ #
1248
+ # Storage Layout
1249
+ # --------------------------------------------------------------------------------
1250
+ #
1251
+ # +---------------------+---------------+
1252
+ # | DESCRIPTION | SLOT |
1253
+ # +---------------------+---------------+
1254
+ # | GUARDIAN SELECTOR (word) | 0 |
1255
+ # | GUARDIAN PUBLIC KEY MAP | 1 |
1256
+ # +---------------------+---------------+
1257
+ #
1258
+ # - GUARDIAN_SELECTOR_SLOT (0):
1259
+ # * Stores a word that is compared against [1, 0, 0, 0] (GUARDIAN_ON).
1260
+ # * Any value != GUARDIAN_ON is treated as GUARDIAN_OFF.
1261
+ #
1262
+ # - GUARDIAN_PUBLIC_KEY_MAP_SLOT (1):
1263
+ # * A map from a fixed key [0, 0, 0, 0] to the single GUARDIAN public key:
1264
+ # [0, 0, 0, 0] => GUARDIAN_PUBLIC_KEY
1265
+ # * GUARDIAN_PUBLIC_KEY is an ECDSA secp256k1 public key represented as a word.
1266
+
1267
+ # CONSTANTS
1268
+ # =================================================================================================
1269
+
1270
+ # Slot where the GUARDIAN selector flag is stored (using word() for named slot access):
1271
+ # - GUARDIAN_ON => GUARDIAN signature required
1272
+ # - GUARDIAN_OFF => GUARDIAN signature skipped
1273
+ const GUARDIAN_SELECTOR_SLOT=word("openzeppelin::guardian::selector")
1274
+
1275
+ # Map slot for GUARDIAN public key
1276
+ # Uses exactly one GUARDIAN public key at index [0, 0, 0, 0]
1277
+ # [0, 0, 0, 0] => GUARDIAN_PUBLIC_KEY
1278
+ const GUARDIAN_PUBLIC_KEY_MAP_SLOT=word("openzeppelin::guardian::public_key")
1279
+
1280
+ # Selector flag values
1281
+ const GUARDIAN_ON=[1, 0, 0, 0]
1282
+ const GUARDIAN_OFF=[0, 0, 0, 0]
1283
+
1284
+ # The event emitted when a signature is not found for a required signer.
1285
+ const AUTH_UNAUTHORIZED_EVENT=event("miden::auth::unauthorized")
1286
+
1287
+ # GUARDIAN PROCEDURES
1288
+ # =================================================================================================
1289
+
1290
+ #! Enable GUARDIAN verification by setting the selector to ON.
1291
+ #!
1292
+ #! Operand stack inputs: []
1293
+ #! Outputs: []
1294
+ #!
1295
+ #! Notes:
1296
+ #! - Sets GUARDIAN_SELECTOR_SLOT to GUARDIAN_ON (1)
1297
+ #! - After this, transactions will require GUARDIAN signature verification
1298
+ proc enable_guardian
1299
+ push.GUARDIAN_ON
1300
+ # => [GUARDIAN_ON]
1301
+
1302
+ push.GUARDIAN_SELECTOR_SLOT[0..2]
1303
+ # => [selector_slot_prefix, selector_slot_suffix, GUARDIAN_ON]
1304
+
1305
+ exec.native_account::set_item
1306
+ # => [OLD_ROOT]
1307
+
1308
+ dropw
1309
+ # => []
1310
+ end
1311
+
1312
+ #! Disable GUARDIAN verification by setting the selector to OFF.
1313
+ #!
1314
+ #! Operand stack inputs: []
1315
+ #! Outputs: []
1316
+ #!
1317
+ #! Notes:
1318
+ #! - Sets GUARDIAN_SELECTOR_SLOT to GUARDIAN_OFF (0)
1319
+ #! - After this, transactions will NOT require GUARDIAN signature verification
1320
+ proc disable_guardian
1321
+ push.GUARDIAN_OFF
1322
+ # => [GUARDIAN_OFF]
1323
+
1324
+ push.GUARDIAN_SELECTOR_SLOT[0..2]
1325
+ # => [selector_slot_prefix, selector_slot_suffix, GUARDIAN_OFF]
1326
+
1327
+ exec.native_account::set_item
1328
+ # => [OLD_ROOT]
1329
+
1330
+ dropw
1331
+ # => []
1332
+ end
1333
+
1334
+ #! Update the GUARDIAN public key.
1335
+ #!
1336
+ #! Operand stack inputs: []
1337
+ #! Advice stack inputs: [PUB_KEY]
1338
+ #! - PUB_KEY is the new GUARDIAN ECDSA secp256k1 public key
1339
+ #!
1340
+ #! Notes:
1341
+ #! - Stores PUB_KEY into GUARDIAN_PUBLIC_KEY_MAP_SLOT:
1342
+ #! [0, 0, 0, 0] => GUARDIAN_PUBLIC_KEY
1343
+ #! - To update the key without requiring GUARDIAN signature, ensure
1344
+ #! GUARDIAN_SELECTOR_SLOT = 0 (OFF) before calling this.
1345
+ pub proc update_guardian_public_key
1346
+ exec.disable_guardian
1347
+ # ------ Update the GUARDIAN public key ------
1348
+ adv_loadw
1349
+ # => [PUB_KEY]
1350
+
1351
+ push.0.0.0.0
1352
+ # => [MAP_KEY, PUB_KEY]
1353
+ # Note that MAP_KEY is [0, 0, 0, 0] for a single GUARDIAN_KEY
1354
+
1355
+ push.GUARDIAN_PUBLIC_KEY_MAP_SLOT[0..2]
1356
+ # => [pub_key_slot_prefix, pub_key_slot_suffix, MAP_KEY, PUB_KEY]
1357
+
1358
+ exec.native_account::set_map_item
1359
+ # => [OLD_MAP_VALUE]
1360
+
1361
+ dropw
1362
+ # => []
1363
+ end
1364
+
1365
+ #! Conditionally verify a "GUARDIAN" signature against a stored public key hash.
1366
+ #! The condition is controlled by the selector at GUARDIAN_SELECTOR_SLOT.
1367
+ #!
1368
+ #! Inputs: [MSG]
1369
+ #! Outputs: [MSG]
1370
+ #!
1371
+ #! Panics if:
1372
+ #! - Selector is ON but the provided GUARDIAN signature is invalid or missing.
1373
+ #!
1374
+ #! Notes:
1375
+ #! - MSG is TX_SUMMARY_COMMITMENT provided by auth procedure
1376
+ #! - If selector is OFF (0), GUARDIAN verification is skipped
1377
+ #! - Selector value is read from initial storage state
1378
+ pub proc verify_guardian_signature(msg: BeWord) -> BeWord
1379
+ push.GUARDIAN_SELECTOR_SLOT[0..2]
1380
+ exec.active_account::get_item
1381
+ drop drop drop
1382
+ # => [selector, MSG]
1383
+
1384
+ push.1 eq
1385
+ if.true
1386
+ push.1
1387
+ push.GUARDIAN_PUBLIC_KEY_MAP_SLOT[0..2]
1388
+ exec.::miden::standards::auth::ecdsa_k256_keccak::verify_signatures
1389
+ push.1 neq
1390
+ if.true
1391
+ emit.AUTH_UNAUTHORIZED_EVENT
1392
+ push.0 assert.err="invalid GUARDIAN signature"
1393
+ end
1394
+ end
1395
+ # => [MSG]
1396
+ exec.enable_guardian
1397
+ end
1398
+ `;