Skip to content

Commit

Permalink
Merge pull request #16 from aragonzkresearch/ax0/integration
Browse files Browse the repository at this point in the history
CLI changes, delegation and TLCS integration
  • Loading branch information
ax0 authored Jul 28, 2023
2 parents 14a42d6 + 29946d8 commit 1a48ee4
Show file tree
Hide file tree
Showing 32 changed files with 4,461 additions and 594 deletions.
49 changes: 24 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# nouns-anonymous-voting

# ⛔ DISCLAIMER: This is a work in progress and does not fully work yet 💀
# ⛔ DISCLAIMER: This is a work in progress. 💀

This repository consists of a library, CLI and smart contracts for the Nouns private voting project.

Expand All @@ -9,10 +9,11 @@ This is a work in progress. Please do not use this in production.
## Running the client

### Pre-requisites

1. Deploy the [Nouns voting contract](contracts/README.md) to an Ethereum network.
2. Copy the `.env.template` file to `.env` and fill in the values.
3. Have the Nouns CLI installed or have a source code to build it from.
1. Install Noir.
2. Run `prep-contracts.sh` and recompile in case any changes have been made to the underlying circuits.
3. Deploy the [Nouns voting contract](contracts/README.md) to an Ethereum network.
4. Copy the `.env.template` file to `.env` and fill in the values.
5. Have the Nouns CLI installed or have a source code to build it from.

If at any point of time you see that some of the environment variables are not being picked up, try
running `source .env` to load them into the current shell.
Expand All @@ -25,19 +26,19 @@ the `-h` flag.
This function is used to register a new private key inside the Zk-Registry contract. This is a one-time operation you
need to do before you can vote in any process.

For that, you need a Private Key (an arbitrary 32-byte value) that can be submitted as part of the command or set as an
For that, you need a private key (an arbitrary 32-byte value) that can be submitted as part of the command (`-k`) or set as an
environment variable. Refer to the `.env.template` file for more information.

1. To run from source with the private key set as an environment variable:

```bash
cargo run -- reg-key
nouns-cli reg-key
```

2. To run from source with the private key set as a command line argument:

```bash
cargo run -- reg-key -k 043c3780cb30f913d1c34d80437f7c61c973461595986e899ee6a8171143db1d
nouns-cli reg-key -k 043c3780cb30f913d1c34d80437f7c61c973461595986e899ee6a8171143db1d
```

### Create Process
Expand All @@ -47,8 +48,9 @@ process in the future.

For that, you need to provide the following information:

1. The TLCS Public Encryption Key that will be used to encrypt the votes. You should get this from the TLCS server.
2. The duration of the voting process. This can be provided in minutes, hours and days.
1. The IPFS address of the proposal (`-i`), assumed to be based on the raw binary codec and sha2-256 hash.
2. The delay period of the voting process (`-s`), which may be expressed in minutes, hours or days. If this argument is omitted, it is assumed to be 0.
3. The duration of the voting process, also expressed in minutes, hours or days (`-d`).

_**Note** You will need an account that has at least one Nouns to participate in the voting process. If you are running
in a local test network, you can mint a Nouns to your account by running `cargo run --bin premint_nouns`
Expand All @@ -57,13 +59,13 @@ command._
1. To create a process with duration of 1 day:

```bash
cargo run -- create-process -d 1d -t '234056D968BAF183FE8D237D496D1C04188220CD33E8F8D14DF9B84479736B20,2624393FAD9B71C04B3B14D8AC45202DBB4EAFF4C2D1350C9453FC08D18651FE'
nouns-cli create-process -i bafkreidfgllkxpigujgbavuq5kxdd5yo2jid3abzuxhwj7l6socllnd3m4 -d 1d
```

2. To create a process with duration of 10 hours:
2. To create a process with duration of 10 hours and a delay period of 1 hour:

```bash
cargo run -- create-process -d 10h -t '234056D968BAF183FE8D237D496D1C04188220CD33E8F8D14DF9B84479736B20,2624393FAD9B71C04B3B14D8AC45202DBB4EAFF4C2D1350C9453FC08D18651FE'
nouns-cli create-process -i bafkreidfgllkxpigujgbavuq5kxdd5yo2jid3abzuxhwj7l6socllnd3m4 -s 1h -d 10h
```

### Vote
Expand All @@ -72,33 +74,30 @@ This function is used to vote in a process.

As part of the vote, you need to provide the following information:

1. The process ID of the process you want to vote in.
2. The NFT Index of the noun you want to vote for.
3. The Registry Private Key of the Account you want to vote with.
4. The TLCS Public Encryption Key that will be used to encrypt the vote. You should get this from the TLCS server.
5. The Vote Option you want to vote for. This can be either `Yes`, `No` or `Abstain`.
1. The process ID of the process you want to vote in (`-p`).
2. The NFT ID of the Noun you want to vote on behalf of (`-n`).
3. (Optional) The voter's address (`-a`). For an undelegated vote, this can be omitted and the address will be deduced from the NFT ID.
4. The zkRegistry private key of the account corresponding to the voter's address (`-k`).
5. The vote itself (`-v`). Here this is either `Yes` (`y`), `No` (`n`) or `Abstain` (`a`).

_**Note:** make sure that the NFT indeed exists in the Nouns Token contract._
_**Note:** Make sure that the NFT indeed exists in the Nouns Token contract._

```bash
cargo run -- vote -p 0 -n 0 -k 043c3780cb30f913d1c34d80437f7c61c973461595986e899ee6a8171143db1d -v y -t '234056D968BAF183FE8D237D496D1C04188220CD33E8F8D14DF9B84479736B20,2624393FAD9B71C04B3B14D8AC45202DBB4EAFF4C2D1350C9453FC08D18651FE'
nouns-cli vote -p 0 -n 0 -k 043c3780cb30f913d1c34d80437f7c61c973461595986e899ee6a8171143db1d -v y
```

### Tally

This function is used to tally the votes in a process.

As part of the tally, you need to provide the following information:

1. The process ID of the process you want to tally.
2. The TLCS private key that will be used to decrypt the votes. You should get this from the TLCS server.
For the tally, all you need to private is the process ID of the voting process you wish to tally (`-p`).

**Note** That you can only run this command after the voting process has ended. If you are working on a local test net,
you can mine these blocks by running `cargo run --bin mine_blocks` command. Note that 1 block is counted as 12
seconds.

```bash
cargo run -- tally -p 0 -t 059D6B0FE7AD950D220261FE28B7C8B514E3B06D8EBC17179C469120A366B8C9
nouns-cli tally -p 0
```


2 changes: 1 addition & 1 deletion circuits/16_voters/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use dep::tally::verify_tally;

fn main(b_k: Field, process_id: Field, contract_addr: Field, chain_id: [Field; 2], vote_count: [Field; 3], num_voters: Field, k_x: [Field; 16], k_y: [Field; 16], v: [Field; 16])
fn main(b_k: pub Field, process_id: pub Field, contract_addr: pub Field, chain_id: pub [Field; 2], vote_count: pub [Field; 3], num_voters: Field, k_x: [Field; 16], k_y: [Field; 16], v: [Field; 16])
{
assert(verify_tally(b_k, process_id, contract_addr, chain_id, vote_count, num_voters, k_x, k_y, v));
}
2 changes: 1 addition & 1 deletion circuits/client-proof/Nargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ authors = ["AZKR Team"]
compiler_version = "0.5.1"

[dependencies]
trie = { tag = "main", git = "https://github.com/aragonzkresearch/noir-trie-proofs" }
trie = { tag = "nouns", git = "https://github.com/aragonzkresearch/noir-trie-proofs" }
143 changes: 129 additions & 14 deletions circuits/client-proof/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use dep::std::hash::keccak256;

// EdDSA dependencies from stdlib
use dep::std::ec::tecurve::affine::Point;
use dep::std::eddsa::eddsa_poseidon_verify as eddsa_verify;
use dep::std::hash::poseidon::bn254;
use dep::std::ec::consts::te::baby_jubjub;

// Trie proof primitives. TODO: Replace with stdlib equivalents once merged.
use dep::trie::TrieProof32;
use dep::trie::verify_storage_proof;
use dep::trie::TrieProof;

global MAX_BITS: Field = 256; // Required for bit representation of BJJ associated field element
global DEPTH8_PROOF_SIZE: Field = 4256;
Expand All @@ -31,7 +32,7 @@ struct VoteProverInput<MAX_PROOF_SIZE>
registry_account_state: [Field; 2], // Storage hash of zkRegistry contract
nft_account_state: [Field; 2], // Storage hash of NFT contract
tlcs_pk: Point,

// Private inputs
v: Field, // in {0,1,2}
blinding_factor: Field, // TODO: Formerly r: [u1; MAX_BITS]
Expand All @@ -42,8 +43,9 @@ struct VoteProverInput<MAX_PROOF_SIZE>
k: Point,

registered_pbk: Point,
registry_key_sp: TrieProof32<MAX_PROOF_SIZE, 32>, // Proof of registration
nft_ownership_proof: TrieProof32<MAX_PROOF_SIZE, 32> // Proof of NFT ownership
registry_key_proof: TrieProof<32, MAX_PROOF_SIZE, 32>, // Proof of registration
nft_ownership_proof: TrieProof<32, MAX_PROOF_SIZE, 32>, // Proof of NFT ownership
delegation_proof: TrieProof<32, MAX_PROOF_SIZE, 32> // Proof of (non-)delegation
}

fn main(
Expand All @@ -57,7 +59,7 @@ fn main(
registry_account_state: pub [Field; 2], // Storage hash of zkRegistry contract
nft_account_state: pub [Field; 2], // Storage hash of NFT contract
tlcs_pk: pub [Field; 2],

// Private inputs
v: Field, // in {0,1,2}
blinding_factor: Field, // TODO: Formerly r: [u1; MAX_BITS]
Expand All @@ -67,8 +69,9 @@ fn main(
nft_id: [Field; 2],
k: [Field; 2],
registered_pbk: [Field; 2],
registry_key_sp: TrieProof32<DEPTH8_PROOF_SIZE, 32>, // Proof of registration
nft_ownership_proof: TrieProof32<DEPTH8_PROOF_SIZE, 32> // Proof of NFT ownership
registry_key_proof: TrieProof<32, DEPTH8_PROOF_SIZE, 32>,
nft_ownership_proof: TrieProof<32, DEPTH8_PROOF_SIZE, 32>,
delegation_proof: TrieProof<32, DEPTH8_PROOF_SIZE, 32>
)
{
let vote = VoteProverInput {
Expand All @@ -90,8 +93,9 @@ fn main(
nft_id,
k: Point::new(k[0], k[1]),
registered_pbk: Point::new(registered_pbk[0], registered_pbk[1]),
registry_key_sp,
nft_ownership_proof
registry_key_proof,
nft_ownership_proof,
delegation_proof
};

verify_vote(vote);
Expand All @@ -111,18 +115,129 @@ fn verify_vote<MAX_PROOF_SIZE>(vote: VoteProverInput<MAX_PROOF_SIZE>)
// Check nullifier
assert(vote.n == bn254::hash_3([vote.signed_id.r_b8.x, vote.signed_id.r_b8.y, vote.signed_id.s]));

// // Check vote encryption
// Check vote encryption
let b8 = baby_jubjub().base8;
assert(bjj_curve.mul(vote.blinding_factor, b8).eq(vote.a));
assert(vote.k.eq(bjj_curve.mul(vote.blinding_factor, vote.tlcs_pk))); // TODO
assert(vote.b == bn254::hash_7([vote.k.x, vote.k.y, vote.v, vote.chain_id[0], vote.chain_id[1], vote.process_id, vote.contract_addr]));
assert((vote.v == 0) | (vote.v == 1) | (vote.v == 2)); // Check validity of vote

assert(verify_storage_proof(u256_conv(vote.registry_account_state), vote.registry_key_sp));
assert(verify_storage_proof(u256_conv(vote.nft_account_state), vote.nft_ownership_proof));
// Check registration by verifying x coordinate of public key
let address_in_bytes = vote.voter_address.to_be_bytes(20);
let calculated_registry_key = {
let mut buf = [0; 64]; // key ++ storage_slot
let mut out = keccak256(buf, 64);

for i in 0..20
{
buf[i + 12] = address_in_bytes[i];
}

for i in 0..32
{
buf[i + 32] = out[i];
}

keccak256(buf, 64)
};

// Keys should match
assert(calculated_registry_key == vote.registry_key_proof.key);

// And value should be what we expect
let pbk_x_bytes = vote.registered_pbk.x.to_be_bytes(32);
for i in 0..32
{
assert(pbk_x_bytes[i] == vote.registry_key_proof.value[i]);
}

// Storage proof verification
assert(vote.registry_key_proof.verify_storage_root(u256_from_fields(vote.registry_account_state)));

// Check NFT ownership
let calculated_nft_key = {
let mut buf = [0; 64];

buf[63] = 3;
let nft_id_bytes1 = vote.nft_id[0].to_be_bytes(16);
let nft_id_bytes2 = vote.nft_id[1].to_be_bytes(16);

for i in 0..16
{
buf[i] = nft_id_bytes1[i];
buf[16 + i] = nft_id_bytes2[i];
}

keccak256(buf, 64)
};

// Keys should match
assert(calculated_nft_key == vote.nft_ownership_proof.key);

// NFT ownership proof verification
assert(vote.nft_ownership_proof.verify_storage_root(u256_from_fields(vote.nft_account_state)));


// The value should be an address
for i in 0..12
{
assert(vote.nft_ownership_proof.value[i] == 0);
}

// Take note of the owner
let mut nft_owner: [u8; 20] = [0; 20];

for i in 0..20
{
nft_owner[i] = vote.nft_ownership_proof.value[12 + i];
}

// Now check whether the owner's address is the voter's address
let mut nft_owner_p = true;
for i in 0..20 { nft_owner_p &= nft_owner[i] == address_in_bytes[i]; }

// If the corresponding delegate storage slot is empty, the corresponding storage proof will
// fail to verify, as there is nothing in that storage slot. In this case, the NFT owner
// should be the voter.
if vote.delegation_proof.value == [0; 32]
{
assert(nft_owner_p);
}
else
{
// Compute NFT owner's delegation key
let calculated_delegation_key = {
let mut buf = [0; 64];

buf[63] = 0x0b;

for i in 0..20
{
buf[12 + i] = nft_owner[i];
}

keccak256(buf, 64)
};

// Check that it is the one in the proof
assert(calculated_delegation_key == vote.delegation_proof.key);

// Verify the storage proof
assert(vote.delegation_proof.verify_storage_root(u256_from_fields(vote.nft_account_state)));

// Check that the voter is the delegatee
for i in 0..12
{
assert(vote.delegation_proof.value[i] == 0);
}
for i in 0..20
{
assert(vote.delegation_proof.value[12 + i] == address_in_bytes[i]);
}
}
}

fn u256_conv(x: [Field; 2]) -> [u8; 32] // Convert a pair of 128-bit field elements to a 32-byte array
fn u256_from_fields(x: [Field; 2]) -> [u8; 32] // Convert a pair of 128-bit field elements to a 32-byte array
{
let mut out = [0; 32];

Expand Down
6 changes: 6 additions & 0 deletions circuits/hash_proof/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
authors = [""]
compiler_version = "0.7.1"

[dependencies]
trie = { tag = "nouns", git = "https://github.com/aragonzkresearch/noir-trie-proofs" }
Loading

0 comments on commit 1a48ee4

Please sign in to comment.