@zentity/fhevm-contracts 0.1.0
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/README.md +160 -0
- package/abi/ComplianceRules.json +352 -0
- package/abi/CompliantERC20.json +493 -0
- package/abi/IdentityRegistry.json +712 -0
- package/abi/index.d.ts +3 -0
- package/abi/index.js +9 -0
- package/contracts/ARCHITECTURE.md +66 -0
- package/contracts/ARCHITECTURE_EXPLAINER.md +77 -0
- package/contracts/compliance/ComplianceRules.sol +255 -0
- package/contracts/core/IdentityRegistry.sol +352 -0
- package/contracts/interfaces/IIdentityRegistry.sol +226 -0
- package/contracts/mocks/.gitkeep +0 -0
- package/contracts/tokens/CompliantERC20.sol +379 -0
- package/deployments/hardhat/addresses.json +20 -0
- package/deployments/localhost/.chainId +1 -0
- package/deployments/localhost/ComplianceRules.json +662 -0
- package/deployments/localhost/CompliantERC20.json +888 -0
- package/deployments/localhost/IdentityRegistry.json +1093 -0
- package/deployments/localhost/solcInputs/e36969353329df673b4fae03d39e01c4.json +60 -0
- package/deployments/sepolia/.chainId +1 -0
- package/deployments/sepolia/.gitkeep +0 -0
- package/deployments/sepolia/ComplianceRules.json +662 -0
- package/deployments/sepolia/CompliantERC20.json +888 -0
- package/deployments/sepolia/IdentityRegistry.json +1093 -0
- package/deployments/sepolia/solcInputs/93d280ff0d4e798a18947a9ed6015031.json +60 -0
- package/dist/index.d.ts +459 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +135 -0
- package/package.json +110 -0
- package/typechain-types/@fhevm/index.ts +5 -0
- package/typechain-types/@fhevm/solidity/config/ZamaConfig.sol/ZamaConfig.ts +69 -0
- package/typechain-types/@fhevm/solidity/config/ZamaConfig.sol/ZamaEthereumConfig.ts +90 -0
- package/typechain-types/@fhevm/solidity/config/ZamaConfig.sol/index.ts +5 -0
- package/typechain-types/@fhevm/solidity/config/index.ts +5 -0
- package/typechain-types/@fhevm/solidity/index.ts +7 -0
- package/typechain-types/@fhevm/solidity/lib/FHE.sol/FHE.ts +112 -0
- package/typechain-types/@fhevm/solidity/lib/FHE.sol/IKMSVerifier.ts +108 -0
- package/typechain-types/@fhevm/solidity/lib/FHE.sol/index.ts +5 -0
- package/typechain-types/@fhevm/solidity/lib/Impl.sol/IACL.ts +190 -0
- package/typechain-types/@fhevm/solidity/lib/Impl.sol/IFHEVMExecutor.ts +623 -0
- package/typechain-types/@fhevm/solidity/lib/Impl.sol/IInputVerifier.ts +90 -0
- package/typechain-types/@fhevm/solidity/lib/Impl.sol/index.ts +6 -0
- package/typechain-types/@fhevm/solidity/lib/index.ts +7 -0
- package/typechain-types/common.ts +131 -0
- package/typechain-types/contracts/compliance/ComplianceRules.ts +479 -0
- package/typechain-types/contracts/compliance/index.ts +4 -0
- package/typechain-types/contracts/core/IdentityRegistry.ts +874 -0
- package/typechain-types/contracts/core/index.ts +4 -0
- package/typechain-types/contracts/index.ts +11 -0
- package/typechain-types/contracts/interfaces/IIdentityRegistry.ts +798 -0
- package/typechain-types/contracts/interfaces/index.ts +4 -0
- package/typechain-types/contracts/tokens/CompliantERC20.sol/CompliantERC20.ts +572 -0
- package/typechain-types/contracts/tokens/CompliantERC20.sol/IComplianceChecker.ts +95 -0
- package/typechain-types/contracts/tokens/CompliantERC20.sol/index.ts +5 -0
- package/typechain-types/contracts/tokens/index.ts +5 -0
- package/typechain-types/factories/@fhevm/index.ts +4 -0
- package/typechain-types/factories/@fhevm/solidity/config/ZamaConfig.sol/ZamaConfig__factory.ts +69 -0
- package/typechain-types/factories/@fhevm/solidity/config/ZamaConfig.sol/ZamaEthereumConfig__factory.ts +43 -0
- package/typechain-types/factories/@fhevm/solidity/config/ZamaConfig.sol/index.ts +5 -0
- package/typechain-types/factories/@fhevm/solidity/config/index.ts +4 -0
- package/typechain-types/factories/@fhevm/solidity/index.ts +5 -0
- package/typechain-types/factories/@fhevm/solidity/lib/FHE.sol/FHE__factory.ts +88 -0
- package/typechain-types/factories/@fhevm/solidity/lib/FHE.sol/IKMSVerifier__factory.ts +54 -0
- package/typechain-types/factories/@fhevm/solidity/lib/FHE.sol/index.ts +5 -0
- package/typechain-types/factories/@fhevm/solidity/lib/Impl.sol/IACL__factory.ts +121 -0
- package/typechain-types/factories/@fhevm/solidity/lib/Impl.sol/IFHEVMExecutor__factory.ts +810 -0
- package/typechain-types/factories/@fhevm/solidity/lib/Impl.sol/IInputVerifier__factory.ts +32 -0
- package/typechain-types/factories/@fhevm/solidity/lib/Impl.sol/index.ts +6 -0
- package/typechain-types/factories/@fhevm/solidity/lib/index.ts +5 -0
- package/typechain-types/factories/contracts/compliance/ComplianceRules__factory.ts +437 -0
- package/typechain-types/factories/contracts/compliance/index.ts +4 -0
- package/typechain-types/factories/contracts/core/IdentityRegistry__factory.ts +777 -0
- package/typechain-types/factories/contracts/core/index.ts +4 -0
- package/typechain-types/factories/contracts/index.ts +7 -0
- package/typechain-types/factories/contracts/interfaces/IIdentityRegistry__factory.ts +640 -0
- package/typechain-types/factories/contracts/interfaces/index.ts +4 -0
- package/typechain-types/factories/contracts/tokens/CompliantERC20.sol/CompliantERC20__factory.ts +581 -0
- package/typechain-types/factories/contracts/tokens/CompliantERC20.sol/IComplianceChecker__factory.ts +44 -0
- package/typechain-types/factories/contracts/tokens/CompliantERC20.sol/index.ts +5 -0
- package/typechain-types/factories/contracts/tokens/index.ts +4 -0
- package/typechain-types/factories/index.ts +5 -0
- package/typechain-types/hardhat.d.ts +261 -0
- package/typechain-types/index.ts +32 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// solhint-disable not-rely-on-time
|
|
3
|
+
pragma solidity ^0.8.27;
|
|
4
|
+
|
|
5
|
+
import {FHE, euint8, euint16, ebool, externalEuint8, externalEuint16, externalEbool} from "@fhevm/solidity/lib/FHE.sol";
|
|
6
|
+
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
7
|
+
import {IIdentityRegistry} from "../interfaces/IIdentityRegistry.sol";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @title IdentityRegistry
|
|
11
|
+
* @author Gustavo Valverde
|
|
12
|
+
* @notice On-chain encrypted identity registry for KYC or compliance platforms
|
|
13
|
+
* @dev Part of zentity-fhevm-contracts - Builder Track
|
|
14
|
+
*
|
|
15
|
+
* @custom:category identity
|
|
16
|
+
* @custom:concept Storing encrypted identity attributes (euint8, euint16, ebool)
|
|
17
|
+
* @custom:difficulty intermediate
|
|
18
|
+
*
|
|
19
|
+
* This contract maintains an encrypted identity registry where authorized registrars
|
|
20
|
+
* (typically a backend service) can attest to user identity attributes. All sensitive data
|
|
21
|
+
* remains encrypted on-chain.
|
|
22
|
+
*
|
|
23
|
+
* Key patterns demonstrated:
|
|
24
|
+
* 1. Multiple encrypted types (euint8, euint16, ebool)
|
|
25
|
+
* 2. Role-based access control (registrars)
|
|
26
|
+
* 3. Struct-like data storage with mappings
|
|
27
|
+
* 4. FHE permission management (allowThis, allow)
|
|
28
|
+
*/
|
|
29
|
+
contract IdentityRegistry is IIdentityRegistry, ZamaEthereumConfig {
|
|
30
|
+
// ============ Encrypted Identity Attributes ============
|
|
31
|
+
|
|
32
|
+
/// @notice Encrypted birth year offset from 1900
|
|
33
|
+
mapping(address user => euint8 birthYearOffset) private birthYearOffsets;
|
|
34
|
+
|
|
35
|
+
/// @notice Encrypted country code (ISO 3166-1 numeric)
|
|
36
|
+
mapping(address user => euint16 countryCode) private countryCodes;
|
|
37
|
+
|
|
38
|
+
/// @notice Encrypted KYC verification level (0-5)
|
|
39
|
+
mapping(address user => euint8 kycLevel) private kycLevels;
|
|
40
|
+
|
|
41
|
+
/// @notice Encrypted blacklist status
|
|
42
|
+
mapping(address user => ebool blacklisted) private isBlacklisted;
|
|
43
|
+
|
|
44
|
+
/// @notice Timestamp of last attestation
|
|
45
|
+
mapping(address user => uint256 timestamp) public attestationTimestamp;
|
|
46
|
+
|
|
47
|
+
/// @notice Current attestation id for a user (0 if not attested)
|
|
48
|
+
mapping(address user => uint256 attestationId) public currentAttestationId;
|
|
49
|
+
|
|
50
|
+
/// @notice Latest attestation id ever issued for a user
|
|
51
|
+
mapping(address user => uint256 attestationId) public latestAttestationId;
|
|
52
|
+
|
|
53
|
+
/// @notice Attestation metadata for auditability
|
|
54
|
+
struct AttestationMetadata {
|
|
55
|
+
uint256 timestamp;
|
|
56
|
+
uint256 revokedAt;
|
|
57
|
+
address registrar;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// @notice Attestation history by user and attestation id
|
|
61
|
+
mapping(address user => mapping(uint256 attestationId => AttestationMetadata)) private attestationHistory;
|
|
62
|
+
|
|
63
|
+
/// @notice Store verification results for external queries
|
|
64
|
+
mapping(bytes32 key => ebool result) private verificationResults;
|
|
65
|
+
|
|
66
|
+
// ============ Access Control ============
|
|
67
|
+
|
|
68
|
+
/// @notice Owner of the registry
|
|
69
|
+
address public owner;
|
|
70
|
+
/// @notice Pending owner for two-step ownership transfer
|
|
71
|
+
address public pendingOwner;
|
|
72
|
+
|
|
73
|
+
/// @notice Authorized registrars who can attest identities
|
|
74
|
+
mapping(address registrar => bool authorized) public registrars;
|
|
75
|
+
|
|
76
|
+
/// @notice Thrown when caller lacks permission for encrypted data
|
|
77
|
+
error AccessProhibited();
|
|
78
|
+
|
|
79
|
+
// ============ Modifiers ============
|
|
80
|
+
|
|
81
|
+
modifier onlyOwner() {
|
|
82
|
+
if (msg.sender != owner) revert OnlyOwner();
|
|
83
|
+
_;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
modifier onlyRegistrar() {
|
|
87
|
+
if (!registrars[msg.sender]) revert OnlyRegistrar();
|
|
88
|
+
_;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ============ Constructor ============
|
|
92
|
+
|
|
93
|
+
/// @notice Initializes the registry with the deployer as owner and initial registrar
|
|
94
|
+
constructor() {
|
|
95
|
+
owner = msg.sender;
|
|
96
|
+
registrars[msg.sender] = true;
|
|
97
|
+
emit RegistrarAdded(msg.sender);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ============ Registrar Management ============
|
|
101
|
+
|
|
102
|
+
/// @inheritdoc IIdentityRegistry
|
|
103
|
+
function addRegistrar(address registrar) external onlyOwner {
|
|
104
|
+
registrars[registrar] = true;
|
|
105
|
+
emit RegistrarAdded(registrar);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// @inheritdoc IIdentityRegistry
|
|
109
|
+
function removeRegistrar(address registrar) external onlyOwner {
|
|
110
|
+
registrars[registrar] = false;
|
|
111
|
+
emit RegistrarRemoved(registrar);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ============ Identity Attestation ============
|
|
115
|
+
|
|
116
|
+
/// @inheritdoc IIdentityRegistry
|
|
117
|
+
function attestIdentity(
|
|
118
|
+
address user,
|
|
119
|
+
externalEuint8 encBirthYearOffset,
|
|
120
|
+
externalEuint16 encCountryCode,
|
|
121
|
+
externalEuint8 encKycLevel,
|
|
122
|
+
externalEbool encIsBlacklisted,
|
|
123
|
+
bytes calldata inputProof
|
|
124
|
+
) external onlyRegistrar {
|
|
125
|
+
if (currentAttestationId[user] != 0) revert AlreadyAttested();
|
|
126
|
+
|
|
127
|
+
// Convert and store encrypted values
|
|
128
|
+
euint8 birthYear = FHE.fromExternal(encBirthYearOffset, inputProof);
|
|
129
|
+
euint16 country = FHE.fromExternal(encCountryCode, inputProof);
|
|
130
|
+
euint8 kyc = FHE.fromExternal(encKycLevel, inputProof);
|
|
131
|
+
ebool blacklisted = FHE.fromExternal(encIsBlacklisted, inputProof);
|
|
132
|
+
|
|
133
|
+
birthYearOffsets[user] = birthYear;
|
|
134
|
+
countryCodes[user] = country;
|
|
135
|
+
kycLevels[user] = kyc;
|
|
136
|
+
isBlacklisted[user] = blacklisted;
|
|
137
|
+
|
|
138
|
+
// Grant contract permission to all values
|
|
139
|
+
FHE.allowThis(birthYear);
|
|
140
|
+
FHE.allowThis(country);
|
|
141
|
+
FHE.allowThis(kyc);
|
|
142
|
+
FHE.allowThis(blacklisted);
|
|
143
|
+
|
|
144
|
+
// Grant user permission to their own data
|
|
145
|
+
FHE.allow(birthYear, user);
|
|
146
|
+
FHE.allow(country, user);
|
|
147
|
+
FHE.allow(kyc, user);
|
|
148
|
+
FHE.allow(blacklisted, user);
|
|
149
|
+
|
|
150
|
+
uint256 newAttestationId = latestAttestationId[user] + 1;
|
|
151
|
+
latestAttestationId[user] = newAttestationId;
|
|
152
|
+
currentAttestationId[user] = newAttestationId;
|
|
153
|
+
attestationHistory[user][newAttestationId] = AttestationMetadata({
|
|
154
|
+
timestamp: block.timestamp,
|
|
155
|
+
revokedAt: 0,
|
|
156
|
+
registrar: msg.sender
|
|
157
|
+
});
|
|
158
|
+
attestationTimestamp[user] = block.timestamp;
|
|
159
|
+
|
|
160
|
+
emit IdentityAttested(user, msg.sender);
|
|
161
|
+
emit IdentityAttestedDetailed(user, msg.sender, newAttestationId, block.timestamp);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// @inheritdoc IIdentityRegistry
|
|
165
|
+
function revokeIdentity(address user) external onlyRegistrar {
|
|
166
|
+
uint256 attestationId = currentAttestationId[user];
|
|
167
|
+
if (attestationId == 0) revert NotAttested();
|
|
168
|
+
|
|
169
|
+
// Set encrypted values to encrypted zeros
|
|
170
|
+
birthYearOffsets[user] = FHE.asEuint8(0);
|
|
171
|
+
countryCodes[user] = FHE.asEuint16(0);
|
|
172
|
+
kycLevels[user] = FHE.asEuint8(0);
|
|
173
|
+
isBlacklisted[user] = FHE.asEbool(false);
|
|
174
|
+
attestationTimestamp[user] = 0;
|
|
175
|
+
currentAttestationId[user] = 0;
|
|
176
|
+
|
|
177
|
+
AttestationMetadata storage metadata = attestationHistory[user][attestationId];
|
|
178
|
+
if (metadata.revokedAt == 0) {
|
|
179
|
+
metadata.revokedAt = block.timestamp;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
emit IdentityRevoked(user);
|
|
183
|
+
emit IdentityRevokedDetailed(user, msg.sender, attestationId, block.timestamp);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ============ Encrypted Queries ============
|
|
187
|
+
|
|
188
|
+
/// @inheritdoc IIdentityRegistry
|
|
189
|
+
function getBirthYearOffset(address user) external view returns (euint8) {
|
|
190
|
+
if (attestationTimestamp[user] == 0) revert NotAttested();
|
|
191
|
+
if (!FHE.isSenderAllowed(birthYearOffsets[user])) revert AccessProhibited();
|
|
192
|
+
return birthYearOffsets[user];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/// @inheritdoc IIdentityRegistry
|
|
196
|
+
function getCountryCode(address user) external view returns (euint16) {
|
|
197
|
+
if (attestationTimestamp[user] == 0) revert NotAttested();
|
|
198
|
+
if (!FHE.isSenderAllowed(countryCodes[user])) revert AccessProhibited();
|
|
199
|
+
return countryCodes[user];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/// @inheritdoc IIdentityRegistry
|
|
203
|
+
function getKycLevel(address user) external view returns (euint8) {
|
|
204
|
+
if (attestationTimestamp[user] == 0) revert NotAttested();
|
|
205
|
+
if (!FHE.isSenderAllowed(kycLevels[user])) revert AccessProhibited();
|
|
206
|
+
return kycLevels[user];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/// @inheritdoc IIdentityRegistry
|
|
210
|
+
function getBlacklistStatus(address user) external view returns (ebool) {
|
|
211
|
+
if (attestationTimestamp[user] == 0) revert NotAttested();
|
|
212
|
+
if (!FHE.isSenderAllowed(isBlacklisted[user])) revert AccessProhibited();
|
|
213
|
+
return isBlacklisted[user];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ============ Verification Helpers ============
|
|
217
|
+
|
|
218
|
+
/// @inheritdoc IIdentityRegistry
|
|
219
|
+
function hasMinKycLevel(address user, uint8 minLevel) external returns (ebool) {
|
|
220
|
+
if (attestationTimestamp[user] == 0) revert NotAttested();
|
|
221
|
+
if (!FHE.isSenderAllowed(kycLevels[user])) revert AccessProhibited();
|
|
222
|
+
ebool result = FHE.ge(kycLevels[user], FHE.asEuint8(minLevel));
|
|
223
|
+
|
|
224
|
+
// Store result for later retrieval
|
|
225
|
+
bytes32 key = keccak256(abi.encodePacked(user, uint8(0), uint256(minLevel)));
|
|
226
|
+
verificationResults[key] = result;
|
|
227
|
+
|
|
228
|
+
// Grant caller permission to decrypt the result
|
|
229
|
+
FHE.allowThis(result);
|
|
230
|
+
FHE.allow(result, msg.sender);
|
|
231
|
+
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/// @inheritdoc IIdentityRegistry
|
|
236
|
+
function isFromCountry(address user, uint16 country) external returns (ebool) {
|
|
237
|
+
if (attestationTimestamp[user] == 0) revert NotAttested();
|
|
238
|
+
if (!FHE.isSenderAllowed(countryCodes[user])) revert AccessProhibited();
|
|
239
|
+
ebool result = FHE.eq(countryCodes[user], FHE.asEuint16(country));
|
|
240
|
+
|
|
241
|
+
// Store result for later retrieval
|
|
242
|
+
bytes32 key = keccak256(abi.encodePacked(user, uint8(1), uint256(country)));
|
|
243
|
+
verificationResults[key] = result;
|
|
244
|
+
|
|
245
|
+
// Grant caller permission to decrypt the result
|
|
246
|
+
FHE.allowThis(result);
|
|
247
|
+
FHE.allow(result, msg.sender);
|
|
248
|
+
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/// @inheritdoc IIdentityRegistry
|
|
253
|
+
function isNotBlacklisted(address user) external returns (ebool) {
|
|
254
|
+
if (attestationTimestamp[user] == 0) revert NotAttested();
|
|
255
|
+
if (!FHE.isSenderAllowed(isBlacklisted[user])) revert AccessProhibited();
|
|
256
|
+
ebool result = FHE.not(isBlacklisted[user]);
|
|
257
|
+
|
|
258
|
+
// Store result for later retrieval
|
|
259
|
+
bytes32 key = keccak256(abi.encodePacked(user, uint8(2), uint256(0)));
|
|
260
|
+
verificationResults[key] = result;
|
|
261
|
+
|
|
262
|
+
// Grant caller permission to decrypt the result
|
|
263
|
+
FHE.allowThis(result);
|
|
264
|
+
FHE.allow(result, msg.sender);
|
|
265
|
+
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ============ Result Getters ============
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @notice Get the last KYC level verification result
|
|
273
|
+
* @param user Address that was checked
|
|
274
|
+
* @param minLevel Level that was checked
|
|
275
|
+
* @return Encrypted boolean result
|
|
276
|
+
*/
|
|
277
|
+
function getKycLevelResult(address user, uint8 minLevel) external view returns (ebool) {
|
|
278
|
+
bytes32 key = keccak256(abi.encodePacked(user, uint8(0), uint256(minLevel)));
|
|
279
|
+
ebool result = verificationResults[key];
|
|
280
|
+
if (!FHE.isSenderAllowed(result)) revert AccessProhibited();
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* @notice Get the last country verification result
|
|
286
|
+
* @param user Address that was checked
|
|
287
|
+
* @param country Country code that was checked
|
|
288
|
+
* @return Encrypted boolean result
|
|
289
|
+
*/
|
|
290
|
+
function getCountryResult(address user, uint16 country) external view returns (ebool) {
|
|
291
|
+
bytes32 key = keccak256(abi.encodePacked(user, uint8(1), uint256(country)));
|
|
292
|
+
ebool result = verificationResults[key];
|
|
293
|
+
if (!FHE.isSenderAllowed(result)) revert AccessProhibited();
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* @notice Get the last blacklist verification result
|
|
299
|
+
* @param user Address that was checked
|
|
300
|
+
* @return Encrypted boolean result
|
|
301
|
+
*/
|
|
302
|
+
function getBlacklistResult(address user) external view returns (ebool) {
|
|
303
|
+
bytes32 key = keccak256(abi.encodePacked(user, uint8(2), uint256(0)));
|
|
304
|
+
ebool result = verificationResults[key];
|
|
305
|
+
if (!FHE.isSenderAllowed(result)) revert AccessProhibited();
|
|
306
|
+
return result;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ============ Access Control ============
|
|
310
|
+
|
|
311
|
+
/// @inheritdoc IIdentityRegistry
|
|
312
|
+
function transferOwnership(address newOwner) external onlyOwner {
|
|
313
|
+
if (newOwner == address(0)) revert InvalidOwner();
|
|
314
|
+
pendingOwner = newOwner;
|
|
315
|
+
emit OwnershipTransferStarted(owner, newOwner);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/// @inheritdoc IIdentityRegistry
|
|
319
|
+
function acceptOwnership() external {
|
|
320
|
+
if (msg.sender != pendingOwner) revert OnlyPendingOwner();
|
|
321
|
+
address previousOwner = owner;
|
|
322
|
+
owner = pendingOwner;
|
|
323
|
+
pendingOwner = address(0);
|
|
324
|
+
emit OwnershipTransferred(previousOwner, owner);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/// @inheritdoc IIdentityRegistry
|
|
328
|
+
function grantAccessTo(address grantee) external {
|
|
329
|
+
if (attestationTimestamp[msg.sender] == 0) revert NotAttested();
|
|
330
|
+
|
|
331
|
+
FHE.allow(birthYearOffsets[msg.sender], grantee);
|
|
332
|
+
FHE.allow(countryCodes[msg.sender], grantee);
|
|
333
|
+
FHE.allow(kycLevels[msg.sender], grantee);
|
|
334
|
+
FHE.allow(isBlacklisted[msg.sender], grantee);
|
|
335
|
+
|
|
336
|
+
emit AccessGranted(msg.sender, grantee);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/// @inheritdoc IIdentityRegistry
|
|
340
|
+
function isAttested(address user) external view returns (bool) {
|
|
341
|
+
return currentAttestationId[user] > 0;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/// @inheritdoc IIdentityRegistry
|
|
345
|
+
function getAttestationMetadata(
|
|
346
|
+
address user,
|
|
347
|
+
uint256 attestationId
|
|
348
|
+
) external view returns (uint256 timestamp, uint256 revokedAt, address registrar) {
|
|
349
|
+
AttestationMetadata storage metadata = attestationHistory[user][attestationId];
|
|
350
|
+
return (metadata.timestamp, metadata.revokedAt, metadata.registrar);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.27;
|
|
3
|
+
|
|
4
|
+
import {euint8, euint16, ebool, externalEuint8, externalEuint16, externalEbool} from "@fhevm/solidity/lib/FHE.sol";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @title IIdentityRegistry
|
|
8
|
+
* @author Gustavo Valverde
|
|
9
|
+
* @notice Interface for the IdentityRegistry contract
|
|
10
|
+
* @dev Part of zentity-fhevm-contracts - Builder Track
|
|
11
|
+
*
|
|
12
|
+
* @custom:category identity
|
|
13
|
+
* @custom:concept Interface for encrypted identity storage and verification
|
|
14
|
+
* @custom:difficulty intermediate
|
|
15
|
+
*/
|
|
16
|
+
interface IIdentityRegistry {
|
|
17
|
+
// ============ Events ============
|
|
18
|
+
|
|
19
|
+
/// @notice Emitted when a new registrar is authorized
|
|
20
|
+
/// @param registrar Address of the authorized registrar
|
|
21
|
+
event RegistrarAdded(address indexed registrar);
|
|
22
|
+
|
|
23
|
+
/// @notice Emitted when a registrar's authorization is revoked
|
|
24
|
+
/// @param registrar Address of the removed registrar
|
|
25
|
+
event RegistrarRemoved(address indexed registrar);
|
|
26
|
+
|
|
27
|
+
/// @notice Emitted when an identity is attested on-chain
|
|
28
|
+
/// @param user Address of the attested user
|
|
29
|
+
/// @param registrar Address of the registrar who performed attestation
|
|
30
|
+
event IdentityAttested(address indexed user, address indexed registrar);
|
|
31
|
+
|
|
32
|
+
/// @notice Emitted when an identity is attested on-chain with metadata
|
|
33
|
+
/// @param user Address of the attested user
|
|
34
|
+
/// @param registrar Address of the registrar who performed attestation
|
|
35
|
+
/// @param attestationId Monotonic attestation identifier for the user
|
|
36
|
+
/// @param timestamp Unix timestamp of attestation
|
|
37
|
+
event IdentityAttestedDetailed(
|
|
38
|
+
address indexed user,
|
|
39
|
+
address indexed registrar,
|
|
40
|
+
uint256 indexed attestationId,
|
|
41
|
+
uint256 timestamp
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
/// @notice Emitted when an identity attestation is revoked
|
|
45
|
+
/// @param user Address whose attestation was revoked
|
|
46
|
+
event IdentityRevoked(address indexed user);
|
|
47
|
+
|
|
48
|
+
/// @notice Emitted when an identity attestation is revoked with metadata
|
|
49
|
+
/// @param user Address whose attestation was revoked
|
|
50
|
+
/// @param registrar Address of the registrar who performed the revocation
|
|
51
|
+
/// @param attestationId Attestation identifier that was revoked
|
|
52
|
+
/// @param timestamp Unix timestamp of revocation
|
|
53
|
+
event IdentityRevokedDetailed(
|
|
54
|
+
address indexed user,
|
|
55
|
+
address indexed registrar,
|
|
56
|
+
uint256 indexed attestationId,
|
|
57
|
+
uint256 timestamp
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
/// @notice Emitted when a user grants access to their encrypted data
|
|
61
|
+
/// @param user Address of the user granting access
|
|
62
|
+
/// @param grantee Address receiving access permission
|
|
63
|
+
event AccessGranted(address indexed user, address indexed grantee);
|
|
64
|
+
|
|
65
|
+
/// @notice Emitted when ownership transfer is initiated
|
|
66
|
+
/// @param currentOwner Current owner address
|
|
67
|
+
/// @param pendingOwner Address that can accept ownership
|
|
68
|
+
event OwnershipTransferStarted(address indexed currentOwner, address indexed pendingOwner);
|
|
69
|
+
|
|
70
|
+
/// @notice Emitted when ownership transfer is completed
|
|
71
|
+
/// @param previousOwner Previous owner address
|
|
72
|
+
/// @param newOwner New owner address
|
|
73
|
+
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
|
|
74
|
+
|
|
75
|
+
// ============ Errors ============
|
|
76
|
+
|
|
77
|
+
/// @notice Thrown when caller is not the contract owner
|
|
78
|
+
error OnlyOwner();
|
|
79
|
+
|
|
80
|
+
/// @notice Thrown when caller is not the pending owner
|
|
81
|
+
error OnlyPendingOwner();
|
|
82
|
+
|
|
83
|
+
/// @notice Thrown when new owner is the zero address
|
|
84
|
+
error InvalidOwner();
|
|
85
|
+
|
|
86
|
+
/// @notice Thrown when caller is not an authorized registrar
|
|
87
|
+
error OnlyRegistrar();
|
|
88
|
+
|
|
89
|
+
/// @notice Thrown when querying a user without attestation
|
|
90
|
+
error NotAttested();
|
|
91
|
+
|
|
92
|
+
/// @notice Thrown when attempting to attest an already-attested user
|
|
93
|
+
error AlreadyAttested();
|
|
94
|
+
|
|
95
|
+
// ============ Registrar Management ============
|
|
96
|
+
|
|
97
|
+
/// @notice Add a new authorized registrar
|
|
98
|
+
/// @param registrar Address to authorize as registrar
|
|
99
|
+
function addRegistrar(address registrar) external;
|
|
100
|
+
|
|
101
|
+
/// @notice Remove an authorized registrar
|
|
102
|
+
/// @param registrar Address to remove from registrars
|
|
103
|
+
function removeRegistrar(address registrar) external;
|
|
104
|
+
|
|
105
|
+
// ============ Identity Attestation ============
|
|
106
|
+
|
|
107
|
+
/// @notice Attest a user's encrypted identity data on-chain
|
|
108
|
+
/// @param user Address of the user being attested
|
|
109
|
+
/// @param encBirthYearOffset Encrypted birth year offset (years since 1900)
|
|
110
|
+
/// @param encCountryCode Encrypted ISO 3166-1 numeric country code
|
|
111
|
+
/// @param encKycLevel Encrypted KYC verification level (0-3)
|
|
112
|
+
/// @param encIsBlacklisted Encrypted blacklist status
|
|
113
|
+
/// @param inputProof FHE proof for encrypted inputs
|
|
114
|
+
function attestIdentity(
|
|
115
|
+
address user,
|
|
116
|
+
externalEuint8 encBirthYearOffset,
|
|
117
|
+
externalEuint16 encCountryCode,
|
|
118
|
+
externalEuint8 encKycLevel,
|
|
119
|
+
externalEbool encIsBlacklisted,
|
|
120
|
+
bytes calldata inputProof
|
|
121
|
+
) external;
|
|
122
|
+
|
|
123
|
+
/// @notice Revoke a user's identity attestation
|
|
124
|
+
/// @param user Address of the user to revoke
|
|
125
|
+
function revokeIdentity(address user) external;
|
|
126
|
+
|
|
127
|
+
// ============ Encrypted Queries ============
|
|
128
|
+
|
|
129
|
+
/// @notice Get user's encrypted birth year offset
|
|
130
|
+
/// @param user Address of the user
|
|
131
|
+
/// @return Encrypted birth year offset (years since 1900)
|
|
132
|
+
function getBirthYearOffset(address user) external view returns (euint8);
|
|
133
|
+
|
|
134
|
+
/// @notice Get user's encrypted country code
|
|
135
|
+
/// @param user Address of the user
|
|
136
|
+
/// @return Encrypted ISO 3166-1 numeric country code
|
|
137
|
+
function getCountryCode(address user) external view returns (euint16);
|
|
138
|
+
|
|
139
|
+
/// @notice Get user's encrypted KYC level
|
|
140
|
+
/// @param user Address of the user
|
|
141
|
+
/// @return Encrypted KYC verification level (0-3)
|
|
142
|
+
function getKycLevel(address user) external view returns (euint8);
|
|
143
|
+
|
|
144
|
+
/// @notice Get user's encrypted blacklist status
|
|
145
|
+
/// @param user Address of the user
|
|
146
|
+
/// @return Encrypted blacklist status (true if blacklisted)
|
|
147
|
+
function getBlacklistStatus(address user) external view returns (ebool);
|
|
148
|
+
|
|
149
|
+
// ============ Verification Helpers ============
|
|
150
|
+
|
|
151
|
+
/// @notice Check if user has minimum KYC level (encrypted comparison)
|
|
152
|
+
/// @param user Address of the user
|
|
153
|
+
/// @param minLevel Minimum KYC level required
|
|
154
|
+
/// @return Encrypted boolean result of comparison
|
|
155
|
+
function hasMinKycLevel(address user, uint8 minLevel) external returns (ebool);
|
|
156
|
+
|
|
157
|
+
/// @notice Check if user is from a specific country (encrypted comparison)
|
|
158
|
+
/// @param user Address of the user
|
|
159
|
+
/// @param country ISO 3166-1 numeric country code to check
|
|
160
|
+
/// @return Encrypted boolean result of comparison
|
|
161
|
+
function isFromCountry(address user, uint16 country) external returns (ebool);
|
|
162
|
+
|
|
163
|
+
/// @notice Check if user is not blacklisted (encrypted)
|
|
164
|
+
/// @param user Address of the user
|
|
165
|
+
/// @return Encrypted boolean (true if NOT blacklisted)
|
|
166
|
+
function isNotBlacklisted(address user) external returns (ebool);
|
|
167
|
+
|
|
168
|
+
// ============ Access Control ============
|
|
169
|
+
|
|
170
|
+
/// @notice Grant a contract access to caller's encrypted identity data
|
|
171
|
+
/// @param grantee Address to grant access to
|
|
172
|
+
function grantAccessTo(address grantee) external;
|
|
173
|
+
|
|
174
|
+
/// @notice Check if a user has been attested
|
|
175
|
+
/// @param user Address of the user
|
|
176
|
+
/// @return True if user has valid attestation
|
|
177
|
+
function isAttested(address user) external view returns (bool);
|
|
178
|
+
|
|
179
|
+
// ============ Public State ============
|
|
180
|
+
|
|
181
|
+
/// @notice Get the contract owner address
|
|
182
|
+
/// @return Owner address
|
|
183
|
+
function owner() external view returns (address);
|
|
184
|
+
|
|
185
|
+
/// @notice Get the pending owner address
|
|
186
|
+
/// @return Pending owner address
|
|
187
|
+
function pendingOwner() external view returns (address);
|
|
188
|
+
|
|
189
|
+
/// @notice Initiate transfer of contract ownership
|
|
190
|
+
/// @param newOwner Address that can accept ownership
|
|
191
|
+
function transferOwnership(address newOwner) external;
|
|
192
|
+
|
|
193
|
+
/// @notice Accept ownership transfer
|
|
194
|
+
function acceptOwnership() external;
|
|
195
|
+
|
|
196
|
+
/// @notice Check if an address is an authorized registrar
|
|
197
|
+
/// @param registrar Address to check
|
|
198
|
+
/// @return True if address is authorized registrar
|
|
199
|
+
function registrars(address registrar) external view returns (bool);
|
|
200
|
+
|
|
201
|
+
/// @notice Get the timestamp when a user was attested
|
|
202
|
+
/// @param user Address of the user
|
|
203
|
+
/// @return Unix timestamp of attestation (0 if not attested)
|
|
204
|
+
function attestationTimestamp(address user) external view returns (uint256);
|
|
205
|
+
|
|
206
|
+
/// @notice Get the current attestation id for a user (0 if not attested)
|
|
207
|
+
/// @param user Address of the user
|
|
208
|
+
/// @return Current attestation id
|
|
209
|
+
function currentAttestationId(address user) external view returns (uint256);
|
|
210
|
+
|
|
211
|
+
/// @notice Get the latest attestation id ever issued for a user
|
|
212
|
+
/// @param user Address of the user
|
|
213
|
+
/// @return Latest attestation id
|
|
214
|
+
function latestAttestationId(address user) external view returns (uint256);
|
|
215
|
+
|
|
216
|
+
/// @notice Get attestation metadata for a user and attestation id
|
|
217
|
+
/// @param user Address of the user
|
|
218
|
+
/// @param attestationId Attestation identifier to query
|
|
219
|
+
/// @return timestamp Unix timestamp of attestation
|
|
220
|
+
/// @return revokedAt Unix timestamp of revocation (0 if not revoked)
|
|
221
|
+
/// @return registrar Registrar who performed the attestation
|
|
222
|
+
function getAttestationMetadata(
|
|
223
|
+
address user,
|
|
224
|
+
uint256 attestationId
|
|
225
|
+
) external view returns (uint256 timestamp, uint256 revokedAt, address registrar);
|
|
226
|
+
}
|
|
File without changes
|