docs/solidity-guides/getting-started/quick-start-tutorial/turn_it_into_fhevm.md
In this tutorial, you'll learn how to take a basic Solidity smart contract and progressively upgrade it to support Fully Homomorphic Encryption using the FHEVM library by Zama.
Starting with the plain Counter.sol contract that you built from the "Write a simple contract" tutorial, and step-by-step, you’ll learn how to:
By the end, you'll have a fully functional smart contract that supports FHE computation.
{% stepper %} {% step %}
FHECounter.sol fileNavigate to your project’s contracts directory:
cd <your-project-root-directory>/contracts
From there, create a new file named FHECounter.sol, and copy the following Solidity code into it:
// 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;
}
}
This is a plain Counter contract that we’ll use as the starting point for adding FHEVM functionality. We will modify this contract step-by-step to progressively integrate FHEVM capabilities. {% endstep %}
{% step %}
Counter into FHECounterTo begin integrating FHEVM features into your contract, we first need to import the required FHEVM libraries.
// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;
// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;
import { FHE, euint32, externalEuint32 } from "@fhevm/solidity/lib/FHE.sol";
import { ZamaEthereumConfig } from "@fhevm/solidity/config/ZamaConfig.sol";
These imports:
/// @title A simple counter contract
contract Counter {
/// @title A simple FHE counter contract
contract FHECounter is ZamaEthereumConfig {
This change:
FHECounterZamaEthereumConfig to enable FHEVM support{% hint style="warning" %}
This contract must inherit from the ZamaEthereumConfig abstract contract; otherwise, it will not be able to execute any FHEVM-related functionality on Sepolia or Hardhat.
{% endhint %}
From your project's root directory, run:
npx hardhat compile
Great! Your smart contract is now compiled and ready to use FHEVM features.
{% endstep %} {% endstepper %}
{% stepper %} {% step %}
increment() and decrement() FunctionsBefore we move forward, let’s comment out the increment() and decrement() functions in FHECounter. We'll replace them later with updated versions that support FHE-encrypted operations.
/// @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;
// }
{% endstep %}
{% step %}
uint32 with the FHEVM euint32 TypeWe’ll now switch from the standard Solidity uint32 type to the encrypted FHEVM type euint32.
This enables private, homomorphic computation on encrypted integers.
uint32 _count;
and
function getCount() external view returns (uint32) {
euint32 _count;
and
function getCount() external view returns (euint32) {
{% endstep %}
{% step %}
increment(uint32 value) with the FHEVM version increment(externalEuint32 value)To support encrypted input, we will update the increment function to accept a value encrypted off-chain.
Instead of using a uint32, the new version will accept an externalEuint32, which is an encrypted integer produced off-chain and sent to the smart contract.
To ensure the validity of this encrypted value, we also include a second argument:inputProof, a bytes array containing a Zero-Knowledge Proof of Knowledge (ZKPoK) that proves two things:
externalEuint32 was encrypted off-chain by the function caller (msg.sender)externalEuint32 is bound to the contract (address(this)) and can only be processed by it. /// @notice Increments the counter by a specific value
// function increment(uint32 value) external {
// _count += value;
// }
/// @notice Increments the counter by a specific value
function increment(externalEuint32 inputEuint32, bytes calldata inputProof) external {
// _count += value;
}
{% endstep %}
{% step %}
externalEuint32 to euint32You cannot directly use externalEuint32 in FHE operations. To manipulate it with the FHEVM library, you first need to convert it into the native FHE type euint32.
This conversion is done using:
FHE.fromExternal(inputEuint32, inputProof);
This method verifies the zero-knowledge proof and returns a usable encrypted value within the contract.
/// @notice Increments the counter by a specific value
function increment(externalEuint32 inputEuint32, bytes calldata inputProof) external {
// _count += value;
}
/// @notice Increments the counter by a specific value
function increment(externalEuint32 inputEuint32, bytes calldata inputProof) external {
euint32 evalue = FHE.fromExternal(inputEuint32, inputProof);
// _count += value;
}
{% endstep %}
{% step %}
_count += value into its FHEVM equivalentTo perform the update _count += value in a Fully Homomorphic way, we use the FHE.add() operator. This function allows us to compute the FHE sum of 2 encrypted integers.
/// @notice Increments the counter by a specific value
function increment(externalEuint32 inputEuint32, bytes calldata inputProof) external {
euint32 evalue = FHE.fromExternal(inputEuint32, inputProof);
// _count += value;
}
/// @notice Increments the counter by a specific value
function increment(externalEuint32 inputEuint32, bytes calldata inputProof) external {
euint32 evalue = FHE.fromExternal(inputEuint32, inputProof);
_count = FHE.add(_count, evalue);
}
{% hint style="info" %} This FHE operation allows the smart contract to process encrypted values without ever decrypting them — a core feature of FHEVM that enables on-chain privacy. {% endhint %}
{% endstep %} {% endstepper %}
{% hint style="warning" %}
This step is critical! You must grant FHE permissions to both the contract and the caller to ensure the encrypted _count value can be decrypted off-chain by the caller. Without these 2 permissions, the caller will not be able to compute the clear result.
{% endhint %}
To grant FHE permission we will call the FHE.allow() function.
/// @notice Increments the counter by a specific value
function increment(externalEuint32 inputEuint32, bytes calldata inputProof) external {
euint32 evalue = FHE.fromExternal(inputEuint32, inputProof);
_count = FHE.add(_count, evalue);
}
/// @notice Increments the counter by a specific value
function increment(externalEuint32 inputEuint32, bytes calldata inputProof) external {
euint32 evalue = FHE.fromExternal(inputEuint32, inputProof);
_count = FHE.add(_count, evalue);
FHE.allowThis(_count);
FHE.allow(_count, msg.sender);
}
{% hint style="info" %} We grant two FHE permissions here — not just one. In the next part of the tutorial, you'll learn why both are necessary. {% endhint %}
decrement() to its FHEVM equivalentJust like with the increment() migration, we’ll now convert the decrement() function to its FHEVM-compatible version.
Replace :
/// @notice Decrements the counter by a specific value
function decrement(uint32 value) external {
require(_count >= value, "Counter: cannot decrement below zero");
_count -= value;
}
with the following :
/// @notice Decrements the counter by a specific 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);
}
{% hint style="warning" %}
The increment() and decrement() functions do not perform any overflow or underflow checks.
{% endhint %}
FHECounter.solFrom your project's root directory, run:
npx hardhat compile
Congratulations! Your smart contract is now fully FHEVM-compatible.
Now you should have the following files in your project:
contracts/FHECounter.sol — your Solidity smart FHEVM contracttest/FHECounter.ts — your FHEVM Hardhat test suite written in TypeScriptIn the next tutorial, we’ll move on to the TypeScript integration, where you’ll learn how to interact with your newly upgraded FHEVM contract in a test suite.