@openzeppelin/compact-contracts 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +278 -0
- package/access/AccessControl.compact +479 -0
- package/access/Ownable.compact +353 -0
- package/access/ShieldedAccessControl.compact +784 -0
- package/access/ZOwnablePK.compact +304 -0
- package/package.json +54 -0
- package/security/Initializable.compact +60 -0
- package/security/Pausable.compact +89 -0
- package/token/FungibleToken.compact +742 -0
- package/token/MultiToken.compact +728 -0
- package/token/NonFungibleToken.compact +954 -0
- package/utils/Utils.compact +98 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.1.0 (access/AccessControl.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.21.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module AccessControl
|
|
8
|
+
* @description An unshielded AccessControl library.
|
|
9
|
+
* This module provides a role-based access control mechanism, where roles can be used to
|
|
10
|
+
* represent a set of permissions.
|
|
11
|
+
*
|
|
12
|
+
* Authorization is based on a witness-derived identity scheme. Each caller proves knowledge
|
|
13
|
+
* of a secret key by injecting it via the `wit_AccessControlSK` witness. The module computes
|
|
14
|
+
* an account identifier as `persistentHash(secretKey)` which is a commitment that hides the key
|
|
15
|
+
* while providing a stable, pseudonymous on-chain identity.
|
|
16
|
+
*
|
|
17
|
+
* Because the account identifier is `H(secretKey)` with no per-deployment salt or domain
|
|
18
|
+
* separator, the same secret key produces the same identity across all contracts. This is
|
|
19
|
+
* intentional. It provides a linkable pseudonymous identity analogous to Solidity's
|
|
20
|
+
* `msg.sender`. Users who desire cross-contract unlinkability can use different secret keys
|
|
21
|
+
* per contract at the wallet layer.
|
|
22
|
+
*
|
|
23
|
+
* Roles are referred to by their `Bytes<32>` identifier. These should be exposed
|
|
24
|
+
* in the top-level contract and be unique. One way to achieve this is by
|
|
25
|
+
* using `export sealed ledger` hash digests that are initialized in the top-level contract:
|
|
26
|
+
*
|
|
27
|
+
* ```compact
|
|
28
|
+
* import CompactStandardLibrary;
|
|
29
|
+
* import "./node_modules/@openzeppelin-compact/accessControl/src/AccessControl" prefix AccessControl_;
|
|
30
|
+
*
|
|
31
|
+
* export sealed ledger MY_ROLE: Bytes<32>;
|
|
32
|
+
*
|
|
33
|
+
* constructor() {
|
|
34
|
+
* MY_ROLE = persistentHash<Bytes<32>>(pad(32, "MY_ROLE"));
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* To restrict access to a circuit, use {assertOnlyRole}:
|
|
39
|
+
*
|
|
40
|
+
* ```compact
|
|
41
|
+
* circuit foo(): [] {
|
|
42
|
+
* AccessControl_assertOnlyRole(MY_ROLE);
|
|
43
|
+
* // ... rest of circuit logic
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* Roles can be granted and revoked dynamically via the {grantRole} and
|
|
48
|
+
* {revokeRole} circuits. Each role has an associated admin role, and only
|
|
49
|
+
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
|
|
50
|
+
*
|
|
51
|
+
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE` (zero bytes), which means
|
|
52
|
+
* that only accounts with this role will be able to grant or revoke other
|
|
53
|
+
* roles. More complex role relationships can be created by using
|
|
54
|
+
* {_setRoleAdmin}.
|
|
55
|
+
*
|
|
56
|
+
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
|
|
57
|
+
* grant and revoke this role. Extra precautions should be taken to secure
|
|
58
|
+
* accounts that have been granted it.
|
|
59
|
+
*
|
|
60
|
+
* @notice Roles can only be granted to Bytes<32> identifiers
|
|
61
|
+
* through the main role approval circuits (`grantRole` and `_grantRole`).
|
|
62
|
+
* In other words, role approvals to contract addresses are disallowed through these
|
|
63
|
+
* circuits.
|
|
64
|
+
* This is because Compact currently does not support contract-to-contract calls which means
|
|
65
|
+
* if a contract is granted a role, the contract cannot directly call the protected
|
|
66
|
+
* circuit.
|
|
67
|
+
*
|
|
68
|
+
* @notice This module does offer an experimental circuit that allows roles to be granted
|
|
69
|
+
* to contract addresses (`_unsafeGrantRole`).
|
|
70
|
+
* Note that the circuit name is very explicit ("unsafe") with this experimental circuit.
|
|
71
|
+
* Until contract-to-contract calls are supported,
|
|
72
|
+
* there is no direct way for a contract to call protected circuits.
|
|
73
|
+
*
|
|
74
|
+
* @notice The unsafe circuits are planned to become deprecated once contract-to-contract calls
|
|
75
|
+
* are supported.
|
|
76
|
+
*
|
|
77
|
+
* @dev Migration plan (safe -> C2C-capable role grants)
|
|
78
|
+
*
|
|
79
|
+
* The account type is the final `Either<Bytes<32>, ContractAddress>` from day one, in both circuit
|
|
80
|
+
* signatures and the roles ledger map. This is deliberate. Midnight's upgrade mechanism, the
|
|
81
|
+
* Contract Maintenance Authority (CMA), can rotate a circuit's verifier key but cannot migrate
|
|
82
|
+
* ledger state (there is no state-rewrite primitive; see the `updatability` tutorial). Fixing the
|
|
83
|
+
* type now makes the future migration a pure verifier-key rotation with zero ledger-state change,
|
|
84
|
+
* which is the only kind of upgrade the CMA supports. Widening the type later would instead force
|
|
85
|
+
* a ledger-layout rewrite, which the CMA cannot perform.
|
|
86
|
+
*
|
|
87
|
+
* When C2C lands, an already-deployed contract migrates without changing its address or ledger
|
|
88
|
+
* state:
|
|
89
|
+
*
|
|
90
|
+
* - Minor release: mark `_unsafeGrantRole` deprecated (and emit a warning if possible); keep its
|
|
91
|
+
* implementation intact so existing callers continue to work.
|
|
92
|
+
* - Major release: ship `grantRole` / `_grantRole` with the `isContract` guard removed and drop
|
|
93
|
+
* `_unsafeGrantRole`. Operators adopt the new logic by signing a CMA `MaintenanceUpdate` that
|
|
94
|
+
* rotates the affected verifier keys (`VerifierKeyRemove` + `VerifierKeyInsert` for each guarded
|
|
95
|
+
* circuit, and a `VerifierKeyRemove` for `_unsafeGrantRole`). The contract address and all ledger
|
|
96
|
+
* state are preserved. Because the CMA allows multiple active VK versions per operation, old and
|
|
97
|
+
* new keys can coexist during a transition window so in-flight proofs still verify. By this
|
|
98
|
+
* point, anyone using `_unsafeGrantRole` should have migrated to the now C2C-capable circuits.
|
|
99
|
+
*
|
|
100
|
+
* @dev Security Considerations:
|
|
101
|
+
* - The `secretKey` must be kept private. Loss of the key prevents role holders
|
|
102
|
+
* from proving access. Key exposure allows impersonation.
|
|
103
|
+
* - It is strongly recommended to use cryptographically secure random values for the secret key
|
|
104
|
+
* (e.g., `crypto.getRandomValues()`). Weak or predictable keys can be brute-forced.
|
|
105
|
+
* - The `secretKey` is provided via the `wit_AccessControlSK` witness. As with all witnesses,
|
|
106
|
+
* the contract must not assume that the witness implementation matches the developer's code.
|
|
107
|
+
* Any DApp may provide any implementation. The ZK proof system constrains what values
|
|
108
|
+
* can produce valid proofs.
|
|
109
|
+
*
|
|
110
|
+
* @dev Canonicalization
|
|
111
|
+
* All `Either<Bytes<32>, ContractAddress>` values used as map keys in `_operatorRoles`
|
|
112
|
+
* are canonicalized before reads and writes. Canonicalization zeroes out the inactive
|
|
113
|
+
* branch of the Either, ensuring that two values with the same active branch always
|
|
114
|
+
* resolve to the same map key regardless of what data the inactive branch carries.
|
|
115
|
+
* `hasRole` canonicalizes on read. `_unsafeGrantRole` and `_revokeRole` canonicalize
|
|
116
|
+
* on write. Internal circuits use `_hasRole` which assumes pre-canonicalized inputs.
|
|
117
|
+
*
|
|
118
|
+
* @notice Missing Features and Improvements:
|
|
119
|
+
*
|
|
120
|
+
* - Role events
|
|
121
|
+
* - An ERC165-like interface
|
|
122
|
+
*/
|
|
123
|
+
module AccessControl {
|
|
124
|
+
import CompactStandardLibrary;
|
|
125
|
+
import "../utils/Utils" prefix Utils_;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @description Mapping from a role identifier -> account -> its permissions.
|
|
129
|
+
* @type {Bytes<32>} roleId - A hash representing a role identifier.
|
|
130
|
+
* @type {Map<Either<Bytes<32>, ContractAddress>, Boolean>} hasRole - A mapping from an account to a
|
|
131
|
+
* Boolean determining if the account is approved for a role.
|
|
132
|
+
* @type {Map<roleId, hasRole>}
|
|
133
|
+
* @type {Map<Bytes<32>, Map<Either<Bytes<32>, ContractAddress>, Boolean>>} _operatorRoles
|
|
134
|
+
*/
|
|
135
|
+
export ledger _operatorRoles: Map<Bytes<32>,
|
|
136
|
+
Map<Either<Bytes<32>, ContractAddress>, Boolean>>;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @description Mapping from a role identifier to an admin role identifier.
|
|
140
|
+
* @type {Bytes<32>} roleId - A hash representing a role identifier.
|
|
141
|
+
* @type {Bytes<32>} adminId - A hash representing an admin identifier.
|
|
142
|
+
* @type {Map<roleId, adminId>}
|
|
143
|
+
* @type {Map<Bytes<32>, Bytes<32>>} _adminRoles
|
|
144
|
+
*/
|
|
145
|
+
export ledger _adminRoles: Map<Bytes<32>, Bytes<32>>;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @description The default admin role for all roles. Only accounts with this role will be able
|
|
149
|
+
* to grant or revoke other roles unless custom admin roles are created via {_setRoleAdmin}.
|
|
150
|
+
*
|
|
151
|
+
* @return {Bytes<32>} - The default admin role identifier (zero bytes).
|
|
152
|
+
*/
|
|
153
|
+
export pure circuit DEFAULT_ADMIN_ROLE(): Bytes<32> {
|
|
154
|
+
return default<Bytes<32>>;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @witness wit_AccessControlSK
|
|
159
|
+
* @description Returns the caller's secret key used in deriving the account identifier.
|
|
160
|
+
*
|
|
161
|
+
* The same key produces the same account identifier across all contracts. Users who
|
|
162
|
+
* desire cross-contract unlinkability should use different keys per contract.
|
|
163
|
+
*
|
|
164
|
+
* @returns {Bytes<32>} secretKey - A 32-byte cryptographically secure random value.
|
|
165
|
+
*/
|
|
166
|
+
witness wit_AccessControlSK(): Bytes<32>;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @description Returns `true` if `account` has been granted `roleId`.
|
|
170
|
+
*
|
|
171
|
+
* @circuitInfo k=10, rows=1019
|
|
172
|
+
*
|
|
173
|
+
* @param {Bytes<32>} roleId - The role identifier.
|
|
174
|
+
* @param {Either<Bytes<32>, ContractAddress>} account - The account to query.
|
|
175
|
+
* @return {Boolean} - Whether the account has the specified role.
|
|
176
|
+
*/
|
|
177
|
+
export circuit hasRole(roleId: Bytes<32>,
|
|
178
|
+
account: Either<Bytes<32>, ContractAddress>
|
|
179
|
+
): Boolean {
|
|
180
|
+
const canonAcct = Utils_canonicalize<Bytes<32>, ContractAddress>(account);
|
|
181
|
+
return _hasRole(roleId, canonAcct);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @description Returns `true` if `account` has been granted `roleId`. Internal variant
|
|
186
|
+
* that assumes `account` has already been canonicalized by the caller. External consumers
|
|
187
|
+
* should use {hasRole} which canonicalizes the input automatically.
|
|
188
|
+
*
|
|
189
|
+
* @param {Bytes<32>} roleId - The role identifier.
|
|
190
|
+
* @param {Either<Bytes<32>, ContractAddress>} account - The canonicalized account to query.
|
|
191
|
+
* @return {Boolean} - Whether the account has the specified role.
|
|
192
|
+
*/
|
|
193
|
+
circuit _hasRole(roleId: Bytes<32>,
|
|
194
|
+
account: Either<Bytes<32>, ContractAddress>
|
|
195
|
+
): Boolean {
|
|
196
|
+
if (_operatorRoles.member(disclose(roleId)) &&
|
|
197
|
+
_operatorRoles.lookup(roleId).member(disclose(account))) {
|
|
198
|
+
return _operatorRoles.lookup(roleId).lookup(disclose(account));
|
|
199
|
+
} else {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @description Reverts if the caller cannot prove ownership of `roleId`.
|
|
206
|
+
* In the case of an external (non-contract) caller, the caller’s identity is
|
|
207
|
+
* derived from the `wit_AccessControlSK` witness as `persistentHash(secretKey)`.
|
|
208
|
+
*
|
|
209
|
+
* @circuitInfo k=13, rows=2669
|
|
210
|
+
*
|
|
211
|
+
* Requirements:
|
|
212
|
+
*
|
|
213
|
+
* - The caller must have `roleId`.
|
|
214
|
+
*
|
|
215
|
+
* @param {Bytes<32>} roleId - The role identifier.
|
|
216
|
+
* @return {[]} - Empty tuple.
|
|
217
|
+
*/
|
|
218
|
+
export circuit assertOnlyRole(roleId: Bytes<32>): [] {
|
|
219
|
+
// The account constructed via left<...>() is already canonical,
|
|
220
|
+
// but we route through _checkRole -> hasRole for consistency.
|
|
221
|
+
// The redundant canonicalization is negligible.
|
|
222
|
+
_checkRole(roleId, left<Bytes<32>, ContractAddress>(_computeAccountId()));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @description Reverts if `account` is missing `roleId`.
|
|
227
|
+
*
|
|
228
|
+
* @circuitInfo k=10, rows=987
|
|
229
|
+
*
|
|
230
|
+
* Requirements:
|
|
231
|
+
*
|
|
232
|
+
* - `account` must have `roleId`.
|
|
233
|
+
*
|
|
234
|
+
* @param {Bytes<32>} roleId - The role identifier.
|
|
235
|
+
* @param {Either<Bytes<32>, ContractAddress>} account - The account to query.
|
|
236
|
+
* @return {[]} - Empty tuple.
|
|
237
|
+
*/
|
|
238
|
+
export circuit _checkRole(
|
|
239
|
+
roleId: Bytes<32>,
|
|
240
|
+
account: Either<Bytes<32>, ContractAddress>
|
|
241
|
+
): [] {
|
|
242
|
+
assert(hasRole(roleId, account), "AccessControl: unauthorized account");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* @description Returns the admin role that controls `roleId` or
|
|
247
|
+
* a byte array with all zero bytes if `roleId` doesn't exist. See {grantRole} and {revokeRole}.
|
|
248
|
+
*
|
|
249
|
+
* To change a role's admin use {_setRoleAdmin}.
|
|
250
|
+
*
|
|
251
|
+
* @circuitInfo k=9, rows=373
|
|
252
|
+
*
|
|
253
|
+
* @param {Bytes<32>} roleId - The role identifier.
|
|
254
|
+
* @return {Bytes<32>} roleAdmin - The admin role that controls `roleId`.
|
|
255
|
+
*/
|
|
256
|
+
export circuit getRoleAdmin(roleId: Bytes<32>): Bytes<32> {
|
|
257
|
+
if (_adminRoles.member(disclose(roleId))) {
|
|
258
|
+
return _adminRoles.lookup(disclose(roleId));
|
|
259
|
+
}
|
|
260
|
+
return DEFAULT_ADMIN_ROLE();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* @description Grants `roleId` to `account`.
|
|
265
|
+
*
|
|
266
|
+
* @circuitInfo k=13, rows=3536
|
|
267
|
+
*
|
|
268
|
+
* Requirements:
|
|
269
|
+
*
|
|
270
|
+
* - `account` must not be a ContractAddress.
|
|
271
|
+
* - The caller must have `roleId`'s admin role.
|
|
272
|
+
*
|
|
273
|
+
* @param {Bytes<32>} roleId - The role identifier.
|
|
274
|
+
* @param {Either<Bytes<32>, ContractAddress>} account - A Bytes<32> or ContractAddress.
|
|
275
|
+
* @return {[]} - Empty tuple.
|
|
276
|
+
*/
|
|
277
|
+
export circuit grantRole(
|
|
278
|
+
roleId: Bytes<32>,
|
|
279
|
+
account: Either<Bytes<32>, ContractAddress>
|
|
280
|
+
): [] {
|
|
281
|
+
assertOnlyRole(getRoleAdmin(roleId));
|
|
282
|
+
_grantRole(roleId, account);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @description Revokes `roleId` from `account`.
|
|
287
|
+
*
|
|
288
|
+
* @circuitInfo k=13, rows=3466
|
|
289
|
+
*
|
|
290
|
+
* Requirements:
|
|
291
|
+
*
|
|
292
|
+
* - The caller must have `roleId`'s admin role.
|
|
293
|
+
*
|
|
294
|
+
* @param {Bytes<32>} roleId - The role identifier.
|
|
295
|
+
* @param {Either<Bytes<32>, ContractAddress>} account - A Bytes<32> or ContractAddress.
|
|
296
|
+
* @return {[]} - Empty tuple.
|
|
297
|
+
*/
|
|
298
|
+
export circuit revokeRole(
|
|
299
|
+
roleId: Bytes<32>,
|
|
300
|
+
account: Either<Bytes<32>, ContractAddress>
|
|
301
|
+
): [] {
|
|
302
|
+
assertOnlyRole(getRoleAdmin(roleId));
|
|
303
|
+
_revokeRole(roleId, account);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* @description Revokes `roleId` from the calling account.
|
|
308
|
+
*
|
|
309
|
+
* @notice Roles are often managed via {grantRole} and {revokeRole}: this circuit's
|
|
310
|
+
* purpose is to provide a mechanism for accounts to lose their privileges
|
|
311
|
+
* if they are compromised (such as when a trusted device is misplaced).
|
|
312
|
+
*
|
|
313
|
+
* The caller's identity is derived from the `wit_AccessControlSK` witness as
|
|
314
|
+
* `persistentHash(secretKey)`. The `callerConfirmation` parameter must match
|
|
315
|
+
* the caller's computed account identifier to prevent accidental renunciation.
|
|
316
|
+
*
|
|
317
|
+
* @circuitInfo k=13, rows=3322
|
|
318
|
+
*
|
|
319
|
+
* Requirements:
|
|
320
|
+
*
|
|
321
|
+
* - The caller's computed account identifier must match `callerConfirmation`.
|
|
322
|
+
* - The caller must not be a `ContractAddress`.
|
|
323
|
+
*
|
|
324
|
+
* @param {Bytes<32>} roleId - The role identifier.
|
|
325
|
+
* @param {Either<Bytes<32>, ContractAddress>} callerConfirmation - The caller's account identifier, must match the internally computed value.
|
|
326
|
+
* @return {[]} - Empty tuple.
|
|
327
|
+
*/
|
|
328
|
+
export circuit renounceRole(
|
|
329
|
+
roleId: Bytes<32>,
|
|
330
|
+
callerConfirmation: Either<Bytes<32>, ContractAddress>
|
|
331
|
+
): [] {
|
|
332
|
+
assert(callerConfirmation == left<Bytes<32>, ContractAddress>(_computeAccountId()),
|
|
333
|
+
"AccessControl: bad confirmation"
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
_revokeRole(roleId, callerConfirmation);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* @description Sets `adminRole` as `roleId`'s admin role.
|
|
341
|
+
*
|
|
342
|
+
* @circuitInfo k=10, rows=581
|
|
343
|
+
*
|
|
344
|
+
* @param {Bytes<32>} roleId - The role identifier.
|
|
345
|
+
* @param {Bytes<32>} adminRole - The admin role identifier.
|
|
346
|
+
* @return {[]} - Empty tuple.
|
|
347
|
+
*/
|
|
348
|
+
export circuit _setRoleAdmin(roleId: Bytes<32>, adminRole: Bytes<32>): [] {
|
|
349
|
+
_adminRoles.insert(disclose(roleId), disclose(adminRole));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* @description Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted.
|
|
354
|
+
* Internal circuit without access restriction.
|
|
355
|
+
*
|
|
356
|
+
* @circuitInfo k=11, rows=1143
|
|
357
|
+
*
|
|
358
|
+
* Requirements:
|
|
359
|
+
*
|
|
360
|
+
* - `account` must not be a ContractAddress.
|
|
361
|
+
*
|
|
362
|
+
* @param {Bytes<32>} roleId - The role identifier.
|
|
363
|
+
* @param {Either<Bytes<32>, ContractAddress>} account - A Bytes<32> or ContractAddress.
|
|
364
|
+
* @return {Boolean} roleGranted - A boolean indicating if `roleId` was granted.
|
|
365
|
+
*/
|
|
366
|
+
export circuit _grantRole(
|
|
367
|
+
roleId: Bytes<32>,
|
|
368
|
+
account: Either<Bytes<32>, ContractAddress>
|
|
369
|
+
): Boolean {
|
|
370
|
+
const isCommitment = account.is_left;
|
|
371
|
+
assert(isCommitment, "AccessControl: unsafe role approval");
|
|
372
|
+
return _unsafeGrantRole(roleId, account);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* @description Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted.
|
|
377
|
+
* Internal circuit without access restriction. It does NOT check if the role is granted to a ContractAddress.
|
|
378
|
+
*
|
|
379
|
+
* @circuitInfo k=11, rows=1142
|
|
380
|
+
*
|
|
381
|
+
* @notice External smart contracts cannot call the access control contract at this time, so granting a role to a ContractAddress may
|
|
382
|
+
* render a circuit permanently inaccessible.
|
|
383
|
+
*
|
|
384
|
+
* @param {Bytes<32>} roleId - The role identifier.
|
|
385
|
+
* @param {Either<Bytes<32>, ContractAddress>} account - A Bytes<32> or ContractAddress.
|
|
386
|
+
* @return {Boolean} roleGranted - A boolean indicating if `role` was granted.
|
|
387
|
+
*/
|
|
388
|
+
export circuit _unsafeGrantRole(
|
|
389
|
+
roleId: Bytes<32>,
|
|
390
|
+
account: Either<Bytes<32>, ContractAddress>
|
|
391
|
+
): Boolean {
|
|
392
|
+
const canonAcct = Utils_canonicalize<Bytes<32>, ContractAddress>(account);
|
|
393
|
+
if (_hasRole(roleId, canonAcct)) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (!_operatorRoles.member(disclose(roleId))) {
|
|
398
|
+
_operatorRoles.insert(
|
|
399
|
+
disclose(roleId),
|
|
400
|
+
default<Map<Either<Bytes<32>, ContractAddress>, Boolean>>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
_operatorRoles.lookup(roleId).insert(disclose(canonAcct), true);
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* @description Attempts to revoke `roleId` from `account` and returns a boolean indicating if `roleId` was revoked.
|
|
410
|
+
* Internal circuit without access restriction.
|
|
411
|
+
*
|
|
412
|
+
* @circuitInfo k=11, rows=1073
|
|
413
|
+
*
|
|
414
|
+
* @param {Bytes<32>} roleId - The role identifier.
|
|
415
|
+
* @param {Either<Bytes<32>, ContractAddress>} account - A Bytes<32> or ContractAddress.
|
|
416
|
+
* @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked.
|
|
417
|
+
*/
|
|
418
|
+
export circuit _revokeRole(
|
|
419
|
+
roleId: Bytes<32>,
|
|
420
|
+
account: Either<Bytes<32>, ContractAddress>
|
|
421
|
+
): Boolean {
|
|
422
|
+
const canonAcct = Utils_canonicalize<Bytes<32>, ContractAddress>(account);
|
|
423
|
+
|
|
424
|
+
if (!_hasRole(roleId, canonAcct)) {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
_operatorRoles.lookup(roleId).insert(disclose(canonAcct), false);
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* @description Computes the caller's account identifier from the `wit_AccessControlSK` witness.
|
|
434
|
+
*
|
|
435
|
+
* ## ID Derivation
|
|
436
|
+
* `accountId = persistentHash(secretKey)`
|
|
437
|
+
*
|
|
438
|
+
* The result is a 32-byte commitment that uniquely identifies the caller.
|
|
439
|
+
*
|
|
440
|
+
* @circuitInfo k=13, rows=2294
|
|
441
|
+
*
|
|
442
|
+
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
443
|
+
*/
|
|
444
|
+
circuit _computeAccountId(): Bytes<32> {
|
|
445
|
+
return computeAccountId(wit_AccessControlSK());
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* @description Computes an account identifier without on-chain state, allowing a user to derive
|
|
450
|
+
* their identity commitment before submitting it in a grant or revoke operation.
|
|
451
|
+
* This is the off-chain counterpart to {_computeAccountId} and produces an identical result
|
|
452
|
+
* given the same inputs.
|
|
453
|
+
*
|
|
454
|
+
* @warning OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can
|
|
455
|
+
* permanently compromise the privacy guarantees of this system:
|
|
456
|
+
*
|
|
457
|
+
* - **Never log or persist** the `secretKey` in plaintext — avoid browser devtools,
|
|
458
|
+
* application logs, analytics pipelines, or any observable side-channel.
|
|
459
|
+
* - **Store offline or in secure enclaves** — hardware security modules (HSMs),
|
|
460
|
+
* air-gapped devices, or encrypted vaults are strongly preferred over hot storage.
|
|
461
|
+
* - **Use cryptographically secure randomness** — generate keys with `crypto.getRandomValues()`
|
|
462
|
+
* or equivalent; weak or predictable keys can be brute-forced to reveal your identity.
|
|
463
|
+
* - **Treat key loss as identity loss** — a lost key cannot be recovered. Back up
|
|
464
|
+
* keys securely before using them in role commitments.
|
|
465
|
+
* - **Avoid calling this circuit in untrusted environments** — executing this in an
|
|
466
|
+
* unverified browser extension, compromised runtime, or shared machine may expose
|
|
467
|
+
* the key to a malicious observer.
|
|
468
|
+
*
|
|
469
|
+
* ## ID Derivation
|
|
470
|
+
* `accountId = persistentHash(secretKey)`
|
|
471
|
+
*
|
|
472
|
+
* @param {Bytes<32>} secretKey - A 32-byte cryptographically secure random value.
|
|
473
|
+
*
|
|
474
|
+
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
475
|
+
*/
|
|
476
|
+
export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
|
|
477
|
+
return persistentHash<Vector<1, Bytes<32>>>([secretKey]);
|
|
478
|
+
}
|
|
479
|
+
}
|