@iexec-nox/nox-protocol-contracts 0.2.2 → 0.2.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.
@@ -1,40 +1,49 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.27;
2
+ pragma solidity ^0.8.35;
3
3
 
4
- import {TEEType} from "../shared/TypeUtils.sol";
4
+ import {TEEType} from "../utils/TypeUtils.sol";
5
5
 
6
6
  /**
7
7
  * @title INoxCompute
8
8
  * @notice Interface for the Nox compute contract powered by TEE.
9
9
  */
10
10
  interface INoxCompute {
11
+ // ------------- General errors -------------
11
12
  /// Error thrown when account address is zero
12
13
  error InvalidZeroAddress();
13
14
  /// Error thrown when bytes parameter is empty
14
15
  error InvalidEmptyBytes();
16
+
17
+ // ------------- ACL errors -------------
15
18
  /// Error thrown when sender doesn't have access to the handle
16
19
  error UnauthorizedSender(address sender);
17
20
  /// Error thrown when an account is not allowed to use a handle
18
21
  error NotAllowed(bytes32 handle, address account);
19
- error InvalidProof(bytes proof, string reason);
20
- error UnsupportedType();
21
- error IncompatibleTypes();
22
+ /// Error thrown when a handle is not publicly decryptable
22
23
  error NotPubliclyDecryptable(bytes32 handle);
23
24
  /// Error thrown when attempting an ACL mutation on a public handle
24
25
  error PublicHandleACLForbidden();
26
+
27
+ // ------------- Compute errors -------------
28
+ /// Error thrown when the input proof is invalid
29
+ error InvalidProof(bytes proof, string reason);
25
30
  /// Error thrown when an operand is bytes32(0), indicating an undefined handle
26
31
  error UndefinedHandle();
27
32
 
33
+ // ------------- ACL events -------------
28
34
  /// Emitted when admin role is granted
29
35
  event Allowed(address indexed sender, address indexed account, bytes32 indexed handle);
30
36
  /// Emitted when viewer role is granted
31
37
  event ViewerAdded(address indexed sender, address indexed viewer, bytes32 indexed handle);
32
38
  /// Emitted when a handle is marked as publicly decryptable
33
39
  event MarkedAsPubliclyDecryptable(address indexed sender, bytes32 indexed handle);
40
+
41
+ // ------------- Protocol config events -------------
34
42
  event KmsPublicKeyUpdated(bytes newKmsPublicKey);
35
43
  event GatewayUpdated(address indexed newGateway);
36
44
  event ProofExpirationDurationUpdated(uint256 newDuration);
37
45
 
46
+ // ------------- Compute events -------------
38
47
  event WrapAsPublicHandle(
39
48
  address indexed caller,
40
49
  bytes32 plaintext,
@@ -228,7 +237,7 @@ interface INoxCompute {
228
237
  function validateAllowedForAll(address account, bytes32[] calldata handles) external view;
229
238
 
230
239
  /**
231
- * Add a viewer for a specific handle
240
+ * Adds a viewer for a specific handle
232
241
  * @dev Only an admin can add a viewer. The viewer address cannot be address(0).
233
242
  * @param handle The handle identifier
234
243
  * @param viewer The address to grant viewer role
@@ -561,6 +570,12 @@ interface INoxCompute {
561
570
  bytes32 totalSupply
562
571
  ) external returns (bytes32 success, bytes32 newBalanceFrom, bytes32 newTotalSupply);
563
572
 
573
+ // ------------- Config getters -------------
574
+
575
+ function kmsPublicKey() external view returns (bytes memory);
576
+ function gateway() external view returns (address);
577
+ function proofExpirationDuration() external view returns (uint256);
578
+
564
579
  // ------------- Admin functions -------------
565
580
 
566
581
  /**
@@ -580,8 +595,4 @@ interface INoxCompute {
580
595
  * @param newDuration The new expiration duration in seconds
581
596
  */
582
597
  function setProofExpirationDuration(uint256 newDuration) external;
583
-
584
- function kmsPublicKey() external view returns (bytes memory);
585
- function gateway() external view returns (address);
586
- function proofExpirationDuration() external view returns (uint256);
587
598
  }
@@ -0,0 +1,192 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity ^0.8.35;
3
+
4
+ import {HandleUtils} from "../utils/HandleUtils.sol";
5
+ import {INoxCompute} from "../interfaces/INoxCompute.sol";
6
+ import {Common} from "./Common.sol";
7
+
8
+ /**
9
+ * @title ACL
10
+ * @notice Access control for encrypted handles. Supports persistent and transient access,
11
+ * plus viewer and public-decryption roles.
12
+ */
13
+ abstract contract ACL is Common {
14
+ modifier notZeroAddress(address account) {
15
+ require(account != address(0), InvalidZeroAddress());
16
+ _;
17
+ }
18
+
19
+ /**
20
+ * Ensures the sender is allowed to access the handle
21
+ * @param handle The handle to check access for
22
+ */
23
+ modifier onlyAllowed(bytes32 handle) {
24
+ require(_isAllowed(handle, msg.sender), UnauthorizedSender(msg.sender));
25
+ _;
26
+ }
27
+
28
+ /**
29
+ * Prevents ACL mutations on public handles.
30
+ * Applied before onlyAllowed to avoid unnecessary storage reads.
31
+ * @param handle The handle to check
32
+ */
33
+ modifier notPublicHandle(bytes32 handle) {
34
+ require(!HandleUtils.isPublicHandle(handle), PublicHandleACLForbidden());
35
+ _;
36
+ }
37
+
38
+ /// @inheritdoc INoxCompute
39
+ function isAllowed(bytes32 handle, address account) external view override returns (bool) {
40
+ return _isAllowed(handle, account);
41
+ }
42
+
43
+ /// @inheritdoc INoxCompute
44
+ function validateAllowedForAll(
45
+ address account,
46
+ bytes32[] calldata handles
47
+ ) external view override {
48
+ _validateAllowedForAll(account, handles);
49
+ }
50
+
51
+ /// @inheritdoc INoxCompute
52
+ function allow(
53
+ bytes32 handle,
54
+ address account
55
+ ) external override notZeroAddress(account) notPublicHandle(handle) onlyAllowed(handle) {
56
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
57
+ $.admins[handle][account] = true;
58
+ emit Allowed(msg.sender, account, handle);
59
+ }
60
+
61
+ /**
62
+ * @inheritdoc INoxCompute
63
+ * @dev To grant transient access, the caller must already have permission on `handle`.
64
+ * Transient access only lasts for the current transaction. It is the responsibility
65
+ * of the application contract to convert this into persistent access via `allow()`
66
+ * if needed.
67
+ */
68
+ function allowTransient(
69
+ bytes32 handle,
70
+ address account
71
+ ) external override notZeroAddress(account) notPublicHandle(handle) onlyAllowed(handle) {
72
+ _allowTransient(handle, account);
73
+ }
74
+
75
+ /// @inheritdoc INoxCompute
76
+ function disallowTransient(
77
+ bytes32 handle,
78
+ address account
79
+ ) external override notZeroAddress(account) notPublicHandle(handle) onlyAllowed(handle) {
80
+ bytes32 key = keccak256(abi.encodePacked(handle, account));
81
+ assembly {
82
+ tstore(key, 0)
83
+ }
84
+ }
85
+
86
+ /// @inheritdoc INoxCompute
87
+ function addViewer(
88
+ bytes32 handle,
89
+ address viewer
90
+ ) external override notZeroAddress(viewer) notPublicHandle(handle) onlyAllowed(handle) {
91
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
92
+ $.viewers[handle][viewer] = true;
93
+ emit ViewerAdded(msg.sender, viewer, handle);
94
+ }
95
+
96
+ /// @inheritdoc INoxCompute
97
+ function isViewer(bytes32 handle, address viewer) external view override returns (bool) {
98
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
99
+ return
100
+ HandleUtils.isPublicHandle(handle) ||
101
+ $.isPubliclyDecryptable[handle] ||
102
+ $.viewers[handle][viewer] ||
103
+ $.admins[handle][viewer];
104
+ }
105
+
106
+ /// @inheritdoc INoxCompute
107
+ function allowPublicDecryption(
108
+ bytes32 handle
109
+ ) external override notPublicHandle(handle) onlyAllowed(handle) {
110
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
111
+ $.isPubliclyDecryptable[handle] = true;
112
+ emit MarkedAsPubliclyDecryptable(msg.sender, handle);
113
+ }
114
+
115
+ /// @inheritdoc INoxCompute
116
+ function isPubliclyDecryptable(bytes32 handle) external view override returns (bool) {
117
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
118
+ return HandleUtils.isPublicHandle(handle) || $.isPubliclyDecryptable[handle];
119
+ }
120
+
121
+ /**
122
+ * Grants transient access to a handle for an account. This function does not do any
123
+ * checks and should be used with caution.
124
+ * This function is used in two scenarios:
125
+ * - For handles generated off-chain by the Handle Gateway, once the proof has been verified
126
+ * - For handles resulting from on-chain operations, where the caller naturally inherits
127
+ * rights on the output handle
128
+ * @param handle Handle identifier
129
+ * @param account Address of the account
130
+ */
131
+ function _allowTransient(bytes32 handle, address account) internal override {
132
+ // Public handles don't need ACL; skip silently to save gas.
133
+ if (HandleUtils.isPublicHandle(handle)) {
134
+ return;
135
+ }
136
+ bytes32 key = keccak256(abi.encodePacked(handle, account));
137
+ assembly {
138
+ tstore(key, 1)
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Reverts if account lacks access to any handle in the array.
144
+ */
145
+ function _validateAllowedForAll(
146
+ address account,
147
+ bytes32[] memory handles
148
+ ) internal view override {
149
+ for (uint256 i = 0; i < handles.length; i++) {
150
+ if (!_isAllowed(handles[i], account)) {
151
+ revert NotAllowed(handles[i], account);
152
+ }
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Checks if the handle is public, or if account has transient or persistent
158
+ * access to the handle.
159
+ */
160
+ function _isAllowed(bytes32 handle, address account) private view returns (bool) {
161
+ return
162
+ HandleUtils.isPublicHandle(handle) ||
163
+ _isAllowedTransient(handle, account) ||
164
+ _isAllowedPersistent(handle, account);
165
+ }
166
+
167
+ /**
168
+ * Check if an address has transient access to handle.
169
+ * @param handle Handle.
170
+ * @param account Address of the account.
171
+ * @return Returns `true` if the address has transient access to a handle and `false` otherwise.
172
+ */
173
+ function _isAllowedTransient(bytes32 handle, address account) private view returns (bool) {
174
+ bool isAllowedTransient_;
175
+ bytes32 key = keccak256(abi.encodePacked(handle, account));
176
+ assembly {
177
+ isAllowedTransient_ := tload(key)
178
+ }
179
+ return isAllowedTransient_;
180
+ }
181
+
182
+ /**
183
+ * Check if an address has persistent access to handle.
184
+ * @param handle Handle.
185
+ * @param account Address of the account.
186
+ * @return Returns `true` if the address has persistent access to a handle and `false` otherwise.
187
+ */
188
+ function _isAllowedPersistent(bytes32 handle, address account) private view returns (bool) {
189
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
190
+ return $.admins[handle][account];
191
+ }
192
+ }
@@ -0,0 +1,99 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity ^0.8.35;
3
+
4
+ import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
5
+ import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
6
+ import {Common} from "./Common.sol";
7
+
8
+ /**
9
+ * @title Admin
10
+ * @notice Role-based admin layer for NoxCompute.
11
+ * @dev Two roles drive privileged actions:
12
+ * - DEFAULT_ADMIN_ROLE: grants/revokes other roles.
13
+ * - UPGRADER_ROLE: authorizes UUPS upgrades AND updates infrastructure config
14
+ * (KMS public key, gateway address, proof expiration duration).
15
+ */
16
+ abstract contract Admin is Common, AccessControlUpgradeable, UUPSUpgradeable {
17
+ bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
18
+
19
+ /**
20
+ * Sets the KMS public key used for ECIES encryption.
21
+ * Only callable by an UPGRADER_ROLE holder.
22
+ * @param newKmsPublicKey Compressed SEC1 secp256k1 public key (33 bytes)
23
+ */
24
+ function setKmsPublicKey(
25
+ bytes calldata newKmsPublicKey
26
+ ) external override onlyRole(UPGRADER_ROLE) {
27
+ require(newKmsPublicKey.length != 0, InvalidEmptyBytes());
28
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
29
+ $.kmsPublicKey = newKmsPublicKey;
30
+ emit KmsPublicKeyUpdated(newKmsPublicKey);
31
+ }
32
+
33
+ /**
34
+ * Sets Gateway wallet address.
35
+ * Only callable by an UPGRADER_ROLE holder.
36
+ * @param gatewayAddress New Gateway wallet address
37
+ */
38
+ function setGateway(address gatewayAddress) external override onlyRole(UPGRADER_ROLE) {
39
+ require(gatewayAddress != address(0), InvalidZeroAddress());
40
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
41
+ $.gateway = gatewayAddress;
42
+ emit GatewayUpdated(gatewayAddress);
43
+ }
44
+
45
+ /**
46
+ * Sets the proof expiration duration.
47
+ * Only callable by an UPGRADER_ROLE holder.
48
+ * @param newDuration New expiration duration in seconds
49
+ */
50
+ function setProofExpirationDuration(
51
+ uint256 newDuration
52
+ ) external override onlyRole(UPGRADER_ROLE) {
53
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
54
+ $.proofExpirationDuration = newDuration;
55
+ emit ProofExpirationDurationUpdated(newDuration);
56
+ }
57
+
58
+ /**
59
+ * Returns the KMS public key used for ECIES encryption.
60
+ */
61
+ function kmsPublicKey() external view override returns (bytes memory) {
62
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
63
+ return $.kmsPublicKey;
64
+ }
65
+
66
+ /**
67
+ * Returns the Gateway wallet address.
68
+ */
69
+ function gateway() external view override returns (address) {
70
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
71
+ return $.gateway;
72
+ }
73
+
74
+ /**
75
+ * Returns the proof expiration duration in seconds.
76
+ */
77
+ function proofExpirationDuration() external view override returns (uint256) {
78
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
79
+ return $.proofExpirationDuration;
80
+ }
81
+
82
+ /**
83
+ * Authorizes contract upgrades only by an UPGRADER_ROLE holder.
84
+ */
85
+ function _authorizeUpgrade(
86
+ address /*newImplementation*/
87
+ ) internal override onlyRole(UPGRADER_ROLE) {}
88
+
89
+ /**
90
+ * @dev Initializes AccessControl and grants the defined roles.
91
+ */
92
+ function _initAccessControl(address admin, address upgrader) internal {
93
+ require(admin != address(0), InvalidZeroAddress());
94
+ require(upgrader != address(0), InvalidZeroAddress());
95
+ __AccessControl_init();
96
+ _grantRole(DEFAULT_ADMIN_ROLE, admin);
97
+ _grantRole(UPGRADER_ROLE, upgrader);
98
+ }
99
+ }
@@ -0,0 +1,55 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity ^0.8.35;
3
+
4
+ import {INoxCompute} from "../interfaces/INoxCompute.sol";
5
+
6
+ /**
7
+ * @title Common
8
+ * @notice Shared base for all modules. Defines the ERC7201 storage layout,
9
+ * the storage accessor, and virtual declarations for cross-module functions.
10
+ */
11
+ abstract contract Common is INoxCompute {
12
+ // TODO: remove `slither-disable-next-line` once Slither supports the `erc7201` builtin (added in solc 0.8.35).
13
+ // slither-disable-next-line uninitialized-state
14
+ bytes32 private constant NOX_COMPUTE_STORAGE_LOCATION = bytes32(
15
+ erc7201("nox.storage.NoxCompute")
16
+ );
17
+
18
+ /// @custom:storage-location erc7201:nox.storage.NoxCompute
19
+ struct NoxComputeStorage {
20
+ // An admin of a handle can:
21
+ // - use it as a computation input
22
+ // - decrypt its associated data off-chain
23
+ // - make it publicly decryptable
24
+ // - add other admins and viewers
25
+ mapping(bytes32 handleId => mapping(address => bool)) admins;
26
+ // A viewer of a handle can only decrypt its associated data off-chain.
27
+ //TODO: Make viewer expirable
28
+ mapping(bytes32 handleId => mapping(address => bool)) viewers;
29
+ // Handles that are publicly decryptable
30
+ mapping(bytes32 handle => bool) isPubliclyDecryptable;
31
+ bytes kmsPublicKey;
32
+ address gateway;
33
+ uint256 proofExpirationDuration;
34
+ // Counter used to guarantee handle uniqueness when all operands are public handles
35
+ uint256 uniqueSeedCounter;
36
+ }
37
+
38
+ function _getNoxComputeStorage() internal pure returns (NoxComputeStorage storage $) {
39
+ bytes32 slot = NOX_COMPUTE_STORAGE_LOCATION;
40
+ assembly {
41
+ $.slot := slot
42
+ }
43
+ }
44
+
45
+ // ----- Functions used cross-modules -----
46
+
47
+ // Implemented by ACL, called by Compute.
48
+ function _allowTransient(bytes32 handle, address account) internal virtual;
49
+
50
+ // Implemented by ACL, called by Compute.
51
+ function _validateAllowedForAll(
52
+ address account,
53
+ bytes32[] memory handles
54
+ ) internal view virtual;
55
+ }