@iexec-nox/nox-protocol-contracts 0.2.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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iexec-nox/nox-protocol-contracts",
3
- "version": "0.2.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/"