noir by example

zk voting og source

the classic “hello world” case-study for noir

say a group is coordinating on some votes, and they want to keep their individual choices secret during the process

the voter set is represented by a merkle tree, where each leaf is a “commitment” of a voter’s H(private key + some group secret)

a smol helper can generate the tree (assuming private keys 0, 1, 2, 3 and a secret 9)

use dep::std;

#[test]
fn test_build_merkle_tree() {
    let secret = 9;
    let commitment_0 =  std::hash::pedersen([0, secret])[0];
    let commitment_1 =  std::hash::pedersen([1, secret])[0];
    let commitment_2 =  std::hash::pedersen([2, secret])[0];
    let commitment_3 =  std::hash::pedersen([3, secret])[0];

    let left_branch = std::hash::pedersen([commitment_0, commitment_1])[0];
    let right_branch = std::hash::pedersen([commitment_2, commitment_3])[0];

    let root = std::hash::pedersen([left_branch, right_branch])[0];

    std::println("Merkle Tree:");
    std::println([root]);
    std::println([left_branch, right_branch]);
    std::println([commitment_0, commitment_1, commitment_2, commitment_3]);
}

yields these branches, where only the root 0x20c77... is publicly known

0x20c77d6d51119d86868b3a37a64cd4510abd7bdb7f62a9e78e51fe8ca615a194
[
    0x0fcf7577aeb374ed0a19b942e0470521d46bde86f01d3138239ccc4b995c00ae,
    0x1e8e7c1e1f6f65d0d3553e0f715c335f0ccdcc21cda1df7355e6c6e851bccf54,
]
[
    0x0ae5f55a6f2c82f74bba34d9e23c7cd69d41c183c8ddbd9a440ee3de5bf1c649,
    0x1053e87bed5f2a4f5144b386c5403701212f4b3c21cb14683b6ebdfed16c854d, 
    0x1eff4379fbc748b53a07ce5961c24eccdfeb8f17154490d2e41c6096a9519329, 
    0x0e0adf2416341b3d868d88043127be8e6965f3758ce6a80d11a494fe21b0cdff,
]

in order to cast a vote from an anonymized account, the voter will need to prove their membership in this set, without revealing which member they are

phrased differently, when casting a vote from some random account, they need to prove knowledge of:

fn main(
    root: pub Field,         // the "group id" for the voters
    proposal_id: pub Field,  // the individual topic being voted on
    vote: pub u8,            // yes (1) / no (0)
    index: Field,
    hash_path: [Field; 2],
    secret: Field,
    priv_key: Field,
) -> pub Field {

    // prove knowledge of some private key & group secret
    let note_commitment = std::hash::pedersen([priv_key, secret])[0];

    // ensure the voter is part of the group they claim to be voting in
    let computed_root = std::merkle::compute_merkle_root(note_commitment, index, hash_path);
    assert(root == computed_root);

    // ensure the vote is valid (only 0 or 1)
    assert(vote <= 1);

    // publicly return a hash making sure the same private key won't
    // vote multiple times for the same proposal, in the same group
    let nullifier = std::hash::pedersen([root, priv_key, proposal_id]);
    nullifier[0]
}

the functionality can be exercised with a simplified test:

#[test]
fn test_main() {
    // prove a vote for the 0th voter
    let group_root = 0x20c77d6d51119d86868b3a37a64cd4510abd7bdb7f62a9e78e51fe8ca615a194;
    let hash_path = [
        0x1053e87bed5f2a4f5144b386c5403701212f4b3c21cb14683b6ebdfed16c854d,
        0x1e8e7c1e1f6f65d0d3553e0f715c335f0ccdcc21cda1df7355e6c6e851bccf54,
    ];
    let private_key = 0;
    let group_secret = 9;

    let want_nullifier = 0x05c982d312d3c204160b6a692fb7b6e129235599f6c6e4077e794fe1cd7e7a63;
    let got_nullifier = main(
        group_root,
        1,
        1,
        0,
        hash_path,
        group_secret,
        private_key,
    );
    assert(want_nullifier == got_nullifier);
}

the rest of the application logic would live in a more traditional contract (EVM or otherwise) – keeping track of groups, their merkle roots, active votes, and used nullifiers

to see this example integrated in a foundry project, check out the official noir-starter


try it out install the noir toolchain if you haven't yet:
        curl -L https://noirup.org/install | bash
        noirup --nightly
        
then checkout and run the example:
        git clone https://github.com/sambarnes/noir-by-example.git
        cd noir-by-example/circuits/gadgets/zk-voting
        nargo test --show-output
        

...

next (zk age verification)