FHE counter
This example demonstrates how to build an confidential counter using FHEVM, in comparison to a simple counter.
A simple counter
// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;
/// @title A simple counter contract
contract Counter {
uint32 private _count;
/// @notice Returns the current count
function getCount() external view returns (uint32) {
return _count;
}
/// @notice Increments the counter by a specific value
function increment(uint32 value) external {
_count += value;
}
/// @notice Decrements the counter by a specific value
function decrement(uint32 value) external {
require(_count >= value, "Counter: cannot decrement below zero");
_count -= value;
}
}import { Counter, Counter__factory } from "../types";
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
import { expect } from "chai";
import { ethers } from "hardhat";
type Signers = {
deployer: HardhatEthersSigner;
alice: HardhatEthersSigner;
bob: HardhatEthersSigner;
};
async function deployFixture() {
const factory = (await ethers.getContractFactory("Counter")) as Counter__factory;
const counterContract = (await factory.deploy()) as Counter;
const counterContractAddress = await counterContract.getAddress();
return { counterContract, counterContractAddress };
}
describe("Counter", function () {
let signers: Signers;
let counterContract: Counter;
before(async function () {
const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
signers = { deployer: ethSigners[0], alice: ethSigners[1], bob: ethSigners[2] };
});
beforeEach(async () => {
({ counterContract } = await deployFixture());
});
it("count should be zero after deployment", async function () {
const count = await counterContract.getCount();
console.log(`Counter.getCount() === ${count}`);
// Expect initial count to be 0 after deployment
expect(count).to.eq(0);
});
it("increment the counter by 1", async function () {
const countBeforeInc = await counterContract.getCount();
const tx = await counterContract.connect(signers.alice).increment(1);
await tx.wait();
const countAfterInc = await counterContract.getCount();
expect(countAfterInc).to.eq(countBeforeInc + 1n);
});
it("decrement the counter by 1", async function () {
// First increment, count becomes 1
let tx = await counterContract.connect(signers.alice).increment(1);
await tx.wait();
// Then decrement, count goes back to 0
tx = await counterContract.connect(signers.alice).decrement(1);
await tx.wait();
const count = await counterContract.getCount();
expect(count).to.eq(0);
});
});An FHE counter
// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;
import { FHE, euint32, externalEuint32 } from "@fhevm/solidity/lib/FHE.sol";
import { SepoliaConfig } from "@fhevm/solidity/config/ZamaConfig.sol";
/// @title A simple FHE counter contract
contract FHECounter is SepoliaConfig {
euint32 private _count;
/// @notice Returns the current count
function getCount() external view returns (euint32) {
return _count;
}
/// @notice Increments the counter by a specified encrypted value.
/// @dev This example omits overflow/underflow checks for simplicity and readability.
/// In a production contract, proper range checks should be implemented.
function increment(externalEuint32 inputEuint32, bytes calldata inputProof) external {
euint32 encryptedEuint32 = FHE.fromExternal(inputEuint32, inputProof);
_count = FHE.add(_count, encryptedEuint32);
FHE.allowThis(_count);
FHE.allow(_count, msg.sender);
}
/// @notice Decrements the counter by a specified encrypted value.
/// @dev This example omits overflow/underflow checks for simplicity and readability.
/// In a production contract, proper range checks should be implemented.
function decrement(externalEuint32 inputEuint32, bytes calldata inputProof) external {
euint32 encryptedEuint32 = FHE.fromExternal(inputEuint32, inputProof);
_count = FHE.sub(_count, encryptedEuint32);
FHE.allowThis(_count);
FHE.allow(_count, msg.sender);
}
}import { FHECounter, FHECounter__factory } from "../types";
import { FhevmType } from "@fhevm/hardhat-plugin";
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
import { expect } from "chai";
import { ethers, fhevm } from "hardhat";
type Signers = {
deployer: HardhatEthersSigner;
alice: HardhatEthersSigner;
bob: HardhatEthersSigner;
};
async function deployFixture() {
const factory = (await ethers.getContractFactory("FHECounter")) as FHECounter__factory;
const fheCounterContract = (await factory.deploy()) as FHECounter;
const fheCounterContractAddress = await fheCounterContract.getAddress();
return { fheCounterContract, fheCounterContractAddress };
}
describe("FHECounter", function () {
let signers: Signers;
let fheCounterContract: FHECounter;
let fheCounterContractAddress: string;
before(async function () {
const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
signers = { deployer: ethSigners[0], alice: ethSigners[1], bob: ethSigners[2] };
});
beforeEach(async () => {
({ fheCounterContract, fheCounterContractAddress } = await deployFixture());
});
it("encrypted count should be uninitialized after deployment", async function () {
const encryptedCount = await fheCounterContract.getCount();
// Expect initial count to be bytes32(0) after deployment,
// (meaning the encrypted count value is uninitialized)
expect(encryptedCount).to.eq(ethers.ZeroHash);
});
it("increment the counter by 1", async function () {
const encryptedCountBeforeInc = await fheCounterContract.getCount();
expect(encryptedCountBeforeInc).to.eq(ethers.ZeroHash);
const clearCountBeforeInc = 0;
// Encrypt constant 1 as a euint32
const clearOne = 1;
const encryptedOne = await fhevm
.createEncryptedInput(fheCounterContractAddress, signers.alice.address)
.add32(clearOne)
.encrypt();
const tx = await fheCounterContract
.connect(signers.alice)
.increment(encryptedOne.handles[0], encryptedOne.inputProof);
await tx.wait();
const encryptedCountAfterInc = await fheCounterContract.getCount();
const clearCountAfterInc = await fhevm.userDecryptEuint(
FhevmType.euint32,
encryptedCountAfterInc,
fheCounterContractAddress,
signers.alice,
);
expect(clearCountAfterInc).to.eq(clearCountBeforeInc + clearOne);
});
it("decrement the counter by 1", async function () {
// Encrypt constant 1 as a euint32
const clearOne = 1;
const encryptedOne = await fhevm
.createEncryptedInput(fheCounterContractAddress, signers.alice.address)
.add32(clearOne)
.encrypt();
// First increment by 1, count becomes 1
let tx = await fheCounterContract
.connect(signers.alice)
.increment(encryptedOne.handles[0], encryptedOne.inputProof);
await tx.wait();
// Then decrement by 1, count goes back to 0
tx = await fheCounterContract.connect(signers.alice).decrement(encryptedOne.handles[0], encryptedOne.inputProof);
await tx.wait();
const encryptedCountAfterDec = await fheCounterContract.getCount();
const clearCountAfterDec = await fhevm.userDecryptEuint(
FhevmType.euint32,
encryptedCountAfterDec,
fheCounterContractAddress,
signers.alice,
);
expect(clearCountAfterDec).to.eq(0);
});
});Last updated