create-fhevm-example 1.3.2 → 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 (125) 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 +14 -33
  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 +11 -8
  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/help.d.ts +0 -9
  115. package/dist/scripts/help.d.ts.map +0 -1
  116. package/dist/scripts/help.js +0 -73
  117. package/dist/scripts/maintenance.d.ts.map +0 -1
  118. package/dist/scripts/ui.d.ts.map +0 -1
  119. package/dist/scripts/utils.d.ts.map +0 -1
  120. /package/dist/scripts/{add-mode.d.ts → commands/add-mode.d.ts} +0 -0
  121. /package/dist/scripts/{doctor.d.ts → commands/doctor.d.ts} +0 -0
  122. /package/dist/scripts/{generate-config.d.ts → commands/generate-config.d.ts} +0 -0
  123. /package/dist/scripts/{generate-docs.d.ts → commands/generate-docs.d.ts} +0 -0
  124. /package/dist/scripts/{maintenance.d.ts → commands/maintenance.d.ts} +0 -0
  125. /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
+ }