x 3 lat temu
rodzic
commit
24f57a1b24

+ 1 - 1
src/contract/dao/src/dao_client.rs

@@ -51,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 {

+ 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))
+    }
+}

+ 3 - 0
src/contract/dao/src/lib.rs

@@ -29,6 +29,9 @@ pub mod note;
 #[cfg(feature = "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 money contract

+ 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 {

+ 64 - 3
src/contract/dao/tests/dao_harness.rs

@@ -41,7 +41,11 @@ use darkfi_serial::serialize;
 use log::{info, warn};
 use rand::rngs::OsRng;
 
-use darkfi_dao_contract::DAO_CONTRACT_ZKAS_DAO_MINT_NS;
+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 {
     pub alice_kp: Keypair,
@@ -53,6 +57,10 @@ pub struct DaoTestHarness {
     pub proving_keys: HashMap<[u8; 32], Vec<(&'static str, 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,
 }
 
 impl DaoTestHarness {
@@ -87,17 +95,66 @@ impl DaoTestHarness {
             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 = 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 =
+            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 =
+            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 =
+            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 =
+            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 = 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 dao_mint_pk = ProvingKey::build(k, &dao_mint_circuit);
-        let pks = vec![(DAO_CONTRACT_ZKAS_DAO_MINT_NS, dao_mint_pk.clone())];
+        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![
+            (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 {
@@ -110,6 +167,10 @@ impl DaoTestHarness {
             proving_keys,
             dao_mint_zkbin,
             dao_mint_pk,
+            dao_propose_burn_zkbin,
+            dao_propose_burn_pk,
+            dao_propose_main_zkbin,
+            dao_propose_main_pk,
         })
     }
 }

+ 66 - 4
src/contract/dao/tests/integration.rs

@@ -19,8 +19,8 @@
 use darkfi::{tx::Transaction, Result};
 use darkfi_sdk::{
     crypto::{
-        coin::Coin, constants::MERKLE_DEPTH, contract_id::MONEY_CONTRACT_ID, poseidon_hash,
-        MerkleNode, TokenId,
+        coin::Coin, constants::MERKLE_DEPTH, contract_id::MONEY_CONTRACT_ID, keypair::Keypair,
+        poseidon_hash, MerkleNode, SecretKey, TokenId,
     },
     incrementalmerkletree::{bridgetree::BridgeTree, Tree},
     pasta::{
@@ -36,7 +36,7 @@ use rand::rngs::OsRng;
 
 use darkfi_dao_contract::{
     dao_client::{build_dao_mint_tx, MerkleTree, WalletCache},
-    money_client, DaoFunction,
+    dao_propose_client, money_client, DaoFunction,
 };
 
 use darkfi_money_contract::{
@@ -59,7 +59,6 @@ use money_harness::{init_logger, MoneyTestHarness};
 // TODO: strategize and cleanup Result/Error usage
 // TODO: fix up code doc
 
-// TODO: Commenting this test until it works properly
 #[async_std::test]
 async fn integration_test() -> Result<()> {
     init_logger()?;
@@ -390,5 +389,68 @@ async fn integration_test() -> Result<()> {
     // =======================================================
     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: money_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,
+    )?;
+
     Ok(())
 }