create-fhevm-example 1.4.5 → 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/concepts/antipatterns/ControlFlow.sol +160 -0
- package/contracts/concepts/antipatterns/OperationsGasNoise.sol +190 -0
- package/contracts/concepts/antipatterns/Permissions.sol +254 -0
- 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-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 +41 -12
- 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 -300
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
|
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
FHE,
|
|
6
|
+
euint32,
|
|
7
|
+
euint256,
|
|
8
|
+
externalEuint32
|
|
9
|
+
} from "@fhevm/solidity/lib/FHE.sol";
|
|
10
|
+
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @notice Operations, gas, and noise anti-patterns in FHE development.
|
|
14
|
+
* Demonstrates performance issues, side-channel leaks, and
|
|
15
|
+
* inefficient encrypted computation patterns.
|
|
16
|
+
*
|
|
17
|
+
* @dev Covers 4 patterns: gas/timing side channels, noise accumulation,
|
|
18
|
+
* deprecated APIs, and type mismatches.
|
|
19
|
+
*/
|
|
20
|
+
contract FHEOperationsGasNoiseAntiPatterns is ZamaEthereumConfig {
|
|
21
|
+
euint32 private _secretValue;
|
|
22
|
+
|
|
23
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
24
|
+
// ANTI-PATTERN 1: Gas/Timing Side Channel Attacks
|
|
25
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* ❌ WRONG: Different code paths have different gas costs
|
|
29
|
+
* @dev Gas consumption reveals which branch was taken
|
|
30
|
+
*/
|
|
31
|
+
function wrongGasLeak(
|
|
32
|
+
externalEuint32 input,
|
|
33
|
+
bytes calldata inputProof
|
|
34
|
+
) external {
|
|
35
|
+
euint32 value = FHE.fromExternal(input, inputProof);
|
|
36
|
+
|
|
37
|
+
// ❌ These operations have different gas costs!
|
|
38
|
+
// If value > 100: expensive operation
|
|
39
|
+
// If value <= 100: cheap operation
|
|
40
|
+
// Gas cost reveals the comparison result!
|
|
41
|
+
|
|
42
|
+
// Simulating different paths (commented to avoid actual leak)
|
|
43
|
+
// if (decrypt(value > 100)) {
|
|
44
|
+
// // Expensive: 10 multiplications
|
|
45
|
+
// } else {
|
|
46
|
+
// // Cheap: 1 addition
|
|
47
|
+
// }
|
|
48
|
+
|
|
49
|
+
_secretValue = value;
|
|
50
|
+
FHE.allowThis(_secretValue);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* ✅ CORRECT: Constant-time operations
|
|
55
|
+
* @dev All paths consume same gas
|
|
56
|
+
*/
|
|
57
|
+
function correctConstantTime(
|
|
58
|
+
externalEuint32 input,
|
|
59
|
+
bytes calldata inputProof
|
|
60
|
+
) external {
|
|
61
|
+
euint32 value = FHE.fromExternal(input, inputProof);
|
|
62
|
+
|
|
63
|
+
// ✅ Always perform same operations regardless of value
|
|
64
|
+
euint32 result = FHE.mul(value, FHE.asEuint32(2));
|
|
65
|
+
|
|
66
|
+
_secretValue = result;
|
|
67
|
+
FHE.allowThis(_secretValue);
|
|
68
|
+
FHE.allow(_secretValue, msg.sender);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
72
|
+
// ANTI-PATTERN 2: Noise Accumulation (Too Many Operations)
|
|
73
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* ❌ WRONG: Long chain of FHE operations
|
|
77
|
+
* @dev Each operation adds noise, eventually corrupting ciphertext
|
|
78
|
+
*/
|
|
79
|
+
function wrongNoiseAccumulation(
|
|
80
|
+
externalEuint32 input,
|
|
81
|
+
bytes calldata inputProof
|
|
82
|
+
) external {
|
|
83
|
+
euint32 value = FHE.fromExternal(input, inputProof);
|
|
84
|
+
|
|
85
|
+
// ❌ Too many chained operations!
|
|
86
|
+
// Each mul adds significant noise
|
|
87
|
+
euint32 result = value;
|
|
88
|
+
for (uint i = 0; i < 5; i++) {
|
|
89
|
+
result = FHE.mul(result, FHE.asEuint32(2)); // High noise per operation
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
_secretValue = result;
|
|
93
|
+
FHE.allowThis(_secretValue);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* ✅ CORRECT: Minimize operation chains
|
|
98
|
+
* @dev Use mathematical optimization to reduce operations
|
|
99
|
+
*/
|
|
100
|
+
function correctMinimizeOperations(
|
|
101
|
+
externalEuint32 input,
|
|
102
|
+
bytes calldata inputProof
|
|
103
|
+
) external {
|
|
104
|
+
euint32 value = FHE.fromExternal(input, inputProof);
|
|
105
|
+
|
|
106
|
+
// ✅ Instead of 5 multiplications by 2, use single multiplication
|
|
107
|
+
// 2^5 = 32
|
|
108
|
+
euint32 result = FHE.mul(value, FHE.asEuint32(32));
|
|
109
|
+
|
|
110
|
+
_secretValue = result;
|
|
111
|
+
FHE.allowThis(_secretValue);
|
|
112
|
+
FHE.allow(_secretValue, msg.sender);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
116
|
+
// ANTI-PATTERN 3: Using Deprecated FHEVM APIs
|
|
117
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* ❌ WRONG: Using old TFHE.decrypt() pattern
|
|
121
|
+
* @dev Deprecated in FHEVM v0.9+
|
|
122
|
+
*/
|
|
123
|
+
function wrongDeprecatedAPI() external pure returns (string memory) {
|
|
124
|
+
// ❌ OLD (v0.8 and earlier):
|
|
125
|
+
// TFHE.decrypt() - went through Zama Oracle
|
|
126
|
+
//
|
|
127
|
+
// This pattern is deprecated and no longer supported
|
|
128
|
+
|
|
129
|
+
return "Don't use TFHE.decrypt() - it's deprecated";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* ✅ CORRECT: Use modern FHEVM v0.9+ APIs
|
|
134
|
+
* @dev Use FHE.makePubliclyDecryptable() or userDecrypt pattern
|
|
135
|
+
*/
|
|
136
|
+
function correctModernAPI() external pure returns (string memory) {
|
|
137
|
+
// ✅ NEW (v0.9+):
|
|
138
|
+
// - FHE.makePubliclyDecryptable() for public decryption
|
|
139
|
+
// - userDecrypt pattern via fhevm.js for user decryption
|
|
140
|
+
|
|
141
|
+
return "Use FHE.makePubliclyDecryptable() or fhevm.js userDecrypt()";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
145
|
+
// ANTI-PATTERN 4: Unnecessary Large Data Types
|
|
146
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* ❌ WRONG: Using euint256 when euint32 is sufficient
|
|
150
|
+
* @dev Larger types = more gas + more noise
|
|
151
|
+
*/
|
|
152
|
+
function wrongOversizedType(
|
|
153
|
+
externalEuint32 input,
|
|
154
|
+
bytes calldata inputProof
|
|
155
|
+
) external {
|
|
156
|
+
euint32 value = FHE.fromExternal(input, inputProof);
|
|
157
|
+
|
|
158
|
+
// ❌ Unnecessarily converting to euint256!
|
|
159
|
+
// euint256 operations are limited and more expensive
|
|
160
|
+
// This conversion is wasteful when euint32 would suffice
|
|
161
|
+
euint256 largeValue = FHE.asEuint256(value);
|
|
162
|
+
|
|
163
|
+
// Converting back to euint32 (double conversion waste!)
|
|
164
|
+
_secretValue = FHE.asEuint32(largeValue);
|
|
165
|
+
FHE.allowThis(_secretValue);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* ✅ CORRECT: Use smallest sufficient data type
|
|
170
|
+
* @dev euint32 is cheaper than euint256
|
|
171
|
+
*/
|
|
172
|
+
function correctRightSizedType(
|
|
173
|
+
externalEuint32 input,
|
|
174
|
+
bytes calldata inputProof
|
|
175
|
+
) external {
|
|
176
|
+
euint32 value = FHE.fromExternal(input, inputProof);
|
|
177
|
+
|
|
178
|
+
// ✅ Keep using euint32 if values fit
|
|
179
|
+
euint32 result = FHE.mul(value, FHE.asEuint32(2));
|
|
180
|
+
|
|
181
|
+
_secretValue = result;
|
|
182
|
+
FHE.allowThis(_secretValue);
|
|
183
|
+
FHE.allow(_secretValue, msg.sender);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// @notice Helper to get value for testing
|
|
187
|
+
function getValue() external view returns (euint32) {
|
|
188
|
+
return _secretValue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import {FHE, euint32, externalEuint32} from "@fhevm/solidity/lib/FHE.sol";
|
|
5
|
+
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @notice Permission management anti-patterns in FHE development.
|
|
9
|
+
* Demonstrates common mistakes with allowThis, allow, and
|
|
10
|
+
* permission propagation across transfers and contracts.
|
|
11
|
+
*
|
|
12
|
+
* @dev Covers 6 critical patterns: missing allowThis, missing allow(user),
|
|
13
|
+
* view functions without permissions, unauthenticated re-encryption,
|
|
14
|
+
* transfer permission issues, and cross-contract delegation.
|
|
15
|
+
*/
|
|
16
|
+
contract FHEPermissionsAntiPatterns is ZamaEthereumConfig {
|
|
17
|
+
euint32 private _secretValue;
|
|
18
|
+
mapping(address => euint32) private _balances;
|
|
19
|
+
|
|
20
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
21
|
+
// ANTI-PATTERN 1: Missing allowThis After Computation
|
|
22
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* ❌ WRONG: Compute but forget allowThis
|
|
26
|
+
* @dev Result exists but contract can't use it in future operations
|
|
27
|
+
*/
|
|
28
|
+
function wrongMissingAllowThis(
|
|
29
|
+
externalEuint32 input,
|
|
30
|
+
bytes calldata inputProof
|
|
31
|
+
) external {
|
|
32
|
+
_secretValue = FHE.fromExternal(input, inputProof);
|
|
33
|
+
euint32 doubled = FHE.mul(_secretValue, FHE.asEuint32(2));
|
|
34
|
+
_secretValue = doubled;
|
|
35
|
+
|
|
36
|
+
// ❌ Missing FHE.allowThis! Contract can't use this value later
|
|
37
|
+
FHE.allow(_secretValue, msg.sender);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* ✅ CORRECT: Always grant allowThis after computation
|
|
42
|
+
* @dev Contract needs permission to use encrypted values
|
|
43
|
+
*/
|
|
44
|
+
function correctWithAllowThis(
|
|
45
|
+
externalEuint32 input,
|
|
46
|
+
bytes calldata inputProof
|
|
47
|
+
) external {
|
|
48
|
+
_secretValue = FHE.fromExternal(input, inputProof);
|
|
49
|
+
euint32 doubled = FHE.mul(_secretValue, FHE.asEuint32(2));
|
|
50
|
+
_secretValue = doubled;
|
|
51
|
+
|
|
52
|
+
FHE.allowThis(_secretValue);
|
|
53
|
+
FHE.allow(_secretValue, msg.sender);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
57
|
+
// ANTI-PATTERN 2: Missing allow(user)
|
|
58
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* ❌ WRONG: Only allowThis without user permission
|
|
62
|
+
* @dev No one can decrypt the value
|
|
63
|
+
*/
|
|
64
|
+
function wrongMissingUserAllow(
|
|
65
|
+
externalEuint32 input,
|
|
66
|
+
bytes calldata inputProof
|
|
67
|
+
) external {
|
|
68
|
+
_secretValue = FHE.fromExternal(input, inputProof);
|
|
69
|
+
|
|
70
|
+
// ❌ Contract can compute but no one can decrypt!
|
|
71
|
+
FHE.allowThis(_secretValue);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* ✅ CORRECT: Grant both allowThis and allow(user)
|
|
76
|
+
* @dev User can decrypt after contract operations
|
|
77
|
+
*/
|
|
78
|
+
function correctWithUserAllow(
|
|
79
|
+
externalEuint32 input,
|
|
80
|
+
bytes calldata inputProof
|
|
81
|
+
) external {
|
|
82
|
+
_secretValue = FHE.fromExternal(input, inputProof);
|
|
83
|
+
|
|
84
|
+
FHE.allowThis(_secretValue);
|
|
85
|
+
FHE.allow(_secretValue, msg.sender);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
89
|
+
// ANTI-PATTERN 3: View Function Without Permissions
|
|
90
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* ❌ WRONG: Store value without granting permission to caller
|
|
94
|
+
* @dev When caller tries to get value via view, they can't decrypt it
|
|
95
|
+
*/
|
|
96
|
+
function wrongStoreWithoutPermission(
|
|
97
|
+
externalEuint32 input,
|
|
98
|
+
bytes calldata inputProof
|
|
99
|
+
) external {
|
|
100
|
+
_secretValue = FHE.fromExternal(input, inputProof);
|
|
101
|
+
|
|
102
|
+
// ❌ Only allowThis, caller has no permission!
|
|
103
|
+
FHE.allowThis(_secretValue);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* ✅ CORRECT: Grant permission to caller when storing
|
|
108
|
+
* @dev Caller can now decrypt value returned from view function
|
|
109
|
+
*/
|
|
110
|
+
function correctStoreWithPermission(
|
|
111
|
+
externalEuint32 input,
|
|
112
|
+
bytes calldata inputProof
|
|
113
|
+
) external {
|
|
114
|
+
_secretValue = FHE.fromExternal(input, inputProof);
|
|
115
|
+
|
|
116
|
+
FHE.allowThis(_secretValue);
|
|
117
|
+
FHE.allow(_secretValue, msg.sender); // ✅ Grant permission!
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/// @notice View function to get stored value
|
|
121
|
+
function getValue() external view returns (euint32) {
|
|
122
|
+
return _secretValue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
126
|
+
// ANTI-PATTERN 4: Unauthenticated Re-encryption
|
|
127
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* ❌ WRONG: Re-encrypt without verifying public key ownership
|
|
131
|
+
* @dev Anyone can provide any public key and steal encrypted data
|
|
132
|
+
*/
|
|
133
|
+
function wrongReencryptWithoutAuth(
|
|
134
|
+
bytes32 publicKey
|
|
135
|
+
) external view returns (bytes memory) {
|
|
136
|
+
// ❌ SECURITY RISK: No verification that caller owns this public key!
|
|
137
|
+
// Attacker can provide victim's public key and get their data
|
|
138
|
+
|
|
139
|
+
// This would allow impersonation attacks:
|
|
140
|
+
// return Gateway.reencrypt(_secretValue, publicKey);
|
|
141
|
+
|
|
142
|
+
return ""; // Placeholder
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* ✅ CORRECT: Use FHEVM's built-in authentication
|
|
147
|
+
* @dev fhevm.js SDK verifies EIP-712 signature automatically
|
|
148
|
+
* Only the owner of the public key can decrypt
|
|
149
|
+
*/
|
|
150
|
+
function correctReencryptWithAuth() external view returns (euint32) {
|
|
151
|
+
// ✅ Return handle directly
|
|
152
|
+
// Client uses fhevm.instance.reencrypt() which:
|
|
153
|
+
// 1. Signs request with their private key (EIP-712)
|
|
154
|
+
// 2. Gateway verifies signature matches public key
|
|
155
|
+
// 3. Only then re-encrypts for that public key
|
|
156
|
+
|
|
157
|
+
return _secretValue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
161
|
+
// ANTI-PATTERN 5: Transfer Without Permission Propagation
|
|
162
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @notice Initialize balance for msg.sender
|
|
166
|
+
* @dev Required before using transfer functions
|
|
167
|
+
*/
|
|
168
|
+
function initializeBalance(
|
|
169
|
+
externalEuint32 initialBalance,
|
|
170
|
+
bytes calldata inputProof
|
|
171
|
+
) external {
|
|
172
|
+
_balances[msg.sender] = FHE.fromExternal(initialBalance, inputProof);
|
|
173
|
+
FHE.allowThis(_balances[msg.sender]);
|
|
174
|
+
FHE.allow(_balances[msg.sender], msg.sender);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* ❌ WRONG: Transfer without granting permissions
|
|
179
|
+
* @dev Recipient gets balance but can't use or decrypt it
|
|
180
|
+
*/
|
|
181
|
+
function wrongTransferWithoutPermission(
|
|
182
|
+
address recipient,
|
|
183
|
+
externalEuint32 amount,
|
|
184
|
+
bytes calldata inputProof
|
|
185
|
+
) external {
|
|
186
|
+
euint32 transferAmount = FHE.fromExternal(amount, inputProof);
|
|
187
|
+
|
|
188
|
+
_balances[msg.sender] = FHE.sub(_balances[msg.sender], transferAmount);
|
|
189
|
+
_balances[recipient] = FHE.add(_balances[recipient], transferAmount);
|
|
190
|
+
|
|
191
|
+
// ❌ Recipient has no permission to use their new balance!
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* ✅ CORRECT: Grant permissions after transfer
|
|
196
|
+
* @dev Both parties can use and decrypt their updated balances
|
|
197
|
+
*/
|
|
198
|
+
function correctTransferWithPermission(
|
|
199
|
+
address recipient,
|
|
200
|
+
externalEuint32 amount,
|
|
201
|
+
bytes calldata inputProof
|
|
202
|
+
) external {
|
|
203
|
+
euint32 transferAmount = FHE.fromExternal(amount, inputProof);
|
|
204
|
+
|
|
205
|
+
_balances[msg.sender] = FHE.sub(_balances[msg.sender], transferAmount);
|
|
206
|
+
_balances[recipient] = FHE.add(_balances[recipient], transferAmount);
|
|
207
|
+
|
|
208
|
+
// ✅ Grant permissions to both parties
|
|
209
|
+
FHE.allowThis(_balances[msg.sender]);
|
|
210
|
+
FHE.allow(_balances[msg.sender], msg.sender);
|
|
211
|
+
FHE.allowThis(_balances[recipient]);
|
|
212
|
+
FHE.allow(_balances[recipient], recipient);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
216
|
+
// ANTI-PATTERN 6: Cross-Contract Permission Delegation
|
|
217
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* ❌ WRONG: Call another contract without granting permission
|
|
221
|
+
* @dev Other contract can't use the encrypted value
|
|
222
|
+
*/
|
|
223
|
+
function wrongCrossContractCall(address processor) external returns (bool) {
|
|
224
|
+
// ❌ processor contract has no permission to use _secretValue!
|
|
225
|
+
// This call will fail or return garbage
|
|
226
|
+
(bool success, ) = processor.call(
|
|
227
|
+
abi.encodeWithSignature("process(uint256)", _secretValue)
|
|
228
|
+
);
|
|
229
|
+
return success;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* ✅ CORRECT: Grant temporary permission before cross-contract call
|
|
234
|
+
* @dev Use allowTransient for gas-efficient temporary access
|
|
235
|
+
*/
|
|
236
|
+
function correctCrossContractCall(
|
|
237
|
+
address processor
|
|
238
|
+
) external returns (bool) {
|
|
239
|
+
// ✅ Grant temporary permission (expires at end of transaction)
|
|
240
|
+
FHE.allowTransient(_secretValue, processor);
|
|
241
|
+
|
|
242
|
+
// Now processor can use _secretValue in this transaction
|
|
243
|
+
(bool success, ) = processor.call(
|
|
244
|
+
abi.encodeWithSignature("process(uint256)", _secretValue)
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
return success;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/// @notice Helper to get balance for testing
|
|
251
|
+
function getBalance(address user) external view returns (euint32) {
|
|
252
|
+
return _balances[user];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add-mode.d.ts","sourceRoot":"","sources":["../../../scripts/commands/add-mode.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"add-mode.d.ts","sourceRoot":"","sources":["../../../scripts/commands/add-mode.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAuOH;;GAEG;AACH,wBAAsB,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8ElE"}
|