Private Voting

// SPDX-License-Identifier: BSD-3-Clause-Clear

pragma solidity >=0.8.13 <0.9.0;

import "fhevm/lib/TFHE.sol";
import "fhevm/gateway/GatewayCaller.sol";

contract Voting is GatewayCaller {
    // Mappings for storing encrypted vote counts, choices, and voting status
    mapping(address => euint64) internal encryptedVoteCounts;
    mapping(address => ebool) internal encryptedVoteChoices;
    mapping(address => bool) internal hasVoted;

    // Encrypted tallies for "in favor" and "against" votes
    euint64 private inFavorCountEncrypted;
    euint64 private againstCountEncrypted;

    // Owner and plaintext tallies for revealed results
    address public owner;
    uint64 public inFavorCount;
    uint64 public againstCount;

    // Constructor sets up initial values and permissions for vote tallies
    constructor() {
        inFavorCountEncrypted = TFHE.asEuint64(0);
        againstCountEncrypted = TFHE.asEuint64(0);

        // Allow the contract to manage these encrypted counts
        TFHE.allow(inFavorCountEncrypted, address(this));
        TFHE.allow(againstCountEncrypted, address(this));

        inFavorCount = 0;
        againstCount = 0;
        owner = msg.sender;
    }

    // Modifier to restrict functions to the contract owner
    modifier onlyOwner() {
        require(msg.sender == owner, "Only Owner");
        _;
    }

    /**
     * @notice Casts a vote with a given encrypted vote count and choice
     * @param encryptedVoteCount The encrypted voting power of the user
     * @param encryptedChoice Encrypted choice where 0 = against, 1 = in favor
     * @param inputProof Proof for decrypting inputs
     */
    function castEncryptedVote(einput encryptedVoteCount, einput encryptedChoice, bytes calldata inputProof) public {
        // Form the encrypted choice (0 or 1) for vote type
        euint8 userChoice = TFHE.asEuint8(encryptedChoice, inputProof);

        // Determine the vote type as a boolean: true = in favor, false = against
        ebool voteChoice = TFHE.eq(TFHE.asEuint8(1), userChoice);

        // Form the vote power
        euint64 votePower = TFHE.asEuint64(encryptedVoteCount, inputProof);

        // Update or initialize the encrypted vote count for the sender
        if (TFHE.isInitialized(encryptedVoteCounts[msg.sender])) {
            TFHE.allow(encryptedVoteCounts[msg.sender], address(this));
            encryptedVoteCounts[msg.sender] = TFHE.add(encryptedVoteCounts[msg.sender], votePower);
        } else {
            encryptedVoteCounts[msg.sender] = votePower;
        }

        // If already initialized, allow updating the global state variable and assign value; otherwise, assign directly
        if (TFHE.isInitialized(encryptedVoteChoices[msg.sender])) {
            TFHE.allow(encryptedVoteCounts[msg.sender], address(this));
            encryptedVoteChoices[msg.sender] = voteChoice;
        } else {
            encryptedVoteChoices[msg.sender] = voteChoice;
        }

        // Initialize local variables to avoid issues with uninitialized handles
        euint64 inFavorCountToCast = TFHE.asEuint64(0);
        euint64 againstCountToCast = TFHE.asEuint64(0);

        // Conditional assignment based on vote choice
        inFavorCountToCast = TFHE.select(
            voteChoice,
            votePower, // Set vote power for in-favor count if vote choice is true
            TFHE.asEuint64(0) // Otherwise, set to 0
        );

        againstCountToCast = TFHE.select(
            voteChoice,
            TFHE.asEuint64(0), // Set to 0 if vote choice is true
            votePower // Set vote power for against count if vote choice is false
        );

        // Add the computed vote powers to the encrypted tallies
        againstCountEncrypted = TFHE.add(againstCountEncrypted, againstCountToCast);
        inFavorCountEncrypted = TFHE.add(inFavorCountEncrypted, inFavorCountToCast);

        // Allow both the contract and owner to access these encrypted tallies in the future
        TFHE.allow(againstCountEncrypted, address(this));
        TFHE.allow(againstCountEncrypted, owner);
        TFHE.allow(inFavorCountEncrypted, address(this));
        TFHE.allow(inFavorCountEncrypted, owner);

        // Allow the user to view their vote choice and vote count in the future
        TFHE.allow(encryptedVoteChoices[msg.sender], address(this));
        TFHE.allow(encryptedVoteChoices[msg.sender], msg.sender);
        TFHE.allow(encryptedVoteCounts[msg.sender], address(this));
        TFHE.allow(encryptedVoteCounts[msg.sender], msg.sender);
    }

    /**
     * @notice Allows the owner to reveal the final result by decrypting tallies
     */
    function revealVotingResults() public onlyOwner {
        // Retrieve encrypted tallies
        euint64 totalInFavorCount = inFavorCountEncrypted;
        euint64 totalAgainstCount = againstCountEncrypted;

        // Grant the contract permission to handle encrypted tallies
        TFHE.allow(totalInFavorCount, address(this));
        TFHE.allow(totalAgainstCount, address(this));

        // Request decryption of the final vote tallies
        uint256[] memory cts = new uint256[](2);
        cts[0] = Gateway.toUint256(totalInFavorCount);
        cts[1] = Gateway.toUint256(totalAgainstCount);

        Gateway.requestDecryption(cts, this.decryptionCallback.selector, 0, block.timestamp + 100, false);
    }

    /**
     * @notice Callback function to handle decrypted results from the gateway
     * @param totalFavourCountDecrypted Decrypted in-favor vote count
     * @param totalAgainstCountDecrypted Decrypted against vote count
     * @return True if the callback is successful
     */
    function decryptionCallback(
        uint256 /*requestID*/,
        uint64 totalFavourCountDecrypted,
        uint64 totalAgainstCountDecrypted
    ) public onlyGateway returns (bool) {
        // Update plaintext tallies with decrypted values
        inFavorCount = totalFavourCountDecrypted;
        againstCount = totalAgainstCountDecrypted;
        return true;
    }

    /**
     * @notice Allows a user to view their own encrypted vote count
     * @return The encrypted vote count of the sender
     */
    function getOwnEncryptedVoteCount() public view returns (euint64) {
        return encryptedVoteCounts[msg.sender];
    }

    /**
     * @notice Allows a user to view their own encrypted vote choice
     * @return The encrypted vote choice of the sender
     */
    function getOwnEncryptedVoteChoice() public view returns (ebool) {
        return encryptedVoteChoices[msg.sender];
    }

    /**
     * @notice View the total encrypted count of in-favor votes
     * @return The encrypted in-favor vote count
     */
    function getEncryptedInFavorVoteCount() public view returns (euint64) {
        return inFavorCountEncrypted;
    }

    /**
     * @notice View the total encrypted count of against votes
     * @return The encrypted against vote count
     */
    function getEncryptedAgainstVoteCount() public view returns (euint64) {
        return againstCountEncrypted;
    }
}

You can explore the Hardhat contract for Private Voting at the following link: PrivateVoting Contract.

Last updated