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.
- package/contracts/advanced/BlindAuction.sol +22 -57
- package/contracts/advanced/EncryptedEscrow.sol +7 -31
- package/contracts/advanced/HiddenVoting.sol +19 -54
- package/contracts/advanced/PrivateKYC.sol +9 -33
- package/contracts/advanced/PrivatePayroll.sol +9 -35
- package/contracts/basic/decryption/PublicDecryptMultipleValues.sol +19 -21
- package/contracts/basic/decryption/PublicDecryptSingleValue.sol +19 -20
- package/contracts/basic/decryption/UserDecryptMultipleValues.sol +11 -21
- package/contracts/basic/decryption/UserDecryptSingleValue.sol +15 -30
- package/contracts/basic/encryption/EncryptMultipleValues.sol +16 -22
- package/contracts/basic/encryption/EncryptSingleValue.sol +15 -17
- package/contracts/basic/encryption/FHECounter.sol +19 -17
- package/contracts/basic/fhe-operations/FHEAdd.sol +14 -16
- package/contracts/basic/fhe-operations/FHEArithmetic.sol +19 -31
- package/contracts/basic/fhe-operations/FHEComparison.sol +12 -29
- package/contracts/basic/fhe-operations/FHEIfThenElse.sol +9 -10
- package/contracts/concepts/FHEAccessControl.sol +28 -28
- package/contracts/concepts/FHEAntiPatterns.sol +8 -37
- package/contracts/concepts/FHEHandles.sol +14 -29
- package/contracts/concepts/FHEInputProof.sol +13 -33
- package/contracts/gaming/EncryptedLottery.sol +11 -38
- package/contracts/gaming/EncryptedPoker.sol +8 -34
- package/contracts/gaming/RockPaperScissors.sol +43 -64
- 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 +13 -17
- package/dist/scripts/commands/generate-config.js +18 -3
- package/dist/scripts/shared/config.d.ts.map +1 -1
- package/dist/scripts/shared/config.js +81 -75
- package/package.json +1 -1
|
@@ -5,21 +5,23 @@ 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
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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.
|
|
13
|
+
|
|
14
|
+
* @dev Shows the most basic FHE operation and permission flow.
|
|
15
|
+
* ⚡ Gas: FHE.add() costs ~100k gas (coprocessor call)
|
|
11
16
|
*/
|
|
12
17
|
contract FHEAdd is ZamaEthereumConfig {
|
|
13
18
|
euint8 private _a;
|
|
14
19
|
euint8 private _b;
|
|
15
20
|
euint8 private _result;
|
|
16
21
|
|
|
17
|
-
constructor() {}
|
|
18
|
-
|
|
19
22
|
/// @notice Set the first operand (encrypted)
|
|
20
23
|
function setA(externalEuint8 inputA, bytes calldata inputProof) external {
|
|
21
24
|
_a = FHE.fromExternal(inputA, inputProof);
|
|
22
|
-
// Only contract needs permission to use this for computation
|
|
23
25
|
FHE.allowThis(_a);
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -30,21 +32,17 @@ contract FHEAdd is ZamaEthereumConfig {
|
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
/// @notice Compute a + b on encrypted values
|
|
33
|
-
/// @dev
|
|
35
|
+
/// @dev Contract operates on ciphertexts - never sees actual values!
|
|
34
36
|
function computeAPlusB() external {
|
|
35
|
-
//
|
|
36
|
-
// Neither the contract nor anyone else knows what a, b, or result are
|
|
37
|
+
// 🧮 Homomorphic: operates on encrypted data without decrypting
|
|
37
38
|
_result = FHE.add(_a, _b);
|
|
38
39
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// We need PERMANENT permissions for future access:
|
|
43
|
-
FHE.allowThis(_result); // Contract can use result later
|
|
44
|
-
FHE.allow(_result, msg.sender); // Caller can decrypt result
|
|
40
|
+
// 🔑 Why both? allowThis = contract can use it, allow = user can decrypt
|
|
41
|
+
FHE.allowThis(_result);
|
|
42
|
+
FHE.allow(_result, msg.sender);
|
|
45
43
|
}
|
|
46
44
|
|
|
47
|
-
/// @notice Returns the encrypted result
|
|
45
|
+
/// @notice Returns the encrypted result handle
|
|
48
46
|
function result() public view returns (euint8) {
|
|
49
47
|
return _result;
|
|
50
48
|
}
|
|
@@ -5,93 +5,81 @@ 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
|
-
* @dev
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Available operations:
|
|
14
|
-
* - FHE.add(a, b) : Addition
|
|
15
|
-
* - FHE.sub(a, b) : Subtraction
|
|
16
|
-
* - FHE.mul(a, b) : Multiplication
|
|
17
|
-
* - FHE.div(a, b) : Division (integer)
|
|
18
|
-
* - FHE.rem(a, b) : Remainder (modulo)
|
|
19
|
-
* - FHE.min(a, b) : Minimum of two values
|
|
20
|
-
* - FHE.max(a, b) : Maximum of two values
|
|
14
|
+
* @dev ⚡ Gas costs vary: add/sub (~100k) < mul (~150k) < div/rem (~300k)
|
|
15
|
+
* ⚠️ div/rem only work with plaintext divisor (not encrypted!)
|
|
21
16
|
*/
|
|
22
17
|
contract FHEArithmetic is ZamaEthereumConfig {
|
|
23
18
|
euint32 private _a;
|
|
24
19
|
euint32 private _b;
|
|
25
20
|
euint32 private _result;
|
|
26
21
|
|
|
27
|
-
// solhint-disable-next-line no-empty-blocks
|
|
28
|
-
constructor() {}
|
|
29
|
-
|
|
30
|
-
/// @notice Sets the first operand (encrypted)
|
|
31
22
|
function setA(externalEuint32 inputA, bytes calldata inputProof) external {
|
|
32
23
|
_a = FHE.fromExternal(inputA, inputProof);
|
|
33
24
|
FHE.allowThis(_a);
|
|
34
25
|
}
|
|
35
26
|
|
|
36
|
-
/// @notice Sets the second operand (encrypted)
|
|
37
27
|
function setB(externalEuint32 inputB, bytes calldata inputProof) external {
|
|
38
28
|
_b = FHE.fromExternal(inputB, inputProof);
|
|
39
29
|
FHE.allowThis(_b);
|
|
40
30
|
}
|
|
41
31
|
|
|
42
|
-
/// @notice
|
|
32
|
+
/// @notice Encrypted addition: result = a + b
|
|
43
33
|
function computeAdd() external {
|
|
44
34
|
_result = FHE.add(_a, _b);
|
|
45
35
|
_grantPermissions();
|
|
46
36
|
}
|
|
47
37
|
|
|
48
|
-
/// @notice
|
|
49
|
-
/// @dev No underflow protection
|
|
38
|
+
/// @notice Encrypted subtraction: result = a - b
|
|
39
|
+
/// @dev ⚠️ No underflow protection! Wraps around at 0.
|
|
50
40
|
function computeSub() external {
|
|
51
41
|
_result = FHE.sub(_a, _b);
|
|
52
42
|
_grantPermissions();
|
|
53
43
|
}
|
|
54
44
|
|
|
55
|
-
/// @notice
|
|
56
|
-
/// @dev No overflow protection
|
|
45
|
+
/// @notice Encrypted multiplication: result = a * b
|
|
46
|
+
/// @dev ⚠️ No overflow protection! May wrap at type max.
|
|
57
47
|
function computeMul() external {
|
|
58
48
|
_result = FHE.mul(_a, _b);
|
|
59
49
|
_grantPermissions();
|
|
60
50
|
}
|
|
61
51
|
|
|
62
|
-
/// @notice
|
|
63
|
-
/// @dev
|
|
52
|
+
/// @notice Encrypted division: result = a / divisor
|
|
53
|
+
/// @dev ❌ WRONG: FHE.div(encryptedA, encryptedB) - not supported!
|
|
54
|
+
/// ✅ CORRECT: FHE.div(encryptedA, plaintextB)
|
|
64
55
|
function computeDiv(uint32 divisor) external {
|
|
65
56
|
_result = FHE.div(_a, divisor);
|
|
66
57
|
_grantPermissions();
|
|
67
58
|
}
|
|
68
59
|
|
|
69
|
-
/// @notice
|
|
70
|
-
/// @dev
|
|
60
|
+
/// @notice Encrypted remainder: result = a % modulus
|
|
61
|
+
/// @dev Modulus must be plaintext (same limitation as div)
|
|
71
62
|
function computeRem(uint32 modulus) external {
|
|
72
63
|
_result = FHE.rem(_a, modulus);
|
|
73
64
|
_grantPermissions();
|
|
74
65
|
}
|
|
75
66
|
|
|
76
|
-
/// @notice
|
|
67
|
+
/// @notice Encrypted minimum: result = min(a, b)
|
|
77
68
|
function computeMin() external {
|
|
78
69
|
_result = FHE.min(_a, _b);
|
|
79
70
|
_grantPermissions();
|
|
80
71
|
}
|
|
81
72
|
|
|
82
|
-
/// @notice
|
|
73
|
+
/// @notice Encrypted maximum: result = max(a, b)
|
|
83
74
|
function computeMax() external {
|
|
84
75
|
_result = FHE.max(_a, _b);
|
|
85
76
|
_grantPermissions();
|
|
86
77
|
}
|
|
87
78
|
|
|
88
|
-
/// @notice Returns the encrypted result
|
|
89
|
-
/// @dev Caller must have FHE permissions to decrypt
|
|
90
79
|
function getResult() public view returns (euint32) {
|
|
91
80
|
return _result;
|
|
92
81
|
}
|
|
93
82
|
|
|
94
|
-
/// @dev Grants FHE permissions to contract and caller for decryption
|
|
95
83
|
function _grantPermissions() internal {
|
|
96
84
|
FHE.allowThis(_result);
|
|
97
85
|
FHE.allow(_result, msg.sender);
|
|
@@ -10,19 +10,15 @@ 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
|
-
* @dev
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* Available operations:
|
|
19
|
-
* - FHE.eq(a, b) : Equal (a == b)
|
|
20
|
-
* - FHE.ne(a, b) : Not equal (a != b)
|
|
21
|
-
* - FHE.gt(a, b) : Greater than (a > b)
|
|
22
|
-
* - FHE.lt(a, b) : Less than (a < b)
|
|
23
|
-
* - FHE.ge(a, b) : Greater or equal (a >= b)
|
|
24
|
-
* - FHE.le(a, b) : Less or equal (a <= b)
|
|
25
|
-
* - FHE.select(cond, a, b) : Conditional selection (cond ? a : b)
|
|
19
|
+
* @dev Results are encrypted booleans (ebool) - comparisons reveal nothing!
|
|
20
|
+
* ⚡ Gas: Comparisons ~100k, select ~120k
|
|
21
|
+
* ❌ WRONG: if (FHE.gt(a,b)) → decrypts! ✅ CORRECT: FHE.select()
|
|
26
22
|
*/
|
|
27
23
|
contract FHEComparison is ZamaEthereumConfig {
|
|
28
24
|
euint32 private _a;
|
|
@@ -30,67 +26,57 @@ contract FHEComparison is ZamaEthereumConfig {
|
|
|
30
26
|
ebool private _boolResult;
|
|
31
27
|
euint32 private _selectedResult;
|
|
32
28
|
|
|
33
|
-
// solhint-disable-next-line no-empty-blocks
|
|
34
|
-
constructor() {}
|
|
35
|
-
|
|
36
|
-
/// @notice Sets the first operand (encrypted)
|
|
37
29
|
function setA(externalEuint32 inputA, bytes calldata inputProof) external {
|
|
38
30
|
_a = FHE.fromExternal(inputA, inputProof);
|
|
39
31
|
FHE.allowThis(_a);
|
|
40
32
|
}
|
|
41
33
|
|
|
42
|
-
/// @notice Sets the second operand (encrypted)
|
|
43
34
|
function setB(externalEuint32 inputB, bytes calldata inputProof) external {
|
|
44
35
|
_b = FHE.fromExternal(inputB, inputProof);
|
|
45
36
|
FHE.allowThis(_b);
|
|
46
37
|
}
|
|
47
38
|
|
|
48
|
-
/// @notice Computes encrypted equality: result = (a == b)
|
|
49
39
|
function computeEq() external {
|
|
50
40
|
_boolResult = FHE.eq(_a, _b);
|
|
51
41
|
_grantBoolPermissions();
|
|
52
42
|
}
|
|
53
43
|
|
|
54
|
-
/// @notice Computes encrypted inequality: result = (a != b)
|
|
55
44
|
function computeNe() external {
|
|
56
45
|
_boolResult = FHE.ne(_a, _b);
|
|
57
46
|
_grantBoolPermissions();
|
|
58
47
|
}
|
|
59
48
|
|
|
60
|
-
/// @notice Computes encrypted greater than: result = (a > b)
|
|
61
49
|
function computeGt() external {
|
|
62
50
|
_boolResult = FHE.gt(_a, _b);
|
|
63
51
|
_grantBoolPermissions();
|
|
64
52
|
}
|
|
65
53
|
|
|
66
|
-
/// @notice Computes encrypted less than: result = (a < b)
|
|
67
54
|
function computeLt() external {
|
|
68
55
|
_boolResult = FHE.lt(_a, _b);
|
|
69
56
|
_grantBoolPermissions();
|
|
70
57
|
}
|
|
71
58
|
|
|
72
|
-
/// @notice Computes encrypted greater or equal: result = (a >= b)
|
|
73
59
|
function computeGe() external {
|
|
74
60
|
_boolResult = FHE.ge(_a, _b);
|
|
75
61
|
_grantBoolPermissions();
|
|
76
62
|
}
|
|
77
63
|
|
|
78
|
-
/// @notice Computes encrypted less or equal: result = (a <= b)
|
|
79
64
|
function computeLe() external {
|
|
80
65
|
_boolResult = FHE.le(_a, _b);
|
|
81
66
|
_grantBoolPermissions();
|
|
82
67
|
}
|
|
83
68
|
|
|
84
|
-
/// @notice
|
|
85
|
-
/// @dev
|
|
69
|
+
/// @notice Encrypted maximum using select: (a > b) ? a : b
|
|
70
|
+
/// @dev 🔀 Why select? Using if(aGtB) would decrypt and leak the comparison!
|
|
86
71
|
function computeMaxViaSelect() external {
|
|
87
72
|
ebool aGtB = FHE.gt(_a, _b);
|
|
73
|
+
// select evaluates both branches, picks one without revealing which
|
|
88
74
|
_selectedResult = FHE.select(aGtB, _a, _b);
|
|
89
75
|
FHE.allowThis(_selectedResult);
|
|
90
76
|
FHE.allow(_selectedResult, msg.sender);
|
|
91
77
|
}
|
|
92
78
|
|
|
93
|
-
/// @notice
|
|
79
|
+
/// @notice Encrypted minimum using select: (a < b) ? a : b
|
|
94
80
|
function computeMinViaSelect() external {
|
|
95
81
|
ebool aLtB = FHE.lt(_a, _b);
|
|
96
82
|
_selectedResult = FHE.select(aLtB, _a, _b);
|
|
@@ -98,17 +84,14 @@ contract FHEComparison is ZamaEthereumConfig {
|
|
|
98
84
|
FHE.allow(_selectedResult, msg.sender);
|
|
99
85
|
}
|
|
100
86
|
|
|
101
|
-
/// @notice Returns the encrypted boolean result
|
|
102
87
|
function getBoolResult() public view returns (ebool) {
|
|
103
88
|
return _boolResult;
|
|
104
89
|
}
|
|
105
90
|
|
|
106
|
-
/// @notice Returns the encrypted selected result
|
|
107
91
|
function getSelectedResult() public view returns (euint32) {
|
|
108
92
|
return _selectedResult;
|
|
109
93
|
}
|
|
110
94
|
|
|
111
|
-
/// @dev Grants FHE permissions for boolean result
|
|
112
95
|
function _grantBoolPermissions() internal {
|
|
113
96
|
FHE.allowThis(_boolResult);
|
|
114
97
|
FHE.allow(_boolResult, msg.sender);
|
|
@@ -5,17 +5,20 @@ 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
|
-
* @dev
|
|
14
|
+
* @dev ❌ Can't use if/else (leaks info!) ✅ Use FHE.select instead
|
|
15
|
+
* ⚡ Gas: ~120k for select operation
|
|
11
16
|
*/
|
|
12
17
|
contract FHEIfThenElse is ZamaEthereumConfig {
|
|
13
18
|
euint8 private _a;
|
|
14
19
|
euint8 private _b;
|
|
15
20
|
euint8 private _max;
|
|
16
21
|
|
|
17
|
-
constructor() {}
|
|
18
|
-
|
|
19
22
|
/// @notice Sets the first operand (encrypted)
|
|
20
23
|
function setA(externalEuint8 inputA, bytes calldata inputProof) external {
|
|
21
24
|
_a = FHE.fromExternal(inputA, inputProof);
|
|
@@ -31,14 +34,10 @@ contract FHEIfThenElse is ZamaEthereumConfig {
|
|
|
31
34
|
/// @notice Compute max(a, b) without revealing which is larger
|
|
32
35
|
/// @dev Uses FHE.select() - the encrypted "if-then-else"
|
|
33
36
|
function computeMax() external {
|
|
34
|
-
// 🔍 Compare encrypted values - result is encrypted boolean!
|
|
35
|
-
// We don't know if a >= b, only the encrypted result
|
|
36
37
|
ebool aIsGreaterOrEqual = FHE.ge(_a, _b);
|
|
37
38
|
|
|
38
|
-
// 🔀
|
|
39
|
-
//
|
|
40
|
-
// - Result is encrypted - no one knows which was selected
|
|
41
|
-
// - This is how you do "if-else" on encrypted values!
|
|
39
|
+
// 🔀 Why select? if/else would leak which branch was taken!
|
|
40
|
+
// select evaluates BOTH branches, picks one based on encrypted condition
|
|
42
41
|
_max = FHE.select(aIsGreaterOrEqual, _a, _b);
|
|
43
42
|
|
|
44
43
|
// Grant permissions for decryption
|
|
@@ -5,21 +5,20 @@ 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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
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
|
|
16
|
+
* Both allowThis + allow required for user decryption!
|
|
14
17
|
*/
|
|
15
18
|
contract FHEAccessControl is ZamaEthereumConfig {
|
|
16
19
|
euint32 private _secretValue;
|
|
17
20
|
mapping(address => bool) public hasAccess;
|
|
18
21
|
|
|
19
|
-
constructor() {}
|
|
20
|
-
|
|
21
|
-
// ==================== CORRECT PATTERN ====================
|
|
22
|
-
|
|
23
22
|
/// @notice ✅ CORRECT: Full access pattern for user decryption
|
|
24
23
|
function storeWithFullAccess(
|
|
25
24
|
externalEuint32 input,
|
|
@@ -27,13 +26,12 @@ contract FHEAccessControl is ZamaEthereumConfig {
|
|
|
27
26
|
) external {
|
|
28
27
|
_secretValue = FHE.fromExternal(input, inputProof);
|
|
29
28
|
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// This requires contract's permission to "release" the value
|
|
29
|
+
// Why BOTH allowThis + allow?
|
|
30
|
+
// - allowThis: Contract authorizes "releasing" the encrypted value
|
|
31
|
+
// - allow(user): User can request decryption for their key
|
|
32
|
+
// Missing either = decryption fails!
|
|
33
|
+
FHE.allowThis(_secretValue);
|
|
34
|
+
FHE.allow(_secretValue, msg.sender);
|
|
37
35
|
|
|
38
36
|
hasAccess[msg.sender] = true;
|
|
39
37
|
}
|
|
@@ -42,7 +40,8 @@ contract FHEAccessControl is ZamaEthereumConfig {
|
|
|
42
40
|
function grantAccess(address user) external {
|
|
43
41
|
require(hasAccess[msg.sender], "Caller has no access to grant");
|
|
44
42
|
|
|
45
|
-
//
|
|
43
|
+
// Why this works: Contract already has allowThis from storeWithFullAccess
|
|
44
|
+
// We only need to grant allow(user) for the new user
|
|
46
45
|
FHE.allow(_secretValue, user);
|
|
47
46
|
hasAccess[user] = true;
|
|
48
47
|
}
|
|
@@ -51,8 +50,6 @@ contract FHEAccessControl is ZamaEthereumConfig {
|
|
|
51
50
|
return _secretValue;
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
// ==================== WRONG PATTERNS (EDUCATIONAL) ====================
|
|
55
|
-
|
|
56
53
|
/// @notice ❌ WRONG: Missing allowThis → user decryption FAILS
|
|
57
54
|
function storeWithoutAllowThis(
|
|
58
55
|
externalEuint32 input,
|
|
@@ -60,8 +57,9 @@ contract FHEAccessControl is ZamaEthereumConfig {
|
|
|
60
57
|
) external {
|
|
61
58
|
_secretValue = FHE.fromExternal(input, inputProof);
|
|
62
59
|
|
|
63
|
-
// ❌
|
|
64
|
-
// User has permission
|
|
60
|
+
// ❌ Common mistake: Only allow(user) without allowThis
|
|
61
|
+
// Result: User has permission but can't decrypt!
|
|
62
|
+
// Why? Decryption needs contract to authorize the release
|
|
65
63
|
FHE.allow(_secretValue, msg.sender);
|
|
66
64
|
}
|
|
67
65
|
|
|
@@ -72,21 +70,23 @@ contract FHEAccessControl is ZamaEthereumConfig {
|
|
|
72
70
|
) external {
|
|
73
71
|
_secretValue = FHE.fromExternal(input, inputProof);
|
|
74
72
|
|
|
73
|
+
// ❌ Another mistake: Only allowThis without allow(user)
|
|
74
|
+
// Result: Contract can compute but no one can decrypt!
|
|
75
75
|
FHE.allowThis(_secretValue);
|
|
76
|
-
// ❌ Missing: FHE.allow(_secretValue, msg.sender)
|
|
77
|
-
// Contract can operate, but no one can decrypt!
|
|
78
76
|
}
|
|
79
77
|
|
|
80
|
-
// ==================== TRANSIENT ACCESS ====================
|
|
81
|
-
|
|
82
78
|
/// @notice Temporary access - expires at end of transaction
|
|
83
|
-
/// @dev
|
|
79
|
+
/// @dev ⚡ Gas: allowTransient ~50% cheaper than allow!
|
|
80
|
+
/// Use for passing values between contracts in same TX
|
|
84
81
|
function computeAndShareTransient(
|
|
85
82
|
address recipient
|
|
86
83
|
) external returns (euint32) {
|
|
87
84
|
euint32 computed = FHE.add(_secretValue, FHE.asEuint32(1));
|
|
88
85
|
|
|
89
|
-
//
|
|
86
|
+
// Why allowTransient instead of allow?
|
|
87
|
+
// - Cheaper: ~50% less gas than permanent allow
|
|
88
|
+
// - Auto-cleanup: Expires at TX end (no storage pollution)
|
|
89
|
+
// - Use case: Passing values between contracts in same transaction
|
|
90
90
|
FHE.allowTransient(computed, recipient);
|
|
91
91
|
|
|
92
92
|
return computed;
|
|
@@ -10,28 +10,19 @@ import {
|
|
|
10
10
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @notice
|
|
13
|
+
* @notice Comprehensive guide to FHE anti-patterns and their solutions.
|
|
14
|
+
* Covers 9 critical mistakes: using if/else on encrypted values,
|
|
15
|
+
* incorrect permission patterns, require() statements that leak info,
|
|
16
|
+
* unbounded loops, noise accumulation, and deprecated APIs.
|
|
17
|
+
* Each pattern shows both ❌ WRONG and ✅ CORRECT implementations.
|
|
14
18
|
*
|
|
15
|
-
* @dev
|
|
16
|
-
*
|
|
17
|
-
* 2. Returning encrypted values without permissions
|
|
18
|
-
* 3. Using require/revert with encrypted conditions
|
|
19
|
-
* 4. Encrypted computation without permission grants
|
|
20
|
-
* 5. Leaking information through gas/timing
|
|
21
|
-
* 6. Unauthenticated re-encryption (security critical!)
|
|
22
|
-
* 7. Encrypted loop iterations (gas/timing leak)
|
|
23
|
-
* 8. Too many chained operations (noise accumulation)
|
|
24
|
-
* 9. Using deprecated FHEVM APIs
|
|
25
|
-
*
|
|
26
|
-
* Each anti-pattern shows both ❌ WRONG and ✅ CORRECT implementations.
|
|
19
|
+
* @dev Covers 9 critical anti-patterns with ❌ WRONG and ✅ CORRECT examples.
|
|
20
|
+
* This is an educational contract - study each pattern before building production code!
|
|
27
21
|
*/
|
|
28
22
|
contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
29
23
|
euint32 private _secretBalance;
|
|
30
24
|
euint32 private _threshold;
|
|
31
25
|
|
|
32
|
-
// solhint-disable-next-line no-empty-blocks
|
|
33
|
-
constructor() {}
|
|
34
|
-
|
|
35
26
|
/// @notice Initialize the contract with encrypted balance and threshold values
|
|
36
27
|
/// @dev Both values are encrypted and permissions are granted to the caller
|
|
37
28
|
function initialize(
|
|
@@ -48,9 +39,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
48
39
|
FHE.allow(_threshold, msg.sender);
|
|
49
40
|
}
|
|
50
41
|
|
|
51
|
-
// ========================================================================
|
|
52
42
|
// ANTI-PATTERN 1: Branching on encrypted values
|
|
53
|
-
// ========================================================================
|
|
54
43
|
|
|
55
44
|
/**
|
|
56
45
|
* ❌ WRONG: if/else on encrypted value causes decryption
|
|
@@ -90,9 +79,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
90
79
|
FHE.allow(_secretBalance, msg.sender);
|
|
91
80
|
}
|
|
92
81
|
|
|
93
|
-
// ========================================================================
|
|
94
82
|
// ANTI-PATTERN 2: View function returning encrypted without permissions
|
|
95
|
-
// ========================================================================
|
|
96
83
|
|
|
97
84
|
/**
|
|
98
85
|
* @notice ❌ WRONG: Returns encrypted value but caller can't decrypt
|
|
@@ -113,9 +100,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
113
100
|
return _secretBalance;
|
|
114
101
|
}
|
|
115
102
|
|
|
116
|
-
// ========================================================================
|
|
117
103
|
// ANTI-PATTERN 3: Using require/revert with encrypted conditions
|
|
118
|
-
// ========================================================================
|
|
119
104
|
|
|
120
105
|
/**
|
|
121
106
|
* ❌ WRONG: Cannot use require with encrypted values
|
|
@@ -141,9 +126,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
141
126
|
return hasEnough;
|
|
142
127
|
}
|
|
143
128
|
|
|
144
|
-
// ========================================================================
|
|
145
129
|
// ANTI-PATTERN 4: Encrypted computation without permission grants
|
|
146
|
-
// ========================================================================
|
|
147
130
|
|
|
148
131
|
/**
|
|
149
132
|
* @notice ❌ WRONG: Compute but forget to grant permissions
|
|
@@ -166,9 +149,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
166
149
|
FHE.allow(_secretBalance, msg.sender);
|
|
167
150
|
}
|
|
168
151
|
|
|
169
|
-
// ========================================================================
|
|
170
152
|
// ANTI-PATTERN 5: Leaking information through gas/timing
|
|
171
|
-
// ========================================================================
|
|
172
153
|
|
|
173
154
|
/**
|
|
174
155
|
* @notice ⚠️ CAUTION: Be aware of side-channel attacks
|
|
@@ -184,9 +165,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
184
165
|
return "Be aware of gas/timing side channels";
|
|
185
166
|
}
|
|
186
167
|
|
|
187
|
-
// ========================================================================
|
|
188
168
|
// ANTI-PATTERN 6: Unauthenticated Re-encryption (SECURITY CRITICAL)
|
|
189
|
-
// ========================================================================
|
|
190
169
|
|
|
191
170
|
/**
|
|
192
171
|
* @notice ❌ WRONG: Re-encrypt for any provided public key
|
|
@@ -221,9 +200,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
221
200
|
"Use fhevm.js userDecrypt which handles this automatically.";
|
|
222
201
|
}
|
|
223
202
|
|
|
224
|
-
// ========================================================================
|
|
225
203
|
// ANTI-PATTERN 7: Encrypted Loop Iterations (GAS/TIMING LEAK)
|
|
226
|
-
// ========================================================================
|
|
227
204
|
|
|
228
205
|
/// ❌ WRONG: Using encrypted value as loop count
|
|
229
206
|
/// @dev Loop count is visible through gas consumption and timing
|
|
@@ -264,9 +241,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
264
241
|
FHE.allow(accumulator, msg.sender);
|
|
265
242
|
}
|
|
266
243
|
|
|
267
|
-
// ========================================================================
|
|
268
244
|
// ANTI-PATTERN 8: Too Many Chained Operations (Noise Accumulation)
|
|
269
|
-
// ========================================================================
|
|
270
245
|
|
|
271
246
|
/// @notice ⚠️ CAUTION: FHE operations accumulate "noise"
|
|
272
247
|
/// @dev Each FHE operation adds noise to the ciphertext. After too many
|
|
@@ -285,9 +260,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
285
260
|
"If you need many operations, consider batching or restructuring logic.";
|
|
286
261
|
}
|
|
287
262
|
|
|
288
|
-
// ========================================================================
|
|
289
263
|
// ANTI-PATTERN 9: Using Deprecated FHEVM APIs
|
|
290
|
-
// ========================================================================
|
|
291
264
|
|
|
292
265
|
/**
|
|
293
266
|
* @notice Caution about deprecated FHEVM APIs
|
|
@@ -306,9 +279,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
306
279
|
"or userDecrypt pattern via fhevm.js for user decryption.";
|
|
307
280
|
}
|
|
308
281
|
|
|
309
|
-
//
|
|
310
|
-
// SUMMARY: Key Rules (Always check before deploying!)
|
|
311
|
-
// ========================================================================
|
|
282
|
+
// SUMMARY: Key Rules
|
|
312
283
|
|
|
313
284
|
/**
|
|
314
285
|
* @notice Quick reference for FHE best practices
|