@zentity/fhevm-contracts 0.3.0 → 0.4.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.
Files changed (117) hide show
  1. package/abi/ComplianceRules.json +25 -16
  2. package/abi/CompliantERC20.json +30 -16
  3. package/abi/IdentityRegistry.json +496 -119
  4. package/contracts/compliance/ComplianceRules.sol +40 -189
  5. package/contracts/core/IdentityRegistry.sol +280 -232
  6. package/contracts/interfaces/IComplianceRules.sol +30 -0
  7. package/contracts/interfaces/IIdentityRegistry.sol +133 -159
  8. package/contracts/proxy/ERC1967Proxy.sol +6 -0
  9. package/contracts/test/MockFacilitator.sol +40 -0
  10. package/contracts/tokens/CompliantERC20.sol +28 -242
  11. package/deployments/hardhat/addresses.json +3 -8
  12. package/dist/index.d.ts +175 -9
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +44 -1
  15. package/package.json +1 -1
  16. package/typechain-types/@openzeppelin/contracts/access/Ownable.ts +153 -0
  17. package/typechain-types/@openzeppelin/contracts/access/Ownable2Step.ts +217 -0
  18. package/typechain-types/@openzeppelin/contracts/access/index.ts +5 -0
  19. package/typechain-types/@openzeppelin/contracts/index.ts +11 -0
  20. package/typechain-types/@openzeppelin/contracts/interfaces/IERC1967.ts +168 -0
  21. package/typechain-types/@openzeppelin/contracts/interfaces/IERC5267.ts +151 -0
  22. package/typechain-types/{contracts/tokens/CompliantERC20.sol/IComplianceChecker.ts → @openzeppelin/contracts/interfaces/draft-IERC1822.sol/IERC1822Proxiable.ts} +12 -17
  23. package/typechain-types/@openzeppelin/contracts/interfaces/draft-IERC1822.sol/index.ts +4 -0
  24. package/typechain-types/@openzeppelin/contracts/interfaces/index.ts +7 -0
  25. package/typechain-types/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.ts +105 -0
  26. package/typechain-types/@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.ts +69 -0
  27. package/typechain-types/@openzeppelin/contracts/proxy/ERC1967/index.ts +5 -0
  28. package/typechain-types/@openzeppelin/contracts/proxy/Proxy.ts +69 -0
  29. package/typechain-types/@openzeppelin/contracts/proxy/beacon/IBeacon.ts +90 -0
  30. package/typechain-types/@openzeppelin/contracts/proxy/beacon/index.ts +4 -0
  31. package/typechain-types/@openzeppelin/contracts/proxy/index.ts +8 -0
  32. package/typechain-types/@openzeppelin/contracts/utils/Address.ts +69 -0
  33. package/typechain-types/@openzeppelin/contracts/utils/Errors.ts +69 -0
  34. package/typechain-types/@openzeppelin/contracts/utils/Strings.ts +69 -0
  35. package/typechain-types/@openzeppelin/contracts/utils/cryptography/ECDSA.ts +69 -0
  36. package/typechain-types/@openzeppelin/contracts/utils/cryptography/index.ts +4 -0
  37. package/typechain-types/@openzeppelin/contracts/utils/index.ts +10 -0
  38. package/typechain-types/@openzeppelin/contracts/utils/math/SafeCast.ts +69 -0
  39. package/typechain-types/@openzeppelin/contracts/utils/math/index.ts +4 -0
  40. package/typechain-types/@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.ts +251 -0
  41. package/typechain-types/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.ts +186 -0
  42. package/typechain-types/@openzeppelin/contracts-upgradeable/access/index.ts +5 -0
  43. package/typechain-types/@openzeppelin/contracts-upgradeable/index.ts +9 -0
  44. package/typechain-types/@openzeppelin/contracts-upgradeable/proxy/index.ts +5 -0
  45. package/typechain-types/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.ts +105 -0
  46. package/typechain-types/@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.ts +196 -0
  47. package/typechain-types/@openzeppelin/contracts-upgradeable/proxy/utils/index.ts +5 -0
  48. package/typechain-types/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.ts +105 -0
  49. package/typechain-types/@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.ts +184 -0
  50. package/typechain-types/@openzeppelin/contracts-upgradeable/utils/cryptography/index.ts +4 -0
  51. package/typechain-types/@openzeppelin/contracts-upgradeable/utils/index.ts +6 -0
  52. package/typechain-types/@openzeppelin/index.ts +7 -0
  53. package/typechain-types/contracts/compliance/ComplianceRules.ts +18 -7
  54. package/typechain-types/contracts/core/IdentityRegistry.ts +493 -233
  55. package/typechain-types/contracts/index.ts +2 -0
  56. package/typechain-types/contracts/interfaces/IComplianceRules.ts +290 -0
  57. package/typechain-types/contracts/interfaces/IIdentityRegistry.ts +269 -341
  58. package/typechain-types/contracts/interfaces/index.ts +1 -0
  59. package/typechain-types/contracts/test/MockFacilitator.ts +187 -0
  60. package/typechain-types/contracts/test/index.ts +4 -0
  61. package/typechain-types/contracts/tokens/{CompliantERC20.sol/CompliantERC20.ts → CompliantERC20.ts} +19 -8
  62. package/typechain-types/contracts/tokens/index.ts +1 -2
  63. package/typechain-types/factories/@openzeppelin/contracts/access/Ownable2Step__factory.ts +138 -0
  64. package/typechain-types/factories/@openzeppelin/contracts/access/Ownable__factory.ts +96 -0
  65. package/typechain-types/factories/@openzeppelin/contracts/access/index.ts +5 -0
  66. package/typechain-types/factories/@openzeppelin/contracts/index.ts +7 -0
  67. package/typechain-types/factories/@openzeppelin/contracts/interfaces/IERC1967__factory.ts +67 -0
  68. package/typechain-types/factories/@openzeppelin/contracts/interfaces/IERC5267__factory.ts +71 -0
  69. package/typechain-types/factories/@openzeppelin/contracts/interfaces/draft-IERC1822.sol/IERC1822Proxiable__factory.ts +38 -0
  70. package/typechain-types/factories/@openzeppelin/contracts/interfaces/draft-IERC1822.sol/index.ts +4 -0
  71. package/typechain-types/factories/@openzeppelin/contracts/interfaces/index.ts +6 -0
  72. package/typechain-types/factories/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy__factory.ts +144 -0
  73. package/typechain-types/factories/@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils__factory.ts +105 -0
  74. package/typechain-types/factories/@openzeppelin/contracts/proxy/ERC1967/index.ts +5 -0
  75. package/typechain-types/factories/@openzeppelin/contracts/proxy/Proxy__factory.ts +26 -0
  76. package/typechain-types/factories/@openzeppelin/contracts/proxy/beacon/IBeacon__factory.ts +35 -0
  77. package/typechain-types/factories/@openzeppelin/contracts/proxy/beacon/index.ts +4 -0
  78. package/typechain-types/factories/@openzeppelin/contracts/proxy/index.ts +6 -0
  79. package/typechain-types/factories/@openzeppelin/contracts/utils/Address__factory.ts +75 -0
  80. package/typechain-types/factories/@openzeppelin/contracts/utils/Errors__factory.ts +101 -0
  81. package/typechain-types/factories/@openzeppelin/contracts/utils/Strings__factory.ts +90 -0
  82. package/typechain-types/factories/@openzeppelin/contracts/utils/cryptography/ECDSA__factory.ts +91 -0
  83. package/typechain-types/factories/@openzeppelin/contracts/utils/cryptography/index.ts +4 -0
  84. package/typechain-types/factories/@openzeppelin/contracts/utils/index.ts +8 -0
  85. package/typechain-types/factories/@openzeppelin/contracts/utils/math/SafeCast__factory.ts +118 -0
  86. package/typechain-types/factories/@openzeppelin/contracts/utils/math/index.ts +4 -0
  87. package/typechain-types/factories/@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable__factory.ts +165 -0
  88. package/typechain-types/factories/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable__factory.ts +122 -0
  89. package/typechain-types/factories/@openzeppelin/contracts-upgradeable/access/index.ts +5 -0
  90. package/typechain-types/factories/@openzeppelin/contracts-upgradeable/index.ts +6 -0
  91. package/typechain-types/factories/@openzeppelin/contracts-upgradeable/proxy/index.ts +4 -0
  92. package/typechain-types/factories/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable__factory.ts +48 -0
  93. package/typechain-types/factories/@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable__factory.ts +153 -0
  94. package/typechain-types/factories/@openzeppelin/contracts-upgradeable/proxy/utils/index.ts +5 -0
  95. package/typechain-types/factories/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable__factory.ts +48 -0
  96. package/typechain-types/factories/@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable__factory.ts +97 -0
  97. package/typechain-types/factories/@openzeppelin/contracts-upgradeable/utils/cryptography/index.ts +4 -0
  98. package/typechain-types/factories/@openzeppelin/contracts-upgradeable/utils/index.ts +5 -0
  99. package/typechain-types/factories/@openzeppelin/index.ts +5 -0
  100. package/typechain-types/factories/contracts/compliance/ComplianceRules__factory.ts +26 -17
  101. package/typechain-types/factories/contracts/core/IdentityRegistry__factory.ts +493 -116
  102. package/typechain-types/factories/contracts/index.ts +1 -0
  103. package/typechain-types/factories/contracts/interfaces/IComplianceRules__factory.ts +193 -0
  104. package/typechain-types/factories/contracts/interfaces/IIdentityRegistry__factory.ts +236 -186
  105. package/typechain-types/factories/contracts/interfaces/index.ts +1 -0
  106. package/typechain-types/factories/contracts/test/MockFacilitator__factory.ts +194 -0
  107. package/typechain-types/factories/contracts/test/index.ts +4 -0
  108. package/typechain-types/factories/contracts/tokens/CompliantERC20__factory.ts +595 -0
  109. package/typechain-types/factories/contracts/tokens/index.ts +1 -1
  110. package/typechain-types/factories/index.ts +1 -0
  111. package/typechain-types/hardhat.d.ts +394 -16
  112. package/typechain-types/index.ts +48 -4
  113. package/deployments/hardhat/.chainId +0 -1
  114. package/typechain-types/contracts/tokens/CompliantERC20.sol/index.ts +0 -5
  115. package/typechain-types/factories/contracts/tokens/CompliantERC20.sol/CompliantERC20__factory.ts +0 -581
  116. package/typechain-types/factories/contracts/tokens/CompliantERC20.sol/IComplianceChecker__factory.ts +0 -44
  117. package/typechain-types/factories/contracts/tokens/CompliantERC20.sol/index.ts +0 -5
@@ -3,229 +3,298 @@
3
3
  pragma solidity ^0.8.27;
4
4
 
5
5
  import {FHE, euint8, euint16, ebool, externalEuint8, externalEuint16, externalEbool} from "@fhevm/solidity/lib/FHE.sol";
6
- import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
6
+ import {ZamaConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
7
+ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
8
+ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
9
+ import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
10
+ import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
11
+ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
7
12
  import {IIdentityRegistry} from "../interfaces/IIdentityRegistry.sol";
8
13
 
9
- /**
10
- * @title IdentityRegistry
11
- * @author Gustavo Valverde
12
- * @notice On-chain encrypted identity registry for 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 {
14
+ /// @title IdentityRegistry
15
+ /// @author Gustavo Valverde
16
+ /// @notice On-chain encrypted identity registry with EIP-712 registrar permits,
17
+ /// per-attribute selective grants, and x402 compliance oracle surface.
18
+ /// @dev UUPS-upgradeable. User submits attestation tx with registrar-signed permit.
19
+ /// Compliance checks are composable by x402 facilitators and DeFi contracts
20
+ /// via the branch-free FHE.select() pattern.
21
+ contract IdentityRegistry is
22
+ IIdentityRegistry,
23
+ Initializable,
24
+ UUPSUpgradeable,
25
+ Ownable2StepUpgradeable,
26
+ EIP712Upgradeable
27
+ {
28
+ // ============ Constants ============
29
+
30
+ uint8 public constant ATTR_BIRTH_YEAR = 0x01;
31
+ uint8 public constant ATTR_COUNTRY = 0x02;
32
+ uint8 public constant ATTR_COMPLIANCE = 0x04;
33
+ uint8 public constant ATTR_BLACKLIST = 0x08;
34
+ uint8 public constant ATTR_ALL = 0x0F;
35
+
36
+ /// @dev EIP-712 typehash for the attestation permit
37
+ bytes32 public constant ATTEST_PERMIT_TYPEHASH = keccak256(
38
+ "AttestPermit(address user,uint8 birthYearOffset,uint16 countryCode,uint8 complianceLevel,bool isBlacklisted,bytes32 proofSetHash,uint32 policyVersion,uint256 nonce,uint256 deadline)"
39
+ );
40
+
30
41
  // ============ Encrypted Identity Attributes ============
31
42
 
32
- /// @notice Encrypted birth year offset from 1900
33
- mapping(address user => euint8 birthYearOffset) private birthYearOffsets;
43
+ mapping(address user => euint8 offset) private birthYearOffsets;
44
+ mapping(address user => euint16 code) private countryCodes;
45
+ mapping(address user => euint8 level) private complianceLevels;
46
+ mapping(address user => ebool status) private blacklistStatuses;
34
47
 
35
- /// @notice Encrypted country code (ISO 3166-1 numeric)
36
- mapping(address user => euint16 countryCode) private countryCodes;
48
+ // ============ Attestation Metadata ============
37
49
 
38
- /// @notice Encrypted compliance verification level (0-5)
39
- mapping(address user => euint8 complianceLevel) private complianceLevels;
50
+ mapping(address user => uint256 id) public currentAttestationId;
51
+ mapping(address user => uint256 ts) public attestationTimestamp;
52
+ mapping(address user => bytes32 hash) public proofSetHashes;
53
+ mapping(address user => uint32 version) public policyVersions;
54
+ uint256 public latestAttestationId;
40
55
 
41
- /// @notice Encrypted blacklist status
42
- mapping(address user => ebool blacklisted) private isBlacklisted;
56
+ // ============ Access Control ============
43
57
 
44
- /// @notice Timestamp of last attestation
45
- mapping(address user => uint256 timestamp) public attestationTimestamp;
58
+ mapping(address registrar => bool authorized) public registrars;
59
+ mapping(address user => uint256 nonce) public nonces;
46
60
 
47
- /// @notice Current attestation id for a user (0 if not attested)
48
- mapping(address user => uint256 attestationId) public currentAttestationId;
61
+ // ============ Per-Attribute Grants ============
49
62
 
50
- /// @notice Latest attestation id ever issued for a user
51
- mapping(address user => uint256 attestationId) public latestAttestationId;
63
+ /// @dev keccak256(user, grantee) => attribute bitmask
64
+ mapping(bytes32 grantKey => uint8 mask) private attributeGrants;
52
65
 
53
- /// @notice Attestation metadata for auditability
54
- struct AttestationMetadata {
55
- uint256 timestamp;
56
- uint256 revokedAt;
57
- address registrar;
58
- }
66
+ // ============ Verification Results ============
59
67
 
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
68
  mapping(bytes32 key => ebool result) private verificationResults;
65
69
 
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;
70
+ // ============ Upgrade Safety ============
72
71
 
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();
72
+ /// @dev Reserved storage gap for future upgrades
73
+ uint256[50] private __gap;
78
74
 
79
75
  // ============ Modifiers ============
80
76
 
81
- modifier onlyOwner() {
82
- if (msg.sender != owner) revert OnlyOwner();
77
+ modifier onlyRegistrar() {
78
+ if (!registrars[msg.sender]) revert OnlyRegistrar();
83
79
  _;
84
80
  }
85
81
 
86
- modifier onlyRegistrar() {
87
- if (!registrars[msg.sender]) revert OnlyRegistrar();
82
+ modifier whenAttested(address user) {
83
+ if (currentAttestationId[user] == 0) revert NotAttested();
88
84
  _;
89
85
  }
90
86
 
91
- // ============ Constructor ============
87
+ // ============ Initializer ============
92
88
 
93
- /// @notice Initializes the registry with the deployer as owner and initial registrar
89
+ /// @custom:oz-upgrades-unsafe-allow constructor
94
90
  constructor() {
95
- owner = msg.sender;
96
- registrars[msg.sender] = true;
97
- emit RegistrarAdded(msg.sender);
91
+ _disableInitializers();
98
92
  }
99
93
 
100
- // ============ Registrar Management ============
94
+ /// @notice Initialize the registry (called once via proxy)
95
+ /// @param initialOwner Address that becomes owner and first registrar
96
+ function initialize(address initialOwner) external initializer {
97
+ if (initialOwner == address(0)) revert ZeroAddress();
101
98
 
102
- /// @inheritdoc IIdentityRegistry
103
- function addRegistrar(address registrar) external onlyOwner {
104
- registrars[registrar] = true;
105
- emit RegistrarAdded(registrar);
99
+ __UUPSUpgradeable_init();
100
+ __Ownable2Step_init();
101
+ __Ownable_init(initialOwner);
102
+ __EIP712_init("ZentityIdentityRegistry", "2");
103
+
104
+ // Set up FHEVM coprocessor (replaces ZamaEthereumConfig constructor)
105
+ FHE.setCoprocessor(ZamaConfig.getEthereumCoprocessorConfig());
106
+
107
+ registrars[initialOwner] = true;
108
+ emit RegistrarUpdated(initialOwner, true);
106
109
  }
107
110
 
111
+ // ============ Attestation with EIP-712 Permit ============
112
+
108
113
  /// @inheritdoc IIdentityRegistry
109
- function removeRegistrar(address registrar) external onlyOwner {
110
- registrars[registrar] = false;
111
- emit RegistrarRemoved(registrar);
114
+ function attestWithPermit(
115
+ AttestPermitData calldata permit,
116
+ externalEuint8 encBirthYearOffset,
117
+ externalEuint16 encCountryCode,
118
+ externalEuint8 encComplianceLevel,
119
+ externalEbool encIsBlacklisted,
120
+ bytes calldata inputProof
121
+ ) external {
122
+ if (currentAttestationId[msg.sender] != 0) revert AlreadyAttested();
123
+ if (block.timestamp > permit.deadline) revert PermitExpired();
124
+
125
+ // Verify registrar signature over plaintext values
126
+ _verifyPermit(permit);
127
+
128
+ // Convert and store FHE-encrypted values
129
+ _storeEncryptedValues(encBirthYearOffset, encCountryCode, encComplianceLevel, encIsBlacklisted, inputProof);
130
+
131
+ // Store attestation metadata
132
+ latestAttestationId++;
133
+ currentAttestationId[msg.sender] = latestAttestationId;
134
+ attestationTimestamp[msg.sender] = block.timestamp;
135
+ proofSetHashes[msg.sender] = permit.proofSetHash;
136
+ policyVersions[msg.sender] = permit.policyVersion;
137
+
138
+ emit IdentityAttested(msg.sender);
112
139
  }
113
140
 
114
- // ============ Identity Attestation ============
141
+ /// @dev Verify the registrar's EIP-712 signature and increment nonce
142
+ function _verifyPermit(AttestPermitData calldata permit) internal {
143
+ uint256 currentNonce = nonces[msg.sender];
144
+ bytes32 structHash = keccak256(
145
+ abi.encode(
146
+ ATTEST_PERMIT_TYPEHASH,
147
+ msg.sender,
148
+ permit.birthYearOffset,
149
+ permit.countryCode,
150
+ permit.complianceLevel,
151
+ permit.isBlacklisted,
152
+ permit.proofSetHash,
153
+ permit.policyVersion,
154
+ currentNonce,
155
+ permit.deadline
156
+ )
157
+ );
158
+
159
+ address signer = ECDSA.recover(_hashTypedDataV4(structHash), permit.v, permit.r, permit.s);
160
+ if (!registrars[signer]) revert InvalidPermit();
161
+
162
+ nonces[msg.sender] = currentNonce + 1;
163
+ }
115
164
 
116
- /// @inheritdoc IIdentityRegistry
117
- function attestIdentity(
118
- address user,
165
+ /// @dev Convert external encrypted inputs, store, and grant ACL permissions
166
+ function _storeEncryptedValues(
119
167
  externalEuint8 encBirthYearOffset,
120
168
  externalEuint16 encCountryCode,
121
169
  externalEuint8 encComplianceLevel,
122
170
  externalEbool encIsBlacklisted,
123
171
  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 compliance = FHE.fromExternal(encComplianceLevel, inputProof);
131
- ebool blacklisted = FHE.fromExternal(encIsBlacklisted, inputProof);
132
-
133
- birthYearOffsets[user] = birthYear;
134
- countryCodes[user] = country;
135
- complianceLevels[user] = compliance;
136
- isBlacklisted[user] = blacklisted;
137
-
138
- // Grant contract permission to all values
139
- FHE.allowThis(birthYear);
140
- FHE.allowThis(country);
141
- FHE.allowThis(compliance);
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(compliance, 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);
172
+ ) internal {
173
+ euint8 encByo = FHE.fromExternal(encBirthYearOffset, inputProof);
174
+ euint16 encCc = FHE.fromExternal(encCountryCode, inputProof);
175
+ euint8 encCl = FHE.fromExternal(encComplianceLevel, inputProof);
176
+ ebool encBl = FHE.fromExternal(encIsBlacklisted, inputProof);
177
+
178
+ birthYearOffsets[msg.sender] = encByo;
179
+ countryCodes[msg.sender] = encCc;
180
+ complianceLevels[msg.sender] = encCl;
181
+ blacklistStatuses[msg.sender] = encBl;
182
+
183
+ FHE.allowThis(encByo);
184
+ FHE.allowThis(encCc);
185
+ FHE.allowThis(encCl);
186
+ FHE.allowThis(encBl);
187
+ FHE.allow(encByo, msg.sender);
188
+ FHE.allow(encCc, msg.sender);
189
+ FHE.allow(encCl, msg.sender);
190
+ FHE.allow(encBl, msg.sender);
162
191
  }
163
192
 
193
+ // ============ Bidirectional Revocation ============
194
+
164
195
  /// @inheritdoc IIdentityRegistry
165
- function revokeIdentity(address user) external onlyRegistrar {
166
- uint256 attestationId = currentAttestationId[user];
167
- if (attestationId == 0) revert NotAttested();
196
+ function revokeIdentity() external {
197
+ _revokeIdentity(msg.sender);
198
+ }
199
+
200
+ /// @inheritdoc IIdentityRegistry
201
+ function revokeIdentityFor(address user) external onlyRegistrar {
202
+ _revokeIdentity(user);
203
+ }
204
+
205
+ function _revokeIdentity(address user) internal {
206
+ if (currentAttestationId[user] == 0) revert NotAttested();
168
207
 
169
- // Set encrypted values to encrypted zeros
208
+ // Zero out encrypted values (new ciphertext handles)
170
209
  birthYearOffsets[user] = FHE.asEuint8(0);
171
210
  countryCodes[user] = FHE.asEuint16(0);
172
211
  complianceLevels[user] = FHE.asEuint8(0);
173
- isBlacklisted[user] = FHE.asEbool(false);
174
- attestationTimestamp[user] = 0;
175
- currentAttestationId[user] = 0;
212
+ blacklistStatuses[user] = FHE.asEbool(false);
176
213
 
177
- AttestationMetadata storage metadata = attestationHistory[user][attestationId];
178
- if (metadata.revokedAt == 0) {
179
- metadata.revokedAt = block.timestamp;
180
- }
214
+ // Clear metadata
215
+ currentAttestationId[user] = 0;
216
+ attestationTimestamp[user] = 0;
217
+ proofSetHashes[user] = bytes32(0);
218
+ policyVersions[user] = 0;
181
219
 
182
220
  emit IdentityRevoked(user);
183
- emit IdentityRevokedDetailed(user, msg.sender, attestationId, block.timestamp);
184
221
  }
185
222
 
186
- // ============ Encrypted Queries ============
223
+ // ============ Per-Attribute Access Grants ============
187
224
 
188
225
  /// @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
- }
226
+ function grantAttributeAccess(
227
+ address grantee,
228
+ uint8 attributeMask,
229
+ Purpose purpose
230
+ ) external whenAttested(msg.sender) {
231
+ if (grantee == address(0)) revert ZeroAddress();
232
+
233
+ bytes32 grantKey = keccak256(abi.encodePacked(msg.sender, grantee));
234
+ attributeGrants[grantKey] |= attributeMask;
235
+
236
+ // Grant FHE ACL per selected attribute
237
+ if (attributeMask & ATTR_BIRTH_YEAR != 0) {
238
+ FHE.allow(birthYearOffsets[msg.sender], grantee);
239
+ }
240
+ if (attributeMask & ATTR_COUNTRY != 0) {
241
+ FHE.allow(countryCodes[msg.sender], grantee);
242
+ }
243
+ if (attributeMask & ATTR_COMPLIANCE != 0) {
244
+ FHE.allow(complianceLevels[msg.sender], grantee);
245
+ }
246
+ if (attributeMask & ATTR_BLACKLIST != 0) {
247
+ FHE.allow(blacklistStatuses[msg.sender], grantee);
248
+ }
194
249
 
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];
250
+ emit AttributeAccessGranted(msg.sender, grantee, attributeMask, purpose);
200
251
  }
201
252
 
202
253
  /// @inheritdoc IIdentityRegistry
203
- function getComplianceLevel(address user) external view returns (euint8) {
204
- if (attestationTimestamp[user] == 0) revert NotAttested();
205
- if (!FHE.isSenderAllowed(complianceLevels[user])) revert AccessProhibited();
206
- return complianceLevels[user];
254
+ function grantAccessTo(address grantee) external whenAttested(msg.sender) {
255
+ if (grantee == address(0)) revert ZeroAddress();
256
+
257
+ bytes32 grantKey = keccak256(abi.encodePacked(msg.sender, grantee));
258
+ attributeGrants[grantKey] = ATTR_ALL;
259
+
260
+ FHE.allow(birthYearOffsets[msg.sender], grantee);
261
+ FHE.allow(countryCodes[msg.sender], grantee);
262
+ FHE.allow(complianceLevels[msg.sender], grantee);
263
+ FHE.allow(blacklistStatuses[msg.sender], grantee);
264
+
265
+ emit AttributeAccessGranted(msg.sender, grantee, ATTR_ALL, Purpose.COMPLIANCE_CHECK);
207
266
  }
208
267
 
268
+ // ============ Compliance Checks (x402 Oracle Surface) ============
269
+
209
270
  /// @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
- }
271
+ function checkCompliance(
272
+ address user,
273
+ uint8 minLevel
274
+ ) external whenAttested(user) returns (ebool) {
275
+ euint8 encMinLevel = FHE.asEuint8(minLevel);
276
+ ebool meetsLevel = FHE.ge(complianceLevels[user], encMinLevel);
277
+ ebool notBlocked = FHE.not(blacklistStatuses[user]);
278
+ ebool result = FHE.and(meetsLevel, notBlocked);
279
+
280
+ // Store result and grant permissions
281
+ bytes32 key = keccak256(abi.encodePacked(user, "compliance", minLevel));
282
+ verificationResults[key] = result;
283
+ FHE.allowThis(result);
284
+ FHE.allow(result, msg.sender);
215
285
 
216
- // ============ Verification Helpers ============
286
+ return result;
287
+ }
217
288
 
218
289
  /// @inheritdoc IIdentityRegistry
219
- function hasMinComplianceLevel(address user, uint8 minLevel) external returns (ebool) {
220
- if (attestationTimestamp[user] == 0) revert NotAttested();
221
- if (!FHE.isSenderAllowed(complianceLevels[user])) revert AccessProhibited();
290
+ function hasMinComplianceLevel(
291
+ address user,
292
+ uint8 minLevel
293
+ ) external whenAttested(user) returns (ebool) {
222
294
  ebool result = FHE.ge(complianceLevels[user], FHE.asEuint8(minLevel));
223
295
 
224
- // Store result for later retrieval
225
- bytes32 key = keccak256(abi.encodePacked(user, uint8(0), uint256(minLevel)));
296
+ bytes32 key = keccak256(abi.encodePacked(user, "minLevel", minLevel));
226
297
  verificationResults[key] = result;
227
-
228
- // Grant caller permission to decrypt the result
229
298
  FHE.allowThis(result);
230
299
  FHE.allow(result, msg.sender);
231
300
 
@@ -233,16 +302,14 @@ contract IdentityRegistry is IIdentityRegistry, ZamaEthereumConfig {
233
302
  }
234
303
 
235
304
  /// @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();
305
+ function isFromCountry(
306
+ address user,
307
+ uint16 country
308
+ ) external whenAttested(user) returns (ebool) {
239
309
  ebool result = FHE.eq(countryCodes[user], FHE.asEuint16(country));
240
310
 
241
- // Store result for later retrieval
242
- bytes32 key = keccak256(abi.encodePacked(user, uint8(1), uint256(country)));
311
+ bytes32 key = keccak256(abi.encodePacked(user, "country", country));
243
312
  verificationResults[key] = result;
244
-
245
- // Grant caller permission to decrypt the result
246
313
  FHE.allowThis(result);
247
314
  FHE.allow(result, msg.sender);
248
315
 
@@ -250,103 +317,84 @@ contract IdentityRegistry is IIdentityRegistry, ZamaEthereumConfig {
250
317
  }
251
318
 
252
319
  /// @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]);
320
+ function isNotBlacklisted(address user) external whenAttested(user) returns (ebool) {
321
+ ebool result = FHE.not(blacklistStatuses[user]);
257
322
 
258
- // Store result for later retrieval
259
- bytes32 key = keccak256(abi.encodePacked(user, uint8(2), uint256(0)));
323
+ bytes32 key = keccak256(abi.encodePacked(user, "notBlacklisted"));
260
324
  verificationResults[key] = result;
261
-
262
- // Grant caller permission to decrypt the result
263
325
  FHE.allowThis(result);
264
326
  FHE.allow(result, msg.sender);
265
327
 
266
328
  return result;
267
329
  }
268
330
 
269
- // ============ Result Getters ============
331
+ // ============ Encrypted Attribute Getters ============
270
332
 
271
- /**
272
- * @notice Get the last compliance 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 getComplianceLevelResult(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;
333
+ /// @inheritdoc IIdentityRegistry
334
+ function getBirthYearOffset(address user) external view whenAttested(user) returns (euint8) {
335
+ if (!FHE.isSenderAllowed(birthYearOffsets[user])) revert AccessProhibited();
336
+ return birthYearOffsets[user];
282
337
  }
283
338
 
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;
339
+ /// @inheritdoc IIdentityRegistry
340
+ function getCountryCode(address user) external view whenAttested(user) returns (euint16) {
341
+ if (!FHE.isSenderAllowed(countryCodes[user])) revert AccessProhibited();
342
+ return countryCodes[user];
295
343
  }
296
344
 
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;
345
+ /// @inheritdoc IIdentityRegistry
346
+ function getComplianceLevel(address user) external view whenAttested(user) returns (euint8) {
347
+ if (!FHE.isSenderAllowed(complianceLevels[user])) revert AccessProhibited();
348
+ return complianceLevels[user];
307
349
  }
308
350
 
309
- // ============ Access Control ============
310
-
311
351
  /// @inheritdoc IIdentityRegistry
312
- function transferOwnership(address newOwner) external onlyOwner {
313
- if (newOwner == address(0)) revert InvalidOwner();
314
- pendingOwner = newOwner;
315
- emit OwnershipTransferStarted(owner, newOwner);
352
+ function getBlacklistStatus(address user) external view whenAttested(user) returns (ebool) {
353
+ if (!FHE.isSenderAllowed(blacklistStatuses[user])) revert AccessProhibited();
354
+ return blacklistStatuses[user];
316
355
  }
317
356
 
357
+ // ============ Metadata Getters ============
358
+
318
359
  /// @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);
360
+ function isAttested(address user) external view returns (bool) {
361
+ return currentAttestationId[user] != 0;
325
362
  }
326
363
 
327
364
  /// @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(complianceLevels[msg.sender], grantee);
334
- FHE.allow(isBlacklisted[msg.sender], grantee);
365
+ function getProofSetHash(address user) external view returns (bytes32) {
366
+ return proofSetHashes[user];
367
+ }
335
368
 
336
- emit AccessGranted(msg.sender, grantee);
369
+ /// @inheritdoc IIdentityRegistry
370
+ function getPolicyVersion(address user) external view returns (uint32) {
371
+ return policyVersions[user];
337
372
  }
338
373
 
339
374
  /// @inheritdoc IIdentityRegistry
340
- function isAttested(address user) external view returns (bool) {
341
- return currentAttestationId[user] > 0;
375
+ function getGrantedAttributes(address user, address grantee) external view returns (uint8) {
376
+ return attributeGrants[keccak256(abi.encodePacked(user, grantee))];
342
377
  }
343
378
 
344
379
  /// @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);
380
+ function getVerificationResult(bytes32 key) external view returns (ebool) {
381
+ ebool result = verificationResults[key];
382
+ if (!FHE.isSenderAllowed(result)) revert AccessProhibited();
383
+ return result;
384
+ }
385
+
386
+ // ============ Admin ============
387
+
388
+ /// @notice Add or remove a registrar
389
+ /// @param registrar Address to update
390
+ /// @param status true to add, false to remove
391
+ function setRegistrar(address registrar, bool status) external onlyOwner {
392
+ if (registrar == address(0)) revert ZeroAddress();
393
+ registrars[registrar] = status;
394
+ emit RegistrarUpdated(registrar, status);
351
395
  }
396
+
397
+ // ============ UUPS ============
398
+
399
+ function _authorizeUpgrade(address) internal override onlyOwner {}
352
400
  }