create-fhevm-example 1.3.1 → 1.4.0
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 +255 -0
- package/contracts/advanced/EncryptedEscrow.sol +315 -0
- package/contracts/advanced/HiddenVoting.sol +231 -0
- package/contracts/advanced/PrivateKYC.sol +309 -0
- package/contracts/advanced/PrivatePayroll.sol +285 -0
- package/contracts/basic/decryption/PublicDecryptMultipleValues.sol +160 -0
- package/contracts/basic/decryption/PublicDecryptSingleValue.sol +142 -0
- package/contracts/basic/decryption/UserDecryptMultipleValues.sol +61 -0
- package/contracts/basic/decryption/UserDecryptSingleValue.sol +59 -0
- package/contracts/basic/encryption/EncryptMultipleValues.sol +72 -0
- package/contracts/basic/encryption/EncryptSingleValue.sol +44 -0
- package/contracts/basic/encryption/FHECounter.sol +54 -0
- package/contracts/basic/fhe-operations/FHEAdd.sol +51 -0
- package/contracts/basic/fhe-operations/FHEArithmetic.sol +99 -0
- package/contracts/basic/fhe-operations/FHEComparison.sol +116 -0
- package/contracts/basic/fhe-operations/FHEIfThenElse.sol +53 -0
- package/contracts/concepts/FHEAccessControl.sol +94 -0
- package/contracts/concepts/FHEAntiPatterns.sol +329 -0
- package/contracts/concepts/FHEHandles.sol +128 -0
- package/contracts/concepts/FHEInputProof.sol +104 -0
- package/contracts/gaming/EncryptedLottery.sol +298 -0
- package/contracts/gaming/EncryptedPoker.sol +337 -0
- package/contracts/gaming/RockPaperScissors.sol +213 -0
- package/contracts/openzeppelin/ERC7984.sol +85 -0
- package/contracts/openzeppelin/ERC7984ERC20Wrapper.sol +43 -0
- package/contracts/openzeppelin/SwapERC7984ToERC20.sol +110 -0
- package/contracts/openzeppelin/SwapERC7984ToERC7984.sol +48 -0
- package/contracts/openzeppelin/VestingWallet.sol +147 -0
- package/contracts/openzeppelin/mocks/ERC20Mock.sol +31 -0
- package/dist/scripts/commands/add-mode.d.ts.map +1 -0
- package/dist/scripts/{add-mode.js → commands/add-mode.js} +27 -61
- package/dist/scripts/commands/doctor.d.ts.map +1 -0
- package/dist/scripts/{doctor.js → commands/doctor.js} +2 -2
- package/dist/scripts/commands/generate-config.d.ts.map +1 -0
- package/dist/scripts/{generate-config.js → commands/generate-config.js} +3 -10
- package/dist/scripts/commands/generate-docs.d.ts.map +1 -0
- package/dist/scripts/{generate-docs.js → commands/generate-docs.js} +4 -3
- package/dist/scripts/commands/maintenance.d.ts.map +1 -0
- package/dist/scripts/{maintenance.js → commands/maintenance.js} +11 -10
- package/dist/scripts/index.js +63 -59
- package/dist/scripts/{builders.d.ts → shared/builders.d.ts} +2 -2
- package/dist/scripts/shared/builders.d.ts.map +1 -0
- package/dist/scripts/{builders.js → shared/builders.js} +49 -30
- package/dist/scripts/{config.d.ts → shared/config.d.ts} +0 -2
- package/dist/scripts/shared/config.d.ts.map +1 -0
- package/dist/scripts/{config.js → shared/config.js} +48 -59
- package/dist/scripts/shared/generators.d.ts +42 -0
- package/dist/scripts/shared/generators.d.ts.map +1 -0
- package/dist/scripts/{utils.js → shared/generators.js} +34 -271
- package/dist/scripts/shared/ui.d.ts.map +1 -0
- package/dist/scripts/{ui.js → shared/ui.js} +3 -2
- package/dist/scripts/{utils.d.ts → shared/utils.d.ts} +4 -27
- package/dist/scripts/shared/utils.d.ts.map +1 -0
- package/dist/scripts/shared/utils.js +228 -0
- package/fhevm-hardhat-template/.eslintignore +26 -0
- package/fhevm-hardhat-template/.eslintrc.yml +21 -0
- package/fhevm-hardhat-template/.github/workflows/main.yml +47 -0
- package/fhevm-hardhat-template/.github/workflows/manual-windows.yml +28 -0
- package/fhevm-hardhat-template/.github/workflows/manual.yml +28 -0
- package/fhevm-hardhat-template/.prettierignore +25 -0
- package/fhevm-hardhat-template/.prettierrc.yml +15 -0
- package/fhevm-hardhat-template/.solcover.js +4 -0
- package/fhevm-hardhat-template/.solhint.json +12 -0
- package/fhevm-hardhat-template/.solhintignore +3 -0
- package/fhevm-hardhat-template/.vscode/extensions.json +3 -0
- package/fhevm-hardhat-template/.vscode/settings.json +9 -0
- package/fhevm-hardhat-template/LICENSE +33 -0
- package/fhevm-hardhat-template/README.md +110 -0
- package/fhevm-hardhat-template/contracts/FHECounter.sol +46 -0
- package/fhevm-hardhat-template/deploy/deploy.ts +17 -0
- package/fhevm-hardhat-template/hardhat.config.ts +90 -0
- package/fhevm-hardhat-template/package-lock.json +10405 -0
- package/fhevm-hardhat-template/package.json +104 -0
- package/fhevm-hardhat-template/tasks/FHECounter.ts +184 -0
- package/fhevm-hardhat-template/tasks/accounts.ts +9 -0
- package/fhevm-hardhat-template/test/FHECounter.ts +104 -0
- package/fhevm-hardhat-template/test/FHECounterSepolia.ts +104 -0
- package/fhevm-hardhat-template/tsconfig.json +23 -0
- package/package.json +13 -10
- package/test/advanced/BlindAuction.ts +246 -0
- package/test/advanced/EncryptedEscrow.ts +295 -0
- package/test/advanced/HiddenVoting.ts +268 -0
- package/test/advanced/PrivateKYC.ts +382 -0
- package/test/advanced/PrivatePayroll.ts +253 -0
- package/test/basic/decryption/PublicDecryptMultipleValues.ts +254 -0
- package/test/basic/decryption/PublicDecryptSingleValue.ts +264 -0
- package/test/basic/decryption/UserDecryptMultipleValues.ts +107 -0
- package/test/basic/decryption/UserDecryptSingleValue.ts +97 -0
- package/test/basic/encryption/EncryptMultipleValues.ts +110 -0
- package/test/basic/encryption/EncryptSingleValue.ts +124 -0
- package/test/basic/encryption/FHECounter.ts +112 -0
- package/test/basic/fhe-operations/FHEAdd.ts +97 -0
- package/test/basic/fhe-operations/FHEArithmetic.ts +161 -0
- package/test/basic/fhe-operations/FHEComparison.ts +167 -0
- package/test/basic/fhe-operations/FHEIfThenElse.ts +97 -0
- package/test/concepts/FHEAccessControl.ts +154 -0
- package/test/concepts/FHEAntiPatterns.ts +111 -0
- package/test/concepts/FHEHandles.ts +156 -0
- package/test/concepts/FHEInputProof.ts +151 -0
- package/test/gaming/EncryptedLottery.ts +214 -0
- package/test/gaming/EncryptedPoker.ts +349 -0
- package/test/gaming/RockPaperScissors.ts +205 -0
- package/test/openzeppelin/ERC7984.ts +142 -0
- package/test/openzeppelin/ERC7984ERC20Wrapper.ts +71 -0
- package/test/openzeppelin/SwapERC7984ToERC20.ts +76 -0
- package/test/openzeppelin/SwapERC7984ToERC7984.ts +113 -0
- package/test/openzeppelin/VestingWallet.ts +89 -0
- package/dist/scripts/add-mode.d.ts.map +0 -1
- package/dist/scripts/builders.d.ts.map +0 -1
- package/dist/scripts/config.d.ts.map +0 -1
- package/dist/scripts/doctor.d.ts.map +0 -1
- package/dist/scripts/generate-config.d.ts.map +0 -1
- package/dist/scripts/generate-docs.d.ts.map +0 -1
- package/dist/scripts/maintenance.d.ts.map +0 -1
- package/dist/scripts/ui.d.ts.map +0 -1
- package/dist/scripts/utils.d.ts.map +0 -1
- /package/dist/scripts/{add-mode.d.ts → commands/add-mode.d.ts} +0 -0
- /package/dist/scripts/{doctor.d.ts → commands/doctor.d.ts} +0 -0
- /package/dist/scripts/{generate-config.d.ts → commands/generate-config.d.ts} +0 -0
- /package/dist/scripts/{generate-docs.d.ts → commands/generate-docs.d.ts} +0 -0
- /package/dist/scripts/{maintenance.d.ts → commands/maintenance.d.ts} +0 -0
- /package/dist/scripts/{ui.d.ts → shared/ui.d.ts} +0 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
|
|
2
|
+
import { expect } from "chai";
|
|
3
|
+
import { ethers as EthersT } from "ethers";
|
|
4
|
+
import { ethers, fhevm } from "hardhat";
|
|
5
|
+
import * as hre from "hardhat";
|
|
6
|
+
|
|
7
|
+
import { HeadsOrTails, HeadsOrTails__factory } from "../types";
|
|
8
|
+
import { Signers } from "./types";
|
|
9
|
+
|
|
10
|
+
async function deployFixture() {
|
|
11
|
+
// Contracts are deployed using the first signer/account by default
|
|
12
|
+
const factory = (await ethers.getContractFactory(
|
|
13
|
+
"HeadsOrTails"
|
|
14
|
+
)) as HeadsOrTails__factory;
|
|
15
|
+
const headsOrTails = (await factory.deploy()) as HeadsOrTails;
|
|
16
|
+
const headsOrTails_address = await headsOrTails.getAddress();
|
|
17
|
+
|
|
18
|
+
return { headsOrTails, headsOrTails_address };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("HeadsOrTails", function () {
|
|
22
|
+
let contract: HeadsOrTails;
|
|
23
|
+
let contractAddress: string;
|
|
24
|
+
let signers: Signers;
|
|
25
|
+
let playerA: HardhatEthersSigner;
|
|
26
|
+
let playerB: HardhatEthersSigner;
|
|
27
|
+
|
|
28
|
+
before(async function () {
|
|
29
|
+
// Check whether the tests are running against an FHEVM mock environment
|
|
30
|
+
if (!hre.fhevm.isMock) {
|
|
31
|
+
throw new Error(`This hardhat test suite cannot run on Sepolia Testnet`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
|
|
35
|
+
signers = {
|
|
36
|
+
owner: ethSigners[0],
|
|
37
|
+
alice: ethSigners[1],
|
|
38
|
+
bob: ethSigners[2],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
playerA = signers.alice;
|
|
42
|
+
playerB = signers.bob;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
beforeEach(async function () {
|
|
46
|
+
// Deploy a new contract each time we run a new test
|
|
47
|
+
const deployment = await deployFixture();
|
|
48
|
+
contractAddress = deployment.headsOrTails_address;
|
|
49
|
+
contract = deployment.headsOrTails;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Helper: Parses the GameCreated event from a transaction receipt.
|
|
54
|
+
* WARNING: This function is for illustrative purposes only and is not production-ready
|
|
55
|
+
* (it does not handle several events in same tx).
|
|
56
|
+
*/
|
|
57
|
+
function parseGameCreatedEvent(
|
|
58
|
+
txReceipt: EthersT.ContractTransactionReceipt | null
|
|
59
|
+
): {
|
|
60
|
+
txHash: `0x${string}`;
|
|
61
|
+
gameId: number;
|
|
62
|
+
headsPlayer: `0x${string}`;
|
|
63
|
+
tailsPlayer: `0x${string}`;
|
|
64
|
+
encryptedHasHeadsWon: `0x${string}`;
|
|
65
|
+
} {
|
|
66
|
+
const gameCreatedEvents: Array<{
|
|
67
|
+
txHash: `0x${string}`;
|
|
68
|
+
gameId: number;
|
|
69
|
+
headsPlayer: `0x${string}`;
|
|
70
|
+
tailsPlayer: `0x${string}`;
|
|
71
|
+
encryptedHasHeadsWon: `0x${string}`;
|
|
72
|
+
}> = [];
|
|
73
|
+
|
|
74
|
+
if (txReceipt) {
|
|
75
|
+
const logs = Array.isArray(txReceipt.logs)
|
|
76
|
+
? txReceipt.logs
|
|
77
|
+
: [txReceipt.logs];
|
|
78
|
+
for (let i = 0; i < logs.length; ++i) {
|
|
79
|
+
const parsedLog = contract.interface.parseLog(logs[i]);
|
|
80
|
+
if (!parsedLog || parsedLog.name !== "GameCreated") {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const ge = {
|
|
84
|
+
txHash: txReceipt.hash as `0x${string}`,
|
|
85
|
+
gameId: Number(parsedLog.args[0]),
|
|
86
|
+
headsPlayer: parsedLog.args[1],
|
|
87
|
+
tailsPlayer: parsedLog.args[2],
|
|
88
|
+
encryptedHasHeadsWon: parsedLog.args[3],
|
|
89
|
+
};
|
|
90
|
+
gameCreatedEvents.push(ge);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// In this example, we expect on one single GameCreated event
|
|
95
|
+
expect(gameCreatedEvents.length).to.eq(1);
|
|
96
|
+
|
|
97
|
+
return gameCreatedEvents[0];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ✅ Test should succeed
|
|
101
|
+
it("decryption should succeed", async function () {
|
|
102
|
+
console.log(``);
|
|
103
|
+
console.log(`🎲 HeadsOrTails Game contract address: ${contractAddress}`);
|
|
104
|
+
console.log(` 🤖 playerA.address: ${playerA.address}`);
|
|
105
|
+
console.log(` 🎃 playerB.address: ${playerB.address}`);
|
|
106
|
+
console.log(``);
|
|
107
|
+
|
|
108
|
+
// Starts a new Heads or Tails game. This will emit a `GameCreated` event
|
|
109
|
+
const tx = await contract
|
|
110
|
+
.connect(signers.owner)
|
|
111
|
+
.headsOrTails(playerA, playerB);
|
|
112
|
+
|
|
113
|
+
// Parse the `GameCreated` event
|
|
114
|
+
const gameCreatedEvent = parseGameCreatedEvent(await tx.wait());
|
|
115
|
+
|
|
116
|
+
// GameId is 1 since we are playing the first game
|
|
117
|
+
expect(gameCreatedEvent.gameId).to.eq(1);
|
|
118
|
+
expect(gameCreatedEvent.headsPlayer).to.eq(playerA.address);
|
|
119
|
+
expect(gameCreatedEvent.tailsPlayer).to.eq(playerB.address);
|
|
120
|
+
expect(await contract.getGamesCount()).to.eq(1);
|
|
121
|
+
|
|
122
|
+
console.log(`✅ New game #${gameCreatedEvent.gameId} created!`);
|
|
123
|
+
console.log(JSON.stringify(gameCreatedEvent, null, 2));
|
|
124
|
+
|
|
125
|
+
const gameId = gameCreatedEvent.gameId;
|
|
126
|
+
const encryptedBool: string = gameCreatedEvent.encryptedHasHeadsWon;
|
|
127
|
+
|
|
128
|
+
// Call the Zama Relayer to compute the decryption
|
|
129
|
+
const publicDecryptResults = await fhevm.publicDecrypt([encryptedBool]);
|
|
130
|
+
|
|
131
|
+
// The Relayer returns a `PublicDecryptResults` object containing:
|
|
132
|
+
// - the ORDERED clear values (here we have only one single value)
|
|
133
|
+
// - the ORDERED clear values in ABI-encoded form
|
|
134
|
+
// - the KMS decryption proof associated with the ORDERED clear values in ABI-encoded form
|
|
135
|
+
const abiEncodedClearGameResult =
|
|
136
|
+
publicDecryptResults.abiEncodedClearValues;
|
|
137
|
+
const decryptionProof = publicDecryptResults.decryptionProof;
|
|
138
|
+
|
|
139
|
+
// Let's forward the `PublicDecryptResults` content to the on-chain contract whose job
|
|
140
|
+
// will simply be to verify the proof and declare the final winner of the game
|
|
141
|
+
await contract.recordAndVerifyWinner(
|
|
142
|
+
gameId,
|
|
143
|
+
abiEncodedClearGameResult,
|
|
144
|
+
decryptionProof
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const winner = await contract.getWinner(gameId);
|
|
148
|
+
|
|
149
|
+
expect(winner === playerA.address || winner === playerB.address).to.eq(
|
|
150
|
+
true
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
console.log(``);
|
|
154
|
+
if (winner === playerA.address) {
|
|
155
|
+
console.log(`🤖 playerA is the winner 🥇🥇`);
|
|
156
|
+
} else if (winner === playerB.address) {
|
|
157
|
+
console.log(`🎃 playerB is the winner 🥇🥇`);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// ❌ The test must fail if the decryption proof is invalid
|
|
162
|
+
it("should fail when the decryption proof is invalid", async function () {
|
|
163
|
+
const tx = await contract
|
|
164
|
+
.connect(signers.owner)
|
|
165
|
+
.headsOrTails(playerA, playerB);
|
|
166
|
+
const gameCreatedEvent = parseGameCreatedEvent(await tx.wait());
|
|
167
|
+
|
|
168
|
+
const publicDecryptResults = await fhevm.publicDecrypt([
|
|
169
|
+
gameCreatedEvent.encryptedHasHeadsWon,
|
|
170
|
+
]);
|
|
171
|
+
await expect(
|
|
172
|
+
contract.recordAndVerifyWinner(
|
|
173
|
+
gameCreatedEvent.gameId,
|
|
174
|
+
publicDecryptResults.abiEncodedClearValues,
|
|
175
|
+
publicDecryptResults.decryptionProof + "dead"
|
|
176
|
+
)
|
|
177
|
+
).to.be.revertedWithCustomError(
|
|
178
|
+
{
|
|
179
|
+
interface: new EthersT.Interface([
|
|
180
|
+
"error KMSInvalidSigner(address invalidSigner)",
|
|
181
|
+
]),
|
|
182
|
+
},
|
|
183
|
+
"KMSInvalidSigner"
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// ❌ The test must fail if a malicious operator attempts to use a decryption proof
|
|
188
|
+
// with a forged game result.
|
|
189
|
+
it("should fail when using a decryption proof with a forged game result", async function () {
|
|
190
|
+
const tx = await contract
|
|
191
|
+
.connect(signers.owner)
|
|
192
|
+
.headsOrTails(playerA, playerB);
|
|
193
|
+
const gameCreatedEvent = parseGameCreatedEvent(await tx.wait());
|
|
194
|
+
|
|
195
|
+
const publicDecryptResults = await fhevm.publicDecrypt([
|
|
196
|
+
gameCreatedEvent.encryptedHasHeadsWon,
|
|
197
|
+
]);
|
|
198
|
+
const clearHeadsHasWon =
|
|
199
|
+
publicDecryptResults.clearValues[gameCreatedEvent.encryptedHasHeadsWon];
|
|
200
|
+
|
|
201
|
+
// The clear value is also ABI-encoded
|
|
202
|
+
const decodedHeadsHasWon = EthersT.AbiCoder.defaultAbiCoder().decode(
|
|
203
|
+
["bool"],
|
|
204
|
+
publicDecryptResults.abiEncodedClearValues
|
|
205
|
+
)[0];
|
|
206
|
+
expect(decodedHeadsHasWon).to.eq(clearHeadsHasWon);
|
|
207
|
+
|
|
208
|
+
// Let's try to forge the game result
|
|
209
|
+
const forgedABIEncodedClearValues =
|
|
210
|
+
EthersT.AbiCoder.defaultAbiCoder().encode(["bool"], [!clearHeadsHasWon]);
|
|
211
|
+
|
|
212
|
+
await expect(
|
|
213
|
+
contract.recordAndVerifyWinner(
|
|
214
|
+
gameCreatedEvent.gameId,
|
|
215
|
+
forgedABIEncodedClearValues,
|
|
216
|
+
publicDecryptResults.decryptionProof
|
|
217
|
+
)
|
|
218
|
+
).to.be.revertedWithCustomError(
|
|
219
|
+
{
|
|
220
|
+
interface: new EthersT.Interface([
|
|
221
|
+
"error KMSInvalidSigner(address invalidSigner)",
|
|
222
|
+
]),
|
|
223
|
+
},
|
|
224
|
+
"KMSInvalidSigner"
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// ❌ Two games (Game1 and Game2) are played between playerA and playerB.
|
|
229
|
+
// The test must fail if a malicious operator attempts to forge the result of Game1
|
|
230
|
+
// with the result of Game2
|
|
231
|
+
it("should fail when using the result of a different game", async function () {
|
|
232
|
+
// Game 1
|
|
233
|
+
const tx1 = await contract
|
|
234
|
+
.connect(signers.owner)
|
|
235
|
+
.headsOrTails(playerA, playerB);
|
|
236
|
+
const gameCreatedEvent1 = parseGameCreatedEvent(await tx1.wait());
|
|
237
|
+
|
|
238
|
+
// Game 2
|
|
239
|
+
const tx2 = await contract
|
|
240
|
+
.connect(signers.owner)
|
|
241
|
+
.headsOrTails(playerA, playerB);
|
|
242
|
+
const gameCreatedEvent2 = parseGameCreatedEvent(await tx2.wait());
|
|
243
|
+
|
|
244
|
+
// Let's try to forge the Game1's winner using the result of Game2
|
|
245
|
+
const publicDecryptResults2 = await fhevm.publicDecrypt([
|
|
246
|
+
gameCreatedEvent2.encryptedHasHeadsWon,
|
|
247
|
+
]);
|
|
248
|
+
|
|
249
|
+
await expect(
|
|
250
|
+
contract.recordAndVerifyWinner(
|
|
251
|
+
gameCreatedEvent1.gameId,
|
|
252
|
+
publicDecryptResults2.abiEncodedClearValues,
|
|
253
|
+
publicDecryptResults2.decryptionProof
|
|
254
|
+
)
|
|
255
|
+
).to.be.revertedWithCustomError(
|
|
256
|
+
{
|
|
257
|
+
interface: new EthersT.Interface([
|
|
258
|
+
"error KMSInvalidSigner(address invalidSigner)",
|
|
259
|
+
]),
|
|
260
|
+
},
|
|
261
|
+
"KMSInvalidSigner"
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {
|
|
2
|
+
UserDecryptMultipleValues,
|
|
3
|
+
UserDecryptMultipleValues__factory,
|
|
4
|
+
} from "../types";
|
|
5
|
+
import type { Signers } from "./types";
|
|
6
|
+
import { HardhatFhevmRuntimeEnvironment } from "@fhevm/hardhat-plugin";
|
|
7
|
+
import { utils as fhevm_utils } from "@fhevm/mock-utils";
|
|
8
|
+
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
|
|
9
|
+
import { DecryptedResults } from "@zama-fhe/relayer-sdk";
|
|
10
|
+
import { expect } from "chai";
|
|
11
|
+
import { ethers } from "hardhat";
|
|
12
|
+
import * as hre from "hardhat";
|
|
13
|
+
|
|
14
|
+
async function deployFixture() {
|
|
15
|
+
// Contracts are deployed using the first signer/account by default
|
|
16
|
+
const factory = (await ethers.getContractFactory(
|
|
17
|
+
"UserDecryptMultipleValues"
|
|
18
|
+
)) as UserDecryptMultipleValues__factory;
|
|
19
|
+
const userDecryptMultipleValues =
|
|
20
|
+
(await factory.deploy()) as UserDecryptMultipleValues;
|
|
21
|
+
const userDecryptMultipleValues_address =
|
|
22
|
+
await userDecryptMultipleValues.getAddress();
|
|
23
|
+
|
|
24
|
+
return { userDecryptMultipleValues, userDecryptMultipleValues_address };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* This trivial example demonstrates the FHE user decryption mechanism
|
|
29
|
+
* and highlights a common pitfall developers may encounter.
|
|
30
|
+
*/
|
|
31
|
+
describe("UserDecryptMultipleValues", function () {
|
|
32
|
+
let contract: UserDecryptMultipleValues;
|
|
33
|
+
let contractAddress: string;
|
|
34
|
+
let signers: Signers;
|
|
35
|
+
|
|
36
|
+
before(async function () {
|
|
37
|
+
// Check whether the tests are running against an FHEVM mock environment
|
|
38
|
+
if (!hre.fhevm.isMock) {
|
|
39
|
+
throw new Error(`This hardhat test suite cannot run on Sepolia Testnet`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
|
|
43
|
+
signers = { owner: ethSigners[0], alice: ethSigners[1] };
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
beforeEach(async function () {
|
|
47
|
+
// Deploy a new contract each time we run a new test
|
|
48
|
+
const deployment = await deployFixture();
|
|
49
|
+
contractAddress = deployment.userDecryptMultipleValues_address;
|
|
50
|
+
contract = deployment.userDecryptMultipleValues;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ✅ Test should succeed
|
|
54
|
+
it("user decryption should succeed", async function () {
|
|
55
|
+
const tx = await contract
|
|
56
|
+
.connect(signers.alice)
|
|
57
|
+
.initialize(true, 123456, 78901234567);
|
|
58
|
+
await tx.wait();
|
|
59
|
+
|
|
60
|
+
const encryptedBool = await contract.encryptedBool();
|
|
61
|
+
const encryptedUint32 = await contract.encryptedUint32();
|
|
62
|
+
const encryptedUint64 = await contract.encryptedUint64();
|
|
63
|
+
|
|
64
|
+
// The FHEVM Hardhat plugin provides a set of convenient helper functions
|
|
65
|
+
// that make it easy to perform FHEVM operations within your Hardhat environment.
|
|
66
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
67
|
+
|
|
68
|
+
const aliceKeypair = fhevm.generateKeypair();
|
|
69
|
+
|
|
70
|
+
const startTimestamp = fhevm_utils.timestampNow();
|
|
71
|
+
const durationDays = 365;
|
|
72
|
+
|
|
73
|
+
const aliceEip712 = fhevm.createEIP712(
|
|
74
|
+
aliceKeypair.publicKey,
|
|
75
|
+
[contractAddress],
|
|
76
|
+
startTimestamp,
|
|
77
|
+
durationDays
|
|
78
|
+
);
|
|
79
|
+
const aliceSignature = await signers.alice.signTypedData(
|
|
80
|
+
aliceEip712.domain,
|
|
81
|
+
{
|
|
82
|
+
UserDecryptRequestVerification:
|
|
83
|
+
aliceEip712.types.UserDecryptRequestVerification,
|
|
84
|
+
},
|
|
85
|
+
aliceEip712.message
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const decrytepResults: DecryptedResults = await fhevm.userDecrypt(
|
|
89
|
+
[
|
|
90
|
+
{ handle: encryptedBool, contractAddress: contractAddress },
|
|
91
|
+
{ handle: encryptedUint32, contractAddress: contractAddress },
|
|
92
|
+
{ handle: encryptedUint64, contractAddress: contractAddress },
|
|
93
|
+
],
|
|
94
|
+
aliceKeypair.privateKey,
|
|
95
|
+
aliceKeypair.publicKey,
|
|
96
|
+
aliceSignature,
|
|
97
|
+
[contractAddress],
|
|
98
|
+
signers.alice.address,
|
|
99
|
+
startTimestamp,
|
|
100
|
+
durationDays
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expect(decrytepResults[encryptedBool]).to.equal(true);
|
|
104
|
+
expect(decrytepResults[encryptedUint32]).to.equal(123456 + 1);
|
|
105
|
+
expect(decrytepResults[encryptedUint64]).to.equal(78901234567 + 1);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import {
|
|
2
|
+
UserDecryptSingleValue,
|
|
3
|
+
UserDecryptSingleValue__factory,
|
|
4
|
+
} from "../types";
|
|
5
|
+
import type { Signers } from "./types";
|
|
6
|
+
import {
|
|
7
|
+
FhevmType,
|
|
8
|
+
HardhatFhevmRuntimeEnvironment,
|
|
9
|
+
} from "@fhevm/hardhat-plugin";
|
|
10
|
+
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
|
|
11
|
+
import { expect } from "chai";
|
|
12
|
+
import { ethers } from "hardhat";
|
|
13
|
+
import * as hre from "hardhat";
|
|
14
|
+
|
|
15
|
+
async function deployFixture() {
|
|
16
|
+
// Contracts are deployed using the first signer/account by default
|
|
17
|
+
const factory = (await ethers.getContractFactory(
|
|
18
|
+
"UserDecryptSingleValue"
|
|
19
|
+
)) as UserDecryptSingleValue__factory;
|
|
20
|
+
const userUserDecryptSingleValue =
|
|
21
|
+
(await factory.deploy()) as UserDecryptSingleValue;
|
|
22
|
+
const userUserDecryptSingleValue_address =
|
|
23
|
+
await userUserDecryptSingleValue.getAddress();
|
|
24
|
+
|
|
25
|
+
return { userUserDecryptSingleValue, userUserDecryptSingleValue_address };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* This trivial example demonstrates the FHE user decryption mechanism
|
|
30
|
+
* and highlights a common pitfall developers may encounter.
|
|
31
|
+
*/
|
|
32
|
+
describe("UserDecryptSingleValue", function () {
|
|
33
|
+
let contract: UserDecryptSingleValue;
|
|
34
|
+
let contractAddress: string;
|
|
35
|
+
let signers: Signers;
|
|
36
|
+
|
|
37
|
+
before(async function () {
|
|
38
|
+
// Check whether the tests are running against an FHEVM mock environment
|
|
39
|
+
if (!hre.fhevm.isMock) {
|
|
40
|
+
throw new Error(`This hardhat test suite cannot run on Sepolia Testnet`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
|
|
44
|
+
signers = { owner: ethSigners[0], alice: ethSigners[1] };
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
beforeEach(async function () {
|
|
48
|
+
// Deploy a new contract each time we run a new test
|
|
49
|
+
const deployment = await deployFixture();
|
|
50
|
+
contractAddress = deployment.userUserDecryptSingleValue_address;
|
|
51
|
+
contract = deployment.userUserDecryptSingleValue;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// ✅ Test should succeed
|
|
55
|
+
it("user decryption should succeed", async function () {
|
|
56
|
+
const tx = await contract.connect(signers.alice).initializeUint32(123456);
|
|
57
|
+
await tx.wait();
|
|
58
|
+
|
|
59
|
+
const encryptedUint32 = await contract.encryptedUint32();
|
|
60
|
+
|
|
61
|
+
// The FHEVM Hardhat plugin provides a set of convenient helper functions
|
|
62
|
+
// that make it easy to perform FHEVM operations within your Hardhat environment.
|
|
63
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
64
|
+
|
|
65
|
+
const clearUint32 = await fhevm.userDecryptEuint(
|
|
66
|
+
FhevmType.euint32, // Specify the encrypted type
|
|
67
|
+
encryptedUint32,
|
|
68
|
+
contractAddress, // The contract address
|
|
69
|
+
signers.alice // The user wallet
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(clearUint32).to.equal(123456 + 1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// ❌ Test should fail
|
|
76
|
+
it("user decryption should fail", async function () {
|
|
77
|
+
const tx = await contract
|
|
78
|
+
.connect(signers.alice)
|
|
79
|
+
.initializeUint32Wrong(123456);
|
|
80
|
+
await tx.wait();
|
|
81
|
+
|
|
82
|
+
const encryptedUint32 = await contract.encryptedUint32();
|
|
83
|
+
|
|
84
|
+
await expect(
|
|
85
|
+
hre.fhevm.userDecryptEuint(
|
|
86
|
+
FhevmType.euint32,
|
|
87
|
+
encryptedUint32,
|
|
88
|
+
contractAddress,
|
|
89
|
+
signers.alice
|
|
90
|
+
)
|
|
91
|
+
).to.be.rejectedWith(
|
|
92
|
+
new RegExp(
|
|
93
|
+
"^dapp contract (.+) is not authorized to user decrypt handle (.+)."
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EncryptMultipleValues,
|
|
3
|
+
EncryptMultipleValues__factory,
|
|
4
|
+
} from "../types";
|
|
5
|
+
import type { Signers } from "./types";
|
|
6
|
+
import {
|
|
7
|
+
FhevmType,
|
|
8
|
+
HardhatFhevmRuntimeEnvironment,
|
|
9
|
+
} from "@fhevm/hardhat-plugin";
|
|
10
|
+
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
|
|
11
|
+
import { expect } from "chai";
|
|
12
|
+
import { ethers } from "hardhat";
|
|
13
|
+
import * as hre from "hardhat";
|
|
14
|
+
|
|
15
|
+
async function deployFixture() {
|
|
16
|
+
// Contracts are deployed using the first signer/account by default
|
|
17
|
+
const factory = (await ethers.getContractFactory(
|
|
18
|
+
"EncryptMultipleValues"
|
|
19
|
+
)) as EncryptMultipleValues__factory;
|
|
20
|
+
const encryptMultipleValues =
|
|
21
|
+
(await factory.deploy()) as EncryptMultipleValues;
|
|
22
|
+
const encryptMultipleValues_address =
|
|
23
|
+
await encryptMultipleValues.getAddress();
|
|
24
|
+
|
|
25
|
+
return { encryptMultipleValues, encryptMultipleValues_address };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* This trivial example demonstrates the FHE encryption mechanism
|
|
30
|
+
* and highlights a common pitfall developers may encounter.
|
|
31
|
+
*/
|
|
32
|
+
describe("EncryptMultipleValues", function () {
|
|
33
|
+
let contract: EncryptMultipleValues;
|
|
34
|
+
let contractAddress: string;
|
|
35
|
+
let signers: Signers;
|
|
36
|
+
|
|
37
|
+
before(async function () {
|
|
38
|
+
// Check whether the tests are running against an FHEVM mock environment
|
|
39
|
+
if (!hre.fhevm.isMock) {
|
|
40
|
+
throw new Error(`This hardhat test suite cannot run on Sepolia Testnet`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
|
|
44
|
+
signers = { owner: ethSigners[0], alice: ethSigners[1] };
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
beforeEach(async function () {
|
|
48
|
+
// Deploy a new contract each time we run a new test
|
|
49
|
+
const deployment = await deployFixture();
|
|
50
|
+
contractAddress = deployment.encryptMultipleValues_address;
|
|
51
|
+
contract = deployment.encryptMultipleValues;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// ✅ Test should succeed
|
|
55
|
+
it("encryption should succeed", async function () {
|
|
56
|
+
// Use the FHEVM Hardhat plugin runtime environment
|
|
57
|
+
// to perform FHEVM input encryptions.
|
|
58
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
59
|
+
|
|
60
|
+
const input = fhevm.createEncryptedInput(
|
|
61
|
+
contractAddress,
|
|
62
|
+
signers.alice.address
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
input.addBool(true);
|
|
66
|
+
input.add32(123456);
|
|
67
|
+
input.addAddress(signers.owner.address);
|
|
68
|
+
|
|
69
|
+
const enc = await input.encrypt();
|
|
70
|
+
|
|
71
|
+
const inputEbool = enc.handles[0];
|
|
72
|
+
const inputEuint32 = enc.handles[1];
|
|
73
|
+
const inputEaddress = enc.handles[2];
|
|
74
|
+
const inputProof = enc.inputProof;
|
|
75
|
+
|
|
76
|
+
// Don't forget to call `connect(signers.alice)` to make sure
|
|
77
|
+
// the Solidity `msg.sender` is `signers.alice.address`.
|
|
78
|
+
const tx = await contract
|
|
79
|
+
.connect(signers.alice)
|
|
80
|
+
.initialize(inputEbool, inputEuint32, inputEaddress, inputProof);
|
|
81
|
+
await tx.wait();
|
|
82
|
+
|
|
83
|
+
const encryptedBool = await contract.encryptedBool();
|
|
84
|
+
const encryptedUint32 = await contract.encryptedUint32();
|
|
85
|
+
const encryptedAddress = await contract.encryptedAddress();
|
|
86
|
+
|
|
87
|
+
const clearBool = await fhevm.userDecryptEbool(
|
|
88
|
+
encryptedBool,
|
|
89
|
+
contractAddress, // The contract address
|
|
90
|
+
signers.alice // The user wallet
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const clearUint32 = await fhevm.userDecryptEuint(
|
|
94
|
+
FhevmType.euint32, // Specify the encrypted type
|
|
95
|
+
encryptedUint32,
|
|
96
|
+
contractAddress, // The contract address
|
|
97
|
+
signers.alice // The user wallet
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const clearAddress = await fhevm.userDecryptEaddress(
|
|
101
|
+
encryptedAddress,
|
|
102
|
+
contractAddress, // The contract address
|
|
103
|
+
signers.alice // The user wallet
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
expect(clearBool).to.equal(true);
|
|
107
|
+
expect(clearUint32).to.equal(123456);
|
|
108
|
+
expect(clearAddress).to.equal(signers.owner.address);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { EncryptSingleValue, EncryptSingleValue__factory } from "../types";
|
|
2
|
+
import type { Signers } from "./types";
|
|
3
|
+
import {
|
|
4
|
+
FhevmType,
|
|
5
|
+
HardhatFhevmRuntimeEnvironment,
|
|
6
|
+
} from "@fhevm/hardhat-plugin";
|
|
7
|
+
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
|
|
8
|
+
import { expect } from "chai";
|
|
9
|
+
import { ethers } from "hardhat";
|
|
10
|
+
import * as hre from "hardhat";
|
|
11
|
+
|
|
12
|
+
async function deployFixture() {
|
|
13
|
+
// Contracts are deployed using the first signer/account by default
|
|
14
|
+
const factory = (await ethers.getContractFactory(
|
|
15
|
+
"EncryptSingleValue"
|
|
16
|
+
)) as EncryptSingleValue__factory;
|
|
17
|
+
const encryptSingleValue = (await factory.deploy()) as EncryptSingleValue;
|
|
18
|
+
const encryptSingleValue_address = await encryptSingleValue.getAddress();
|
|
19
|
+
|
|
20
|
+
return { encryptSingleValue, encryptSingleValue_address };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* This trivial example demonstrates the FHE encryption mechanism
|
|
25
|
+
* and highlights a common pitfall developers may encounter.
|
|
26
|
+
*/
|
|
27
|
+
describe("EncryptSingleValue", function () {
|
|
28
|
+
let contract: EncryptSingleValue;
|
|
29
|
+
let contractAddress: string;
|
|
30
|
+
let signers: Signers;
|
|
31
|
+
|
|
32
|
+
before(async function () {
|
|
33
|
+
// Check whether the tests are running against an FHEVM mock environment
|
|
34
|
+
if (!hre.fhevm.isMock) {
|
|
35
|
+
throw new Error(`This hardhat test suite cannot run on Sepolia Testnet`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
|
|
39
|
+
signers = { owner: ethSigners[0], alice: ethSigners[1] };
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
beforeEach(async function () {
|
|
43
|
+
// Deploy a new contract each time we run a new test
|
|
44
|
+
const deployment = await deployFixture();
|
|
45
|
+
contractAddress = deployment.encryptSingleValue_address;
|
|
46
|
+
contract = deployment.encryptSingleValue;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// ✅ Test should succeed
|
|
50
|
+
it("encryption should succeed", async function () {
|
|
51
|
+
// Use the FHEVM Hardhat plugin runtime environment
|
|
52
|
+
// to perform FHEVM input encryptions.
|
|
53
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
54
|
+
|
|
55
|
+
// 🔐 Encryption Process:
|
|
56
|
+
// Values are encrypted locally and bound to a specific contract/user pair.
|
|
57
|
+
// This grants the bound contract FHE permissions to receive and process the encrypted value,
|
|
58
|
+
// but only when it is sent by the bound user.
|
|
59
|
+
const input = fhevm.createEncryptedInput(
|
|
60
|
+
contractAddress,
|
|
61
|
+
signers.alice.address
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Add a uint32 value to the list of values to encrypt locally.
|
|
65
|
+
input.add32(123456);
|
|
66
|
+
|
|
67
|
+
// Perform the local encryption. This operation produces two components:
|
|
68
|
+
// 1. `handles`: an array of FHEVM handles. In this case, a single handle associated with the
|
|
69
|
+
// locally encrypted uint32 value `123456`.
|
|
70
|
+
// 2. `inputProof`: a zero-knowledge proof that attests the `handles` are cryptographically
|
|
71
|
+
// bound to the pair `[contractAddress, signers.alice.address]`.
|
|
72
|
+
const enc = await input.encrypt();
|
|
73
|
+
|
|
74
|
+
// a 32-bytes FHEVM handle that represents a future Solidity `euint32` value.
|
|
75
|
+
const inputEuint32 = enc.handles[0];
|
|
76
|
+
const inputProof = enc.inputProof;
|
|
77
|
+
|
|
78
|
+
// Now `signers.alice.address` can send the encrypted value and its associated zero-knowledge proof
|
|
79
|
+
// to the smart contract deployed at `contractAddress`.
|
|
80
|
+
const tx = await contract
|
|
81
|
+
.connect(signers.alice)
|
|
82
|
+
.initialize(inputEuint32, inputProof);
|
|
83
|
+
await tx.wait();
|
|
84
|
+
|
|
85
|
+
// Let's try to decrypt it to check that everything is ok!
|
|
86
|
+
const encryptedUint32 = await contract.encryptedUint32();
|
|
87
|
+
|
|
88
|
+
const clearUint32 = await fhevm.userDecryptEuint(
|
|
89
|
+
FhevmType.euint32, // Specify the encrypted type
|
|
90
|
+
encryptedUint32,
|
|
91
|
+
contractAddress, // The contract address
|
|
92
|
+
signers.alice // The user wallet
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
expect(clearUint32).to.equal(123456);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ❌ This test illustrates a very common pitfall
|
|
99
|
+
it("encryption should fail", async function () {
|
|
100
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
101
|
+
|
|
102
|
+
const enc = await fhevm
|
|
103
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
104
|
+
.add32(123456)
|
|
105
|
+
.encrypt();
|
|
106
|
+
|
|
107
|
+
const inputEuint32 = enc.handles[0];
|
|
108
|
+
const inputProof = enc.inputProof;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
// Here is a very common error !
|
|
112
|
+
// `contract.initialize` will sign the Ethereum transaction using user `signers.owner`
|
|
113
|
+
// instead of `signers.alice`.
|
|
114
|
+
//
|
|
115
|
+
// In the Solidity contract the following is checked:
|
|
116
|
+
// - Is the contract allowed to manipulate `inputEuint32`? Answer is: ✅ yes!
|
|
117
|
+
// - Is the sender allowed to manipulate `inputEuint32`? Answer is: ❌ no! Only `signers.alice` is!
|
|
118
|
+
const tx = await contract.initialize(inputEuint32, inputProof);
|
|
119
|
+
await tx.wait();
|
|
120
|
+
} catch {
|
|
121
|
+
//console.log(e);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|