@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,199 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ // solhint-disable one-contract-per-file
3
+
4
+ // NOTE: This file was renamed from `MockQueryDecrypter.sol` to `MockThresholdNetwork.sol`
5
+ // to better reflect that it mocks threshold-network-style decryption/sealing behavior
6
+ // in local/testing environments.
7
+
8
+ pragma solidity >=0.8.19 <0.9.0;
9
+
10
+ import { MockACL } from './MockACL.sol';
11
+ import { MockTaskManager } from './MockTaskManager.sol';
12
+ import { Permission, MockPermissioned } from './Permissioned.sol';
13
+
14
+ contract MockThresholdNetwork {
15
+ MockTaskManager public mockTaskManager;
16
+ MockACL public mockAcl;
17
+
18
+ error NotAllowed();
19
+ error SealingKeyMissing();
20
+ error SealingKeyInvalid();
21
+
22
+ function initialize(address _taskManager, address _acl) public {
23
+ mockTaskManager = MockTaskManager(_taskManager);
24
+ mockAcl = MockACL(_acl);
25
+ }
26
+
27
+ // EXISTENCE
28
+
29
+ function exists() public pure returns (bool) {
30
+ return true;
31
+ }
32
+
33
+ // BODY
34
+
35
+ function queryDecrypt(
36
+ uint256 ctHash,
37
+ uint256,
38
+ Permission memory permission
39
+ ) public view returns (bool allowed, string memory error, uint256) {
40
+ bool isAllowed;
41
+ try mockAcl.isAllowedWithPermission(permission, ctHash) returns (bool _isAllowed) {
42
+ isAllowed = _isAllowed;
43
+ } catch Error(string memory reason) {
44
+ // Handle string error messages
45
+ return (false, reason, 0);
46
+ } catch Panic(uint /*errorCode*/) {
47
+ // Handle panic errors
48
+ return (false, 'Panic', 0);
49
+ } catch (bytes memory lowLevelData) {
50
+ return (false, decodeLowLevelReversion(lowLevelData), 0);
51
+ }
52
+
53
+ if (!isAllowed) return (false, 'NotAllowed', 0);
54
+
55
+ return (true, '', mockTaskManager.mockStorage(ctHash));
56
+ }
57
+
58
+ function seal(uint256 input, bytes32 key) public pure returns (bytes32) {
59
+ return bytes32(input) ^ key;
60
+ }
61
+
62
+ function unseal(bytes32 hashed, bytes32 key) public pure returns (uint256) {
63
+ return uint256(hashed ^ key);
64
+ }
65
+
66
+ // changed function name from 'testQueryDecrypt'
67
+ // Foundry was trying to run fuzz tests on it
68
+ function mockQueryDecrypt(
69
+ uint256 ctHash,
70
+ uint256,
71
+ address issuer
72
+ ) public view returns (bool allowed, string memory error, uint256) {
73
+ bool isAllowed;
74
+ try mockAcl.isAllowed(ctHash, issuer) returns (bool _isAllowed) {
75
+ isAllowed = _isAllowed;
76
+ } catch Error(string memory reason) {
77
+ // Handle string error messages
78
+ return (false, reason, 0);
79
+ } catch Panic(uint /*errorCode*/) {
80
+ // Handle panic errors
81
+ return (false, 'Panic', 0);
82
+ } catch (bytes memory lowLevelData) {
83
+ return (false, decodeLowLevelReversion(lowLevelData), 0);
84
+ }
85
+
86
+ if (!isAllowed) return (false, 'NotAllowed', 0);
87
+
88
+ uint256 value = mockTaskManager.mockStorage(ctHash);
89
+ return (true, '', value);
90
+ }
91
+
92
+ function querySealOutput(
93
+ uint256 ctHash,
94
+ uint256,
95
+ Permission memory permission
96
+ ) public view returns (bool allowed, string memory error, bytes32) {
97
+ if (permission.sealingKey == bytes32(0)) revert SealingKeyMissing();
98
+
99
+ bool isAllowed;
100
+ try mockAcl.isAllowedWithPermission(permission, ctHash) returns (bool _isAllowed) {
101
+ isAllowed = _isAllowed;
102
+ } catch Error(string memory reason) {
103
+ // Handle string error messages
104
+ return (false, reason, bytes32(0));
105
+ } catch Panic(uint /*errorCode*/) {
106
+ // Handle panic errors
107
+ return (false, 'Panic', bytes32(0));
108
+ } catch (bytes memory lowLevelData) {
109
+ return (false, decodeLowLevelReversion(lowLevelData), bytes32(0));
110
+ }
111
+
112
+ if (!isAllowed) return (false, 'NotAllowed', bytes32(0));
113
+
114
+ uint256 value = mockTaskManager.mockStorage(ctHash);
115
+ return (true, '', seal(value, permission.sealingKey));
116
+ }
117
+
118
+ // DECRYPT FOR TX
119
+
120
+ function _isAllowedWithPermit(
121
+ uint256 ctHash,
122
+ Permission memory permission
123
+ ) internal view returns (bool isAllowed, string memory error) {
124
+ try mockTaskManager.isAllowedWithPermission(permission, ctHash) returns (bool _isAllowed) {
125
+ isAllowed = _isAllowed;
126
+ } catch Error(string memory reason) {
127
+ return (false, reason);
128
+ } catch Panic(uint /*errorCode*/) {
129
+ return (false, 'Panic');
130
+ } catch (bytes memory lowLevelData) {
131
+ return (false, decodeLowLevelReversion(lowLevelData));
132
+ }
133
+
134
+ if (!isAllowed) return (false, 'NotAllowed');
135
+ return (true, '');
136
+ }
137
+
138
+ function _isAllowedWithoutPermit(uint256 ctHash) internal view returns (bool isAllowed, string memory error) {
139
+ try mockAcl.globalAllowed(ctHash) returns (bool _isAllowed) {
140
+ isAllowed = _isAllowed;
141
+ } catch Error(string memory reason) {
142
+ return (false, reason);
143
+ } catch Panic(uint /*errorCode*/) {
144
+ return (false, 'Panic');
145
+ } catch (bytes memory lowLevelData) {
146
+ return (false, decodeLowLevelReversion(lowLevelData));
147
+ }
148
+
149
+ if (!isAllowed) return (false, 'NotAllowed');
150
+ return (true, '');
151
+ }
152
+
153
+ /// @notice Decrypt a ciphertext for a transaction using a permit.
154
+ function decryptForTxWithPermit(
155
+ uint256 ctHash,
156
+ Permission memory permission
157
+ ) public view returns (bool allowed, string memory error, uint256 decryptedValue) {
158
+ if (permission.issuer == address(0)) {
159
+ return (false, 'PermissionMissing', 0);
160
+ }
161
+
162
+ (bool isAllowed, string memory err) = _isAllowedWithPermit(ctHash, permission);
163
+ if (!isAllowed) return (false, err, 0);
164
+
165
+ uint256 value = mockTaskManager.mockStorage(ctHash);
166
+ return (true, '', value);
167
+ }
168
+
169
+ /// @notice Decrypt a ciphertext for a transaction using global allowance (no permit).
170
+ function decryptForTxWithoutPermit(
171
+ uint256 ctHash
172
+ ) public view returns (bool allowed, string memory error, uint256 decryptedValue) {
173
+ (bool isAllowed, string memory err) = _isAllowedWithoutPermit(ctHash);
174
+ if (!isAllowed) return (false, err, 0);
175
+
176
+ uint256 value = mockTaskManager.mockStorage(ctHash);
177
+ return (true, '', value);
178
+ }
179
+
180
+ // UTIL
181
+
182
+ function decodeLowLevelReversion(bytes memory data) public pure returns (string memory error) {
183
+ bytes4 selector = bytes4(data);
184
+ if (selector == MockPermissioned.PermissionInvalid_Expired.selector) {
185
+ return 'PermissionInvalid_Expired';
186
+ }
187
+ if (selector == MockPermissioned.PermissionInvalid_IssuerSignature.selector) {
188
+ return 'PermissionInvalid_IssuerSignature';
189
+ }
190
+ if (selector == MockPermissioned.PermissionInvalid_RecipientSignature.selector) {
191
+ return 'PermissionInvalid_RecipientSignature';
192
+ }
193
+ if (selector == MockPermissioned.PermissionInvalid_Disabled.selector) {
194
+ return 'PermissionInvalid_Disabled';
195
+ }
196
+ // Handle other errors
197
+ return 'Low Level Error';
198
+ }
199
+ }
@@ -0,0 +1,140 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ // solhint-disable one-contract-per-file
3
+
4
+ pragma solidity >=0.8.19 <0.9.0;
5
+
6
+ import { TASK_MANAGER_ADDRESS } from '@fhenixprotocol/cofhe-contracts/FHE.sol';
7
+ import { EncryptedInput } from '@fhenixprotocol/cofhe-contracts/ICofhe.sol';
8
+ import { MockTaskManager } from './MockTaskManager.sol';
9
+
10
+ contract MockZkVerifier {
11
+ // TMCommon
12
+ uint256 constant hashMaskForMetadata = type(uint256).max - type(uint16).max; // 2 bytes reserved for metadata
13
+ uint256 constant uintTypeMask = (type(uint8).max >> 1); // 0x7f - 7 bits reserved for uint type in the one before last byte
14
+ uint256 constant triviallyEncryptedMask = type(uint8).max - uintTypeMask; //0x80 1 bit reserved for isTriviallyEncrypted
15
+
16
+ // Specific
17
+ uint256 salt = 0;
18
+ error InvalidInputs();
19
+
20
+ // EXISTENCE
21
+
22
+ function exists() public pure returns (bool) {
23
+ return true;
24
+ }
25
+
26
+ // HASHING
27
+
28
+ function getByteForTrivialAndType(bool isTrivial, uint8 uintType) internal pure returns (uint256) {
29
+ /// @dev first bit for isTriviallyEncrypted
30
+ /// @dev last 7 bits for uintType
31
+
32
+ return uint256(((isTrivial ? triviallyEncryptedMask : 0x00) | (uintType & uintTypeMask)));
33
+ }
34
+
35
+ function _appendMetadata(
36
+ uint256 preCtHash,
37
+ uint8 securityZone,
38
+ uint8 uintType,
39
+ bool isTrivial
40
+ ) internal pure returns (uint256 result) {
41
+ result = preCtHash & hashMaskForMetadata;
42
+ uint256 metadata = (getByteForTrivialAndType(isTrivial, uintType) << 8) | (uint256(uint8(int8(securityZone)))); /// @dev 8 bits for type, 8 bits for securityZone
43
+ result = result | metadata;
44
+ }
45
+
46
+ function uint256ToBytes32(uint256 value) internal pure returns (bytes memory) {
47
+ bytes memory result = new bytes(32);
48
+ assembly {
49
+ mstore(add(result, 32), value)
50
+ }
51
+ return result;
52
+ }
53
+
54
+ function _calcPlaceholderKey(
55
+ address user,
56
+ uint8 utype,
57
+ uint8 securityZone,
58
+ uint256 input
59
+ ) internal view returns (uint256) {
60
+ bytes memory combined = bytes.concat(uint256ToBytes32(input));
61
+ combined = bytes.concat(combined, uint256ToBytes32(uint256(uint160(user))));
62
+ combined = bytes.concat(combined, keccak256(abi.encodePacked(salt)));
63
+
64
+ // Calculate Keccak256 hash
65
+ bytes32 ctHash = keccak256(combined);
66
+
67
+ return _appendMetadata(uint256(ctHash), securityZone, utype, false);
68
+ }
69
+
70
+ // CORE
71
+
72
+ function zkVerifyCalcCtHashesPacked(
73
+ uint256[] memory values,
74
+ uint8[] memory utypes,
75
+ address user,
76
+ uint8 securityZone,
77
+ uint256 chainId
78
+ ) public view returns (uint256[] memory ctHashes) {
79
+ if (utypes.length != values.length) {
80
+ revert InvalidInputs();
81
+ }
82
+
83
+ ctHashes = new uint256[](utypes.length);
84
+
85
+ for (uint256 i = 0; i < utypes.length; i++) {
86
+ ctHashes[i] = zkVerifyCalcCtHash(values[i], utypes[i], user, securityZone, chainId);
87
+ }
88
+ }
89
+
90
+ function zkVerifyCalcCtHash(
91
+ uint256 value,
92
+ uint8 utype,
93
+ address user,
94
+ uint8 securityZone,
95
+ uint256
96
+ ) public view returns (uint256 ctHash) {
97
+ ctHash = _calcPlaceholderKey(user, utype, securityZone, value);
98
+ }
99
+
100
+ function insertPackedCtHashes(uint256[] memory ctHashes, uint256[] memory values) public {
101
+ for (uint256 i = 0; i < ctHashes.length; i++) {
102
+ insertCtHash(ctHashes[i], values[i]);
103
+ }
104
+ }
105
+
106
+ function insertCtHash(uint256 ctHash, uint256 value) public {
107
+ MockTaskManager(TASK_MANAGER_ADDRESS).MOCK_setInEuintKey(ctHash, value);
108
+ salt += 1;
109
+ }
110
+
111
+ function zkVerifyPacked(
112
+ uint256[] memory values,
113
+ uint8[] memory utypes,
114
+ address user,
115
+ uint8 securityZone,
116
+ uint256 chainId
117
+ ) public returns (EncryptedInput[] memory inputs) {
118
+ if (utypes.length != values.length) {
119
+ revert InvalidInputs();
120
+ }
121
+
122
+ inputs = new EncryptedInput[](utypes.length);
123
+
124
+ for (uint256 i = 0; i < utypes.length; i++) {
125
+ inputs[i] = zkVerify(values[i], utypes[i], user, securityZone, chainId);
126
+ }
127
+ }
128
+
129
+ function zkVerify(
130
+ uint256 value,
131
+ uint8 utype,
132
+ address user,
133
+ uint8 securityZone,
134
+ uint256
135
+ ) public returns (EncryptedInput memory) {
136
+ uint256 ctHash = _calcPlaceholderKey(user, utype, securityZone, value);
137
+ insertCtHash(ctHash, value);
138
+ return EncryptedInput({ ctHash: ctHash, securityZone: securityZone, utype: utype, signature: hex'' });
139
+ }
140
+ }
@@ -0,0 +1,213 @@
1
+ // solhint-disable func-name-mixedcase
2
+ // SPDX-License-Identifier: MIT
3
+ pragma solidity >=0.8.19 <0.9.0;
4
+
5
+ import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
6
+ import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
7
+
8
+ /**
9
+ * @dev Permission body that must be passed to a contract to allow access to sensitive data.
10
+ *
11
+ * The minimum permission to access a user's own data requires the fields
12
+ * < issuer, expiration, sealingKey, issuerSignature >
13
+ *
14
+ * ---
15
+ *
16
+ * If not sharing the permission, `issuer` signs a signature using the fields:
17
+ * < issuer, expiration, sealingKey, issuerSignature >
18
+ * This signature can now be used by `issuer` to access their own encrypted data.
19
+ *
20
+ * ---
21
+ *
22
+ * Sharing a permission is a two step process: `issuer` completes step 1, and `recipient` completes step 2.
23
+ *
24
+ * 1:
25
+ * `issuer` creates a permission with `recipient` populated with the address of the user to give access to.
26
+ * `issuer` does not include a `sealingKey` in the permission, it will be populated by the `recipient`.
27
+ * `issuer` signs a signature including the fields: (note: `sealingKey` is absent in this signature)
28
+ * < issuer, expiration, sealingKey, issuerSignature >
29
+ * `issuer` packages the permission data and `issuerSignature` and shares it with `recipient`
30
+ * ** None of this data is sensitive, and can be shared as cleartext **
31
+ *
32
+ * 2:
33
+ * `recipient` adds their `sealingKey` to the data received from `issuer`
34
+ * `recipient` signs a signature including the fields:
35
+ * < sealingKey, issuerSignature >
36
+ * `recipient` can now use the completed Permission to access `issuer`s encrypted data.
37
+ *
38
+ * ---
39
+ *
40
+ * `validatorId` and `validatorContract` are optional and can be used together to
41
+ * increase security and control by disabling a permission after it has been created.
42
+ * Useful when sharing permits to provide external access to sensitive data (eg auditors).
43
+ */
44
+ struct Permission {
45
+ // (base) User that initially created the permission, target of data fetching
46
+ address issuer;
47
+ // (base) Expiration timestamp
48
+ uint64 expiration;
49
+ // (sharing) The user that this permission will be shared with
50
+ // ** optional, use `address(0)` to disable **
51
+ address recipient;
52
+ // (issuer defined validation) An id used to query a contract to check this permissions validity
53
+ // ** optional, use `0` to disable **
54
+ uint256 validatorId;
55
+ // (issuer defined validation) The contract to query to determine permission validity
56
+ // ** optional, user `address(0)` to disable **
57
+ address validatorContract;
58
+ // (base) The publicKey of a sealingPair used to re-encrypt `issuer`s confidential data
59
+ // (non-sharing) Populated by `issuer`
60
+ // (sharing) Populated by `recipient`
61
+ bytes32 sealingKey;
62
+ // (base) `signTypedData` signature created by `issuer`.
63
+ // (base) Shared- and Self- permissions differ in signature format: (`sealingKey` absent in shared signature)
64
+ // (non-sharing) < issuer, expiration, recipient, validatorId, validatorContract, sealingKey >
65
+ // (sharing) < issuer, expiration, recipient, validatorId, validatorContract >
66
+ bytes issuerSignature;
67
+ // (sharing) `signTypedData` signature created by `recipient` with format:
68
+ // (sharing) < sealingKey, issuerSignature>
69
+ // ** required for shared permits **
70
+ bytes recipientSignature;
71
+ }
72
+
73
+ /// @dev Minimum required interface to create a custom permission validator.
74
+ /// Permission validators are optional, and provide extra security and control when sharing permits.
75
+ interface IPermissionCustomIdValidator {
76
+ /// @dev Checks whether a permission is valid, returning `false` disables the permission.
77
+ function disabled(address issuer, uint256 id) external view returns (bool);
78
+ }
79
+
80
+ contract MockPermissioned is EIP712 {
81
+ using PermissionUtils for Permission;
82
+
83
+ constructor() EIP712("ACL", "1") {}
84
+
85
+ /// @dev Emitted when `permission.expiration` is in the past (< block.timestamp)
86
+ error PermissionInvalid_Expired();
87
+
88
+ /// @dev Emitted when `issuerSignature` is malformed or was not signed by `permission.issuer`
89
+ error PermissionInvalid_IssuerSignature();
90
+
91
+ /// @dev Emitted when `recipientSignature` is malformed or was not signed by `permission.recipient`
92
+ error PermissionInvalid_RecipientSignature();
93
+
94
+ /// @dev Emitted when `validatorContract` returned `false` indicating that this permission has been externally disabled
95
+ error PermissionInvalid_Disabled();
96
+
97
+ /// @dev Validate's a `permissions` access of sensitive data.
98
+ /// `permission` may be invalid or unauthorized for the following reasons:
99
+ /// - Expired: `permission.expiration` is in the past (< block.timestamp)
100
+ /// - Issuer signature: `issuerSignature` is malformed or was not signed by `permission.issuer`
101
+ /// - Recipient signature: `recipientSignature` is malformed or was not signed by `permission.recipient`
102
+ /// - Disabled: `validatorContract` returned `false` indicating that this permission has been externally disabled
103
+ /// @param permission Permission struct containing data necessary to validate data access and seal for return.
104
+ ///
105
+ /// NOTE: Functions protected by `withPermission` should return ONLY the sensitive data of `permission.issuer`.
106
+ /// !! Returning data of `msg.sender` will leak sensitive values - `msg.sender` cannot be trusted in view functions !!
107
+ modifier withPermission(Permission memory permission) {
108
+ // Expiration
109
+ if (permission.expiration < block.timestamp)
110
+ revert PermissionInvalid_Expired();
111
+
112
+ // Issuer signature
113
+ if (
114
+ !SignatureChecker.isValidSignatureNow(
115
+ permission.issuer,
116
+ _hashTypedDataV4(permission.issuerHash()),
117
+ permission.issuerSignature
118
+ )
119
+ ) revert PermissionInvalid_IssuerSignature();
120
+
121
+ // (if applicable) Recipient signature
122
+ if (
123
+ permission.recipient != address(0) &&
124
+ !SignatureChecker.isValidSignatureNow(
125
+ permission.recipient,
126
+ _hashTypedDataV4(permission.recipientHash()),
127
+ permission.recipientSignature
128
+ )
129
+ ) revert PermissionInvalid_RecipientSignature();
130
+
131
+ // (if applicable) Externally disabled
132
+ if (
133
+ permission.validatorId != 0 &&
134
+ permission.validatorContract != address(0) &&
135
+ IPermissionCustomIdValidator(permission.validatorContract).disabled(
136
+ permission.issuer,
137
+ permission.validatorId
138
+ )
139
+ ) revert PermissionInvalid_Disabled();
140
+
141
+ _;
142
+ }
143
+
144
+ function hashTypedDataV4(
145
+ bytes32 structHash
146
+ ) public view virtual returns (bytes32) {
147
+ return _hashTypedDataV4(structHash);
148
+ }
149
+ }
150
+
151
+ /// @dev Internal utility library to improve the readability of PermissionedV2
152
+ /// Primarily focused on signature type hashes
153
+ library PermissionUtils {
154
+ function issuerHash(
155
+ Permission memory permission
156
+ ) internal pure returns (bytes32) {
157
+ if (permission.recipient == address(0))
158
+ return issuerSelfHash(permission);
159
+ return issuerSharedHash(permission);
160
+ }
161
+
162
+ function issuerSelfHash(
163
+ Permission memory permission
164
+ ) internal pure returns (bytes32) {
165
+ return
166
+ keccak256(
167
+ abi.encode(
168
+ keccak256(
169
+ "PermissionedV2IssuerSelf(address issuer,uint64 expiration,address recipient,uint256 validatorId,address validatorContract,bytes32 sealingKey)"
170
+ ),
171
+ permission.issuer,
172
+ permission.expiration,
173
+ permission.recipient,
174
+ permission.validatorId,
175
+ permission.validatorContract,
176
+ permission.sealingKey
177
+ )
178
+ );
179
+ }
180
+
181
+ function issuerSharedHash(
182
+ Permission memory permission
183
+ ) internal pure returns (bytes32) {
184
+ return
185
+ keccak256(
186
+ abi.encode(
187
+ keccak256(
188
+ "PermissionedV2IssuerShared(address issuer,uint64 expiration,address recipient,uint256 validatorId,address validatorContract)"
189
+ ),
190
+ permission.issuer,
191
+ permission.expiration,
192
+ permission.recipient,
193
+ permission.validatorId,
194
+ permission.validatorContract
195
+ )
196
+ );
197
+ }
198
+
199
+ function recipientHash(
200
+ Permission memory permission
201
+ ) internal pure returns (bytes32) {
202
+ return
203
+ keccak256(
204
+ abi.encode(
205
+ keccak256(
206
+ "PermissionedV2Recipient(bytes32 sealingKey,bytes issuerSignature)"
207
+ ),
208
+ permission.sealingKey,
209
+ keccak256(permission.issuerSignature)
210
+ )
211
+ );
212
+ }
213
+ }
@@ -0,0 +1,98 @@
1
+ // SPDX-License-Identifier: UNLICENSED
2
+ pragma solidity ^0.8.13;
3
+
4
+ import '@fhenixprotocol/cofhe-contracts/FHE.sol';
5
+
6
+ /// @title TestBed
7
+ /// @notice Minimal contract used to smoke-test CoFHE/FHE flows (mocks + permissions + decrypt plumbing).
8
+ /// @dev This contract is intentionally simple and is primarily a convenience for Hardhat-based local
9
+ /// development: the Hardhat plugin can deploy it automatically when `deployTestBed` is enabled.
10
+ ///
11
+ /// In Foundry flows, nothing deploys or depends on this contract automatically — tests must
12
+ /// instantiate it explicitly (this repository includes such Foundry tests). You may deploy/use it
13
+ /// in `forge test` if you want a known-good reference target.
14
+ ///
15
+ /// Important concepts:
16
+ /// - `eNumber` is an on-chain encrypted handle type (`euint32`). In real CoFHE, this represents a ciphertext.
17
+ /// - `numberHash` stores the unwrapped handle (`ctHash`) as a uint256 for easy inspection/assertions.
18
+ /// - After every state update we call `FHE.allowThis(...)` and `FHE.allowSender(...)` so the contract
19
+ /// and the transaction sender can continue operating on / decrypting the updated handle.
20
+ contract TestBed {
21
+ euint32 public eNumber;
22
+ bytes32 public numberHash;
23
+
24
+ /// @notice Marker used by deploy scripts/tests to confirm the contract is deployed.
25
+ function exists() public pure returns (bool) {
26
+ return true;
27
+ }
28
+
29
+ /// @notice Sets `eNumber` from an encrypted input struct.
30
+ /// @dev Typically used when testing client-side encryption flows.
31
+ function setNumber(InEuint32 memory inNumber) public {
32
+ eNumber = FHE.asEuint32(inNumber);
33
+ numberHash = euint32.unwrap(eNumber);
34
+ FHE.allowThis(eNumber);
35
+ FHE.allowSender(eNumber);
36
+ }
37
+
38
+ /// @notice Convenience setter that casts a plaintext value into an encrypted handle.
39
+ /// @dev Useful for quick smoke tests that don't need pre-encryption.
40
+ function setNumberTrivial(uint32 inNumber) public {
41
+ eNumber = FHE.asEuint32(inNumber);
42
+ numberHash = euint32.unwrap(eNumber);
43
+ FHE.allowThis(eNumber);
44
+ FHE.allowSender(eNumber);
45
+ }
46
+
47
+ /// @notice Increments `eNumber` by 1 using FHE arithmetic.
48
+ function increment() public {
49
+ eNumber = FHE.add(eNumber, FHE.asEuint32(1));
50
+ FHE.allowThis(eNumber);
51
+ FHE.allowSender(eNumber);
52
+ }
53
+
54
+ /// @notice Adds an encrypted input to `eNumber`.
55
+ function add(InEuint32 memory inNumber) public {
56
+ eNumber = FHE.add(eNumber, FHE.asEuint32(inNumber));
57
+
58
+ FHE.allowThis(eNumber);
59
+ FHE.allowSender(eNumber);
60
+ }
61
+
62
+ /// @notice Subtracts an encrypted input from `eNumber`, clamped to 0 to avoid underflow.
63
+ function sub(InEuint32 memory inNumber) public {
64
+ euint32 inAsEuint32 = FHE.asEuint32(inNumber);
65
+ euint32 eSubOrZero = FHE.select(FHE.lte(inAsEuint32, eNumber), inAsEuint32, FHE.asEuint32(0));
66
+ eNumber = FHE.sub(eNumber, eSubOrZero);
67
+ FHE.allowThis(eNumber);
68
+ FHE.allowSender(eNumber);
69
+ }
70
+
71
+ /// @notice Multiplies `eNumber` by an encrypted input.
72
+ function mul(InEuint32 memory inNumber) public {
73
+ eNumber = FHE.mul(eNumber, FHE.asEuint32(inNumber));
74
+ FHE.allowThis(eNumber);
75
+ FHE.allowSender(eNumber);
76
+ }
77
+
78
+ /// @notice Requests decryption of `eNumber`.
79
+ /// @dev In real CoFHE this is asynchronous; in mocks it is simulated.
80
+ function decrypt() public {
81
+ FHE.decrypt(eNumber);
82
+ }
83
+
84
+ /// @notice Reads a decryption result (reverts if not ready depending on implementation).
85
+ function getDecryptResult(euint32 input1) public view returns (uint32) {
86
+ return FHE.getDecryptResult(input1);
87
+ }
88
+
89
+ /// @notice Reads a decryption result safely, returning a readiness flag.
90
+ function getDecryptResultSafe(euint32 input1) public view returns (uint32 value, bool decrypted) {
91
+ return FHE.getDecryptResultSafe(input1);
92
+ }
93
+
94
+ /// @notice Publishes a decrypt result for an encrypted handle.
95
+ function publishDecryptResult(euint32 input, uint32 result, bytes memory signature) external {
96
+ FHE.publishDecryptResult(input, result, signature);
97
+ }
98
+ }