create-fhevm-example 1.4.4 → 1.4.6
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/README.md +3 -4
- package/contracts/advanced/BlindAuction.sol +5 -1
- package/contracts/advanced/EncryptedEscrow.sol +5 -1
- package/contracts/advanced/HiddenVoting.sol +5 -1
- package/contracts/advanced/PrivateKYC.sol +6 -1
- package/contracts/advanced/PrivatePayroll.sol +5 -1
- package/contracts/basic/decryption/PublicDecryptMultipleValues.sol +11 -5
- package/contracts/basic/decryption/PublicDecryptSingleValue.sol +11 -5
- package/contracts/basic/decryption/UserDecryptMultipleValues.sol +5 -1
- package/contracts/basic/decryption/UserDecryptSingleValue.sol +5 -1
- package/contracts/basic/encryption/EncryptMultipleValues.sol +4 -1
- package/contracts/basic/encryption/EncryptSingleValue.sol +6 -2
- package/contracts/basic/encryption/FHECounter.sol +4 -1
- package/contracts/basic/fhe-operations/FHEAdd.sol +5 -1
- package/contracts/basic/fhe-operations/FHEArithmetic.sol +5 -1
- package/contracts/basic/fhe-operations/FHEComparison.sol +5 -1
- package/contracts/basic/fhe-operations/FHEIfThenElse.sol +5 -1
- package/contracts/concepts/FHEAccessControl.sol +8 -3
- package/contracts/concepts/FHEHandles.sol +5 -1
- package/contracts/concepts/FHEInputProof.sol +5 -1
- package/contracts/concepts/antipatterns/ControlFlow.sol +160 -0
- package/contracts/concepts/antipatterns/OperationsGasNoise.sol +190 -0
- package/contracts/concepts/antipatterns/Permissions.sol +254 -0
- package/contracts/gaming/EncryptedLottery.sol +5 -1
- package/contracts/gaming/EncryptedPoker.sol +5 -1
- package/contracts/gaming/RockPaperScissors.sol +5 -1
- package/contracts/openzeppelin/ERC7984.sol +6 -1
- package/contracts/openzeppelin/ERC7984ERC20Wrapper.sol +5 -1
- package/contracts/openzeppelin/SwapERC7984ToERC20.sol +5 -1
- package/contracts/openzeppelin/SwapERC7984ToERC7984.sol +5 -1
- package/contracts/openzeppelin/VestingWallet.sol +6 -1
- package/dist/scripts/commands/add-mode.d.ts.map +1 -1
- package/dist/scripts/commands/add-mode.js +10 -21
- package/dist/scripts/commands/doctor.js +4 -1
- package/dist/scripts/commands/generate-config.js +18 -3
- package/dist/scripts/commands/generate-docs.js +4 -1
- package/dist/scripts/index.js +43 -24
- package/dist/scripts/shared/builders.d.ts.map +1 -1
- package/dist/scripts/shared/builders.js +8 -14
- package/dist/scripts/shared/config.d.ts.map +1 -1
- package/dist/scripts/shared/config.js +119 -84
- package/dist/scripts/shared/utils.js +2 -2
- package/package.json +1 -1
- package/test/concepts/FHEAntiPatterns.ts +2 -1
- package/test/concepts/antipatterns/ControlFlow.ts +125 -0
- package/test/concepts/antipatterns/OperationsGasNoise.ts +187 -0
- package/test/concepts/antipatterns/Permissions.ts +327 -0
- package/contracts/concepts/FHEAntiPatterns.sol +0 -296
package/README.md
CHANGED
|
@@ -56,8 +56,8 @@ npx create-fhevm-example --category basic
|
|
|
56
56
|
npx create-fhevm-example --add
|
|
57
57
|
npx create-fhevm-example --add --target ./my-existing-project
|
|
58
58
|
|
|
59
|
-
# With auto-install
|
|
60
|
-
npx create-fhevm-example --example fhe-counter --output ./my-project --install
|
|
59
|
+
# With auto-install
|
|
60
|
+
npx create-fhevm-example --example fhe-counter --output ./my-project --install
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
---
|
|
@@ -72,7 +72,6 @@ npx create-fhevm-example --example fhe-counter --output ./my-project --install -
|
|
|
72
72
|
| `--target <dir>` | Target directory for `--add` mode (default: current dir) |
|
|
73
73
|
| `--output <dir>` | Output directory for new projects |
|
|
74
74
|
| `--install` | Auto-install dependencies after scaffolding |
|
|
75
|
-
| `--test` | Auto-run tests (requires `--install`) |
|
|
76
75
|
| `--help`, `-h` | Show help information |
|
|
77
76
|
|
|
78
77
|
---
|
|
@@ -85,7 +84,7 @@ npx create-fhevm-example --example fhe-counter --output ./my-project --install -
|
|
|
85
84
|
|
|
86
85
|
**FHE Operations** (4): `fhe-add`, `fhe-if-then-else`, `fhe-arithmetic`, `fhe-comparison`
|
|
87
86
|
|
|
88
|
-
**Concepts** (
|
|
87
|
+
**Concepts** (6): `fhe-access-control`, `fhe-input-proof`, `fhe-handles`, `control-flow`, `permissions`, `operations-gas-noise`
|
|
89
88
|
|
|
90
89
|
**Gaming** (3): `rock-paper-scissors`, `encrypted-lottery`, `encrypted-poker`
|
|
91
90
|
|
|
@@ -10,7 +10,11 @@ import {
|
|
|
10
10
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @notice Blind
|
|
13
|
+
* @notice Blind auction where bids remain encrypted until the end.
|
|
14
|
+
* Bidders submit encrypted amounts. The contract finds the highest bid
|
|
15
|
+
* using FHE.gt and FHE.select without ever decrypting losing bids.
|
|
16
|
+
* Only the winning bid amount is revealed after the auction closes.
|
|
17
|
+
* Losing bids remain private forever, ensuring true bid confidentiality.
|
|
14
18
|
|
|
15
19
|
* @dev Flow: bid() → endAuction() → revealWinner()
|
|
16
20
|
* Uses FHE.gt/select to find winner without revealing losing bids.
|
|
@@ -10,7 +10,11 @@ import {
|
|
|
10
10
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @notice
|
|
13
|
+
* @notice Confidential escrow service with hidden transaction amounts.
|
|
14
|
+
* Buyer and seller agree on encrypted escrow amount. Funds are held
|
|
15
|
+
* securely until conditions are met. Amount remains hidden from public
|
|
16
|
+
* view until release or refund. Includes arbiter for dispute resolution.
|
|
17
|
+
* Perfect for high-value transactions requiring privacy.
|
|
14
18
|
*
|
|
15
19
|
* @dev Flow: createEscrow() → fundEscrow() → release()/requestRefund()/raiseDispute()
|
|
16
20
|
* Multi-party agreement with arbiter for disputes.
|
|
@@ -5,7 +5,11 @@ import {FHE, euint64, ebool, externalEuint8} from "@fhevm/solidity/lib/FHE.sol";
|
|
|
5
5
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* @notice
|
|
8
|
+
* @notice Private voting system with homomorphic vote tallying.
|
|
9
|
+
* Voters submit encrypted ballots (Yes/No). Votes are tallied using
|
|
10
|
+
* homomorphic addition WITHOUT decrypting individual ballots. Only the
|
|
11
|
+
* final tally is revealed - individual votes remain private forever.
|
|
12
|
+
* Perfect for DAO governance, elections, or any scenario requiring ballot secrecy.
|
|
9
13
|
*
|
|
10
14
|
* @dev Flow: vote() → closeVoting() → revealResults()
|
|
11
15
|
* ⚡ Gas: Each vote costs ~200k gas (FHE.add + FHE.select operations)
|
|
@@ -12,7 +12,12 @@ import {
|
|
|
12
12
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* @notice
|
|
15
|
+
* @notice Privacy-preserving identity verification using predicate proofs.
|
|
16
|
+
* Users submit encrypted personal data (age, country, credit score).
|
|
17
|
+
* Contract can verify predicates like "Is user 18+?" or "Good credit?"
|
|
18
|
+
* without learning the actual values. Returns encrypted booleans that
|
|
19
|
+
* prove compliance without revealing sensitive information. Revolutionary
|
|
20
|
+
* for KYC/AML compliance while preserving user privacy.
|
|
16
21
|
*
|
|
17
22
|
* @dev Flow: submitKYC() → verifyAge18()/verifyGoodCredit()/etc.
|
|
18
23
|
* Returns encrypted booleans: "Is 18+?", "Good credit?" without revealing actual values.
|
|
@@ -10,7 +10,11 @@ import {
|
|
|
10
10
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @notice
|
|
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
19
|
* @dev Flow: addEmployee() → fund() → processPayment()
|
|
16
20
|
* Each employee can decrypt only their own salary.
|
|
@@ -5,7 +5,11 @@ 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
|
|
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
14
|
* @dev Uses FHE.randEuint8() + FHE.makePubliclyDecryptable() for both dice rolls.
|
|
11
15
|
* ⚠️ Order matters in cts[] array for checkSignatures!
|
|
@@ -29,7 +33,8 @@ contract HighestDieRoll is ZamaEthereumConfig {
|
|
|
29
33
|
// Mapping to store all game states, accessible by a unique game ID.
|
|
30
34
|
mapping(uint256 gameId => Game game) public games;
|
|
31
35
|
|
|
32
|
-
/// @notice Emitted when a new game is started,
|
|
36
|
+
/// @notice Emitted when a new game is started,
|
|
37
|
+
/// providing the encrypted handle required for decryption
|
|
33
38
|
/// @param gameId The unique identifier for the game
|
|
34
39
|
/// @param playerA The address of playerA
|
|
35
40
|
/// @param playerB The address of playerB
|
|
@@ -97,7 +102,8 @@ contract HighestDieRoll is ZamaEthereumConfig {
|
|
|
97
102
|
return games[gameId].playerBEncryptedDieRoll;
|
|
98
103
|
}
|
|
99
104
|
|
|
100
|
-
/// @notice Returns the address of the game winner.
|
|
105
|
+
/// @notice Returns the address of the game winner.
|
|
106
|
+
/// If the game is finalized, the function returns `address(0)`
|
|
101
107
|
/// @notice if the game is a draw.
|
|
102
108
|
function getWinner(uint256 gameId) public view returns (address) {
|
|
103
109
|
require(games[gameId].revealed, "Game winner not yet revealed");
|
|
@@ -109,8 +115,8 @@ contract HighestDieRoll is ZamaEthereumConfig {
|
|
|
109
115
|
return games[gameId].revealed;
|
|
110
116
|
}
|
|
111
117
|
|
|
112
|
-
/// @notice Verifies the provided (decryption proof, ABI-encoded clear values)
|
|
113
|
-
///
|
|
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.
|
|
114
120
|
function recordAndVerifyWinner(
|
|
115
121
|
uint256 gameId,
|
|
116
122
|
bytes memory abiEncodedClearGameResult,
|
|
@@ -5,7 +5,11 @@ 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
|
|
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
14
|
* @dev Uses FHE.randEbool() for random result + FHE.makePubliclyDecryptable() for revealing.
|
|
11
15
|
* Anyone can decrypt results with valid KMS proof.
|
|
@@ -27,7 +31,8 @@ contract HeadsOrTails is ZamaEthereumConfig {
|
|
|
27
31
|
// Mapping to store all game states, accessible by a unique game ID.
|
|
28
32
|
mapping(uint256 gameId => Game game) public games;
|
|
29
33
|
|
|
30
|
-
/// @notice Emitted when a new game is started,
|
|
34
|
+
/// @notice Emitted when a new game is started,
|
|
35
|
+
/// providing the encrypted handle required for decryption
|
|
31
36
|
/// @param gameId The unique identifier for the game
|
|
32
37
|
/// @param headsPlayer The address choosing Heads
|
|
33
38
|
/// @param tailsPlayer The address choosing Tails
|
|
@@ -97,10 +102,11 @@ contract HeadsOrTails is ZamaEthereumConfig {
|
|
|
97
102
|
return games[gameId].winner;
|
|
98
103
|
}
|
|
99
104
|
|
|
100
|
-
/// @notice Verifies the provided (decryption proof, ABI-encoded clear value)
|
|
101
|
-
///
|
|
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.
|
|
102
107
|
/// @dev gameId: The ID of the game to settle.
|
|
103
|
-
/// abiEncodedClearGameResult: The ABI-encoded clear value (bool)
|
|
108
|
+
/// abiEncodedClearGameResult: The ABI-encoded clear value (bool)
|
|
109
|
+
/// associated to the `decryptionProof`.
|
|
104
110
|
/// decryptionProof: The proof that validates the decryption.
|
|
105
111
|
function recordAndVerifyWinner(
|
|
106
112
|
uint256 gameId,
|
|
@@ -5,7 +5,11 @@ 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
|
|
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
14
|
* @dev Each value needs separate permission grants (no batching).
|
|
11
15
|
* ⚠️ Cannot batch permission grants - must call allow() for each value!
|
|
@@ -5,7 +5,11 @@ 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
|
|
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
14
|
* @dev Shows CORRECT vs INCORRECT permission granting.
|
|
11
15
|
* ⚠️ Both allowThis + allow required for user decryption!
|
|
@@ -13,7 +13,10 @@ import {
|
|
|
13
13
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* @notice
|
|
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
21
|
* @dev Demonstrates batched input (ONE proof for multiple values).
|
|
19
22
|
* ⚡ Gas: Batching saves ~50k gas vs separate proofs!
|
|
@@ -5,7 +5,10 @@ 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
|
|
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).
|
|
9
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.
|
|
@@ -20,7 +23,8 @@ contract EncryptSingleValue is ZamaEthereumConfig {
|
|
|
20
23
|
externalEuint32 inputEuint32,
|
|
21
24
|
bytes calldata inputProof
|
|
22
25
|
) external {
|
|
23
|
-
// 🔐 Why proof?
|
|
26
|
+
// 🔐 Why proof?
|
|
27
|
+
// Prevents: replay attacks, wrong contract, invalid ciphertext
|
|
24
28
|
_encryptedEuint32 = FHE.fromExternal(inputEuint32, inputProof);
|
|
25
29
|
|
|
26
30
|
// 🔑 Why both?
|
|
@@ -5,7 +5,10 @@ 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
|
|
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.
|
|
9
12
|
|
|
10
13
|
* @dev Demonstrates basic FHE workflow: fromExternal() → compute → allow permissions.
|
|
11
14
|
* All arithmetic happens on encrypted values without revealing the count.
|
|
@@ -5,7 +5,11 @@ import {FHE, euint8, externalEuint8} from "@fhevm/solidity/lib/FHE.sol";
|
|
|
5
5
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* @notice
|
|
8
|
+
* @notice Introduction to homomorphic addition on encrypted values.
|
|
9
|
+
* Demonstrates the most fundamental FHE operation: adding two encrypted
|
|
10
|
+
* numbers without decrypting them. Shows the complete flow from receiving
|
|
11
|
+
* encrypted inputs, performing the addition, and granting permissions
|
|
12
|
+
* for both contract storage and user decryption.
|
|
9
13
|
|
|
10
14
|
* @dev Shows the most basic FHE operation and permission flow.
|
|
11
15
|
* ⚡ Gas: FHE.add() costs ~100k gas (coprocessor call)
|
|
@@ -5,7 +5,11 @@ 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
|
|
8
|
+
* @notice Complete suite of FHE arithmetic operations on encrypted values.
|
|
9
|
+
* Covers all basic math: addition, subtraction, multiplication, division,
|
|
10
|
+
* remainder (modulo), minimum, and maximum. Includes gas cost comparisons
|
|
11
|
+
* and important limitations (e.g., division/remainder only work with
|
|
12
|
+
* plaintext divisors, not encrypted divisors).
|
|
9
13
|
*
|
|
10
14
|
* @dev ⚡ Gas costs vary: add/sub (~100k) < mul (~150k) < div/rem (~300k)
|
|
11
15
|
* ⚠️ div/rem only work with plaintext divisor (not encrypted!)
|
|
@@ -10,7 +10,11 @@ import {
|
|
|
10
10
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @notice
|
|
13
|
+
* @notice Complete guide to encrypted comparisons and conditional selection.
|
|
14
|
+
* Covers all comparison operators (eq, ne, gt, lt, ge, le) that return
|
|
15
|
+
* encrypted booleans (ebool), and demonstrates FHE.select for branching
|
|
16
|
+
* without information leakage. Critical for implementing logic like
|
|
17
|
+
* "find maximum" or "check threshold" without revealing values.
|
|
14
18
|
*
|
|
15
19
|
* @dev Results are encrypted booleans (ebool) - comparisons reveal nothing!
|
|
16
20
|
* ⚡ Gas: Comparisons ~100k, select ~120k
|
|
@@ -5,7 +5,11 @@ import {FHE, ebool, euint8, externalEuint8} from "@fhevm/solidity/lib/FHE.sol";
|
|
|
5
5
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* @notice
|
|
8
|
+
* @notice Conditional logic without information leakage using FHE.select.
|
|
9
|
+
* Demonstrates how to implement if-then-else logic on encrypted values
|
|
10
|
+
* (computing max of two numbers). Using regular if/else would decrypt
|
|
11
|
+
* and leak which branch was taken. FHE.select evaluates BOTH branches
|
|
12
|
+
* and picks one based on the encrypted condition, preserving privacy.
|
|
9
13
|
*
|
|
10
14
|
* @dev ❌ Can't use if/else (leaks info!) ✅ Use FHE.select instead
|
|
11
15
|
* ⚡ Gas: ~120k for select operation
|
|
@@ -5,9 +5,14 @@ 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
|
|
9
|
-
|
|
10
|
-
*
|
|
8
|
+
* @notice Master class for FHE permission patterns and access control.
|
|
9
|
+
* Explains the three permission types: allow() for permanent access,
|
|
10
|
+
* allowThis() for contract operations, and allowTransient() for
|
|
11
|
+
* temporary cross-contract calls. Includes correct and incorrect
|
|
12
|
+
* usage examples to prevent common decryption failures.
|
|
13
|
+
|
|
14
|
+
* @dev allow() = permanent, allowThis() = contract permission,
|
|
15
|
+
* allowTransient() = expires at TX end
|
|
11
16
|
* Both allowThis + allow required for user decryption!
|
|
12
17
|
*/
|
|
13
18
|
contract FHEAccessControl is ZamaEthereumConfig {
|
|
@@ -5,7 +5,11 @@ 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
|
|
8
|
+
* @notice Deep dive into FHE handles: what they are and how they work.
|
|
9
|
+
* Explains that handles are uint256 pointers to encrypted data,
|
|
10
|
+
* demonstrates three creation methods (fromExternal, asEuint, operations),
|
|
11
|
+
* and emphasizes immutability - every operation creates a NEW handle.
|
|
12
|
+
* Includes gas cost comparisons for different operations.
|
|
9
13
|
*
|
|
10
14
|
* @dev Handle = uint256 pointer to encrypted data. Operations create NEW handles (immutable).
|
|
11
15
|
* ⚡ Gas: asEuint32 ~20k, fromExternal ~50k, add/sub ~100k
|
|
@@ -11,7 +11,11 @@ import {
|
|
|
11
11
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* @notice
|
|
14
|
+
* @notice Input proof validation and batching strategies in FHEVM.
|
|
15
|
+
* Explains why proofs are essential (prevent garbage data, wrong types,
|
|
16
|
+
* and replay attacks) and demonstrates the gas-efficient batching pattern
|
|
17
|
+
* where one proof validates multiple encrypted inputs, saving ~50k gas
|
|
18
|
+
* per additional value.
|
|
15
19
|
*
|
|
16
20
|
* @dev Proofs ensure: valid ciphertext + correct range + proof of knowledge.
|
|
17
21
|
* ⚡ Gas: Batching multiple values in ONE proof saves ~50k gas vs separate proofs!
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
FHE,
|
|
6
|
+
euint32,
|
|
7
|
+
ebool,
|
|
8
|
+
externalEuint32
|
|
9
|
+
} from "@fhevm/solidity/lib/FHE.sol";
|
|
10
|
+
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @notice Control flow anti-patterns in FHE development.
|
|
14
|
+
* Demonstrates common mistakes when using conditional logic
|
|
15
|
+
* and loops with encrypted values.
|
|
16
|
+
*
|
|
17
|
+
* @dev Covers 3 critical patterns: if/else branching, require statements,
|
|
18
|
+
* and encrypted loop iterations.
|
|
19
|
+
* Each shows ❌ WRONG and ✅ CORRECT implementations.
|
|
20
|
+
*/
|
|
21
|
+
contract FHEControlFlowAntiPatterns is ZamaEthereumConfig {
|
|
22
|
+
euint32 private _secretBalance;
|
|
23
|
+
euint32 private _threshold;
|
|
24
|
+
ebool private _validationResult;
|
|
25
|
+
|
|
26
|
+
/// @notice Initialize contract with encrypted balance (threshold fixed for simplicity)
|
|
27
|
+
function initialize(
|
|
28
|
+
externalEuint32 balance,
|
|
29
|
+
bytes calldata inputProof
|
|
30
|
+
) external {
|
|
31
|
+
_secretBalance = FHE.fromExternal(balance, inputProof);
|
|
32
|
+
_threshold = FHE.asEuint32(100); // Fixed threshold for simpler testing
|
|
33
|
+
|
|
34
|
+
FHE.allowThis(_secretBalance);
|
|
35
|
+
FHE.allowThis(_threshold);
|
|
36
|
+
FHE.allow(_secretBalance, msg.sender);
|
|
37
|
+
FHE.allow(_threshold, msg.sender);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
41
|
+
// ANTI-PATTERN 1: If/Else Branching on Encrypted Values
|
|
42
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* ❌ WRONG: Using if/else with encrypted comparison
|
|
46
|
+
* @dev This pattern leaks information through control flow
|
|
47
|
+
*/
|
|
48
|
+
function wrongBranching() external returns (uint256) {
|
|
49
|
+
// ❌ This would decrypt the comparison result!
|
|
50
|
+
// The branch taken reveals encrypted information
|
|
51
|
+
// if (decrypt(_secretBalance > _threshold)) {
|
|
52
|
+
// return 1;
|
|
53
|
+
// }
|
|
54
|
+
// return 0;
|
|
55
|
+
|
|
56
|
+
// Placeholder to make function compile
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* ✅ CORRECT: Use FHE.select for conditional logic
|
|
62
|
+
* @dev All computation stays encrypted
|
|
63
|
+
*/
|
|
64
|
+
function correctConditional() external {
|
|
65
|
+
ebool isAboveThreshold = FHE.gt(_secretBalance, _threshold);
|
|
66
|
+
|
|
67
|
+
// Apply penalty if above threshold, otherwise keep balance
|
|
68
|
+
euint32 penaltyAmount = FHE.asEuint32(10);
|
|
69
|
+
euint32 balanceMinusPenalty = FHE.sub(_secretBalance, penaltyAmount);
|
|
70
|
+
|
|
71
|
+
_secretBalance = FHE.select(
|
|
72
|
+
isAboveThreshold,
|
|
73
|
+
balanceMinusPenalty,
|
|
74
|
+
_secretBalance
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
FHE.allowThis(_secretBalance);
|
|
78
|
+
FHE.allow(_secretBalance, msg.sender);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
82
|
+
// ANTI-PATTERN 2: Require/Revert with Encrypted Conditions
|
|
83
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* ❌ WRONG: Cannot use require with encrypted boolean
|
|
87
|
+
* @dev This doesn't compile - ebool cannot be used in require
|
|
88
|
+
*/
|
|
89
|
+
function wrongRequire() external pure returns (string memory) {
|
|
90
|
+
// ❌ COMPILE ERROR: require expects bool, not ebool
|
|
91
|
+
// ebool hasEnough = FHE.ge(_secretBalance, FHE.asEuint32(100));
|
|
92
|
+
// require(hasEnough, "Insufficient balance");
|
|
93
|
+
|
|
94
|
+
return "This pattern doesn't work with encrypted values";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* ✅ CORRECT: Store encrypted boolean for client to check
|
|
99
|
+
* @dev Let the client decrypt via getter and handle validation
|
|
100
|
+
*/
|
|
101
|
+
function correctValidation() external {
|
|
102
|
+
ebool hasEnough = FHE.ge(_secretBalance, FHE.asEuint32(100));
|
|
103
|
+
|
|
104
|
+
_validationResult = hasEnough;
|
|
105
|
+
FHE.allowThis(_validationResult);
|
|
106
|
+
FHE.allow(_validationResult, msg.sender);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// @notice Get validation result
|
|
110
|
+
function getValidationResult() external view returns (ebool) {
|
|
111
|
+
return _validationResult;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
115
|
+
// ANTI-PATTERN 3: Encrypted Loop Iterations
|
|
116
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* ❌ WRONG: Loop count based on encrypted value
|
|
120
|
+
* @dev Gas consumption reveals the loop count
|
|
121
|
+
*/
|
|
122
|
+
function wrongEncryptedLoop() external pure returns (string memory) {
|
|
123
|
+
// ❌ GAS LEAK: Number of iterations visible through gas cost!
|
|
124
|
+
// for (uint i = 0; i < decrypt(_secretBalance); i++) {
|
|
125
|
+
// // Each iteration costs gas
|
|
126
|
+
// }
|
|
127
|
+
|
|
128
|
+
return "Loop iterations leak through gas consumption";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* ✅ CORRECT: Fixed iterations with FHE.select
|
|
133
|
+
* @dev Always loop maximum times, conditionally apply operations
|
|
134
|
+
*/
|
|
135
|
+
function correctFixedIterations() external {
|
|
136
|
+
uint256 MAX_ITERATIONS = 5;
|
|
137
|
+
euint32 result = FHE.asEuint32(0);
|
|
138
|
+
|
|
139
|
+
for (uint256 i = 0; i < MAX_ITERATIONS; i++) {
|
|
140
|
+
// Check if we should add (i < _secretBalance)
|
|
141
|
+
ebool shouldAdd = FHE.lt(FHE.asEuint32(uint32(i)), _secretBalance);
|
|
142
|
+
|
|
143
|
+
// Add 1 if condition true, 0 otherwise
|
|
144
|
+
euint32 toAdd = FHE.select(
|
|
145
|
+
shouldAdd,
|
|
146
|
+
FHE.asEuint32(1),
|
|
147
|
+
FHE.asEuint32(0)
|
|
148
|
+
);
|
|
149
|
+
result = FHE.add(result, toAdd);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
FHE.allowThis(result);
|
|
153
|
+
FHE.allow(result, msg.sender);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/// @notice Helper to get balance for testing
|
|
157
|
+
function getBalance() external view returns (euint32) {
|
|
158
|
+
return _secretBalance;
|
|
159
|
+
}
|
|
160
|
+
}
|