create-fhevm-example 1.3.2 → 1.4.1

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.
Files changed (125) hide show
  1. package/contracts/advanced/BlindAuction.sol +255 -0
  2. package/contracts/advanced/EncryptedEscrow.sol +315 -0
  3. package/contracts/advanced/HiddenVoting.sol +231 -0
  4. package/contracts/advanced/PrivateKYC.sol +309 -0
  5. package/contracts/advanced/PrivatePayroll.sol +285 -0
  6. package/contracts/basic/decryption/PublicDecryptMultipleValues.sol +160 -0
  7. package/contracts/basic/decryption/PublicDecryptSingleValue.sol +142 -0
  8. package/contracts/basic/decryption/UserDecryptMultipleValues.sol +61 -0
  9. package/contracts/basic/decryption/UserDecryptSingleValue.sol +59 -0
  10. package/contracts/basic/encryption/EncryptMultipleValues.sol +72 -0
  11. package/contracts/basic/encryption/EncryptSingleValue.sol +44 -0
  12. package/contracts/basic/encryption/FHECounter.sol +54 -0
  13. package/contracts/basic/fhe-operations/FHEAdd.sol +51 -0
  14. package/contracts/basic/fhe-operations/FHEArithmetic.sol +99 -0
  15. package/contracts/basic/fhe-operations/FHEComparison.sol +116 -0
  16. package/contracts/basic/fhe-operations/FHEIfThenElse.sol +53 -0
  17. package/contracts/concepts/FHEAccessControl.sol +94 -0
  18. package/contracts/concepts/FHEAntiPatterns.sol +329 -0
  19. package/contracts/concepts/FHEHandles.sol +128 -0
  20. package/contracts/concepts/FHEInputProof.sol +104 -0
  21. package/contracts/gaming/EncryptedLottery.sol +298 -0
  22. package/contracts/gaming/EncryptedPoker.sol +337 -0
  23. package/contracts/gaming/RockPaperScissors.sol +213 -0
  24. package/contracts/openzeppelin/ERC7984.sol +85 -0
  25. package/contracts/openzeppelin/ERC7984ERC20Wrapper.sol +43 -0
  26. package/contracts/openzeppelin/SwapERC7984ToERC20.sol +110 -0
  27. package/contracts/openzeppelin/SwapERC7984ToERC7984.sol +48 -0
  28. package/contracts/openzeppelin/VestingWallet.sol +147 -0
  29. package/contracts/openzeppelin/mocks/ERC20Mock.sol +31 -0
  30. package/dist/scripts/commands/add-mode.d.ts.map +1 -0
  31. package/dist/scripts/{add-mode.js → commands/add-mode.js} +27 -61
  32. package/dist/scripts/commands/doctor.d.ts.map +1 -0
  33. package/dist/scripts/{doctor.js → commands/doctor.js} +2 -2
  34. package/dist/scripts/commands/generate-config.d.ts.map +1 -0
  35. package/dist/scripts/{generate-config.js → commands/generate-config.js} +3 -10
  36. package/dist/scripts/commands/generate-docs.d.ts.map +1 -0
  37. package/dist/scripts/{generate-docs.js → commands/generate-docs.js} +4 -3
  38. package/dist/scripts/commands/maintenance.d.ts.map +1 -0
  39. package/dist/scripts/{maintenance.js → commands/maintenance.js} +11 -10
  40. package/dist/scripts/index.js +14 -33
  41. package/dist/scripts/{builders.d.ts → shared/builders.d.ts} +2 -2
  42. package/dist/scripts/shared/builders.d.ts.map +1 -0
  43. package/dist/scripts/{builders.js → shared/builders.js} +49 -30
  44. package/dist/scripts/{config.d.ts → shared/config.d.ts} +0 -2
  45. package/dist/scripts/shared/config.d.ts.map +1 -0
  46. package/dist/scripts/{config.js → shared/config.js} +48 -59
  47. package/dist/scripts/shared/generators.d.ts +42 -0
  48. package/dist/scripts/shared/generators.d.ts.map +1 -0
  49. package/dist/scripts/{utils.js → shared/generators.js} +34 -271
  50. package/dist/scripts/shared/ui.d.ts.map +1 -0
  51. package/dist/scripts/{ui.js → shared/ui.js} +3 -2
  52. package/dist/scripts/{utils.d.ts → shared/utils.d.ts} +4 -27
  53. package/dist/scripts/shared/utils.d.ts.map +1 -0
  54. package/dist/scripts/shared/utils.js +236 -0
  55. package/fhevm-hardhat-template/.eslintignore +26 -0
  56. package/fhevm-hardhat-template/.eslintrc.yml +21 -0
  57. package/fhevm-hardhat-template/.github/workflows/main.yml +47 -0
  58. package/fhevm-hardhat-template/.github/workflows/manual-windows.yml +28 -0
  59. package/fhevm-hardhat-template/.github/workflows/manual.yml +28 -0
  60. package/fhevm-hardhat-template/.prettierignore +25 -0
  61. package/fhevm-hardhat-template/.prettierrc.yml +15 -0
  62. package/fhevm-hardhat-template/.solcover.js +4 -0
  63. package/fhevm-hardhat-template/.solhint.json +12 -0
  64. package/fhevm-hardhat-template/.solhintignore +3 -0
  65. package/fhevm-hardhat-template/.vscode/extensions.json +3 -0
  66. package/fhevm-hardhat-template/.vscode/settings.json +9 -0
  67. package/fhevm-hardhat-template/LICENSE +33 -0
  68. package/fhevm-hardhat-template/README.md +110 -0
  69. package/fhevm-hardhat-template/contracts/FHECounter.sol +46 -0
  70. package/fhevm-hardhat-template/deploy/deploy.ts +17 -0
  71. package/fhevm-hardhat-template/hardhat.config.ts +90 -0
  72. package/fhevm-hardhat-template/package-lock.json +10405 -0
  73. package/fhevm-hardhat-template/package.json +104 -0
  74. package/fhevm-hardhat-template/tasks/FHECounter.ts +184 -0
  75. package/fhevm-hardhat-template/tasks/accounts.ts +9 -0
  76. package/fhevm-hardhat-template/test/FHECounter.ts +104 -0
  77. package/fhevm-hardhat-template/test/FHECounterSepolia.ts +104 -0
  78. package/fhevm-hardhat-template/tsconfig.json +23 -0
  79. package/package.json +11 -8
  80. package/test/advanced/BlindAuction.ts +246 -0
  81. package/test/advanced/EncryptedEscrow.ts +295 -0
  82. package/test/advanced/HiddenVoting.ts +268 -0
  83. package/test/advanced/PrivateKYC.ts +382 -0
  84. package/test/advanced/PrivatePayroll.ts +253 -0
  85. package/test/basic/decryption/PublicDecryptMultipleValues.ts +254 -0
  86. package/test/basic/decryption/PublicDecryptSingleValue.ts +264 -0
  87. package/test/basic/decryption/UserDecryptMultipleValues.ts +107 -0
  88. package/test/basic/decryption/UserDecryptSingleValue.ts +97 -0
  89. package/test/basic/encryption/EncryptMultipleValues.ts +110 -0
  90. package/test/basic/encryption/EncryptSingleValue.ts +124 -0
  91. package/test/basic/encryption/FHECounter.ts +112 -0
  92. package/test/basic/fhe-operations/FHEAdd.ts +97 -0
  93. package/test/basic/fhe-operations/FHEArithmetic.ts +161 -0
  94. package/test/basic/fhe-operations/FHEComparison.ts +167 -0
  95. package/test/basic/fhe-operations/FHEIfThenElse.ts +97 -0
  96. package/test/concepts/FHEAccessControl.ts +154 -0
  97. package/test/concepts/FHEAntiPatterns.ts +111 -0
  98. package/test/concepts/FHEHandles.ts +156 -0
  99. package/test/concepts/FHEInputProof.ts +151 -0
  100. package/test/gaming/EncryptedLottery.ts +214 -0
  101. package/test/gaming/EncryptedPoker.ts +349 -0
  102. package/test/gaming/RockPaperScissors.ts +205 -0
  103. package/test/openzeppelin/ERC7984.ts +142 -0
  104. package/test/openzeppelin/ERC7984ERC20Wrapper.ts +71 -0
  105. package/test/openzeppelin/SwapERC7984ToERC20.ts +76 -0
  106. package/test/openzeppelin/SwapERC7984ToERC7984.ts +113 -0
  107. package/test/openzeppelin/VestingWallet.ts +89 -0
  108. package/dist/scripts/add-mode.d.ts.map +0 -1
  109. package/dist/scripts/builders.d.ts.map +0 -1
  110. package/dist/scripts/config.d.ts.map +0 -1
  111. package/dist/scripts/doctor.d.ts.map +0 -1
  112. package/dist/scripts/generate-config.d.ts.map +0 -1
  113. package/dist/scripts/generate-docs.d.ts.map +0 -1
  114. package/dist/scripts/help.d.ts +0 -9
  115. package/dist/scripts/help.d.ts.map +0 -1
  116. package/dist/scripts/help.js +0 -73
  117. package/dist/scripts/maintenance.d.ts.map +0 -1
  118. package/dist/scripts/ui.d.ts.map +0 -1
  119. package/dist/scripts/utils.d.ts.map +0 -1
  120. /package/dist/scripts/{add-mode.d.ts → commands/add-mode.d.ts} +0 -0
  121. /package/dist/scripts/{doctor.d.ts → commands/doctor.d.ts} +0 -0
  122. /package/dist/scripts/{generate-config.d.ts → commands/generate-config.d.ts} +0 -0
  123. /package/dist/scripts/{generate-docs.d.ts → commands/generate-docs.d.ts} +0 -0
  124. /package/dist/scripts/{maintenance.d.ts → commands/maintenance.d.ts} +0 -0
  125. /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
+ });