Suffragium: An Encrypted Onchain Voting System Leveraging ZK and FHE Using Zama's fhEVM

November 6, 2024
Alessandro Manfredi

This is a guest blog post written by Alessandro Manfredi.

TLDR: Suffragium is a secure, privacy-preserving voting system that combines zero-knowledge proofs (ZKP) and Fully Homomorphic Encryption (FHE) to create a trustless and tamper-resistant voting platform. Leveraging Zama's fhEVM, Suffragium preserves all essential properties of a secure voting system.

The need for secure and private voting

Traditional voting systems, while foundational to democracy, grapple with challenges like:

  • Ensuring voter anonymity
  • Preventing vote tampering
  • Reducing risks of voter coercion

As technology rapidly advances, new opportunities arise to improve the voting process—yet so do potential threats. To protect and strengthen democratic values, it’s crucial to innovate and enhance our voting systems, adapting them to meet modern security and privacy standards.

Introducing Suffragium: A new standard for voting

Discover Suffragium—a new voting system designed to transform the electoral process. Leveraging Fully Homomorphic Encryption (FHE) and Zero-Knowledge Proofs (ZK proofs), Suffragium aims to uphold the core properties of secure voting: privacy, integrity, transparency, and verifiability. This approach not only protects voter anonymity but also ensures that every vote is counted accurately and remains immune to manipulation.

  • Zero-Knowledge Proofs (ZK proofs): Allow one party to verify the truth of a statement to another without disclosing additional information, maintaining privacy while confirming authenticity.
  • Fully Homomorphic Encryption (FHE): Enables computations on encrypted data without decrypting it, ensuring that results remain confidential throughout processing.

Privacy and security in action with Suffragium

In Suffragium, Zero-Knowledge Proofs play a critical role in securing the voting process and maintaining privacy. Voters gain the right to vote anonymously through identity proofs generated from an email sent by the authority upon registration. These cryptographic proofs contain no identifiable information beyond a hash of the email used for registration. Additional anonymity measures, such as the option to use a one-time email address, protect voters against potential attacks that might reveal their participation.

To participate, users undergo a Know Your Customer (KYC) process, authenticating their identity similarly to traditional ballot verification. However, this KYC process is structured to prevent the authority from knowing whether or how users will vote. The ZK proof is generated deterministically, preventing double voting by the same voter. Although theoretically, the authority could submit multiple votes by creating varied email addresses, Suffragium operates under principles of integrity, trusting the authority to uphold good governance. Note that there could be other, sometimes simpler ways of authenticating voters, but this example is about demonstrating more generally how an email address can be tied to an onchain confidential smart contract.

The voting process: Behind the code

Casting a confidential vote on blockchain.

Once voters successfully generate their cryptographic proof, they can cast their vote. To protect voter privacy on the blockchain, Suffragium avoids storing votes in plain text. Instead, it utilizes Zama’s fhEVM, enabling confidential smart contracts on the Ethereum Virtual Machine (EVM) using FHE. This technology ensures that individual votes remain private, secure, and inaccessible to anyone, including the authority, preserving confidentiality and trust within the voting process.

Casting a vote.

To cast a vote, a user calls the [.c-inline-code]castVote[.c-inline-code] function:

function castVote(
        uint256 voteId,
        einput encryptedSupport,
        bytes calldata supportProof,
        bytes calldata identityPublicValues,
        bytes calldata identityProofBytes
    ) external {
        bytes32 voterId = verifyProofAndGetVoterId(identityPublicValues, identityProofBytes);
        if (_castedVotes[voteId][voterId]) revert AlreadyVoted();
        _castedVotes[voteId][voterId] = true;

        Vote storage vote = _getVote(voteId);
        if (block.number > vote.endBlock) revert VoteClosed();
		
        ebool support = TFHE.asEbool(encryptedSupport, supportProof);

        vote.encryptedResult = TFHE.add(vote.encryptedResult, TFHE.asEuint64(support));
        TFHE.allow(vote.encryptedResult, address(this));
        
        vote.voteCount++;

        emit VoteCasted(voteId);
}
  • Parameters needed: The [.c-inline-code]castVote[.c-inline-code] function requires the [.c-inline-code]encryptedSupport[.c-inline-code] and the ZK proof to validate the user’s right to vote.
  • Vote counters: Votes are tallied with counters—[.c-inline-code]encryptedResult[.c-inline-code] sums up [.c-inline-code]encryptedSupport[.c-inline-code]. In this example votes are a simple boolean (for / against), but the code can easily be extended to support voting with any number of choices.

Revealing the vote results.

Once the voting period ends, anyone can initiate the decryption of results by calling [.c-inline-code]requestRevealVote[.c-inline-code]:

function requestRevealVote(uint256 voteId) external {
        Vote storage vote = _getVote(voteId);
        if (block.number <= vote.endBlock) revert VoteNotClosed();

        uint256[] memory cts = new uint256[](1);
        cts[0] = Gateway.toUint256(vote.encryptedResult);
        uint256 requestId = Gateway.requestDecryption(cts, this.revealVote.selector, 0, block.timestamp + 100, false);
        addParamsUint256(requestId, voteId);
        vote.state = VoteState.RequestedToReveal;

        emit VoteRevealRequested(voteId);
  }

This call triggers an off-chain relayer to interact with a Key Management System (KMS) that holds the decryption keys. Once decrypted, the relayer invokes the callback function [.c-inline-code]revealVote[.c-inline-code] to store the final results onchain.

Finalizing and publishing the results.

The [.c-inline-code]revealVote[.c-inline-code] function updates the Suffragium smart contract with the decrypted results, making them accessible to all participants:

function revealVote(uint256 requestId, uint256 result) external onlyGateway {
        uint256[] memory params = getParamsUint256(requestId);
        uint256 voteId = params[0];

        // Update state and publish results
        Vote storage vote = _getVote(voteId);
        vote.state = VoteState.Revealed;O
        emit VoteRevealed(voteId);
    }

Once [.c-inline-code]revealVote[.c-inline-code] is called, the final vote count is visible to everyone, ensuring transparency and accountability while maintaining voter privacy throughout the process.

Why onchain, confidential voting is great

Consider the typical government voting process: you drive to a designated polling station, wait in long lines, and cast your vote in a physical booth, surrounded by others. Your vote is then dropped into a box, to be manually counted later. This system feels outdated, catering primarily to older demographics accustomed to in-person voting, while limiting accessibility for younger, digitally-native generations.

Suffragium offers a transformative alternative. By moving the entire process onchain with secure, confidential smart contracts, it ensures that voting can be private, tamper-resistant, and accessible from anywhere. Suffragium sets a new benchmark for modern voting solutions, making it easier, safer, and more inclusive for everyone to participate in the democratic process.

Additional links 

Read more related posts