create-fhevm-example 1.4.5 → 1.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -4
- package/contracts/concepts/antipatterns/ControlFlow.sol +160 -0
- package/contracts/concepts/antipatterns/OperationsGasNoise.sol +190 -0
- package/contracts/concepts/antipatterns/Permissions.sol +254 -0
- package/dist/scripts/commands/add-mode.d.ts.map +1 -1
- package/dist/scripts/commands/add-mode.js +10 -21
- package/dist/scripts/commands/doctor.js +76 -2
- package/dist/scripts/commands/generate-config.js +13 -2
- 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 +96 -67
- package/dist/scripts/shared/utils.d.ts.map +1 -1
- package/dist/scripts/shared/utils.js +5 -4
- package/package.json +1 -1
- package/test/concepts/antipatterns/ControlFlow.ts +125 -0
- package/test/concepts/antipatterns/OperationsGasNoise.ts +187 -0
- package/test/concepts/antipatterns/Permissions.ts +327 -0
- package/contracts/concepts/FHEAntiPatterns.sol +0 -300
- package/test/concepts/FHEAntiPatterns.ts +0 -111
- /package/contracts/concepts/{FHEAccessControl.sol → core/FHEAccessControl.sol} +0 -0
- /package/contracts/concepts/{FHEHandles.sol → core/FHEHandles.sol} +0 -0
- /package/contracts/concepts/{FHEInputProof.sol → core/FHEInputProof.sol} +0 -0
- /package/test/concepts/{FHEAccessControl.ts → core/FHEAccessControl.ts} +0 -0
- /package/test/concepts/{FHEHandles.ts → core/FHEHandles.ts} +0 -0
- /package/test/concepts/{FHEInputProof.ts → core/FHEInputProof.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-fhevm-example",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.7",
|
|
4
4
|
"description": "Create FHEVM example projects with a single command - A comprehensive toolkit for building privacy-preserving smart contracts",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-fhevm-example": "./dist/scripts/index.js"
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FhevmType,
|
|
3
|
+
HardhatFhevmRuntimeEnvironment,
|
|
4
|
+
} from "@fhevm/hardhat-plugin";
|
|
5
|
+
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
|
|
6
|
+
import { expect } from "chai";
|
|
7
|
+
import { ethers } from "hardhat";
|
|
8
|
+
import * as hre from "hardhat";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
FHEControlFlowAntiPatterns,
|
|
12
|
+
FHEControlFlowAntiPatterns__factory,
|
|
13
|
+
} from "../types";
|
|
14
|
+
import type { Signers } from "./types";
|
|
15
|
+
|
|
16
|
+
async function deployFixture() {
|
|
17
|
+
const factory = (await ethers.getContractFactory(
|
|
18
|
+
"FHEControlFlowAntiPatterns"
|
|
19
|
+
)) as FHEControlFlowAntiPatterns__factory;
|
|
20
|
+
const contract = (await factory.deploy()) as FHEControlFlowAntiPatterns;
|
|
21
|
+
const contractAddress = await contract.getAddress();
|
|
22
|
+
return { contract, contractAddress };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @notice Tests for FHE control flow anti-patterns
|
|
27
|
+
* Demonstrates wrong and correct patterns for conditional logic and loops
|
|
28
|
+
*/
|
|
29
|
+
describe("FHEControlFlowAntiPatterns", function () {
|
|
30
|
+
let contract: FHEControlFlowAntiPatterns;
|
|
31
|
+
let contractAddress: string;
|
|
32
|
+
let signers: Signers;
|
|
33
|
+
|
|
34
|
+
before(async function () {
|
|
35
|
+
if (!hre.fhevm.isMock) {
|
|
36
|
+
throw new Error(`This hardhat test suite cannot run on Sepolia Testnet`);
|
|
37
|
+
}
|
|
38
|
+
const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
|
|
39
|
+
signers = { owner: ethSigners[0], alice: ethSigners[1] };
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
beforeEach(async function () {
|
|
43
|
+
const deployment = await deployFixture();
|
|
44
|
+
contractAddress = deployment.contractAddress;
|
|
45
|
+
contract = deployment.contract;
|
|
46
|
+
|
|
47
|
+
// Initialize contract with test values
|
|
48
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
49
|
+
|
|
50
|
+
// Create encrypted input for balance only (threshold is fixed at 100)
|
|
51
|
+
const input = await fhevm
|
|
52
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
53
|
+
.add32(50) // balance
|
|
54
|
+
.encrypt();
|
|
55
|
+
|
|
56
|
+
await contract
|
|
57
|
+
.connect(signers.alice)
|
|
58
|
+
.initialize(input.handles[0], input.inputProof);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("Pattern 1: If/Else Branching", function () {
|
|
62
|
+
it("should execute correctConditional without leaking information", async function () {
|
|
63
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
64
|
+
|
|
65
|
+
await contract.connect(signers.alice).correctConditional();
|
|
66
|
+
|
|
67
|
+
const encrypted = await contract.getBalance();
|
|
68
|
+
const decrypted = await fhevm.userDecryptEuint(
|
|
69
|
+
FhevmType.euint32,
|
|
70
|
+
encrypted,
|
|
71
|
+
contractAddress,
|
|
72
|
+
signers.alice
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Balance (50) is not above threshold (100), so no penalty applied
|
|
76
|
+
expect(decrypted).to.equal(50);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("wrongBranching should return placeholder value", async function () {
|
|
80
|
+
const result = await contract.wrongBranching.staticCall();
|
|
81
|
+
expect(result).to.equal(0);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("Pattern 2: Require/Revert", function () {
|
|
86
|
+
it("should return encrypted boolean for validation", async function () {
|
|
87
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
88
|
+
|
|
89
|
+
// Execute validation (stores result)
|
|
90
|
+
await contract.connect(signers.alice).correctValidation();
|
|
91
|
+
|
|
92
|
+
// Get result via getter
|
|
93
|
+
const encrypted = await contract.getValidationResult();
|
|
94
|
+
const decrypted = await fhevm.userDecryptEbool(
|
|
95
|
+
encrypted,
|
|
96
|
+
contractAddress,
|
|
97
|
+
signers.alice
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Balance (50) < 100, so should be false
|
|
101
|
+
expect(decrypted).to.equal(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("wrongRequire should return explanation string", async function () {
|
|
105
|
+
const result = await contract.wrongRequire();
|
|
106
|
+
expect(result).to.include("doesn't work with encrypted values");
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("Pattern 3: Encrypted Loop Iterations", function () {
|
|
111
|
+
it("should use fixed iterations with FHE.select", async function () {
|
|
112
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
113
|
+
|
|
114
|
+
await contract.connect(signers.alice).correctFixedIterations();
|
|
115
|
+
|
|
116
|
+
// Result should count up to min(balance, MAX_ITERATIONS)
|
|
117
|
+
// Balance is 50, MAX_ITERATIONS is 5, so result should be 5
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("wrongEncryptedLoop should return explanation string", async function () {
|
|
121
|
+
const result = await contract.wrongEncryptedLoop();
|
|
122
|
+
expect(result).to.include("Loop iterations leak");
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FhevmType,
|
|
3
|
+
HardhatFhevmRuntimeEnvironment,
|
|
4
|
+
} from "@fhevm/hardhat-plugin";
|
|
5
|
+
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
|
|
6
|
+
import { expect } from "chai";
|
|
7
|
+
import { ethers } from "hardhat";
|
|
8
|
+
import * as hre from "hardhat";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
FHEOperationsGasNoiseAntiPatterns,
|
|
12
|
+
FHEOperationsGasNoiseAntiPatterns__factory,
|
|
13
|
+
} from "../types";
|
|
14
|
+
import type { Signers } from "./types";
|
|
15
|
+
|
|
16
|
+
async function deployFixture() {
|
|
17
|
+
const factory = (await ethers.getContractFactory(
|
|
18
|
+
"FHEOperationsGasNoiseAntiPatterns"
|
|
19
|
+
)) as FHEOperationsGasNoiseAntiPatterns__factory;
|
|
20
|
+
const contract =
|
|
21
|
+
(await factory.deploy()) as FHEOperationsGasNoiseAntiPatterns;
|
|
22
|
+
const contractAddress = await contract.getAddress();
|
|
23
|
+
return { contract, contractAddress };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @notice Tests for FHE operations, gas, and noise anti-patterns
|
|
28
|
+
* Demonstrates performance issues and optimization techniques
|
|
29
|
+
*/
|
|
30
|
+
describe("FHEOperationsGasNoiseAntiPatterns", function () {
|
|
31
|
+
let contract: FHEOperationsGasNoiseAntiPatterns;
|
|
32
|
+
let contractAddress: string;
|
|
33
|
+
let signers: Signers;
|
|
34
|
+
|
|
35
|
+
before(async function () {
|
|
36
|
+
if (!hre.fhevm.isMock) {
|
|
37
|
+
throw new Error(`This hardhat test suite cannot run on Sepolia Testnet`);
|
|
38
|
+
}
|
|
39
|
+
const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
|
|
40
|
+
signers = { owner: ethSigners[0], alice: ethSigners[1] };
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
beforeEach(async function () {
|
|
44
|
+
const deployment = await deployFixture();
|
|
45
|
+
contractAddress = deployment.contractAddress;
|
|
46
|
+
contract = deployment.contract;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("Pattern 1: Gas/Timing Side Channel", function () {
|
|
50
|
+
const testValue = 50;
|
|
51
|
+
|
|
52
|
+
it("wrongGasLeak should store value", async function () {
|
|
53
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
54
|
+
|
|
55
|
+
const input = await fhevm
|
|
56
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
57
|
+
.add32(testValue)
|
|
58
|
+
.encrypt();
|
|
59
|
+
|
|
60
|
+
await contract
|
|
61
|
+
.connect(signers.alice)
|
|
62
|
+
.wrongGasLeak(input.handles[0], input.inputProof);
|
|
63
|
+
|
|
64
|
+
// Value should be stored (but without permission to decrypt)
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("correctConstantTime should use constant gas", async function () {
|
|
68
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
69
|
+
|
|
70
|
+
const input = await fhevm
|
|
71
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
72
|
+
.add32(testValue)
|
|
73
|
+
.encrypt();
|
|
74
|
+
|
|
75
|
+
await contract
|
|
76
|
+
.connect(signers.alice)
|
|
77
|
+
.correctConstantTime(input.handles[0], input.inputProof);
|
|
78
|
+
|
|
79
|
+
const encrypted = await contract.getValue();
|
|
80
|
+
const decrypted = await fhevm.userDecryptEuint(
|
|
81
|
+
FhevmType.euint32,
|
|
82
|
+
encrypted,
|
|
83
|
+
contractAddress,
|
|
84
|
+
signers.alice
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(decrypted).to.equal(testValue * 2);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("Pattern 2: Noise Accumulation", function () {
|
|
92
|
+
const testValue = 2;
|
|
93
|
+
|
|
94
|
+
it("wrongNoiseAccumulation should chain many operations", async function () {
|
|
95
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
96
|
+
|
|
97
|
+
const input = await fhevm
|
|
98
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
99
|
+
.add32(testValue)
|
|
100
|
+
.encrypt();
|
|
101
|
+
|
|
102
|
+
await contract
|
|
103
|
+
.connect(signers.alice)
|
|
104
|
+
.wrongNoiseAccumulation(input.handles[0], input.inputProof);
|
|
105
|
+
|
|
106
|
+
// Result stored but may have accumulated noise
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("correctMinimizeOperations should use single operation", async function () {
|
|
110
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
111
|
+
|
|
112
|
+
const input = await fhevm
|
|
113
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
114
|
+
.add32(testValue)
|
|
115
|
+
.encrypt();
|
|
116
|
+
|
|
117
|
+
await contract
|
|
118
|
+
.connect(signers.alice)
|
|
119
|
+
.correctMinimizeOperations(input.handles[0], input.inputProof);
|
|
120
|
+
|
|
121
|
+
const encrypted = await contract.getValue();
|
|
122
|
+
const decrypted = await fhevm.userDecryptEuint(
|
|
123
|
+
FhevmType.euint32,
|
|
124
|
+
encrypted,
|
|
125
|
+
contractAddress,
|
|
126
|
+
signers.alice
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// 2 * 32 = 64
|
|
130
|
+
expect(decrypted).to.equal(64);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("Pattern 3: Deprecated APIs", function () {
|
|
135
|
+
it("wrongDeprecatedAPI should return warning string", async function () {
|
|
136
|
+
const result = await contract.wrongDeprecatedAPI();
|
|
137
|
+
expect(result).to.include("deprecated");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("correctModernAPI should return guidance string", async function () {
|
|
141
|
+
const result = await contract.correctModernAPI();
|
|
142
|
+
expect(result).to.include("FHE.makePubliclyDecryptable");
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("Pattern 4: Type Mismatch", function () {
|
|
147
|
+
const testValue = 100;
|
|
148
|
+
|
|
149
|
+
it("wrongOversizedType should use euint256 unnecessarily", async function () {
|
|
150
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
151
|
+
|
|
152
|
+
const input = await fhevm
|
|
153
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
154
|
+
.add32(testValue)
|
|
155
|
+
.encrypt();
|
|
156
|
+
|
|
157
|
+
await contract
|
|
158
|
+
.connect(signers.alice)
|
|
159
|
+
.wrongOversizedType(input.handles[0], input.inputProof);
|
|
160
|
+
|
|
161
|
+
// Value stored but with wasteful type conversion
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("correctRightSizedType should use euint32", async function () {
|
|
165
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
166
|
+
|
|
167
|
+
const input = await fhevm
|
|
168
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
169
|
+
.add32(testValue)
|
|
170
|
+
.encrypt();
|
|
171
|
+
|
|
172
|
+
await contract
|
|
173
|
+
.connect(signers.alice)
|
|
174
|
+
.correctRightSizedType(input.handles[0], input.inputProof);
|
|
175
|
+
|
|
176
|
+
const encrypted = await contract.getValue();
|
|
177
|
+
const decrypted = await fhevm.userDecryptEuint(
|
|
178
|
+
FhevmType.euint32,
|
|
179
|
+
encrypted,
|
|
180
|
+
contractAddress,
|
|
181
|
+
signers.alice
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
expect(decrypted).to.equal(testValue * 2);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FhevmType,
|
|
3
|
+
HardhatFhevmRuntimeEnvironment,
|
|
4
|
+
} from "@fhevm/hardhat-plugin";
|
|
5
|
+
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
|
|
6
|
+
import { expect } from "chai";
|
|
7
|
+
import { ethers } from "hardhat";
|
|
8
|
+
import * as hre from "hardhat";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
FHEPermissionsAntiPatterns,
|
|
12
|
+
FHEPermissionsAntiPatterns__factory,
|
|
13
|
+
} from "../types";
|
|
14
|
+
import type { Signers } from "./types";
|
|
15
|
+
|
|
16
|
+
async function deployFixture() {
|
|
17
|
+
const factory = (await ethers.getContractFactory(
|
|
18
|
+
"FHEPermissionsAntiPatterns"
|
|
19
|
+
)) as FHEPermissionsAntiPatterns__factory;
|
|
20
|
+
const contract = (await factory.deploy()) as FHEPermissionsAntiPatterns;
|
|
21
|
+
const contractAddress = await contract.getAddress();
|
|
22
|
+
return { contract, contractAddress };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @notice Tests for FHE permission anti-patterns
|
|
27
|
+
* Demonstrates wrong and correct permission handling patterns
|
|
28
|
+
*/
|
|
29
|
+
describe("FHEPermissionsAntiPatterns", function () {
|
|
30
|
+
let contract: FHEPermissionsAntiPatterns;
|
|
31
|
+
let contractAddress: string;
|
|
32
|
+
let signers: Signers;
|
|
33
|
+
let bob: HardhatEthersSigner;
|
|
34
|
+
|
|
35
|
+
before(async function () {
|
|
36
|
+
if (!hre.fhevm.isMock) {
|
|
37
|
+
throw new Error(`This hardhat test suite cannot run on Sepolia Testnet`);
|
|
38
|
+
}
|
|
39
|
+
const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
|
|
40
|
+
signers = { owner: ethSigners[0], alice: ethSigners[1] };
|
|
41
|
+
bob = ethSigners[2];
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
beforeEach(async function () {
|
|
45
|
+
const deployment = await deployFixture();
|
|
46
|
+
contractAddress = deployment.contractAddress;
|
|
47
|
+
contract = deployment.contract;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("Pattern 1: Missing allowThis", function () {
|
|
51
|
+
const testValue = 42;
|
|
52
|
+
|
|
53
|
+
it("should FAIL without allowThis", async function () {
|
|
54
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
55
|
+
|
|
56
|
+
const input = await fhevm
|
|
57
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
58
|
+
.add32(testValue)
|
|
59
|
+
.encrypt();
|
|
60
|
+
|
|
61
|
+
await contract
|
|
62
|
+
.connect(signers.alice)
|
|
63
|
+
.wrongMissingAllowThis(input.handles[0], input.inputProof);
|
|
64
|
+
|
|
65
|
+
// Decryption should fail
|
|
66
|
+
const encrypted = await contract.getValue();
|
|
67
|
+
await expect(
|
|
68
|
+
fhevm.userDecryptEuint(
|
|
69
|
+
FhevmType.euint32,
|
|
70
|
+
encrypted,
|
|
71
|
+
contractAddress,
|
|
72
|
+
signers.alice
|
|
73
|
+
)
|
|
74
|
+
).to.be.rejected;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should succeed with allowThis", async function () {
|
|
78
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
79
|
+
|
|
80
|
+
const input = await fhevm
|
|
81
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
82
|
+
.add32(testValue)
|
|
83
|
+
.encrypt();
|
|
84
|
+
|
|
85
|
+
await contract
|
|
86
|
+
.connect(signers.alice)
|
|
87
|
+
.correctWithAllowThis(input.handles[0], input.inputProof);
|
|
88
|
+
|
|
89
|
+
const encrypted = await contract.getValue();
|
|
90
|
+
const decrypted = await fhevm.userDecryptEuint(
|
|
91
|
+
FhevmType.euint32,
|
|
92
|
+
encrypted,
|
|
93
|
+
contractAddress,
|
|
94
|
+
signers.alice
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
expect(decrypted).to.equal(testValue * 2);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("Pattern 2: Missing allow(user)", function () {
|
|
102
|
+
const testValue = 100;
|
|
103
|
+
|
|
104
|
+
it("should FAIL without user allow", async function () {
|
|
105
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
106
|
+
|
|
107
|
+
const input = await fhevm
|
|
108
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
109
|
+
.add32(testValue)
|
|
110
|
+
.encrypt();
|
|
111
|
+
|
|
112
|
+
await contract
|
|
113
|
+
.connect(signers.alice)
|
|
114
|
+
.wrongMissingUserAllow(input.handles[0], input.inputProof);
|
|
115
|
+
|
|
116
|
+
const encrypted = await contract.getValue();
|
|
117
|
+
await expect(
|
|
118
|
+
fhevm.userDecryptEuint(
|
|
119
|
+
FhevmType.euint32,
|
|
120
|
+
encrypted,
|
|
121
|
+
contractAddress,
|
|
122
|
+
signers.alice
|
|
123
|
+
)
|
|
124
|
+
).to.be.rejected;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should succeed with user allow", async function () {
|
|
128
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
129
|
+
|
|
130
|
+
const input = await fhevm
|
|
131
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
132
|
+
.add32(testValue)
|
|
133
|
+
.encrypt();
|
|
134
|
+
|
|
135
|
+
await contract
|
|
136
|
+
.connect(signers.alice)
|
|
137
|
+
.correctWithUserAllow(input.handles[0], input.inputProof);
|
|
138
|
+
|
|
139
|
+
const encrypted = await contract.getValue();
|
|
140
|
+
const decrypted = await fhevm.userDecryptEuint(
|
|
141
|
+
FhevmType.euint32,
|
|
142
|
+
encrypted,
|
|
143
|
+
contractAddress,
|
|
144
|
+
signers.alice
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
expect(decrypted).to.equal(testValue);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("Pattern 3: View Function Without Permissions", function () {
|
|
152
|
+
const testValue = 200;
|
|
153
|
+
|
|
154
|
+
it("should FAIL when stored without permission", async function () {
|
|
155
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
156
|
+
|
|
157
|
+
const input = await fhevm
|
|
158
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
159
|
+
.add32(testValue)
|
|
160
|
+
.encrypt();
|
|
161
|
+
|
|
162
|
+
await contract
|
|
163
|
+
.connect(signers.alice)
|
|
164
|
+
.wrongStoreWithoutPermission(input.handles[0], input.inputProof);
|
|
165
|
+
|
|
166
|
+
const encrypted = await contract.getValue();
|
|
167
|
+
await expect(
|
|
168
|
+
fhevm.userDecryptEuint(
|
|
169
|
+
FhevmType.euint32,
|
|
170
|
+
encrypted,
|
|
171
|
+
contractAddress,
|
|
172
|
+
signers.alice
|
|
173
|
+
)
|
|
174
|
+
).to.be.rejected;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should succeed when stored with permission", async function () {
|
|
178
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
179
|
+
|
|
180
|
+
const input = await fhevm
|
|
181
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
182
|
+
.add32(testValue)
|
|
183
|
+
.encrypt();
|
|
184
|
+
|
|
185
|
+
await contract
|
|
186
|
+
.connect(signers.alice)
|
|
187
|
+
.correctStoreWithPermission(input.handles[0], input.inputProof);
|
|
188
|
+
|
|
189
|
+
const encrypted = await contract.getValue();
|
|
190
|
+
const decrypted = await fhevm.userDecryptEuint(
|
|
191
|
+
FhevmType.euint32,
|
|
192
|
+
encrypted,
|
|
193
|
+
contractAddress,
|
|
194
|
+
signers.alice
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
expect(decrypted).to.equal(testValue);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("Pattern 4: Unauthenticated Re-encryption", function () {
|
|
202
|
+
it("wrongReencryptWithoutAuth should return empty bytes", async function () {
|
|
203
|
+
const dummyPublicKey =
|
|
204
|
+
"0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
205
|
+
const result = await contract.wrongReencryptWithoutAuth(dummyPublicKey);
|
|
206
|
+
expect(result).to.equal("0x");
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("correctReencryptWithAuth should return encrypted handle", async function () {
|
|
210
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
211
|
+
|
|
212
|
+
const input = await fhevm
|
|
213
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
214
|
+
.add32(123)
|
|
215
|
+
.encrypt();
|
|
216
|
+
|
|
217
|
+
await contract
|
|
218
|
+
.connect(signers.alice)
|
|
219
|
+
.correctWithUserAllow(input.handles[0], input.inputProof);
|
|
220
|
+
|
|
221
|
+
const encrypted = await contract.correctReencryptWithAuth();
|
|
222
|
+
expect(encrypted).to.not.equal(0);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe("Pattern 5: Transfer Without Permission", function () {
|
|
227
|
+
const initialBalance = 1000;
|
|
228
|
+
const transferAmount = 100;
|
|
229
|
+
|
|
230
|
+
beforeEach(async function () {
|
|
231
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
232
|
+
|
|
233
|
+
// Initialize Alice's balance
|
|
234
|
+
const input = await fhevm
|
|
235
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
236
|
+
.add32(initialBalance)
|
|
237
|
+
.encrypt();
|
|
238
|
+
|
|
239
|
+
await contract
|
|
240
|
+
.connect(signers.alice)
|
|
241
|
+
.initializeBalance(input.handles[0], input.inputProof);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("should FAIL recipient decryption without permission", async function () {
|
|
245
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
246
|
+
|
|
247
|
+
const input = await fhevm
|
|
248
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
249
|
+
.add32(transferAmount)
|
|
250
|
+
.encrypt();
|
|
251
|
+
|
|
252
|
+
await contract
|
|
253
|
+
.connect(signers.alice)
|
|
254
|
+
.wrongTransferWithoutPermission(
|
|
255
|
+
bob.address,
|
|
256
|
+
input.handles[0],
|
|
257
|
+
input.inputProof
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
// Bob cannot decrypt his balance
|
|
261
|
+
const encrypted = await contract.getBalance(bob.address);
|
|
262
|
+
await expect(
|
|
263
|
+
fhevm.userDecryptEuint(
|
|
264
|
+
FhevmType.euint32,
|
|
265
|
+
encrypted,
|
|
266
|
+
contractAddress,
|
|
267
|
+
bob
|
|
268
|
+
)
|
|
269
|
+
).to.be.rejected;
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("should succeed with permission propagation", async function () {
|
|
273
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
274
|
+
|
|
275
|
+
const input = await fhevm
|
|
276
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
277
|
+
.add32(transferAmount)
|
|
278
|
+
.encrypt();
|
|
279
|
+
|
|
280
|
+
await contract
|
|
281
|
+
.connect(signers.alice)
|
|
282
|
+
.correctTransferWithPermission(
|
|
283
|
+
bob.address,
|
|
284
|
+
input.handles[0],
|
|
285
|
+
input.inputProof
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// Bob can decrypt his balance
|
|
289
|
+
const encrypted = await contract.getBalance(bob.address);
|
|
290
|
+
const decrypted = await fhevm.userDecryptEuint(
|
|
291
|
+
FhevmType.euint32,
|
|
292
|
+
encrypted,
|
|
293
|
+
contractAddress,
|
|
294
|
+
bob
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
expect(decrypted).to.equal(transferAmount);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe("Pattern 6: Cross-Contract Permission", function () {
|
|
302
|
+
beforeEach(async function () {
|
|
303
|
+
// Initialize _secretValue so the contract has permission on it
|
|
304
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
305
|
+
const input = await fhevm
|
|
306
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
307
|
+
.add32(42)
|
|
308
|
+
.encrypt();
|
|
309
|
+
|
|
310
|
+
await contract
|
|
311
|
+
.connect(signers.alice)
|
|
312
|
+
.correctWithAllowThis(input.handles[0], input.inputProof);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("wrongCrossContractCall should call without permission", async function () {
|
|
316
|
+
const dummyAddress = "0x0000000000000000000000000000000000000001";
|
|
317
|
+
// This will fail but we're just testing it doesn't revert
|
|
318
|
+
await contract.wrongCrossContractCall(dummyAddress);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("correctCrossContractCall should grant allowTransient", async function () {
|
|
322
|
+
const dummyAddress = "0x0000000000000000000000000000000000000001";
|
|
323
|
+
// This should succeed - contract has permission on _secretValue
|
|
324
|
+
await contract.correctCrossContractCall(dummyAddress);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
});
|