create-fhevm-example 1.3.1 → 1.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 (122) hide show
  1. package/contracts/advanced/BlindAuction.sol +255 -0
  2. package/contracts/advanced/EncryptedEscrow.sol +315 -0
  3. package/contracts/advanced/HiddenVoting.sol +231 -0
  4. package/contracts/advanced/PrivateKYC.sol +309 -0
  5. package/contracts/advanced/PrivatePayroll.sol +285 -0
  6. package/contracts/basic/decryption/PublicDecryptMultipleValues.sol +160 -0
  7. package/contracts/basic/decryption/PublicDecryptSingleValue.sol +142 -0
  8. package/contracts/basic/decryption/UserDecryptMultipleValues.sol +61 -0
  9. package/contracts/basic/decryption/UserDecryptSingleValue.sol +59 -0
  10. package/contracts/basic/encryption/EncryptMultipleValues.sol +72 -0
  11. package/contracts/basic/encryption/EncryptSingleValue.sol +44 -0
  12. package/contracts/basic/encryption/FHECounter.sol +54 -0
  13. package/contracts/basic/fhe-operations/FHEAdd.sol +51 -0
  14. package/contracts/basic/fhe-operations/FHEArithmetic.sol +99 -0
  15. package/contracts/basic/fhe-operations/FHEComparison.sol +116 -0
  16. package/contracts/basic/fhe-operations/FHEIfThenElse.sol +53 -0
  17. package/contracts/concepts/FHEAccessControl.sol +94 -0
  18. package/contracts/concepts/FHEAntiPatterns.sol +329 -0
  19. package/contracts/concepts/FHEHandles.sol +128 -0
  20. package/contracts/concepts/FHEInputProof.sol +104 -0
  21. package/contracts/gaming/EncryptedLottery.sol +298 -0
  22. package/contracts/gaming/EncryptedPoker.sol +337 -0
  23. package/contracts/gaming/RockPaperScissors.sol +213 -0
  24. package/contracts/openzeppelin/ERC7984.sol +85 -0
  25. package/contracts/openzeppelin/ERC7984ERC20Wrapper.sol +43 -0
  26. package/contracts/openzeppelin/SwapERC7984ToERC20.sol +110 -0
  27. package/contracts/openzeppelin/SwapERC7984ToERC7984.sol +48 -0
  28. package/contracts/openzeppelin/VestingWallet.sol +147 -0
  29. package/contracts/openzeppelin/mocks/ERC20Mock.sol +31 -0
  30. package/dist/scripts/commands/add-mode.d.ts.map +1 -0
  31. package/dist/scripts/{add-mode.js → commands/add-mode.js} +27 -61
  32. package/dist/scripts/commands/doctor.d.ts.map +1 -0
  33. package/dist/scripts/{doctor.js → commands/doctor.js} +2 -2
  34. package/dist/scripts/commands/generate-config.d.ts.map +1 -0
  35. package/dist/scripts/{generate-config.js → commands/generate-config.js} +3 -10
  36. package/dist/scripts/commands/generate-docs.d.ts.map +1 -0
  37. package/dist/scripts/{generate-docs.js → commands/generate-docs.js} +4 -3
  38. package/dist/scripts/commands/maintenance.d.ts.map +1 -0
  39. package/dist/scripts/{maintenance.js → commands/maintenance.js} +11 -10
  40. package/dist/scripts/index.js +63 -59
  41. package/dist/scripts/{builders.d.ts → shared/builders.d.ts} +2 -2
  42. package/dist/scripts/shared/builders.d.ts.map +1 -0
  43. package/dist/scripts/{builders.js → shared/builders.js} +49 -30
  44. package/dist/scripts/{config.d.ts → shared/config.d.ts} +0 -2
  45. package/dist/scripts/shared/config.d.ts.map +1 -0
  46. package/dist/scripts/{config.js → shared/config.js} +48 -59
  47. package/dist/scripts/shared/generators.d.ts +42 -0
  48. package/dist/scripts/shared/generators.d.ts.map +1 -0
  49. package/dist/scripts/{utils.js → shared/generators.js} +34 -271
  50. package/dist/scripts/shared/ui.d.ts.map +1 -0
  51. package/dist/scripts/{ui.js → shared/ui.js} +3 -2
  52. package/dist/scripts/{utils.d.ts → shared/utils.d.ts} +4 -27
  53. package/dist/scripts/shared/utils.d.ts.map +1 -0
  54. package/dist/scripts/shared/utils.js +228 -0
  55. package/fhevm-hardhat-template/.eslintignore +26 -0
  56. package/fhevm-hardhat-template/.eslintrc.yml +21 -0
  57. package/fhevm-hardhat-template/.github/workflows/main.yml +47 -0
  58. package/fhevm-hardhat-template/.github/workflows/manual-windows.yml +28 -0
  59. package/fhevm-hardhat-template/.github/workflows/manual.yml +28 -0
  60. package/fhevm-hardhat-template/.prettierignore +25 -0
  61. package/fhevm-hardhat-template/.prettierrc.yml +15 -0
  62. package/fhevm-hardhat-template/.solcover.js +4 -0
  63. package/fhevm-hardhat-template/.solhint.json +12 -0
  64. package/fhevm-hardhat-template/.solhintignore +3 -0
  65. package/fhevm-hardhat-template/.vscode/extensions.json +3 -0
  66. package/fhevm-hardhat-template/.vscode/settings.json +9 -0
  67. package/fhevm-hardhat-template/LICENSE +33 -0
  68. package/fhevm-hardhat-template/README.md +110 -0
  69. package/fhevm-hardhat-template/contracts/FHECounter.sol +46 -0
  70. package/fhevm-hardhat-template/deploy/deploy.ts +17 -0
  71. package/fhevm-hardhat-template/hardhat.config.ts +90 -0
  72. package/fhevm-hardhat-template/package-lock.json +10405 -0
  73. package/fhevm-hardhat-template/package.json +104 -0
  74. package/fhevm-hardhat-template/tasks/FHECounter.ts +184 -0
  75. package/fhevm-hardhat-template/tasks/accounts.ts +9 -0
  76. package/fhevm-hardhat-template/test/FHECounter.ts +104 -0
  77. package/fhevm-hardhat-template/test/FHECounterSepolia.ts +104 -0
  78. package/fhevm-hardhat-template/tsconfig.json +23 -0
  79. package/package.json +13 -10
  80. package/test/advanced/BlindAuction.ts +246 -0
  81. package/test/advanced/EncryptedEscrow.ts +295 -0
  82. package/test/advanced/HiddenVoting.ts +268 -0
  83. package/test/advanced/PrivateKYC.ts +382 -0
  84. package/test/advanced/PrivatePayroll.ts +253 -0
  85. package/test/basic/decryption/PublicDecryptMultipleValues.ts +254 -0
  86. package/test/basic/decryption/PublicDecryptSingleValue.ts +264 -0
  87. package/test/basic/decryption/UserDecryptMultipleValues.ts +107 -0
  88. package/test/basic/decryption/UserDecryptSingleValue.ts +97 -0
  89. package/test/basic/encryption/EncryptMultipleValues.ts +110 -0
  90. package/test/basic/encryption/EncryptSingleValue.ts +124 -0
  91. package/test/basic/encryption/FHECounter.ts +112 -0
  92. package/test/basic/fhe-operations/FHEAdd.ts +97 -0
  93. package/test/basic/fhe-operations/FHEArithmetic.ts +161 -0
  94. package/test/basic/fhe-operations/FHEComparison.ts +167 -0
  95. package/test/basic/fhe-operations/FHEIfThenElse.ts +97 -0
  96. package/test/concepts/FHEAccessControl.ts +154 -0
  97. package/test/concepts/FHEAntiPatterns.ts +111 -0
  98. package/test/concepts/FHEHandles.ts +156 -0
  99. package/test/concepts/FHEInputProof.ts +151 -0
  100. package/test/gaming/EncryptedLottery.ts +214 -0
  101. package/test/gaming/EncryptedPoker.ts +349 -0
  102. package/test/gaming/RockPaperScissors.ts +205 -0
  103. package/test/openzeppelin/ERC7984.ts +142 -0
  104. package/test/openzeppelin/ERC7984ERC20Wrapper.ts +71 -0
  105. package/test/openzeppelin/SwapERC7984ToERC20.ts +76 -0
  106. package/test/openzeppelin/SwapERC7984ToERC7984.ts +113 -0
  107. package/test/openzeppelin/VestingWallet.ts +89 -0
  108. package/dist/scripts/add-mode.d.ts.map +0 -1
  109. package/dist/scripts/builders.d.ts.map +0 -1
  110. package/dist/scripts/config.d.ts.map +0 -1
  111. package/dist/scripts/doctor.d.ts.map +0 -1
  112. package/dist/scripts/generate-config.d.ts.map +0 -1
  113. package/dist/scripts/generate-docs.d.ts.map +0 -1
  114. package/dist/scripts/maintenance.d.ts.map +0 -1
  115. package/dist/scripts/ui.d.ts.map +0 -1
  116. package/dist/scripts/utils.d.ts.map +0 -1
  117. /package/dist/scripts/{add-mode.d.ts → commands/add-mode.d.ts} +0 -0
  118. /package/dist/scripts/{doctor.d.ts → commands/doctor.d.ts} +0 -0
  119. /package/dist/scripts/{generate-config.d.ts → commands/generate-config.d.ts} +0 -0
  120. /package/dist/scripts/{generate-docs.d.ts → commands/generate-docs.d.ts} +0 -0
  121. /package/dist/scripts/{maintenance.d.ts → commands/maintenance.d.ts} +0 -0
  122. /package/dist/scripts/{ui.d.ts → shared/ui.d.ts} +0 -0
@@ -0,0 +1,231 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {FHE, euint64, ebool, externalEuint8} from "@fhevm/solidity/lib/FHE.sol";
5
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
6
+
7
+ /**
8
+ * @notice Hidden Voting with encrypted ballots and homomorphic tallying
9
+ *
10
+ * @dev Demonstrates FHE voting patterns:
11
+ * - Encrypted vote submission (0 = No, 1 = Yes)
12
+ * - Homomorphic addition for vote tallying
13
+ * - FHE.makePubliclyDecryptable() for revealing final results
14
+ * - Privacy: individual votes remain encrypted forever
15
+ *
16
+ * Flow:
17
+ * 1. Owner creates a proposal with voting duration
18
+ * 2. Eligible voters submit encrypted votes (0 or 1)
19
+ * 3. Votes are homomorphically added to running totals
20
+ * 4. After voting ends, owner closes and results become decryptable
21
+ * 5. Anyone can reveal final Yes/No counts with valid proof
22
+ */
23
+ contract HiddenVoting is ZamaEthereumConfig {
24
+ // ==================== TYPES ====================
25
+
26
+ enum VotingState {
27
+ Active, // Accepting votes
28
+ Closed, // Voting ended, pending reveal
29
+ Revealed // Results revealed on-chain
30
+ }
31
+
32
+ // ==================== STATE ====================
33
+
34
+ /// Voting owner (deployer)
35
+ address public owner;
36
+
37
+ /// Current voting state
38
+ VotingState public votingState;
39
+
40
+ /// Proposal description
41
+ string public proposal;
42
+
43
+ /// Voting end timestamp
44
+ uint256 public endTime;
45
+
46
+ /// Whether an address has voted
47
+ mapping(address => bool) public hasVoted;
48
+
49
+ /// Total number of voters
50
+ uint256 public voterCount;
51
+
52
+ /// Encrypted total of Yes votes (1s)
53
+ euint64 private _yesVotes;
54
+
55
+ /// Encrypted total of No votes (0s counted separately for verification)
56
+ euint64 private _noVotes;
57
+
58
+ /// Revealed results
59
+ uint64 public revealedYesVotes;
60
+ uint64 public revealedNoVotes;
61
+
62
+ // ==================== EVENTS ====================
63
+
64
+ /// @notice Emitted when a vote is cast
65
+ /// @param voter Address of the voter
66
+ event VoteCast(address indexed voter);
67
+
68
+ /// @notice Emitted when voting is closed
69
+ /// @param encryptedYes Handle for encrypted Yes count
70
+ /// @param encryptedNo Handle for encrypted No count
71
+ event VotingClosed(euint64 encryptedYes, euint64 encryptedNo);
72
+
73
+ /// @notice Emitted when results are revealed
74
+ /// @param yesVotes Final Yes vote count
75
+ /// @param noVotes Final No vote count
76
+ event ResultsRevealed(uint64 yesVotes, uint64 noVotes);
77
+
78
+ // ==================== MODIFIERS ====================
79
+
80
+ /// @dev Restricts function to owner only
81
+ modifier onlyOwner() {
82
+ require(msg.sender == owner, "Only owner can call");
83
+ _;
84
+ }
85
+
86
+ // ==================== CONSTRUCTOR ====================
87
+
88
+ /// @notice Creates a new voting proposal
89
+ /// @param _proposal Description of what is being voted on
90
+ /// @param _durationSeconds How long voting is open
91
+ constructor(string memory _proposal, uint256 _durationSeconds) {
92
+ require(bytes(_proposal).length > 0, "Empty proposal");
93
+ require(_durationSeconds > 0, "Duration must be positive");
94
+
95
+ owner = msg.sender;
96
+ proposal = _proposal;
97
+ endTime = block.timestamp + _durationSeconds;
98
+ votingState = VotingState.Active;
99
+
100
+ // 🔐 Initialize encrypted counters to zero
101
+ _yesVotes = FHE.asEuint64(0);
102
+ _noVotes = FHE.asEuint64(0);
103
+ FHE.allowThis(_yesVotes);
104
+ FHE.allowThis(_noVotes);
105
+ }
106
+
107
+ // ==================== VOTING ====================
108
+
109
+ /// @notice Cast an encrypted vote
110
+ /// @dev Vote must be 0 (No) or 1 (Yes). Values > 1 are treated as Yes.
111
+ /// @param encryptedVote Encrypted vote (0 or 1)
112
+ /// @param inputProof Proof validating the encrypted input
113
+ function vote(
114
+ externalEuint8 encryptedVote,
115
+ bytes calldata inputProof
116
+ ) external {
117
+ require(votingState == VotingState.Active, "Voting not active");
118
+ require(block.timestamp < endTime, "Voting has ended");
119
+ require(!hasVoted[msg.sender], "Already voted");
120
+
121
+ // 🔐 Convert external encrypted input
122
+ // Note: Using euint8 for vote, but storing as euint64 for addition
123
+ euint64 voteValue = FHE.asEuint64(
124
+ FHE.fromExternal(encryptedVote, inputProof)
125
+ );
126
+
127
+ // 📊 Homomorphic vote counting
128
+ // We use select to normalize: if vote > 0, count as 1 for Yes
129
+ ebool isYes = FHE.gt(voteValue, FHE.asEuint64(0));
130
+
131
+ // ➕ Add to Yes counter if vote is Yes (1)
132
+ euint64 yesIncrement = FHE.select(
133
+ isYes,
134
+ FHE.asEuint64(1),
135
+ FHE.asEuint64(0)
136
+ );
137
+ _yesVotes = FHE.add(_yesVotes, yesIncrement);
138
+
139
+ // ➕ Add to No counter if vote is No (0)
140
+ euint64 noIncrement = FHE.select(
141
+ isYes,
142
+ FHE.asEuint64(0),
143
+ FHE.asEuint64(1)
144
+ );
145
+ _noVotes = FHE.add(_noVotes, noIncrement);
146
+
147
+ // ✅ Update permissions for new values
148
+ FHE.allowThis(_yesVotes);
149
+ FHE.allowThis(_noVotes);
150
+
151
+ // 📋 Record that this address has voted
152
+ hasVoted[msg.sender] = true;
153
+ voterCount++;
154
+
155
+ emit VoteCast(msg.sender);
156
+ }
157
+
158
+ // ==================== CLOSE VOTING ====================
159
+
160
+ /// @notice Close voting and prepare results for decryption
161
+ /// @dev Only owner can call after voting period ends
162
+ function closeVoting() external onlyOwner {
163
+ require(votingState == VotingState.Active, "Voting not active");
164
+ require(block.timestamp >= endTime, "Voting not yet ended");
165
+
166
+ // 🔓 Make results publicly decryptable
167
+ FHE.makePubliclyDecryptable(_yesVotes);
168
+ FHE.makePubliclyDecryptable(_noVotes);
169
+
170
+ votingState = VotingState.Closed;
171
+
172
+ emit VotingClosed(_yesVotes, _noVotes);
173
+ }
174
+
175
+ /// @notice Reveal voting results with KMS decryption proof
176
+ /// @dev Anyone can call with valid decryption proof
177
+ /// @param abiEncodedResults ABI-encoded (uint64 yes, uint64 no)
178
+ /// @param decryptionProof KMS signature proving decryption
179
+ function revealResults(
180
+ bytes memory abiEncodedResults,
181
+ bytes memory decryptionProof
182
+ ) external {
183
+ require(votingState == VotingState.Closed, "Voting not closed");
184
+
185
+ // 🔐 Build ciphertext list for verification
186
+ bytes32[] memory cts = new bytes32[](2);
187
+ cts[0] = FHE.toBytes32(_yesVotes);
188
+ cts[1] = FHE.toBytes32(_noVotes);
189
+
190
+ // 🔍 Verify the decryption proof (reverts if invalid)
191
+ FHE.checkSignatures(cts, abiEncodedResults, decryptionProof);
192
+
193
+ // 📤 Decode the verified results
194
+ (uint64 yesCount, uint64 noCount) = abi.decode(
195
+ abiEncodedResults,
196
+ (uint64, uint64)
197
+ );
198
+
199
+ revealedYesVotes = yesCount;
200
+ revealedNoVotes = noCount;
201
+ votingState = VotingState.Revealed;
202
+
203
+ emit ResultsRevealed(yesCount, noCount);
204
+ }
205
+
206
+ // ==================== VIEW FUNCTIONS ====================
207
+
208
+ /// @notice Get encrypted Yes votes handle (after voting closed)
209
+ function getEncryptedYesVotes() external view returns (euint64) {
210
+ require(votingState != VotingState.Active, "Voting still active");
211
+ return _yesVotes;
212
+ }
213
+
214
+ /// @notice Get encrypted No votes handle (after voting closed)
215
+ function getEncryptedNoVotes() external view returns (euint64) {
216
+ require(votingState != VotingState.Active, "Voting still active");
217
+ return _noVotes;
218
+ }
219
+
220
+ /// @notice Check if proposal passed (more Yes than No)
221
+ function hasPassed() external view returns (bool) {
222
+ require(votingState == VotingState.Revealed, "Results not revealed");
223
+ return revealedYesVotes > revealedNoVotes;
224
+ }
225
+
226
+ /// @notice Get time remaining for voting
227
+ function timeRemaining() external view returns (uint256) {
228
+ if (block.timestamp >= endTime) return 0;
229
+ return endTime - block.timestamp;
230
+ }
231
+ }
@@ -0,0 +1,309 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {
5
+ FHE,
6
+ euint8,
7
+ euint16,
8
+ ebool,
9
+ externalEuint8,
10
+ externalEuint16
11
+ } from "@fhevm/solidity/lib/FHE.sol";
12
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
13
+
14
+ /**
15
+ * @notice Private KYC - verify identity without revealing personal data!
16
+ *
17
+ * @dev Demonstrates identity verification using FHE predicates:
18
+ * - Users submit encrypted KYC attributes (age, country, credit score)
19
+ * - Verifiers check conditions without seeing actual values
20
+ * - Only boolean results revealed: "Is 18+?", "Is from allowed country?"
21
+ * - Personal data never leaves encrypted form
22
+ *
23
+ * Verification types:
24
+ * 1. Age verification: isAbove18(), isAbove21()
25
+ * 2. Country check: isFromAllowedCountry()
26
+ * 3. Credit score: hasCreditScoreAbove(threshold)
27
+ *
28
+ * ⚠️ IMPORTANT: This is a demo - production KYC needs trusted attesters!
29
+ */
30
+ contract PrivateKYC is ZamaEthereumConfig {
31
+ // ==================== TYPES ====================
32
+
33
+ struct Identity {
34
+ euint8 age; // 0-255 years
35
+ euint8 countryCode; // ISO 3166-1 numeric (1-255)
36
+ euint16 creditScore; // 0-65535
37
+ bool isVerified; // Has submitted KYC
38
+ uint256 verifiedAt; // Timestamp of verification
39
+ }
40
+
41
+ // ==================== STATE ====================
42
+
43
+ /// Contract owner (KYC admin)
44
+ address public owner;
45
+
46
+ /// Mapping from user address to their encrypted identity
47
+ mapping(address => Identity) private _identities;
48
+
49
+ /// Allowed country codes (stored as mapping for O(1) lookup)
50
+ mapping(uint8 => bool) public allowedCountries;
51
+
52
+ /// Minimum age requirement
53
+ uint8 public constant MIN_AGE_ADULT = 18;
54
+ uint8 public constant MIN_AGE_DRINKING_US = 21;
55
+
56
+ /// Minimum credit score for "good" rating
57
+ uint16 public constant MIN_CREDIT_GOOD = 700;
58
+
59
+ // ==================== EVENTS ====================
60
+
61
+ /// @notice Emitted when user submits KYC
62
+ /// @param user Address of user
63
+ event KYCSubmitted(address indexed user);
64
+
65
+ /// @notice Emitted when KYC is revoked
66
+ /// @param user Address of user
67
+ event KYCRevoked(address indexed user);
68
+
69
+ /// @notice Emitted when country allowlist is updated
70
+ /// @param countryCode Country code
71
+ /// @param allowed Whether country is allowed
72
+ event CountryAllowlistUpdated(uint8 countryCode, bool allowed);
73
+
74
+ /// @notice Emitted when age verification is requested
75
+ /// @param user Address being verified
76
+ /// @param minAge Minimum age required
77
+ event AgeVerificationRequested(address indexed user, uint8 minAge);
78
+
79
+ // ==================== MODIFIERS ====================
80
+
81
+ modifier onlyOwner() {
82
+ require(msg.sender == owner, "Only owner");
83
+ _;
84
+ }
85
+
86
+ modifier hasKYC(address user) {
87
+ require(_identities[user].isVerified, "No KYC submitted");
88
+ _;
89
+ }
90
+
91
+ // ==================== CONSTRUCTOR ====================
92
+
93
+ /// @notice Creates the KYC contract
94
+ /// @param _allowedCountryCodes Initial list of allowed country codes
95
+ constructor(uint8[] memory _allowedCountryCodes) {
96
+ owner = msg.sender;
97
+
98
+ // Initialize allowed countries
99
+ for (uint256 i = 0; i < _allowedCountryCodes.length; i++) {
100
+ allowedCountries[_allowedCountryCodes[i]] = true;
101
+ }
102
+ }
103
+
104
+ // ==================== KYC SUBMISSION ====================
105
+
106
+ /// @notice Submit encrypted KYC data
107
+ /// @param encAge Encrypted age
108
+ /// @param encCountry Encrypted country code
109
+ /// @param encCreditScore Encrypted credit score
110
+ /// @param inputProof Proof validating encrypted inputs
111
+ function submitKYC(
112
+ externalEuint8 encAge,
113
+ externalEuint8 encCountry,
114
+ externalEuint16 encCreditScore,
115
+ bytes calldata inputProof
116
+ ) external {
117
+ require(!_identities[msg.sender].isVerified, "Already verified");
118
+
119
+ // 🔐 Convert encrypted inputs
120
+ euint8 age = FHE.fromExternal(encAge, inputProof);
121
+ euint8 country = FHE.fromExternal(encCountry, inputProof);
122
+ euint16 creditScore = FHE.fromExternal(encCreditScore, inputProof);
123
+
124
+ // ✅ Grant permissions - only contract and user can access
125
+ FHE.allowThis(age);
126
+ FHE.allowThis(country);
127
+ FHE.allowThis(creditScore);
128
+ FHE.allow(age, msg.sender);
129
+ FHE.allow(country, msg.sender);
130
+ FHE.allow(creditScore, msg.sender);
131
+
132
+ // 📋 Store identity
133
+ _identities[msg.sender] = Identity({
134
+ age: age,
135
+ countryCode: country,
136
+ creditScore: creditScore,
137
+ isVerified: true,
138
+ verifiedAt: block.timestamp
139
+ });
140
+
141
+ emit KYCSubmitted(msg.sender);
142
+ }
143
+
144
+ /// @notice Revoke own KYC data
145
+ function revokeKYC() external hasKYC(msg.sender) {
146
+ delete _identities[msg.sender];
147
+ emit KYCRevoked(msg.sender);
148
+ }
149
+
150
+ // ==================== VERIFICATION FUNCTIONS ====================
151
+
152
+ /// @notice Check if user is 18 or older
153
+ /// @param user Address to verify
154
+ /// @return result Encrypted boolean result
155
+ function verifyAge18(address user) external hasKYC(user) returns (ebool) {
156
+ // 🔍 Compare: age >= 18
157
+ ebool isAdult = FHE.ge(
158
+ _identities[user].age,
159
+ FHE.asEuint8(MIN_AGE_ADULT)
160
+ );
161
+ return isAdult;
162
+ }
163
+
164
+ /// @notice Check if user is 21 or older (US drinking age)
165
+ /// @param user Address to verify
166
+ /// @return result Encrypted boolean result
167
+ function verifyAge21(address user) external hasKYC(user) returns (ebool) {
168
+ ebool isOldEnough = FHE.ge(
169
+ _identities[user].age,
170
+ FHE.asEuint8(MIN_AGE_DRINKING_US)
171
+ );
172
+ return isOldEnough;
173
+ }
174
+
175
+ /// @notice Check if user is above a custom age threshold
176
+ /// @param user Address to verify
177
+ /// @param minAge Minimum age required
178
+ /// @return result Encrypted boolean result
179
+ function verifyAgeAbove(
180
+ address user,
181
+ uint8 minAge
182
+ ) external hasKYC(user) returns (ebool) {
183
+ ebool meetsAge = FHE.ge(_identities[user].age, FHE.asEuint8(minAge));
184
+ return meetsAge;
185
+ }
186
+
187
+ /// @notice Check if user has good credit score (700+)
188
+ /// @param user Address to verify
189
+ /// @return result Encrypted boolean result
190
+ function verifyGoodCredit(
191
+ address user
192
+ ) external hasKYC(user) returns (ebool) {
193
+ ebool hasGoodCredit = FHE.ge(
194
+ _identities[user].creditScore,
195
+ FHE.asEuint16(MIN_CREDIT_GOOD)
196
+ );
197
+ return hasGoodCredit;
198
+ }
199
+
200
+ /// @notice Check if user's credit score is above threshold
201
+ /// @param user Address to verify
202
+ /// @param minScore Minimum credit score
203
+ /// @return result Encrypted boolean result
204
+ function verifyCreditAbove(
205
+ address user,
206
+ uint16 minScore
207
+ ) external hasKYC(user) returns (ebool) {
208
+ ebool meetsScore = FHE.ge(
209
+ _identities[user].creditScore,
210
+ FHE.asEuint16(minScore)
211
+ );
212
+ return meetsScore;
213
+ }
214
+
215
+ /// @notice Combined verification: 18+ AND good credit
216
+ /// @param user Address to verify
217
+ /// @return result Encrypted boolean result
218
+ function verifyAdultWithGoodCredit(
219
+ address user
220
+ ) external hasKYC(user) returns (ebool) {
221
+ ebool isAdult = FHE.ge(
222
+ _identities[user].age,
223
+ FHE.asEuint8(MIN_AGE_ADULT)
224
+ );
225
+ ebool hasGoodCredit = FHE.ge(
226
+ _identities[user].creditScore,
227
+ FHE.asEuint16(MIN_CREDIT_GOOD)
228
+ );
229
+
230
+ // 🔗 Combine with AND
231
+ ebool result = FHE.and(isAdult, hasGoodCredit);
232
+ return result;
233
+ }
234
+
235
+ // ==================== DECRYPTABLE VERIFICATION ====================
236
+
237
+ /// @notice Request age verification with public result
238
+ /// @dev Makes the result publicly decryptable
239
+ /// @param user Address to verify
240
+ /// @param minAge Minimum age required
241
+ /// @return resultHandle Handle to the encrypted result
242
+ function requestAgeVerification(
243
+ address user,
244
+ uint8 minAge
245
+ ) external hasKYC(user) returns (ebool resultHandle) {
246
+ ebool result = FHE.ge(_identities[user].age, FHE.asEuint8(minAge));
247
+
248
+ FHE.allowThis(result);
249
+ FHE.makePubliclyDecryptable(result);
250
+
251
+ emit AgeVerificationRequested(user, minAge);
252
+ return result;
253
+ }
254
+
255
+ // ==================== ADMIN FUNCTIONS ====================
256
+
257
+ /// @notice Update country allowlist
258
+ /// @param countryCode Country code to update
259
+ /// @param allowed Whether to allow or deny
260
+ function setCountryAllowed(
261
+ uint8 countryCode,
262
+ bool allowed
263
+ ) external onlyOwner {
264
+ allowedCountries[countryCode] = allowed;
265
+ emit CountryAllowlistUpdated(countryCode, allowed);
266
+ }
267
+
268
+ /// @notice Batch update country allowlist
269
+ /// @param countryCodes Array of country codes
270
+ /// @param allowed Whether to allow or deny all
271
+ function setCountriesAllowed(
272
+ uint8[] calldata countryCodes,
273
+ bool allowed
274
+ ) external onlyOwner {
275
+ for (uint256 i = 0; i < countryCodes.length; i++) {
276
+ allowedCountries[countryCodes[i]] = allowed;
277
+ emit CountryAllowlistUpdated(countryCodes[i], allowed);
278
+ }
279
+ }
280
+
281
+ // ==================== VIEW FUNCTIONS ====================
282
+
283
+ /// @notice Check if user has submitted KYC
284
+ function hasSubmittedKYC(address user) external view returns (bool) {
285
+ return _identities[user].isVerified;
286
+ }
287
+
288
+ /// @notice Get KYC submission timestamp
289
+ function getVerificationTime(address user) external view returns (uint256) {
290
+ return _identities[user].verifiedAt;
291
+ }
292
+
293
+ /// @notice Get own encrypted data handles (for decryption by user)
294
+ /// @dev Only callable by the identity owner
295
+ function getMyIdentity()
296
+ external
297
+ view
298
+ hasKYC(msg.sender)
299
+ returns (euint8 age, euint8 countryCode, euint16 creditScore)
300
+ {
301
+ Identity storage id = _identities[msg.sender];
302
+ return (id.age, id.countryCode, id.creditScore);
303
+ }
304
+
305
+ /// @notice Check if a country code is allowed
306
+ function isCountryAllowed(uint8 countryCode) external view returns (bool) {
307
+ return allowedCountries[countryCode];
308
+ }
309
+ }