瀏覽代碼

Merge branch 'dao-contract'

x 3 年之前
父節點
當前提交
93752a0b74

+ 6 - 0
Cargo.toml

@@ -297,3 +297,9 @@ zkas = [
 name = "net"
 path = "example/net.rs"
 required-features = ["async-runtime", "net"]
+
+[[example]]
+name = "zk"
+path = "example/zk.rs"
+required-features = ["zk"]
+

+ 16 - 10
example/zk.rs

@@ -19,24 +19,30 @@
 // ../zkas simple.zk
 
 use darkfi::{
-    crypto::{
-        proof::{ProvingKey, VerifyingKey},
-        Proof,
-    },
     zk::{
+        proof::{Proof, ProvingKey, VerifyingKey},
         vm::{Witness, ZkCircuit},
         vm_stack::empty_witnesses,
     },
     zkas::decoder::ZkBinary,
     Result,
 };
-use darkfi_sdk::crypto::pedersen::pedersen_commitment_u64;
-use halo2_proofs::circuit::Value;
-use pasta_curves::{
-    arithmetic::CurveAffine,
-    group::{ff::Field, Curve},
-    pallas,
+use darkfi_sdk::{
+    crypto::{
+        pedersen::pedersen_commitment_u64, poseidon_hash, MerkleNode, PublicKey, SecretKey, TokenId,
+    },
+    incrementalmerkletree,
+    incrementalmerkletree::{bridgetree::BridgeTree, Hashable, Tree},
+    pasta::{
+        arithmetic::CurveAffine,
+        group::{
+            ff::{Field, PrimeField},
+            Curve,
+        },
+        pallas,
+    },
 };
+use halo2_proofs::circuit::Value;
 use rand::rngs::OsRng;
 
 fn main() -> Result<()> {

+ 2 - 4
src/consensus/validator.rs

@@ -22,7 +22,7 @@ use async_std::sync::{Arc, RwLock};
 use darkfi_sdk::{
     crypto::{
         constants::MERKLE_DEPTH,
-        contract_id::MONEY_CONTRACT_ID,
+        contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
         schnorr::{SchnorrPublic, SchnorrSecret},
         MerkleNode, PublicKey, SecretKey,
     },
@@ -149,7 +149,7 @@ impl ValidatorState {
         // The faucet pubkeys are pubkeys which are allowed to create clear inputs
         // in the money contract.
         let money_contract_deploy_payload = serialize(&faucet_pubkeys);
-        //let dao_contract_deploy_payload = vec![];
+        let dao_contract_deploy_payload = vec![];
 
         // In this hashmap, we keep references to ZK proof verifying keys needed
         // for the circuits our native contracts provide.
@@ -162,14 +162,12 @@ impl ValidatorState {
                 include_bytes!("../contract/money/money_contract.wasm").to_vec(),
                 money_contract_deploy_payload,
             ),
-            /*
             (
                 "DAO Contract",
                 *DAO_CONTRACT_ID,
                 include_bytes!("../contract/dao/dao_contract.wasm").to_vec(),
                 dao_contract_deploy_payload,
             ),
-            */
         ];
 
         info!("Deploying native wasm contracts");

+ 1 - 0
src/contract/dao/Cargo.toml

@@ -25,6 +25,7 @@ rand = { version = "0.8.5", optional = true }
 [dev-dependencies]
 async-std = {version = "1.12.0", features = ["attributes"]}
 darkfi = {path = "../../../", features = ["tx", "blockchain"]}
+darkfi-money-contract = { path = "../money", features = ["client", "no-entrypoint"] }
 simplelog = "0.12.0"
 sled = "0.34.7"
 sqlx = {version = "0.6.2", features = ["runtime-async-std-native-tls", "sqlite"]}

+ 1 - 1
src/contract/dao/Makefile

@@ -33,7 +33,7 @@ test-integration: all
 		--package darkfi-dao-contract \
 		--test integration
 
-test:
+test: test-integration
 
 clean:
 	rm -f $(PROOFS_BIN) $(WASM_BIN)

+ 2 - 2
src/contract/dao/proof/dao-propose-burn.zk

@@ -20,8 +20,8 @@ contract "DaoProposeInput" {
 }
 
 circuit "DaoProposeInput" {
-	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);

+ 7 - 9
src/contract/dao/src/client.rs → src/contract/dao/src/dao_client.rs

@@ -26,6 +26,7 @@ use darkfi_sdk::{
         coin::Coin, constants::MERKLE_DEPTH, poseidon_hash, MerkleNode, PublicKey, SecretKey,
         TokenId,
     },
+    incrementalmerkletree,
     incrementalmerkletree::{bridgetree::BridgeTree, Tree},
     pasta::pallas,
 };
@@ -33,17 +34,15 @@ use halo2_proofs::circuit::Value;
 use log::debug;
 use rand::rngs::OsRng;
 
-use crate::{
-    note::EncryptedNote2,
-    state::{DaoBulla, DaoMintParams},
-};
+use darkfi_money_contract::client::{EncryptedNote, Note};
+
+use crate::state::{DaoBulla, DaoMintParams};
 
 pub type MerkleTree = BridgeTree<MerkleNode, { MERKLE_DEPTH }>;
 
-/*
 pub struct OwnCoin {
     pub coin: Coin,
-    pub note: money::transfer::wallet::Note,
+    pub note: Note,
     pub leaf_position: incrementalmerkletree::Position,
 }
 
@@ -52,7 +51,7 @@ pub struct WalletCache {
     // TODO: This can be HashableBase
     cache: Vec<(SecretKey, Vec<OwnCoin>)>,
     /// The entire Merkle tree state
-    tree: MerkleTree,
+    pub tree: MerkleTree,
 }
 
 impl Default for WalletCache {
@@ -83,7 +82,7 @@ impl WalletCache {
         panic!("you forget to track() this secret!");
     }
 
-    pub fn try_decrypt_note(&mut self, coin: Coin, ciphertext: &EncryptedNote2) {
+    pub fn try_decrypt_note(&mut self, coin: Coin, ciphertext: &EncryptedNote) {
         // Add the new coins to the Merkle tree
         let node = MerkleNode::from(coin.inner());
         self.tree.append(&node);
@@ -98,7 +97,6 @@ impl WalletCache {
         }
     }
 }
-*/
 
 struct DaoMintRevealed {
     pub bulla: DaoBulla,

+ 228 - 0
src/contract/dao/src/dao_exec_client.rs

@@ -0,0 +1,228 @@
+/* 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::{
+        pedersen::pedersen_commitment_u64, poseidon_hash, MerkleNode, PublicKey, SecretKey, TokenId,
+    },
+    incrementalmerkletree,
+    incrementalmerkletree::{bridgetree::BridgeTree, Hashable, Tree},
+    pasta::{
+        arithmetic::CurveAffine,
+        group::{
+            ff::{Field, PrimeField},
+            Curve,
+        },
+        pallas,
+    },
+};
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+use halo2_proofs::circuit::Value;
+use log::{debug, info};
+use rand::rngs::OsRng;
+
+use darkfi::{
+    zk::{
+        proof::{Proof, ProvingKey},
+        vm::ZkCircuit,
+        vm_stack::Witness,
+    },
+    zkas::ZkBinary,
+    Error, Result,
+};
+
+use crate::{
+    dao_propose_client::{DaoParams, Proposal},
+    state::DaoExecParams,
+};
+
+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,
+        exec_zkbin: &ZkBinary,
+        exec_pk: &ProvingKey,
+    ) -> Result<(DaoExecParams, Vec<Proof>)> {
+        debug!(target: "dao_contract::exec::wallet::Builder", "build()");
+        let mut proofs = vec![];
+
+        let (proposal_dest_x, proposal_dest_y) = self.proposal.dest.xy();
+
+        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_pub_x, dao_pub_y) = self.dao.public_key.xy();
+
+        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.inner(),
+            dao_pub_x,
+            dao_pub_y,
+            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.inner(),
+            dao_bulla,
+            self.proposal.blind,
+            // @tmp-workaround
+            self.proposal.blind,
+        ]);
+
+        let coin_0 = poseidon_hash::<8>([
+            proposal_dest_x,
+            proposal_dest_y,
+            proposal_amount,
+            self.proposal.token_id.inner(),
+            self.proposal.serial,
+            user_spend_hook,
+            user_data,
+            self.proposal.blind,
+        ]);
+
+        let coin_1 = poseidon_hash::<8>([
+            dao_pub_x,
+            dao_pub_y,
+            change,
+            self.proposal.token_id.inner(),
+            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_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.inner())),
+            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.inner())),
+            Witness::Base(Value::known(dao_pub_x)),
+            Witness::Base(Value::known(dao_pub_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, exec_zkbin.clone());
+        let input_proof = Proof::create(&exec_pk, &[circuit], &public_inputs, &mut OsRng)
+            .expect("DAO::exec() proving error!)");
+        proofs.push(input_proof);
+
+        let params = DaoExecParams {
+            proposal: proposal_bulla,
+            coin_0,
+            coin_1,
+            yes_votes_commit,
+            all_votes_commit,
+            input_value_commit,
+        };
+
+        Ok((params, proofs))
+    }
+}

+ 287 - 0
src/contract/dao/src/dao_propose_client.rs

@@ -0,0 +1,287 @@
+/* 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::{
+        pedersen::pedersen_commitment_u64, poseidon_hash, MerkleNode, PublicKey, SecretKey, TokenId,
+    },
+    incrementalmerkletree,
+    incrementalmerkletree::{bridgetree::BridgeTree, Hashable, Tree},
+    pasta::{
+        arithmetic::CurveAffine,
+        group::{
+            ff::{Field, PrimeField},
+            Curve,
+        },
+        pallas,
+    },
+};
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+use halo2_proofs::circuit::Value;
+use rand::rngs::OsRng;
+
+use darkfi::{
+    zk::{
+        proof::{Proof, ProvingKey},
+        vm::ZkCircuit,
+        vm_stack::Witness,
+    },
+    zkas::ZkBinary,
+    Error, Result,
+};
+
+use crate::{
+    note,
+    state::{DaoProposeParams, ProposeInput},
+};
+
+#[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: TokenId,
+    pub public_key: PublicKey,
+    pub bulla_blind: pallas::Base,
+}
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct Note {
+    pub proposal: Proposal,
+}
+
+pub struct BuilderInput {
+    pub secret: SecretKey,
+    //pub note: money::transfer::wallet::Note,
+    pub note: darkfi_money_contract::client::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: TokenId,
+    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 */) -> Result<(DaoProposeParams, Vec<Proof>)> {
+    pub fn build(
+        self,
+        burn_zkbin: &ZkBinary,
+        burn_pk: &ProvingKey,
+        main_zkbin: &ZkBinary,
+        main_pk: &ProvingKey,
+    ) -> Result<(DaoProposeParams, Vec<Proof>)> {
+        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);
+
+            // 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.inner())),
+                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.inner())),
+                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.inner())),
+            ];
+
+            let public_key = PublicKey::from_secret(input.secret);
+            let (pub_x, pub_y) = public_key.xy();
+
+            let coin = poseidon_hash::<8>([
+                pub_x,
+                pub_y,
+                pallas::Base::from(note.value),
+                note.token_id.inner(),
+                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.inner(), 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 (sig_x, sig_y) = signature_public.xy();
+
+            let public_inputs = vec![
+                *value_coords.x(),
+                *value_coords.y(),
+                token_commit,
+                merkle_root.inner(),
+                sig_x,
+                sig_y,
+            ];
+            let circuit = ZkCircuit::new(prover_witnesses, burn_zkbin.clone());
+
+            let proving_key = &burn_pk;
+            let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
+                .expect("DAO::propose() proving error!");
+            proofs.push(input_proof);
+
+            let input = ProposeInput { 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.inner(), gov_token_blind]);
+
+        let (proposal_dest_x, proposal_dest_y) = self.proposal.dest.xy();
+
+        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_pub_x, dao_pub_y) = self.dao.public_key.xy();
+
+        let dao_bulla = poseidon_hash::<8>([
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_quot,
+            dao_approval_ratio_base,
+            self.dao.gov_token_id.inner(),
+            dao_pub_x,
+            dao_pub_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.inner(),
+            dao_bulla,
+            self.proposal.blind,
+            // @tmp-workaround
+            self.proposal.blind,
+        ]);
+
+        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.inner())),
+            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.inner())),
+            Witness::Base(Value::known(dao_pub_x)),
+            Witness::Base(Value::known(dao_pub_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, main_zkbin.clone());
+
+        let main_proof = Proof::create(&main_pk, &[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 params = DaoProposeParams {
+            dao_merkle_root: self.dao_merkle_root,
+            proposal_bulla,
+            token_commit,
+            ciphertext: enc_note.ciphertext,
+            ephem_public: enc_note.ephem_public,
+            inputs,
+        };
+
+        Ok((params, proofs))
+    }
+}

+ 321 - 0
src/contract/dao/src/dao_vote_client.rs

@@ -0,0 +1,321 @@
+/* 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::{
+        keypair::Keypair, pedersen::pedersen_commitment_u64, poseidon_hash, MerkleNode, Nullifier,
+        PublicKey, SecretKey, TokenId,
+    },
+    incrementalmerkletree,
+    incrementalmerkletree::{bridgetree::BridgeTree, Hashable, Tree},
+    pasta::{
+        arithmetic::CurveAffine,
+        group::{
+            ff::{Field, PrimeField},
+            Curve,
+        },
+        pallas,
+    },
+};
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+use halo2_proofs::circuit::Value;
+use log::{debug, info};
+use rand::rngs::OsRng;
+
+use darkfi::{
+    zk::{
+        proof::{Proof, ProvingKey},
+        vm::ZkCircuit,
+        vm_stack::Witness,
+    },
+    zkas::ZkBinary,
+    Error, Result,
+};
+
+use crate::{
+    dao_propose_client::{DaoParams, Proposal},
+    note,
+    state::{DaoVoteParams, VoteInput},
+};
+
+#[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 note: darkfi_money_contract::client::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,
+        burn_zkbin: &ZkBinary,
+        burn_pk: &ProvingKey,
+        main_zkbin: &ZkBinary,
+        main_pk: &ProvingKey,
+    ) -> Result<(DaoVoteParams, Vec<Proof>)> {
+        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.inner())),
+                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.inner())),
+                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.inner())),
+            ];
+
+            let public_key = PublicKey::from_secret(input.secret);
+            let (pub_x, pub_y) = public_key.xy();
+
+            let coin = poseidon_hash::<8>([
+                pub_x,
+                pub_y,
+                pallas::Base::from(note.value),
+                note.token_id.inner(),
+                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.inner(), gov_token_blind]);
+            assert_eq!(self.dao.gov_token_id, note.token_id);
+
+            let nullifier = poseidon_hash::<2>([input.secret.inner(), note.serial]);
+
+            let vote_commit = pedersen_commitment_u64(note.value, vote_value_blind);
+            let vote_commit_coords = vote_commit.to_affine().coordinates().unwrap();
+
+            let (sig_x, sig_y) = signature_public.xy();
+
+            let public_inputs = vec![
+                nullifier,
+                *vote_commit_coords.x(),
+                *vote_commit_coords.y(),
+                token_commit,
+                merkle_root.inner(),
+                sig_x,
+                sig_y,
+            ];
+
+            let circuit = ZkCircuit::new(prover_witnesses, burn_zkbin.clone());
+            debug!(target: "dao_contract::vote::wallet::Builder", "input_proof Proof::create()");
+            let input_proof = Proof::create(&burn_pk, &[circuit], &public_inputs, &mut OsRng)
+                .expect("DAO::vote() proving error!");
+            proofs.push(input_proof);
+
+            let input = VoteInput {
+                nullifier: Nullifier::from(nullifier),
+                vote_commit,
+                merkle_root,
+                signature_public,
+            };
+            inputs.push(input);
+        }
+
+        let token_commit = poseidon_hash::<2>([self.dao.gov_token_id.inner(), gov_token_blind]);
+
+        let (proposal_dest_x, proposal_dest_y) = self.proposal.dest.xy();
+
+        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_pub_x, dao_pub_y) = self.dao.public_key.xy();
+
+        let dao_bulla = poseidon_hash::<8>([
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_quot,
+            dao_approval_ratio_base,
+            self.dao.gov_token_id.inner(),
+            dao_pub_x,
+            dao_pub_y,
+            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.inner(),
+            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_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.inner())),
+            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.inner())),
+            Witness::Base(Value::known(dao_pub_x)),
+            Witness::Base(Value::known(dao_pub_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, main_zkbin.clone());
+
+        debug!(target: "dao_contract::vote::wallet::Builder", "main_proof = Proof::create()");
+        let main_proof = Proof::create(&main_pk, &[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 params = DaoVoteParams {
+            token_commit,
+            proposal_bulla,
+            yes_vote_commit,
+
+            ciphertext: enc_note.ciphertext,
+            ephem_public: enc_note.ephem_public,
+            inputs,
+        };
+
+        Ok((params, proofs))
+    }
+}

+ 36 - 26
src/contract/dao/src/entrypoint.rs

@@ -17,7 +17,10 @@
  */
 
 use darkfi_sdk::{
-    crypto::{contract_id::MONEY_CONTRACT_ID, ContractId, MerkleNode, MerkleTree, PublicKey},
+    crypto::{
+        contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
+        ContractId, MerkleNode, MerkleTree, PublicKey,
+    },
     db::{db_contains_key, db_get, db_init, db_lookup, db_set, SMART_CONTRACT_ZKAS_DB_NAME},
     error::{ContractError, ContractResult},
     merkle::merkle_add,
@@ -57,8 +60,8 @@ darkfi_sdk::define_contract!(
 // These are the different sled trees that will be created
 pub const DAO_BULLA_TREE: &str = "dao_info";
 pub const DAO_ROOTS_TREE: &str = "dao_roots";
-pub const DAO_PROPOSAL_TREE: &str = "dao_proposals";
-pub const DAO_PROPOSAL_ROOTS_TREE: &str = "dao_proposal_roots";
+//pub const DAO_PROPOSAL_TREE: &str = "dao_proposals";
+//pub const DAO_PROPOSAL_ROOTS_TREE: &str = "dao_proposal_roots";
 pub const DAO_PROPOSAL_VOTES_TREE: &str = "dao_proposal_votes";
 
 // These are keys inside the some db trees
@@ -117,11 +120,14 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
     };
 
     // Set up a database tree to hold the Merkle tree for proposal bullas
+    /*
     let dao_proposal_db = match db_lookup(cid, DAO_PROPOSAL_TREE) {
         Ok(v) => v,
         Err(_) => db_init(cid, DAO_PROPOSAL_TREE)?,
     };
+    */
 
+    /*
     match db_get(dao_proposal_db, &serialize(&DAO_PROPOSAL_MERKLE_TREE))? {
         Some(bytes) => {
             // We found some bytes, try to deserialize into a tree.
@@ -138,12 +144,13 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
             db_set(dao_proposal_db, &serialize(&DAO_PROPOSAL_MERKLE_TREE), &tree_data)?;
         }
     };
+    */
 
     // Set up a database tree to hold Merkle roots for the proposal bullas Merkle tree
-    let _ = match db_lookup(cid, DAO_PROPOSAL_ROOTS_TREE) {
+    /*let _ = match db_lookup(cid, DAO_PROPOSAL_ROOTS_TREE) {
         Ok(v) => v,
         Err(_) => db_init(cid, DAO_PROPOSAL_ROOTS_TREE)?,
-    };
+    };*/
 
     // Set up a database tree to hold proposal votes (k: proposalbulla, v: ProposalVotes)
     let _ = match db_lookup(cid, DAO_PROPOSAL_VOTES_TREE) {
@@ -220,7 +227,7 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
                 msg!("Invalid proposal {:?}", params.proposal_bulla);
                 return Err(ContractError::Custom(4))
             };
-            let proposal_votes: ProposalVotes = deserialize(&proposal_votes)?;
+            let mut proposal_votes: ProposalVotes = deserialize(&proposal_votes)?;
 
             // Check the Merkle roots and nullifiers for the input coins are valid
             let mut vote_nullifiers = vec![];
@@ -246,15 +253,17 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
                     return Err(ContractError::Custom(7))
                 }
 
-                all_vote_commit += input.vote_commit;
-                vote_nullifiers.push(input.nullifier);
+                proposal_votes.all_votes_commit += input.vote_commit;
+                proposal_votes.vote_nullifiers.push(input.nullifier);
             }
 
+            proposal_votes.yes_votes_commit += params.yes_vote_commit;
+
             let update = DaoVoteUpdate {
                 proposal_bulla: params.proposal_bulla,
-                vote_nullifiers,
-                yes_vote_commit: params.yes_vote_commit,
-                all_vote_commit,
+                proposal_votes, //vote_nullifiers,
+                                //yes_vote_commit: params.yes_vote_commit,
+                                //all_vote_commit,
             };
             let mut update_data = vec![];
             update_data.write_u8(DaoFunction::Vote as u8)?;
@@ -300,7 +309,7 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
             assert!(input_valcoms == params.input_value_commit);
 
             // 3. Get the ProposalVote from DAO state
-            let proposal_db = db_lookup(cid, DAO_PROPOSAL_TREE)?;
+            let proposal_db = db_lookup(cid, DAO_PROPOSAL_VOTES_TREE)?;
             let Some(proposal_votes) = db_get(proposal_db, &serialize(&params.proposal))? else {
                 msg!("Proposal {:?} not found in db", params.proposal);
                 return Err(ContractError::Custom(1));
@@ -340,10 +349,11 @@ fn process_update(cid: ContractId, ix: &[u8]) -> ContractResult {
         DaoFunction::Propose => {
             let update: DaoProposeUpdate = deserialize(&ix[1..])?;
 
-            let proposal_tree_db = db_lookup(cid, DAO_PROPOSAL_TREE)?;
-            let proposal_root_db = db_lookup(cid, DAO_PROPOSAL_ROOTS_TREE)?;
+            //let proposal_tree_db = db_lookup(cid, DAO_PROPOSAL_TREE)?;
+            //let proposal_root_db = db_lookup(cid, DAO_PROPOSAL_ROOTS_TREE)?;
             let proposal_vote_db = db_lookup(cid, DAO_PROPOSAL_VOTES_TREE)?;
 
+            /*
             let node = MerkleNode::from(update.proposal_bulla);
             merkle_add(
                 proposal_tree_db,
@@ -351,6 +361,7 @@ fn process_update(cid: ContractId, ix: &[u8]) -> ContractResult {
                 &serialize(&DAO_PROPOSAL_MERKLE_TREE),
                 &[node],
             )?;
+            */
 
             let pv = ProposalVotes::default();
             db_set(proposal_vote_db, &serialize(&update.proposal_bulla), &serialize(&pv))?;
@@ -361,18 +372,17 @@ fn process_update(cid: ContractId, ix: &[u8]) -> ContractResult {
         DaoFunction::Vote => {
             let mut update: DaoVoteUpdate = deserialize(&ix[1..])?;
 
-            let proposal_vote_db = db_lookup(cid, DAO_PROPOSAL_VOTES_TREE)?;
+            // Perform this code:
+            //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);
 
-            let Some(proposal_votes) = db_get(proposal_vote_db, &serialize(&update.proposal_bulla))? else {
-                msg!("Proposal {:?} not found in db", update.proposal_bulla);
-                return Err(ContractError::Custom(1));
-            };
-
-            let mut proposal_votes: ProposalVotes = deserialize(&proposal_votes)?;
-
-            proposal_votes.yes_votes_commit += update.yes_vote_commit;
-            proposal_votes.all_votes_commit += update.all_vote_commit;
-            proposal_votes.vote_nullifiers.append(&mut update.vote_nullifiers);
+            let proposal_vote_db = db_lookup(cid, DAO_PROPOSAL_VOTES_TREE)?;
+            db_set(
+                proposal_vote_db,
+                &serialize(&update.proposal_bulla),
+                &serialize(&update.proposal_votes),
+            )?;
 
             Ok(())
         }
@@ -540,7 +550,7 @@ fn get_metadata(cid: ContractId, ix: &[u8]) -> ContractResult {
                     *all_votes_coords.y(),
                     *input_value_coords.x(),
                     *input_value_coords.y(),
-                    MONEY_CONTRACT_ID.inner(), // <-- TODO: Should be money contract id?
+                    DAO_CONTRACT_ID.inner(), // <-- TODO: Should be money contract id?
                     pallas::Base::zero(),
                     pallas::Base::zero(),
                 ],

+ 18 - 2
src/contract/dao/src/lib.rs

@@ -27,8 +27,24 @@ pub mod state;
 pub mod note;
 
 #[cfg(feature = "client")]
-/// Transaction building API for clients interacting with this contract
-pub mod client;
+/// Transaction building API for clients interacting with DAO contract
+pub mod dao_client;
+
+#[cfg(feature = "client")]
+/// Transaction building API for clients interacting with DAO contract
+pub mod dao_propose_client;
+
+#[cfg(feature = "client")]
+/// Transaction building API for clients interacting with DAO contract
+pub mod dao_vote_client;
+
+#[cfg(feature = "client")]
+/// Transaction building API for clients interacting with DAO contract
+pub mod dao_exec_client;
+
+#[cfg(feature = "client")]
+/// Transaction building API for clients interacting with money contract
+pub mod money_client;
 
 // These are the zkas circuit namespaces
 pub const DAO_CONTRACT_ZKAS_DAO_MINT_NS: &str = "DaoMint";

+ 275 - 0
src/contract/dao/src/money_client.rs

@@ -0,0 +1,275 @@
+/* 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 chacha20poly1305::{AeadInPlace, ChaCha20Poly1305, KeyInit};
+use darkfi::{
+    consensus::leadcoin::LeadCoin,
+    zk::{
+        proof::{Proof, ProvingKey},
+        vm::ZkCircuit,
+        vm_stack::Witness,
+    },
+    zkas::ZkBinary,
+    ClientFailed, Error, Result,
+};
+use darkfi_sdk::{
+    crypto::{
+        constants::MERKLE_DEPTH,
+        diffie_hellman::{kdf_sapling, sapling_ka_agree},
+        pedersen::{pedersen_commitment_base, pedersen_commitment_u64, ValueBlind, ValueCommit},
+        poseidon_hash, Keypair, MerkleNode, Nullifier, PublicKey, SecretKey, TokenId,
+    },
+    incrementalmerkletree,
+    incrementalmerkletree::{bridgetree::BridgeTree, Hashable, Tree},
+    pasta::{
+        arithmetic::CurveAffine,
+        group::{
+            ff::{Field, PrimeField},
+            Curve,
+        },
+        pallas,
+    },
+};
+use darkfi_serial::{serialize, Decodable, Encodable, SerialDecodable, SerialEncodable};
+use halo2_proofs::circuit::Value;
+use log::{debug, error, info};
+use rand::rngs::OsRng;
+
+use darkfi_money_contract::{
+    client::{create_transfer_burn_proof, create_transfer_mint_proof, Note},
+    state::{ClearInput, Input, MoneyTransferParams, Output},
+};
+
+/*
+use darkfi::{
+    crypto::{
+        burn_proof::create_burn_proof,
+        mint_proof::create_mint_proof,
+        types::{
+            DrkCoinBlind, DrkSerial, DrkSpendHook, DrkUserData, DrkUserDataBlind, DrkValueBlind,
+        },
+    },
+    Result,
+};
+
+use crate::{
+    contract::money::{
+        transfer::validate::{CallData, ClearInput, Input, Output},
+        CONTRACT_ID,
+    },
+    note,
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
+};
+*/
+
+/*
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct Note {
+    pub serial: DrkSerial,
+    pub value: u64,
+    pub token_id: TokenId,
+    pub spend_hook: DrkSpendHook,
+    pub user_data: DrkUserData,
+    pub coin_blind: DrkCoinBlind,
+    pub value_blind: DrkValueBlind,
+    pub token_blind: DrkValueBlind,
+}
+*/
+
+pub struct Builder {
+    pub clear_inputs: Vec<BuilderClearInputInfo>,
+    pub inputs: Vec<BuilderInputInfo>,
+    pub outputs: Vec<BuilderOutputInfo>,
+}
+
+pub struct BuilderClearInputInfo {
+    pub value: u64,
+    pub token_id: TokenId,
+    pub signature_secret: SecretKey,
+}
+
+pub struct BuilderInputInfo {
+    pub leaf_position: incrementalmerkletree::Position,
+    pub merkle_path: Vec<MerkleNode>,
+    pub secret: SecretKey,
+    pub note: Note,
+    pub user_data_blind: pallas::Base,
+    pub value_blind: ValueBlind,
+    pub signature_secret: SecretKey,
+}
+
+pub struct BuilderOutputInfo {
+    pub value: u64,
+    pub token_id: TokenId,
+    pub public: PublicKey,
+    pub serial: pallas::Base,
+    pub coin_blind: pallas::Base,
+    pub spend_hook: pallas::Base,
+    pub user_data: pallas::Base,
+}
+
+impl Builder {
+    fn compute_remainder_blind(
+        clear_inputs: &[ClearInput],
+        input_blinds: &[ValueBlind],
+        output_blinds: &[ValueBlind],
+    ) -> ValueBlind {
+        let mut total = ValueBlind::zero();
+
+        for input in clear_inputs {
+            total += input.value_blind;
+        }
+
+        for input_blind in input_blinds {
+            total += input_blind;
+        }
+
+        for output_blind in output_blinds {
+            total -= output_blind;
+        }
+
+        total
+    }
+
+    pub fn build(
+        self,
+        mint_zkbin: &ZkBinary,
+        mint_pk: &ProvingKey,
+        burn_zkbin: &ZkBinary,
+        burn_pk: &ProvingKey,
+    ) -> Result<(MoneyTransferParams, Vec<Proof>)> {
+        assert!(self.clear_inputs.len() + self.inputs.len() > 0);
+
+        let mut clear_inputs = vec![];
+        let token_blind = ValueBlind::random(&mut OsRng);
+        for input in &self.clear_inputs {
+            let signature_public = PublicKey::from_secret(input.signature_secret);
+            let value_blind = ValueBlind::random(&mut OsRng);
+
+            let clear_input = ClearInput {
+                value: input.value,
+                token_id: input.token_id,
+                value_blind,
+                token_blind,
+                signature_public,
+            };
+            clear_inputs.push(clear_input);
+        }
+
+        let mut proofs = vec![];
+        let mut inputs = vec![];
+        let mut input_blinds = vec![];
+
+        for input in self.inputs {
+            let value_blind = input.value_blind;
+            input_blinds.push(value_blind);
+
+            // Note from the previous output
+            let note = input.note.clone();
+
+            let (proof, revealed) = create_transfer_burn_proof(
+                burn_zkbin,
+                burn_pk,
+                note.value,
+                note.token_id,
+                value_blind,
+                token_blind,
+                note.serial,
+                note.spend_hook,
+                note.user_data,
+                input.user_data_blind,
+                note.coin_blind,
+                input.secret,
+                input.leaf_position,
+                input.merkle_path.clone(),
+                input.signature_secret,
+            )?;
+
+            proofs.push(proof);
+
+            let input = Input {
+                value_commit: revealed.value_commit,
+                token_commit: revealed.token_commit,
+                nullifier: revealed.nullifier,
+                merkle_root: revealed.merkle_root,
+                spend_hook: revealed.spend_hook,
+                user_data_enc: revealed.user_data_enc,
+                signature_public: revealed.signature_public,
+            };
+            inputs.push(input);
+        }
+
+        let mut outputs = vec![];
+        let mut output_blinds = vec![];
+        // This value_blind calc assumes there will always be at least a single output
+        assert!(!self.outputs.is_empty());
+
+        for (i, output) in self.outputs.iter().enumerate() {
+            let value_blind = if i == self.outputs.len() - 1 {
+                Self::compute_remainder_blind(&clear_inputs, &input_blinds, &output_blinds)
+            } else {
+                ValueBlind::random(&mut OsRng)
+            };
+            output_blinds.push(value_blind);
+
+            let serial = output.serial;
+            let coin_blind = output.coin_blind;
+
+            let (proof, revealed) = create_transfer_mint_proof(
+                mint_zkbin,
+                mint_pk,
+                output.value,
+                output.token_id,
+                value_blind,
+                token_blind,
+                serial,
+                output.spend_hook,
+                output.user_data,
+                coin_blind,
+                output.public,
+            )?;
+
+            proofs.push(proof);
+
+            let note = Note {
+                serial,
+                value: output.value,
+                token_id: output.token_id,
+                spend_hook: output.spend_hook,
+                user_data: output.user_data,
+                coin_blind,
+                value_blind,
+                token_blind,
+                memo: Vec::new(),
+            };
+
+            let encrypted_note = note.encrypt(&output.public)?;
+
+            let output = Output {
+                value_commit: revealed.value_commit,
+                token_commit: revealed.token_commit,
+                coin: revealed.coin.inner(),
+                ciphertext: encrypted_note.ciphertext,
+                ephem_public: encrypted_note.ephem_public,
+            };
+            outputs.push(output);
+        }
+
+        Ok((MoneyTransferParams { clear_inputs, inputs, outputs }, proofs))
+    }
+}

+ 2 - 2
src/contract/dao/src/note.rs

@@ -50,8 +50,8 @@ pub fn encrypt<T: Encodable>(note: &T, public: &PublicKey) -> Result<EncryptedNo
 
 #[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
 pub struct EncryptedNote2 {
-    ciphertext: Vec<u8>,
-    ephem_public: PublicKey,
+    pub ciphertext: Vec<u8>,
+    pub ephem_public: PublicKey,
 }
 
 impl EncryptedNote2 {

+ 5 - 3
src/contract/dao/src/state.rs

@@ -117,9 +117,11 @@ pub struct DaoVoteParams {
 #[derive(SerialEncodable, SerialDecodable)]
 pub struct DaoVoteUpdate {
     pub proposal_bulla: pallas::Base,
-    pub vote_nullifiers: Vec<Nullifier>,
-    pub yes_vote_commit: pallas::Point,
-    pub all_vote_commit: pallas::Point,
+    // bad but lets get it just working...
+    pub proposal_votes: ProposalVotes,
+    //pub vote_nullifiers: Vec<Nullifier>,
+    //pub yes_vote_commit: pallas::Point,
+    //pub all_vote_commit: pallas::Point,
 }
 
 #[derive(SerialEncodable, SerialDecodable)]

+ 246 - 0
src/contract/dao/tests/dao_harness.rs

@@ -0,0 +1,246 @@
+/* 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::collections::HashMap;
+
+use darkfi::{
+    consensus::{
+        constants::{
+            TESTNET_BOOTSTRAP_TIMESTAMP, TESTNET_GENESIS_HASH_BYTES, TESTNET_GENESIS_TIMESTAMP,
+        },
+        ValidatorState, ValidatorStatePtr,
+    },
+    wallet::WalletDb,
+    zk::{proof::ProvingKey, vm::ZkCircuit, vm_stack::empty_witnesses},
+    zkas::ZkBinary,
+    Result,
+};
+use darkfi_sdk::{
+    crypto::{
+        contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
+        ContractId, Keypair, MerkleTree,
+    },
+    db::SMART_CONTRACT_ZKAS_DB_NAME,
+    pasta::group::ff::PrimeField,
+};
+use darkfi_serial::serialize;
+use log::{info, warn};
+use rand::rngs::OsRng;
+
+use darkfi_money_contract::{MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1};
+
+use darkfi_dao_contract::{
+    DAO_CONTRACT_ZKAS_DAO_EXEC_NS, DAO_CONTRACT_ZKAS_DAO_MINT_NS,
+    DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
+    DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
+};
+
+pub struct DaoTestHarness {
+    /// Minting all new coins
+    pub faucet_kp: Keypair,
+    /// Governance token holder 1
+    pub alice_kp: Keypair,
+    /// Governance token holder 2
+    pub bob_kp: Keypair,
+    /// Governance token holder 3
+    pub charlie_kp: Keypair,
+    /// Receiver for treasury tokens
+    pub rachel_kp: Keypair,
+    /// DAO keypair
+    pub dao_kp: Keypair,
+
+    pub alice_state: ValidatorStatePtr,
+    pub money_contract_id: ContractId,
+    pub dao_contract_id: ContractId,
+    pub proving_keys: HashMap<[u8; 32], Vec<(&'static str, ProvingKey)>>,
+
+    pub money_mint_zkbin: ZkBinary,
+    pub money_mint_pk: ProvingKey,
+
+    pub money_burn_zkbin: ZkBinary,
+    pub money_burn_pk: ProvingKey,
+
+    pub dao_mint_zkbin: ZkBinary,
+    pub dao_mint_pk: ProvingKey,
+
+    pub dao_propose_burn_zkbin: ZkBinary,
+    pub dao_propose_burn_pk: ProvingKey,
+
+    pub dao_propose_main_zkbin: ZkBinary,
+    pub dao_propose_main_pk: ProvingKey,
+
+    pub dao_vote_burn_zkbin: ZkBinary,
+    pub dao_vote_burn_pk: ProvingKey,
+
+    pub dao_vote_main_zkbin: ZkBinary,
+    pub dao_vote_main_pk: ProvingKey,
+
+    pub dao_exec_zkbin: ZkBinary,
+    pub dao_exec_pk: ProvingKey,
+}
+
+impl DaoTestHarness {
+    pub async fn new() -> Result<Self> {
+        let faucet_kp = Keypair::random(&mut OsRng);
+        let alice_kp = Keypair::random(&mut OsRng);
+        let bob_kp = Keypair::random(&mut OsRng);
+        let charlie_kp = Keypair::random(&mut OsRng);
+        let rachel_kp = Keypair::random(&mut OsRng);
+        let dao_kp = Keypair::random(&mut OsRng);
+
+        let faucet_pubkeys = vec![faucet_kp.public];
+
+        let alice_wallet = WalletDb::new("sqlite::memory:", "foo").await?;
+
+        let alice_sled_db = sled::Config::new().temporary(true).open()?;
+
+        let alice_state = ValidatorState::new(
+            &alice_sled_db,
+            *TESTNET_BOOTSTRAP_TIMESTAMP,
+            *TESTNET_GENESIS_TIMESTAMP,
+            *TESTNET_GENESIS_HASH_BYTES,
+            alice_wallet,
+            faucet_pubkeys,
+            false,
+        )
+        .await?;
+
+        let money_contract_id = *MONEY_CONTRACT_ID;
+        let dao_contract_id = *DAO_CONTRACT_ID;
+
+        let alice_sled = alice_state.read().await.blockchain.sled_db.clone();
+        let money_db_handle = alice_state.read().await.blockchain.contracts.lookup(
+            &alice_sled,
+            &money_contract_id,
+            SMART_CONTRACT_ZKAS_DB_NAME,
+        )?;
+        let dao_db_handle = alice_state.read().await.blockchain.contracts.lookup(
+            &alice_sled,
+            &dao_contract_id,
+            SMART_CONTRACT_ZKAS_DB_NAME,
+        )?;
+
+        info!("Decoding bincode");
+
+        let money_mint_zkbin =
+            money_db_handle.get(&serialize(&MONEY_CONTRACT_ZKAS_MINT_NS_V1))?.unwrap();
+        let money_mint_zkbin = ZkBinary::decode(&money_mint_zkbin)?;
+        let money_mint_witnesses = empty_witnesses(&money_mint_zkbin);
+        let money_mint_circuit = ZkCircuit::new(money_mint_witnesses, money_mint_zkbin.clone());
+
+        let money_burn_zkbin =
+            money_db_handle.get(&serialize(&MONEY_CONTRACT_ZKAS_BURN_NS_V1))?.unwrap();
+        let money_burn_zkbin = ZkBinary::decode(&money_burn_zkbin)?;
+        let money_burn_witnesses = empty_witnesses(&money_burn_zkbin);
+        let money_burn_circuit = ZkCircuit::new(money_burn_witnesses, money_burn_zkbin.clone());
+
+        let dao_mint_zkbin =
+            dao_db_handle.get(&serialize(&DAO_CONTRACT_ZKAS_DAO_MINT_NS))?.unwrap();
+        let dao_mint_zkbin = ZkBinary::decode(&dao_mint_zkbin)?;
+        let dao_mint_witnesses = empty_witnesses(&dao_mint_zkbin);
+        let dao_mint_circuit = ZkCircuit::new(dao_mint_witnesses, dao_mint_zkbin.clone());
+
+        let dao_propose_burn_zkbin =
+            dao_db_handle.get(&serialize(&DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS))?.unwrap();
+        let dao_propose_burn_zkbin = ZkBinary::decode(&dao_propose_burn_zkbin)?;
+        let dao_propose_burn_witnesses = empty_witnesses(&dao_propose_burn_zkbin);
+        let dao_propose_burn_circuit =
+            ZkCircuit::new(dao_propose_burn_witnesses, dao_propose_burn_zkbin.clone());
+
+        let dao_propose_main_zkbin =
+            dao_db_handle.get(&serialize(&DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS))?.unwrap();
+        let dao_propose_main_zkbin = ZkBinary::decode(&dao_propose_main_zkbin)?;
+        let dao_propose_main_witnesses = empty_witnesses(&dao_propose_main_zkbin);
+        let dao_propose_main_circuit =
+            ZkCircuit::new(dao_propose_main_witnesses, dao_propose_main_zkbin.clone());
+
+        let dao_vote_burn_zkbin =
+            dao_db_handle.get(&serialize(&DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS))?.unwrap();
+        let dao_vote_burn_zkbin = ZkBinary::decode(&dao_vote_burn_zkbin)?;
+        let dao_vote_burn_witnesses = empty_witnesses(&dao_vote_burn_zkbin);
+        let dao_vote_burn_circuit =
+            ZkCircuit::new(dao_vote_burn_witnesses, dao_vote_burn_zkbin.clone());
+
+        let dao_vote_main_zkbin =
+            dao_db_handle.get(&serialize(&DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS))?.unwrap();
+        let dao_vote_main_zkbin = ZkBinary::decode(&dao_vote_main_zkbin)?;
+        let dao_vote_main_witnesses = empty_witnesses(&dao_vote_main_zkbin);
+        let dao_vote_main_circuit =
+            ZkCircuit::new(dao_vote_main_witnesses, dao_vote_main_zkbin.clone());
+
+        let dao_exec_zkbin =
+            dao_db_handle.get(&serialize(&DAO_CONTRACT_ZKAS_DAO_EXEC_NS))?.unwrap();
+        let dao_exec_zkbin = ZkBinary::decode(&dao_exec_zkbin)?;
+        let dao_exec_witnesses = empty_witnesses(&dao_exec_zkbin);
+        let dao_exec_circuit = ZkCircuit::new(dao_exec_witnesses, dao_exec_zkbin.clone());
+
+        info!("Creating zk proving keys");
+
+        let k = 13;
+        let mut proving_keys = HashMap::<[u8; 32], Vec<(&str, ProvingKey)>>::new();
+
+        let money_mint_pk = ProvingKey::build(k, &money_mint_circuit);
+        let money_burn_pk = ProvingKey::build(k, &money_burn_circuit);
+        let dao_mint_pk = ProvingKey::build(k, &dao_mint_circuit);
+        let dao_propose_burn_pk = ProvingKey::build(k, &dao_propose_burn_circuit);
+        let dao_propose_main_pk = ProvingKey::build(k, &dao_propose_main_circuit);
+        let dao_vote_burn_pk = ProvingKey::build(k, &dao_vote_burn_circuit);
+        let dao_vote_main_pk = ProvingKey::build(k, &dao_vote_main_circuit);
+        let dao_exec_pk = ProvingKey::build(k, &dao_exec_circuit);
+
+        let pks = vec![
+            (MONEY_CONTRACT_ZKAS_MINT_NS_V1, money_mint_pk.clone()),
+            (MONEY_CONTRACT_ZKAS_BURN_NS_V1, money_burn_pk.clone()),
+            (DAO_CONTRACT_ZKAS_DAO_MINT_NS, dao_mint_pk.clone()),
+            (DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS, dao_propose_burn_pk.clone()),
+            (DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS, dao_propose_burn_pk.clone()),
+            (DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS, dao_propose_burn_pk.clone()),
+            (DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS, dao_propose_burn_pk.clone()),
+            (DAO_CONTRACT_ZKAS_DAO_EXEC_NS, dao_propose_burn_pk.clone()),
+        ];
+        proving_keys.insert(dao_contract_id.inner().to_repr(), pks);
+
+        Ok(Self {
+            faucet_kp,
+            alice_kp,
+            bob_kp,
+            charlie_kp,
+            rachel_kp,
+            dao_kp,
+            alice_state,
+            money_contract_id,
+            dao_contract_id,
+            proving_keys,
+            money_mint_pk,
+            money_mint_zkbin,
+            money_burn_pk,
+            money_burn_zkbin,
+            dao_mint_zkbin,
+            dao_mint_pk,
+            dao_propose_burn_zkbin,
+            dao_propose_burn_pk,
+            dao_propose_main_zkbin,
+            dao_propose_main_pk,
+            dao_vote_burn_zkbin,
+            dao_vote_burn_pk,
+            dao_vote_main_zkbin,
+            dao_vote_main_pk,
+            dao_exec_zkbin,
+            dao_exec_pk,
+        })
+    }
+}

+ 0 - 134
src/contract/dao/tests/harness.rs

@@ -1,134 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2023 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::collections::HashMap;
-
-use darkfi::{
-    consensus::{
-        constants::{
-            TESTNET_BOOTSTRAP_TIMESTAMP, TESTNET_GENESIS_HASH_BYTES, TESTNET_GENESIS_TIMESTAMP,
-            TESTNET_INITIAL_DISTRIBUTION,
-        },
-        ValidatorState, ValidatorStatePtr,
-    },
-    wallet::WalletDb,
-    zk::{proof::ProvingKey, vm::ZkCircuit, vm_stack::empty_witnesses},
-    zkas::ZkBinary,
-    Result,
-};
-use darkfi_sdk::{
-    crypto::{
-        contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
-        ContractId, Keypair, MerkleTree,
-    },
-    db::SMART_CONTRACT_ZKAS_DB_NAME,
-    pasta::group::ff::PrimeField,
-};
-use darkfi_serial::serialize;
-use log::{info, warn};
-use rand::rngs::OsRng;
-
-use darkfi_dao_contract::DAO_CONTRACT_ZKAS_DAO_MINT_NS;
-
-pub fn init_logger() -> Result<()> {
-    let mut cfg = simplelog::ConfigBuilder::new();
-    cfg.add_filter_ignore("sled".to_string());
-    if let Err(_) = simplelog::TermLogger::init(
-        //simplelog::LevelFilter::Info,
-        simplelog::LevelFilter::Debug,
-        //simplelog::LevelFilter::Trace,
-        cfg.build(),
-        simplelog::TerminalMode::Mixed,
-        simplelog::ColorChoice::Auto,
-    ) {
-        warn!("Logger already initialized");
-    }
-
-    Ok(())
-}
-
-pub struct DaoTestHarness {
-    pub alice_kp: Keypair,
-    pub dao_kp: Keypair,
-    pub alice_state: ValidatorStatePtr,
-    pub alice_dao_merkle_tree: MerkleTree,
-    pub money_contract_id: ContractId,
-    pub dao_contract_id: ContractId,
-    pub proving_keys: HashMap<[u8; 32], Vec<(&'static str, ProvingKey)>>,
-    pub dao_mint_zkbin: ZkBinary,
-    pub dao_mint_pk: ProvingKey,
-}
-
-impl DaoTestHarness {
-    pub async fn new() -> Result<Self> {
-        let alice_kp = Keypair::random(&mut OsRng);
-        let dao_kp = Keypair::random(&mut OsRng);
-
-        let alice_wallet = WalletDb::new("sqlite::memory:", "foo").await?;
-
-        let alice_sled_db = sled::Config::new().temporary(true).open()?;
-
-        let alice_state = ValidatorState::new(
-            &alice_sled_db,
-            *TESTNET_BOOTSTRAP_TIMESTAMP,
-            *TESTNET_GENESIS_TIMESTAMP,
-            *TESTNET_GENESIS_HASH_BYTES,
-            *TESTNET_INITIAL_DISTRIBUTION,
-            alice_wallet,
-            vec![],
-            false,
-        )
-        .await?;
-
-        let alice_dao_merkle_tree = MerkleTree::new(100);
-
-        let money_contract_id = *MONEY_CONTRACT_ID;
-        let dao_contract_id = *DAO_CONTRACT_ID;
-
-        let alice_sled = alice_state.read().await.blockchain.sled_db.clone();
-        let db_handle = alice_state.read().await.blockchain.contracts.lookup(
-            &alice_sled,
-            &dao_contract_id,
-            SMART_CONTRACT_ZKAS_DB_NAME,
-        )?;
-
-        let dao_mint_zkbin = db_handle.get(&serialize(&DAO_CONTRACT_ZKAS_DAO_MINT_NS))?.unwrap();
-        info!("Decoding bincode");
-        let dao_mint_zkbin = ZkBinary::decode(&dao_mint_zkbin)?;
-        let dao_mint_witnesses = empty_witnesses(&dao_mint_zkbin);
-        let dao_mint_circuit = ZkCircuit::new(dao_mint_witnesses, dao_mint_zkbin.clone());
-
-        info!("Creating zk proving keys");
-        let k = 13;
-        let mut proving_keys = HashMap::<[u8; 32], Vec<(&str, ProvingKey)>>::new();
-        let dao_mint_pk = ProvingKey::build(k, &dao_mint_circuit);
-        let pks = vec![(DAO_CONTRACT_ZKAS_DAO_MINT_NS, dao_mint_pk.clone())];
-        proving_keys.insert(dao_contract_id.inner().to_repr(), pks);
-
-        Ok(Self {
-            alice_kp,
-            dao_kp,
-            alice_state,
-            alice_dao_merkle_tree,
-            money_contract_id,
-            dao_contract_id,
-            proving_keys,
-            dao_mint_zkbin,
-            dao_mint_pk,
-        })
-    }
-}

+ 795 - 20
src/contract/dao/tests/integration.rs

@@ -18,22 +18,42 @@
 
 use darkfi::{tx::Transaction, Result};
 use darkfi_sdk::{
-    crypto::{constants::MERKLE_DEPTH, MerkleNode, TokenId},
+    crypto::{
+        coin::Coin,
+        constants::MERKLE_DEPTH,
+        contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
+        keypair::Keypair,
+        pedersen::pedersen_commitment_u64,
+        poseidon_hash, MerkleNode, SecretKey, TokenId,
+    },
     incrementalmerkletree::{bridgetree::BridgeTree, Tree},
-    pasta::{group::ff::Field, pallas},
+    pasta::{
+        arithmetic::CurveAffine,
+        group::{ff::Field, Curve, Group},
+        pallas,
+    },
     tx::ContractCall,
 };
-use darkfi_serial::Encodable;
+use darkfi_serial::{Decodable, Encodable};
 use log::{debug, info};
 use rand::rngs::OsRng;
 
 use darkfi_dao_contract::{
-    client::{build_dao_mint_tx, MerkleTree},
-    DaoFunction,
+    dao_client::{build_dao_mint_tx, MerkleTree, WalletCache},
+    dao_exec_client, dao_propose_client, dao_vote_client, money_client, note, DaoFunction,
 };
 
-mod harness;
-use harness::{init_logger, DaoTestHarness};
+use darkfi_money_contract::{
+    client::{build_half_swap_tx, build_transfer_tx, EncryptedNote, OwnCoin},
+    state::MoneyTransferParams,
+    MoneyFunction,
+};
+
+mod dao_harness;
+use dao_harness::DaoTestHarness;
+
+mod money_harness;
+use money_harness::{init_logger, MoneyTestHarness};
 
 // TODO: Anonymity leaks in this proof of concept:
 //
@@ -43,19 +63,18 @@ use harness::{init_logger, DaoTestHarness};
 // TODO: strategize and cleanup Result/Error usage
 // TODO: fix up code doc
 
-// TODO: Commenting this test until it works properly
-//#[async_std::test]
+#[async_std::test]
 async fn integration_test() -> Result<()> {
     init_logger()?;
 
-    let mut th = DaoTestHarness::new().await?;
+    let mut dao_th = DaoTestHarness::new().await?;
 
     // Money parameters
-    //let xdrk_supply = 1_000_000;
-    //let xrdk_token_id = TokenId::from(pallas::Base::random(&mut OsRng));
+    let xdrk_supply = 1_000_000;
+    let xdrk_token_id = TokenId::from(pallas::Base::random(&mut OsRng));
 
     // Governance token parameters
-    //let gdrk_supply = 1_000_000;
+    let gdrk_supply = 1_000_000;
     let gdrk_token_id = TokenId::from(pallas::Base::random(&mut OsRng));
 
     // DAO parameters
@@ -64,6 +83,9 @@ async fn integration_test() -> Result<()> {
     let dao_approval_ratio_quot = 1;
     let dao_approval_ratio_base = 2;
 
+    // We use this to receive coins
+    let mut cache = WalletCache::new();
+
     // =======================================================
     // Dao::Mint
     //
@@ -82,11 +104,11 @@ async fn integration_test() -> Result<()> {
         dao_approval_ratio_quot,
         dao_approval_ratio_base,
         gdrk_token_id,
-        &th.dao_kp.public,
+        &dao_th.dao_kp.public,
         dao_bulla_blind,
-        &th.dao_kp.secret,
-        &th.dao_mint_zkbin,
-        &th.dao_mint_pk,
+        &dao_th.dao_kp.secret,
+        &dao_th.dao_mint_zkbin,
+        &dao_th.dao_mint_pk,
     )?;
 
     info!("[Alice] ==========================================");
@@ -94,7 +116,7 @@ async fn integration_test() -> Result<()> {
     info!("[Alice] ==========================================");
     let mut data = vec![DaoFunction::Mint as u8];
     params.encode(&mut data)?;
-    let calls = vec![ContractCall { contract_id: th.dao_contract_id, data }];
+    let calls = vec![ContractCall { contract_id: dao_th.dao_contract_id, data }];
     let proofs = vec![proofs];
     let mut tx = Transaction { calls, proofs, signatures: vec![] };
     let sigs = tx.create_sigs(&mut OsRng, &[])?;
@@ -103,7 +125,7 @@ async fn integration_test() -> Result<()> {
     info!("[Alice] ===============================");
     info!("[Alice] Executing Dao::Mint transaction");
     info!("[Alice] ===============================");
-    th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+    dao_th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
     // TODO: Witness and add to wallet merkle tree?
 
     let mut dao_tree = MerkleTree::new(100);
@@ -112,7 +134,8 @@ async fn integration_test() -> Result<()> {
         dao_tree.append(&node);
         dao_tree.witness().unwrap()
     };
-    debug!(target: "demo", "Created DAO bulla: {:?}", params.dao_bulla.inner());
+    let dao_bulla = params.dao_bulla;
+    debug!(target: "demo", "Created DAO bulla: {:?}", dao_bulla.inner());
 
     // =======================================================
     // Money::Transfer
@@ -122,5 +145,757 @@ async fn integration_test() -> Result<()> {
     // =======================================================
     debug!(target: "demo", "Stage 2. Minting treasury token");
 
+    cache.track(dao_th.dao_kp.secret);
+
+    // Address of deployed contract in our example is dao::exec::FUNC_ID
+    // This field is public, you can see it's being sent to a DAO
+    // but nothing else is visible.
+    //
+    // In the python code we wrote:
+    //
+    //   spend_hook = b"0xdao_ruleset"
+    //
+    // TODO: this should be the contract/func ID
+    let spend_hook = pallas::Base::from(110);
+    // The user_data can be a simple hash of the items passed into the ZK proof
+    // up to corresponding linked ZK proof to interpret however they need.
+    // In out case, it's the bulla for the DAO
+    let user_data = dao_bulla.inner();
+
+    let builder = money_client::Builder {
+        clear_inputs: vec![money_client::BuilderClearInputInfo {
+            value: xdrk_supply,
+            token_id: xdrk_token_id,
+            signature_secret: dao_th.faucet_kp.secret,
+        }],
+        inputs: vec![],
+        outputs: vec![money_client::BuilderOutputInfo {
+            value: xdrk_supply,
+            token_id: xdrk_token_id,
+            public: dao_th.dao_kp.public,
+            serial: pallas::Base::random(&mut OsRng),
+            coin_blind: pallas::Base::random(&mut OsRng),
+            spend_hook,
+            user_data,
+        }],
+    };
+    let (params, proofs) = builder.build(
+        &dao_th.money_mint_zkbin,
+        &dao_th.money_mint_pk,
+        &dao_th.money_burn_zkbin,
+        &dao_th.money_burn_pk,
+    )?;
+
+    let contract_id = *MONEY_CONTRACT_ID;
+
+    let mut data = vec![MoneyFunction::Transfer as u8];
+    params.encode(&mut data)?;
+    let calls = vec![ContractCall { contract_id, data }];
+    let proofs = vec![proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = tx.create_sigs(&mut OsRng, &vec![dao_th.faucet_kp.secret])?;
+    tx.signatures = vec![sigs];
+
+    dao_th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+
+    // Wallet stuff
+
+    // DAO reads the money received from the encrypted note
+    {
+        assert_eq!(tx.calls.len(), 1);
+        let calldata = &tx.calls[0].data;
+        let params_data = &calldata[1..];
+        let params: MoneyTransferParams = Decodable::decode(params_data)?;
+
+        for output in params.outputs {
+            let coin = output.coin;
+            let enc_note =
+                EncryptedNote { ciphertext: output.ciphertext, ephem_public: output.ephem_public };
+
+            let coin = Coin(coin);
+            cache.try_decrypt_note(coin, &enc_note);
+        }
+    }
+
+    let mut recv_coins = cache.get_received(&dao_th.dao_kp.secret);
+    assert_eq!(recv_coins.len(), 1);
+    let dao_recv_coin = recv_coins.pop().unwrap();
+    let treasury_note = dao_recv_coin.note;
+
+    // Check the actual coin received is valid before accepting it
+
+    let coords = dao_th.dao_kp.public.inner().to_affine().coordinates().unwrap();
+    let coin = poseidon_hash::<8>([
+        *coords.x(),
+        *coords.y(),
+        pallas::Base::from(treasury_note.value),
+        treasury_note.token_id.inner(),
+        treasury_note.serial,
+        treasury_note.spend_hook,
+        treasury_note.user_data,
+        treasury_note.coin_blind,
+    ]);
+    assert_eq!(coin, dao_recv_coin.coin.0);
+
+    assert_eq!(treasury_note.spend_hook, spend_hook);
+    assert_eq!(treasury_note.user_data, dao_bulla.inner());
+
+    debug!("DAO received a coin worth {} xDRK", treasury_note.value);
+
+    // =======================================================
+    // Money::Transfer
+    //
+    // Mint the governance token
+    // Send it to three hodlers
+    // =======================================================
+    debug!(target: "demo", "Stage 3. Minting governance token");
+
+    cache.track(dao_th.alice_kp.secret);
+    cache.track(dao_th.bob_kp.secret);
+    cache.track(dao_th.charlie_kp.secret);
+
+    // Spend hook and user data disabled
+    let spend_hook = pallas::Base::from(0);
+    let user_data = pallas::Base::from(0);
+
+    let output1 = money_client::BuilderOutputInfo {
+        value: 400000,
+        token_id: gdrk_token_id,
+        public: dao_th.alice_kp.public,
+        serial: pallas::Base::random(&mut OsRng),
+        coin_blind: pallas::Base::random(&mut OsRng),
+        spend_hook,
+        user_data,
+    };
+
+    let output2 = money_client::BuilderOutputInfo {
+        value: 400000,
+        token_id: gdrk_token_id,
+        public: dao_th.bob_kp.public,
+        serial: pallas::Base::random(&mut OsRng),
+        coin_blind: pallas::Base::random(&mut OsRng),
+        spend_hook,
+        user_data,
+    };
+
+    let output3 = money_client::BuilderOutputInfo {
+        value: 200000,
+        token_id: gdrk_token_id,
+        public: dao_th.charlie_kp.public,
+        serial: pallas::Base::random(&mut OsRng),
+        coin_blind: pallas::Base::random(&mut OsRng),
+        spend_hook,
+        user_data,
+    };
+
+    assert!(2 * 400000 + 200000 == gdrk_supply);
+
+    let builder = money_client::Builder {
+        clear_inputs: vec![money_client::BuilderClearInputInfo {
+            value: gdrk_supply,
+            token_id: gdrk_token_id,
+            // This might be different for various tokens but lets reuse it here
+            signature_secret: dao_th.faucet_kp.secret,
+        }],
+        inputs: vec![],
+        outputs: vec![output1, output2, output3],
+    };
+    let (params, proofs) = builder.build(
+        &dao_th.money_mint_zkbin,
+        &dao_th.money_mint_pk,
+        &dao_th.money_burn_zkbin,
+        &dao_th.money_burn_pk,
+    )?;
+
+    let contract_id = *MONEY_CONTRACT_ID;
+
+    let mut data = vec![MoneyFunction::Transfer as u8];
+    params.encode(&mut data)?;
+    let calls = vec![ContractCall { contract_id, data }];
+    let proofs = vec![proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = tx.create_sigs(&mut OsRng, &vec![dao_th.faucet_kp.secret])?;
+    tx.signatures = vec![sigs];
+
+    dao_th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+
+    // Wallet
+    {
+        assert_eq!(tx.calls.len(), 1);
+        let calldata = &tx.calls[0].data;
+        let params_data = &calldata[1..];
+        let params: MoneyTransferParams = Decodable::decode(params_data)?;
+
+        for output in params.outputs {
+            let coin = output.coin;
+            let enc_note =
+                EncryptedNote { ciphertext: output.ciphertext, ephem_public: output.ephem_public };
+            let coin = Coin(coin);
+            cache.try_decrypt_note(coin, &enc_note);
+        }
+    }
+
+    let gov_keypairs = vec![dao_th.alice_kp, dao_th.bob_kp, dao_th.charlie_kp];
+    let mut gov_recv = vec![None, None, None];
+    // Check that each person received one coin
+    for (i, key) in gov_keypairs.iter().enumerate() {
+        let gov_recv_coin = {
+            let mut recv_coins = cache.get_received(&key.secret);
+            assert_eq!(recv_coins.len(), 1);
+            let recv_coin = recv_coins.pop().unwrap();
+            let note = &recv_coin.note;
+
+            assert_eq!(note.token_id, gdrk_token_id);
+            // Normal payment
+            assert_eq!(note.spend_hook, pallas::Base::from(0));
+            assert_eq!(note.user_data, pallas::Base::from(0));
+
+            let (pub_x, pub_y) = key.public.xy();
+            let coin = poseidon_hash::<8>([
+                pub_x,
+                pub_y,
+                pallas::Base::from(note.value),
+                note.token_id.inner(),
+                note.serial,
+                note.spend_hook,
+                note.user_data,
+                note.coin_blind,
+            ]);
+            assert_eq!(coin, recv_coin.coin.0);
+
+            debug!("Holder{} received a coin worth {} gDRK", i, note.value);
+
+            recv_coin
+        };
+        gov_recv[i] = Some(gov_recv_coin);
+    }
+    // unwrap them for this demo
+    let gov_recv: Vec<_> = gov_recv.into_iter().map(|r| r.unwrap()).collect();
+
+    // =======================================================
+    // Dao::Propose
+    //
+    // Propose the vote
+    // In order to make a valid vote, first the proposer must
+    // meet a criteria for a minimum number of gov tokens
+    //
+    // DAO rules:
+    // 1. gov token IDs must match on all inputs
+    // 2. proposals must be submitted by minimum amount
+    // 3. all votes >= quorum
+    // 4. outcome > approval_ratio
+    // 5. structure of outputs
+    //   output 0: value and address
+    //   output 1: change address
+    // =======================================================
+    debug!(target: "demo", "Stage 4. Propose the vote");
+
+    // TODO: look into proposal expiry once time for voting has finished
+
+    let receiver_keypair = Keypair::random(&mut OsRng);
+
+    let (money_leaf_position, money_merkle_path) = {
+        let tree = &cache.tree;
+        let leaf_position = gov_recv[0].leaf_position;
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+        (leaf_position, merkle_path)
+    };
+
+    // TODO: is it possible for an invalid transfer() to be constructed on exec()?
+    //       need to look into this
+    let signature_secret = SecretKey::random(&mut OsRng);
+    let input = dao_propose_client::BuilderInput {
+        secret: dao_th.alice_kp.secret,
+        note: gov_recv[0].note.clone(),
+        leaf_position: money_leaf_position,
+        merkle_path: money_merkle_path,
+        signature_secret,
+    };
+
+    let (dao_merkle_path, dao_merkle_root) = {
+        let tree = &dao_tree;
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(dao_leaf_position, &root).unwrap();
+        (merkle_path, root)
+    };
+
+    let dao_params = dao_propose_client::DaoParams {
+        proposer_limit: dao_proposer_limit,
+        quorum: dao_quorum,
+        approval_ratio_base: dao_approval_ratio_base,
+        approval_ratio_quot: dao_approval_ratio_quot,
+        gov_token_id: gdrk_token_id,
+        public_key: dao_th.dao_kp.public,
+        bulla_blind: dao_bulla_blind,
+    };
+
+    let proposal = dao_propose_client::Proposal {
+        dest: receiver_keypair.public,
+        amount: 1000,
+        serial: pallas::Base::random(&mut OsRng),
+        token_id: xdrk_token_id,
+        blind: pallas::Base::random(&mut OsRng),
+    };
+
+    let builder = dao_propose_client::Builder {
+        inputs: vec![input],
+        proposal,
+        dao: dao_params.clone(),
+        dao_leaf_position,
+        dao_merkle_path,
+        dao_merkle_root,
+    };
+    let (params, proofs) = builder.build(
+        &dao_th.dao_propose_burn_zkbin,
+        &dao_th.dao_propose_burn_pk,
+        &dao_th.dao_propose_main_zkbin,
+        &dao_th.dao_propose_main_pk,
+    )?;
+
+    let contract_id = *DAO_CONTRACT_ID;
+
+    let mut data = vec![DaoFunction::Propose as u8];
+    params.encode(&mut data)?;
+    let calls = vec![ContractCall { contract_id, data }];
+    let proofs = vec![proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = tx.create_sigs(&mut OsRng, &vec![signature_secret])?;
+    tx.signatures = vec![sigs];
+
+    dao_th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+
+    //// Wallet
+
+    // Read received proposal
+    let (proposal, proposal_bulla) = {
+        // TODO: EncryptedNote should be accessible by wasm and put in the structs directly
+        let enc_note = note::EncryptedNote2 {
+            ciphertext: params.ciphertext,
+            ephem_public: params.ephem_public,
+        };
+        let note: dao_propose_client::Note = enc_note.decrypt(&dao_th.dao_kp.secret).unwrap();
+
+        // TODO: check it belongs to DAO bulla
+
+        // Return the proposal info
+        (note.proposal, params.proposal_bulla)
+    };
+    debug!(target: "demo", "Proposal now active!");
+    debug!(target: "demo", "  destination: {:?}", proposal.dest);
+    debug!(target: "demo", "  amount: {}", proposal.amount);
+    debug!(target: "demo", "  token_id: {:?}", proposal.token_id);
+    debug!(target: "demo", "  dao_bulla: {:?}", dao_bulla.inner());
+    debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla);
+
+    // =======================================================
+    // Proposal is accepted!
+    // Start the voting
+    // =======================================================
+
+    // Copying these schizo comments from python code:
+    // Lets the voting begin
+    // Voters have access to the proposal and dao data
+    //   vote_state = VoteState()
+    // We don't need to copy nullifier set because it is checked from gov_state
+    // in vote_state_transition() anyway
+    //
+    // TODO: what happens if voters don't unblind their vote
+    // Answer:
+    //   1. there is a time limit
+    //   2. both the MPC or users can unblind
+    //
+    // TODO: bug if I vote then send money, then we can double vote
+    // TODO: all timestamps missing
+    //       - timelock (future voting starts in 2 days)
+    // Fix: use nullifiers from money gov state only from
+    // beginning of gov period
+    // Cannot use nullifiers from before voting period
+
+    debug!(target: "demo", "Stage 5. Start voting");
+
+    // We were previously saving updates here for testing
+    // let mut updates = vec![];
+
+    // User 1: YES
+
+    let (money_leaf_position, money_merkle_path) = {
+        let tree = &cache.tree;
+        let leaf_position = gov_recv[0].leaf_position;
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+        (leaf_position, merkle_path)
+    };
+
+    let signature_secret = SecretKey::random(&mut OsRng);
+    let input = dao_vote_client::BuilderInput {
+        secret: dao_th.alice_kp.secret,
+        note: gov_recv[0].note.clone(),
+        leaf_position: money_leaf_position,
+        merkle_path: money_merkle_path,
+        signature_secret,
+    };
+
+    let vote_option: bool = true;
+    // assert!(vote_option || !vote_option); // wtf
+
+    // We create a new keypair to encrypt the vote.
+    // For the demo MVP, you can just use the dao_keypair secret
+    let vote_keypair_1 = Keypair::random(&mut OsRng);
+
+    let builder = dao_vote_client::Builder {
+        inputs: vec![input],
+        vote: dao_vote_client::Vote {
+            vote_option,
+            vote_option_blind: pallas::Scalar::random(&mut OsRng),
+        },
+        vote_keypair: vote_keypair_1,
+        proposal: proposal.clone(),
+        dao: dao_params.clone(),
+    };
+    let (params, proofs) = builder.build(
+        &dao_th.dao_vote_burn_zkbin,
+        &dao_th.dao_vote_burn_pk,
+        &dao_th.dao_vote_main_zkbin,
+        &dao_th.dao_vote_main_pk,
+    )?;
+
+    let contract_id = *DAO_CONTRACT_ID;
+
+    let mut data = vec![DaoFunction::Vote as u8];
+    params.encode(&mut data)?;
+    let calls = vec![ContractCall { contract_id, data }];
+    let proofs = vec![proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = tx.create_sigs(&mut OsRng, &vec![signature_secret])?;
+    tx.signatures = vec![sigs];
+
+    dao_th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+
+    // 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 = {
+        // TODO: EncryptedNote should be accessible by wasm and put in the structs directly
+        let enc_note = note::EncryptedNote2 {
+            ciphertext: params.ciphertext,
+            ephem_public: params.ephem_public,
+        };
+        let note: dao_vote_client::Note = 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.vote_value);
+
+    // User 2: NO
+
+    let (money_leaf_position, money_merkle_path) = {
+        let tree = &cache.tree;
+        let leaf_position = gov_recv[1].leaf_position;
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+        (leaf_position, merkle_path)
+    };
+
+    let signature_secret = SecretKey::random(&mut OsRng);
+    let input = dao_vote_client::BuilderInput {
+        //secret: gov_keypair_2.secret,
+        secret: dao_th.bob_kp.secret,
+        note: gov_recv[1].note.clone(),
+        leaf_position: money_leaf_position,
+        merkle_path: money_merkle_path,
+        signature_secret,
+    };
+
+    let vote_option: bool = false;
+    // assert!(vote_option || !vote_option); // wtf
+
+    // We create a new keypair to encrypt the vote.
+    let vote_keypair_2 = Keypair::random(&mut OsRng);
+
+    let builder = dao_vote_client::Builder {
+        inputs: vec![input],
+        vote: dao_vote_client::Vote {
+            vote_option,
+            vote_option_blind: pallas::Scalar::random(&mut OsRng),
+        },
+        vote_keypair: vote_keypair_2,
+        proposal: proposal.clone(),
+        dao: dao_params.clone(),
+    };
+    let (params, proofs) = builder.build(
+        &dao_th.dao_vote_burn_zkbin,
+        &dao_th.dao_vote_burn_pk,
+        &dao_th.dao_vote_main_zkbin,
+        &dao_th.dao_vote_main_pk,
+    )?;
+
+    let contract_id = *DAO_CONTRACT_ID;
+
+    let mut data = vec![DaoFunction::Vote as u8];
+    params.encode(&mut data)?;
+    let calls = vec![ContractCall { contract_id, data }];
+    let proofs = vec![proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = tx.create_sigs(&mut OsRng, &vec![signature_secret])?;
+    tx.signatures = vec![sigs];
+
+    dao_th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+
+    let vote_note_2 = {
+        // TODO: EncryptedNote should be accessible by wasm and put in the structs directly
+        let enc_note = note::EncryptedNote2 {
+            ciphertext: params.ciphertext,
+            ephem_public: params.ephem_public,
+        };
+        let note: dao_vote_client::Note = enc_note.decrypt(&vote_keypair_2.secret).unwrap();
+        note
+    };
+    debug!(target: "demo", "User 2 voted!");
+    debug!(target: "demo", "  vote_option: {}", vote_note_2.vote.vote_option);
+    debug!(target: "demo", "  value: {}", vote_note_2.vote_value);
+
+    // User 3: YES
+
+    let (money_leaf_position, money_merkle_path) = {
+        let tree = &cache.tree;
+        let leaf_position = gov_recv[2].leaf_position;
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+        (leaf_position, merkle_path)
+    };
+
+    let signature_secret = SecretKey::random(&mut OsRng);
+    let input = dao_vote_client::BuilderInput {
+        //secret: gov_keypair_3.secret,
+        secret: dao_th.charlie_kp.secret,
+        note: gov_recv[2].note.clone(),
+        leaf_position: money_leaf_position,
+        merkle_path: money_merkle_path,
+        signature_secret,
+    };
+
+    let vote_option: bool = true;
+    // assert!(vote_option || !vote_option); // wtf
+
+    // We create a new keypair to encrypt the vote.
+    let vote_keypair_3 = Keypair::random(&mut OsRng);
+
+    let builder = dao_vote_client::Builder {
+        inputs: vec![input],
+        vote: dao_vote_client::Vote {
+            vote_option,
+            vote_option_blind: pallas::Scalar::random(&mut OsRng),
+        },
+        vote_keypair: vote_keypair_3,
+        proposal: proposal.clone(),
+        dao: dao_params.clone(),
+    };
+    let (params, proofs) = builder.build(
+        &dao_th.dao_vote_burn_zkbin,
+        &dao_th.dao_vote_burn_pk,
+        &dao_th.dao_vote_main_zkbin,
+        &dao_th.dao_vote_main_pk,
+    )?;
+
+    let contract_id = *DAO_CONTRACT_ID;
+
+    let mut data = vec![DaoFunction::Vote as u8];
+    params.encode(&mut data)?;
+    let calls = vec![ContractCall { contract_id, data }];
+    let proofs = vec![proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = tx.create_sigs(&mut OsRng, &vec![signature_secret])?;
+    tx.signatures = vec![sigs];
+
+    dao_th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+
+    // 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_3 = {
+        // TODO: EncryptedNote should be accessible by wasm and put in the structs directly
+        let enc_note = note::EncryptedNote2 {
+            ciphertext: params.ciphertext,
+            ephem_public: params.ephem_public,
+        };
+        let note: dao_vote_client::Note = enc_note.decrypt(&vote_keypair_3.secret).unwrap();
+        note
+    };
+    debug!(target: "demo", "User 3 voted!");
+    debug!(target: "demo", "  vote_option: {}", vote_note_3.vote.vote_option);
+    debug!(target: "demo", "  value: {}", vote_note_3.vote_value);
+
+    // Every votes produces a semi-homomorphic encryption of their vote.
+    // Which is either yes or no
+    // We copy the state tree for the governance token so coins can be used
+    // to vote on other proposals at the same time.
+    // With their vote, they produce a ZK proof + nullifier
+    // The votes are unblinded by MPC to a selected party at the end of the
+    // voting period.
+    // (that's if we want votes to be hidden during voting)
+
+    let mut yes_votes_value = 0;
+    let mut yes_votes_blind = pallas::Scalar::from(0);
+    let mut yes_votes_commit = pallas::Point::identity();
+
+    let mut all_votes_value = 0;
+    let mut all_votes_blind = pallas::Scalar::from(0);
+    let mut all_votes_commit = pallas::Point::identity();
+
+    // We were previously saving votes to a Vec<Update> for testing.
+    // However since Update is now UpdateBase it gets moved into update.apply().
+    // So we need to think of another way to run these tests.
+    //assert!(updates.len() == 3);
+
+    for (i, note /* update*/) in [vote_note_1, vote_note_2, vote_note_3]
+        .iter() /*.zip(updates)*/
+        .enumerate()
+    {
+        let vote_commit = pedersen_commitment_u64(note.vote_value, note.vote_value_blind);
+        //assert!(update.value_commit == all_vote_value_commit);
+        all_votes_commit += vote_commit;
+        all_votes_blind += note.vote_value_blind;
+
+        let yes_vote_commit = pedersen_commitment_u64(
+            note.vote.vote_option as u64 * note.vote_value,
+            note.vote.vote_option_blind,
+        );
+        //assert!(update.yes_vote_commit == yes_vote_commit);
+
+        yes_votes_commit += yes_vote_commit;
+        yes_votes_blind += note.vote.vote_option_blind;
+
+        let vote_option = note.vote.vote_option;
+
+        if vote_option {
+            yes_votes_value += note.vote_value;
+        }
+        all_votes_value += note.vote_value;
+        let vote_result: String = if vote_option { "yes".to_string() } else { "no".to_string() };
+
+        debug!("Voter {} voted {}", i, vote_result);
+    }
+
+    debug!("Outcome = {} / {}", yes_votes_value, all_votes_value);
+
+    assert!(all_votes_commit == pedersen_commitment_u64(all_votes_value, all_votes_blind));
+    assert!(yes_votes_commit == pedersen_commitment_u64(yes_votes_value, yes_votes_blind));
+
+    // =======================================================
+    // Execute the vote
+    // =======================================================
+
+    debug!(target: "demo", "Stage 6. Execute vote");
+
+    // Used to export user_data from this coin so it can be accessed by DAO::exec()
+    let user_data_blind = pallas::Base::random(&mut OsRng);
+
+    let user_serial = pallas::Base::random(&mut OsRng);
+    let user_coin_blind = pallas::Base::random(&mut OsRng);
+    let dao_serial = pallas::Base::random(&mut OsRng);
+    let dao_coin_blind = pallas::Base::random(&mut OsRng);
+    let input_value = treasury_note.value;
+    let input_value_blind = pallas::Scalar::random(&mut OsRng);
+    let tx_signature_secret = SecretKey::random(&mut OsRng);
+    let exec_signature_secret = SecretKey::random(&mut OsRng);
+
+    let (treasury_leaf_position, treasury_merkle_path) = {
+        let tree = &cache.tree;
+        let leaf_position = dao_recv_coin.leaf_position;
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+        (leaf_position, merkle_path)
+    };
+
+    let input = money_client::BuilderInputInfo {
+        leaf_position: treasury_leaf_position,
+        merkle_path: treasury_merkle_path,
+        secret: dao_th.dao_kp.secret,
+        note: treasury_note,
+        user_data_blind,
+        value_blind: input_value_blind,
+        signature_secret: tx_signature_secret,
+    };
+
+    // TODO: this should be the contract/func ID
+    //let spend_hook = pallas::Base::from(110);
+    let spend_hook = DAO_CONTRACT_ID.inner();
+    // The user_data can be a simple hash of the items passed into the ZK proof
+    // up to corresponding linked ZK proof to interpret however they need.
+    // In out case, it's the bulla for the DAO
+    let user_data = dao_bulla.inner();
+
+    let builder = money_client::Builder {
+        clear_inputs: vec![],
+        inputs: vec![input],
+        outputs: vec![
+            // Sending money
+            money_client::BuilderOutputInfo {
+                value: 1000,
+                token_id: xdrk_token_id,
+                //public: user_keypair.public,
+                public: receiver_keypair.public,
+                serial: proposal.serial,
+                coin_blind: proposal.blind,
+                spend_hook: pallas::Base::from(0),
+                user_data: pallas::Base::from(0),
+            },
+            // Change back to DAO
+            money_client::BuilderOutputInfo {
+                value: xdrk_supply - 1000,
+                token_id: xdrk_token_id,
+                public: dao_th.dao_kp.public,
+                serial: dao_serial,
+                coin_blind: dao_coin_blind,
+                spend_hook,
+                user_data,
+            },
+        ],
+    };
+    let (xfer_params, xfer_proofs) = builder.build(
+        &dao_th.money_mint_zkbin,
+        &dao_th.money_mint_pk,
+        &dao_th.money_burn_zkbin,
+        &dao_th.money_burn_pk,
+    )?;
+
+    let mut data = vec![MoneyFunction::Transfer as u8];
+    xfer_params.encode(&mut data)?;
+    let xfer_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
+
+    let builder = dao_exec_client::Builder {
+        proposal,
+        dao: dao_params.clone(),
+        yes_votes_value,
+        all_votes_value,
+        yes_votes_blind,
+        all_votes_blind,
+        user_serial,
+        user_coin_blind,
+        dao_serial,
+        dao_coin_blind,
+        input_value,
+        input_value_blind,
+        hook_dao_exec: spend_hook,
+        signature_secret: exec_signature_secret,
+    };
+    let (exec_params, mut exec_proofs) =
+        builder.build(&dao_th.dao_exec_zkbin, &dao_th.dao_exec_pk)?;
+
+    let mut data = vec![DaoFunction::Exec as u8];
+    exec_params.encode(&mut data)?;
+    let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
+
+    let calls = vec![xfer_call, exec_call];
+    let proofs = vec![xfer_proofs, exec_proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let xfer_sigs = tx.create_sigs(&mut OsRng, &vec![tx_signature_secret])?;
+    let exec_sigs = tx.create_sigs(&mut OsRng, &vec![exec_signature_secret])?;
+    tx.signatures = vec![xfer_sigs, exec_sigs];
+
+    dao_th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+
     Ok(())
 }

+ 1 - 0
src/contract/dao/tests/money_harness.rs

@@ -0,0 +1 @@
+../../money/tests/harness.rs

+ 26 - 7
src/contract/money/src/client.rs

@@ -47,12 +47,15 @@ use darkfi_sdk::{
     incrementalmerkletree::{bridgetree::BridgeTree, Hashable, Tree},
     pasta::{
         arithmetic::CurveAffine,
-        group::{ff::PrimeField, Curve},
+        group::{
+            ff::{Field, PrimeField},
+            Curve,
+        },
         pallas,
     },
 };
 use darkfi_serial::{serialize, Decodable, Encodable, SerialDecodable, SerialEncodable};
-use halo2_proofs::{arithmetic::Field, circuit::Value};
+use halo2_proofs::circuit::Value;
 use log::{debug, error, info};
 use rand::rngs::OsRng;
 
@@ -95,7 +98,7 @@ pub const AEAD_TAG_SIZE: usize = 16;
 
 /// The `Coin` is represented as a base field element.
 #[derive(Debug, Clone, Copy, Eq, PartialEq, SerialEncodable, SerialDecodable)]
-pub struct Coin(pallas::Base);
+pub struct Coin(pub pallas::Base);
 
 impl Coin {
     /// Reference the raw inner base field element
@@ -143,6 +146,11 @@ pub struct Note {
     pub value: u64,
     /// Token ID of the coin
     pub token_id: TokenId,
+    /// Spend hook used for protocol owned liquidity.
+    /// Specifies which contract owns this coin.
+    pub spend_hook: pallas::Base,
+    /// User data used by protocol when spend hook is enabled.
+    pub user_data: pallas::Base,
     /// Blinding factor for the coin bulla
     pub coin_blind: pallas::Base,
     /// Blinding factor for the value pedersen commitment
@@ -207,6 +215,9 @@ impl EncryptedNote {
     }
 }
 
+// TODO: we can put all these in an internal module like:
+// money_transfer::builder::ClearInputInfo
+
 struct TransactionBuilderClearInputInfo {
     pub value: u64,
     pub token_id: TokenId,
@@ -226,7 +237,7 @@ struct TransactionBuilderOutputInfo {
     pub public_key: PublicKey,
 }
 
-struct TransferBurnRevealed {
+pub struct TransferBurnRevealed {
     pub value_commit: ValueCommit,
     pub token_commit: ValueCommit,
     pub nullifier: Nullifier,
@@ -321,7 +332,7 @@ impl TransferBurnRevealed {
     }
 }
 
-struct TransferMintRevealed {
+pub struct TransferMintRevealed {
     pub coin: Coin,
     pub value_commit: ValueCommit,
     pub token_commit: ValueCommit,
@@ -376,7 +387,7 @@ impl TransferMintRevealed {
 }
 
 #[allow(clippy::too_many_arguments)]
-fn create_transfer_mint_proof(
+pub fn create_transfer_mint_proof(
     zkbin: &ZkBinary,
     pk: &ProvingKey,
     value: u64,
@@ -424,7 +435,7 @@ fn create_transfer_mint_proof(
 }
 
 #[allow(clippy::too_many_arguments)]
-fn create_transfer_burn_proof(
+pub fn create_transfer_burn_proof(
     zkbin: &ZkBinary,
     pk: &ProvingKey,
     value: u64,
@@ -799,6 +810,8 @@ pub fn build_half_swap_tx(
         serial,
         value: output.value,
         token_id: output.token_id,
+        spend_hook: pallas::Base::zero(),
+        user_data: pallas::Base::zero(),
         coin_blind,
         value_blind: value_recv_blind,
         token_blind: token_recv_blind,
@@ -1025,6 +1038,8 @@ pub fn build_transfer_tx(
             serial,
             value: output.value,
             token_id: output.token_id,
+            spend_hook: pallas::Base::zero(),
+            user_data: pallas::Base::zero(),
             coin_blind,
             value_blind,
             token_blind,
@@ -1236,6 +1251,8 @@ pub fn build_unstake_tx(
             serial,
             value: coin.value,
             token_id: token_id_recv,
+            spend_hook: pallas::Base::zero(),
+            user_data: pallas::Base::zero(),
             coin_blind,
             value_blind,
             token_blind: token_recv_blind,
@@ -1290,6 +1307,8 @@ mod tests {
             serial: pallas::Base::random(&mut OsRng),
             value: 100,
             token_id: TokenId::from(pallas::Base::random(&mut OsRng)),
+            spend_hook: pallas::Base::zero(),
+            user_data: pallas::Base::zero(),
             coin_blind: pallas::Base::random(&mut OsRng),
             value_blind: pallas::Scalar::random(&mut OsRng),
             token_blind: pallas::Scalar::random(&mut OsRng),

+ 23 - 2
src/contract/money/tests/harness.rs

@@ -54,8 +54,8 @@ pub fn init_logger() -> Result<()> {
     let mut cfg = simplelog::ConfigBuilder::new();
     cfg.add_filter_ignore("sled".to_string());
     if let Err(_) = simplelog::TermLogger::init(
-        simplelog::LevelFilter::Info,
-        //simplelog::LevelFilter::Debug,
+        //simplelog::LevelFilter::Info,
+        simplelog::LevelFilter::Debug,
         //simplelog::LevelFilter::Trace,
         cfg.build(),
         simplelog::TerminalMode::Mixed,
@@ -71,10 +71,12 @@ pub struct MoneyTestHarness {
     pub faucet_kp: Keypair,
     pub alice_kp: Keypair,
     pub bob_kp: Keypair,
+    pub charlie_kp: Keypair,
     pub faucet_pubkeys: Vec<PublicKey>,
     pub faucet_state: ValidatorStatePtr,
     pub alice_state: ValidatorStatePtr,
     pub bob_state: ValidatorStatePtr,
+    pub charlie_state: ValidatorStatePtr,
     pub money_contract_id: ContractId,
     pub proving_keys: HashMap<[u8; 32], Vec<(&'static str, ProvingKey)>>,
     pub mint_zkbin: ZkBinary,
@@ -84,6 +86,7 @@ pub struct MoneyTestHarness {
     pub faucet_merkle_tree: BridgeTree<MerkleNode, MERKLE_DEPTH>,
     pub alice_merkle_tree: BridgeTree<MerkleNode, MERKLE_DEPTH>,
     pub bob_merkle_tree: BridgeTree<MerkleNode, MERKLE_DEPTH>,
+    pub charlie_merkle_tree: BridgeTree<MerkleNode, MERKLE_DEPTH>,
 }
 
 impl MoneyTestHarness {
@@ -91,15 +94,18 @@ impl MoneyTestHarness {
         let faucet_kp = Keypair::random(&mut OsRng);
         let alice_kp = Keypair::random(&mut OsRng);
         let bob_kp = Keypair::random(&mut OsRng);
+        let charlie_kp = Keypair::random(&mut OsRng);
         let faucet_pubkeys = vec![faucet_kp.public];
 
         let faucet_wallet = WalletDb::new("sqlite::memory:", "foo").await?;
         let alice_wallet = WalletDb::new("sqlite::memory:", "foo").await?;
         let bob_wallet = WalletDb::new("sqlite::memory:", "foo").await?;
+        let charlie_wallet = WalletDb::new("sqlite::memory:", "foo").await?;
 
         let faucet_sled_db = sled::Config::new().temporary(true).open()?;
         let alice_sled_db = sled::Config::new().temporary(true).open()?;
         let bob_sled_db = sled::Config::new().temporary(true).open()?;
+        let charlie_sled_db = sled::Config::new().temporary(true).open()?;
 
         let faucet_state = ValidatorState::new(
             &faucet_sled_db,
@@ -137,6 +143,17 @@ impl MoneyTestHarness {
         )
         .await?;
 
+        let charlie_state = ValidatorState::new(
+            &charlie_sled_db,
+            *TESTNET_BOOTSTRAP_TIMESTAMP,
+            *TESTNET_GENESIS_TIMESTAMP,
+            *TESTNET_GENESIS_HASH_BYTES,
+            charlie_wallet,
+            faucet_pubkeys.clone(),
+            false,
+        )
+        .await?;
+
         let money_contract_id = *MONEY_CONTRACT_ID;
 
         let alice_sled = alice_state.read().await.blockchain.sled_db.clone();
@@ -170,15 +187,18 @@ impl MoneyTestHarness {
         let faucet_merkle_tree = BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(100);
         let alice_merkle_tree = BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(100);
         let bob_merkle_tree = BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(100);
+        let charlie_merkle_tree = BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(100);
 
         Ok(Self {
             faucet_kp,
             alice_kp,
             bob_kp,
+            charlie_kp,
             faucet_pubkeys,
             faucet_state,
             alice_state,
             bob_state,
+            charlie_state,
             money_contract_id,
             proving_keys,
             mint_pk,
@@ -188,6 +208,7 @@ impl MoneyTestHarness {
             faucet_merkle_tree,
             alice_merkle_tree,
             bob_merkle_tree,
+            charlie_merkle_tree,
         })
     }
 

+ 1 - 1
src/sdk/src/crypto/coin.rs

@@ -25,7 +25,7 @@ use pasta_curves::{group::ff::PrimeField, pallas};
 /// The `Coin` is represented as a base field element.
 #[repr(C)]
 #[derive(Debug, Clone, Copy, Eq, PartialEq, SerialEncodable, SerialDecodable)]
-pub struct Coin(pallas::Base);
+pub struct Coin(pub pallas::Base);
 
 impl Coin {
     /// Reference the raw inner base field element