Behind the Scenes of a Confidential Vote

Onchain voting is a killer blockchain use case, but there’s currently one issue holding adoption back: confidentiality. Currently, onchain voting, whether it’s for governance proposals or potential future use cases such as political elections, reveals which address voted for which option due to the public nature of blockchains, creating the possibility of corruption or intimidation. Not ideal.
Fully Homomorphic Encryption (FHE) can help. By leveraging FHE, we can create a voting process where ballots remain private while still ensuring that the final results are accurate.
A key part of this system is how encrypted votes are processed and tallied. This is possible with two core operations: TFHE.add() and TFHE.select(). Let’s break down what happens behind the scenes when a confidential vote is cast.
Recap: What Are Confidential Votes?
Confidential onchain voting systems work similarly to standard onchain voting mechanisms, but with one major difference: all votes and voter identities remain encrypted. Only authorized entities—such as a designated decryption service or a set of threshold key holders—can reveal the final results.
Unlike traditional blockchain voting, where all votes are visible onchain, a confidential vote ensures that no one (not even validators or the smart contract itself) can see who voted for what. The only thing that becomes public is the final tally, once decrypted.
Homomorphic Operations Within TFHE
The fhEVM, which makes confidential voting possible, is built on TFHE (Fast Fully Homomorphic Encryption over the Torus). It’s one of the most efficient FHE schemes for computing over encrypted data without decrypting it.
At the core of confidential voting, we use homomorphic addition and conditional selection, powered by TFHE-rs, an implementation of TFHE:
- TFHE.add() → Adds two encrypted values without decrypting them (used to tally votes).
- TFHE.select() → Conditionally assigns a value based on an encrypted boolean (used to update the correct vote count).
These operations allow votes to be counted without ever decrypting individual ballots. The results remain encrypted until an authorized party reveals them.
Casting a Confidential Vote
Here’s how a confidential vote works at the code level:
function castEncryptedVote(einput encryptedVoteCount, einput encryptedChoice, bytes calldata inputProof) public {
// Extract the encrypted choice (0 = against, 1 = in favor)
euint8 userChoice = TFHE.asEuint8(encryptedChoice, inputProof);
ebool voteChoice = TFHE.eq(TFHE.asEuint8(1), userChoice);
// Convert encrypted vote count
euint64 votePower = TFHE.asEuint64(encryptedVoteCount, inputProof);
// Determine which tally to update using homomorphic selection
euint64 inFavorCountToCast = TFHE.select(voteChoice, votePower, TFHE.asEuint64(0));
euint64 againstCountToCast = TFHE.select(voteChoice, TFHE.asEuint64(0), votePower);
// Update encrypted vote tallies
inFavorCountEncrypted = TFHE.add(inFavorCountEncrypted, inFavorCountToCast);
againstCountEncrypted = TFHE.add(againstCountEncrypted, againstCountToCast);
}
In this function:
- Encrypted vote choices (0 = against, 1 = in favor) remain hidden.
- TFHE.select() ensures that votes are counted in the correct category without revealing their values.
- TFHE.add() updates the encrypted tally while keeping all votes confidential.
Recap: TLWE Ciphertexts in TFHE
TFHE operates on Torus Learning-With-Errors (TLWE) ciphertexts, a cryptographic structure designed for secure computation over encrypted data. LWE ciphertexts follow the equation:
$$c = (a, b), \quad \text{where} \quad b = a \cdot s + m + e$$
Here:
- $a$ is a random vector with entries from the torus.
- $s$ is a secret vector (the private key) with entries from the torus.
- $m$ is the plaintext message (the actual vote count).
- $e$ is a small error term.
In confidential voting, vote counts are stored as TLWE ciphertexts, meaning the blockchain records only encrypted votes, not plaintext values.
Adding Encrypted Votes
When a new vote is cast, it is homomorphically added to the existing total:
Given two encrypted votes:
$$c_1 = (a_1, b_1), \quad c_2 = (a_2, b_2)$$
The smart contract updates the vote count as follows:
$$c_3 = (a_1 + a_2, b_1 + b_2)$$
Since this operation preserves encryption, the blockchain never sees individual votes—only the encrypted sum. However, the final total can still be decrypted by an authorized party when necessary.
Decrypting the Final Vote Count
After voting ends, the contract owner (or an authorized threshold decryption system) can reveal the final tally. This is done using:
function revealVotingResults() public onlyOwner {
// Prepare encrypted totals for decryption
uint256;
cts[0] = Gateway.toUint256(inFavorCountEncrypted);
cts[1] = Gateway.toUint256(againstCountEncrypted);
// Request decryption via the FHE gateway
Gateway.requestDecryption(cts, this.decryptionCallback.selector, 0, block.timestamp + 100, false);
}
Conclusion
TFHE.add() and TFHE.select() are the building blocks that enable confidential voting on fhEVM blockchains. By allowing arithmetic over encrypted values, they help ensure that votes are counted correctly without revealing individual choices.
This innovation enables private, trustless elections in DAOs, corporate governance, and any blockchain-based voting system—without sacrificing the security, transparency, and composability of blockchains.
Subscribe to our newsletter
Top industry insights on FHE.