create-fhevm-example 1.4.3 → 1.4.5

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 (32) hide show
  1. package/contracts/advanced/BlindAuction.sol +22 -57
  2. package/contracts/advanced/EncryptedEscrow.sol +7 -31
  3. package/contracts/advanced/HiddenVoting.sol +19 -54
  4. package/contracts/advanced/PrivateKYC.sol +9 -33
  5. package/contracts/advanced/PrivatePayroll.sol +9 -35
  6. package/contracts/basic/decryption/PublicDecryptMultipleValues.sol +19 -21
  7. package/contracts/basic/decryption/PublicDecryptSingleValue.sol +19 -20
  8. package/contracts/basic/decryption/UserDecryptMultipleValues.sol +11 -21
  9. package/contracts/basic/decryption/UserDecryptSingleValue.sol +15 -30
  10. package/contracts/basic/encryption/EncryptMultipleValues.sol +16 -22
  11. package/contracts/basic/encryption/EncryptSingleValue.sol +15 -17
  12. package/contracts/basic/encryption/FHECounter.sol +19 -17
  13. package/contracts/basic/fhe-operations/FHEAdd.sol +14 -16
  14. package/contracts/basic/fhe-operations/FHEArithmetic.sol +19 -31
  15. package/contracts/basic/fhe-operations/FHEComparison.sol +12 -29
  16. package/contracts/basic/fhe-operations/FHEIfThenElse.sol +9 -10
  17. package/contracts/concepts/FHEAccessControl.sol +28 -28
  18. package/contracts/concepts/FHEAntiPatterns.sol +8 -37
  19. package/contracts/concepts/FHEHandles.sol +14 -29
  20. package/contracts/concepts/FHEInputProof.sol +13 -33
  21. package/contracts/gaming/EncryptedLottery.sol +11 -38
  22. package/contracts/gaming/EncryptedPoker.sol +8 -34
  23. package/contracts/gaming/RockPaperScissors.sol +43 -64
  24. package/contracts/openzeppelin/ERC7984.sol +6 -1
  25. package/contracts/openzeppelin/ERC7984ERC20Wrapper.sol +5 -1
  26. package/contracts/openzeppelin/SwapERC7984ToERC20.sol +5 -1
  27. package/contracts/openzeppelin/SwapERC7984ToERC7984.sol +5 -1
  28. package/contracts/openzeppelin/VestingWallet.sol +13 -17
  29. package/dist/scripts/commands/generate-config.js +18 -3
  30. package/dist/scripts/shared/config.d.ts.map +1 -1
  31. package/dist/scripts/shared/config.js +81 -75
  32. package/package.json +1 -1
@@ -10,26 +10,16 @@ import {
10
10
  import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
11
11
 
12
12
  /**
13
- * @notice Private Payroll system - salaries stay encrypted, only employees see their own!
13
+ * @notice Confidential payroll system with encrypted salaries.
14
+ * Employers can add employees with encrypted salary amounts. Each employee
15
+ * can decrypt only their own salary - other salaries remain hidden.
16
+ * Demonstrates selective decryption permissions where different users
17
+ * see different encrypted values. Perfect for privacy-preserving HR systems.
14
18
  *
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
19
+ * @dev Flow: addEmployee() fund() → processPayment()
20
+ * Each employee can decrypt only their own salary.
28
21
  */
29
22
  contract PrivatePayroll is ZamaEthereumConfig {
30
- // ==================== STATE ====================
31
-
32
- /// Contract owner (employer)
33
23
  address public employer;
34
24
 
35
25
  /// List of employee addresses
@@ -50,9 +40,7 @@ contract PrivatePayroll is ZamaEthereumConfig {
50
40
  /// Payment period in seconds (default: 30 days)
51
41
  uint256 public paymentPeriod;
52
42
 
53
- // ==================== EVENTS ====================
54
-
55
- /// @notice Emitted when a new employee is added
43
+ /// Emitted when a new employee is added
56
44
  /// @param employee Address of the employee
57
45
  event EmployeeAdded(address indexed employee);
58
46
 
@@ -73,8 +61,6 @@ contract PrivatePayroll is ZamaEthereumConfig {
73
61
  /// @param amount Amount funded
74
62
  event ContractFunded(uint256 amount);
75
63
 
76
- // ==================== MODIFIERS ====================
77
-
78
64
  modifier onlyEmployer() {
79
65
  require(msg.sender == employer, "Only employer");
80
66
  _;
@@ -85,10 +71,6 @@ contract PrivatePayroll is ZamaEthereumConfig {
85
71
  _;
86
72
  }
87
73
 
88
- // ==================== CONSTRUCTOR ====================
89
-
90
- /// @notice Creates a new private payroll contract
91
- /// @param _paymentPeriod Time between payments in seconds
92
74
  constructor(uint256 _paymentPeriod) {
93
75
  employer = msg.sender;
94
76
  paymentPeriod = _paymentPeriod > 0 ? _paymentPeriod : 30 days;
@@ -96,8 +78,6 @@ contract PrivatePayroll is ZamaEthereumConfig {
96
78
  FHE.allowThis(_totalSalaries);
97
79
  }
98
80
 
99
- // ==================== EMPLOYEE MANAGEMENT ====================
100
-
101
81
  /// @notice Add a new employee with encrypted salary
102
82
  /// @param employee Address of the employee
103
83
  /// @param encryptedSalary Encrypted salary amount
@@ -190,8 +170,6 @@ contract PrivatePayroll is ZamaEthereumConfig {
190
170
  emit EmployeeRemoved(employee);
191
171
  }
192
172
 
193
- // ==================== PAYMENTS ====================
194
-
195
173
  /// @notice Fund the contract for payroll
196
174
  function fund() external payable onlyEmployer {
197
175
  require(msg.value > 0, "Must send funds");
@@ -231,9 +209,7 @@ contract PrivatePayroll is ZamaEthereumConfig {
231
209
  emit PaymentProcessed(employee, block.timestamp);
232
210
  }
233
211
 
234
- // ==================== VIEW FUNCTIONS ====================
235
-
236
- /// @notice Get encrypted salary handle for an employee
212
+ /// @notice Get encrypted salary handle for employee
237
213
  /// @dev Only callable by the employee themselves
238
214
  function getMySalary() external view onlyEmployee returns (euint64) {
239
215
  return _salaries[msg.sender];
@@ -276,8 +252,6 @@ contract PrivatePayroll is ZamaEthereumConfig {
276
252
  return (employees.length, address(this).balance, paymentPeriod);
277
253
  }
278
254
 
279
- // ==================== RECEIVE ====================
280
-
281
255
  /// @notice Accept ETH deposits
282
256
  receive() external payable {
283
257
  emit ContractFunded(msg.value);
@@ -5,14 +5,16 @@ import {FHE, euint8} from "@fhevm/solidity/lib/FHE.sol";
5
5
  import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
6
6
 
7
7
  /**
8
- * @notice Implements a simple 8-sided Die Roll game demonstrating public, permissionless decryption
8
+ * @notice Highest die roll game with public decryption of multiple values.
9
+ * Two players roll encrypted 8-sided dice, results are made publicly
10
+ * decryptable. Demonstrates handling multiple encrypted values in
11
+ * checkSignatures() where ORDER MATTERS - the cts[] array must match
12
+ * the order of values in the ABI-encoded result.
9
13
  *
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().
14
+ * @dev Uses FHE.randEuint8() + FHE.makePubliclyDecryptable() for both dice rolls.
15
+ * ⚠️ Order matters in cts[] array for checkSignatures!
12
16
  */
13
17
  contract HighestDieRoll is ZamaEthereumConfig {
14
- constructor() {}
15
-
16
18
  // Simple counter to assign a unique ID to each new game.
17
19
  uint256 private counter = 0;
18
20
 
@@ -31,7 +33,8 @@ contract HighestDieRoll is ZamaEthereumConfig {
31
33
  // Mapping to store all game states, accessible by a unique game ID.
32
34
  mapping(uint256 gameId => Game game) public games;
33
35
 
34
- /// @notice Emitted when a new game is started, providing the encrypted handle required for decryption
36
+ /// @notice Emitted when a new game is started,
37
+ /// providing the encrypted handle required for decryption
35
38
  /// @param gameId The unique identifier for the game
36
39
  /// @param playerA The address of playerA
37
40
  /// @param playerB The address of playerB
@@ -68,7 +71,8 @@ contract HighestDieRoll is ZamaEthereumConfig {
68
71
  revealed: false
69
72
  });
70
73
 
71
- // We make the results publicly decryptable.
74
+ // 🌐 Both values marked for public decryption
75
+ // Anyone can decrypt with valid KMS proof
72
76
  FHE.makePubliclyDecryptable(playerAEncryptedDieRoll);
73
77
  FHE.makePubliclyDecryptable(playerBEncryptedDieRoll);
74
78
 
@@ -98,7 +102,8 @@ contract HighestDieRoll is ZamaEthereumConfig {
98
102
  return games[gameId].playerBEncryptedDieRoll;
99
103
  }
100
104
 
101
- /// @notice Returns the address of the game winner. If the game is finalized, the function returns `address(0)`
105
+ /// @notice Returns the address of the game winner.
106
+ /// If the game is finalized, the function returns `address(0)`
102
107
  /// @notice if the game is a draw.
103
108
  function getWinner(uint256 gameId) public view returns (address) {
104
109
  require(games[gameId].revealed, "Game winner not yet revealed");
@@ -110,8 +115,8 @@ contract HighestDieRoll is ZamaEthereumConfig {
110
115
  return games[gameId].revealed;
111
116
  }
112
117
 
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.
118
+ /// @notice Verifies the provided (decryption proof, ABI-encoded clear values)
119
+ /// pair against the stored ciphertext, and then stores the winner of the game.
115
120
  function recordAndVerifyWinner(
116
121
  uint256 gameId,
117
122
  bytes memory abiEncodedClearGameResult,
@@ -119,13 +124,7 @@ contract HighestDieRoll is ZamaEthereumConfig {
119
124
  ) public {
120
125
  require(!games[gameId].revealed, "Game already revealed");
121
126
 
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.
127
+ // Verify KMS proof - ORDER MATTERS!
129
128
  bytes32[] memory cts = new bytes32[](2);
130
129
  cts[0] = FHE.toBytes32(games[gameId].playerAEncryptedDieRoll);
131
130
  cts[1] = FHE.toBytes32(games[gameId].playerBEncryptedDieRoll);
@@ -133,9 +132,8 @@ contract HighestDieRoll is ZamaEthereumConfig {
133
132
  // This FHE call reverts the transaction if the decryption proof is invalid.
134
133
  FHE.checkSignatures(cts, abiEncodedClearGameResult, decryptionProof);
135
134
 
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
135
+ // Decode both decrypted die rolls
136
+ // Note: Using abi.decode here, but could also accept two uint8 parameters
139
137
  (
140
138
  uint8 decodedClearPlayerADieRoll,
141
139
  uint8 decodedClearPlayerBDieRoll
@@ -153,7 +151,7 @@ contract HighestDieRoll is ZamaEthereumConfig {
153
151
  : address(0)
154
152
  );
155
153
 
156
- // 3. Store the revealed flag
154
+ // Store game result
157
155
  games[gameId].revealed = true;
158
156
  games[gameId].winner = winner;
159
157
  }
@@ -5,14 +5,16 @@ import {FHE, ebool} from "@fhevm/solidity/lib/FHE.sol";
5
5
  import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
6
6
 
7
7
  /**
8
- * @notice Implements a simple Heads or Tails game demonstrating public, permissionless decryption
8
+ * @notice Heads or Tails game with public, permissionless decryption.
9
+ * Demonstrates makePubliclyDecryptable() which allows ANYONE to decrypt
10
+ * the result (not just allowed users). Perfect for public game results,
11
+ * lottery winners, or voting tallies. Uses FHE.randEbool() for fair
12
+ * randomness and KMS-verified decryption proofs.
9
13
  *
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().
14
+ * @dev Uses FHE.randEbool() for random result + FHE.makePubliclyDecryptable() for revealing.
15
+ * Anyone can decrypt results with valid KMS proof.
12
16
  */
13
17
  contract HeadsOrTails is ZamaEthereumConfig {
14
- constructor() {}
15
-
16
18
  /// Simple counter to assign a unique ID to each new game.
17
19
  uint256 private counter = 0;
18
20
 
@@ -29,7 +31,8 @@ contract HeadsOrTails is ZamaEthereumConfig {
29
31
  // Mapping to store all game states, accessible by a unique game ID.
30
32
  mapping(uint256 gameId => Game game) public games;
31
33
 
32
- /// @notice Emitted when a new game is started, providing the encrypted handle required for decryption
34
+ /// @notice Emitted when a new game is started,
35
+ /// providing the encrypted handle required for decryption
33
36
  /// @param gameId The unique identifier for the game
34
37
  /// @param headsPlayer The address choosing Heads
35
38
  /// @param tailsPlayer The address choosing Tails
@@ -66,7 +69,8 @@ contract HeadsOrTails is ZamaEthereumConfig {
66
69
  winner: address(0)
67
70
  });
68
71
 
69
- // We make the result publicly decryptable.
72
+ // 🌐 Why makePubliclyDecryptable? Allows ANYONE to decrypt (not just allowed users)
73
+ // Use case: Public game results, lottery winners, voting tallies
70
74
  FHE.makePubliclyDecryptable(headsOrTailsResult);
71
75
 
72
76
  // You can catch the event to get the gameId and the encryptedHasHeadsWon handle
@@ -98,10 +102,11 @@ contract HeadsOrTails is ZamaEthereumConfig {
98
102
  return games[gameId].winner;
99
103
  }
100
104
 
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.
105
+ /// @notice Verifies the provided (decryption proof, ABI-encoded clear value)
106
+ /// pair against the stored ciphertext, and then stores the winner of the game.
103
107
  /// @dev gameId: The ID of the game to settle.
104
- /// abiEncodedClearGameResult: The ABI-encoded clear value (bool) associated to the `decryptionProof`.
108
+ /// abiEncodedClearGameResult: The ABI-encoded clear value (bool)
109
+ /// associated to the `decryptionProof`.
105
110
  /// decryptionProof: The proof that validates the decryption.
106
111
  function recordAndVerifyWinner(
107
112
  uint256 gameId,
@@ -113,21 +118,15 @@ contract HeadsOrTails is ZamaEthereumConfig {
113
118
  "Game winner already revealed"
114
119
  );
115
120
 
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.
121
+ // Verify KMS decryption proof
122
122
  bytes32[] memory cts = new bytes32[](1);
123
123
  cts[0] = FHE.toBytes32(games[gameId].encryptedHasHeadsWon);
124
124
 
125
125
  // This FHE call reverts the transaction if the decryption proof is invalid.
126
126
  FHE.checkSignatures(cts, abiEncodedClearGameResult, decryptionProof);
127
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
128
+ // Decode the decrypted result to determine winner
129
+ // Note: Using abi.decode here, but could also accept plain bool parameter
131
130
  bool decodedClearGameResult = abi.decode(
132
131
  abiEncodedClearGameResult,
133
132
  (bool)
@@ -136,7 +135,7 @@ contract HeadsOrTails is ZamaEthereumConfig {
136
135
  ? games[gameId].headsPlayer
137
136
  : games[gameId].tailsPlayer;
138
137
 
139
- // 3. Store the winner
138
+ // Store the winner
140
139
  games[gameId].winner = winner;
141
140
  }
142
141
  }
@@ -5,33 +5,29 @@ import {FHE, ebool, euint32, euint64} from "@fhevm/solidity/lib/FHE.sol";
5
5
  import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
6
6
 
7
7
  /**
8
- * @notice Demonstrates user decryption of multiple encrypted values
8
+ * @notice Decrypting multiple encrypted values of different types for a user.
9
+ * Shows how to handle ebool, euint32, and euint64 in one contract.
10
+ * Each value requires individual permission grants - there's no batching
11
+ * for permissions (unlike input proofs). Demonstrates the pattern of
12
+ * granting allowThis() for each value separately.
9
13
  *
10
- * @dev Shows how to create encrypted values from plaintext and grant permissions
11
- * for multiple values of different types (ebool, euint32, euint64).
14
+ * @dev Each value needs separate permission grants (no batching).
15
+ * ⚠️ Cannot batch permission grants - must call allow() for each value!
12
16
  */
13
17
  contract UserDecryptMultipleValues is ZamaEthereumConfig {
14
- // 🔐 Multiple encrypted values of different types
15
18
  ebool private _encryptedBool;
16
19
  euint32 private _encryptedUint32;
17
20
  euint64 private _encryptedUint64;
18
21
 
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)
22
+ /// @notice Initialize multiple values from plaintext
23
+ /// @dev FHE.asEuintX() creates encrypted constants from plaintext
24
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
25
+ // Create encrypted values from plaintext
27
26
  _encryptedBool = FHE.xor(FHE.asEbool(a), FHE.asEbool(false));
28
-
29
- // FHE.asEuint32(b) + 1 creates an encrypted (b + 1)
30
27
  _encryptedUint32 = FHE.add(FHE.asEuint32(b), FHE.asEuint32(1));
31
28
  _encryptedUint64 = FHE.add(FHE.asEuint64(c), FHE.asEuint64(1));
32
29
 
33
- // ⚠️ CRITICAL: Grant permissions for EACH value separately
34
- // You cannot batch permission grants!
30
+ // ⚠️ Why separate? No batching for permissions - each needs individual allow()
35
31
  FHE.allowThis(_encryptedBool);
36
32
  FHE.allowThis(_encryptedUint32);
37
33
  FHE.allowThis(_encryptedUint64);
@@ -41,20 +37,14 @@ contract UserDecryptMultipleValues is ZamaEthereumConfig {
41
37
  FHE.allow(_encryptedUint64, msg.sender);
42
38
  }
43
39
 
44
- /// @notice Returns the encrypted boolean value
45
- /// @dev Client decrypts with: fhevm.userDecrypt(FhevmType.ebool, handle, ...)
46
40
  function encryptedBool() public view returns (ebool) {
47
41
  return _encryptedBool;
48
42
  }
49
43
 
50
- /// @notice Returns the encrypted uint32 value
51
- /// @dev Client decrypts with: fhevm.userDecrypt(FhevmType.euint32, handle, ...)
52
44
  function encryptedUint32() public view returns (euint32) {
53
45
  return _encryptedUint32;
54
46
  }
55
47
 
56
- /// @notice Returns the encrypted uint64 value
57
- /// @dev Client decrypts with: fhevm.userDecrypt(FhevmType.euint64, handle, ...)
58
48
  function encryptedUint64() public view returns (euint64) {
59
49
  return _encryptedUint64;
60
50
  }
@@ -5,54 +5,39 @@ import {FHE, euint32} from "@fhevm/solidity/lib/FHE.sol";
5
5
  import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
6
6
 
7
7
  /**
8
- * @notice Demonstrates the FHE decryption mechanism and highlights common pitfalls
8
+ * @notice User-controlled decryption with proper permission management.
9
+ * Demonstrates the critical two-step permission pattern: allowThis()
10
+ * grants the contract permission to store/compute, while allow() grants
11
+ * the user permission to decrypt. Missing either step causes decryption
12
+ * to fail. Includes examples of both correct and incorrect patterns.
9
13
  *
10
- * @dev This trivial example shows correct vs incorrect permission granting patterns.
11
- * Emphasizes that BOTH FHE.allowThis() and FHE.allow(user) are required for user decryption.
14
+ * @dev Shows CORRECT vs INCORRECT permission granting.
15
+ * ⚠️ Both allowThis + allow required for user decryption!
12
16
  */
13
17
  contract UserDecryptSingleValue is ZamaEthereumConfig {
14
18
  euint32 private _trivialEuint32;
15
19
 
16
- // solhint-disable-next-line no-empty-blocks
17
- constructor() {}
18
-
19
- /// @notice Initialize with a trivial formula (value + 1)
20
- /// @dev Demonstrates correct permission granting
20
+ /// @notice ✅ CORRECT: Proper permission pattern
21
21
  function initializeUint32(uint32 value) external {
22
- // Compute a trivial FHE formula _trivialEuint32 = value + 1
23
22
  _trivialEuint32 = FHE.add(FHE.asEuint32(value), FHE.asEuint32(1));
24
23
 
25
- // Grant FHE permissions to:
26
- // The contract caller (`msg.sender`): allows them to decrypt `_trivialEuint32`.
27
- // The contract itself (`address(this)`): allows it to operate on `_trivialEuint32` and
28
- // also enables the caller to perform user decryption.
29
- //
30
- // Note: If you forget to call `FHE.allowThis(_trivialEuint32)`, the user will NOT be able
31
- // to user decrypt the value! Both the contract and the caller must have FHE permissions
32
- // for user decryption to succeed.
24
+ // 🔑 Why both needed?
25
+ // - allowThis: Contract authorizes releasing the value
26
+ // - allow: User can request decryption
33
27
  FHE.allowThis(_trivialEuint32);
34
28
  FHE.allow(_trivialEuint32, msg.sender);
35
29
  }
36
30
 
37
- /// @notice Demonstrate INCORRECT permission granting (Anti-pattern)
38
- /// @dev Missing FHE.allowThis() causes user decryption to fail
31
+ /// @notice WRONG: Missing allowThis causes decryption to FAIL!
32
+ /// @dev Common mistake - user gets permission but decryption still fails
39
33
  function initializeUint32Wrong(uint32 value) external {
40
- // Compute a trivial FHE formula _trivialEuint32 = value + 1
41
34
  _trivialEuint32 = FHE.add(FHE.asEuint32(value), FHE.asEuint32(1));
42
35
 
43
- // ❌ Common FHE permission mistake:
44
- // ================================================================
45
- // We grant FHE permissions to the contract caller (`msg.sender`),
46
- // expecting they will be able to user decrypt the encrypted value later.
47
- //
48
- // However, this will fail! 💥
49
- // The contract itself (`address(this)`) also needs FHE permissions to allow user decryption.
50
- // Without granting the contract access using `FHE.allowThis(...)`,
51
- // the user decryption attempt by the user will not succeed.
36
+ // ❌ Missing allowThis user can't decrypt!
37
+ // Why? Decryption needs contract authorization to release
52
38
  FHE.allow(_trivialEuint32, msg.sender);
53
39
  }
54
40
 
55
- /// @notice Returns the encrypted uint32 value
56
41
  function encryptedUint32() public view returns (euint32) {
57
42
  return _trivialEuint32;
58
43
  }
@@ -13,41 +13,35 @@ import {
13
13
  import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
14
14
 
15
15
  /**
16
- * @notice Encrypting and handling multiple values in a single transaction efficiently.
16
+ * @notice Efficient handling of multiple encrypted values in one transaction.
17
+ * Demonstrates batched input validation where a single proof covers
18
+ * multiple encrypted values (ebool, euint32, eaddress), saving ~50k gas
19
+ * per additional value compared to separate proofs.
17
20
  *
18
- * @dev Supported encrypted types:
19
- * - euint8/16/32/64/128/256: encrypted integers
20
- * - ebool: encrypted boolean
21
- * - eaddress: encrypted address
22
- * - ebytes64/128/256: encrypted bytes
21
+ * @dev Demonstrates batched input (ONE proof for multiple values).
22
+ * Gas: Batching saves ~50k gas vs separate proofs!
23
23
  */
24
24
  contract EncryptMultipleValues is ZamaEthereumConfig {
25
- // 🔐 Three different encrypted types stored together
26
- ebool private _encryptedEbool; // e.g., vote, permission flag
27
- euint32 private _encryptedEuint32; // e.g., amount, balance
28
- eaddress private _encryptedEaddress; // e.g., hidden recipient
25
+ ebool private _encryptedEbool;
26
+ euint32 private _encryptedEuint32;
27
+ eaddress private _encryptedEaddress;
29
28
 
30
- constructor() {}
31
-
32
- /// @notice Store multiple encrypted values from a single batched input
33
- /// @dev Client-side batching example:
34
- /// const input = await fhevm.createEncryptedInput(contractAddr, userAddr)
35
- /// .addBool(true).add32(123).addAddress(addr).encrypt();
36
- /// // This creates ONE proof for ALL values - more gas efficient!
29
+ /// @notice Store multiple encrypted values from single batched input
30
+ /// @dev Client creates ONE proof for ALL values using createEncryptedInput().
31
+ /// Much cheaper than separate encrypt() calls!
37
32
  function initialize(
38
33
  externalEbool inputEbool,
39
34
  externalEuint32 inputEuint32,
40
35
  externalEaddress inputEaddress,
41
- bytes calldata inputProof // Single proof covers all values
36
+ bytes calldata inputProof // Single proof covers all!
42
37
  ) external {
43
- // Convert each external input to internal handle
38
+ // 💡 Why one proof? Client batches all values before encrypt()
39
+ // Saves ~50k gas per additional value!
44
40
  _encryptedEbool = FHE.fromExternal(inputEbool, inputProof);
45
41
  _encryptedEuint32 = FHE.fromExternal(inputEuint32, inputProof);
46
42
  _encryptedEaddress = FHE.fromExternal(inputEaddress, inputProof);
47
43
 
48
- // ⚠️ IMPORTANT: Each value needs its own permission grants!
49
- // You cannot batch permission grants
50
-
44
+ // 🔐 Each value needs own permissions (no batching here)
51
45
  FHE.allowThis(_encryptedEbool);
52
46
  FHE.allow(_encryptedEbool, msg.sender);
53
47
 
@@ -5,39 +5,37 @@ import {FHE, externalEuint32, euint32} from "@fhevm/solidity/lib/FHE.sol";
5
5
  import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
6
6
 
7
7
  /**
8
- * @notice FHE encryption mechanism with single values, including common pitfalls and best practices for developers.
9
- *
8
+ * @notice Single value encryption with proof validation.
9
+ * Shows how to receive encrypted data from users, validate proofs,
10
+ * and grant proper permissions. Includes examples of common mistakes
11
+ * and the correct permission pattern (allowThis + allow).
12
+
10
13
  * @dev Shows the complete flow: receiving encrypted input from user, validating proof,
11
14
  * storing the encrypted value, and granting permissions for decryption.
12
15
  */
13
16
  contract EncryptSingleValue is ZamaEthereumConfig {
14
- // 🔐 Stored encrypted - only authorized users can decrypt
15
17
  euint32 private _encryptedEuint32;
16
18
 
17
- constructor() {}
18
-
19
19
  /// @notice Store an encrypted value submitted by the user
20
- /// @dev inputEuint32: Encrypted value (created client-side with fhevm.createEncryptedInput())
21
- /// inputProof: Zero-knowledge proof that the encryption is valid
20
+ /// @dev inputProof ensures: value encrypted for THIS contract + THIS user.
21
+ /// Prevents replay attacks from other contracts/users.
22
22
  function initialize(
23
23
  externalEuint32 inputEuint32,
24
24
  bytes calldata inputProof
25
25
  ) external {
26
- // Convert external input to internal handle
27
- // 📋 The proof ensures:
28
- // - Value was encrypted for THIS contract address
29
- // - Value was encrypted by THIS user (msg.sender)
30
- // - Prevents replay attacks from other contracts/users
26
+ // 🔐 Why proof?
27
+ // Prevents: replay attacks, wrong contract, invalid ciphertext
31
28
  _encryptedEuint32 = FHE.fromExternal(inputEuint32, inputProof);
32
29
 
33
- // ⚠️ CRITICAL: Grant permissions for future decryption
34
- // Without BOTH of these, user decryption will fail!
35
- FHE.allowThis(_encryptedEuint32); // Contract can operate on it
36
- FHE.allow(_encryptedEuint32, msg.sender); // User can decrypt it
30
+ // 🔑 Why both?
31
+ // - allowThis: Contract can store/compute with it
32
+ // - allow(user): User can decrypt it
33
+ FHE.allowThis(_encryptedEuint32);
34
+ FHE.allow(_encryptedEuint32, msg.sender);
37
35
  }
38
36
 
39
37
  /// @notice Returns the encrypted handle (not the actual value!)
40
- /// @dev To decrypt, use fhevm.userDecryptEuint() on the client side
38
+ /// @dev To decrypt, use fhevm.userDecryptEuint32() on client side
41
39
  function encryptedUint32() public view returns (euint32) {
42
40
  return _encryptedEuint32;
43
41
  }
@@ -5,47 +5,49 @@ import {FHE, euint32, externalEuint32} from "@fhevm/solidity/lib/FHE.sol";
5
5
  import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
6
6
 
7
7
  /**
8
- * @notice Confidential counter implementation using FHEVM, compared with a standard counter to highlight encryption benefits.
9
- *
10
- * @dev Demonstrates basic FHE operations: encryption, computation, and permission management.
11
- * Shows how to work with encrypted values without ever revealing the underlying data.
8
+ * @notice Confidential counter with encrypted increment/decrement operations.
9
+ * Demonstrates the complete FHE workflow: encryption, computation,
10
+ * and permission management. The counter value remains private,
11
+ * only accessible through decryption by authorized users.
12
+
13
+ * @dev Demonstrates basic FHE workflow: fromExternal() → compute → allow permissions.
14
+ * All arithmetic happens on encrypted values without revealing the count.
12
15
  */
13
16
  contract FHECounter is ZamaEthereumConfig {
14
- // 🔐 The count is always encrypted - no one can see the actual value
15
17
  euint32 private _count;
16
18
 
17
- /// @notice Returns the encrypted count handle (not the actual value!)
18
19
  function getCount() external view returns (euint32) {
19
20
  return _count;
20
21
  }
21
22
 
22
- /// @notice Increments the counter by an encrypted value
23
+ /// @notice Increments counter by encrypted amount
24
+ /// @dev Why allowThis + allow? Contract needs permission to store,
25
+ /// user needs permission to decrypt. Both required!
23
26
  function increment(
24
27
  externalEuint32 inputEuint32,
25
28
  bytes calldata inputProof
26
29
  ) external {
27
- // Convert external encrypted input to internal handle
28
- // The proof is verified automatically
30
+ // 🔐 Why proof? Ensures valid ciphertext encrypted for THIS contract
29
31
  euint32 encryptedValue = FHE.fromExternal(inputEuint32, inputProof);
30
32
 
31
- // Add encrypted values - computation happens on ciphertexts
32
- // Neither the contract nor anyone else sees the actual numbers
33
+ // 🧮 Homomorphic add: works on encrypted data
33
34
  _count = FHE.add(_count, encryptedValue);
34
35
 
35
- // ⚠️ CRITICAL: Both permissions required for user decryption!
36
- FHE.allowThis(_count); // Contract can continue using this value
37
- FHE.allow(_count, msg.sender); // Caller can decrypt it
36
+ // 🔑 Both needed: allowThis = contract stores, allow = user decrypts
37
+ FHE.allowThis(_count);
38
+ FHE.allow(_count, msg.sender);
38
39
  }
39
40
 
40
- /// @notice Decrements the counter by an encrypted value
41
+ /// @notice Decrements counter by encrypted amount
42
+ /// @dev ⚠️ No underflow protection! FHE.sub wraps around at 0.
43
+ /// ❌ WRONG: Checking result < 0 reveals information
44
+ /// ✅ CORRECT: Use application-level balance tracking or FHE.select()
41
45
  function decrement(
42
46
  externalEuint32 inputEuint32,
43
47
  bytes calldata inputProof
44
48
  ) external {
45
49
  euint32 encryptedValue = FHE.fromExternal(inputEuint32, inputProof);
46
50
 
47
- // Subtract encrypted values
48
- // ⚠️ No underflow protection here - add checks in production!
49
51
  _count = FHE.sub(_count, encryptedValue);
50
52
 
51
53
  FHE.allowThis(_count);