@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.
- package/CHANGELOG.md +86 -0
- package/README.md +180 -0
- package/contracts/ABITest.sol +99 -0
- package/contracts/MockACL.sol +342 -0
- package/contracts/MockCoFHE.sol +444 -0
- package/contracts/MockTaskManager.sol +676 -0
- package/contracts/MockThresholdNetwork.sol +199 -0
- package/contracts/MockZkVerifier.sol +140 -0
- package/contracts/Permissioned.sol +213 -0
- package/contracts/TestBed.sol +98 -0
- package/dist/index.d.mts +3193 -0
- package/dist/index.d.ts +3193 -0
- package/dist/index.js +2864 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2858 -0
- package/dist/index.mjs.map +1 -0
- package/foundry.toml +10 -0
- package/package.json +65 -0
- package/remappings.txt +5 -0
- package/src/MockACL.ts +620 -0
- package/src/MockTaskManager.ts +1211 -0
- package/src/MockThresholdNetwork.ts +457 -0
- package/src/MockZkVerifier.ts +266 -0
- package/src/TestBed.ts +311 -0
- package/src/index.ts +7 -0
- package/src/typechain-types/MockACL.ts +382 -0
- package/src/typechain-types/MockTaskManager.ts +565 -0
- package/src/typechain-types/MockThresholdNetwork.ts +247 -0
- package/src/typechain-types/MockZkVerifier.ts +205 -0
- package/src/typechain-types/TestBed.ts +172 -0
- package/src/typechain-types/common.ts +92 -0
- package/src/typechain-types/index.ts +8 -0
- package/src/types.ts +14 -0
|
@@ -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
|
+
}
|