x 3 лет назад
Родитель
Сommit
4c57bc428f

+ 3 - 9
example/dao2/contract/dao/src/lib.rs

@@ -71,9 +71,8 @@ fn get_metadata(_cid: ContractId, ix: &[u8]) -> ContractResult {
             let data = &self_.data[1..];
             let data = &self_.data[1..];
             let params: DaoMintParams = deserialize(data)?;
             let params: DaoMintParams = deserialize(data)?;
 
 
-            let zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![
-                ("dao-mint".to_string(), vec![params.dao_bulla.0])
-            ];
+            let zk_public_values: Vec<(String, Vec<pallas::Base>)> =
+                vec![("dao-mint".to_string(), vec![params.dao_bulla.0])];
             let signature_public_keys: Vec<pallas::Point> = Vec::new();
             let signature_public_keys: Vec<pallas::Point> = Vec::new();
 
 
             let mut metadata = Vec::new();
             let mut metadata = Vec::new();
@@ -125,12 +124,7 @@ fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
             let db_info = db_lookup(cid, "info")?;
             let db_info = db_lookup(cid, "info")?;
             let db_roots = db_lookup(cid, "dao_roots")?;
             let db_roots = db_lookup(cid, "dao_roots")?;
             let node = MerkleNode::new(update.dao_bulla.0);
             let node = MerkleNode::new(update.dao_bulla.0);
-            merkle_add(
-                db_info,
-                db_roots,
-                &serialize(&"dao_tree".to_string()),
-                &node,
-            )?;
+            merkle_add(db_info, db_roots, &serialize(&"dao_tree".to_string()), &node)?;
         }
         }
         DaoFunction::Foo => {
         DaoFunction::Foo => {
             unimplemented!();
             unimplemented!();

+ 28 - 0
example/dao2/src/contract/dao/exec/mod.rs

@@ -0,0 +1,28 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use lazy_static::lazy_static;
+use pasta_curves::{group::ff::Field, pallas};
+use rand::rngs::OsRng;
+
+pub mod validate;
+pub mod wallet;
+
+lazy_static! {
+    pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng);
+}

+ 217 - 0
example/dao2/src/contract/dao/exec/validate.rs

@@ -0,0 +1,217 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::any::{Any, TypeId};
+
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{Curve, Group},
+    pallas,
+};
+
+use darkfi::{
+    crypto::{coin::Coin, keypair::PublicKey, types::DrkCircuitField},
+    Error as DarkFiError,
+};
+use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
+
+use crate::{
+    contract::{dao, dao::CONTRACT_ID, money},
+    util::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase},
+};
+
+type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum Error {
+    #[error("DarkFi error: {0}")]
+    DarkFiError(String),
+
+    #[error("InvalidNumberOfFuncCalls")]
+    InvalidNumberOfFuncCalls,
+
+    #[error("InvalidIndex")]
+    InvalidIndex,
+
+    #[error("InvalidCallData")]
+    InvalidCallData,
+
+    #[error("InvalidNumberOfOutputs")]
+    InvalidNumberOfOutputs,
+
+    #[error("InvalidOutput")]
+    InvalidOutput,
+
+    #[error("InvalidValueCommit")]
+    InvalidValueCommit,
+
+    #[error("InvalidVoteCommit")]
+    InvalidVoteCommit,
+}
+
+impl From<DarkFiError> for Error {
+    fn from(err: DarkFiError) -> Self {
+        Self::DarkFiError(err.to_string())
+    }
+}
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct CallData {
+    pub proposal: pallas::Base,
+    pub coin_0: pallas::Base,
+    pub coin_1: pallas::Base,
+    pub yes_votes_commit: pallas::Point,
+    pub all_votes_commit: pallas::Point,
+    pub input_value_commit: pallas::Point,
+}
+
+impl CallDataBase for CallData {
+    fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
+        let yes_votes_commit_coords = self.yes_votes_commit.to_affine().coordinates().unwrap();
+
+        let all_votes_commit_coords = self.all_votes_commit.to_affine().coordinates().unwrap();
+
+        let input_value_commit_coords = self.input_value_commit.to_affine().coordinates().unwrap();
+
+        vec![(
+            "dao-exec".to_string(),
+            vec![
+                self.proposal,
+                self.coin_0,
+                self.coin_1,
+                *yes_votes_commit_coords.x(),
+                *yes_votes_commit_coords.y(),
+                *all_votes_commit_coords.x(),
+                *all_votes_commit_coords.y(),
+                *input_value_commit_coords.x(),
+                *input_value_commit_coords.y(),
+                *super::FUNC_ID,
+                pallas::Base::from(0),
+                pallas::Base::from(0),
+            ],
+        )]
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn signature_public_keys(&self) -> Vec<PublicKey> {
+        vec![]
+    }
+
+    fn encode_bytes(
+        &self,
+        mut writer: &mut dyn std::io::Write,
+    ) -> std::result::Result<usize, std::io::Error> {
+        self.encode(&mut writer)
+    }
+}
+
+pub fn state_transition(
+    states: &StateRegistry,
+    func_call_index: usize,
+    parent_tx: &Transaction,
+) -> Result<Box<dyn UpdateBase + Send>> {
+    let func_call = &parent_tx.func_calls[func_call_index];
+    let call_data = func_call.call_data.as_any();
+
+    assert_eq!((*call_data).type_id(), TypeId::of::<CallData>());
+    let call_data = call_data.downcast_ref::<CallData>();
+
+    // This will be inside wasm so unwrap is fine.
+    let call_data = call_data.unwrap();
+
+    // Enforce tx has correct format:
+    // 1. There should only be 2 func_call's
+    if parent_tx.func_calls.len() != 2 {
+        return Err(Error::InvalidNumberOfFuncCalls)
+    }
+
+    // 2. func_call_index == 1
+    if func_call_index != 1 {
+        return Err(Error::InvalidIndex)
+    }
+
+    // 3. First item should be a Money::transfer() calldata
+    if parent_tx.func_calls[0].func_id != *money::transfer::FUNC_ID {
+        return Err(Error::InvalidCallData)
+    }
+
+    let money_transfer_call_data = parent_tx.func_calls[0].call_data.as_any();
+    let money_transfer_call_data =
+        money_transfer_call_data.downcast_ref::<money::transfer::validate::CallData>();
+    let money_transfer_call_data = money_transfer_call_data.unwrap();
+    assert_eq!(
+        money_transfer_call_data.type_id(),
+        TypeId::of::<money::transfer::validate::CallData>()
+    );
+
+    // 4. Money::transfer() has exactly 2 outputs
+    if money_transfer_call_data.outputs.len() != 2 {
+        return Err(Error::InvalidNumberOfOutputs)
+    }
+
+    // Checks:
+    // 1. Check both coins in Money::transfer() are equal to our coin_0, coin_1
+    if money_transfer_call_data.outputs[0].revealed.coin != Coin(call_data.coin_0) {
+        return Err(Error::InvalidOutput)
+    }
+    //if money_transfer_call_data.outputs[1].revealed.coin != Coin(call_data.coin_1) {
+    //    return Err(Error::InvalidOutput)
+    //}
+
+    // 2. sum of Money::transfer() calldata input_value_commits == our input value commit
+    let mut input_value_commits = pallas::Point::identity();
+    for input in &money_transfer_call_data.inputs {
+        input_value_commits += input.revealed.value_commit;
+    }
+    if input_value_commits != call_data.input_value_commit {
+        return Err(Error::InvalidValueCommit)
+    }
+
+    // 3. get the ProposalVote from DAO::State
+    let state =
+        states.lookup::<dao::State>(*CONTRACT_ID).expect("Return type is not of type State");
+    let proposal_votes = state.proposal_votes.get(&HashableBase(call_data.proposal)).unwrap();
+
+    // 4. check yes_votes_commit is the same as in ProposalVote
+    if proposal_votes.yes_votes_commit != call_data.yes_votes_commit {
+        return Err(Error::InvalidVoteCommit)
+    }
+    // 5. also check all_votes_commit
+    if proposal_votes.all_votes_commit != call_data.all_votes_commit {
+        return Err(Error::InvalidVoteCommit)
+    }
+
+    Ok(Box::new(Update { proposal: call_data.proposal }))
+}
+
+#[derive(Clone)]
+pub struct Update {
+    pub proposal: pallas::Base,
+}
+
+impl UpdateBase for Update {
+    fn apply(self: Box<Self>, states: &mut StateRegistry) {
+        let state = states
+            .lookup_mut::<dao::State>(*CONTRACT_ID)
+            .expect("Return type is not of type State");
+        state.proposal_votes.remove(&HashableBase(self.proposal)).unwrap();
+    }
+}

+ 214 - 0
example/dao2/src/contract/dao/exec/wallet.rs

@@ -0,0 +1,214 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use halo2_proofs::circuit::Value;
+use log::debug;
+use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas};
+use rand::rngs::OsRng;
+
+use darkfi::{
+    crypto::{
+        keypair::SecretKey,
+        util::{pedersen_commitment_u64, poseidon_hash},
+        Proof,
+    },
+    zk::vm::{Witness, ZkCircuit},
+};
+
+use crate::{
+    contract::dao::{
+        exec::validate::CallData, mint::wallet::DaoParams, propose::wallet::Proposal, CONTRACT_ID,
+    },
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
+};
+
+pub struct Builder {
+    pub proposal: Proposal,
+    pub dao: DaoParams,
+    pub yes_votes_value: u64,
+    pub all_votes_value: u64,
+    pub yes_votes_blind: pallas::Scalar,
+    pub all_votes_blind: pallas::Scalar,
+    pub user_serial: pallas::Base,
+    pub user_coin_blind: pallas::Base,
+    pub dao_serial: pallas::Base,
+    pub dao_coin_blind: pallas::Base,
+    pub input_value: u64,
+    pub input_value_blind: pallas::Scalar,
+    pub hook_dao_exec: pallas::Base,
+    pub signature_secret: SecretKey,
+}
+
+impl Builder {
+    pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
+        debug!(target: "dao_contract::exec::wallet::Builder", "build()");
+        let mut proofs = vec![];
+
+        let proposal_dest_coords = self.proposal.dest.0.to_affine().coordinates().unwrap();
+
+        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_quot = pallas::Base::from(self.dao.approval_ratio_quot);
+        let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
+
+        let dao_pubkey_coords = self.dao.public_key.0.to_affine().coordinates().unwrap();
+
+        let user_spend_hook = pallas::Base::from(0);
+        let user_data = pallas::Base::from(0);
+        let input_value = pallas::Base::from(self.input_value);
+        let change = input_value - proposal_amount;
+
+        let dao_bulla = poseidon_hash::<8>([
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_quot,
+            dao_approval_ratio_base,
+            self.dao.gov_token_id,
+            *dao_pubkey_coords.x(),
+            *dao_pubkey_coords.y(),
+            self.dao.bulla_blind,
+        ]);
+
+        let proposal_bulla = poseidon_hash::<8>([
+            *proposal_dest_coords.x(),
+            *proposal_dest_coords.y(),
+            proposal_amount,
+            self.proposal.serial,
+            self.proposal.token_id,
+            dao_bulla,
+            self.proposal.blind,
+            // @tmp-workaround
+            self.proposal.blind,
+        ]);
+
+        let coin_0 = poseidon_hash::<8>([
+            *proposal_dest_coords.x(),
+            *proposal_dest_coords.y(),
+            proposal_amount,
+            self.proposal.token_id,
+            self.proposal.serial,
+            user_spend_hook,
+            user_data,
+            self.proposal.blind,
+        ]);
+
+        let coin_1 = poseidon_hash::<8>([
+            *dao_pubkey_coords.x(),
+            *dao_pubkey_coords.y(),
+            change,
+            self.proposal.token_id,
+            self.dao_serial,
+            self.hook_dao_exec,
+            dao_bulla,
+            self.dao_coin_blind,
+        ]);
+
+        let yes_votes_commit = pedersen_commitment_u64(self.yes_votes_value, self.yes_votes_blind);
+        let yes_votes_commit_coords = yes_votes_commit.to_affine().coordinates().unwrap();
+
+        let all_votes_commit = pedersen_commitment_u64(self.all_votes_value, self.all_votes_blind);
+        let all_votes_commit_coords = all_votes_commit.to_affine().coordinates().unwrap();
+
+        let input_value_commit = pedersen_commitment_u64(self.input_value, self.input_value_blind);
+        let input_value_commit_coords = input_value_commit.to_affine().coordinates().unwrap();
+
+        let zk_info = zk_bins.lookup(&"dao-exec".to_string()).unwrap();
+        let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
+            info
+        } else {
+            panic!("Not binary info")
+        };
+
+        let zk_bin = zk_info.bincode.clone();
+
+        let prover_witnesses = vec![
+            //
+            // proposal params
+            Witness::Base(Value::known(*proposal_dest_coords.x())),
+            Witness::Base(Value::known(*proposal_dest_coords.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_quot)),
+            Witness::Base(Value::known(dao_approval_ratio_base)),
+            Witness::Base(Value::known(self.dao.gov_token_id)),
+            Witness::Base(Value::known(*dao_pubkey_coords.x())),
+            Witness::Base(Value::known(*dao_pubkey_coords.y())),
+            Witness::Base(Value::known(self.dao.bulla_blind)),
+            // votes
+            Witness::Base(Value::known(pallas::Base::from(self.yes_votes_value))),
+            Witness::Base(Value::known(pallas::Base::from(self.all_votes_value))),
+            Witness::Scalar(Value::known(self.yes_votes_blind)),
+            Witness::Scalar(Value::known(self.all_votes_blind)),
+            // outputs + inputs
+            Witness::Base(Value::known(self.user_serial)),
+            Witness::Base(Value::known(self.user_coin_blind)),
+            Witness::Base(Value::known(self.dao_serial)),
+            Witness::Base(Value::known(self.dao_coin_blind)),
+            Witness::Base(Value::known(input_value)),
+            Witness::Scalar(Value::known(self.input_value_blind)),
+            // misc
+            Witness::Base(Value::known(self.hook_dao_exec)),
+            Witness::Base(Value::known(user_spend_hook)),
+            Witness::Base(Value::known(user_data)),
+        ];
+
+        let public_inputs = vec![
+            proposal_bulla,
+            coin_0,
+            coin_1,
+            *yes_votes_commit_coords.x(),
+            *yes_votes_commit_coords.y(),
+            *all_votes_commit_coords.x(),
+            *all_votes_commit_coords.y(),
+            *input_value_commit_coords.x(),
+            *input_value_commit_coords.y(),
+            self.hook_dao_exec,
+            user_spend_hook,
+            user_data,
+        ];
+
+        let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
+        let proving_key = &zk_info.proving_key;
+        let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
+            .expect("DAO::exec() proving error!)");
+        proofs.push(input_proof);
+
+        let call_data = CallData {
+            proposal: proposal_bulla,
+            coin_0,
+            coin_1,
+            yes_votes_commit,
+            all_votes_commit,
+            input_value_commit,
+        };
+
+        FuncCall {
+            contract_id: *CONTRACT_ID,
+            func_id: *super::FUNC_ID,
+            call_data: Box::new(call_data),
+            proofs,
+        }
+    }
+}

+ 62 - 0
example/dao2/src/contract/dao/mint/mod.rs

@@ -0,0 +1,62 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use lazy_static::lazy_static;
+use pasta_curves::{group::ff::Field, pallas};
+use rand::rngs::OsRng;
+
+pub mod validate;
+/// This is an anonymous contract function that mutates the internal DAO state.
+///
+/// Corresponds to `mint(proposer_limit, quorum, approval_ratio, dao_pubkey, dao_blind)`
+///
+/// The prover creates a `Builder`, which then constructs the `Tx` that the verifier can
+/// check using `state_transition()`.
+///
+/// # Arguments
+///
+/// * `proposer_limit` - Number of governance tokens that holder must possess in order to
+///   propose a new vote.
+/// * `quorum` - Number of minimum votes that must be met for a proposal to pass.
+/// * `approval_ratio` - Ratio of winning to total votes for a proposal to pass.
+/// * `dao_pubkey` - Public key of the DAO for permissioned access. This can also be
+///   shared publicly if you want a full decentralized DAO.
+/// * `dao_blind` - Blinding factor for the DAO bulla.
+///
+/// # Example
+///
+/// ```rust
+/// let dao_proposer_limit = 110;
+/// let dao_quorum = 110;
+/// let dao_approval_ratio = 2;
+///
+/// let builder = dao_contract::Mint::Builder(
+///     dao_proposer_limit,
+///     dao_quorum,
+///     dao_approval_ratio,
+///     gov_token_id,
+///     dao_pubkey,
+///     dao_blind
+/// );
+/// let tx = builder.build();
+/// ```
+pub mod wallet;
+
+lazy_static! {
+    pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng);
+}

+ 89 - 0
example/dao2/src/contract/dao/mint/validate.rs

@@ -0,0 +1,89 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::any::{Any, TypeId};
+
+use darkfi::crypto::{keypair::PublicKey, types::DrkCircuitField};
+use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
+
+use crate::{
+    contract::dao::{DaoBulla, State, CONTRACT_ID},
+    util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
+};
+
+pub fn state_transition(
+    _states: &StateRegistry,
+    func_call_index: usize,
+    parent_tx: &Transaction,
+) -> Result<Box<dyn UpdateBase + Send>> {
+    let func_call = &parent_tx.func_calls[func_call_index];
+    let call_data = func_call.call_data.as_any();
+
+    assert_eq!((*call_data).type_id(), TypeId::of::<CallData>());
+    let call_data = call_data.downcast_ref::<CallData>();
+
+    // This will be inside wasm so unwrap is fine.
+    let call_data = call_data.unwrap();
+
+    Ok(Box::new(Update { dao_bulla: call_data.dao_bulla.clone() }))
+}
+
+#[derive(Clone)]
+pub struct Update {
+    pub dao_bulla: DaoBulla,
+}
+
+impl UpdateBase for Update {
+    fn apply(self: Box<Self>, states: &mut StateRegistry) {
+        // Lookup dao_contract state from registry
+        let state = states.lookup_mut::<State>(*CONTRACT_ID).unwrap();
+        // Add dao_bulla to state.dao_bullas
+        state.add_dao_bulla(self.dao_bulla);
+    }
+}
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum Error {}
+
+type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct CallData {
+    pub dao_bulla: DaoBulla,
+}
+
+impl CallDataBase for CallData {
+    fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
+        vec![("dao-mint".to_string(), vec![self.dao_bulla.0])]
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn signature_public_keys(&self) -> Vec<PublicKey> {
+        vec![]
+    }
+
+    fn encode_bytes(
+        &self,
+        mut writer: &mut dyn std::io::Write,
+    ) -> std::result::Result<usize, std::io::Error> {
+        self.encode(&mut writer)
+    }
+}

+ 120 - 0
example/dao2/src/contract/dao/mint/wallet.rs

@@ -0,0 +1,120 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use halo2_proofs::circuit::Value;
+use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas};
+use rand::rngs::OsRng;
+
+use darkfi::{
+    crypto::{
+        keypair::{PublicKey, SecretKey},
+        util::poseidon_hash,
+        Proof,
+    },
+    zk::vm::{Witness, ZkCircuit},
+};
+
+use dao_contract::{DaoBulla, DaoFunction, DaoMintParams};
+
+use crate::{
+    contract::dao::CONTRACT_ID,
+    util::{ZkContractInfo, ZkContractTable},
+};
+
+#[derive(Clone)]
+pub struct DaoParams {
+    pub proposer_limit: u64,
+    pub quorum: u64,
+    pub approval_ratio_quot: u64,
+    pub approval_ratio_base: u64,
+    pub gov_token_id: pallas::Base,
+    pub public_key: PublicKey,
+    pub bulla_blind: pallas::Base,
+}
+
+pub struct Builder {
+    pub dao_proposer_limit: u64,
+    pub dao_quorum: u64,
+    pub dao_approval_ratio_quot: u64,
+    pub dao_approval_ratio_base: u64,
+    pub gov_token_id: pallas::Base,
+    pub dao_pubkey: PublicKey,
+    pub dao_bulla_blind: pallas::Base,
+    pub signature_secret: SecretKey,
+}
+
+impl Builder {
+    /// Consumes self, and produces the function call
+    pub fn build(self, zk_bins: &ZkContractTable) -> (DaoMintParams, Vec<Proof>) {
+        // Dao bulla
+        let dao_proposer_limit = pallas::Base::from(self.dao_proposer_limit);
+        let dao_quorum = pallas::Base::from(self.dao_quorum);
+        let dao_approval_ratio_quot = pallas::Base::from(self.dao_approval_ratio_quot);
+        let dao_approval_ratio_base = pallas::Base::from(self.dao_approval_ratio_base);
+
+        let dao_pubkey_coords = self.dao_pubkey.0.to_affine().coordinates().unwrap();
+
+        let dao_bulla = poseidon_hash::<8>([
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_quot,
+            dao_approval_ratio_base,
+            self.gov_token_id,
+            *dao_pubkey_coords.x(),
+            *dao_pubkey_coords.y(),
+            self.dao_bulla_blind,
+        ]);
+        let dao_bulla = DaoBulla(dao_bulla);
+
+        // Now create the mint proof
+        let zk_info = zk_bins.lookup(&"dao-mint".to_string()).unwrap();
+        let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
+            info
+        } else {
+            panic!("Not binary info")
+        };
+        let zk_bin = zk_info.bincode.clone();
+        let prover_witnesses = vec![
+            Witness::Base(Value::known(dao_proposer_limit)),
+            Witness::Base(Value::known(dao_quorum)),
+            Witness::Base(Value::known(dao_approval_ratio_quot)),
+            Witness::Base(Value::known(dao_approval_ratio_base)),
+            Witness::Base(Value::known(self.gov_token_id)),
+            Witness::Base(Value::known(*dao_pubkey_coords.x())),
+            Witness::Base(Value::known(*dao_pubkey_coords.y())),
+            Witness::Base(Value::known(self.dao_bulla_blind)),
+        ];
+        let public_inputs = vec![dao_bulla.0];
+        let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
+
+        let proving_key = &zk_info.proving_key;
+        let mint_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
+            .expect("DAO::mint() proving error!");
+
+        /*
+        let call_data = CallData { dao_bulla };
+        FuncCall {
+            contract_id: *CONTRACT_ID,
+            func_id: *super::FUNC_ID,
+            call_data: Box::new(call_data),
+            proofs: vec![mint_proof],
+        }
+        */
+        (DaoMintParams { dao_bulla }, vec![mint_proof])
+    }
+}

+ 38 - 0
example/dao2/src/contract/dao/mod.rs

@@ -0,0 +1,38 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use lazy_static::lazy_static;
+use pasta_curves::{group::ff::Field, pallas};
+use rand::rngs::OsRng;
+
+// mint()
+pub mod mint;
+// propose()
+pub mod propose;
+// vote{}
+pub mod vote;
+// exec{}
+pub mod exec;
+
+pub mod state;
+
+pub use state::{DaoBulla, State};
+
+lazy_static! {
+    pub static ref CONTRACT_ID: pallas::Base = pallas::Base::random(&mut OsRng);
+}

+ 28 - 0
example/dao2/src/contract/dao/propose/mod.rs

@@ -0,0 +1,28 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use lazy_static::lazy_static;
+use pasta_curves::{group::ff::Field, pallas};
+use rand::rngs::OsRng;
+
+pub mod validate;
+pub mod wallet;
+
+lazy_static! {
+    pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng);
+}

+ 189 - 0
example/dao2/src/contract/dao/propose/validate.rs

@@ -0,0 +1,189 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::any::{Any, TypeId};
+
+use darkfi_sdk::crypto::MerkleNode;
+use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
+use log::error;
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{Curve, Group},
+    pallas,
+};
+
+use darkfi::{
+    crypto::{keypair::PublicKey, types::DrkCircuitField},
+    Error as DarkFiError,
+};
+
+use crate::{
+    contract::{dao, dao::State as DaoState, money, money::state::State as MoneyState},
+    note::EncryptedNote2,
+    util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
+};
+
+// used for debugging
+// const TARGET: &str = "dao_contract::propose::validate::state_transition()";
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum Error {
+    #[error("Invalid input merkle root")]
+    InvalidInputMerkleRoot,
+
+    #[error("Invalid DAO merkle root")]
+    InvalidDaoMerkleRoot,
+
+    #[error("DarkFi error: {0}")]
+    DarkFiError(String),
+}
+type Result<T> = std::result::Result<T, Error>;
+
+impl From<DarkFiError> for Error {
+    fn from(err: DarkFiError) -> Self {
+        Self::DarkFiError(err.to_string())
+    }
+}
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct CallData {
+    pub header: Header,
+    pub inputs: Vec<Input>,
+}
+
+impl CallDataBase for CallData {
+    fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
+        let mut zk_publics = Vec::new();
+        let mut total_funds_commit = pallas::Point::identity();
+
+        assert!(!self.inputs.is_empty(), "inputs length cannot be zero");
+        for input in &self.inputs {
+            total_funds_commit += input.value_commit;
+            let value_coords = input.value_commit.to_affine().coordinates().unwrap();
+
+            let sigpub_coords = input.signature_public.0.to_affine().coordinates().unwrap();
+
+            zk_publics.push((
+                "dao-propose-burn".to_string(),
+                vec![
+                    *value_coords.x(),
+                    *value_coords.y(),
+                    self.header.token_commit,
+                    input.merkle_root.inner(),
+                    *sigpub_coords.x(),
+                    *sigpub_coords.y(),
+                ],
+            ));
+        }
+
+        let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap();
+        zk_publics.push((
+            "dao-propose-main".to_string(),
+            vec![
+                self.header.token_commit,
+                self.header.dao_merkle_root.inner(),
+                self.header.proposal_bulla,
+                *total_funds_coords.x(),
+                *total_funds_coords.y(),
+            ],
+        ));
+
+        zk_publics
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn signature_public_keys(&self) -> Vec<PublicKey> {
+        let mut signature_public_keys = vec![];
+        for input in self.inputs.clone() {
+            signature_public_keys.push(input.signature_public);
+        }
+        signature_public_keys
+    }
+
+    fn encode_bytes(
+        &self,
+        mut writer: &mut dyn std::io::Write,
+    ) -> std::result::Result<usize, std::io::Error> {
+        self.encode(&mut writer)
+    }
+}
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct Header {
+    pub dao_merkle_root: MerkleNode,
+    pub token_commit: pallas::Base,
+    pub proposal_bulla: pallas::Base,
+    pub enc_note: EncryptedNote2,
+}
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct Input {
+    pub value_commit: pallas::Point,
+    pub merkle_root: MerkleNode,
+    pub signature_public: PublicKey,
+}
+
+pub fn state_transition(
+    states: &StateRegistry,
+    func_call_index: usize,
+    parent_tx: &Transaction,
+) -> Result<Box<dyn UpdateBase + Send>> {
+    let func_call = &parent_tx.func_calls[func_call_index];
+    let call_data = func_call.call_data.as_any();
+
+    assert_eq!((*call_data).type_id(), TypeId::of::<CallData>());
+    let call_data = call_data.downcast_ref::<CallData>();
+
+    // This will be inside wasm so unwrap is fine.
+    let call_data = call_data.unwrap();
+
+    // Check the merkle roots for the input coins are valid
+    for input in &call_data.inputs {
+        let money_state = states.lookup::<MoneyState>(*money::CONTRACT_ID).unwrap();
+        if !money_state.is_valid_merkle(&input.merkle_root) {
+            return Err(Error::InvalidInputMerkleRoot)
+        }
+    }
+
+    let state = states.lookup::<DaoState>(*dao::CONTRACT_ID).unwrap();
+
+    // Is the DAO bulla generated in the ZK proof valid
+    if !state.is_valid_dao_merkle(&call_data.header.dao_merkle_root) {
+        return Err(Error::InvalidDaoMerkleRoot)
+    }
+
+    // TODO: look at gov tokens avoid using already spent ones
+    // Need to spend original coin and generate 2 nullifiers?
+
+    Ok(Box::new(Update { proposal_bulla: call_data.header.proposal_bulla }))
+}
+
+#[derive(Clone)]
+pub struct Update {
+    pub proposal_bulla: pallas::Base,
+}
+
+impl UpdateBase for Update {
+    fn apply(self: Box<Self>, states: &mut StateRegistry) {
+        let state = states.lookup_mut::<DaoState>(*dao::CONTRACT_ID).unwrap();
+        state.add_proposal_bulla(self.proposal_bulla);
+    }
+}

+ 290 - 0
example/dao2/src/contract/dao/propose/wallet.rs

@@ -0,0 +1,290 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use darkfi_sdk::crypto::MerkleNode;
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+use halo2_proofs::circuit::Value;
+use incrementalmerkletree::Hashable;
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{ff::Field, Curve},
+    pallas,
+};
+use rand::rngs::OsRng;
+
+use darkfi::{
+    crypto::{
+        keypair::{PublicKey, SecretKey},
+        util::{pedersen_commitment_u64, poseidon_hash},
+        Proof,
+    },
+    zk::vm::{Witness, ZkCircuit},
+};
+
+use crate::{
+    contract::{
+        dao::{
+            mint::wallet::DaoParams,
+            propose::validate::{CallData, Header, Input},
+            CONTRACT_ID,
+        },
+        money,
+    },
+    note,
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
+};
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct Note {
+    pub proposal: Proposal,
+}
+
+pub struct BuilderInput {
+    pub secret: SecretKey,
+    pub note: money::transfer::wallet::Note,
+    pub leaf_position: incrementalmerkletree::Position,
+    pub merkle_path: Vec<MerkleNode>,
+    pub signature_secret: SecretKey,
+}
+
+#[derive(SerialEncodable, SerialDecodable, Clone)]
+pub struct Proposal {
+    pub dest: PublicKey,
+    pub amount: u64,
+    pub serial: pallas::Base,
+    pub token_id: pallas::Base,
+    pub blind: pallas::Base,
+}
+
+pub struct Builder {
+    pub inputs: Vec<BuilderInput>,
+    pub proposal: Proposal,
+    pub dao: DaoParams,
+    pub dao_leaf_position: incrementalmerkletree::Position,
+    pub dao_merkle_path: Vec<MerkleNode>,
+    pub dao_merkle_root: MerkleNode,
+}
+
+impl Builder {
+    pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
+        let mut proofs = vec![];
+
+        let gov_token_blind = pallas::Base::random(&mut OsRng);
+
+        let mut inputs = vec![];
+        let mut total_funds = 0;
+        let mut total_funds_blinds = pallas::Scalar::from(0);
+
+        for input in self.inputs {
+            let funds_blind = pallas::Scalar::random(&mut OsRng);
+            total_funds += input.note.value;
+            total_funds_blinds += funds_blind;
+
+            let signature_public = PublicKey::from_secret(input.signature_secret);
+
+            let zk_info = zk_bins.lookup(&"dao-propose-burn".to_string()).unwrap();
+            let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
+                info
+            } else {
+                panic!("Not binary info")
+            };
+            let zk_bin = zk_info.bincode.clone();
+
+            // Note from the previous output
+            let note = input.note;
+            let leaf_pos: u64 = input.leaf_position.into();
+
+            let prover_witnesses = vec![
+                Witness::Base(Value::known(input.secret.0)),
+                Witness::Base(Value::known(note.serial)),
+                Witness::Base(Value::known(pallas::Base::from(0))),
+                Witness::Base(Value::known(pallas::Base::from(0))),
+                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(funds_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())),
+                Witness::Base(Value::known(input.signature_secret.0)),
+            ];
+
+            let public_key = PublicKey::from_secret(input.secret);
+            let coords = public_key.0.to_affine().coordinates().unwrap();
+
+            let coin = poseidon_hash::<8>([
+                *coords.x(),
+                *coords.y(),
+                pallas::Base::from(note.value),
+                note.token_id,
+                note.serial,
+                pallas::Base::from(0),
+                pallas::Base::from(0),
+                note.coin_blind,
+            ]);
+
+            let merkle_root = {
+                let position: u64 = input.leaf_position.into();
+                let mut current = MerkleNode::from(coin);
+                for (level, sibling) in input.merkle_path.iter().enumerate() {
+                    let level = level as u8;
+                    current = if position & (1 << level) == 0 {
+                        MerkleNode::combine(level.into(), &current, sibling)
+                    } else {
+                        MerkleNode::combine(level.into(), sibling, &current)
+                    };
+                }
+                current
+            };
+
+            let token_commit = poseidon_hash::<2>([note.token_id, gov_token_blind]);
+            assert_eq!(self.dao.gov_token_id, note.token_id);
+
+            let value_commit = pedersen_commitment_u64(note.value, funds_blind);
+            let value_coords = value_commit.to_affine().coordinates().unwrap();
+
+            let sigpub_coords = signature_public.0.to_affine().coordinates().unwrap();
+
+            let public_inputs = vec![
+                *value_coords.x(),
+                *value_coords.y(),
+                token_commit,
+                merkle_root.inner(),
+                *sigpub_coords.x(),
+                *sigpub_coords.y(),
+            ];
+            let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
+
+            let proving_key = &zk_info.proving_key;
+            let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
+                .expect("DAO::propose() proving error!");
+            proofs.push(input_proof);
+
+            let input = Input { value_commit, merkle_root, signature_public };
+            inputs.push(input);
+        }
+
+        let total_funds_commit = pedersen_commitment_u64(total_funds, total_funds_blinds);
+        let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap();
+        let total_funds = pallas::Base::from(total_funds);
+
+        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_quot = pallas::Base::from(self.dao.approval_ratio_quot);
+        let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
+
+        let dao_pubkey_coords = self.dao.public_key.0.to_affine().coordinates().unwrap();
+
+        let dao_bulla = poseidon_hash::<8>([
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_quot,
+            dao_approval_ratio_base,
+            self.dao.gov_token_id,
+            *dao_pubkey_coords.x(),
+            *dao_pubkey_coords.y(),
+            self.dao.bulla_blind,
+        ]);
+
+        let dao_leaf_position: u64 = self.dao_leaf_position.into();
+
+        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 zk_info = zk_bins.lookup(&"dao-propose-main".to_string()).unwrap();
+        let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
+            info
+        } else {
+            panic!("Not binary info")
+        };
+        let zk_bin = zk_info.bincode.clone();
+        let prover_witnesses = vec![
+            // Proposers total number of gov tokens
+            Witness::Base(Value::known(total_funds)),
+            Witness::Scalar(Value::known(total_funds_blinds)),
+            // Used for blinding exported gov token ID
+            Witness::Base(Value::known(gov_token_blind)),
+            // 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_quot)),
+            Witness::Base(Value::known(dao_approval_ratio_base)),
+            Witness::Base(Value::known(self.dao.gov_token_id)),
+            Witness::Base(Value::known(*dao_pubkey_coords.x())),
+            Witness::Base(Value::known(*dao_pubkey_coords.y())),
+            Witness::Base(Value::known(self.dao.bulla_blind)),
+            Witness::Uint32(Value::known(dao_leaf_position.try_into().unwrap())),
+            Witness::MerklePath(Value::known(self.dao_merkle_path.try_into().unwrap())),
+        ];
+        let public_inputs = vec![
+            token_commit,
+            self.dao_merkle_root.inner(),
+            proposal_bulla,
+            *total_funds_coords.x(),
+            *total_funds_coords.y(),
+        ];
+        let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
+
+        let proving_key = &zk_info.proving_key;
+        let main_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
+            .expect("DAO::propose() proving error!");
+        proofs.push(main_proof);
+
+        let note = Note { proposal: self.proposal };
+        let enc_note = note::encrypt(&note, &self.dao.public_key).unwrap();
+        let header = Header {
+            dao_merkle_root: self.dao_merkle_root,
+            proposal_bulla,
+            token_commit,
+            enc_note,
+        };
+
+        let call_data = CallData { header, inputs };
+
+        FuncCall {
+            contract_id: *CONTRACT_ID,
+            func_id: *super::FUNC_ID,
+            call_data: Box::new(call_data),
+            proofs,
+        }
+    }
+}

+ 114 - 0
example/dao2/src/contract/dao/state.rs

@@ -0,0 +1,114 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::{any::Any, collections::HashMap};
+
+use darkfi_sdk::crypto::{constants::MERKLE_DEPTH, MerkleNode, Nullifier};
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
+use pasta_curves::{group::Group, pallas};
+
+use crate::util::HashableBase;
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct DaoBulla(pub pallas::Base);
+
+type MerkleTree = BridgeTree<MerkleNode, { MERKLE_DEPTH }>;
+
+pub struct ProposalVotes {
+    // TODO: might be more logical to have 'yes_votes_commit' and 'no_votes_commit'
+    /// Weighted vote commit
+    pub yes_votes_commit: pallas::Point,
+    /// All value staked in the vote
+    pub all_votes_commit: 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.
+pub struct State {
+    dao_bullas: Vec<DaoBulla>,
+    pub dao_tree: MerkleTree,
+    pub dao_roots: Vec<MerkleNode>,
+
+    //proposal_bullas: Vec<pallas::Base>,
+    pub proposal_tree: MerkleTree,
+    pub proposal_roots: Vec<MerkleNode>,
+    pub proposal_votes: HashMap<HashableBase, ProposalVotes>,
+}
+
+impl State {
+    pub fn new() -> Box<dyn Any + Send> {
+        Box::new(Self {
+            dao_bullas: Vec::new(),
+            dao_tree: MerkleTree::new(100),
+            dao_roots: Vec::new(),
+            //proposal_bullas: Vec::new(),
+            proposal_tree: MerkleTree::new(100),
+            proposal_roots: Vec::new(),
+            proposal_votes: HashMap::new(),
+        })
+    }
+
+    pub fn add_dao_bulla(&mut self, bulla: DaoBulla) {
+        let node = MerkleNode::from(bulla.0);
+        self.dao_bullas.push(bulla);
+        self.dao_tree.append(&node);
+        self.dao_roots.push(self.dao_tree.root(0).unwrap());
+    }
+
+    pub fn add_proposal_bulla(&mut self, bulla: pallas::Base) {
+        let node = MerkleNode::from(bulla);
+        //self.proposal_bullas.push(bulla);
+        self.proposal_tree.append(&node);
+        self.proposal_roots.push(self.proposal_tree.root(0).unwrap());
+        self.proposal_votes.insert(
+            HashableBase(bulla),
+            ProposalVotes {
+                yes_votes_commit: pallas::Point::identity(),
+                all_votes_commit: 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 is_valid_dao_merkle(&self, root: &MerkleNode) -> bool {
+        self.dao_roots.iter().any(|m| m == root)
+    }
+
+    // TODO: This never gets called.
+    pub fn _is_valid_proposal_merkle(&self, root: &MerkleNode) -> bool {
+        self.proposal_roots.iter().any(|m| m == root)
+    }
+}

+ 28 - 0
example/dao2/src/contract/dao/vote/mod.rs

@@ -0,0 +1,28 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use lazy_static::lazy_static;
+use pasta_curves::{group::ff::Field, pallas};
+use rand::rngs::OsRng;
+
+pub mod validate;
+pub mod wallet;
+
+lazy_static! {
+    pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng);
+}

+ 222 - 0
example/dao2/src/contract/dao/vote/validate.rs

@@ -0,0 +1,222 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::any::{Any, TypeId};
+
+use darkfi_sdk::crypto::{MerkleNode, Nullifier};
+use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
+use log::error;
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{Curve, Group},
+    pallas,
+};
+
+use darkfi::{
+    crypto::{keypair::PublicKey, types::DrkCircuitField},
+    Error as DarkFiError,
+};
+
+use crate::{
+    contract::{dao, dao::State as DaoState, money, money::state::State as MoneyState},
+    note::EncryptedNote2,
+    util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
+};
+
+#[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,
+
+    #[error("DarkFi error: {0}")]
+    DarkFiError(String),
+}
+type Result<T> = std::result::Result<T, Error>;
+
+impl From<DarkFiError> for Error {
+    fn from(err: DarkFiError) -> Self {
+        Self::DarkFiError(err.to_string())
+    }
+}
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct CallData {
+    pub header: Header,
+    pub inputs: Vec<Input>,
+}
+
+impl CallDataBase for CallData {
+    fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
+        let mut zk_publics = Vec::new();
+        let mut all_votes_commit = pallas::Point::identity();
+
+        assert!(!self.inputs.is_empty(), "inputs length cannot be zero");
+        for input in &self.inputs {
+            all_votes_commit += input.vote_commit;
+            let value_coords = input.vote_commit.to_affine().coordinates().unwrap();
+
+            let sigpub_coords = input.signature_public.0.to_affine().coordinates().unwrap();
+
+            zk_publics.push((
+                "dao-vote-burn".to_string(),
+                vec![
+                    input.nullifier.inner(),
+                    *value_coords.x(),
+                    *value_coords.y(),
+                    self.header.token_commit,
+                    input.merkle_root.inner(),
+                    *sigpub_coords.x(),
+                    *sigpub_coords.y(),
+                ],
+            ));
+        }
+
+        let yes_vote_commit_coords = self.header.yes_vote_commit.to_affine().coordinates().unwrap();
+
+        let vote_commit_coords = all_votes_commit.to_affine().coordinates().unwrap();
+
+        zk_publics.push((
+            "dao-vote-main".to_string(),
+            vec![
+                self.header.token_commit,
+                self.header.proposal_bulla,
+                *yes_vote_commit_coords.x(),
+                *yes_vote_commit_coords.y(),
+                *vote_commit_coords.x(),
+                *vote_commit_coords.y(),
+            ],
+        ));
+
+        zk_publics
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn signature_public_keys(&self) -> Vec<PublicKey> {
+        let mut signature_public_keys = vec![];
+        for input in self.inputs.clone() {
+            signature_public_keys.push(input.signature_public);
+        }
+        signature_public_keys
+    }
+
+    fn encode_bytes(
+        &self,
+        mut writer: &mut dyn std::io::Write,
+    ) -> std::result::Result<usize, std::io::Error> {
+        self.encode(&mut writer)
+    }
+}
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct Header {
+    pub token_commit: pallas::Base,
+    pub proposal_bulla: pallas::Base,
+    pub yes_vote_commit: pallas::Point,
+    pub enc_note: EncryptedNote2,
+}
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct Input {
+    pub nullifier: Nullifier,
+    pub vote_commit: pallas::Point,
+    pub merkle_root: MerkleNode,
+    pub signature_public: PublicKey,
+}
+
+pub fn state_transition(
+    states: &StateRegistry,
+    func_call_index: usize,
+    parent_tx: &Transaction,
+) -> Result<Box<dyn UpdateBase + Send>> {
+    let func_call = &parent_tx.func_calls[func_call_index];
+    let call_data = func_call.call_data.as_any();
+
+    assert_eq!((*call_data).type_id(), TypeId::of::<CallData>());
+    let call_data = call_data.downcast_ref::<CallData>();
+
+    // This will be inside wasm so unwrap is fine.
+    let call_data = call_data.unwrap();
+
+    let dao_state = states.lookup::<DaoState>(*dao::CONTRACT_ID).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 all_vote_commit = pallas::Point::identity();
+    for input in &call_data.inputs {
+        let money_state = states.lookup::<MoneyState>(*money::CONTRACT_ID).unwrap();
+        if !money_state.is_valid_merkle(&input.merkle_root) {
+            return Err(Error::InvalidInputMerkleRoot)
+        }
+
+        if money_state.nullifier_exists(&input.nullifier) {
+            return Err(Error::SpentCoin)
+        }
+
+        if votes_info.nullifier_exists(&input.nullifier) {
+            return Err(Error::DoubleVote)
+        }
+
+        all_vote_commit += input.vote_commit;
+
+        vote_nulls.push(input.nullifier);
+    }
+
+    Ok(Box::new(Update {
+        proposal_bulla: call_data.header.proposal_bulla,
+        vote_nulls,
+        yes_vote_commit: call_data.header.yes_vote_commit,
+        all_vote_commit,
+    }))
+}
+
+#[derive(Clone)]
+pub struct Update {
+    proposal_bulla: pallas::Base,
+    vote_nulls: Vec<Nullifier>,
+    pub yes_vote_commit: pallas::Point,
+    pub all_vote_commit: pallas::Point,
+}
+
+impl UpdateBase for Update {
+    fn apply(mut self: Box<Self>, states: &mut StateRegistry) {
+        let state = states.lookup_mut::<DaoState>(*dao::CONTRACT_ID).unwrap();
+        let votes_info = state.lookup_proposal_votes_mut(self.proposal_bulla).unwrap();
+        votes_info.yes_votes_commit += self.yes_vote_commit;
+        votes_info.all_votes_commit += self.all_vote_commit;
+        votes_info.vote_nulls.append(&mut self.vote_nulls);
+    }
+}

+ 309 - 0
example/dao2/src/contract/dao/vote/wallet.rs

@@ -0,0 +1,309 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use darkfi_sdk::crypto::{MerkleNode, Nullifier};
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+use halo2_proofs::circuit::Value;
+use incrementalmerkletree::Hashable;
+use log::debug;
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{ff::Field, Curve},
+    pallas,
+};
+use rand::rngs::OsRng;
+
+use darkfi::{
+    crypto::{
+        keypair::{Keypair, PublicKey, SecretKey},
+        util::{pedersen_commitment_u64, poseidon_hash},
+        Proof,
+    },
+    zk::vm::{Witness, ZkCircuit},
+};
+
+use crate::{
+    contract::{
+        dao::{
+            mint::wallet::DaoParams,
+            propose::wallet::Proposal,
+            vote::validate::{CallData, Header, Input},
+            CONTRACT_ID,
+        },
+        money,
+    },
+    note,
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
+};
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct Note {
+    pub vote: Vote,
+    pub vote_value: u64,
+    pub vote_value_blind: pallas::Scalar,
+}
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct Vote {
+    pub vote_option: bool,
+    pub vote_option_blind: pallas::Scalar,
+}
+
+pub struct BuilderInput {
+    pub secret: SecretKey,
+    pub note: money::transfer::wallet::Note,
+    pub leaf_position: incrementalmerkletree::Position,
+    pub merkle_path: Vec<MerkleNode>,
+    pub signature_secret: SecretKey,
+}
+
+// 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 {
+    pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
+        debug!(target: "dao_contract::vote::wallet::Builder", "build()");
+        let mut proofs = vec![];
+
+        let gov_token_blind = pallas::Base::random(&mut OsRng);
+
+        let mut inputs = vec![];
+        let mut vote_value = 0;
+        let mut vote_value_blind = pallas::Scalar::from(0);
+
+        for input in self.inputs {
+            let value_blind = pallas::Scalar::random(&mut OsRng);
+
+            vote_value += input.note.value;
+            vote_value_blind += value_blind;
+
+            let signature_public = PublicKey::from_secret(input.signature_secret);
+
+            let zk_info = zk_bins.lookup(&"dao-vote-burn".to_string()).unwrap();
+
+            let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
+                info
+            } else {
+                panic!("Not binary info")
+            };
+            let zk_bin = zk_info.bincode.clone();
+
+            // Note from the previous output
+            let note = input.note;
+            let leaf_pos: u64 = input.leaf_position.into();
+
+            let prover_witnesses = vec![
+                Witness::Base(Value::known(input.secret.0)),
+                Witness::Base(Value::known(note.serial)),
+                Witness::Base(Value::known(pallas::Base::from(0))),
+                Witness::Base(Value::known(pallas::Base::from(0))),
+                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(vote_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())),
+                Witness::Base(Value::known(input.signature_secret.0)),
+            ];
+
+            let public_key = PublicKey::from_secret(input.secret);
+            let coords = public_key.0.to_affine().coordinates().unwrap();
+
+            let coin = poseidon_hash::<8>([
+                *coords.x(),
+                *coords.y(),
+                pallas::Base::from(note.value),
+                note.token_id,
+                note.serial,
+                pallas::Base::from(0),
+                pallas::Base::from(0),
+                note.coin_blind,
+            ]);
+
+            let merkle_root = {
+                let position: u64 = input.leaf_position.into();
+                let mut current = MerkleNode::from(coin);
+                for (level, sibling) in input.merkle_path.iter().enumerate() {
+                    let level = level as u8;
+                    current = if position & (1 << level) == 0 {
+                        MerkleNode::combine(level.into(), &current, sibling)
+                    } else {
+                        MerkleNode::combine(level.into(), sibling, &current)
+                    };
+                }
+                current
+            };
+
+            let token_commit = poseidon_hash::<2>([note.token_id, gov_token_blind]);
+            assert_eq!(self.dao.gov_token_id, note.token_id);
+
+            let nullifier = poseidon_hash::<2>([input.secret.0, note.serial]);
+
+            let vote_commit = pedersen_commitment_u64(note.value, vote_value_blind);
+            let vote_commit_coords = vote_commit.to_affine().coordinates().unwrap();
+
+            let sigpub_coords = signature_public.0.to_affine().coordinates().unwrap();
+
+            let public_inputs = vec![
+                nullifier,
+                *vote_commit_coords.x(),
+                *vote_commit_coords.y(),
+                token_commit,
+                merkle_root.inner(),
+                *sigpub_coords.x(),
+                *sigpub_coords.y(),
+            ];
+
+            let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
+            let proving_key = &zk_info.proving_key;
+            debug!(target: "dao_contract::vote::wallet::Builder", "input_proof Proof::create()");
+            let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
+                .expect("DAO::vote() proving error!");
+            proofs.push(input_proof);
+
+            let input = Input {
+                nullifier: Nullifier::from(nullifier),
+                vote_commit,
+                merkle_root,
+                signature_public,
+            };
+            inputs.push(input);
+        }
+
+        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_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_quot = pallas::Base::from(self.dao.approval_ratio_quot);
+        let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
+
+        let dao_pubkey_coords = self.dao.public_key.0.to_affine().coordinates().unwrap();
+
+        let dao_bulla = poseidon_hash::<8>([
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_quot,
+            dao_approval_ratio_base,
+            self.dao.gov_token_id,
+            *dao_pubkey_coords.x(),
+            *dao_pubkey_coords.y(),
+            self.dao.bulla_blind,
+        ]);
+
+        let proposal_bulla = poseidon_hash::<8>([
+            *proposal_dest_coords.x(),
+            *proposal_dest_coords.y(),
+            proposal_amount,
+            self.proposal.serial,
+            self.proposal.token_id,
+            dao_bulla,
+            self.proposal.blind,
+            // @tmp-workaround
+            self.proposal.blind,
+        ]);
+
+        let vote_option = self.vote.vote_option as u64;
+        assert!(vote_option == 0 || vote_option == 1);
+
+        let yes_vote_commit =
+            pedersen_commitment_u64(vote_option * vote_value, self.vote.vote_option_blind);
+        let yes_vote_commit_coords = yes_vote_commit.to_affine().coordinates().unwrap();
+
+        let all_vote_commit = pedersen_commitment_u64(vote_value, vote_value_blind);
+        let all_vote_commit_coords = all_vote_commit.to_affine().coordinates().unwrap();
+
+        let zk_info = zk_bins.lookup(&"dao-vote-main".to_string()).unwrap();
+        let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
+            info
+        } else {
+            panic!("Not binary info")
+        };
+        let zk_bin = zk_info.bincode.clone();
+
+        let prover_witnesses = vec![
+            // proposal params
+            Witness::Base(Value::known(*proposal_dest_coords.x())),
+            Witness::Base(Value::known(*proposal_dest_coords.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_quot)),
+            Witness::Base(Value::known(dao_approval_ratio_base)),
+            Witness::Base(Value::known(self.dao.gov_token_id)),
+            Witness::Base(Value::known(*dao_pubkey_coords.x())),
+            Witness::Base(Value::known(*dao_pubkey_coords.y())),
+            Witness::Base(Value::known(self.dao.bulla_blind)),
+            // Vote
+            Witness::Base(Value::known(pallas::Base::from(vote_option))),
+            Witness::Scalar(Value::known(self.vote.vote_option_blind)),
+            // Total number of gov tokens allocated
+            Witness::Base(Value::known(pallas::Base::from(vote_value))),
+            Witness::Scalar(Value::known(vote_value_blind)),
+            // gov token
+            Witness::Base(Value::known(gov_token_blind)),
+        ];
+
+        let public_inputs = vec![
+            token_commit,
+            proposal_bulla,
+            // this should be a value commit??
+            *yes_vote_commit_coords.x(),
+            *yes_vote_commit_coords.y(),
+            *all_vote_commit_coords.x(),
+            *all_vote_commit_coords.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, vote_value, vote_value_blind };
+        let enc_note = note::encrypt(&note, &self.vote_keypair.public).unwrap();
+
+        let header = Header { token_commit, proposal_bulla, yes_vote_commit, enc_note };
+
+        let call_data = CallData { header, inputs };
+
+        FuncCall {
+            contract_id: *CONTRACT_ID,
+            func_id: *super::FUNC_ID,
+            call_data: Box::new(call_data),
+            proofs,
+        }
+    }
+}