@iexec-nox/nox-protocol-contracts 0.1.0 → 0.2.1

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.
@@ -0,0 +1,904 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity ^0.8.27;
3
+
4
+ import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
5
+ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
6
+ import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
7
+ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
8
+ import {INoxCompute} from "./interfaces/INoxCompute.sol";
9
+ import {HandleUtils} from "./shared/HandleUtils.sol";
10
+ import {TEEType, TypeUtils} from "./shared/TypeUtils.sol";
11
+
12
+ /**
13
+ * @title NoxCompute
14
+ * This contract is the main entry point to the TEE compute functionalities of the Nox protocol.
15
+ * It's role includes:
16
+ * - Managing the access control list (ACL) for encrypted handles
17
+ * - Validating handle proofs issued by a trusted gateway
18
+ * - Facilitating the conversion of plaintext values to encrypted values
19
+ * - Triggering off-chain TEE computations through event emissions
20
+ *
21
+ * @dev Using non upgradeable EIP712 is safe here because it has no storage and the config is saved
22
+ * in immutable variables which should be enough here since we don't use multiple proxies with the
23
+ * same implementation.
24
+ */
25
+ contract NoxCompute is INoxCompute, UUPSUpgradeable, OwnableUpgradeable, EIP712 {
26
+ /// @custom:storage-location erc7201:nox.storage.NoxCompute
27
+ struct NoxComputeStorage {
28
+ // An admin of a handle can:
29
+ // - use it as a computation input
30
+ // - decrypt its associated data off-chain
31
+ // - make it publicly decryptable
32
+ // - add other admins and viewers
33
+ mapping(bytes32 handleId => mapping(address => bool)) admins;
34
+ // A viewer of a handle can only decrypt its associated data off-chain.
35
+ //TODO: Make viewer expirable
36
+ mapping(bytes32 handleId => mapping(address => bool)) viewers;
37
+ // Handles that are publicly decryptable
38
+ mapping(bytes32 handle => bool) isPubliclyDecryptable;
39
+ bytes kmsPublicKey;
40
+ address gateway;
41
+ uint256 proofExpirationDuration;
42
+ // Counter used to guarantee handle uniqueness when all operands are public handles
43
+ uint256 uniqueSeedCounter;
44
+ }
45
+
46
+ uint8 private constant HANDLE_VERSION = 0;
47
+
48
+ // keccak256(abi.encode(uint256(keccak256("nox.storage.NoxCompute")) - 1)) & ~bytes32(uint256(0xff))
49
+ bytes32 private constant NOX_COMPUTE_STORAGE_LOCATION =
50
+ 0x118a408ef9c0c38d6620cca4d300c2ce1c4f4cbcd93520940a6461e96acdcd00;
51
+ bytes32 public constant HANDLE_PROOF_TYPEHASH = keccak256(
52
+ "HandleProof(bytes32 handle,address owner,address app,uint256 createdAt)"
53
+ );
54
+ bytes32 public constant DECRYPTION_PROOF_TYPEHASH = keccak256(
55
+ "DecryptionProof(bytes32 handle,bytes decryptedResult)"
56
+ );
57
+
58
+ /**
59
+ * Ensures the account address is not zero
60
+ * @param account The address to validate
61
+ */
62
+ modifier notZeroAddress(address account) {
63
+ require(account != address(0), InvalidZeroAddress());
64
+ _;
65
+ }
66
+
67
+ /**
68
+ * Ensures the sender is allowed to access the handle
69
+ * @param handle The handle to check access for
70
+ */
71
+ modifier onlyAllowed(bytes32 handle) {
72
+ require(isAllowed(handle, msg.sender), UnauthorizedSender(msg.sender));
73
+ _;
74
+ }
75
+
76
+ /**
77
+ * Prevents ACL mutations on public handles.
78
+ * Applied before onlyAllowed to avoid unnecessary storage reads.
79
+ * @param handle The handle to check
80
+ */
81
+ modifier notPublicHandle(bytes32 handle) {
82
+ require(!HandleUtils.isPublicHandle(handle), PublicHandleACLForbidden());
83
+ _;
84
+ }
85
+
86
+ /**
87
+ * @custom:oz-upgrades-unsafe-allow constructor
88
+ */
89
+ constructor() EIP712("NoxCompute", "1") {
90
+ _disableInitializers();
91
+ }
92
+
93
+ /**
94
+ * Initializes the proxy contract state.
95
+ * @param initialOwner Initial owner address
96
+ * @param kmsPublicKey_ KMS public key for ECIES encryption
97
+ */
98
+ function initialize(address initialOwner, bytes calldata kmsPublicKey_) public initializer {
99
+ require(kmsPublicKey_.length != 0, InvalidEmptyBytes());
100
+ __Ownable_init(initialOwner);
101
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
102
+ $.proofExpirationDuration = 1 hours;
103
+ $.kmsPublicKey = kmsPublicKey_;
104
+ _emitZeroHandleSeeds();
105
+ }
106
+
107
+ /**
108
+ * @notice Initializer of 0.1.1 upgrade for already deployed proxies.
109
+ * @notice Emits zero handle seeds for existing proxies.
110
+ * @dev The same logic is also called in `initialize()` for fresh deployments.
111
+ * @dev The call to this function does not need to be protected because it does
112
+ * not do any critical operations.
113
+ */
114
+ function initializeV2() public reinitializer(2) {
115
+ _emitZeroHandleSeeds();
116
+ }
117
+
118
+ // ----------- ACL management -----------
119
+
120
+ /// @inheritdoc INoxCompute
121
+ function allow(
122
+ bytes32 handle,
123
+ address account
124
+ ) external override notZeroAddress(account) notPublicHandle(handle) onlyAllowed(handle) {
125
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
126
+ $.admins[handle][account] = true;
127
+ emit Allowed(msg.sender, account, handle);
128
+ }
129
+
130
+ /**
131
+ * @inheritdoc INoxCompute
132
+ * @dev To grant transient access, the caller must already have permission on `handle`.
133
+ * Transient access only lasts for the current transaction. It is the responsibility
134
+ * of the application contract to convert this into persistent access via `allow()`
135
+ * if needed.
136
+ */
137
+ function allowTransient(
138
+ bytes32 handle,
139
+ address account
140
+ ) external override notZeroAddress(account) notPublicHandle(handle) onlyAllowed(handle) {
141
+ _allowTransient(handle, account);
142
+ }
143
+
144
+ /// @inheritdoc INoxCompute
145
+ function disallowTransient(
146
+ bytes32 handle,
147
+ address account
148
+ ) external override notZeroAddress(account) notPublicHandle(handle) onlyAllowed(handle) {
149
+ bytes32 key = keccak256(abi.encodePacked(handle, account));
150
+ assembly {
151
+ tstore(key, 0)
152
+ }
153
+ }
154
+
155
+ /// @inheritdoc INoxCompute
156
+ function isAllowed(bytes32 handle, address account) public view override returns (bool) {
157
+ return
158
+ HandleUtils.isPublicHandle(handle) ||
159
+ _isAllowedTransient(handle, account) ||
160
+ _isAllowedPersistent(handle, account);
161
+ }
162
+
163
+ /// @inheritdoc INoxCompute
164
+ function validateAllowedForAll(address account, bytes32[] memory handles) public view override {
165
+ for (uint256 i = 0; i < handles.length; i++) {
166
+ if (!isAllowed(handles[i], account)) {
167
+ revert NotAllowed(handles[i], account);
168
+ }
169
+ }
170
+ }
171
+
172
+ /// @inheritdoc INoxCompute
173
+ function addViewer(
174
+ bytes32 handle,
175
+ address viewer
176
+ ) external override notZeroAddress(viewer) notPublicHandle(handle) onlyAllowed(handle) {
177
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
178
+ $.viewers[handle][viewer] = true;
179
+ emit ViewerAdded(msg.sender, viewer, handle);
180
+ }
181
+
182
+ /// @inheritdoc INoxCompute
183
+ function isViewer(bytes32 handle, address viewer) external view override returns (bool) {
184
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
185
+ return
186
+ HandleUtils.isPublicHandle(handle) ||
187
+ $.isPubliclyDecryptable[handle] ||
188
+ $.viewers[handle][viewer] ||
189
+ $.admins[handle][viewer];
190
+ }
191
+
192
+ /// @inheritdoc INoxCompute
193
+ function allowPublicDecryption(
194
+ bytes32 handle
195
+ ) external override notPublicHandle(handle) onlyAllowed(handle) {
196
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
197
+ $.isPubliclyDecryptable[handle] = true;
198
+ emit MarkedAsPubliclyDecryptable(msg.sender, handle);
199
+ }
200
+
201
+ /// @inheritdoc INoxCompute
202
+ function isPubliclyDecryptable(bytes32 handle) external view override returns (bool) {
203
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
204
+ return HandleUtils.isPublicHandle(handle) || $.isPubliclyDecryptable[handle];
205
+ }
206
+
207
+ /**
208
+ * Grants transient access to a handle for an account. This function does not do any
209
+ * checks and should be used with caution.
210
+ * This function is used in two scenarios:
211
+ * - For handles generated off-chain by the Handle Gateway, once the proof has been verified
212
+ * - For handles resulting from on-chain operations, where the caller naturally inherits
213
+ * rights on the output handle
214
+ * @param handle Handle identifier
215
+ * @param account Address of the account
216
+ */
217
+ function _allowTransient(bytes32 handle, address account) private {
218
+ // Public handles don't need ACL; skip silently to save gas.
219
+ if (HandleUtils.isPublicHandle(handle)) {
220
+ return;
221
+ }
222
+ bytes32 key = keccak256(abi.encodePacked(handle, account));
223
+ assembly {
224
+ tstore(key, 1)
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Check if an address has transient access to handle.
230
+ * @param handle Handle.
231
+ * @param account Address of the account.
232
+ * @return Returns `true` if the address has transient access to a handle and `false` otherwise.
233
+ */
234
+ function _isAllowedTransient(bytes32 handle, address account) private view returns (bool) {
235
+ bool isAllowedTransient_;
236
+ bytes32 key = keccak256(abi.encodePacked(handle, account));
237
+ assembly {
238
+ isAllowedTransient_ := tload(key)
239
+ }
240
+ return isAllowedTransient_;
241
+ }
242
+
243
+ /**
244
+ * Check if an address has persistent access to handle.
245
+ * @param handle Handle.
246
+ * @param account Address of the account.
247
+ * @return Returns `true` if the address has persistent access to a handle and `false` otherwise.
248
+ */
249
+ function _isAllowedPersistent(bytes32 handle, address account) private view returns (bool) {
250
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
251
+ return $.admins[handle][account];
252
+ }
253
+
254
+ // ----------- Compute functions -----------
255
+
256
+ /// @inheritdoc INoxCompute
257
+ function wrapAsPublicHandle(bytes32 value, TEEType teeType) external returns (bytes32 result) {
258
+ bytes32[] memory operands = new bytes32[](1);
259
+ operands[0] = value;
260
+ // Deterministic handle: same (value, type) always produces the same handle
261
+ result = _generatePublicHandle(Operator.WrapAsPublicHandle, operands, teeType);
262
+ _allowTransient(result, msg.sender);
263
+ emit WrapAsPublicHandle(msg.sender, value, teeType, result);
264
+ }
265
+
266
+ /// @inheritdoc INoxCompute
267
+ function validateInputProof(
268
+ bytes32 handle,
269
+ address owner,
270
+ bytes calldata proof,
271
+ TEEType teeType
272
+ ) public {
273
+ bytes4 chainIdInHandle = bytes4(handle << (1 * 8));
274
+ require(
275
+ chainIdInHandle == bytes4(uint32(block.chainid)),
276
+ InvalidProof(proof, "Handle chain id mismatch")
277
+ );
278
+ require(TypeUtils.typeOf(handle) == teeType, InvalidProof(proof, "Handle type mismatch"));
279
+ require(proof.length == 137, InvalidProof(proof, "Invalid proof length"));
280
+ address ownerInProof;
281
+ address appInProof;
282
+ uint256 createdAt;
283
+ assembly {
284
+ ownerInProof := shr(96, calldataload(proof.offset))
285
+ appInProof := shr(96, calldataload(add(proof.offset, 20)))
286
+ createdAt := calldataload(add(proof.offset, 40))
287
+ }
288
+ bytes calldata signature = proof[72:137];
289
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
290
+ require(
291
+ block.timestamp <= createdAt + $.proofExpirationDuration,
292
+ InvalidProof(proof, "Proof expired")
293
+ );
294
+ require(appInProof == msg.sender, InvalidProof(proof, "App mismatch"));
295
+ require(ownerInProof == owner, InvalidProof(proof, "Owner mismatch"));
296
+ bytes32 eip712MessageHash = _hashTypedDataV4(
297
+ keccak256(
298
+ abi.encode(HANDLE_PROOF_TYPEHASH, handle, ownerInProof, appInProof, createdAt)
299
+ )
300
+ );
301
+ require(
302
+ ECDSA.recover(eip712MessageHash, signature) == $.gateway,
303
+ InvalidProof(proof, "Invalid signature")
304
+ );
305
+ // Give caller contract transient access to the handle.
306
+ _allowTransient(handle, msg.sender);
307
+ }
308
+
309
+ /// @inheritdoc INoxCompute
310
+ function validateDecryptionProof(
311
+ bytes32 handle,
312
+ bytes calldata decryptionProof
313
+ ) external view returns (bytes memory) {
314
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
315
+ require(decryptionProof.length >= 65, InvalidProof(decryptionProof, "Proof too short"));
316
+ bytes calldata decryptedResult = decryptionProof[65:];
317
+ bytes32 eip712MessageHash = _hashTypedDataV4(
318
+ keccak256(abi.encode(DECRYPTION_PROOF_TYPEHASH, handle, keccak256(decryptedResult)))
319
+ );
320
+ require(
321
+ ECDSA.recoverCalldata(eip712MessageHash, decryptionProof[:65]) == $.gateway,
322
+ InvalidProof(decryptionProof, "Invalid signature")
323
+ );
324
+ return decryptedResult;
325
+ }
326
+
327
+ /// @inheritdoc INoxCompute
328
+ function add(
329
+ bytes32 leftHandOperand,
330
+ bytes32 rightHandOperand
331
+ ) external returns (bytes32 result) {
332
+ bytes32[] memory operands = new bytes32[](2);
333
+ operands[0] = leftHandOperand;
334
+ operands[1] = rightHandOperand;
335
+ (, result) = _executeArithmeticOperation(Operator.Add, operands, false);
336
+ emit Add(msg.sender, leftHandOperand, rightHandOperand, result);
337
+ }
338
+
339
+ /// @inheritdoc INoxCompute
340
+ function sub(
341
+ bytes32 leftHandOperand,
342
+ bytes32 rightHandOperand
343
+ ) external returns (bytes32 result) {
344
+ bytes32[] memory operands = new bytes32[](2);
345
+ operands[0] = leftHandOperand;
346
+ operands[1] = rightHandOperand;
347
+ (, result) = _executeArithmeticOperation(Operator.Sub, operands, false);
348
+ emit Sub(msg.sender, leftHandOperand, rightHandOperand, result);
349
+ }
350
+
351
+ /// @inheritdoc INoxCompute
352
+ function div(bytes32 numerator, bytes32 denominator) external returns (bytes32 result) {
353
+ bytes32[] memory operands = new bytes32[](2);
354
+ operands[0] = numerator;
355
+ operands[1] = denominator;
356
+ (, result) = _executeArithmeticOperation(Operator.Div, operands, false);
357
+ emit Div(msg.sender, numerator, denominator, result);
358
+ }
359
+
360
+ /// @inheritdoc INoxCompute
361
+ function mul(
362
+ bytes32 leftHandOperand,
363
+ bytes32 rightHandOperand
364
+ ) external returns (bytes32 result) {
365
+ bytes32[] memory operands = new bytes32[](2);
366
+ operands[0] = leftHandOperand;
367
+ operands[1] = rightHandOperand;
368
+ (, result) = _executeArithmeticOperation(Operator.Mul, operands, false);
369
+ emit Mul(msg.sender, leftHandOperand, rightHandOperand, result);
370
+ }
371
+
372
+ /// @inheritdoc INoxCompute
373
+ function eq(
374
+ bytes32 leftHandOperand,
375
+ bytes32 rightHandOperand
376
+ ) external returns (bytes32 result) {
377
+ result = _executeComparisonOperation(Operator.Eq, leftHandOperand, rightHandOperand);
378
+ emit Eq(msg.sender, leftHandOperand, rightHandOperand, result);
379
+ }
380
+
381
+ /// @inheritdoc INoxCompute
382
+ function ne(
383
+ bytes32 leftHandOperand,
384
+ bytes32 rightHandOperand
385
+ ) external returns (bytes32 result) {
386
+ result = _executeComparisonOperation(Operator.Ne, leftHandOperand, rightHandOperand);
387
+ emit Ne(msg.sender, leftHandOperand, rightHandOperand, result);
388
+ }
389
+
390
+ /// @inheritdoc INoxCompute
391
+ function lt(
392
+ bytes32 leftHandOperand,
393
+ bytes32 rightHandOperand
394
+ ) external returns (bytes32 result) {
395
+ result = _executeComparisonOperation(Operator.Lt, leftHandOperand, rightHandOperand);
396
+ emit Lt(msg.sender, leftHandOperand, rightHandOperand, result);
397
+ }
398
+
399
+ /// @inheritdoc INoxCompute
400
+ function le(
401
+ bytes32 leftHandOperand,
402
+ bytes32 rightHandOperand
403
+ ) external returns (bytes32 result) {
404
+ result = _executeComparisonOperation(Operator.Le, leftHandOperand, rightHandOperand);
405
+ emit Le(msg.sender, leftHandOperand, rightHandOperand, result);
406
+ }
407
+
408
+ /// @inheritdoc INoxCompute
409
+ function gt(
410
+ bytes32 leftHandOperand,
411
+ bytes32 rightHandOperand
412
+ ) external returns (bytes32 result) {
413
+ result = _executeComparisonOperation(Operator.Gt, leftHandOperand, rightHandOperand);
414
+ emit Gt(msg.sender, leftHandOperand, rightHandOperand, result);
415
+ }
416
+
417
+ /// @inheritdoc INoxCompute
418
+ function ge(
419
+ bytes32 leftHandOperand,
420
+ bytes32 rightHandOperand
421
+ ) external returns (bytes32 result) {
422
+ result = _executeComparisonOperation(Operator.Ge, leftHandOperand, rightHandOperand);
423
+ emit Ge(msg.sender, leftHandOperand, rightHandOperand, result);
424
+ }
425
+
426
+ /// @inheritdoc INoxCompute
427
+ function safeAdd(
428
+ bytes32 leftHandOperand,
429
+ bytes32 rightHandOperand
430
+ ) external returns (bytes32 success, bytes32 result) {
431
+ bytes32[] memory operands = new bytes32[](2);
432
+ operands[0] = leftHandOperand;
433
+ operands[1] = rightHandOperand;
434
+ (success, result) = _executeArithmeticOperation(Operator.SafeAdd, operands, true);
435
+ emit SafeAdd(msg.sender, leftHandOperand, rightHandOperand, success, result);
436
+ }
437
+
438
+ /// @inheritdoc INoxCompute
439
+ function safeSub(
440
+ bytes32 leftHandOperand,
441
+ bytes32 rightHandOperand
442
+ ) external returns (bytes32 success, bytes32 result) {
443
+ bytes32[] memory operands = new bytes32[](2);
444
+ operands[0] = leftHandOperand;
445
+ operands[1] = rightHandOperand;
446
+ (success, result) = _executeArithmeticOperation(Operator.SafeSub, operands, true);
447
+ emit SafeSub(msg.sender, leftHandOperand, rightHandOperand, success, result);
448
+ }
449
+
450
+ /// @inheritdoc INoxCompute
451
+ function safeMul(
452
+ bytes32 leftHandOperand,
453
+ bytes32 rightHandOperand
454
+ ) external returns (bytes32 success, bytes32 result) {
455
+ bytes32[] memory operands = new bytes32[](2);
456
+ operands[0] = leftHandOperand;
457
+ operands[1] = rightHandOperand;
458
+ (success, result) = _executeArithmeticOperation(Operator.SafeMul, operands, true);
459
+ emit SafeMul(msg.sender, leftHandOperand, rightHandOperand, success, result);
460
+ }
461
+
462
+ /// @inheritdoc INoxCompute
463
+ function safeDiv(
464
+ bytes32 numerator,
465
+ bytes32 denominator
466
+ ) external returns (bytes32 success, bytes32 result) {
467
+ bytes32[] memory operands = new bytes32[](2);
468
+ operands[0] = numerator;
469
+ operands[1] = denominator;
470
+ (success, result) = _executeArithmeticOperation(Operator.SafeDiv, operands, true);
471
+ emit SafeDiv(msg.sender, numerator, denominator, success, result);
472
+ }
473
+
474
+ /// @inheritdoc INoxCompute
475
+ function select(
476
+ bytes32 condition,
477
+ bytes32 ifTrue,
478
+ bytes32 ifFalse
479
+ ) external returns (bytes32 result) {
480
+ require(
481
+ condition != bytes32(0) && ifTrue != bytes32(0) && ifFalse != bytes32(0),
482
+ UndefinedHandle()
483
+ );
484
+ require(TypeUtils.typeOf(condition) == TEEType.Bool, UnsupportedType());
485
+ TEEType resultType = TypeUtils.typeOf(ifTrue);
486
+ require(resultType == TypeUtils.typeOf(ifFalse), IncompatibleTypes());
487
+ bytes32[] memory operands = new bytes32[](3);
488
+ operands[0] = condition;
489
+ operands[1] = ifTrue;
490
+ operands[2] = ifFalse;
491
+ validateAllowedForAll(msg.sender, operands);
492
+ result = _generateHandle(Operator.Select, operands, resultType);
493
+ _allowTransient(result, msg.sender);
494
+ emit Select(msg.sender, condition, ifTrue, ifFalse, result);
495
+ }
496
+
497
+ /// @inheritdoc INoxCompute
498
+ function transfer(
499
+ bytes32 balanceFrom,
500
+ bytes32 balanceTo,
501
+ bytes32 amount
502
+ ) external returns (bytes32 success, bytes32 newBalanceFrom, bytes32 newBalanceTo) {
503
+ bytes32[] memory operands = new bytes32[](3);
504
+ operands[0] = balanceFrom;
505
+ operands[1] = balanceTo;
506
+ operands[2] = amount;
507
+ (success, newBalanceFrom, newBalanceTo) = _executeCompositeOperation(
508
+ Operator.Transfer,
509
+ operands
510
+ );
511
+ emit Transfer(
512
+ msg.sender,
513
+ balanceFrom,
514
+ balanceTo,
515
+ amount,
516
+ success,
517
+ newBalanceFrom,
518
+ newBalanceTo
519
+ );
520
+ }
521
+
522
+ /// @inheritdoc INoxCompute
523
+ function mint(
524
+ bytes32 balanceTo,
525
+ bytes32 amount,
526
+ bytes32 totalSupply
527
+ ) external returns (bytes32 success, bytes32 newBalanceTo, bytes32 newTotalSupply) {
528
+ bytes32[] memory operands = new bytes32[](3);
529
+ operands[0] = balanceTo;
530
+ operands[1] = amount;
531
+ operands[2] = totalSupply;
532
+ (success, newBalanceTo, newTotalSupply) = _executeCompositeOperation(
533
+ Operator.Mint,
534
+ operands
535
+ );
536
+ emit Mint(
537
+ msg.sender,
538
+ balanceTo,
539
+ amount,
540
+ totalSupply,
541
+ success,
542
+ newBalanceTo,
543
+ newTotalSupply
544
+ );
545
+ }
546
+
547
+ /// @inheritdoc INoxCompute
548
+ function burn(
549
+ bytes32 balanceFrom,
550
+ bytes32 amount,
551
+ bytes32 totalSupply
552
+ ) external returns (bytes32 success, bytes32 newBalanceFrom, bytes32 newTotalSupply) {
553
+ bytes32[] memory operands = new bytes32[](3);
554
+ operands[0] = balanceFrom;
555
+ operands[1] = amount;
556
+ operands[2] = totalSupply;
557
+ (success, newBalanceFrom, newTotalSupply) = _executeCompositeOperation(
558
+ Operator.Burn,
559
+ operands
560
+ );
561
+ emit Burn(
562
+ msg.sender,
563
+ balanceFrom,
564
+ amount,
565
+ totalSupply,
566
+ success,
567
+ newBalanceFrom,
568
+ newTotalSupply
569
+ );
570
+ }
571
+
572
+ /**
573
+ * Executes an arithmetic operation on N encrypted handles.
574
+ * All operands must share the same type as the first operand, which also determines the result type.
575
+ * Verifies ACL permissions for all operands, checks type compatibility,
576
+ * generates result handle(s), and grants transient access to the caller.
577
+ *
578
+ * When `isSafeOperation` is true, generates an additional Bool success handle (outputIndex 1)
579
+ * and the result handle at outputIndex 0, enabling overflow/underflow detection.
580
+ *
581
+ * @dev Reverts with NotAllowed if caller lacks permission on any operand
582
+ * @dev Reverts with IncompatibleTypes if operand types don't match
583
+ *
584
+ * @param operator The operator to apply
585
+ * @param operands Array of operand handles
586
+ * @param isSafeOperation Whether to generate a Bool success handle alongside the result
587
+ * @return success The success flag handle (Bool type), bytes32(0) if not safe operation
588
+ * @return result The resulting encrypted handle
589
+ */
590
+ function _executeArithmeticOperation(
591
+ Operator operator,
592
+ bytes32[] memory operands,
593
+ bool isSafeOperation
594
+ ) private returns (bytes32 success, bytes32 result) {
595
+ _requireDefinedHandles(operands);
596
+ TEEType resultType = TypeUtils.typeOf(operands[0]);
597
+ TypeUtils.validateArithmeticType(resultType);
598
+ for (uint256 i = 1; i < operands.length; i++) {
599
+ if (resultType != TypeUtils.typeOf(operands[i])) {
600
+ revert IncompatibleTypes();
601
+ }
602
+ }
603
+ validateAllowedForAll(msg.sender, operands);
604
+ // Outputs differ by outputIndex and type, so they can safely share the same seed
605
+ uint256 uniqueSeed = _generateHandleUniqueSeed(operands);
606
+ result = _generateHandle(
607
+ operator,
608
+ operands,
609
+ resultType,
610
+ 0,
611
+ uniqueSeed,
612
+ HandleUtils.ATTR_IS_UNIQUE_HANDLE
613
+ );
614
+ _allowTransient(result, msg.sender);
615
+ if (isSafeOperation) {
616
+ success = _generateHandle(
617
+ operator,
618
+ operands,
619
+ TEEType.Bool,
620
+ 1,
621
+ uniqueSeed,
622
+ HandleUtils.ATTR_IS_UNIQUE_HANDLE
623
+ );
624
+ _allowTransient(success, msg.sender);
625
+ }
626
+ }
627
+
628
+ /**
629
+ * Executes a comparison operation on two encrypted handles.
630
+ * Both operands must share the same arithmetic type.
631
+ * Verifies ACL permissions for both operands, checks type compatibility,
632
+ * generates a Bool result handle, and grants transient access to the caller.
633
+ *
634
+ * @dev Reverts with NotAllowed if caller lacks permission on any operand
635
+ * @dev Reverts with IncompatibleTypes if operand types don't match
636
+ *
637
+ * @param operator The comparison operator to apply
638
+ * @param leftOperand Left-hand side operand handle
639
+ * @param rightOperand Right-hand side operand handle
640
+ * @return result The resulting Bool handle
641
+ */
642
+ function _executeComparisonOperation(
643
+ Operator operator,
644
+ bytes32 leftOperand,
645
+ bytes32 rightOperand
646
+ ) private returns (bytes32 result) {
647
+ require(leftOperand != bytes32(0) && rightOperand != bytes32(0), UndefinedHandle());
648
+ TEEType operandType = TypeUtils.typeOf(leftOperand);
649
+ TypeUtils.validateArithmeticType(operandType);
650
+ require(TypeUtils.typeOf(rightOperand) == operandType, IncompatibleTypes());
651
+ bytes32[] memory operands = new bytes32[](2);
652
+ operands[0] = leftOperand;
653
+ operands[1] = rightOperand;
654
+ validateAllowedForAll(msg.sender, operands);
655
+ result = _generateHandle(operator, operands, TEEType.Bool);
656
+ _allowTransient(result, msg.sender);
657
+ }
658
+
659
+ /**
660
+ * Executes a composite operation on 3 encrypted handles (e.g. transfer, mint, burn).
661
+ * All operands must share the same arithmetic type.
662
+ * Generates 3 output handles: a Bool success flag and two result handles of the input type.
663
+ *
664
+ * @param operator The operator to apply
665
+ * @param operands Array of 3 operand handles
666
+ * @return success The success flag handle (Bool type)
667
+ * @return result1 First result handle
668
+ * @return result2 Second result handle
669
+ */
670
+ function _executeCompositeOperation(
671
+ Operator operator,
672
+ bytes32[] memory operands
673
+ ) private returns (bytes32 success, bytes32 result1, bytes32 result2) {
674
+ _requireDefinedHandles(operands);
675
+ TEEType resultType = TypeUtils.typeOf(operands[0]);
676
+ TypeUtils.validateArithmeticType(resultType);
677
+ for (uint256 i = 1; i < operands.length; i++) {
678
+ if (resultType != TypeUtils.typeOf(operands[i])) {
679
+ revert IncompatibleTypes();
680
+ }
681
+ }
682
+ validateAllowedForAll(msg.sender, operands);
683
+ // Outputs differ by outputIndex and type, so they can safely share the same seed
684
+ uint256 uniqueSeed = _generateHandleUniqueSeed(operands);
685
+ success = _generateHandle(
686
+ operator,
687
+ operands,
688
+ TEEType.Bool,
689
+ 0,
690
+ uniqueSeed,
691
+ HandleUtils.ATTR_IS_UNIQUE_HANDLE
692
+ );
693
+ result1 = _generateHandle(
694
+ operator,
695
+ operands,
696
+ resultType,
697
+ 1,
698
+ uniqueSeed,
699
+ HandleUtils.ATTR_IS_UNIQUE_HANDLE
700
+ );
701
+ result2 = _generateHandle(
702
+ operator,
703
+ operands,
704
+ resultType,
705
+ 2,
706
+ uniqueSeed,
707
+ HandleUtils.ATTR_IS_UNIQUE_HANDLE
708
+ );
709
+ _allowTransient(success, msg.sender);
710
+ _allowTransient(result1, msg.sender);
711
+ _allowTransient(result2, msg.sender);
712
+ }
713
+
714
+ /**
715
+ * Reverts if any operand is bytes32(0) (undefined handle).
716
+ */
717
+ function _requireDefinedHandles(bytes32[] memory operands) private pure {
718
+ for (uint256 i = 0; i < operands.length; i++) {
719
+ require(operands[i] != bytes32(0), UndefinedHandle());
720
+ }
721
+ }
722
+
723
+ /**
724
+ * @dev Alias for _generateHandle producing a public handle (outputIndex=0, uniqueSeed=0, attrs=0x00).
725
+ */
726
+ function _generatePublicHandle(
727
+ Operator operator,
728
+ bytes32[] memory operands,
729
+ TEEType handleType
730
+ ) private view returns (bytes32 result) {
731
+ result = _generateHandle(operator, operands, handleType, 0, 0, bytes1(0x00));
732
+ }
733
+
734
+ /**
735
+ * @dev Alias for single-output confidential operations (outputIndex=0, attrs=ATTR_IS_UNIQUE_HANDLE).
736
+ * Computes the uniqueness seed internally.
737
+ * Must NOT be called multiple times for multi-output operations (the seed counter would diverge).
738
+ */
739
+ function _generateHandle(
740
+ Operator operator,
741
+ bytes32[] memory operands,
742
+ TEEType handleType
743
+ ) private returns (bytes32 result) {
744
+ uint256 uniqueSeed = _generateHandleUniqueSeed(operands);
745
+ result = _generateHandle(
746
+ operator,
747
+ operands,
748
+ handleType,
749
+ 0,
750
+ uniqueSeed,
751
+ HandleUtils.ATTR_IS_UNIQUE_HANDLE
752
+ );
753
+ }
754
+
755
+ /**
756
+ * Generates a complete handle from an operator and its operands.
757
+ *
758
+ * Pre-handle format:
759
+ * keccak256(abi.encode(
760
+ * operator, // Operator identifier (e.g., Add, Sub, WrapAsPublicHandle)
761
+ * operands, // Array of operand handles (or plaintext value)
762
+ * address(this), // NoxCompute contract address
763
+ * uniqueSeed, // Uniqueness seed (0 or counter value)
764
+ * outputIndex // For operations that return multiple outputs
765
+ * ))
766
+ *
767
+ * Handle format (32 bytes):
768
+ * [0] : Handle version
769
+ * [1-4] : Chain ID (4 bytes, uint32)
770
+ * [5] : TEE type
771
+ * [6] : Attributes (bit 0 = isUniqueHandle)
772
+ * [7-31] : Truncated pre-handle hash (25 bytes)
773
+ *
774
+ * @param operator The operator to apply
775
+ * @param operands Array of operand handles
776
+ * @param handleType The TEE type to encode in the handle
777
+ * @param outputIndex Index for operations returning multiple outputs
778
+ * @param uniqueSeed Uniqueness seed (0 for wrapAsPublicHandle and unique operands)
779
+ * @param attrs Attributes byte (0x00 for public handle, 0x01 for confidential)
780
+ * @return result The complete handle with metadata appended
781
+ */
782
+ function _generateHandle(
783
+ Operator operator,
784
+ bytes32[] memory operands,
785
+ TEEType handleType,
786
+ uint8 outputIndex,
787
+ uint256 uniqueSeed,
788
+ bytes1 attrs
789
+ ) private view returns (bytes32 result) {
790
+ result = keccak256(abi.encode(operator, operands, address(this), uniqueSeed, outputIndex));
791
+ // Shift hash to bytes 7-31 (truncate to 25 bytes), leaving bytes 0-6 free for metadata.
792
+ result = result >> (7 * 8);
793
+ result = result | bytes32(bytes1(uint8(HANDLE_VERSION)));
794
+ result = result | (bytes32(bytes4(uint32(block.chainid))) >> (1 * 8));
795
+ result = result | (bytes32(bytes1(uint8(handleType))) >> (5 * 8));
796
+ result = result | (bytes32(attrs) >> (6 * 8));
797
+ }
798
+
799
+ /**
800
+ * Determines the uniqueness seed for a confidential operation.
801
+ * If at least one operand has isUniqueHandle=1, returns 0 (no storage access needed).
802
+ * If all operands are public handles, increments a storage counter to guarantee uniqueness.
803
+ * @param operands Array of operand handles
804
+ * @return The uniqueness seed
805
+ */
806
+ function _generateHandleUniqueSeed(bytes32[] memory operands) private returns (uint256) {
807
+ for (uint256 i = 0; i < operands.length; i++) {
808
+ if (!HandleUtils.isPublicHandle(operands[i])) {
809
+ return 0;
810
+ }
811
+ }
812
+ // All operands are public handles: need storage counter for uniqueness
813
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
814
+ return ++$.uniqueSeedCounter;
815
+ }
816
+
817
+ /**
818
+ * Emits events to seed the zero handles for all supported types. This allows off-chain
819
+ * services to recognize the zero handle for each type without needing to hardcode them.
820
+ */
821
+ function _emitZeroHandleSeeds() private {
822
+ TEEType[] memory types = TypeUtils.allCurrentlySupportedTypes();
823
+ for (uint i = 0; i < types.length; i++) {
824
+ emit WrapAsPublicHandle(
825
+ address(this),
826
+ bytes32(0),
827
+ types[i],
828
+ HandleUtils.zeroHandle(types[i])
829
+ );
830
+ }
831
+ }
832
+
833
+ // ----------- Admin functions ----------
834
+
835
+ /**
836
+ * Sets the KMS public key used for ECIES encryption.
837
+ * Only callable by the owner.
838
+ * @param newKmsPublicKey Compressed SEC1 secp256k1 public key (33 bytes)
839
+ */
840
+ function setKmsPublicKey(bytes calldata newKmsPublicKey) external onlyOwner {
841
+ require(newKmsPublicKey.length != 0, InvalidEmptyBytes());
842
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
843
+ $.kmsPublicKey = newKmsPublicKey;
844
+ emit KmsPublicKeyUpdated(newKmsPublicKey);
845
+ }
846
+
847
+ /**
848
+ * Sets Gateway wallet address.
849
+ * Only callable by the owner.
850
+ * @param gatewayAddress New Gateway wallet address
851
+ */
852
+ function setGateway(address gatewayAddress) external onlyOwner {
853
+ require(gatewayAddress != address(0), InvalidZeroAddress());
854
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
855
+ $.gateway = gatewayAddress;
856
+ emit GatewayUpdated(gatewayAddress);
857
+ }
858
+
859
+ /**
860
+ * Sets the proof expiration duration.
861
+ * Only callable by the owner.
862
+ * @param newDuration New expiration duration in seconds
863
+ */
864
+ function setProofExpirationDuration(uint256 newDuration) external onlyOwner {
865
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
866
+ $.proofExpirationDuration = newDuration;
867
+ emit ProofExpirationDurationUpdated(newDuration);
868
+ }
869
+
870
+ /**
871
+ * Returns the KMS public key used for ECIES encryption.
872
+ */
873
+ function kmsPublicKey() external view returns (bytes memory) {
874
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
875
+ return $.kmsPublicKey;
876
+ }
877
+
878
+ /**
879
+ * Returns the Gateway wallet address.
880
+ */
881
+ function gateway() external view returns (address) {
882
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
883
+ return $.gateway;
884
+ }
885
+
886
+ /**
887
+ * Returns the proof expiration duration in seconds.
888
+ */
889
+ function proofExpirationDuration() external view returns (uint256) {
890
+ NoxComputeStorage storage $ = _getNoxComputeStorage();
891
+ return $.proofExpirationDuration;
892
+ }
893
+
894
+ /**
895
+ * Authorizes contract upgrades only by the owner.
896
+ */
897
+ function _authorizeUpgrade(address /*newImplementation*/) internal override onlyOwner {}
898
+
899
+ function _getNoxComputeStorage() internal pure returns (NoxComputeStorage storage $) {
900
+ assembly {
901
+ $.slot := NOX_COMPUTE_STORAGE_LOCATION
902
+ }
903
+ }
904
+ }
@@ -34,7 +34,7 @@ library Nox {
34
34
  }
35
35
  // Local development chain
36
36
  if (block.chainid == 31337) {
37
- return 0x39847AeBa923Cc7367d4684194091D022B3F8548;
37
+ return 0x44C00793aD4975617b3B5Fc27D4FB78E772c8236;
38
38
  }
39
39
  revert("Nox: Unsupported chain");
40
40
  }
@@ -85,16 +85,6 @@ library Nox {
85
85
  return ebool.unwrap(handle) != 0;
86
86
  }
87
87
 
88
- /**
89
- * @dev Checks if an encrypted address handle is initialized.
90
- * This is a basic check and does not guarantee that the handle
91
- * is valid or recognized by the ACL.
92
- * @param handle encrypted address handle
93
- */
94
- function isInitialized(eaddress handle) internal pure returns (bool) {
95
- return eaddress.unwrap(handle) != 0;
96
- }
97
-
98
88
  /**
99
89
  * @dev Checks if an encrypted uint16 handle is initialized.
100
90
  * This is a basic check and does not guarantee that the handle
@@ -204,15 +194,6 @@ library Nox {
204
194
  return ebool.wrap(handle);
205
195
  }
206
196
 
207
- function fromExternal(
208
- externalEaddress externalHandle,
209
- bytes calldata handleProof
210
- ) internal returns (eaddress) {
211
- bytes32 handle = externalEaddress.unwrap(externalHandle);
212
- _noxComputeContract().validateInputProof(handle, msg.sender, handleProof, TEEType.Address);
213
- return eaddress.wrap(handle);
214
- }
215
-
216
197
  function fromExternal(
217
198
  externalEuint16 externalHandle,
218
199
  bytes calldata handleProof
@@ -902,14 +883,6 @@ library Nox {
902
883
  _allowIfNotPublic(ebool.unwrap(value), account);
903
884
  }
904
885
 
905
- /**
906
- * @dev Allows the use of value for the address account.
907
- * Silently skips public handles (they are already accessible by everyone).
908
- */
909
- function allow(eaddress value, address account) internal {
910
- _allowIfNotPublic(eaddress.unwrap(value), account);
911
- }
912
-
913
886
  /**
914
887
  * @dev Allows the use of value for the address account.
915
888
  * Silently skips public handles (they are already accessible by everyone).
@@ -950,14 +923,6 @@ library Nox {
950
923
  _allowIfNotPublic(ebool.unwrap(value), address(this));
951
924
  }
952
925
 
953
- /**
954
- * @dev Allows the use of value for this address (address(this)).
955
- * Silently skips public handles (they are already accessible by everyone).
956
- */
957
- function allowThis(eaddress value) internal {
958
- _allowIfNotPublic(eaddress.unwrap(value), address(this));
959
- }
960
-
961
926
  /**
962
927
  * @dev Allows the use of value for this address (address(this)).
963
928
  * Silently skips public handles (they are already accessible by everyone).
@@ -998,14 +963,6 @@ library Nox {
998
963
  _allowTransientIfNotPublic(ebool.unwrap(value), account);
999
964
  }
1000
965
 
1001
- /**
1002
- * @dev Allows the use of value by address account for this transaction.
1003
- * Silently skips public handles (they are already accessible by everyone).
1004
- */
1005
- function allowTransient(eaddress value, address account) internal {
1006
- _allowTransientIfNotPublic(eaddress.unwrap(value), account);
1007
- }
1008
-
1009
966
  /**
1010
967
  * @dev Allows the use of value by address account for this transaction.
1011
968
  * Silently skips public handles (they are already accessible by everyone).
@@ -1046,14 +1003,6 @@ library Nox {
1046
1003
  _disallowTransientIfNotPublic(ebool.unwrap(value), account);
1047
1004
  }
1048
1005
 
1049
- /**
1050
- * @dev Revokes transient access to value for address account within the current transaction.
1051
- * Silently skips public handles (they are already accessible by everyone).
1052
- */
1053
- function disallowTransient(eaddress value, address account) internal {
1054
- _disallowTransientIfNotPublic(eaddress.unwrap(value), account);
1055
- }
1056
-
1057
1006
  /**
1058
1007
  * @dev Revokes transient access to value for address account within the current transaction.
1059
1008
  * Silently skips public handles (they are already accessible by everyone).
@@ -1093,13 +1042,6 @@ library Nox {
1093
1042
  return _noxComputeContract().isAllowed(ebool.unwrap(handle), account);
1094
1043
  }
1095
1044
 
1096
- /**
1097
- * @dev Checks if the handle is allowed for the account.
1098
- */
1099
- function isAllowed(eaddress handle, address account) internal view returns (bool) {
1100
- return _noxComputeContract().isAllowed(eaddress.unwrap(handle), account);
1101
- }
1102
-
1103
1045
  /**
1104
1046
  * @dev Checks if the handle is allowed for the account.
1105
1047
  */
@@ -1137,13 +1079,6 @@ library Nox {
1137
1079
  _noxComputeContract().addViewer(ebool.unwrap(value), viewer);
1138
1080
  }
1139
1081
 
1140
- /**
1141
- * @dev Adds a viewer for an eaddress handle.
1142
- */
1143
- function addViewer(eaddress value, address viewer) internal {
1144
- _noxComputeContract().addViewer(eaddress.unwrap(value), viewer);
1145
- }
1146
-
1147
1082
  /**
1148
1083
  * @dev Adds a viewer for an euint16 handle.
1149
1084
  */
@@ -1179,13 +1114,6 @@ library Nox {
1179
1114
  return _noxComputeContract().isViewer(ebool.unwrap(handle), viewer);
1180
1115
  }
1181
1116
 
1182
- /**
1183
- * @dev Checks if the viewer can view the handle.
1184
- */
1185
- function isViewer(eaddress handle, address viewer) internal view returns (bool) {
1186
- return _noxComputeContract().isViewer(eaddress.unwrap(handle), viewer);
1187
- }
1188
-
1189
1117
  /**
1190
1118
  * @dev Checks if the viewer can view the handle.
1191
1119
  */
@@ -1223,13 +1151,6 @@ library Nox {
1223
1151
  _noxComputeContract().allowPublicDecryption(ebool.unwrap(value));
1224
1152
  }
1225
1153
 
1226
- /**
1227
- * @dev Marks an eaddress handle as publicly decryptable.
1228
- */
1229
- function allowPublicDecryption(eaddress value) internal {
1230
- _noxComputeContract().allowPublicDecryption(eaddress.unwrap(value));
1231
- }
1232
-
1233
1154
  /**
1234
1155
  * @dev Marks an euint16 handle as publicly decryptable.
1235
1156
  */
@@ -1265,13 +1186,6 @@ library Nox {
1265
1186
  return _noxComputeContract().isPubliclyDecryptable(ebool.unwrap(handle));
1266
1187
  }
1267
1188
 
1268
- /**
1269
- * @dev Checks if the handle is publicly decryptable.
1270
- */
1271
- function isPubliclyDecryptable(eaddress handle) internal view returns (bool) {
1272
- return _noxComputeContract().isPubliclyDecryptable(eaddress.unwrap(handle));
1273
- }
1274
-
1275
1189
  /**
1276
1190
  * @dev Checks if the handle is publicly decryptable.
1277
1191
  */
@@ -1318,21 +1232,6 @@ library Nox {
1318
1232
  return result[0] != 0x00;
1319
1233
  }
1320
1234
 
1321
- /**
1322
- * @dev Verifies a decryption proof and returns the decrypted address value.
1323
- */
1324
- function publicDecrypt(
1325
- eaddress handle,
1326
- bytes calldata decryptionProof
1327
- ) internal view returns (address plaintextValue) {
1328
- bytes memory result = _noxComputeContract().validateDecryptionProof(
1329
- eaddress.unwrap(handle),
1330
- decryptionProof
1331
- );
1332
- require(result.length == 20, MalformedDecryptedData(result));
1333
- return address(bytes20(result));
1334
- }
1335
-
1336
1235
  /**
1337
1236
  * @dev Verifies a decryption proof and returns the decrypted uint16 value.
1338
1237
  */
@@ -116,6 +116,19 @@ error NonArithmeticType();
116
116
  error UnsupportedArithmeticType();
117
117
 
118
118
  library TypeUtils {
119
+ /**
120
+ * Returns the list of all currently supported TEE types.
121
+ * @dev Update this list when new types are supported.
122
+ */
123
+ function allCurrentlySupportedTypes() internal pure returns (TEEType[] memory types) {
124
+ types = new TEEType[](5);
125
+ types[0] = TEEType.Bool;
126
+ types[1] = TEEType.Uint16;
127
+ types[2] = TEEType.Uint256;
128
+ types[3] = TEEType.Int16;
129
+ types[4] = TEEType.Int256;
130
+ }
131
+
119
132
  /**
120
133
  * @notice Extracts the TEE type from a handle.
121
134
  * The type is stored at byte position 5 in the handle.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iexec-nox/nox-protocol-contracts",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Nox protocol smart contracts",
5
5
  "keywords": [
6
6
  "Nox",
@@ -11,6 +11,7 @@
11
11
  "packageManager": "pnpm@10.33.0",
12
12
  "type": "module",
13
13
  "files": [
14
+ "/contracts/NoxCompute.sol",
14
15
  "/contracts/interfaces/",
15
16
  "/contracts/sdk/",
16
17
  "/contracts/shared/"