@cofhe/mock-contracts 0.0.0-alpha-20260409113701

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,676 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ /* solhint-disable one-contract-per-file */
3
+ pragma solidity >=0.8.25 <0.9.0;
4
+
5
+ import { MockACL, Permission } from './MockACL.sol';
6
+ // import {PlaintextsStorage} from "./PlaintextsStorage.sol";
7
+ import { Strings } from '@openzeppelin/contracts/utils/Strings.sol';
8
+ import { ECDSA } from '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
9
+ import { MockCoFHE } from './MockCoFHE.sol';
10
+ import { ITaskManager, FunctionId, Utils, EncryptedInput } from '@fhenixprotocol/cofhe-contracts/ICofhe.sol';
11
+
12
+ error DecryptionResultNotReady(uint256 ctHash);
13
+ // Input validation errors
14
+ error InvalidInputsAmount(string operation, uint256 got, uint256 expected);
15
+ error InvalidOperationInputs(string operation);
16
+ error TooManyInputs(string operation, uint256 got, uint256 maxAllowed);
17
+ error InvalidBytesLength(uint256 got, uint256 expected);
18
+ // Type and security validation errors
19
+ error InvalidTypeOrSecurityZone(string operation);
20
+ error InvalidInputType(uint8 actual, uint8 expected);
21
+ error InvalidInputForFunction(string functionName, uint8 inputType);
22
+ error InvalidSecurityZone(int32 zone, int32 min, int32 max);
23
+ error InvalidSignature();
24
+ error InvalidSigner(address signer, address expectedSigner);
25
+
26
+ // Access control errors
27
+ error InvalidAddress();
28
+ error OnlyOwnerAllowed(address caller);
29
+ error OnlyAggregatorAllowed(address caller);
30
+
31
+ // Operation-specific errors
32
+ error RandomFunctionNotSupported();
33
+ error NotImplemented();
34
+
35
+ library TMCommon {
36
+ uint256 private constant HASH_MASK_FOR_METADATA = type(uint256).max - type(uint16).max; // 2 bytes reserved for metadata
37
+ uint256 private constant SECURITY_ZONE_MASK = type(uint8).max; // 0xff - 1 bytes reserved for security zone
38
+ uint256 private constant UINT_TYPE_MASK = (type(uint8).max >> 1); // 0x7f - 7 bits reserved for uint type in the one before last byte
39
+ uint256 private constant TRIVIALLY_ENCRYPTED_MASK = type(uint8).max - UINT_TYPE_MASK; //0x80 1 bit reserved for isTriviallyEncrypted
40
+ uint256 private constant SHIFTED_TYPE_MASK = UINT_TYPE_MASK << 8; // 0x7f007 bits reserved for uint type in the one before last byte
41
+
42
+ function uint256ToBytes32(uint256 value) internal pure returns (bytes memory) {
43
+ bytes memory result = new bytes(32);
44
+ assembly {
45
+ mstore(add(result, 32), value)
46
+ }
47
+ return result;
48
+ }
49
+
50
+ function combineInputs(
51
+ uint256[] memory encryptedHashes,
52
+ uint256[] memory extraInputs
53
+ ) internal pure returns (uint256[] memory) {
54
+ uint256[] memory inputs = new uint256[](encryptedHashes.length + extraInputs.length);
55
+ uint8 i = 0;
56
+ for (; i < encryptedHashes.length; i++) {
57
+ inputs[i] = encryptedHashes[i];
58
+ }
59
+ for (; i < encryptedHashes.length + extraInputs.length; i++) {
60
+ inputs[i] = extraInputs[i - encryptedHashes.length];
61
+ }
62
+
63
+ return inputs;
64
+ }
65
+
66
+ function getReturnType(FunctionId functionId, uint8 ctType) internal pure returns (uint8) {
67
+ if (
68
+ functionId == FunctionId.lte ||
69
+ functionId == FunctionId.lt ||
70
+ functionId == FunctionId.gte ||
71
+ functionId == FunctionId.gt ||
72
+ functionId == FunctionId.eq ||
73
+ functionId == FunctionId.ne
74
+ ) {
75
+ return Utils.EBOOL_TFHE;
76
+ }
77
+
78
+ return ctType;
79
+ }
80
+
81
+ /// @notice Calculates the temporary hash for async operations
82
+ /// @dev Must result the same temp hash as calculated by warp-drive/fhe-driver/CalcBinaryPlaceholderValueHash
83
+ /// @param functionId - The function id
84
+ /// @return The calculated temporary key
85
+ function calcPlaceholderKey(
86
+ uint8 ctType,
87
+ int32 securityZone,
88
+ uint256[] memory inputs,
89
+ FunctionId functionId
90
+ ) internal pure returns (uint256) {
91
+ bytes memory combined;
92
+ bool isTriviallyEncrypted = (functionId == FunctionId.trivialEncrypt);
93
+ for (uint8 i = 0; i < inputs.length; i++) {
94
+ combined = bytes.concat(combined, uint256ToBytes32(inputs[i]));
95
+ }
96
+
97
+ // Square is doing mul behind the scene
98
+ if (functionId == FunctionId.square) {
99
+ functionId = FunctionId.mul;
100
+ combined = bytes.concat(combined, uint256ToBytes32(inputs[0]));
101
+ }
102
+
103
+ bytes1 functionIdByte = bytes1(uint8(functionId));
104
+ combined = bytes.concat(combined, functionIdByte);
105
+
106
+ // Calculate Keccak256 hash
107
+ bytes32 hash = keccak256(combined);
108
+
109
+ // TODO: Trivially encrypted here isn't matching the check in `checkAllowed`
110
+ // - arcitect 2025-04-02
111
+
112
+ uint256 ctHash = appendMetadata(
113
+ uint256(hash),
114
+ securityZone,
115
+ getReturnType(functionId, ctType),
116
+ isTriviallyEncrypted
117
+ );
118
+
119
+ return ctHash;
120
+ }
121
+
122
+ function getByteForTrivialAndType(bool isTrivial, uint8 uintType) internal pure returns (uint256) {
123
+ /// @dev first bit for isTriviallyEncrypted
124
+ /// @dev last 7 bits for uintType
125
+
126
+ return uint256(((isTrivial ? TRIVIALLY_ENCRYPTED_MASK : 0x00) | (uintType & UINT_TYPE_MASK)));
127
+ }
128
+
129
+ /**
130
+ * Results format is: keccak256(operands_list, op)[0:29] || is_trivial (1 bit) & ct_type (7 bit) || securityZone || ct_version
131
+ */
132
+ function appendMetadata(
133
+ uint256 preCtHash,
134
+ int32 securityZone,
135
+ uint8 uintType,
136
+ bool isTrivial
137
+ ) internal pure returns (uint256 result) {
138
+ result = preCtHash & HASH_MASK_FOR_METADATA;
139
+ uint256 metadata = (getByteForTrivialAndType(isTrivial, uintType) << 8) | (uint256(uint8(int8(securityZone)))); /// @dev 8 bits for type, 8 bits for securityZone
140
+ result = result | metadata;
141
+ }
142
+
143
+ function getSecurityZoneFromHash(uint256 hash) internal pure returns (int32) {
144
+ return int32(int8(uint8(hash & SECURITY_ZONE_MASK)));
145
+ }
146
+
147
+ function getUintTypeFromHash(uint256 hash) internal pure returns (uint8) {
148
+ return uint8((hash & SHIFTED_TYPE_MASK) >> 8);
149
+ }
150
+
151
+ function getSecAndTypeFromHash(uint256 hash) internal pure returns (uint256) {
152
+ return uint256((SHIFTED_TYPE_MASK | SECURITY_ZONE_MASK) & hash);
153
+ }
154
+
155
+ function isTriviallyEncryptedFromHash(uint256 hash) internal pure returns (bool) {
156
+ return (hash & TRIVIALLY_ENCRYPTED_MASK) == TRIVIALLY_ENCRYPTED_MASK;
157
+ }
158
+ }
159
+
160
+ contract MockTaskManager is ITaskManager, MockCoFHE {
161
+ address owner;
162
+ bool private initialized;
163
+
164
+ // NOTE: MOCK PLAINTEXTS STORAGE
165
+ mapping(uint256 ctHash => uint256) private _decryptResult;
166
+ mapping(uint256 ctHash => bool) private _decryptResultReady;
167
+ mapping(uint256 ctHash => uint64) private _decryptResultReadyTimestamp;
168
+
169
+ /// @custom:oz-upgrades-unsafe-allow constructor
170
+ constructor() {}
171
+
172
+ /**
173
+ * @notice Initializes the contract.
174
+ * @param initialOwner Initial owner address.
175
+ */
176
+ function initialize(address initialOwner) public {
177
+ owner = initialOwner;
178
+ initialized = true;
179
+ verifierSigner = address(0);
180
+ decryptResultSigner = address(0);
181
+ }
182
+
183
+ modifier onlyOwner() {
184
+ if (msg.sender != owner) revert OnlyOwnerAllowed(msg.sender);
185
+ _;
186
+ }
187
+
188
+ function setSecurityZones(int32 minSZ, int32 maxSZ) external onlyOwner {
189
+ securityZoneMin = minSZ;
190
+ securityZoneMax = maxSZ;
191
+ }
192
+
193
+ function isInitialized() public view returns (bool) {
194
+ return initialized;
195
+ }
196
+
197
+ // Errors
198
+ // Returned when the handle is not allowed in the ACL for the account.
199
+ error ACLNotAllowed(uint256 handle, address account);
200
+
201
+ // Events
202
+ event TaskCreated(uint256 ctHash, string operation, uint256 input1, uint256 input2, uint256 input3);
203
+ event ProtocolNotification(uint256 ctHash, string operation, string errorMessage);
204
+ event DecryptionResult(uint256 ctHash, uint256 result, address indexed requestor);
205
+
206
+ struct Task {
207
+ address creator;
208
+ uint256 createdAt;
209
+ bool isResultReady;
210
+ }
211
+
212
+ // Supported Security Zones
213
+ int32 private securityZoneMax;
214
+ int32 private securityZoneMin;
215
+
216
+ // Address of the aggregator
217
+ address public aggregator;
218
+
219
+ // Access-Control contract
220
+ MockACL public acl;
221
+
222
+ address private verifierSigner;
223
+
224
+ // Signer address for decrypt result verification (threshold network's signing key)
225
+ address public decryptResultSigner;
226
+
227
+ // Storage contract for plaintext results of decrypt operations
228
+ // PlaintextsStorage public plaintextsStorage;
229
+
230
+ modifier onlyAggregator() {
231
+ if (msg.sender != aggregator) {
232
+ revert OnlyAggregatorAllowed(msg.sender);
233
+ }
234
+ _;
235
+ }
236
+
237
+ function exists() public pure returns (bool) {
238
+ return true;
239
+ }
240
+
241
+ function sendEventCreated(uint256 ctHash, string memory operation, uint256[] memory inputs) private {
242
+ if (inputs.length == 1 || opIs(operation, FunctionId.cast)) {
243
+ emit TaskCreated(ctHash, operation, inputs[0], 0, 0);
244
+
245
+ // NOTE: MOCK
246
+ MOCK_unaryOperation(ctHash, operation, inputs[0]);
247
+ } else if (inputs.length == 2) {
248
+ emit TaskCreated(ctHash, operation, inputs[0], inputs[1], 0);
249
+
250
+ // NOTE: MOCK
251
+ MOCK_twoInputOperation(ctHash, operation, inputs[0], inputs[1]);
252
+ } else {
253
+ emit TaskCreated(ctHash, operation, inputs[0], inputs[1], inputs[2]);
254
+
255
+ // NOTE: MOCK
256
+ MOCK_threeInputOperation(ctHash, operation, inputs[0], inputs[1], inputs[2]);
257
+ }
258
+ }
259
+
260
+ function createDecryptTask(uint256 ctHash, address /*requestor*/) public {
261
+ checkAllowed(ctHash);
262
+
263
+ // NOTE: MOCK COMMENTED
264
+ // (uint256 result, bool hasResult) = plaintextsStorage.getResult(ctHash);
265
+ // if (hasResult) {
266
+ // emit DecryptionResult(ctHash, result, requestor);
267
+ // } else {
268
+ // uint256[] memory inputs = new uint256[](1);
269
+ // inputs[0] = uint256(uint160(requestor));
270
+ // sendEventCreated(
271
+ // ctHash,
272
+ // Utils.functionIdToString(FunctionId.decrypt),
273
+ // inputs
274
+ // );
275
+ // }
276
+
277
+ // NOTE: MOCK
278
+ _decryptResultReady[ctHash] = true;
279
+ _decryptResult[ctHash] = _get(ctHash);
280
+
281
+ uint64 asyncOffset = uint64((block.timestamp % 10) + 1);
282
+ _decryptResultReadyTimestamp[ctHash] = uint64(block.timestamp) + asyncOffset;
283
+ }
284
+
285
+ function getDecryptResult(uint256 ctHash) public view returns (uint256) {
286
+ (uint256 result, bool decrypted) = getDecryptResultSafe(ctHash);
287
+ if (!decrypted) revert DecryptionResultNotReady(ctHash);
288
+ return result;
289
+ }
290
+
291
+ function getDecryptResultSafe(uint256 ctHash) public view returns (uint256 result, bool decrypted) {
292
+ if (!_decryptResultReady[ctHash]) return (0, false);
293
+
294
+ // NOTE: MOCK
295
+ if (block.timestamp < _decryptResultReadyTimestamp[ctHash]) return (0, false);
296
+
297
+ return (_decryptResult[ctHash], true);
298
+ }
299
+
300
+ function checkAllowed(uint256 ctHash) internal view {
301
+ if (!TMCommon.isTriviallyEncryptedFromHash(ctHash)) {
302
+ if (!acl.isAllowed(ctHash, msg.sender)) revert ACLNotAllowed(ctHash, msg.sender);
303
+ }
304
+ }
305
+
306
+ function isUnaryOperation(FunctionId funcId) internal pure returns (bool) {
307
+ return funcId == FunctionId.not || funcId == FunctionId.square || funcId == FunctionId.cast;
308
+ }
309
+
310
+ function isPlaintextOperation(FunctionId funcId) internal pure returns (bool) {
311
+ return funcId == FunctionId.random || funcId == FunctionId.trivialEncrypt;
312
+ }
313
+
314
+ function getSecurityZone(
315
+ FunctionId functionId,
316
+ uint256[] memory encryptedInputs,
317
+ uint256[] memory plaintextInputs
318
+ ) internal pure returns (int32) {
319
+ if (isPlaintextOperation(functionId)) {
320
+ // If inputs are plaintext (currently trivialEncrypt and random) the security zone will be the last input
321
+ return int32(int256(plaintextInputs[plaintextInputs.length - 1]));
322
+ }
323
+
324
+ // First param of a function that receives some encrypted values will always be encrypted
325
+ // Refer to: combineInput for more details
326
+ return TMCommon.getSecurityZoneFromHash(encryptedInputs[0]);
327
+ }
328
+
329
+ function isValidSecurityZone(int32 _securityZone) internal view returns (bool) {
330
+ return _securityZone >= securityZoneMin && _securityZone <= securityZoneMax;
331
+ }
332
+
333
+ function validateEncryptedHashes(uint256[] memory encryptedHashes) internal view {
334
+ for (uint8 i = 0; i < encryptedHashes.length; i++) {
335
+ checkAllowed(encryptedHashes[i]);
336
+ }
337
+ }
338
+
339
+ // Verifies if a function is a function that supports all types (including select for ifTrue, ifFalse)
340
+ function isAllTypesFunction(FunctionId funcId) internal pure returns (bool) {
341
+ return
342
+ funcId == FunctionId.select || funcId == FunctionId.eq || funcId == FunctionId.ne || funcId == FunctionId.cast;
343
+ }
344
+
345
+ // Verifies if a function is receives ONLY boolean or numeral inputs
346
+ function isBooleanAndNumeralFunction(FunctionId funcId) internal pure returns (bool) {
347
+ return funcId == FunctionId.xor || funcId == FunctionId.and || funcId == FunctionId.or || funcId == FunctionId.not;
348
+ }
349
+
350
+ function validateFunctionInputTypes(
351
+ FunctionId funcId,
352
+ string memory functionName,
353
+ uint256[] memory inputs
354
+ ) internal pure {
355
+ if (isAllTypesFunction(funcId)) {
356
+ return;
357
+ }
358
+
359
+ if (isBooleanAndNumeralFunction(funcId)) {
360
+ for (uint8 i = 0; i < inputs.length; i++) {
361
+ uint8 inputType = TMCommon.getUintTypeFromHash(inputs[i]);
362
+ if ((inputType ^ Utils.EADDRESS_TFHE) == 0) {
363
+ revert InvalidInputForFunction(functionName, Utils.EADDRESS_TFHE);
364
+ }
365
+ }
366
+ } else {
367
+ // In this case we expect a function that only work with numbers
368
+ for (uint8 i = 0; i < inputs.length; i++) {
369
+ uint8 inputType = TMCommon.getUintTypeFromHash(inputs[i]);
370
+ if ((inputType ^ Utils.EADDRESS_TFHE) == 0 || (inputType ^ Utils.EBOOL_TFHE) == 0) {
371
+ revert InvalidInputForFunction(functionName, inputType);
372
+ }
373
+ }
374
+ }
375
+ }
376
+
377
+ function validateInputs(uint256[] memory encryptedHashes, FunctionId funcId) internal view {
378
+ string memory functionName = Utils.functionIdToString(funcId);
379
+
380
+ if (encryptedHashes.length == 0) {
381
+ if (!isPlaintextOperation(funcId)) {
382
+ revert InvalidOperationInputs(functionName);
383
+ }
384
+ return;
385
+ }
386
+
387
+ if (funcId == FunctionId.select) {
388
+ validateSelectInputs(encryptedHashes);
389
+ } else if (isUnaryOperation(funcId)) {
390
+ if (encryptedHashes.length != 1) {
391
+ revert InvalidInputsAmount(functionName, encryptedHashes.length, 1);
392
+ }
393
+ } else {
394
+ if (encryptedHashes.length != 2) {
395
+ revert InvalidInputsAmount(functionName, encryptedHashes.length, 2);
396
+ }
397
+ if ((TMCommon.getSecAndTypeFromHash(encryptedHashes[0] ^ encryptedHashes[1])) != 0) {
398
+ revert InvalidTypeOrSecurityZone(functionName);
399
+ }
400
+ }
401
+
402
+ int32 securityZone = TMCommon.getSecurityZoneFromHash(encryptedHashes[0]);
403
+ if (!isValidSecurityZone(securityZone)) {
404
+ revert InvalidSecurityZone(securityZone, securityZoneMin, securityZoneMax);
405
+ }
406
+ validateEncryptedHashes(encryptedHashes);
407
+ validateFunctionInputTypes(funcId, functionName, encryptedHashes);
408
+ }
409
+
410
+ function validateSelectInputs(uint256[] memory encryptedHashes) internal pure {
411
+ if (encryptedHashes.length != 3) {
412
+ revert InvalidInputsAmount('select', encryptedHashes.length, 3);
413
+ }
414
+ if ((TMCommon.getSecAndTypeFromHash(encryptedHashes[1] ^ encryptedHashes[2])) != 0) {
415
+ revert InvalidTypeOrSecurityZone('select');
416
+ }
417
+
418
+ uint8 uintType = TMCommon.getUintTypeFromHash(encryptedHashes[0]);
419
+ if ((uintType ^ Utils.EBOOL_TFHE) != 0) {
420
+ revert InvalidInputType(uintType, Utils.EBOOL_TFHE);
421
+ }
422
+ }
423
+
424
+ function createTask(
425
+ uint8 returnType,
426
+ FunctionId funcId,
427
+ uint256[] memory encryptedHashes,
428
+ uint256[] memory extraInputs
429
+ ) external returns (uint256) {
430
+ if (funcId == FunctionId.random) {
431
+ revert RandomFunctionNotSupported();
432
+ }
433
+ uint256 inputsLength = encryptedHashes.length + extraInputs.length;
434
+ if (inputsLength > 3) {
435
+ revert TooManyInputs(Utils.functionIdToString(funcId), inputsLength, 3);
436
+ }
437
+
438
+ validateInputs(encryptedHashes, funcId);
439
+ uint256[] memory inputs = TMCommon.combineInputs(encryptedHashes, extraInputs);
440
+
441
+ int32 securityZone = getSecurityZone(funcId, encryptedHashes, extraInputs);
442
+ uint256 ctHash = TMCommon.calcPlaceholderKey(returnType, securityZone, inputs, funcId);
443
+
444
+ acl.allowTransient(ctHash, msg.sender, address(this));
445
+ sendEventCreated(ctHash, Utils.functionIdToString(funcId), inputs);
446
+
447
+ return ctHash;
448
+ }
449
+
450
+ function handleDecryptResult(
451
+ uint256 ctHash,
452
+ uint256 result,
453
+ address[] calldata /*requestors*/
454
+ ) external onlyAggregator {
455
+ // plaintextsStorage.storeResult(ctHash, result);
456
+ // for (uint8 i = 0; i < requestors.length; i++) {
457
+ // emit DecryptionResult(ctHash, result, requestors[i]);
458
+ // }
459
+
460
+ _decryptResultReady[ctHash] = true;
461
+ _decryptResult[ctHash] = result;
462
+ _decryptResultReadyTimestamp[ctHash] = uint64(block.timestamp);
463
+ }
464
+
465
+ function handleError(uint256 ctHash, string memory operation, string memory errorMessage) external onlyAggregator {
466
+ emit ProtocolNotification(ctHash, operation, errorMessage);
467
+ }
468
+
469
+ /// @notice Publish a signed decrypt result to the chain
470
+ /// @dev Anyone with a valid signature from the decrypt network can call this
471
+ function publishDecryptResult(uint256 ctHash, uint256 result, bytes calldata signature) external {
472
+ _verifyDecryptResult(ctHash, result, signature, true);
473
+ _decryptResultReady[ctHash] = true;
474
+ _decryptResult[ctHash] = result;
475
+
476
+ _decryptResultReadyTimestamp[ctHash] = uint64(block.timestamp);
477
+ emit DecryptionResult(ctHash, result, msg.sender);
478
+ }
479
+
480
+ function publishDecryptResultBatch(
481
+ uint256[] calldata ctHashes,
482
+ uint256[] calldata results,
483
+ bytes[] calldata signatures
484
+ ) external {
485
+ // Mock implementation: process each result individually
486
+ // Note: publishDecryptResult is defined later in the contract
487
+ for (uint256 i = 0; i < ctHashes.length; i++) {
488
+ // Call via external interface to avoid forward reference issue
489
+ this.publishDecryptResult(ctHashes[i], results[i], signatures[i]);
490
+ }
491
+ }
492
+
493
+ function verifyDecryptResult(uint256 ctHash, uint256 result, bytes calldata signature) external view returns (bool) {
494
+ return _verifyDecryptResult(ctHash, result, signature, true);
495
+ }
496
+
497
+ function verifyDecryptResultSafe(
498
+ uint256 ctHash,
499
+ uint256 result,
500
+ bytes calldata signature
501
+ ) external view returns (bool) {
502
+ return _verifyDecryptResult(ctHash, result, signature, false);
503
+ }
504
+
505
+ function _verifyDecryptResult(
506
+ uint256 ctHash,
507
+ uint256 result,
508
+ bytes calldata signature,
509
+ bool shouldRevert
510
+ ) private view returns (bool) {
511
+ if (decryptResultSigner == address(0)) {
512
+ return true;
513
+ }
514
+
515
+ bytes32 messageHash = keccak256(abi.encodePacked(ctHash, result));
516
+ (address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(messageHash, signature);
517
+
518
+ if (err != ECDSA.RecoverError.NoError || recovered == address(0)) {
519
+ if (shouldRevert) revert InvalidSignature();
520
+ return false;
521
+ }
522
+ if (recovered != decryptResultSigner) {
523
+ if (shouldRevert) revert InvalidSigner(recovered, decryptResultSigner);
524
+ return false;
525
+ }
526
+ return true;
527
+ }
528
+
529
+ function verifyType(uint8 ctType, uint8 desiredType) internal pure {
530
+ if (ctType != desiredType) {
531
+ revert InvalidInputType(ctType, desiredType);
532
+ }
533
+ }
534
+
535
+ function verifyInput(EncryptedInput memory input, address sender) external returns (uint256) {
536
+ int32 securityZone = int32(uint32(input.securityZone));
537
+
538
+ // When signer is set to 0 address we skip this logic to be able to support debug use cases.
539
+ // In debug use cases we assume that the verifier is not necessarily running.
540
+ if (verifierSigner != address(0)) {
541
+ if (!isValidSecurityZone(securityZone)) {
542
+ revert InvalidSecurityZone(securityZone, securityZoneMin, securityZoneMax);
543
+ }
544
+
545
+ address signer = extractSigner(input, sender);
546
+ if (signer != verifierSigner) {
547
+ revert InvalidSigner(signer, verifierSigner);
548
+ }
549
+ }
550
+
551
+ uint256 appendedHash = TMCommon.appendMetadata(input.ctHash, securityZone, input.utype, false);
552
+
553
+ acl.allowTransient(appendedHash, msg.sender, address(this));
554
+ return appendedHash;
555
+ }
556
+
557
+ function allow(uint256 ctHash, address account) external {
558
+ if (!TMCommon.isTriviallyEncryptedFromHash(ctHash)) {
559
+ acl.allow(ctHash, account, msg.sender);
560
+
561
+ // NOTE: MOCK
562
+ MOCK_logAllow(account == msg.sender ? 'FHE.allowThis' : 'FHE.allow', ctHash, account);
563
+ }
564
+ }
565
+
566
+ function allowGlobal(uint256 ctHash) external {
567
+ if (!TMCommon.isTriviallyEncryptedFromHash(ctHash)) {
568
+ acl.allowGlobal(ctHash, msg.sender);
569
+
570
+ // NOTE: MOCK
571
+ MOCK_logAllow('FHE.allowGlobal', ctHash, msg.sender);
572
+ }
573
+ }
574
+
575
+ function allowTransient(uint256 ctHash, address account) external {
576
+ if (!TMCommon.isTriviallyEncryptedFromHash(ctHash)) {
577
+ acl.allowTransient(ctHash, account, msg.sender);
578
+
579
+ // NOTE: MOCK
580
+ MOCK_logAllow('FHE.allowTransient', ctHash, account);
581
+ }
582
+ }
583
+
584
+ function allowForDecryption(uint256 ctHash) external {
585
+ if (!TMCommon.isTriviallyEncryptedFromHash(ctHash)) {
586
+ uint256[] memory hashes = new uint256[](1);
587
+ hashes[0] = ctHash;
588
+ acl.allowForDecryption(hashes, msg.sender);
589
+
590
+ // NOTE: MOCK
591
+ MOCK_logAllow('FHE.allowForDecryption', ctHash, msg.sender);
592
+ }
593
+ }
594
+
595
+ function isAllowed(uint256 ctHash, address account) external view returns (bool) {
596
+ if (TMCommon.isTriviallyEncryptedFromHash(ctHash)) {
597
+ return true;
598
+ }
599
+ return acl.isAllowed(ctHash, account);
600
+ }
601
+
602
+ function extractSigner(EncryptedInput memory input, address sender) private view returns (address) {
603
+ bytes memory combined = abi.encodePacked(input.ctHash, input.utype, input.securityZone, sender, block.chainid);
604
+ bytes32 expectedHash = keccak256(combined);
605
+
606
+ address signer = ECDSA.recover(expectedHash, input.signature);
607
+
608
+ if (signer == address(0)) {
609
+ revert InvalidSignature();
610
+ }
611
+
612
+ return signer;
613
+ }
614
+
615
+ function setVerifierSigner(address signer) external onlyOwner {
616
+ verifierSigner = signer;
617
+ }
618
+
619
+ /// @notice Set the authorized signer for decrypt results
620
+ /// @param signer The new signer address (must be non-zero for publishDecryptResult to work)
621
+ function setDecryptResultSigner(address signer) external onlyOwner {
622
+ decryptResultSigner = signer;
623
+ }
624
+
625
+ function setSecurityZoneMax(int32 securityZone) external onlyOwner {
626
+ if (securityZone < securityZoneMin) {
627
+ revert InvalidSecurityZone(securityZone, securityZoneMin, securityZoneMax);
628
+ }
629
+ securityZoneMax = securityZone;
630
+ }
631
+
632
+ function setSecurityZoneMin(int32 securityZone) external onlyOwner {
633
+ if (securityZone > securityZoneMax) {
634
+ revert InvalidSecurityZone(securityZone, securityZoneMin, securityZoneMax);
635
+ }
636
+ securityZoneMin = securityZone;
637
+ }
638
+
639
+ function setACLContract(address _aclAddress) external onlyOwner {
640
+ if (_aclAddress == address(0)) {
641
+ revert InvalidAddress();
642
+ }
643
+ acl = MockACL(_aclAddress);
644
+ }
645
+
646
+ // function setPlaintextsStorage(
647
+ // address _plaintextsStorageAddress
648
+ // ) external onlyOwner {
649
+ // if (_plaintextsStorageAddress == address(0)) {
650
+ // revert InvalidAddress();
651
+ // }
652
+ // plaintextsStorage = PlaintextsStorage(_plaintextsStorageAddress);
653
+ // }
654
+
655
+ function setAggregator(address _aggregatorAddress) external onlyOwner {
656
+ if (_aggregatorAddress == address(0)) {
657
+ revert InvalidAddress();
658
+ }
659
+ aggregator = _aggregatorAddress;
660
+ }
661
+
662
+ function isAllowedWithPermission(Permission memory permission, uint256 handle) public view returns (bool) {
663
+ return acl.isAllowedWithPermission(permission, handle);
664
+ }
665
+
666
+ // Stub implementations for new ITaskManager interface methods (inc PR #48)
667
+
668
+ function createRandomTask(uint8 returnType, uint256 seed, int32 securityZone) external returns (uint256) {
669
+ // Mock implementation: just return a pseudo-random hash based on seed
670
+ return uint256(keccak256(abi.encode(returnType, seed, securityZone, block.timestamp)));
671
+ }
672
+
673
+ function isPubliclyAllowed(uint256 ctHash) external view returns (bool) {
674
+ revert NotImplemented();
675
+ }
676
+ }