瀏覽代碼

complete DAO::vote()

narodnik 4 年之前
父節點
當前提交
7266ffadbd

+ 2 - 2
bin/daod/proof/dao-vote-burn.zk

@@ -21,8 +21,8 @@ contract "DaoVoteInput" {
 
 circuit "DaoVoteInput" {
 	# Poseidon hash of the nullifier
-	# nullifier = poseidon_hash(secret, serial);
-	# constrain_instance(nullifier);
+	nullifier = poseidon_hash(secret, serial);
+	constrain_instance(nullifier);
 
 	# Pedersen commitment for coin's value
 	vcv = ec_mul_short(value, VALUE_COMMIT_VALUE);

+ 67 - 22
bin/daod/proof/dao-vote-main.zk

@@ -4,46 +4,91 @@ constant "DaoVoteMain" {
 }
 
 contract "DaoVoteMain" {
-    # Total amount of capital allocated to vote
-    Base total_value,
-    Scalar total_value_blind,
+    # proposal params
+    Base proposal_dest_x,
+    Base proposal_dest_y,
+    Base proposal_amount,
+    Base proposal_serial,
+    Base proposal_token_id,
+    Base proposal_blind,
+
+    # DAO params
+    Base dao_proposer_limit,
+    Base dao_quorum,
+    Base dao_approval_ratio,
+    Base gov_token_id,
+    Base dao_public_x,
+    Base dao_public_y,
+    Base dao_bulla_blind,
 
     # Is the vote yes or no
     Base vote_option,
     Scalar vote_option_blind,
 
+    # Total amount of capital allocated to vote
+    Base value,
+    Scalar value_blind,
+
     # Check the inputs and this proof are for the same token
     Base gov_token_blind,
-    Base gov_token_id,
 }
 
 circuit "DaoVoteMain" {
     token_commit = poseidon_hash(gov_token_id, gov_token_blind);
 	constrain_instance(token_commit);
 
+    dao_bulla = poseidon_hash(
+        dao_proposer_limit,
+        dao_quorum,
+        dao_approval_ratio,
+        gov_token_id,
+        dao_public_x,
+        dao_public_y,
+        dao_bulla_blind,
+        # @tmp-workaround
+        dao_bulla_blind,
+    );
+    # Proposal bulla is valid means DAO bulla is also valid
+    # because of dao-propose-main.zk, already checks that when
+    # we first create the proposal. So it is redundant here.
+
+    proposal_bulla = poseidon_hash(
+        proposal_dest_x,
+        proposal_dest_y,
+        proposal_amount,
+        proposal_serial,
+        proposal_token_id,
+        dao_bulla,
+        proposal_blind,
+        # @tmp-workaround
+        proposal_blind,
+    );
+    constrain_instance(proposal_bulla);
+    # TODO: we need to check the proposal isn't invalidated
+    # that is expired or already executed.
+
 	# Pedersen commitment for vote option
-    # Make the weighted vote
-    wv = base_mul(vote_option, total_value);
-	vco = ec_mul_short(wv, VALUE_COMMIT_VALUE);
-	vcr = ec_mul(vote_option_blind, VALUE_COMMIT_RANDOM);
-	total_vote_commit = ec_add(vco, vcr);
-	# Since total_vote_commit is a curve point, we fetch its coordinates
+    weighted_vote = base_mul(vote_option, value);
+	vote_co = ec_mul_short(weighted_vote, VALUE_COMMIT_VALUE);
+	vote_cr = ec_mul(vote_option_blind, VALUE_COMMIT_RANDOM);
+	vote_commit = ec_add(vote_co, vote_cr);
+	# Since vote_commit is a curve point, we fetch its coordinates
 	# and constrain them:
-	total_vote_commit_x = ec_get_x(total_vote_commit);
-	total_vote_commit_y = ec_get_y(total_vote_commit);
-	constrain_instance(total_vote_commit_x);
-	constrain_instance(total_vote_commit_y);
+	vote_commit_x = ec_get_x(vote_commit);
+	vote_commit_y = ec_get_y(vote_commit);
+	constrain_instance(vote_commit_x);
+	constrain_instance(vote_commit_y);
 
 	# Pedersen commitment for vote value
-	vcv = ec_mul_short(total_value, VALUE_COMMIT_VALUE);
-	vcr = ec_mul(total_value_blind, VALUE_COMMIT_RANDOM);
-	total_value_commit = ec_add(vcv, vcr);
-	# Since total_funds_commit is a curve point, we fetch its coordinates
+	vcv = ec_mul_short(value, VALUE_COMMIT_VALUE);
+	vcr = ec_mul(value_blind, VALUE_COMMIT_RANDOM);
+	value_commit = ec_add(vcv, vcr);
+	# Since value_commit is a curve point, we fetch its coordinates
 	# and constrain them:
-	total_value_commit_x = ec_get_x(total_value_commit);
-	total_value_commit_y = ec_get_y(total_value_commit);
-	constrain_instance(total_value_commit_x);
-	constrain_instance(total_value_commit_y);
+	value_commit_x = ec_get_x(value_commit);
+	value_commit_y = ec_get_y(value_commit);
+	constrain_instance(value_commit_x);
+	constrain_instance(value_commit_y);
 
     # This is the main check
     # TODO: vote option should be 0 or 1

+ 1 - 0
bin/daod/src/dao_contract/propose/wallet.rs

@@ -54,6 +54,7 @@ pub struct Proposal {
     pub blind: pallas::Base,
 }
 
+#[derive(Clone)]
 pub struct DaoParams {
     pub proposer_limit: u64,
     pub quorum: u64,

+ 47 - 14
bin/daod/src/dao_contract/state.rs

@@ -1,12 +1,16 @@
 use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
 use pasta_curves::{
     arithmetic::CurveAffine,
-    group::{ff::Field, Curve, Group},
+    group::{
+        ff::{Field, PrimeField},
+        Curve, Group, GroupEncoding,
+    },
     pallas,
 };
 use std::{
     any::{Any, TypeId},
     collections::HashMap,
+    hash::{Hash, Hasher},
 };
 
 use darkfi::{
@@ -31,12 +35,30 @@ pub struct DaoBulla(pub pallas::Base);
 
 type MerkleTree = BridgeTree<MerkleNode, MERKLE_DEPTH>;
 
-pub struct ProposalVote {
-    proposal_bulla: pallas::Base,
+#[derive(Eq, PartialEq)]
+pub struct HashableBase(pallas::Base);
+
+impl std::hash::Hash for HashableBase {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        let bytes = self.0.to_repr();
+        bytes.hash(state);
+    }
+}
+
+pub struct ProposalVotes {
+    // TODO: might be more logical to have 'yes_vote_commits' and 'no_vote_commits'
     /// Weighted vote commits
-    vote_commits: pallas::Point,
+    pub vote_commits: pallas::Point,
     /// All value staked in the vote
-    value_commits: pallas::Point,
+    pub value_commits: pallas::Point,
+    /// Vote nullifiers
+    pub vote_nulls: Vec<Nullifier>,
+}
+
+impl ProposalVotes {
+    pub fn nullifier_exists(&self, nullifier: &Nullifier) -> bool {
+        self.vote_nulls.iter().any(|n| n == nullifier)
+    }
 }
 
 /// This DAO state is for all DAOs on the network. There should only be a single instance.
@@ -48,9 +70,7 @@ pub struct State {
     //proposal_bullas: Vec<pallas::Base>,
     pub proposal_tree: MerkleTree,
     pub proposal_roots: Vec<MerkleNode>,
-    // Annoying, we cannot use pallas::Base as a HashMap key
-    //proposal_votes: HashMap<pallas::Base, ProposalVote>,
-    proposal_votes: Vec<ProposalVote>,
+    proposal_votes: HashMap<HashableBase, ProposalVotes>,
 }
 
 impl State {
@@ -62,7 +82,7 @@ impl State {
             //proposal_bullas: Vec::new(),
             proposal_tree: MerkleTree::new(100),
             proposal_roots: Vec::new(),
-            proposal_votes: Vec::new(),
+            proposal_votes: HashMap::new(),
         })
     }
 
@@ -78,11 +98,24 @@ impl State {
         //self.proposal_bullas.push(bulla);
         self.proposal_tree.append(&node);
         self.proposal_roots.push(self.proposal_tree.root(0).unwrap());
-        self.proposal_votes.push(ProposalVote {
-            proposal_bulla: bulla,
-            vote_commits: pallas::Point::identity(),
-            value_commits: pallas::Point::identity(),
-        });
+        self.proposal_votes.insert(
+            HashableBase(bulla),
+            ProposalVotes {
+                vote_commits: pallas::Point::identity(),
+                value_commits: pallas::Point::identity(),
+                vote_nulls: Vec::new(),
+            },
+        );
+    }
+
+    pub fn lookup_proposal_votes(&self, proposal_bulla: pallas::Base) -> Option<&ProposalVotes> {
+        self.proposal_votes.get(&HashableBase(proposal_bulla))
+    }
+    pub fn lookup_proposal_votes_mut(
+        &mut self,
+        proposal_bulla: pallas::Base,
+    ) -> Option<&mut ProposalVotes> {
+        self.proposal_votes.get_mut(&HashableBase(proposal_bulla))
     }
 
     //pub fn add_proposal_vote(&mut self,

+ 78 - 29
bin/daod/src/dao_contract/vote/validate.rs

@@ -1,7 +1,7 @@
 use darkfi::{
     crypto::{
-        keypair::PublicKey, merkle_node::MerkleNode, schnorr, schnorr::SchnorrPublic,
-        types::DrkCircuitField, Proof,
+        keypair::PublicKey, merkle_node::MerkleNode, nullifier::Nullifier, schnorr,
+        schnorr::SchnorrPublic, types::DrkCircuitField, Proof,
     },
     util::serial::{Encodable, SerialDecodable, SerialEncodable, VarInt},
     Error as DarkFiError,
@@ -23,6 +23,15 @@ use crate::{
 
 #[derive(Debug, Clone, thiserror::Error)]
 pub enum Error {
+    #[error("Invalid proposal")]
+    InvalidProposal,
+
+    #[error("Voting with already spent coinage")]
+    SpentCoin,
+
+    #[error("Double voting")]
+    DoubleVote,
+
     #[error("Invalid input merkle root")]
     InvalidInputMerkleRoot,
 
@@ -52,11 +61,11 @@ pub struct CallData {
 impl CallDataBase for CallData {
     fn zk_public_values(&self) -> Vec<Vec<DrkCircuitField>> {
         let mut zk_publics = Vec::new();
-        let mut total_funds_commit = pallas::Point::identity();
+        let mut total_value_commit = pallas::Point::identity();
 
         assert!(self.inputs.len() > 0, "inputs length cannot be zero");
         for input in &self.inputs {
-            total_funds_commit += input.value_commit;
+            total_value_commit += input.value_commit;
             let value_coords = input.value_commit.to_affine().coordinates().unwrap();
             let value_commit_x = *value_coords.x();
             let value_commit_y = *value_coords.y();
@@ -65,32 +74,38 @@ impl CallDataBase for CallData {
             let sigpub_x = *sigpub_coords.x();
             let sigpub_y = *sigpub_coords.y();
 
+            // dao-vote-burn proof
             zk_publics.push(vec![
+                input.nullifier.0,
                 value_commit_x,
                 value_commit_y,
-                //TODO: self.header.token_commit,
+                self.header.token_commit,
                 input.merkle_root.0,
                 sigpub_x,
                 sigpub_y,
             ]);
         }
 
-        let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap();
-        let total_funds_x = *total_funds_coords.x();
-        let total_funds_y = *total_funds_coords.y();
+        let vote_commit_coords = self.header.vote_commit.to_affine().coordinates().unwrap();
+        let vote_commit_x = *vote_commit_coords.x();
+        let vote_commit_y = *vote_commit_coords.y();
+
+        let value_commit_coords = total_value_commit.to_affine().coordinates().unwrap();
+        let value_commit_x = *value_commit_coords.x();
+        let value_commit_y = *value_commit_coords.y();
         zk_publics.push(
-            // dao-propose-main proof
+            // dao-vote-main proof
             vec![
-                //self.header.token_commit,
-                //self.header.dao_merkle_root.0,
-                //self.header.proposal_bulla,
-                total_funds_x,
-                total_funds_y,
+                self.header.token_commit,
+                self.header.proposal_bulla,
+                vote_commit_x,
+                vote_commit_y,
+                value_commit_x,
+                value_commit_y,
             ],
         );
 
         zk_publics
-        //vec![]
     }
 
     fn zk_proof_addrs(&self) -> Vec<String> {
@@ -98,7 +113,7 @@ impl CallDataBase for CallData {
         for input in &self.inputs {
             zk_addrs.push("dao-vote-burn".to_string());
         }
-        zk_addrs.push("dao-voe-main".to_string());
+        zk_addrs.push("dao-vote-main".to_string());
         zk_addrs
     }
 
@@ -109,11 +124,15 @@ impl CallDataBase for CallData {
 
 #[derive(Clone, SerialEncodable, SerialDecodable)]
 pub struct Header {
+    pub token_commit: pallas::Base,
+    pub proposal_bulla: pallas::Base,
+    pub vote_commit: pallas::Point,
     pub enc_note: EncryptedNote2,
 }
 
 #[derive(Clone, SerialEncodable, SerialDecodable)]
 pub struct Input {
+    pub nullifier: Nullifier,
     pub value_commit: pallas::Point,
     pub merkle_root: MerkleNode,
     pub signature_public: PublicKey,
@@ -123,7 +142,7 @@ pub fn state_transition(
     states: &StateRegistry,
     func_call_index: usize,
     parent_tx: &Transaction,
-) -> Result<()> {
+) -> Result<Update> {
     let func_call = &parent_tx.func_calls[func_call_index];
     let call_data = func_call.call_data.as_any();
 
@@ -133,17 +152,36 @@ pub fn state_transition(
     // This will be inside wasm so unwrap is fine.
     let call_data = call_data.unwrap();
 
+    let dao_state = states.lookup::<DaoState>(&"DAO".to_string()).unwrap();
+
+    // Check proposal_bulla exists
+    let votes_info = dao_state.lookup_proposal_votes(call_data.header.proposal_bulla);
+    if votes_info.is_none() {
+        return Err(Error::InvalidProposal)
+    }
+    let votes_info = votes_info.unwrap();
+
     // Check the merkle roots for the input coins are valid
+    let mut vote_nulls = Vec::new();
+    let mut total_value_commit = pallas::Point::identity();
     for input in &call_data.inputs {
         let money_state = states.lookup::<MoneyState>(&"Money".to_string()).unwrap();
         if !money_state.is_valid_merkle(&input.merkle_root) {
             return Err(Error::InvalidInputMerkleRoot)
         }
-    }
 
-    let state = states.lookup::<DaoState>(&"DAO".to_string()).unwrap();
+        if money_state.nullifier_exists(&input.nullifier) {
+            return Err(Error::SpentCoin)
+        }
+
+        if votes_info.nullifier_exists(&input.nullifier) {
+            return Err(Error::DoubleVote)
+        }
 
-    //// TODO: check if the proposal bulla generated in the ZK proof is valid?
+        total_value_commit += input.value_commit;
+
+        vote_nulls.push(input.nullifier);
+    }
 
     // Verify the available signatures
     let mut unsigned_tx_data = vec![];
@@ -151,6 +189,8 @@ pub fn state_transition(
     call_data.inputs.encode(&mut unsigned_tx_data).expect("failed to encode inputs");
     func_call.proofs.encode(&mut unsigned_tx_data).expect("failed to encode proofs");
 
+    //debug!("unsigned_tx_data: {:?}", unsigned_tx_data);
+
     for (i, (input, signature)) in
         call_data.inputs.iter().zip(call_data.signatures.iter()).enumerate()
     {
@@ -160,16 +200,25 @@ pub fn state_transition(
         }
     }
 
-    //// TODO: look at gov tokens avoid using already spent ones
-    //// Need to spend original coin and generate 2 nullifiers?
-
-    //Ok(Update { proposal_bulla: call_data.header.proposal_bulla })
-    Ok(())
+    Ok(Update {
+        proposal_bulla: call_data.header.proposal_bulla,
+        vote_nulls,
+        vote_commit: call_data.header.vote_commit,
+        value_commit: total_value_commit,
+    })
 }
 
 pub struct Update {
-    // TODO
-    //value_commit:
-    //vote_commit:
-    //token_commit:
+    proposal_bulla: pallas::Base,
+    vote_nulls: Vec<Nullifier>,
+    vote_commit: pallas::Point,
+    value_commit: pallas::Point,
+}
+
+pub fn apply(states: &mut StateRegistry, mut update: Update) {
+    let state = states.lookup_mut::<DaoState>(&"DAO".to_string()).unwrap();
+    let votes_info = state.lookup_proposal_votes_mut(update.proposal_bulla).unwrap();
+    votes_info.vote_commits += update.vote_commit;
+    votes_info.value_commits += update.value_commit;
+    votes_info.vote_nulls.append(&mut update.vote_nulls);
 }

+ 137 - 83
bin/daod/src/dao_contract/vote/wallet.rs

@@ -14,6 +14,7 @@ use darkfi::{
         keypair::{Keypair, PublicKey, SecretKey},
         merkle_node::MerkleNode,
         mint_proof::create_mint_proof,
+        nullifier::Nullifier,
         proof::ProvingKey,
         schnorr::SchnorrSecret,
         types::{
@@ -29,8 +30,8 @@ use darkfi::{
 
 use crate::{
     dao_contract::{
-        propose::wallet::Proposal,
-        vote::validate::{Header, Input},
+        propose::wallet::{DaoParams, Proposal},
+        vote::validate::{CallData, Header, Input},
     },
     demo::{CallDataBase, FuncCall, StateRegistry, ZkContractInfo, ZkContractTable},
     money_contract, note,
@@ -39,33 +40,18 @@ use crate::{
 
 use log::debug;
 
-struct CallData {}
-
-impl CallDataBase for CallData {
-    fn zk_public_values(&self) -> Vec<Vec<DrkCircuitField>> {
-        vec![]
-    }
-    fn zk_proof_addrs(&self) -> Vec<String> {
-        vec![]
-    }
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-}
-
 #[derive(SerialEncodable, SerialDecodable)]
 pub struct Note {
-    vote: Vote,
-    value: u64,
+    pub vote: Vote,
+    pub value: u64,
+    pub value_blind: pallas::Scalar,
 }
 
 #[derive(SerialEncodable, SerialDecodable)]
 // All info needed for vote and value commits
 pub struct Vote {
-    pub value_blind: pallas::Scalar,
     pub vote_option: bool,
     pub vote_option_blind: pallas::Scalar,
-    //TODO: gov_token_id: pallas::Base,
 }
 
 pub struct BuilderInput {
@@ -75,13 +61,14 @@ pub struct BuilderInput {
     pub merkle_path: Vec<MerkleNode>,
 }
 
-// TODO: Pass DAO and Proposal into Builder.
+// TODO: should be token locking voting?
 // Inside ZKproof, check proposal is correct.
 pub struct Builder {
     pub inputs: Vec<BuilderInput>,
     pub vote: Vote,
     pub vote_keypair: Keypair,
     pub proposal: Proposal,
+    pub dao: DaoParams,
 }
 
 impl Builder {
@@ -93,15 +80,15 @@ impl Builder {
         let vote_blind = pallas::Scalar::random(&mut OsRng);
 
         let mut inputs = vec![];
-        let mut total_value = 0;
-        let mut total_value_blind = pallas::Scalar::from(0);
+        let mut value = 0;
+        let mut value_blind = pallas::Scalar::from(0);
         let mut signature_secrets = vec![];
 
         for input in self.inputs {
-            let value_blind = pallas::Scalar::random(&mut OsRng);
+            let input_value_blind = pallas::Scalar::random(&mut OsRng);
 
-            total_value += input.note.value;
-            total_value_blind += value_blind;
+            value += input.note.value;
+            value_blind += input_value_blind;
 
             let signature_secret = SecretKey::random(&mut OsRng);
             let signature_public = PublicKey::from_secret(signature_secret);
@@ -127,7 +114,7 @@ impl Builder {
                 Witness::Base(Value::known(pallas::Base::from(note.value))),
                 Witness::Base(Value::known(note.token_id)),
                 Witness::Base(Value::known(note.coin_blind)),
-                Witness::Scalar(Value::known(value_blind)),
+                Witness::Scalar(Value::known(input_value_blind)),
                 Witness::Base(Value::known(gov_token_blind)),
                 Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())),
                 Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
@@ -163,7 +150,9 @@ impl Builder {
             };
 
             let token_commit = poseidon_hash::<2>([note.token_id, gov_token_blind]);
-            //TODO: assert_eq!(self.dao.gov_token_id, note.token_id);
+            assert_eq!(self.dao.gov_token_id, note.token_id);
+
+            let nullifier = poseidon_hash::<2>([input.secret.0, note.serial]);
 
             let value_commit = pedersen_commitment_u64(note.value, value_blind);
             let value_coords = value_commit.to_affine().coordinates().unwrap();
@@ -175,6 +164,7 @@ impl Builder {
             let sigpub_y = *sigpub_coords.y();
 
             let public_inputs = vec![
+                nullifier,
                 value_commit_x,
                 value_commit_y,
                 token_commit,
@@ -193,16 +183,59 @@ impl Builder {
             // First we make the tx then sign after
             signature_secrets.push(signature_secret);
 
-            let input = Input { value_commit, merkle_root, signature_public };
+            let input = Input {
+                nullifier: Nullifier(nullifier),
+                value_commit,
+                merkle_root,
+                signature_public,
+            };
             inputs.push(input);
         }
 
-        //TODO: let token_commit = poseidon_hash::<2>([self.dao.gov_token_id, gov_token_blind]);
+        let token_commit = poseidon_hash::<2>([self.dao.gov_token_id, gov_token_blind]);
+
+        let proposal_dest_coords = self.proposal.dest.0.to_affine().coordinates().unwrap();
+        let proposal_dest_x = *proposal_dest_coords.x();
+        let proposal_dest_y = *proposal_dest_coords.y();
+
+        let proposal_amount = pallas::Base::from(self.proposal.amount);
+
+        let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
+        let dao_quorum = pallas::Base::from(self.dao.quorum);
+        let dao_approval_ratio = pallas::Base::from(self.dao.approval_ratio);
+
+        let dao_pubkey_coords = self.dao.public_key.0.to_affine().coordinates().unwrap();
+        let dao_public_x = *dao_pubkey_coords.x();
+        let dao_public_y = *dao_pubkey_coords.x();
+
+        let dao_bulla = poseidon_hash::<8>([
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio,
+            self.dao.gov_token_id,
+            dao_public_x,
+            dao_public_y,
+            self.dao.bulla_blind,
+            // @tmp-workaround
+            self.dao.bulla_blind,
+        ]);
+
+        let proposal_bulla = poseidon_hash::<8>([
+            proposal_dest_x,
+            proposal_dest_y,
+            proposal_amount,
+            self.proposal.serial,
+            self.proposal.token_id,
+            dao_bulla,
+            self.proposal.blind,
+            // @tmp-workaround
+            self.proposal.blind,
+        ]);
 
         let vote = self.vote.vote_option as u64;
         assert!(vote == 0 || vote == 1);
 
-        let weighted_vote = vote * total_value;
+        let weighted_vote = vote * value;
 
         let vote_commit = pedersen_commitment_u64(weighted_vote, vote_blind);
         let vote_coords = vote_commit.to_affine().coordinates().unwrap();
@@ -210,11 +243,11 @@ impl Builder {
         let vote_commit_y = *vote_coords.y();
         let vote = pallas::Base::from(vote);
 
-        let total_value_commit = pedersen_commitment_u64(total_value, total_value_blind);
-        let total_value_coords = total_value_commit.to_affine().coordinates().unwrap();
-        let total_value_x = *total_value_coords.x();
-        let total_value_y = *total_value_coords.y();
-        let value_base = pallas::Base::from(total_value);
+        let value_commit = pedersen_commitment_u64(value, value_blind);
+        let value_coords = value_commit.to_affine().coordinates().unwrap();
+        let value_commit_x = *value_coords.x();
+        let value_commit_y = *value_coords.y();
+        let value_base = pallas::Base::from(value);
 
         let zk_info = zk_bins.lookup(&"dao-vote-main".to_string()).unwrap();
         let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
@@ -224,58 +257,79 @@ impl Builder {
         };
         let zk_bin = zk_info.bincode.clone();
 
-        //let prover_witnesses = vec![
-        //    // Total number of gov tokens allocated
-        //    Witness::Base(Value::known(value_base)),
-        //    Witness::Scalar(Value::known(total_value_blind)),
-        //    // Vote
-        //    Witness::Base(Value::known(vote)),
-        //    Witness::Scalar(Value::known(vote_blind)),
-        //    // TODO: gov token
-        //    Witness::Base(Value::known(vote.token_id)),
-        //    Witness::Base(Value::known(vote.token_blind)),
-        //];
-
-        //let public_inputs = vec![
-        //    //TODO: token_commit
-        //    total_value_x,
-        //    total_value_y,
-        //    vote_commit_x,
-        //    vote_commit_y,
-        //];
-
-        //let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
-
-        //let proving_key = &zk_info.proving_key;
-        //debug!(target: "dao_contract::vote::wallet::Builder", "main_proof Proof::create()");
-        //let main_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
-        //    .expect("DAO::vote() proving error!");
-        //proofs.push(main_proof);
-
-        //let note = Note { vote: self.vote, value: total_value };
-        //let enc_note = note::encrypt(&note, &self.vote_keypair.public).unwrap();
-
-        //let header = Header { enc_note };
-
-        //let mut unsigned_tx_data = vec![];
-        //header.encode(&mut unsigned_tx_data).expect("failed to encode data");
-        //inputs.encode(&mut unsigned_tx_data).expect("failed to encode inputs");
-        //proofs.encode(&mut unsigned_tx_data).expect("failed to encode proofs");
-
-        //let mut signatures = vec![];
-        //for signature_secret in &signature_secrets {
-        //    let signature = signature_secret.sign(&unsigned_tx_data[..]);
-        //    signatures.push(signature);
-        //}
-
-        //let call_data = CallData { header, inputs, signatures };
-        let call_data = CallData {};
+        let prover_witnesses = vec![
+            // proposal params
+            Witness::Base(Value::known(proposal_dest_x)),
+            Witness::Base(Value::known(proposal_dest_y)),
+            Witness::Base(Value::known(proposal_amount)),
+            Witness::Base(Value::known(self.proposal.serial)),
+            Witness::Base(Value::known(self.proposal.token_id)),
+            Witness::Base(Value::known(self.proposal.blind)),
+            // DAO params
+            Witness::Base(Value::known(dao_proposer_limit)),
+            Witness::Base(Value::known(dao_quorum)),
+            Witness::Base(Value::known(dao_approval_ratio)),
+            Witness::Base(Value::known(self.dao.gov_token_id)),
+            Witness::Base(Value::known(dao_public_x)),
+            Witness::Base(Value::known(dao_public_y)),
+            Witness::Base(Value::known(self.dao.bulla_blind)),
+            // Vote
+            Witness::Base(Value::known(vote)),
+            Witness::Scalar(Value::known(vote_blind)),
+            // Total number of gov tokens allocated
+            Witness::Base(Value::known(value_base)),
+            Witness::Scalar(Value::known(value_blind)),
+            // gov token
+            Witness::Base(Value::known(gov_token_blind)),
+        ];
+
+        let public_inputs = vec![
+            token_commit,
+            proposal_bulla,
+            vote_commit_x,
+            vote_commit_y,
+            value_commit_x,
+            value_commit_y,
+        ];
+
+        let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
+
+        let proving_key = &zk_info.proving_key;
+        debug!(target: "dao_contract::vote::wallet::Builder", "main_proof = Proof::create()");
+        let main_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
+            .expect("DAO::vote() proving error!");
+        proofs.push(main_proof);
+
+        let note = Note { vote: self.vote, value, value_blind };
+        let enc_note = note::encrypt(&note, &self.vote_keypair.public).unwrap();
+
+        let header = Header { token_commit, proposal_bulla, vote_commit, enc_note };
+
+        let mut unsigned_tx_data = vec![];
+        header.encode(&mut unsigned_tx_data).expect("failed to encode data");
+        inputs.encode(&mut unsigned_tx_data).expect("failed to encode inputs");
+        proofs.encode(&mut unsigned_tx_data).expect("failed to encode proofs");
+
+        //debug!("unsigned_tx_data: {:?}", unsigned_tx_data);
+
+        let mut signatures = vec![];
+        assert_eq!(
+            signature_secrets.len(),
+            inputs.len(),
+            "non matching signature_secrets and inputs length!"
+        );
+        for signature_secret in &signature_secrets {
+            let signature = signature_secret.sign(&unsigned_tx_data[..]);
+            signatures.push(signature);
+        }
+
+        let call_data = CallData { header, inputs, signatures };
 
         FuncCall {
             contract_id: "DAO".to_string(),
             func_id: "DAO::vote()".to_string(),
             call_data: Box::new(call_data),
-            proofs: vec![],
+            proofs,
         }
     }
 }

+ 44 - 18
bin/daod/src/demo.rs

@@ -619,6 +619,15 @@ pub async fn demo() -> Result<()> {
         (merkle_path, root)
     };
 
+    let dao_params = dao_contract::propose::wallet::DaoParams {
+        proposer_limit: dao_proposer_limit,
+        quorum: dao_quorum,
+        approval_ratio: dao_approval_ratio,
+        gov_token_id: gdrk_token_id,
+        public_key: dao_keypair.public,
+        bulla_blind: dao_bulla_blind,
+    };
+
     let builder = dao_contract::propose::wallet::Builder {
         inputs: vec![input],
         proposal: dao_contract::propose::wallet::Proposal {
@@ -628,14 +637,7 @@ pub async fn demo() -> Result<()> {
             token_id: xdrk_token_id,
             blind: pallas::Base::random(&mut OsRng),
         },
-        dao: dao_contract::propose::wallet::DaoParams {
-            proposer_limit: dao_proposer_limit,
-            quorum: dao_quorum,
-            approval_ratio: dao_approval_ratio,
-            gov_token_id: gdrk_token_id,
-            public_key: dao_keypair.public,
-            bulla_blind: dao_bulla_blind,
-        },
+        dao: dao_params.clone(),
         dao_leaf_position,
         dao_merkle_path,
         dao_merkle_root,
@@ -715,13 +717,13 @@ pub async fn demo() -> Result<()> {
     let (money_leaf_position, money_merkle_path) = {
         let state = states.lookup::<money_contract::State>(&"Money".to_string()).unwrap();
         let tree = &state.tree;
-        let leaf_position = gov_recv[0].leaf_position.clone();
+        let leaf_position = gov_recv[1].leaf_position.clone();
         let root = tree.root(0).unwrap();
         let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
         (leaf_position, merkle_path)
     };
 
-    debug!(target: "demo", "Stage 5. Creating inputs...");
+    debug!(target: "demo", "Creating inputs...");
     let input = dao_contract::vote::wallet::BuilderInput {
         secret: gov_keypair_2.secret,
         note: gov_recv[1].note.clone(),
@@ -734,20 +736,20 @@ pub async fn demo() -> Result<()> {
     assert!(vote_option == true || vote_option == false);
 
     // We create a new keypair to encrypt the vote.
-    let vote_keypair = Keypair::random(&mut OsRng);
+    let vote_keypair_1 = Keypair::random(&mut OsRng);
 
-    debug!(target: "demo", "Stage 5. Creating builder...");
+    debug!(target: "demo", "Creating builder...");
     let builder = dao_contract::vote::wallet::Builder {
         inputs: vec![input],
         vote: dao_contract::vote::wallet::Vote {
-            value_blind: pallas::Scalar::random(&mut OsRng),
             vote_option,
             vote_option_blind: pallas::Scalar::random(&mut OsRng),
         },
-        vote_keypair,
+        vote_keypair: vote_keypair_1,
         proposal,
+        dao: dao_params.clone(),
     };
-    debug!(target: "demo", "Stage 5. build()...");
+    debug!(target: "demo", "build()...");
     let func_call = builder.build(&zk_bins);
 
     let tx = Transaction { func_calls: vec![func_call] };
@@ -758,10 +760,34 @@ pub async fn demo() -> Result<()> {
         if func_call.func_id == "DAO::vote()" {
             debug!(target: "demo", "dao_contract::vote::state_transition()");
 
-            //let update = dao_contract::vote::validate::state_transition(&states, idx, &tx)
-            //    .expect("dao_contract::vote::validate::state_transition() failed!");
-            //dao_contract::vote::validate::apply(&mut states, update);
+            let update = dao_contract::vote::validate::state_transition(&states, idx, &tx)
+                .expect("dao_contract::vote::validate::state_transition() failed!");
+            dao_contract::vote::validate::apply(&mut states, update);
         }
     }
+
+    tx.zk_verify(&zk_bins);
+
+    //// Wallet
+
+    // Secret vote info. Needs to be revealed at some point.
+    // TODO: look into verifiable encryption for notes
+    // TODO: look into timelock puzzle as a possibility
+    let vote_note_1 = {
+        assert_eq!(tx.func_calls.len(), 1);
+        let func_call = &tx.func_calls[0];
+        let call_data = func_call.call_data.as_any();
+        assert_eq!((&*call_data).type_id(), TypeId::of::<dao_contract::vote::validate::CallData>());
+        let call_data = call_data.downcast_ref::<dao_contract::vote::validate::CallData>().unwrap();
+
+        let header = &call_data.header;
+        let note: dao_contract::vote::wallet::Note =
+            header.enc_note.decrypt(&vote_keypair_1.secret).unwrap();
+        note
+    };
+    debug!(target: "demo", "User 1 voted!");
+    debug!(target: "demo", "  vote_option: {}", vote_note_1.vote.vote_option);
+    debug!(target: "demo", "  value: {}", vote_note_1.value);
+
     Ok(())
 }

+ 1 - 1
src/crypto/nullifier.rs

@@ -10,7 +10,7 @@ use crate::{
 };
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub struct Nullifier(pub(crate) pallas::Base);
+pub struct Nullifier(pub pallas::Base);
 
 impl Nullifier {
     pub fn new(secret: SecretKey, serial: pallas::Base) -> Self {