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.
- 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 +63 -59
- 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 +228 -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 +13 -10
- 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/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,285 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
FHE,
|
|
6
|
+
euint64,
|
|
7
|
+
ebool,
|
|
8
|
+
externalEuint64
|
|
9
|
+
} from "@fhevm/solidity/lib/FHE.sol";
|
|
10
|
+
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @notice Private Payroll system - salaries stay encrypted, only employees see their own!
|
|
14
|
+
*
|
|
15
|
+
* @dev Demonstrates confidential enterprise use case:
|
|
16
|
+
* - Employee salaries stored encrypted
|
|
17
|
+
* - Bulk payments without revealing individual amounts
|
|
18
|
+
* - Each employee can decrypt only their own salary
|
|
19
|
+
* - Total payroll visible to employer for budgeting
|
|
20
|
+
*
|
|
21
|
+
* Flow:
|
|
22
|
+
* 1. Employer adds employees with encrypted salaries
|
|
23
|
+
* 2. Employer funds the contract
|
|
24
|
+
* 3. Employer calls payAll() to process all salaries
|
|
25
|
+
* 4. Employees can view (decrypt) their own salary
|
|
26
|
+
*
|
|
27
|
+
* ⚠️ IMPORTANT: Uses cumulative encrypted sum for total payroll tracking
|
|
28
|
+
*/
|
|
29
|
+
contract PrivatePayroll is ZamaEthereumConfig {
|
|
30
|
+
// ==================== STATE ====================
|
|
31
|
+
|
|
32
|
+
/// Contract owner (employer)
|
|
33
|
+
address public employer;
|
|
34
|
+
|
|
35
|
+
/// List of employee addresses
|
|
36
|
+
address[] public employees;
|
|
37
|
+
|
|
38
|
+
/// Mapping from employee to their encrypted salary
|
|
39
|
+
mapping(address => euint64) private _salaries;
|
|
40
|
+
|
|
41
|
+
/// Whether an address is an employee
|
|
42
|
+
mapping(address => bool) public isEmployee;
|
|
43
|
+
|
|
44
|
+
/// Last payment timestamp per employee
|
|
45
|
+
mapping(address => uint256) public lastPayment;
|
|
46
|
+
|
|
47
|
+
/// Total encrypted salary sum (for budget tracking)
|
|
48
|
+
euint64 private _totalSalaries;
|
|
49
|
+
|
|
50
|
+
/// Payment period in seconds (default: 30 days)
|
|
51
|
+
uint256 public paymentPeriod;
|
|
52
|
+
|
|
53
|
+
// ==================== EVENTS ====================
|
|
54
|
+
|
|
55
|
+
/// @notice Emitted when a new employee is added
|
|
56
|
+
/// @param employee Address of the employee
|
|
57
|
+
event EmployeeAdded(address indexed employee);
|
|
58
|
+
|
|
59
|
+
/// @notice Emitted when an employee is removed
|
|
60
|
+
/// @param employee Address of the removed employee
|
|
61
|
+
event EmployeeRemoved(address indexed employee);
|
|
62
|
+
|
|
63
|
+
/// @notice Emitted when salary is updated
|
|
64
|
+
/// @param employee Address of the employee
|
|
65
|
+
event SalaryUpdated(address indexed employee);
|
|
66
|
+
|
|
67
|
+
/// @notice Emitted when payment is processed
|
|
68
|
+
/// @param employee Address of paid employee
|
|
69
|
+
/// @param timestamp Payment time
|
|
70
|
+
event PaymentProcessed(address indexed employee, uint256 timestamp);
|
|
71
|
+
|
|
72
|
+
/// @notice Emitted when contract is funded
|
|
73
|
+
/// @param amount Amount funded
|
|
74
|
+
event ContractFunded(uint256 amount);
|
|
75
|
+
|
|
76
|
+
// ==================== MODIFIERS ====================
|
|
77
|
+
|
|
78
|
+
modifier onlyEmployer() {
|
|
79
|
+
require(msg.sender == employer, "Only employer");
|
|
80
|
+
_;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
modifier onlyEmployee() {
|
|
84
|
+
require(isEmployee[msg.sender], "Not an employee");
|
|
85
|
+
_;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ==================== CONSTRUCTOR ====================
|
|
89
|
+
|
|
90
|
+
/// @notice Creates a new private payroll contract
|
|
91
|
+
/// @param _paymentPeriod Time between payments in seconds
|
|
92
|
+
constructor(uint256 _paymentPeriod) {
|
|
93
|
+
employer = msg.sender;
|
|
94
|
+
paymentPeriod = _paymentPeriod > 0 ? _paymentPeriod : 30 days;
|
|
95
|
+
_totalSalaries = FHE.asEuint64(0);
|
|
96
|
+
FHE.allowThis(_totalSalaries);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ==================== EMPLOYEE MANAGEMENT ====================
|
|
100
|
+
|
|
101
|
+
/// @notice Add a new employee with encrypted salary
|
|
102
|
+
/// @param employee Address of the employee
|
|
103
|
+
/// @param encryptedSalary Encrypted salary amount
|
|
104
|
+
/// @param inputProof Proof validating the encrypted input
|
|
105
|
+
function addEmployee(
|
|
106
|
+
address employee,
|
|
107
|
+
externalEuint64 encryptedSalary,
|
|
108
|
+
bytes calldata inputProof
|
|
109
|
+
) external onlyEmployer {
|
|
110
|
+
require(employee != address(0), "Invalid address");
|
|
111
|
+
require(!isEmployee[employee], "Already an employee");
|
|
112
|
+
|
|
113
|
+
// 🔐 Convert external encrypted input
|
|
114
|
+
euint64 salary = FHE.fromExternal(encryptedSalary, inputProof);
|
|
115
|
+
|
|
116
|
+
// ✅ Grant permissions
|
|
117
|
+
FHE.allowThis(salary);
|
|
118
|
+
FHE.allow(salary, employee); // Employee can view their own salary
|
|
119
|
+
|
|
120
|
+
// 📋 Store employee data
|
|
121
|
+
_salaries[employee] = salary;
|
|
122
|
+
isEmployee[employee] = true;
|
|
123
|
+
employees.push(employee);
|
|
124
|
+
|
|
125
|
+
// 📊 Update total
|
|
126
|
+
_totalSalaries = FHE.add(_totalSalaries, salary);
|
|
127
|
+
FHE.allowThis(_totalSalaries);
|
|
128
|
+
|
|
129
|
+
emit EmployeeAdded(employee);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// @notice Update an employee's salary
|
|
133
|
+
/// @param employee Address of the employee
|
|
134
|
+
/// @param encryptedSalary New encrypted salary amount
|
|
135
|
+
/// @param inputProof Proof validating the encrypted input
|
|
136
|
+
function updateSalary(
|
|
137
|
+
address employee,
|
|
138
|
+
externalEuint64 encryptedSalary,
|
|
139
|
+
bytes calldata inputProof
|
|
140
|
+
) external onlyEmployer {
|
|
141
|
+
require(isEmployee[employee], "Not an employee");
|
|
142
|
+
|
|
143
|
+
// Get old salary for total adjustment
|
|
144
|
+
euint64 oldSalary = _salaries[employee];
|
|
145
|
+
|
|
146
|
+
// 🔐 Convert new salary
|
|
147
|
+
euint64 newSalary = FHE.fromExternal(encryptedSalary, inputProof);
|
|
148
|
+
|
|
149
|
+
// ✅ Grant permissions
|
|
150
|
+
FHE.allowThis(newSalary);
|
|
151
|
+
FHE.allow(newSalary, employee);
|
|
152
|
+
|
|
153
|
+
// 📋 Update salary
|
|
154
|
+
_salaries[employee] = newSalary;
|
|
155
|
+
|
|
156
|
+
// 📊 Update total: subtract old, add new
|
|
157
|
+
_totalSalaries = FHE.sub(_totalSalaries, oldSalary);
|
|
158
|
+
_totalSalaries = FHE.add(_totalSalaries, newSalary);
|
|
159
|
+
FHE.allowThis(_totalSalaries);
|
|
160
|
+
|
|
161
|
+
emit SalaryUpdated(employee);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// @notice Remove an employee
|
|
165
|
+
/// @param employee Address to remove
|
|
166
|
+
function removeEmployee(address employee) external onlyEmployer {
|
|
167
|
+
require(isEmployee[employee], "Not an employee");
|
|
168
|
+
|
|
169
|
+
// Get salary for total adjustment
|
|
170
|
+
euint64 salary = _salaries[employee];
|
|
171
|
+
|
|
172
|
+
// 📊 Update total
|
|
173
|
+
_totalSalaries = FHE.sub(_totalSalaries, salary);
|
|
174
|
+
FHE.allowThis(_totalSalaries);
|
|
175
|
+
|
|
176
|
+
// Remove from list
|
|
177
|
+
for (uint256 i = 0; i < employees.length; i++) {
|
|
178
|
+
if (employees[i] == employee) {
|
|
179
|
+
employees[i] = employees[employees.length - 1];
|
|
180
|
+
employees.pop();
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Clear data - euint64 cannot use delete, assign to zero instead
|
|
186
|
+
_salaries[employee] = FHE.asEuint64(0);
|
|
187
|
+
isEmployee[employee] = false;
|
|
188
|
+
lastPayment[employee] = 0;
|
|
189
|
+
|
|
190
|
+
emit EmployeeRemoved(employee);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ==================== PAYMENTS ====================
|
|
194
|
+
|
|
195
|
+
/// @notice Fund the contract for payroll
|
|
196
|
+
function fund() external payable onlyEmployer {
|
|
197
|
+
require(msg.value > 0, "Must send funds");
|
|
198
|
+
emit ContractFunded(msg.value);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/// @notice Process payment for a single employee
|
|
202
|
+
/// @dev Requires decryption of salary - simplified for demo
|
|
203
|
+
/// @param employee Address to pay
|
|
204
|
+
/// @param abiEncodedSalary ABI-encoded salary amount
|
|
205
|
+
/// @param decryptionProof KMS decryption proof
|
|
206
|
+
function processPayment(
|
|
207
|
+
address employee,
|
|
208
|
+
bytes memory abiEncodedSalary,
|
|
209
|
+
bytes memory decryptionProof
|
|
210
|
+
) external onlyEmployer {
|
|
211
|
+
require(isEmployee[employee], "Not an employee");
|
|
212
|
+
require(
|
|
213
|
+
block.timestamp >= lastPayment[employee] + paymentPeriod,
|
|
214
|
+
"Too early for next payment"
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// Verify decryption
|
|
218
|
+
bytes32[] memory cts = new bytes32[](1);
|
|
219
|
+
cts[0] = FHE.toBytes32(_salaries[employee]);
|
|
220
|
+
FHE.checkSignatures(cts, abiEncodedSalary, decryptionProof);
|
|
221
|
+
|
|
222
|
+
uint64 salaryAmount = abi.decode(abiEncodedSalary, (uint64));
|
|
223
|
+
require(address(this).balance >= salaryAmount, "Insufficient funds");
|
|
224
|
+
|
|
225
|
+
lastPayment[employee] = block.timestamp;
|
|
226
|
+
|
|
227
|
+
// Transfer salary
|
|
228
|
+
(bool sent, ) = employee.call{value: salaryAmount}("");
|
|
229
|
+
require(sent, "Payment failed");
|
|
230
|
+
|
|
231
|
+
emit PaymentProcessed(employee, block.timestamp);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ==================== VIEW FUNCTIONS ====================
|
|
235
|
+
|
|
236
|
+
/// @notice Get encrypted salary handle for an employee
|
|
237
|
+
/// @dev Only callable by the employee themselves
|
|
238
|
+
function getMySalary() external view onlyEmployee returns (euint64) {
|
|
239
|
+
return _salaries[msg.sender];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/// @notice Get encrypted total salaries handle
|
|
243
|
+
/// @dev Only employer can access for budget planning
|
|
244
|
+
function getTotalSalaries() external view onlyEmployer returns (euint64) {
|
|
245
|
+
return _totalSalaries;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/// @notice Get number of employees
|
|
249
|
+
function getEmployeeCount() external view returns (uint256) {
|
|
250
|
+
return employees.length;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/// @notice Get employee at index
|
|
254
|
+
function getEmployee(uint256 index) external view returns (address) {
|
|
255
|
+
require(index < employees.length, "Index out of bounds");
|
|
256
|
+
return employees[index];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/// @notice Check if payment is due for an employee
|
|
260
|
+
function isPaymentDue(address employee) external view returns (bool) {
|
|
261
|
+
if (!isEmployee[employee]) return false;
|
|
262
|
+
return block.timestamp >= lastPayment[employee] + paymentPeriod;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/// @notice Get contract balance
|
|
266
|
+
function getBalance() external view returns (uint256) {
|
|
267
|
+
return address(this).balance;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/// @notice Get payroll info
|
|
271
|
+
function getPayrollInfo()
|
|
272
|
+
external
|
|
273
|
+
view
|
|
274
|
+
returns (uint256 employeeCount, uint256 balance, uint256 period)
|
|
275
|
+
{
|
|
276
|
+
return (employees.length, address(this).balance, paymentPeriod);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ==================== RECEIVE ====================
|
|
280
|
+
|
|
281
|
+
/// @notice Accept ETH deposits
|
|
282
|
+
receive() external payable {
|
|
283
|
+
emit ContractFunded(msg.value);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import {FHE, euint8} from "@fhevm/solidity/lib/FHE.sol";
|
|
5
|
+
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @notice Implements a simple 8-sided Die Roll game demonstrating public, permissionless decryption
|
|
9
|
+
*
|
|
10
|
+
* @dev Uses FHE.makePubliclyDecryptable feature to allow anyone to decrypt the game results.
|
|
11
|
+
* Inherits from ZamaEthereumConfig to access FHE functions like FHE.randEuint8() and FHE.checkSignatures().
|
|
12
|
+
*/
|
|
13
|
+
contract HighestDieRoll is ZamaEthereumConfig {
|
|
14
|
+
constructor() {}
|
|
15
|
+
|
|
16
|
+
// Simple counter to assign a unique ID to each new game.
|
|
17
|
+
uint256 private counter = 0;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Defines the entire state for a single Die Roll game instance.
|
|
21
|
+
*/
|
|
22
|
+
struct Game {
|
|
23
|
+
address playerA;
|
|
24
|
+
address playerB;
|
|
25
|
+
euint8 playerAEncryptedDieRoll;
|
|
26
|
+
euint8 playerBEncryptedDieRoll;
|
|
27
|
+
address winner;
|
|
28
|
+
bool revealed;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Mapping to store all game states, accessible by a unique game ID.
|
|
32
|
+
mapping(uint256 gameId => Game game) public games;
|
|
33
|
+
|
|
34
|
+
/// @notice Emitted when a new game is started, providing the encrypted handle required for decryption
|
|
35
|
+
/// @param gameId The unique identifier for the game
|
|
36
|
+
/// @param playerA The address of playerA
|
|
37
|
+
/// @param playerB The address of playerB
|
|
38
|
+
/// @param playerAEncryptedDieRoll The encrypted die roll result of playerA
|
|
39
|
+
/// @param playerBEncryptedDieRoll The encrypted die roll result of playerB
|
|
40
|
+
event GameCreated(
|
|
41
|
+
uint256 indexed gameId,
|
|
42
|
+
address indexed playerA,
|
|
43
|
+
address indexed playerB,
|
|
44
|
+
euint8 playerAEncryptedDieRoll,
|
|
45
|
+
euint8 playerBEncryptedDieRoll
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
/// @notice Initiates a new highest die roll game, generates the result using FHE,
|
|
49
|
+
/// @notice and makes the result publicly available for decryption.
|
|
50
|
+
function highestDieRoll(address playerA, address playerB) external {
|
|
51
|
+
require(playerA != address(0), "playerA is address zero");
|
|
52
|
+
require(playerB != address(0), "playerB player is address zero");
|
|
53
|
+
require(playerA != playerB, "playerA and playerB should be different");
|
|
54
|
+
|
|
55
|
+
euint8 playerAEncryptedDieRoll = FHE.randEuint8();
|
|
56
|
+
euint8 playerBEncryptedDieRoll = FHE.randEuint8();
|
|
57
|
+
|
|
58
|
+
counter++;
|
|
59
|
+
|
|
60
|
+
// gameId > 0
|
|
61
|
+
uint256 gameId = counter;
|
|
62
|
+
games[gameId] = Game({
|
|
63
|
+
playerA: playerA,
|
|
64
|
+
playerB: playerB,
|
|
65
|
+
playerAEncryptedDieRoll: playerAEncryptedDieRoll,
|
|
66
|
+
playerBEncryptedDieRoll: playerBEncryptedDieRoll,
|
|
67
|
+
winner: address(0),
|
|
68
|
+
revealed: false
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// We make the results publicly decryptable.
|
|
72
|
+
FHE.makePubliclyDecryptable(playerAEncryptedDieRoll);
|
|
73
|
+
FHE.makePubliclyDecryptable(playerBEncryptedDieRoll);
|
|
74
|
+
|
|
75
|
+
// You can catch the event to get the gameId and the die rolls handles
|
|
76
|
+
// for further decryption requests, or create a view function.
|
|
77
|
+
emit GameCreated(
|
|
78
|
+
gameId,
|
|
79
|
+
playerA,
|
|
80
|
+
playerB,
|
|
81
|
+
playerAEncryptedDieRoll,
|
|
82
|
+
playerBEncryptedDieRoll
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// @notice Returns the number of games created so far.
|
|
87
|
+
function getGamesCount() public view returns (uint256) {
|
|
88
|
+
return counter;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// @notice Returns the encrypted euint8 handle that stores the playerA die roll.
|
|
92
|
+
function getPlayerADieRoll(uint256 gameId) public view returns (euint8) {
|
|
93
|
+
return games[gameId].playerAEncryptedDieRoll;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// @notice Returns the encrypted euint8 handle that stores the playerB die roll.
|
|
97
|
+
function getPlayerBDieRoll(uint256 gameId) public view returns (euint8) {
|
|
98
|
+
return games[gameId].playerBEncryptedDieRoll;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// @notice Returns the address of the game winner. If the game is finalized, the function returns `address(0)`
|
|
102
|
+
/// @notice if the game is a draw.
|
|
103
|
+
function getWinner(uint256 gameId) public view returns (address) {
|
|
104
|
+
require(games[gameId].revealed, "Game winner not yet revealed");
|
|
105
|
+
return games[gameId].winner;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// @notice Returns `true` if the game result is publicly revealed, `false` otherwise.
|
|
109
|
+
function isGameRevealed(uint256 gameId) public view returns (bool) {
|
|
110
|
+
return games[gameId].revealed;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// @notice Verifies the provided (decryption proof, ABI-encoded clear values) pair against the stored ciphertext,
|
|
114
|
+
/// @notice and then stores the winner of the game.
|
|
115
|
+
function recordAndVerifyWinner(
|
|
116
|
+
uint256 gameId,
|
|
117
|
+
bytes memory abiEncodedClearGameResult,
|
|
118
|
+
bytes memory decryptionProof
|
|
119
|
+
) public {
|
|
120
|
+
require(!games[gameId].revealed, "Game already revealed");
|
|
121
|
+
|
|
122
|
+
// 1. FHE Verification: Build the list of ciphertexts (handles) and verify the proof.
|
|
123
|
+
// The verification checks that 'abiEncodedClearGameResult' is the true decryption
|
|
124
|
+
// of the '(playerAEncryptedDieRoll, playerBEncryptedDieRoll)' handle pair using
|
|
125
|
+
// the provided 'decryptionProof'.
|
|
126
|
+
|
|
127
|
+
// Creating the list of handles in the right order! In this case the order does not matter since the proof
|
|
128
|
+
// only involves 1 single handle.
|
|
129
|
+
bytes32[] memory cts = new bytes32[](2);
|
|
130
|
+
cts[0] = FHE.toBytes32(games[gameId].playerAEncryptedDieRoll);
|
|
131
|
+
cts[1] = FHE.toBytes32(games[gameId].playerBEncryptedDieRoll);
|
|
132
|
+
|
|
133
|
+
// This FHE call reverts the transaction if the decryption proof is invalid.
|
|
134
|
+
FHE.checkSignatures(cts, abiEncodedClearGameResult, decryptionProof);
|
|
135
|
+
|
|
136
|
+
// 2. Decode the clear result and determine the winner's address.
|
|
137
|
+
// In this very specific case, the function argument `abiEncodedClearGameResult` could have been replaced by two
|
|
138
|
+
// `uint8` instead of an abi-encoded uint8 pair. In this case, we should have to compute abi.encode on-chain
|
|
139
|
+
(
|
|
140
|
+
uint8 decodedClearPlayerADieRoll,
|
|
141
|
+
uint8 decodedClearPlayerBDieRoll
|
|
142
|
+
) = abi.decode(abiEncodedClearGameResult, (uint8, uint8));
|
|
143
|
+
|
|
144
|
+
// The die is an 8-sided die (d8) (1..8)
|
|
145
|
+
decodedClearPlayerADieRoll = (decodedClearPlayerADieRoll % 8) + 1;
|
|
146
|
+
decodedClearPlayerBDieRoll = (decodedClearPlayerBDieRoll % 8) + 1;
|
|
147
|
+
|
|
148
|
+
address winner = decodedClearPlayerADieRoll > decodedClearPlayerBDieRoll
|
|
149
|
+
? games[gameId].playerA
|
|
150
|
+
: (
|
|
151
|
+
decodedClearPlayerADieRoll < decodedClearPlayerBDieRoll
|
|
152
|
+
? games[gameId].playerB
|
|
153
|
+
: address(0)
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// 3. Store the revealed flag
|
|
157
|
+
games[gameId].revealed = true;
|
|
158
|
+
games[gameId].winner = winner;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import {FHE, ebool} from "@fhevm/solidity/lib/FHE.sol";
|
|
5
|
+
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @notice Implements a simple Heads or Tails game demonstrating public, permissionless decryption
|
|
9
|
+
*
|
|
10
|
+
* @dev Uses FHE.makePubliclyDecryptable feature to allow anyone to decrypt the game results.
|
|
11
|
+
* Inherits from ZamaEthereumConfig to access FHE functions like FHE.randEbool() and FHE.checkSignatures().
|
|
12
|
+
*/
|
|
13
|
+
contract HeadsOrTails is ZamaEthereumConfig {
|
|
14
|
+
constructor() {}
|
|
15
|
+
|
|
16
|
+
/// Simple counter to assign a unique ID to each new game.
|
|
17
|
+
uint256 private counter = 0;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Defines the entire state for a single Heads or Tails game instance.
|
|
21
|
+
*/
|
|
22
|
+
struct Game {
|
|
23
|
+
address headsPlayer;
|
|
24
|
+
address tailsPlayer;
|
|
25
|
+
ebool encryptedHasHeadsWon;
|
|
26
|
+
address winner;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Mapping to store all game states, accessible by a unique game ID.
|
|
30
|
+
mapping(uint256 gameId => Game game) public games;
|
|
31
|
+
|
|
32
|
+
/// @notice Emitted when a new game is started, providing the encrypted handle required for decryption
|
|
33
|
+
/// @param gameId The unique identifier for the game
|
|
34
|
+
/// @param headsPlayer The address choosing Heads
|
|
35
|
+
/// @param tailsPlayer The address choosing Tails
|
|
36
|
+
/// @param encryptedHasHeadsWon The encrypted handle (ciphertext) storing the result
|
|
37
|
+
event GameCreated(
|
|
38
|
+
uint256 indexed gameId,
|
|
39
|
+
address indexed headsPlayer,
|
|
40
|
+
address indexed tailsPlayer,
|
|
41
|
+
ebool encryptedHasHeadsWon
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
/// @notice Initiates a new Heads or Tails game, generates the result using FHE,
|
|
45
|
+
/// @notice and makes the result publicly available for decryption.
|
|
46
|
+
function headsOrTails(address headsPlayer, address tailsPlayer) external {
|
|
47
|
+
require(headsPlayer != address(0), "Heads player is address zero");
|
|
48
|
+
require(tailsPlayer != address(0), "Tails player is address zero");
|
|
49
|
+
require(
|
|
50
|
+
headsPlayer != tailsPlayer,
|
|
51
|
+
"Heads player and Tails player should be different"
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// true: Heads
|
|
55
|
+
// false: Tails
|
|
56
|
+
ebool headsOrTailsResult = FHE.randEbool();
|
|
57
|
+
|
|
58
|
+
counter++;
|
|
59
|
+
|
|
60
|
+
// gameId > 0
|
|
61
|
+
uint256 gameId = counter;
|
|
62
|
+
games[gameId] = Game({
|
|
63
|
+
headsPlayer: headsPlayer,
|
|
64
|
+
tailsPlayer: tailsPlayer,
|
|
65
|
+
encryptedHasHeadsWon: headsOrTailsResult,
|
|
66
|
+
winner: address(0)
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// We make the result publicly decryptable.
|
|
70
|
+
FHE.makePubliclyDecryptable(headsOrTailsResult);
|
|
71
|
+
|
|
72
|
+
// You can catch the event to get the gameId and the encryptedHasHeadsWon handle
|
|
73
|
+
// for further decryption requests, or create a view function.
|
|
74
|
+
emit GameCreated(
|
|
75
|
+
gameId,
|
|
76
|
+
headsPlayer,
|
|
77
|
+
tailsPlayer,
|
|
78
|
+
games[gameId].encryptedHasHeadsWon
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// @notice Returns the number of games created so far.
|
|
83
|
+
function getGamesCount() public view returns (uint256) {
|
|
84
|
+
return counter;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// @notice Returns the encrypted ebool handle that stores the game result.
|
|
88
|
+
function hasHeadsWon(uint256 gameId) public view returns (ebool) {
|
|
89
|
+
return games[gameId].encryptedHasHeadsWon;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// @notice Returns the address of the game winner.
|
|
93
|
+
function getWinner(uint256 gameId) public view returns (address) {
|
|
94
|
+
require(
|
|
95
|
+
games[gameId].winner != address(0),
|
|
96
|
+
"Game winner not yet revealed"
|
|
97
|
+
);
|
|
98
|
+
return games[gameId].winner;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// @notice Verifies the provided (decryption proof, ABI-encoded clear value) pair against the stored ciphertext,
|
|
102
|
+
/// @notice and then stores the winner of the game.
|
|
103
|
+
/// @dev gameId: The ID of the game to settle.
|
|
104
|
+
/// abiEncodedClearGameResult: The ABI-encoded clear value (bool) associated to the `decryptionProof`.
|
|
105
|
+
/// decryptionProof: The proof that validates the decryption.
|
|
106
|
+
function recordAndVerifyWinner(
|
|
107
|
+
uint256 gameId,
|
|
108
|
+
bytes memory abiEncodedClearGameResult,
|
|
109
|
+
bytes memory decryptionProof
|
|
110
|
+
) public {
|
|
111
|
+
require(
|
|
112
|
+
games[gameId].winner == address(0),
|
|
113
|
+
"Game winner already revealed"
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// 1. FHE Verification: Build the list of ciphertexts (handles) and verify the proof.
|
|
117
|
+
// The verification checks that 'abiEncodedClearGameResult' is the true decryption
|
|
118
|
+
// of the 'encryptedHasHeadsWon' handle using the provided 'decryptionProof'.
|
|
119
|
+
|
|
120
|
+
// Creating the list of handles in the right order! In this case the order does not matter since the proof
|
|
121
|
+
// only involves 1 single handle.
|
|
122
|
+
bytes32[] memory cts = new bytes32[](1);
|
|
123
|
+
cts[0] = FHE.toBytes32(games[gameId].encryptedHasHeadsWon);
|
|
124
|
+
|
|
125
|
+
// This FHE call reverts the transaction if the decryption proof is invalid.
|
|
126
|
+
FHE.checkSignatures(cts, abiEncodedClearGameResult, decryptionProof);
|
|
127
|
+
|
|
128
|
+
// 2. Decode the clear result and determine the winner's address.
|
|
129
|
+
// In this very specific case, the function argument `abiEncodedClearGameResult` could have been a simple
|
|
130
|
+
// `bool` instead of an abi-encoded bool. In this case, we should have compute abi.encode on-chain
|
|
131
|
+
bool decodedClearGameResult = abi.decode(
|
|
132
|
+
abiEncodedClearGameResult,
|
|
133
|
+
(bool)
|
|
134
|
+
);
|
|
135
|
+
address winner = decodedClearGameResult
|
|
136
|
+
? games[gameId].headsPlayer
|
|
137
|
+
: games[gameId].tailsPlayer;
|
|
138
|
+
|
|
139
|
+
// 3. Store the winner
|
|
140
|
+
games[gameId].winner = winner;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import {FHE, ebool, euint32, euint64} from "@fhevm/solidity/lib/FHE.sol";
|
|
5
|
+
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @notice Demonstrates user decryption of multiple encrypted values
|
|
9
|
+
*
|
|
10
|
+
* @dev Shows how to create encrypted values from plaintext and grant permissions
|
|
11
|
+
* for multiple values of different types (ebool, euint32, euint64).
|
|
12
|
+
*/
|
|
13
|
+
contract UserDecryptMultipleValues is ZamaEthereumConfig {
|
|
14
|
+
// 🔐 Multiple encrypted values of different types
|
|
15
|
+
ebool private _encryptedBool;
|
|
16
|
+
euint32 private _encryptedUint32;
|
|
17
|
+
euint64 private _encryptedUint64;
|
|
18
|
+
|
|
19
|
+
constructor() {}
|
|
20
|
+
|
|
21
|
+
/// @notice Initialize multiple encrypted values from plaintext
|
|
22
|
+
/// @dev Uses FHE.asEuintX() to create encrypted constants from plaintext
|
|
23
|
+
/// (The plaintext IS visible on-chain, but result is encrypted)
|
|
24
|
+
function initialize(bool a, uint32 b, uint64 c) external {
|
|
25
|
+
// Create encrypted values from plaintext constants
|
|
26
|
+
// FHE.asEbool(a) encrypts a boolean value
|
|
27
|
+
_encryptedBool = FHE.xor(FHE.asEbool(a), FHE.asEbool(false));
|
|
28
|
+
|
|
29
|
+
// FHE.asEuint32(b) + 1 creates an encrypted (b + 1)
|
|
30
|
+
_encryptedUint32 = FHE.add(FHE.asEuint32(b), FHE.asEuint32(1));
|
|
31
|
+
_encryptedUint64 = FHE.add(FHE.asEuint64(c), FHE.asEuint64(1));
|
|
32
|
+
|
|
33
|
+
// ⚠️ CRITICAL: Grant permissions for EACH value separately
|
|
34
|
+
// You cannot batch permission grants!
|
|
35
|
+
FHE.allowThis(_encryptedBool);
|
|
36
|
+
FHE.allowThis(_encryptedUint32);
|
|
37
|
+
FHE.allowThis(_encryptedUint64);
|
|
38
|
+
|
|
39
|
+
FHE.allow(_encryptedBool, msg.sender);
|
|
40
|
+
FHE.allow(_encryptedUint32, msg.sender);
|
|
41
|
+
FHE.allow(_encryptedUint64, msg.sender);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// @notice Returns the encrypted boolean value
|
|
45
|
+
/// @dev Client decrypts with: fhevm.userDecrypt(FhevmType.ebool, handle, ...)
|
|
46
|
+
function encryptedBool() public view returns (ebool) {
|
|
47
|
+
return _encryptedBool;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// @notice Returns the encrypted uint32 value
|
|
51
|
+
/// @dev Client decrypts with: fhevm.userDecrypt(FhevmType.euint32, handle, ...)
|
|
52
|
+
function encryptedUint32() public view returns (euint32) {
|
|
53
|
+
return _encryptedUint32;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// @notice Returns the encrypted uint64 value
|
|
57
|
+
/// @dev Client decrypts with: fhevm.userDecrypt(FhevmType.euint64, handle, ...)
|
|
58
|
+
function encryptedUint64() public view returns (euint64) {
|
|
59
|
+
return _encryptedUint64;
|
|
60
|
+
}
|
|
61
|
+
}
|