create-fhevm-example 1.4.3 → 1.4.4
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 +18 -57
- package/contracts/advanced/EncryptedEscrow.sol +2 -30
- package/contracts/advanced/HiddenVoting.sol +15 -54
- package/contracts/advanced/PrivateKYC.sol +3 -32
- package/contracts/advanced/PrivatePayroll.sol +4 -34
- package/contracts/basic/decryption/PublicDecryptMultipleValues.sol +9 -17
- package/contracts/basic/decryption/PublicDecryptSingleValue.sol +9 -16
- package/contracts/basic/decryption/UserDecryptMultipleValues.sol +7 -21
- package/contracts/basic/decryption/UserDecryptSingleValue.sol +11 -30
- package/contracts/basic/encryption/EncryptMultipleValues.sol +12 -21
- package/contracts/basic/encryption/EncryptSingleValue.sol +11 -17
- package/contracts/basic/encryption/FHECounter.sol +15 -16
- package/contracts/basic/fhe-operations/FHEAdd.sol +10 -16
- package/contracts/basic/fhe-operations/FHEArithmetic.sol +15 -31
- package/contracts/basic/fhe-operations/FHEComparison.sol +8 -29
- package/contracts/basic/fhe-operations/FHEIfThenElse.sol +4 -9
- package/contracts/concepts/FHEAccessControl.sol +23 -28
- package/contracts/concepts/FHEAntiPatterns.sol +4 -37
- package/contracts/concepts/FHEHandles.sol +10 -29
- package/contracts/concepts/FHEInputProof.sol +9 -33
- package/contracts/gaming/EncryptedLottery.sol +6 -37
- package/contracts/gaming/EncryptedPoker.sol +3 -33
- package/contracts/gaming/RockPaperScissors.sol +39 -64
- package/contracts/openzeppelin/VestingWallet.sol +8 -17
- package/package.json +1 -1
|
@@ -10,19 +10,11 @@ import {
|
|
|
10
10
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @notice Demonstrates all FHE comparison operations
|
|
13
|
+
* @notice Demonstrates all FHE comparison operations: eq, ne, gt, lt, ge, le, select
|
|
14
14
|
*
|
|
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)
|
|
15
|
+
* @dev Results are encrypted booleans (ebool) - comparisons reveal nothing!
|
|
16
|
+
* ⚡ Gas: Comparisons ~100k, select ~120k
|
|
17
|
+
* ❌ WRONG: if (FHE.gt(a,b)) → decrypts! ✅ CORRECT: FHE.select()
|
|
26
18
|
*/
|
|
27
19
|
contract FHEComparison is ZamaEthereumConfig {
|
|
28
20
|
euint32 private _a;
|
|
@@ -30,67 +22,57 @@ contract FHEComparison is ZamaEthereumConfig {
|
|
|
30
22
|
ebool private _boolResult;
|
|
31
23
|
euint32 private _selectedResult;
|
|
32
24
|
|
|
33
|
-
// solhint-disable-next-line no-empty-blocks
|
|
34
|
-
constructor() {}
|
|
35
|
-
|
|
36
|
-
/// @notice Sets the first operand (encrypted)
|
|
37
25
|
function setA(externalEuint32 inputA, bytes calldata inputProof) external {
|
|
38
26
|
_a = FHE.fromExternal(inputA, inputProof);
|
|
39
27
|
FHE.allowThis(_a);
|
|
40
28
|
}
|
|
41
29
|
|
|
42
|
-
/// @notice Sets the second operand (encrypted)
|
|
43
30
|
function setB(externalEuint32 inputB, bytes calldata inputProof) external {
|
|
44
31
|
_b = FHE.fromExternal(inputB, inputProof);
|
|
45
32
|
FHE.allowThis(_b);
|
|
46
33
|
}
|
|
47
34
|
|
|
48
|
-
/// @notice Computes encrypted equality: result = (a == b)
|
|
49
35
|
function computeEq() external {
|
|
50
36
|
_boolResult = FHE.eq(_a, _b);
|
|
51
37
|
_grantBoolPermissions();
|
|
52
38
|
}
|
|
53
39
|
|
|
54
|
-
/// @notice Computes encrypted inequality: result = (a != b)
|
|
55
40
|
function computeNe() external {
|
|
56
41
|
_boolResult = FHE.ne(_a, _b);
|
|
57
42
|
_grantBoolPermissions();
|
|
58
43
|
}
|
|
59
44
|
|
|
60
|
-
/// @notice Computes encrypted greater than: result = (a > b)
|
|
61
45
|
function computeGt() external {
|
|
62
46
|
_boolResult = FHE.gt(_a, _b);
|
|
63
47
|
_grantBoolPermissions();
|
|
64
48
|
}
|
|
65
49
|
|
|
66
|
-
/// @notice Computes encrypted less than: result = (a < b)
|
|
67
50
|
function computeLt() external {
|
|
68
51
|
_boolResult = FHE.lt(_a, _b);
|
|
69
52
|
_grantBoolPermissions();
|
|
70
53
|
}
|
|
71
54
|
|
|
72
|
-
/// @notice Computes encrypted greater or equal: result = (a >= b)
|
|
73
55
|
function computeGe() external {
|
|
74
56
|
_boolResult = FHE.ge(_a, _b);
|
|
75
57
|
_grantBoolPermissions();
|
|
76
58
|
}
|
|
77
59
|
|
|
78
|
-
/// @notice Computes encrypted less or equal: result = (a <= b)
|
|
79
60
|
function computeLe() external {
|
|
80
61
|
_boolResult = FHE.le(_a, _b);
|
|
81
62
|
_grantBoolPermissions();
|
|
82
63
|
}
|
|
83
64
|
|
|
84
|
-
/// @notice
|
|
85
|
-
/// @dev
|
|
65
|
+
/// @notice Encrypted maximum using select: (a > b) ? a : b
|
|
66
|
+
/// @dev 🔀 Why select? Using if(aGtB) would decrypt and leak the comparison!
|
|
86
67
|
function computeMaxViaSelect() external {
|
|
87
68
|
ebool aGtB = FHE.gt(_a, _b);
|
|
69
|
+
// select evaluates both branches, picks one without revealing which
|
|
88
70
|
_selectedResult = FHE.select(aGtB, _a, _b);
|
|
89
71
|
FHE.allowThis(_selectedResult);
|
|
90
72
|
FHE.allow(_selectedResult, msg.sender);
|
|
91
73
|
}
|
|
92
74
|
|
|
93
|
-
/// @notice
|
|
75
|
+
/// @notice Encrypted minimum using select: (a < b) ? a : b
|
|
94
76
|
function computeMinViaSelect() external {
|
|
95
77
|
ebool aLtB = FHE.lt(_a, _b);
|
|
96
78
|
_selectedResult = FHE.select(aLtB, _a, _b);
|
|
@@ -98,17 +80,14 @@ contract FHEComparison is ZamaEthereumConfig {
|
|
|
98
80
|
FHE.allow(_selectedResult, msg.sender);
|
|
99
81
|
}
|
|
100
82
|
|
|
101
|
-
/// @notice Returns the encrypted boolean result
|
|
102
83
|
function getBoolResult() public view returns (ebool) {
|
|
103
84
|
return _boolResult;
|
|
104
85
|
}
|
|
105
86
|
|
|
106
|
-
/// @notice Returns the encrypted selected result
|
|
107
87
|
function getSelectedResult() public view returns (euint32) {
|
|
108
88
|
return _selectedResult;
|
|
109
89
|
}
|
|
110
90
|
|
|
111
|
-
/// @dev Grants FHE permissions for boolean result
|
|
112
91
|
function _grantBoolPermissions() internal {
|
|
113
92
|
FHE.allowThis(_boolResult);
|
|
114
93
|
FHE.allow(_boolResult, msg.sender);
|
|
@@ -7,15 +7,14 @@ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
|
7
7
|
/**
|
|
8
8
|
* @notice Demonstrates conditional logic: max(a, b) using encrypted comparison
|
|
9
9
|
*
|
|
10
|
-
* @dev
|
|
10
|
+
* @dev ❌ Can't use if/else (leaks info!) ✅ Use FHE.select instead
|
|
11
|
+
* ⚡ Gas: ~120k for select operation
|
|
11
12
|
*/
|
|
12
13
|
contract FHEIfThenElse is ZamaEthereumConfig {
|
|
13
14
|
euint8 private _a;
|
|
14
15
|
euint8 private _b;
|
|
15
16
|
euint8 private _max;
|
|
16
17
|
|
|
17
|
-
constructor() {}
|
|
18
|
-
|
|
19
18
|
/// @notice Sets the first operand (encrypted)
|
|
20
19
|
function setA(externalEuint8 inputA, bytes calldata inputProof) external {
|
|
21
20
|
_a = FHE.fromExternal(inputA, inputProof);
|
|
@@ -31,14 +30,10 @@ contract FHEIfThenElse is ZamaEthereumConfig {
|
|
|
31
30
|
/// @notice Compute max(a, b) without revealing which is larger
|
|
32
31
|
/// @dev Uses FHE.select() - the encrypted "if-then-else"
|
|
33
32
|
function computeMax() external {
|
|
34
|
-
// 🔍 Compare encrypted values - result is encrypted boolean!
|
|
35
|
-
// We don't know if a >= b, only the encrypted result
|
|
36
33
|
ebool aIsGreaterOrEqual = FHE.ge(_a, _b);
|
|
37
34
|
|
|
38
|
-
// 🔀
|
|
39
|
-
//
|
|
40
|
-
// - Result is encrypted - no one knows which was selected
|
|
41
|
-
// - This is how you do "if-else" on encrypted values!
|
|
35
|
+
// 🔀 Why select? if/else would leak which branch was taken!
|
|
36
|
+
// select evaluates BOTH branches, picks one based on encrypted condition
|
|
42
37
|
_max = FHE.select(aIsGreaterOrEqual, _a, _b);
|
|
43
38
|
|
|
44
39
|
// Grant permissions for decryption
|
|
@@ -5,21 +5,15 @@ 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 Critical access control patterns in FHEVM: FHE.allow, FHE.allowThis, FHE.allowTransient
|
|
9
|
-
|
|
10
|
-
* @dev
|
|
11
|
-
*
|
|
12
|
-
* FHE.allowThis(handle) - permission for contract itself
|
|
13
|
-
* FHE.allowTransient(handle, address) - temporary, expires at tx end
|
|
8
|
+
* @notice Critical access control patterns in FHEVM: FHE.allow, FHE.allowThis, FHE.allowTransient with common mistakes.
|
|
9
|
+
|
|
10
|
+
* @dev allow() = permanent, allowThis() = contract permission, allowTransient() = expires at TX end
|
|
11
|
+
* Both allowThis + allow required for user decryption!
|
|
14
12
|
*/
|
|
15
13
|
contract FHEAccessControl is ZamaEthereumConfig {
|
|
16
14
|
euint32 private _secretValue;
|
|
17
15
|
mapping(address => bool) public hasAccess;
|
|
18
16
|
|
|
19
|
-
constructor() {}
|
|
20
|
-
|
|
21
|
-
// ==================== CORRECT PATTERN ====================
|
|
22
|
-
|
|
23
17
|
/// @notice ✅ CORRECT: Full access pattern for user decryption
|
|
24
18
|
function storeWithFullAccess(
|
|
25
19
|
externalEuint32 input,
|
|
@@ -27,13 +21,12 @@ contract FHEAccessControl is ZamaEthereumConfig {
|
|
|
27
21
|
) external {
|
|
28
22
|
_secretValue = FHE.fromExternal(input, inputProof);
|
|
29
23
|
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// This requires contract's permission to "release" the value
|
|
24
|
+
// Why BOTH allowThis + allow?
|
|
25
|
+
// - allowThis: Contract authorizes "releasing" the encrypted value
|
|
26
|
+
// - allow(user): User can request decryption for their key
|
|
27
|
+
// Missing either = decryption fails!
|
|
28
|
+
FHE.allowThis(_secretValue);
|
|
29
|
+
FHE.allow(_secretValue, msg.sender);
|
|
37
30
|
|
|
38
31
|
hasAccess[msg.sender] = true;
|
|
39
32
|
}
|
|
@@ -42,7 +35,8 @@ contract FHEAccessControl is ZamaEthereumConfig {
|
|
|
42
35
|
function grantAccess(address user) external {
|
|
43
36
|
require(hasAccess[msg.sender], "Caller has no access to grant");
|
|
44
37
|
|
|
45
|
-
//
|
|
38
|
+
// Why this works: Contract already has allowThis from storeWithFullAccess
|
|
39
|
+
// We only need to grant allow(user) for the new user
|
|
46
40
|
FHE.allow(_secretValue, user);
|
|
47
41
|
hasAccess[user] = true;
|
|
48
42
|
}
|
|
@@ -51,8 +45,6 @@ contract FHEAccessControl is ZamaEthereumConfig {
|
|
|
51
45
|
return _secretValue;
|
|
52
46
|
}
|
|
53
47
|
|
|
54
|
-
// ==================== WRONG PATTERNS (EDUCATIONAL) ====================
|
|
55
|
-
|
|
56
48
|
/// @notice ❌ WRONG: Missing allowThis → user decryption FAILS
|
|
57
49
|
function storeWithoutAllowThis(
|
|
58
50
|
externalEuint32 input,
|
|
@@ -60,8 +52,9 @@ contract FHEAccessControl is ZamaEthereumConfig {
|
|
|
60
52
|
) external {
|
|
61
53
|
_secretValue = FHE.fromExternal(input, inputProof);
|
|
62
54
|
|
|
63
|
-
// ❌
|
|
64
|
-
// User has permission
|
|
55
|
+
// ❌ Common mistake: Only allow(user) without allowThis
|
|
56
|
+
// Result: User has permission but can't decrypt!
|
|
57
|
+
// Why? Decryption needs contract to authorize the release
|
|
65
58
|
FHE.allow(_secretValue, msg.sender);
|
|
66
59
|
}
|
|
67
60
|
|
|
@@ -72,21 +65,23 @@ contract FHEAccessControl is ZamaEthereumConfig {
|
|
|
72
65
|
) external {
|
|
73
66
|
_secretValue = FHE.fromExternal(input, inputProof);
|
|
74
67
|
|
|
68
|
+
// ❌ Another mistake: Only allowThis without allow(user)
|
|
69
|
+
// Result: Contract can compute but no one can decrypt!
|
|
75
70
|
FHE.allowThis(_secretValue);
|
|
76
|
-
// ❌ Missing: FHE.allow(_secretValue, msg.sender)
|
|
77
|
-
// Contract can operate, but no one can decrypt!
|
|
78
71
|
}
|
|
79
72
|
|
|
80
|
-
// ==================== TRANSIENT ACCESS ====================
|
|
81
|
-
|
|
82
73
|
/// @notice Temporary access - expires at end of transaction
|
|
83
|
-
/// @dev
|
|
74
|
+
/// @dev ⚡ Gas: allowTransient ~50% cheaper than allow!
|
|
75
|
+
/// Use for passing values between contracts in same TX
|
|
84
76
|
function computeAndShareTransient(
|
|
85
77
|
address recipient
|
|
86
78
|
) external returns (euint32) {
|
|
87
79
|
euint32 computed = FHE.add(_secretValue, FHE.asEuint32(1));
|
|
88
80
|
|
|
89
|
-
//
|
|
81
|
+
// Why allowTransient instead of allow?
|
|
82
|
+
// - Cheaper: ~50% less gas than permanent allow
|
|
83
|
+
// - Auto-cleanup: Expires at TX end (no storage pollution)
|
|
84
|
+
// - Use case: Passing values between contracts in same transaction
|
|
90
85
|
FHE.allowTransient(computed, recipient);
|
|
91
86
|
|
|
92
87
|
return computed;
|
|
@@ -10,28 +10,15 @@ import {
|
|
|
10
10
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @notice Common FHE mistakes and
|
|
13
|
+
* @notice Common FHE mistakes and correct alternatives: branching, permissions, require, loops, noise, deprecated APIs.
|
|
14
14
|
*
|
|
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.
|
|
15
|
+
* @dev Covers 9 critical anti-patterns with ❌ WRONG and ✅ CORRECT examples.
|
|
16
|
+
* This is an educational contract - study each pattern before building production code!
|
|
27
17
|
*/
|
|
28
18
|
contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
29
19
|
euint32 private _secretBalance;
|
|
30
20
|
euint32 private _threshold;
|
|
31
21
|
|
|
32
|
-
// solhint-disable-next-line no-empty-blocks
|
|
33
|
-
constructor() {}
|
|
34
|
-
|
|
35
22
|
/// @notice Initialize the contract with encrypted balance and threshold values
|
|
36
23
|
/// @dev Both values are encrypted and permissions are granted to the caller
|
|
37
24
|
function initialize(
|
|
@@ -48,9 +35,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
48
35
|
FHE.allow(_threshold, msg.sender);
|
|
49
36
|
}
|
|
50
37
|
|
|
51
|
-
// ========================================================================
|
|
52
38
|
// ANTI-PATTERN 1: Branching on encrypted values
|
|
53
|
-
// ========================================================================
|
|
54
39
|
|
|
55
40
|
/**
|
|
56
41
|
* ❌ WRONG: if/else on encrypted value causes decryption
|
|
@@ -90,9 +75,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
90
75
|
FHE.allow(_secretBalance, msg.sender);
|
|
91
76
|
}
|
|
92
77
|
|
|
93
|
-
// ========================================================================
|
|
94
78
|
// ANTI-PATTERN 2: View function returning encrypted without permissions
|
|
95
|
-
// ========================================================================
|
|
96
79
|
|
|
97
80
|
/**
|
|
98
81
|
* @notice ❌ WRONG: Returns encrypted value but caller can't decrypt
|
|
@@ -113,9 +96,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
113
96
|
return _secretBalance;
|
|
114
97
|
}
|
|
115
98
|
|
|
116
|
-
// ========================================================================
|
|
117
99
|
// ANTI-PATTERN 3: Using require/revert with encrypted conditions
|
|
118
|
-
// ========================================================================
|
|
119
100
|
|
|
120
101
|
/**
|
|
121
102
|
* ❌ WRONG: Cannot use require with encrypted values
|
|
@@ -141,9 +122,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
141
122
|
return hasEnough;
|
|
142
123
|
}
|
|
143
124
|
|
|
144
|
-
// ========================================================================
|
|
145
125
|
// ANTI-PATTERN 4: Encrypted computation without permission grants
|
|
146
|
-
// ========================================================================
|
|
147
126
|
|
|
148
127
|
/**
|
|
149
128
|
* @notice ❌ WRONG: Compute but forget to grant permissions
|
|
@@ -166,9 +145,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
166
145
|
FHE.allow(_secretBalance, msg.sender);
|
|
167
146
|
}
|
|
168
147
|
|
|
169
|
-
// ========================================================================
|
|
170
148
|
// ANTI-PATTERN 5: Leaking information through gas/timing
|
|
171
|
-
// ========================================================================
|
|
172
149
|
|
|
173
150
|
/**
|
|
174
151
|
* @notice ⚠️ CAUTION: Be aware of side-channel attacks
|
|
@@ -184,9 +161,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
184
161
|
return "Be aware of gas/timing side channels";
|
|
185
162
|
}
|
|
186
163
|
|
|
187
|
-
// ========================================================================
|
|
188
164
|
// ANTI-PATTERN 6: Unauthenticated Re-encryption (SECURITY CRITICAL)
|
|
189
|
-
// ========================================================================
|
|
190
165
|
|
|
191
166
|
/**
|
|
192
167
|
* @notice ❌ WRONG: Re-encrypt for any provided public key
|
|
@@ -221,9 +196,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
221
196
|
"Use fhevm.js userDecrypt which handles this automatically.";
|
|
222
197
|
}
|
|
223
198
|
|
|
224
|
-
// ========================================================================
|
|
225
199
|
// ANTI-PATTERN 7: Encrypted Loop Iterations (GAS/TIMING LEAK)
|
|
226
|
-
// ========================================================================
|
|
227
200
|
|
|
228
201
|
/// ❌ WRONG: Using encrypted value as loop count
|
|
229
202
|
/// @dev Loop count is visible through gas consumption and timing
|
|
@@ -264,9 +237,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
264
237
|
FHE.allow(accumulator, msg.sender);
|
|
265
238
|
}
|
|
266
239
|
|
|
267
|
-
// ========================================================================
|
|
268
240
|
// ANTI-PATTERN 8: Too Many Chained Operations (Noise Accumulation)
|
|
269
|
-
// ========================================================================
|
|
270
241
|
|
|
271
242
|
/// @notice ⚠️ CAUTION: FHE operations accumulate "noise"
|
|
272
243
|
/// @dev Each FHE operation adds noise to the ciphertext. After too many
|
|
@@ -285,9 +256,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
285
256
|
"If you need many operations, consider batching or restructuring logic.";
|
|
286
257
|
}
|
|
287
258
|
|
|
288
|
-
// ========================================================================
|
|
289
259
|
// ANTI-PATTERN 9: Using Deprecated FHEVM APIs
|
|
290
|
-
// ========================================================================
|
|
291
260
|
|
|
292
261
|
/**
|
|
293
262
|
* @notice Caution about deprecated FHEVM APIs
|
|
@@ -306,9 +275,7 @@ contract FHEAntiPatterns is ZamaEthereumConfig {
|
|
|
306
275
|
"or userDecrypt pattern via fhevm.js for user decryption.";
|
|
307
276
|
}
|
|
308
277
|
|
|
309
|
-
//
|
|
310
|
-
// SUMMARY: Key Rules (Always check before deploying!)
|
|
311
|
-
// ========================================================================
|
|
278
|
+
// SUMMARY: Key Rules
|
|
312
279
|
|
|
313
280
|
/**
|
|
314
281
|
* @notice Quick reference for FHE best practices
|
|
@@ -5,24 +5,18 @@ 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 Understanding FHE handles: creation, computation,
|
|
8
|
+
* @notice Understanding FHE handles: creation, computation, and immut ability.
|
|
9
9
|
*
|
|
10
|
-
* @dev Handle = uint256 pointer to encrypted data
|
|
11
|
-
*
|
|
10
|
+
* @dev Handle = uint256 pointer to encrypted data. Operations create NEW handles (immutable).
|
|
11
|
+
* ⚡ Gas: asEuint32 ~20k, fromExternal ~50k, add/sub ~100k
|
|
12
12
|
*/
|
|
13
13
|
contract FHEHandles is ZamaEthereumConfig {
|
|
14
|
-
// 🔐 Storage handles - persist across transactions
|
|
15
|
-
// These are just uint256 pointers, actual ciphertext is off-chain
|
|
16
14
|
euint32 private _storedValue;
|
|
17
15
|
euint32 private _computedValue;
|
|
18
16
|
|
|
19
17
|
event HandleCreated(string operation, uint256 gasUsed);
|
|
20
18
|
event HandleStored(string description);
|
|
21
19
|
|
|
22
|
-
constructor() {}
|
|
23
|
-
|
|
24
|
-
// ==================== HANDLE CREATION ====================
|
|
25
|
-
|
|
26
20
|
/// @notice Pattern 1: Create handle from user's encrypted input
|
|
27
21
|
function createFromExternal(
|
|
28
22
|
externalEuint32 input,
|
|
@@ -30,8 +24,7 @@ contract FHEHandles is ZamaEthereumConfig {
|
|
|
30
24
|
) external {
|
|
31
25
|
uint256 gasBefore = gasleft();
|
|
32
26
|
|
|
33
|
-
//
|
|
34
|
-
// The proof is verified automatically
|
|
27
|
+
// fromExternal: validates proof and creates internal handle
|
|
35
28
|
_storedValue = FHE.fromExternal(input, inputProof);
|
|
36
29
|
|
|
37
30
|
emit HandleCreated("fromExternal", gasBefore - gasleft());
|
|
@@ -45,8 +38,7 @@ contract FHEHandles is ZamaEthereumConfig {
|
|
|
45
38
|
function createFromPlaintext(uint32 plaintextValue) external {
|
|
46
39
|
uint256 gasBefore = gasleft();
|
|
47
40
|
|
|
48
|
-
//
|
|
49
|
-
// Use for: thresholds, comparison values, zero-initialization
|
|
41
|
+
// asEuint32: encrypts a public constant (visible on-chain!)
|
|
50
42
|
_storedValue = FHE.asEuint32(plaintextValue);
|
|
51
43
|
|
|
52
44
|
emit HandleCreated("asEuint32", gasBefore - gasleft());
|
|
@@ -55,23 +47,18 @@ contract FHEHandles is ZamaEthereumConfig {
|
|
|
55
47
|
FHE.allow(_storedValue, msg.sender);
|
|
56
48
|
}
|
|
57
49
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
/// @notice Key insight: FHE operations create NEW handles
|
|
61
|
-
/// @dev Original handles are IMMUTABLE - they never change
|
|
50
|
+
/// @notice ⚠️ Key insight: FHE operations create NEW handles (immutable!)
|
|
62
51
|
function computeNewHandle() external {
|
|
63
52
|
uint256 gasBefore = gasleft();
|
|
64
53
|
|
|
65
54
|
euint32 constant10 = FHE.asEuint32(10);
|
|
66
55
|
|
|
67
|
-
// 🔄 FHE
|
|
68
|
-
// _storedValue handle is UNCHANGED
|
|
69
|
-
// _computedValue gets the NEW handle
|
|
56
|
+
// 🔄 Why NEW handle? FHE values are immutable - operations always create new ones
|
|
70
57
|
_computedValue = FHE.add(_storedValue, constant10);
|
|
71
58
|
|
|
72
59
|
emit HandleCreated("add (new handle)", gasBefore - gasleft());
|
|
73
60
|
|
|
74
|
-
//
|
|
61
|
+
// Must grant permissions for NEW handle
|
|
75
62
|
FHE.allowThis(_computedValue);
|
|
76
63
|
FHE.allow(_computedValue, msg.sender);
|
|
77
64
|
|
|
@@ -94,18 +81,14 @@ contract FHEHandles is ZamaEthereumConfig {
|
|
|
94
81
|
FHE.allow(_computedValue, msg.sender);
|
|
95
82
|
}
|
|
96
83
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
/// @notice Demonstrates: updating a variable creates NEW handle
|
|
84
|
+
/// @notice Demonstrates: updating variable creates NEW handle
|
|
100
85
|
function demonstrateImmutability()
|
|
101
86
|
external
|
|
102
87
|
returns (euint32 original, euint32 updated)
|
|
103
88
|
{
|
|
104
|
-
// 📌 Save reference to current handle
|
|
105
89
|
euint32 originalHandle = _storedValue;
|
|
106
90
|
|
|
107
|
-
//
|
|
108
|
-
// originalHandle still points to OLD value!
|
|
91
|
+
// This creates NEW handle! originalHandle still points to OLD value
|
|
109
92
|
_storedValue = FHE.add(_storedValue, FHE.asEuint32(100));
|
|
110
93
|
|
|
111
94
|
FHE.allowThis(_storedValue);
|
|
@@ -116,8 +99,6 @@ contract FHEHandles is ZamaEthereumConfig {
|
|
|
116
99
|
return (originalHandle, _storedValue);
|
|
117
100
|
}
|
|
118
101
|
|
|
119
|
-
// ==================== GETTERS ====================
|
|
120
|
-
|
|
121
102
|
function getStoredValue() external view returns (euint32) {
|
|
122
103
|
return _storedValue;
|
|
123
104
|
}
|
|
@@ -11,68 +11,46 @@ import {
|
|
|
11
11
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* @notice Explains input proof validation in FHEVM:
|
|
14
|
+
* @notice Explains input proof validation in FHEVM: why proofs matter and how to batch inputs efficiently.
|
|
15
15
|
*
|
|
16
|
-
* @dev
|
|
17
|
-
*
|
|
18
|
-
* 2. Value is in valid range for the type
|
|
19
|
-
* 3. Sender knows the plaintext (proof of knowledge)
|
|
16
|
+
* @dev Proofs ensure: valid ciphertext + correct range + proof of knowledge.
|
|
17
|
+
* ⚡ Gas: Batching multiple values in ONE proof saves ~50k gas vs separate proofs!
|
|
20
18
|
*/
|
|
21
19
|
contract FHEInputProof is ZamaEthereumConfig {
|
|
22
20
|
euint32 private _singleValue;
|
|
23
21
|
euint32 private _valueA;
|
|
24
22
|
euint64 private _valueB;
|
|
25
23
|
|
|
26
|
-
constructor() {}
|
|
27
|
-
|
|
28
|
-
// ==================== SINGLE INPUT ====================
|
|
29
|
-
|
|
30
24
|
/// @notice Receive single encrypted value with proof
|
|
31
25
|
function setSingleValue(
|
|
32
26
|
externalEuint32 encryptedInput,
|
|
33
27
|
bytes calldata inputProof
|
|
34
28
|
) external {
|
|
35
|
-
//
|
|
36
|
-
// If proof is invalid → transaction reverts
|
|
37
|
-
// Proof ensures:
|
|
38
|
-
// ✓ Valid euint32 ciphertext
|
|
39
|
-
// ✓ Encrypted for THIS contract
|
|
40
|
-
// ✓ Sender knows the plaintext
|
|
29
|
+
// 🔐 Why proof needed? Prevents: garbage data, wrong type, replay attacks
|
|
41
30
|
_singleValue = FHE.fromExternal(encryptedInput, inputProof);
|
|
42
31
|
|
|
43
32
|
FHE.allowThis(_singleValue);
|
|
44
33
|
FHE.allow(_singleValue, msg.sender);
|
|
45
34
|
}
|
|
46
35
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
/// @notice Receive multiple encrypted values with SINGLE proof
|
|
50
|
-
/// @dev Client-side batching is more gas-efficient!
|
|
51
|
-
///
|
|
52
|
-
/// Client code:
|
|
53
|
-
/// const input = fhevm.createEncryptedInput(contractAddr, userAddr);
|
|
54
|
-
/// input.add32(valueA); // → handles[0]
|
|
55
|
-
/// input.add64(valueB); // → handles[1]
|
|
56
|
-
/// const enc = await input.encrypt();
|
|
57
|
-
/// // enc.inputProof covers BOTH values!
|
|
36
|
+
/// @notice Receive multiple values with SINGLE proof (gas efficient!)
|
|
37
|
+
/// @dev Client batches: input.add32(a).add64(b).encrypt() → one proof for both!
|
|
58
38
|
function setMultipleValues(
|
|
59
39
|
externalEuint32 inputA,
|
|
60
40
|
externalEuint64 inputB,
|
|
61
|
-
bytes calldata inputProof //
|
|
41
|
+
bytes calldata inputProof // Single proof covers both!
|
|
62
42
|
) external {
|
|
63
|
-
//
|
|
43
|
+
// 💡 Same proof validates both - saves ~50k gas!
|
|
64
44
|
_valueA = FHE.fromExternal(inputA, inputProof);
|
|
65
45
|
_valueB = FHE.fromExternal(inputB, inputProof);
|
|
66
46
|
|
|
67
|
-
//
|
|
47
|
+
// Each value needs own permissions
|
|
68
48
|
FHE.allowThis(_valueA);
|
|
69
49
|
FHE.allowThis(_valueB);
|
|
70
50
|
FHE.allow(_valueA, msg.sender);
|
|
71
51
|
FHE.allow(_valueB, msg.sender);
|
|
72
52
|
}
|
|
73
53
|
|
|
74
|
-
// ==================== COMPUTATION WITH NEW INPUT ====================
|
|
75
|
-
|
|
76
54
|
/// @notice Add new encrypted input to existing stored value
|
|
77
55
|
function addToValue(
|
|
78
56
|
externalEuint32 addend,
|
|
@@ -88,8 +66,6 @@ contract FHEInputProof is ZamaEthereumConfig {
|
|
|
88
66
|
FHE.allow(_singleValue, msg.sender);
|
|
89
67
|
}
|
|
90
68
|
|
|
91
|
-
// ==================== GETTERS ====================
|
|
92
|
-
|
|
93
69
|
function getSingleValue() external view returns (euint32) {
|
|
94
70
|
return _singleValue;
|
|
95
71
|
}
|