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
|
@@ -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
|
+
}
|
|
@@ -11,7 +11,11 @@ import {
|
|
|
11
11
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* @notice
|
|
14
|
+
* @notice Provably fair lottery with encrypted ticket numbers.
|
|
15
|
+
* Players buy tickets with encrypted numbers. Winning number is generated
|
|
16
|
+
* using FHE randomness. Winner is determined by comparing encrypted values
|
|
17
|
+
* without revealing losing tickets. Ensures fairness and privacy - no one
|
|
18
|
+
* can see ticket numbers before the draw.
|
|
15
19
|
*
|
|
16
20
|
* @dev Flow: buyTicket() → startDrawing() → checkAndClaim() → revealWinner()
|
|
17
21
|
* ⚡ Gas: Loop in checkAndClaim can be expensive with many tickets!
|
|
@@ -11,7 +11,11 @@ import {
|
|
|
11
11
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* @notice
|
|
14
|
+
* @notice On-chain Texas Hold'em poker with encrypted hole cards.
|
|
15
|
+
* Two players receive encrypted hole cards that remain hidden throughout
|
|
16
|
+
* the game. Hand strength is computed using FHE operations. Winner is
|
|
17
|
+
* determined by comparing encrypted hand strengths. Demonstrates complex
|
|
18
|
+
* game logic with multiple encrypted states and conditional operations.
|
|
15
19
|
*
|
|
16
20
|
* @dev Flow: joinGame() → bet()/fold() → showdown() → revealWinner()
|
|
17
21
|
* Hand strength = card1 + card2 (simplified for demo)
|
|
@@ -5,7 +5,11 @@ import {FHE, euint8, ebool, externalEuint8} from "@fhevm/solidity/lib/FHE.sol";
|
|
|
5
5
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* @notice Rock-Paper-Scissors game with encrypted moves
|
|
8
|
+
* @notice Fair Rock-Paper-Scissors game with encrypted moves.
|
|
9
|
+
* Players submit encrypted moves (0=Rock, 1=Paper, 2=Scissors) ensuring
|
|
10
|
+
* neither player can see the other's choice before committing. Winner is
|
|
11
|
+
* determined using FHE operations and revealed publicly. No trusted third
|
|
12
|
+
* party needed - cryptography guarantees fairness.
|
|
9
13
|
|
|
10
14
|
* @dev Commit-reveal pattern without trusted third party.
|
|
11
15
|
* Move encoding: 0=Rock, 1=Paper, 2=Scissors
|
|
@@ -12,7 +12,12 @@ import {
|
|
|
12
12
|
} from "@openzeppelin/confidential-contracts/token/ERC7984/ERC7984.sol";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* @notice Confidential token using OpenZeppelin's ERC7984 standard
|
|
15
|
+
* @notice Confidential ERC20-compatible token using OpenZeppelin's ERC7984 standard.
|
|
16
|
+
* Implements a fully private token where balances and transfer amounts
|
|
17
|
+
* are encrypted. Compatible with standard ERC20 interfaces but with FHE
|
|
18
|
+
* under the hood. Supports both visible minting (owner knows amount) and
|
|
19
|
+
* confidential minting (fully private). Foundation for building private
|
|
20
|
+
* DeFi applications.
|
|
16
21
|
*
|
|
17
22
|
* @dev Demonstrates minting and burning with both visible and encrypted amounts.
|
|
18
23
|
* Shows how to integrate FHE with standard token operations.
|
|
@@ -9,7 +9,11 @@ import {
|
|
|
9
9
|
} from "@openzeppelin/confidential-contracts/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* @notice
|
|
12
|
+
* @notice Bridge between public ERC20 and confidential ERC7984 tokens.
|
|
13
|
+
* Allows users to wrap regular ERC20 tokens into privacy-preserving
|
|
14
|
+
* ERC7984 tokens (public → private) and unwrap them back (private → public).
|
|
15
|
+
* Wrapping is instant, unwrapping requires decryption proof from KMS.
|
|
16
|
+
* Essential for bringing existing tokens into the confidential ecosystem.
|
|
13
17
|
*
|
|
14
18
|
* @dev WRAP: ERC20 → ERC7984 (public → private)
|
|
15
19
|
* UNWRAP: ERC7984 → ERC20 (private → public, requires decryption)
|
|
@@ -12,7 +12,11 @@ import {
|
|
|
12
12
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* @notice
|
|
15
|
+
* @notice Atomic swap from confidential ERC7984 to public ERC20 tokens.
|
|
16
|
+
* Two-step process: (1) Initiate swap with encrypted amount, request
|
|
17
|
+
* decryption from KMS. (2) Finalize swap with decryption proof, receive
|
|
18
|
+
* ERC20 tokens. Demonstrates the FHEVM v0.9 public decryption flow with
|
|
19
|
+
* makePubliclyDecryptable() and checkSignatures() for trustless swaps.
|
|
16
20
|
*
|
|
17
21
|
* @dev Uses FHEVM v0.9 decryption flow:
|
|
18
22
|
* FHE.makePubliclyDecryptable() + FHE.checkSignatures()
|
|
@@ -8,7 +8,11 @@ import {
|
|
|
8
8
|
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* @notice Fully
|
|
11
|
+
* @notice Fully private atomic swap between two confidential ERC7984 tokens.
|
|
12
|
+
* Both input and output amounts remain encrypted throughout the entire
|
|
13
|
+
* swap process. No decryption needed - amounts stay private from start
|
|
14
|
+
* to finish. Perfect for confidential DEX operations where trade sizes
|
|
15
|
+
* must remain hidden. The ultimate privacy-preserving token exchange.
|
|
12
16
|
*
|
|
13
17
|
* @dev Both input and output amounts remain encrypted throughout the swap.
|
|
14
18
|
*/
|
|
@@ -12,7 +12,12 @@ import {
|
|
|
12
12
|
} from "@openzeppelin/confidential-contracts/interfaces/IERC7984.sol";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* @notice
|
|
15
|
+
* @notice Time-locked vesting wallet with fully encrypted token amounts.
|
|
16
|
+
* Implements linear vesting for ERC7984 confidential tokens. Vesting
|
|
17
|
+
* schedule, amounts, and release calculations all happen on encrypted
|
|
18
|
+
* values using FHE operations. Beneficiary can release vested tokens
|
|
19
|
+
* over time without revealing the total allocation or vesting progress
|
|
20
|
+
* to observers.
|
|
16
21
|
|
|
17
22
|
* @dev Timeline: |--START--|---VESTING---|--END--| (0% → linear → 100%)
|
|
18
23
|
* All vesting calculations performed on encrypted values using FHE.
|
|
@@ -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"}
|
|
@@ -58,7 +58,7 @@ const utils_1 = require("../shared/utils");
|
|
|
58
58
|
function detectHardhatProject(targetDir) {
|
|
59
59
|
const packageJsonPath = path.join(targetDir, "package.json");
|
|
60
60
|
const hardhatConfigTs = path.join(targetDir, "hardhat.config.ts");
|
|
61
|
-
const hardhatConfigJs = path.join(targetDir, "hardhat.config");
|
|
61
|
+
const hardhatConfigJs = path.join(targetDir, "hardhat.config.js");
|
|
62
62
|
if (!fs.existsSync(packageJsonPath)) {
|
|
63
63
|
return false;
|
|
64
64
|
}
|
|
@@ -106,7 +106,7 @@ function updateHardhatConfig(targetDir) {
|
|
|
106
106
|
? configPathJs
|
|
107
107
|
: null;
|
|
108
108
|
if (!actualPath) {
|
|
109
|
-
throw new Error(
|
|
109
|
+
throw new Error(utils_1.ERROR_MESSAGES.CONFIG_NOT_FOUND);
|
|
110
110
|
}
|
|
111
111
|
let content = fs.readFileSync(actualPath, "utf-8");
|
|
112
112
|
if (content.includes("@fhevm/hardhat-plugin")) {
|
|
@@ -139,12 +139,12 @@ function updateHardhatConfig(targetDir) {
|
|
|
139
139
|
function addExampleFiles(exampleName, targetDir) {
|
|
140
140
|
const example = config_1.EXAMPLES[exampleName];
|
|
141
141
|
if (!example) {
|
|
142
|
-
throw new Error(
|
|
142
|
+
throw new Error(utils_1.ERROR_MESSAGES.UNKNOWN_EXAMPLE(exampleName));
|
|
143
143
|
}
|
|
144
144
|
const rootDir = (0, utils_1.getRootDir)();
|
|
145
145
|
const contractName = (0, utils_1.getContractName)(example.contract);
|
|
146
146
|
if (!contractName) {
|
|
147
|
-
throw new Error(
|
|
147
|
+
throw new Error(utils_1.ERROR_MESSAGES.CONTRACT_NAME_FAILED);
|
|
148
148
|
}
|
|
149
149
|
const contractSource = path.join(rootDir, example.contract);
|
|
150
150
|
const testSource = path.join(rootDir, example.test);
|
|
@@ -154,15 +154,9 @@ function addExampleFiles(exampleName, targetDir) {
|
|
|
154
154
|
if (!fs.existsSync(contractsDir)) {
|
|
155
155
|
fs.mkdirSync(contractsDir, { recursive: true });
|
|
156
156
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
p.log.success(`Overwritten: ${contractName}.sol`);
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
fs.copyFileSync(contractSource, contractDest);
|
|
164
|
-
p.log.success(`Added: ${contractName}.sol`);
|
|
165
|
-
}
|
|
157
|
+
const isContractOverwrite = fs.existsSync(contractDest);
|
|
158
|
+
fs.copyFileSync(contractSource, contractDest);
|
|
159
|
+
p.log.success(`${isContractOverwrite ? "Overwritten" : "Added"}: ${contractName}.sol`);
|
|
166
160
|
// Handle test file
|
|
167
161
|
const testFileName = path.basename(example.test);
|
|
168
162
|
const testDest = path.join(targetDir, "test", testFileName);
|
|
@@ -170,14 +164,9 @@ function addExampleFiles(exampleName, targetDir) {
|
|
|
170
164
|
if (!fs.existsSync(testDir)) {
|
|
171
165
|
fs.mkdirSync(testDir, { recursive: true });
|
|
172
166
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
fs.copyFileSync(testSource, testDest);
|
|
179
|
-
p.log.success(`Added: ${testFileName}`);
|
|
180
|
-
}
|
|
167
|
+
const isTestOverwrite = fs.existsSync(testDest);
|
|
168
|
+
fs.copyFileSync(testSource, testDest);
|
|
169
|
+
p.log.success(`${isTestOverwrite ? "Overwritten" : "Added"}: ${testFileName}`);
|
|
181
170
|
// Handle contract dependencies
|
|
182
171
|
if (example.dependencies) {
|
|
183
172
|
p.log.message("");
|