create-fhevm-example 1.3.2 → 1.4.1
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/contracts/advanced/BlindAuction.sol +255 -0
- package/contracts/advanced/EncryptedEscrow.sol +315 -0
- package/contracts/advanced/HiddenVoting.sol +231 -0
- package/contracts/advanced/PrivateKYC.sol +309 -0
- package/contracts/advanced/PrivatePayroll.sol +285 -0
- package/contracts/basic/decryption/PublicDecryptMultipleValues.sol +160 -0
- package/contracts/basic/decryption/PublicDecryptSingleValue.sol +142 -0
- package/contracts/basic/decryption/UserDecryptMultipleValues.sol +61 -0
- package/contracts/basic/decryption/UserDecryptSingleValue.sol +59 -0
- package/contracts/basic/encryption/EncryptMultipleValues.sol +72 -0
- package/contracts/basic/encryption/EncryptSingleValue.sol +44 -0
- package/contracts/basic/encryption/FHECounter.sol +54 -0
- package/contracts/basic/fhe-operations/FHEAdd.sol +51 -0
- package/contracts/basic/fhe-operations/FHEArithmetic.sol +99 -0
- package/contracts/basic/fhe-operations/FHEComparison.sol +116 -0
- package/contracts/basic/fhe-operations/FHEIfThenElse.sol +53 -0
- package/contracts/concepts/FHEAccessControl.sol +94 -0
- package/contracts/concepts/FHEAntiPatterns.sol +329 -0
- package/contracts/concepts/FHEHandles.sol +128 -0
- package/contracts/concepts/FHEInputProof.sol +104 -0
- package/contracts/gaming/EncryptedLottery.sol +298 -0
- package/contracts/gaming/EncryptedPoker.sol +337 -0
- package/contracts/gaming/RockPaperScissors.sol +213 -0
- package/contracts/openzeppelin/ERC7984.sol +85 -0
- package/contracts/openzeppelin/ERC7984ERC20Wrapper.sol +43 -0
- package/contracts/openzeppelin/SwapERC7984ToERC20.sol +110 -0
- package/contracts/openzeppelin/SwapERC7984ToERC7984.sol +48 -0
- package/contracts/openzeppelin/VestingWallet.sol +147 -0
- package/contracts/openzeppelin/mocks/ERC20Mock.sol +31 -0
- package/dist/scripts/commands/add-mode.d.ts.map +1 -0
- package/dist/scripts/{add-mode.js → commands/add-mode.js} +27 -61
- package/dist/scripts/commands/doctor.d.ts.map +1 -0
- package/dist/scripts/{doctor.js → commands/doctor.js} +2 -2
- package/dist/scripts/commands/generate-config.d.ts.map +1 -0
- package/dist/scripts/{generate-config.js → commands/generate-config.js} +3 -10
- package/dist/scripts/commands/generate-docs.d.ts.map +1 -0
- package/dist/scripts/{generate-docs.js → commands/generate-docs.js} +4 -3
- package/dist/scripts/commands/maintenance.d.ts.map +1 -0
- package/dist/scripts/{maintenance.js → commands/maintenance.js} +11 -10
- package/dist/scripts/index.js +14 -33
- package/dist/scripts/{builders.d.ts → shared/builders.d.ts} +2 -2
- package/dist/scripts/shared/builders.d.ts.map +1 -0
- package/dist/scripts/{builders.js → shared/builders.js} +49 -30
- package/dist/scripts/{config.d.ts → shared/config.d.ts} +0 -2
- package/dist/scripts/shared/config.d.ts.map +1 -0
- package/dist/scripts/{config.js → shared/config.js} +48 -59
- package/dist/scripts/shared/generators.d.ts +42 -0
- package/dist/scripts/shared/generators.d.ts.map +1 -0
- package/dist/scripts/{utils.js → shared/generators.js} +34 -271
- package/dist/scripts/shared/ui.d.ts.map +1 -0
- package/dist/scripts/{ui.js → shared/ui.js} +3 -2
- package/dist/scripts/{utils.d.ts → shared/utils.d.ts} +4 -27
- package/dist/scripts/shared/utils.d.ts.map +1 -0
- package/dist/scripts/shared/utils.js +236 -0
- package/fhevm-hardhat-template/.eslintignore +26 -0
- package/fhevm-hardhat-template/.eslintrc.yml +21 -0
- package/fhevm-hardhat-template/.github/workflows/main.yml +47 -0
- package/fhevm-hardhat-template/.github/workflows/manual-windows.yml +28 -0
- package/fhevm-hardhat-template/.github/workflows/manual.yml +28 -0
- package/fhevm-hardhat-template/.prettierignore +25 -0
- package/fhevm-hardhat-template/.prettierrc.yml +15 -0
- package/fhevm-hardhat-template/.solcover.js +4 -0
- package/fhevm-hardhat-template/.solhint.json +12 -0
- package/fhevm-hardhat-template/.solhintignore +3 -0
- package/fhevm-hardhat-template/.vscode/extensions.json +3 -0
- package/fhevm-hardhat-template/.vscode/settings.json +9 -0
- package/fhevm-hardhat-template/LICENSE +33 -0
- package/fhevm-hardhat-template/README.md +110 -0
- package/fhevm-hardhat-template/contracts/FHECounter.sol +46 -0
- package/fhevm-hardhat-template/deploy/deploy.ts +17 -0
- package/fhevm-hardhat-template/hardhat.config.ts +90 -0
- package/fhevm-hardhat-template/package-lock.json +10405 -0
- package/fhevm-hardhat-template/package.json +104 -0
- package/fhevm-hardhat-template/tasks/FHECounter.ts +184 -0
- package/fhevm-hardhat-template/tasks/accounts.ts +9 -0
- package/fhevm-hardhat-template/test/FHECounter.ts +104 -0
- package/fhevm-hardhat-template/test/FHECounterSepolia.ts +104 -0
- package/fhevm-hardhat-template/tsconfig.json +23 -0
- package/package.json +11 -8
- package/test/advanced/BlindAuction.ts +246 -0
- package/test/advanced/EncryptedEscrow.ts +295 -0
- package/test/advanced/HiddenVoting.ts +268 -0
- package/test/advanced/PrivateKYC.ts +382 -0
- package/test/advanced/PrivatePayroll.ts +253 -0
- package/test/basic/decryption/PublicDecryptMultipleValues.ts +254 -0
- package/test/basic/decryption/PublicDecryptSingleValue.ts +264 -0
- package/test/basic/decryption/UserDecryptMultipleValues.ts +107 -0
- package/test/basic/decryption/UserDecryptSingleValue.ts +97 -0
- package/test/basic/encryption/EncryptMultipleValues.ts +110 -0
- package/test/basic/encryption/EncryptSingleValue.ts +124 -0
- package/test/basic/encryption/FHECounter.ts +112 -0
- package/test/basic/fhe-operations/FHEAdd.ts +97 -0
- package/test/basic/fhe-operations/FHEArithmetic.ts +161 -0
- package/test/basic/fhe-operations/FHEComparison.ts +167 -0
- package/test/basic/fhe-operations/FHEIfThenElse.ts +97 -0
- package/test/concepts/FHEAccessControl.ts +154 -0
- package/test/concepts/FHEAntiPatterns.ts +111 -0
- package/test/concepts/FHEHandles.ts +156 -0
- package/test/concepts/FHEInputProof.ts +151 -0
- package/test/gaming/EncryptedLottery.ts +214 -0
- package/test/gaming/EncryptedPoker.ts +349 -0
- package/test/gaming/RockPaperScissors.ts +205 -0
- package/test/openzeppelin/ERC7984.ts +142 -0
- package/test/openzeppelin/ERC7984ERC20Wrapper.ts +71 -0
- package/test/openzeppelin/SwapERC7984ToERC20.ts +76 -0
- package/test/openzeppelin/SwapERC7984ToERC7984.ts +113 -0
- package/test/openzeppelin/VestingWallet.ts +89 -0
- package/dist/scripts/add-mode.d.ts.map +0 -1
- package/dist/scripts/builders.d.ts.map +0 -1
- package/dist/scripts/config.d.ts.map +0 -1
- package/dist/scripts/doctor.d.ts.map +0 -1
- package/dist/scripts/generate-config.d.ts.map +0 -1
- package/dist/scripts/generate-docs.d.ts.map +0 -1
- package/dist/scripts/help.d.ts +0 -9
- package/dist/scripts/help.d.ts.map +0 -1
- package/dist/scripts/help.js +0 -73
- package/dist/scripts/maintenance.d.ts.map +0 -1
- package/dist/scripts/ui.d.ts.map +0 -1
- package/dist/scripts/utils.d.ts.map +0 -1
- /package/dist/scripts/{add-mode.d.ts → commands/add-mode.d.ts} +0 -0
- /package/dist/scripts/{doctor.d.ts → commands/doctor.d.ts} +0 -0
- /package/dist/scripts/{generate-config.d.ts → commands/generate-config.d.ts} +0 -0
- /package/dist/scripts/{generate-docs.d.ts → commands/generate-docs.d.ts} +0 -0
- /package/dist/scripts/{maintenance.d.ts → commands/maintenance.d.ts} +0 -0
- /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
|
+
}
|