diff --git a/example/dao/contract/dao_contract/propose/validate.rs b/example/dao/contract/dao_contract/propose/validate.rs deleted file mode 100644 index 9be422d71..000000000 --- a/example/dao/contract/dao_contract/propose/validate.rs +++ /dev/null @@ -1,174 +0,0 @@ -use std::any::{Any, TypeId}; - -use darkfi_sdk::crypto::MerkleNode; -use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable}; -use log::error; -use pasta_curves::{ - arithmetic::CurveAffine, - group::{Curve, Group}, - pallas, -}; - -use darkfi::{ - crypto::{keypair::PublicKey, types::DrkCircuitField}, - Error as DarkFiError, -}; - -use crate::{ - contract::{ - dao_contract, dao_contract::State as DaoState, money_contract, - money_contract::state::State as MoneyState, - }, - note::EncryptedNote2, - util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, -}; - -// used for debugging -// const TARGET: &str = "dao_contract::propose::validate::state_transition()"; - -#[derive(Debug, Clone, thiserror::Error)] -pub enum Error { - #[error("Invalid input merkle root")] - InvalidInputMerkleRoot, - - #[error("Invalid DAO merkle root")] - InvalidDaoMerkleRoot, - - #[error("DarkFi error: {0}")] - DarkFiError(String), -} -type Result = std::result::Result; - -impl From for Error { - fn from(err: DarkFiError) -> Self { - Self::DarkFiError(err.to_string()) - } -} - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct CallData { - pub header: Header, - pub inputs: Vec, -} - -impl CallDataBase for CallData { - fn zk_public_values(&self) -> Vec<(String, Vec)> { - let mut zk_publics = Vec::new(); - let mut total_funds_commit = pallas::Point::identity(); - - assert!(!self.inputs.is_empty(), "inputs length cannot be zero"); - for input in &self.inputs { - total_funds_commit += input.value_commit; - let value_coords = input.value_commit.to_affine().coordinates().unwrap(); - - let sigpub_coords = input.signature_public.0.to_affine().coordinates().unwrap(); - - zk_publics.push(( - "dao-propose-burn".to_string(), - vec![ - *value_coords.x(), - *value_coords.y(), - self.header.token_commit, - input.merkle_root.inner(), - *sigpub_coords.x(), - *sigpub_coords.y(), - ], - )); - } - - let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap(); - zk_publics.push(( - "dao-propose-main".to_string(), - vec![ - self.header.token_commit, - self.header.dao_merkle_root.inner(), - self.header.proposal_bulla, - *total_funds_coords.x(), - *total_funds_coords.y(), - ], - )); - - zk_publics - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn signature_public_keys(&self) -> Vec { - let mut signature_public_keys = vec![]; - for input in self.inputs.clone() { - signature_public_keys.push(input.signature_public); - } - signature_public_keys - } - - fn encode_bytes( - &self, - mut writer: &mut dyn std::io::Write, - ) -> core::result::Result { - self.encode(&mut writer) - } -} - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct Header { - pub dao_merkle_root: MerkleNode, - pub token_commit: pallas::Base, - pub proposal_bulla: pallas::Base, - pub enc_note: EncryptedNote2, -} - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct Input { - pub value_commit: pallas::Point, - pub merkle_root: MerkleNode, - pub signature_public: PublicKey, -} - -pub fn state_transition( - states: &StateRegistry, - func_call_index: usize, - parent_tx: &Transaction, -) -> Result> { - let func_call = &parent_tx.func_calls[func_call_index]; - let call_data = func_call.call_data.as_any(); - - assert_eq!((*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::(); - - // This will be inside wasm so unwrap is fine. - let call_data = call_data.unwrap(); - - // Check the merkle roots for the input coins are valid - for input in &call_data.inputs { - let money_state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - if !money_state.is_valid_merkle(&input.merkle_root) { - return Err(Error::InvalidInputMerkleRoot) - } - } - - let state = states.lookup::(*dao_contract::CONTRACT_ID).unwrap(); - - // Is the DAO bulla generated in the ZK proof valid - if !state.is_valid_dao_merkle(&call_data.header.dao_merkle_root) { - return Err(Error::InvalidDaoMerkleRoot) - } - - // TODO: look at gov tokens avoid using already spent ones - // Need to spend original coin and generate 2 nullifiers? - - Ok(Box::new(Update { proposal_bulla: call_data.header.proposal_bulla })) -} - -#[derive(Clone)] -pub struct Update { - pub proposal_bulla: pallas::Base, -} - -impl UpdateBase for Update { - fn apply(self: Box, states: &mut StateRegistry) { - let state = states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); - state.add_proposal_bulla(self.proposal_bulla); - } -} diff --git a/example/dao/contract/dao_contract/propose/wallet.rs b/example/dao/contract/dao_contract/propose/wallet.rs deleted file mode 100644 index 5deaae0ae..000000000 --- a/example/dao/contract/dao_contract/propose/wallet.rs +++ /dev/null @@ -1,272 +0,0 @@ -use darkfi_sdk::crypto::MerkleNode; -use darkfi_serial::{SerialDecodable, SerialEncodable}; -use halo2_proofs::circuit::Value; -use incrementalmerkletree::Hashable; -use pasta_curves::{ - arithmetic::CurveAffine, - group::{ff::Field, Curve}, - pallas, -}; -use rand::rngs::OsRng; - -use darkfi::{ - crypto::{ - keypair::{PublicKey, SecretKey}, - util::{pedersen_commitment_u64, poseidon_hash}, - Proof, - }, - zk::vm::{Witness, ZkCircuit}, -}; - -use crate::{ - contract::{ - dao_contract::{ - mint::wallet::DaoParams, - propose::validate::{CallData, Header, Input}, - CONTRACT_ID, - }, - money_contract, - }, - note, - util::{FuncCall, ZkContractInfo, ZkContractTable}, -}; - -#[derive(SerialEncodable, SerialDecodable)] -pub struct Note { - pub proposal: Proposal, -} - -pub struct BuilderInput { - pub secret: SecretKey, - pub note: money_contract::transfer::wallet::Note, - pub leaf_position: incrementalmerkletree::Position, - pub merkle_path: Vec, - pub signature_secret: SecretKey, -} - -#[derive(SerialEncodable, SerialDecodable, Clone)] -pub struct Proposal { - pub dest: PublicKey, - pub amount: u64, - pub serial: pallas::Base, - pub token_id: pallas::Base, - pub blind: pallas::Base, -} - -pub struct Builder { - pub inputs: Vec, - pub proposal: Proposal, - pub dao: DaoParams, - pub dao_leaf_position: incrementalmerkletree::Position, - pub dao_merkle_path: Vec, - pub dao_merkle_root: MerkleNode, -} - -impl Builder { - pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall { - let mut proofs = vec![]; - - let gov_token_blind = pallas::Base::random(&mut OsRng); - - let mut inputs = vec![]; - let mut total_funds = 0; - let mut total_funds_blinds = pallas::Scalar::from(0); - - for input in self.inputs { - let funds_blind = pallas::Scalar::random(&mut OsRng); - total_funds += input.note.value; - total_funds_blinds += funds_blind; - - let signature_public = PublicKey::from_secret(input.signature_secret); - - let zk_info = zk_bins.lookup(&"dao-propose-burn".to_string()).unwrap(); - let zk_info = if let ZkContractInfo::Binary(info) = zk_info { - info - } else { - panic!("Not binary info") - }; - let zk_bin = zk_info.bincode.clone(); - - // Note from the previous output - let note = input.note; - let leaf_pos: u64 = input.leaf_position.into(); - - let prover_witnesses = vec![ - Witness::Base(Value::known(input.secret.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)), - 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 coords = public_key.0.to_affine().coordinates().unwrap(); - - let coin = poseidon_hash::<8>([ - *coords.x(), - *coords.y(), - pallas::Base::from(note.value), - note.token_id, - note.serial, - pallas::Base::from(0), - pallas::Base::from(0), - note.coin_blind, - ]); - - let merkle_root = { - let position: u64 = input.leaf_position.into(); - let mut current = MerkleNode::from(coin); - for (level, sibling) in input.merkle_path.iter().enumerate() { - let level = level as u8; - current = if position & (1 << level) == 0 { - MerkleNode::combine(level.into(), ¤t, sibling) - } else { - MerkleNode::combine(level.into(), sibling, ¤t) - }; - } - current - }; - - let token_commit = poseidon_hash::<2>([note.token_id, gov_token_blind]); - assert_eq!(self.dao.gov_token_id, note.token_id); - - let value_commit = pedersen_commitment_u64(note.value, funds_blind); - let value_coords = value_commit.to_affine().coordinates().unwrap(); - - let sigpub_coords = signature_public.0.to_affine().coordinates().unwrap(); - - let public_inputs = vec![ - *value_coords.x(), - *value_coords.y(), - token_commit, - merkle_root.inner(), - *sigpub_coords.x(), - *sigpub_coords.y(), - ]; - let circuit = ZkCircuit::new(prover_witnesses, zk_bin); - - let proving_key = &zk_info.proving_key; - let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) - .expect("DAO::propose() proving error!"); - proofs.push(input_proof); - - let input = Input { value_commit, merkle_root, signature_public }; - inputs.push(input); - } - - let total_funds_commit = pedersen_commitment_u64(total_funds, total_funds_blinds); - let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap(); - let total_funds = pallas::Base::from(total_funds); - - let token_commit = poseidon_hash::<2>([self.dao.gov_token_id, gov_token_blind]); - - let proposal_dest_coords = self.proposal.dest.0.to_affine().coordinates().unwrap(); - let proposal_dest_x = *proposal_dest_coords.x(); - let proposal_dest_y = *proposal_dest_coords.y(); - - let proposal_amount = pallas::Base::from(self.proposal.amount); - - let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit); - let dao_quorum = pallas::Base::from(self.dao.quorum); - let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot); - let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base); - - let dao_pubkey_coords = self.dao.public_key.0.to_affine().coordinates().unwrap(); - - let dao_bulla = poseidon_hash::<8>([ - dao_proposer_limit, - dao_quorum, - dao_approval_ratio_quot, - dao_approval_ratio_base, - self.dao.gov_token_id, - *dao_pubkey_coords.x(), - *dao_pubkey_coords.y(), - self.dao.bulla_blind, - ]); - - let dao_leaf_position: u64 = self.dao_leaf_position.into(); - - let proposal_bulla = poseidon_hash::<8>([ - proposal_dest_x, - proposal_dest_y, - proposal_amount, - self.proposal.serial, - self.proposal.token_id, - dao_bulla, - self.proposal.blind, - // @tmp-workaround - self.proposal.blind, - ]); - - let zk_info = zk_bins.lookup(&"dao-propose-main".to_string()).unwrap(); - let zk_info = if let ZkContractInfo::Binary(info) = zk_info { - info - } else { - panic!("Not binary info") - }; - let zk_bin = zk_info.bincode.clone(); - let prover_witnesses = vec![ - // Proposers total number of gov tokens - Witness::Base(Value::known(total_funds)), - Witness::Scalar(Value::known(total_funds_blinds)), - // Used for blinding exported gov token ID - Witness::Base(Value::known(gov_token_blind)), - // proposal params - Witness::Base(Value::known(proposal_dest_x)), - Witness::Base(Value::known(proposal_dest_y)), - Witness::Base(Value::known(proposal_amount)), - Witness::Base(Value::known(self.proposal.serial)), - Witness::Base(Value::known(self.proposal.token_id)), - Witness::Base(Value::known(self.proposal.blind)), - // DAO params - Witness::Base(Value::known(dao_proposer_limit)), - Witness::Base(Value::known(dao_quorum)), - Witness::Base(Value::known(dao_approval_ratio_quot)), - Witness::Base(Value::known(dao_approval_ratio_base)), - Witness::Base(Value::known(self.dao.gov_token_id)), - Witness::Base(Value::known(*dao_pubkey_coords.x())), - Witness::Base(Value::known(*dao_pubkey_coords.y())), - Witness::Base(Value::known(self.dao.bulla_blind)), - Witness::Uint32(Value::known(dao_leaf_position.try_into().unwrap())), - Witness::MerklePath(Value::known(self.dao_merkle_path.try_into().unwrap())), - ]; - let public_inputs = vec![ - token_commit, - self.dao_merkle_root.inner(), - proposal_bulla, - *total_funds_coords.x(), - *total_funds_coords.y(), - ]; - let circuit = ZkCircuit::new(prover_witnesses, zk_bin); - - let proving_key = &zk_info.proving_key; - let main_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) - .expect("DAO::propose() proving error!"); - proofs.push(main_proof); - - let note = Note { proposal: self.proposal }; - let enc_note = note::encrypt(¬e, &self.dao.public_key).unwrap(); - let header = Header { - dao_merkle_root: self.dao_merkle_root, - proposal_bulla, - token_commit, - enc_note, - }; - - let call_data = CallData { header, inputs }; - - FuncCall { - contract_id: *CONTRACT_ID, - func_id: *super::FUNC_ID, - call_data: Box::new(call_data), - proofs, - } - } -} diff --git a/example/dao/contract/dao_contract/state.rs b/example/dao/contract/dao_contract/state.rs deleted file mode 100644 index b4b77dd9a..000000000 --- a/example/dao/contract/dao_contract/state.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::{any::Any, collections::HashMap}; - -use darkfi_sdk::crypto::{constants::MERKLE_DEPTH, MerkleNode, Nullifier}; -use darkfi_serial::{SerialDecodable, SerialEncodable}; -use incrementalmerkletree::{bridgetree::BridgeTree, Tree}; -use pasta_curves::{group::Group, pallas}; - -use crate::util::HashableBase; - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct DaoBulla(pub pallas::Base); - -type MerkleTree = BridgeTree; - -pub struct ProposalVotes { - // TODO: might be more logical to have 'yes_votes_commit' and 'no_votes_commit' - /// Weighted vote commit - pub yes_votes_commit: pallas::Point, - /// All value staked in the vote - pub all_votes_commit: pallas::Point, - /// Vote nullifiers - pub vote_nulls: Vec, -} - -impl ProposalVotes { - pub fn nullifier_exists(&self, nullifier: &Nullifier) -> bool { - self.vote_nulls.iter().any(|n| n == nullifier) - } -} - -/// This DAO state is for all DAOs on the network. There should only be a single instance. -pub struct State { - dao_bullas: Vec, - pub dao_tree: MerkleTree, - pub dao_roots: Vec, - - //proposal_bullas: Vec, - pub proposal_tree: MerkleTree, - pub proposal_roots: Vec, - pub proposal_votes: HashMap, -} - -impl State { - pub fn new() -> Box { - Box::new(Self { - dao_bullas: Vec::new(), - dao_tree: MerkleTree::new(100), - dao_roots: Vec::new(), - //proposal_bullas: Vec::new(), - proposal_tree: MerkleTree::new(100), - proposal_roots: Vec::new(), - proposal_votes: HashMap::new(), - }) - } - - pub fn add_dao_bulla(&mut self, bulla: DaoBulla) { - let node = MerkleNode::from(bulla.0); - self.dao_bullas.push(bulla); - self.dao_tree.append(&node); - self.dao_roots.push(self.dao_tree.root(0).unwrap()); - } - - pub fn add_proposal_bulla(&mut self, bulla: pallas::Base) { - let node = MerkleNode::from(bulla); - //self.proposal_bullas.push(bulla); - self.proposal_tree.append(&node); - self.proposal_roots.push(self.proposal_tree.root(0).unwrap()); - self.proposal_votes.insert( - HashableBase(bulla), - ProposalVotes { - yes_votes_commit: pallas::Point::identity(), - all_votes_commit: pallas::Point::identity(), - vote_nulls: Vec::new(), - }, - ); - } - - pub fn lookup_proposal_votes(&self, proposal_bulla: pallas::Base) -> Option<&ProposalVotes> { - self.proposal_votes.get(&HashableBase(proposal_bulla)) - } - pub fn lookup_proposal_votes_mut( - &mut self, - proposal_bulla: pallas::Base, - ) -> Option<&mut ProposalVotes> { - self.proposal_votes.get_mut(&HashableBase(proposal_bulla)) - } - - pub fn is_valid_dao_merkle(&self, root: &MerkleNode) -> bool { - self.dao_roots.iter().any(|m| m == root) - } - - // TODO: This never gets called. - pub fn _is_valid_proposal_merkle(&self, root: &MerkleNode) -> bool { - self.proposal_roots.iter().any(|m| m == root) - } -} diff --git a/example/dao/contract/dao_contract/vote/validate.rs b/example/dao/contract/dao_contract/vote/validate.rs deleted file mode 100644 index 7fd60cd1f..000000000 --- a/example/dao/contract/dao_contract/vote/validate.rs +++ /dev/null @@ -1,207 +0,0 @@ -use std::any::{Any, TypeId}; - -use darkfi_sdk::crypto::{MerkleNode, Nullifier}; -use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable}; -use log::error; -use pasta_curves::{ - arithmetic::CurveAffine, - group::{Curve, Group}, - pallas, -}; - -use darkfi::{ - crypto::{keypair::PublicKey, types::DrkCircuitField}, - Error as DarkFiError, -}; - -use crate::{ - contract::{ - dao_contract, dao_contract::State as DaoState, money_contract, - money_contract::state::State as MoneyState, - }, - note::EncryptedNote2, - util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, -}; - -#[derive(Debug, Clone, thiserror::Error)] -pub enum Error { - #[error("Invalid proposal")] - InvalidProposal, - - #[error("Voting with already spent coinage")] - SpentCoin, - - #[error("Double voting")] - DoubleVote, - - #[error("Invalid input merkle root")] - InvalidInputMerkleRoot, - - #[error("DarkFi error: {0}")] - DarkFiError(String), -} -type Result = std::result::Result; - -impl From for Error { - fn from(err: DarkFiError) -> Self { - Self::DarkFiError(err.to_string()) - } -} - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct CallData { - pub header: Header, - pub inputs: Vec, -} - -impl CallDataBase for CallData { - fn zk_public_values(&self) -> Vec<(String, Vec)> { - let mut zk_publics = Vec::new(); - let mut all_votes_commit = pallas::Point::identity(); - - assert!(!self.inputs.is_empty(), "inputs length cannot be zero"); - for input in &self.inputs { - all_votes_commit += input.vote_commit; - let value_coords = input.vote_commit.to_affine().coordinates().unwrap(); - - let sigpub_coords = input.signature_public.0.to_affine().coordinates().unwrap(); - - zk_publics.push(( - "dao-vote-burn".to_string(), - vec![ - input.nullifier.inner(), - *value_coords.x(), - *value_coords.y(), - self.header.token_commit, - input.merkle_root.inner(), - *sigpub_coords.x(), - *sigpub_coords.y(), - ], - )); - } - - let yes_vote_commit_coords = self.header.yes_vote_commit.to_affine().coordinates().unwrap(); - - let vote_commit_coords = all_votes_commit.to_affine().coordinates().unwrap(); - - zk_publics.push(( - "dao-vote-main".to_string(), - vec![ - self.header.token_commit, - self.header.proposal_bulla, - *yes_vote_commit_coords.x(), - *yes_vote_commit_coords.y(), - *vote_commit_coords.x(), - *vote_commit_coords.y(), - ], - )); - - zk_publics - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn signature_public_keys(&self) -> Vec { - let mut signature_public_keys = vec![]; - for input in self.inputs.clone() { - signature_public_keys.push(input.signature_public); - } - signature_public_keys - } - - fn encode_bytes( - &self, - mut writer: &mut dyn std::io::Write, - ) -> core::result::Result { - self.encode(&mut writer) - } -} - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct Header { - pub token_commit: pallas::Base, - pub proposal_bulla: pallas::Base, - pub yes_vote_commit: pallas::Point, - pub enc_note: EncryptedNote2, -} - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct Input { - pub nullifier: Nullifier, - pub vote_commit: pallas::Point, - pub merkle_root: MerkleNode, - pub signature_public: PublicKey, -} - -pub fn state_transition( - states: &StateRegistry, - func_call_index: usize, - parent_tx: &Transaction, -) -> Result> { - let func_call = &parent_tx.func_calls[func_call_index]; - let call_data = func_call.call_data.as_any(); - - assert_eq!((*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::(); - - // This will be inside wasm so unwrap is fine. - let call_data = call_data.unwrap(); - - let dao_state = states.lookup::(*dao_contract::CONTRACT_ID).unwrap(); - - // Check proposal_bulla exists - let votes_info = dao_state.lookup_proposal_votes(call_data.header.proposal_bulla); - if votes_info.is_none() { - return Err(Error::InvalidProposal) - } - let votes_info = votes_info.unwrap(); - - // Check the merkle roots for the input coins are valid - let mut vote_nulls = Vec::new(); - let mut all_vote_commit = pallas::Point::identity(); - for input in &call_data.inputs { - let money_state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - if !money_state.is_valid_merkle(&input.merkle_root) { - return Err(Error::InvalidInputMerkleRoot) - } - - if money_state.nullifier_exists(&input.nullifier) { - return Err(Error::SpentCoin) - } - - if votes_info.nullifier_exists(&input.nullifier) { - return Err(Error::DoubleVote) - } - - all_vote_commit += input.vote_commit; - - vote_nulls.push(input.nullifier); - } - - Ok(Box::new(Update { - proposal_bulla: call_data.header.proposal_bulla, - vote_nulls, - yes_vote_commit: call_data.header.yes_vote_commit, - all_vote_commit, - })) -} - -#[derive(Clone)] -pub struct Update { - proposal_bulla: pallas::Base, - vote_nulls: Vec, - pub yes_vote_commit: pallas::Point, - pub all_vote_commit: pallas::Point, -} - -impl UpdateBase for Update { - fn apply(mut self: Box, states: &mut StateRegistry) { - let state = states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); - let votes_info = state.lookup_proposal_votes_mut(self.proposal_bulla).unwrap(); - votes_info.yes_votes_commit += self.yes_vote_commit; - votes_info.all_votes_commit += self.all_vote_commit; - votes_info.vote_nulls.append(&mut self.vote_nulls); - } -} diff --git a/example/dao/contract/dao_contract/vote/wallet.rs b/example/dao/contract/dao_contract/vote/wallet.rs deleted file mode 100644 index cf402fbaf..000000000 --- a/example/dao/contract/dao_contract/vote/wallet.rs +++ /dev/null @@ -1,287 +0,0 @@ -use darkfi_sdk::crypto::{MerkleNode, Nullifier}; -use darkfi_serial::{SerialDecodable, SerialEncodable}; -use halo2_proofs::circuit::Value; -use incrementalmerkletree::Hashable; -use log::debug; -use pasta_curves::{ - arithmetic::CurveAffine, - group::{ff::Field, Curve}, - pallas, -}; -use rand::rngs::OsRng; - -use darkfi::{ - crypto::{ - keypair::{Keypair, PublicKey, SecretKey}, - util::{pedersen_commitment_u64, poseidon_hash}, - Proof, - }, - zk::vm::{Witness, ZkCircuit}, -}; - -use crate::{ - contract::{ - dao_contract::{ - mint::wallet::DaoParams, - propose::wallet::Proposal, - vote::validate::{CallData, Header, Input}, - CONTRACT_ID, - }, - money_contract, - }, - note, - util::{FuncCall, ZkContractInfo, ZkContractTable}, -}; - -#[derive(SerialEncodable, SerialDecodable)] -pub struct Note { - pub vote: Vote, - pub vote_value: u64, - pub vote_value_blind: pallas::Scalar, -} - -#[derive(SerialEncodable, SerialDecodable)] -pub struct Vote { - pub vote_option: bool, - pub vote_option_blind: pallas::Scalar, -} - -pub struct BuilderInput { - pub secret: SecretKey, - pub note: money_contract::transfer::wallet::Note, - pub leaf_position: incrementalmerkletree::Position, - pub merkle_path: Vec, - pub signature_secret: SecretKey, -} - -// TODO: should be token locking voting? -// Inside ZKproof, check proposal is correct. -pub struct Builder { - pub inputs: Vec, - pub vote: Vote, - pub vote_keypair: Keypair, - pub proposal: Proposal, - pub dao: DaoParams, -} - -impl Builder { - pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall { - debug!(target: "dao_contract::vote::wallet::Builder", "build()"); - let mut proofs = vec![]; - - let gov_token_blind = pallas::Base::random(&mut OsRng); - - let mut inputs = vec![]; - let mut vote_value = 0; - let mut vote_value_blind = pallas::Scalar::from(0); - - for input in self.inputs { - let value_blind = pallas::Scalar::random(&mut OsRng); - - vote_value += input.note.value; - vote_value_blind += value_blind; - - let signature_public = PublicKey::from_secret(input.signature_secret); - - let zk_info = zk_bins.lookup(&"dao-vote-burn".to_string()).unwrap(); - - let zk_info = if let ZkContractInfo::Binary(info) = zk_info { - info - } else { - panic!("Not binary info") - }; - let zk_bin = zk_info.bincode.clone(); - - // Note from the previous output - let note = input.note; - let leaf_pos: u64 = input.leaf_position.into(); - - let prover_witnesses = vec![ - Witness::Base(Value::known(input.secret.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)), - 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 coords = public_key.0.to_affine().coordinates().unwrap(); - - let coin = poseidon_hash::<8>([ - *coords.x(), - *coords.y(), - pallas::Base::from(note.value), - note.token_id, - note.serial, - pallas::Base::from(0), - pallas::Base::from(0), - note.coin_blind, - ]); - - let merkle_root = { - let position: u64 = input.leaf_position.into(); - let mut current = MerkleNode::from(coin); - for (level, sibling) in input.merkle_path.iter().enumerate() { - let level = level as u8; - current = if position & (1 << level) == 0 { - MerkleNode::combine(level.into(), ¤t, sibling) - } else { - MerkleNode::combine(level.into(), sibling, ¤t) - }; - } - current - }; - - let token_commit = poseidon_hash::<2>([note.token_id, gov_token_blind]); - assert_eq!(self.dao.gov_token_id, note.token_id); - - let nullifier = - Nullifier::from(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 sigpub_coords = signature_public.0.to_affine().coordinates().unwrap(); - - let public_inputs = vec![ - nullifier.inner(), - *vote_commit_coords.x(), - *vote_commit_coords.y(), - token_commit, - merkle_root.inner(), - *sigpub_coords.x(), - *sigpub_coords.y(), - ]; - - let circuit = ZkCircuit::new(prover_witnesses, zk_bin); - let proving_key = &zk_info.proving_key; - debug!(target: "dao_contract::vote::wallet::Builder", "input_proof Proof::create()"); - let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) - .expect("DAO::vote() proving error!"); - proofs.push(input_proof); - - let input = Input { nullifier, vote_commit, merkle_root, signature_public }; - inputs.push(input); - } - - let token_commit = poseidon_hash::<2>([self.dao.gov_token_id, gov_token_blind]); - - let proposal_dest_coords = self.proposal.dest.0.to_affine().coordinates().unwrap(); - - let proposal_amount = pallas::Base::from(self.proposal.amount); - - let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit); - let dao_quorum = pallas::Base::from(self.dao.quorum); - let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot); - let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base); - - let dao_pubkey_coords = self.dao.public_key.0.to_affine().coordinates().unwrap(); - - let dao_bulla = poseidon_hash::<8>([ - dao_proposer_limit, - dao_quorum, - dao_approval_ratio_quot, - dao_approval_ratio_base, - self.dao.gov_token_id, - *dao_pubkey_coords.x(), - *dao_pubkey_coords.y(), - self.dao.bulla_blind, - ]); - - let proposal_bulla = poseidon_hash::<8>([ - *proposal_dest_coords.x(), - *proposal_dest_coords.y(), - proposal_amount, - self.proposal.serial, - self.proposal.token_id, - dao_bulla, - self.proposal.blind, - // @tmp-workaround - self.proposal.blind, - ]); - - let vote_option = self.vote.vote_option as u64; - assert!(vote_option == 0 || vote_option == 1); - - let yes_vote_commit = - pedersen_commitment_u64(vote_option * vote_value, self.vote.vote_option_blind); - let yes_vote_commit_coords = yes_vote_commit.to_affine().coordinates().unwrap(); - - let all_vote_commit = pedersen_commitment_u64(vote_value, vote_value_blind); - let all_vote_commit_coords = all_vote_commit.to_affine().coordinates().unwrap(); - - let zk_info = zk_bins.lookup(&"dao-vote-main".to_string()).unwrap(); - let zk_info = if let ZkContractInfo::Binary(info) = zk_info { - info - } else { - panic!("Not binary info") - }; - let zk_bin = zk_info.bincode.clone(); - - let prover_witnesses = vec![ - // proposal params - Witness::Base(Value::known(*proposal_dest_coords.x())), - Witness::Base(Value::known(*proposal_dest_coords.y())), - Witness::Base(Value::known(proposal_amount)), - Witness::Base(Value::known(self.proposal.serial)), - Witness::Base(Value::known(self.proposal.token_id)), - Witness::Base(Value::known(self.proposal.blind)), - // DAO params - Witness::Base(Value::known(dao_proposer_limit)), - Witness::Base(Value::known(dao_quorum)), - Witness::Base(Value::known(dao_approval_ratio_quot)), - Witness::Base(Value::known(dao_approval_ratio_base)), - Witness::Base(Value::known(self.dao.gov_token_id)), - Witness::Base(Value::known(*dao_pubkey_coords.x())), - Witness::Base(Value::known(*dao_pubkey_coords.y())), - Witness::Base(Value::known(self.dao.bulla_blind)), - // Vote - Witness::Base(Value::known(pallas::Base::from(vote_option))), - Witness::Scalar(Value::known(self.vote.vote_option_blind)), - // Total number of gov tokens allocated - Witness::Base(Value::known(pallas::Base::from(vote_value))), - Witness::Scalar(Value::known(vote_value_blind)), - // gov token - Witness::Base(Value::known(gov_token_blind)), - ]; - - let public_inputs = vec![ - token_commit, - proposal_bulla, - // this should be a value commit?? - *yes_vote_commit_coords.x(), - *yes_vote_commit_coords.y(), - *all_vote_commit_coords.x(), - *all_vote_commit_coords.y(), - ]; - - let circuit = ZkCircuit::new(prover_witnesses, zk_bin); - - let proving_key = &zk_info.proving_key; - debug!(target: "dao_contract::vote::wallet::Builder", "main_proof = Proof::create()"); - let main_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) - .expect("DAO::vote() proving error!"); - proofs.push(main_proof); - - let note = Note { vote: self.vote, vote_value, vote_value_blind }; - let enc_note = note::encrypt(¬e, &self.vote_keypair.public).unwrap(); - - let header = Header { token_commit, proposal_bulla, yes_vote_commit, enc_note }; - - let call_data = CallData { header, inputs }; - - FuncCall { - contract_id: *CONTRACT_ID, - func_id: *super::FUNC_ID, - call_data: Box::new(call_data), - proofs, - } - } -} diff --git a/example/dao/contract/money_contract/state.rs b/example/dao/contract/money_contract/state.rs deleted file mode 100644 index 28c3f11fe..000000000 --- a/example/dao/contract/money_contract/state.rs +++ /dev/null @@ -1,114 +0,0 @@ -use darkfi_sdk::crypto::{constants::MERKLE_DEPTH, MerkleNode, Nullifier}; -use incrementalmerkletree::{bridgetree::BridgeTree, Tree}; - -use darkfi::crypto::{ - coin::Coin, - keypair::{PublicKey, SecretKey}, -}; - -use super::transfer; -use crate::note::EncryptedNote2; - -type MerkleTree = BridgeTree; - -pub struct OwnCoin { - pub coin: Coin, - pub note: transfer::wallet::Note, - pub leaf_position: incrementalmerkletree::Position, -} - -pub struct WalletCache { - // Normally this would be a HashMap, but SecretKey is not Hash-able - // TODO: This can be HashableBase - cache: Vec<(SecretKey, Vec)>, -} - -impl WalletCache { - pub fn new() -> Self { - Self { cache: Vec::new() } - } - - /// Must be called at the start to begin tracking received coins for this secret. - pub fn track(&mut self, secret: SecretKey) { - self.cache.push((secret, Vec::new())); - } - - /// Get all coins received by this secret key - /// track() must be called on this secret before calling this or the function will panic. - pub fn get_received(&mut self, secret: &SecretKey) -> Vec { - for (other_secret, own_coins) in self.cache.iter_mut() { - if *secret == *other_secret { - // clear own_coins vec, and return current contents - return std::mem::take(own_coins) - } - } - panic!("you forget to track() this secret!"); - } - - pub fn try_decrypt_note( - &mut self, - coin: Coin, - ciphertext: EncryptedNote2, - tree: &mut MerkleTree, - ) { - // Loop through all our secret keys... - for (secret, own_coins) in self.cache.iter_mut() { - // .. attempt to decrypt the note ... - if let Ok(note) = ciphertext.decrypt(secret) { - let leaf_position = tree.witness().expect("coin should be in tree"); - own_coins.push(OwnCoin { coin, note, leaf_position }); - } - } - } -} - -/// The state machine, held in memory. -pub struct State { - /// The entire Merkle tree state - pub tree: MerkleTree, - /// List of all previous and the current Merkle roots. - /// This is the hashed value of all the children. - pub merkle_roots: Vec, - /// Nullifiers prevent double spending - pub nullifiers: Vec, - - /// Public key of the cashier - pub cashier_signature_public: PublicKey, - - /// Public key of the faucet - pub faucet_signature_public: PublicKey, - - pub wallet_cache: WalletCache, -} - -impl State { - pub fn new( - cashier_signature_public: PublicKey, - faucet_signature_public: PublicKey, - ) -> Box { - Box::new(Self { - tree: MerkleTree::new(100), - merkle_roots: vec![], - nullifiers: vec![], - cashier_signature_public, - faucet_signature_public, - wallet_cache: WalletCache::new(), - }) - } - - pub fn is_valid_cashier_public_key(&self, public: &PublicKey) -> bool { - public == &self.cashier_signature_public - } - - pub fn is_valid_faucet_public_key(&self, public: &PublicKey) -> bool { - public == &self.faucet_signature_public - } - - pub fn is_valid_merkle(&self, merkle_root: &MerkleNode) -> bool { - self.merkle_roots.iter().any(|m| m == merkle_root) - } - - pub fn nullifier_exists(&self, nullifier: &Nullifier) -> bool { - self.nullifiers.iter().any(|n| n == nullifier) - } -} diff --git a/example/dao/contract/money_contract/transfer/validate.rs b/example/dao/contract/money_contract/transfer/validate.rs deleted file mode 100644 index e301041d4..000000000 --- a/example/dao/contract/money_contract/transfer/validate.rs +++ /dev/null @@ -1,374 +0,0 @@ -use std::any::{Any, TypeId}; - -use darkfi_sdk::crypto::{MerkleNode, Nullifier}; -use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable}; -use incrementalmerkletree::Tree; -use log::{debug, error}; -use pasta_curves::{group::Group, pallas}; - -use darkfi::{ - crypto::{ - coin::Coin, - keypair::PublicKey, - types::{DrkCircuitField, DrkTokenId, DrkValueBlind, DrkValueCommit}, - util::{pedersen_commitment_base, pedersen_commitment_u64}, - BurnRevealedValues, MintRevealedValues, - }, - Error as DarkFiError, -}; - -use crate::{ - contract::{ - dao_contract, - money_contract::{state::State, CONTRACT_ID}, - }, - note::EncryptedNote2, - util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, -}; - -const TARGET: &str = "money_contract::transfer::validate::state_transition()"; - -/// A struct representing a state update. -/// This gets applied on top of an existing state. -#[derive(Clone)] -pub struct Update { - /// All nullifiers in a transaction - pub nullifiers: Vec, - /// All coins in a transaction - pub coins: Vec, - /// All encrypted notes in a transaction - pub enc_notes: Vec, -} - -impl UpdateBase for Update { - fn apply(mut self: Box, states: &mut StateRegistry) { - let state = states.lookup_mut::(*CONTRACT_ID).unwrap(); - - // Extend our list of nullifiers with the ones from the update - state.nullifiers.append(&mut self.nullifiers); - - //// Update merkle tree and witnesses - for (coin, enc_note) in self.coins.into_iter().zip(self.enc_notes.into_iter()) { - // Add the new coins to the Merkle tree - let node = MerkleNode::from(coin.0); - state.tree.append(&node); - - // Keep track of all Merkle roots that have existed - state.merkle_roots.push(state.tree.root(0).unwrap()); - - state.wallet_cache.try_decrypt_note(coin, enc_note, &mut state.tree); - } - } -} - -pub fn state_transition( - states: &StateRegistry, - func_call_index: usize, - parent_tx: &Transaction, -) -> Result> { - // Check the public keys in the clear inputs to see if they're coming - // from a valid cashier or faucet. - debug!(target: TARGET, "Iterate clear_inputs"); - let func_call = &parent_tx.func_calls[func_call_index]; - let call_data = func_call.call_data.as_any(); - - assert_eq!((*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::(); - - // This will be inside wasm so unwrap is fine. - let call_data = call_data.unwrap(); - - let state = states.lookup::(*CONTRACT_ID).expect("Return type is not of type State"); - - // Code goes here - for (i, input) in call_data.clear_inputs.iter().enumerate() { - let pk = &input.signature_public; - // TODO: this depends on the token ID - if !state.is_valid_cashier_public_key(pk) && !state.is_valid_faucet_public_key(pk) { - error!(target: TARGET, "Invalid pubkey for clear input: {:?}", pk); - return Err(Error::VerifyFailed(VerifyFailed::InvalidCashierOrFaucetKey(i))) - } - } - - // Nullifiers in the transaction - let mut nullifiers = Vec::with_capacity(call_data.inputs.len()); - - debug!(target: TARGET, "Iterate inputs"); - for (i, input) in call_data.inputs.iter().enumerate() { - let merkle = &input.revealed.merkle_root; - - // The Merkle root is used to know whether this is a coin that - // existed in a previous state. - if !state.is_valid_merkle(merkle) { - error!(target: TARGET, "Invalid Merkle root (input {})", i); - debug!(target: TARGET, "root: {:?}", merkle); - return Err(Error::VerifyFailed(VerifyFailed::InvalidMerkle(i))) - } - - // Check the spend_hook is satisfied - // The spend_hook says a coin must invoke another contract function when being spent - // If the value is set, then we check the function call exists - let spend_hook = &input.revealed.spend_hook; - if spend_hook != &pallas::Base::from(0) { - // spend_hook is set so we enforce the rules - let mut is_found = false; - for (i, func_call) in parent_tx.func_calls.iter().enumerate() { - // Skip current func_call - if i == func_call_index { - continue - } - - // TODO: we need to change these to pallas::Base - // temporary workaround for now - // if func_call.func_id == spend_hook ... - if func_call.func_id == *dao_contract::exec::FUNC_ID { - is_found = true; - break - } - } - if !is_found { - return Err(Error::VerifyFailed(VerifyFailed::SpendHookNotSatisfied)) - } - } - - // The nullifiers should not already exist. - // It is the double-spend protection. - let nullifier = &input.revealed.nullifier; - if state.nullifier_exists(nullifier) || - (1..nullifiers.len()).any(|i| nullifiers[i..].contains(&nullifiers[i - 1])) - { - error!(target: TARGET, "Duplicate nullifier found (input {})", i); - debug!(target: TARGET, "nullifier: {:?}", nullifier); - return Err(Error::VerifyFailed(VerifyFailed::NullifierExists(i))) - } - - nullifiers.push(input.revealed.nullifier); - } - - debug!(target: TARGET, "Verifying call data"); - match call_data.verify() { - Ok(()) => { - debug!(target: TARGET, "Verified successfully") - } - Err(e) => { - error!(target: TARGET, "Failed verifying zk proofs: {}", e); - return Err(Error::VerifyFailed(VerifyFailed::ProofVerifyFailed(e.to_string()))) - } - } - - // Newly created coins for this transaction - let mut coins = Vec::with_capacity(call_data.outputs.len()); - let mut enc_notes = Vec::with_capacity(call_data.outputs.len()); - - for output in &call_data.outputs { - // Gather all the coins - coins.push(output.revealed.coin); - enc_notes.push(output.enc_note.clone()); - } - - Ok(Box::new(Update { nullifiers, coins, enc_notes })) -} - -/// A DarkFi transaction -#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)] -pub struct CallData { - /// Clear inputs - pub clear_inputs: Vec, - /// Anonymous inputs - pub inputs: Vec, - /// Anonymous outputs - pub outputs: Vec, -} - -impl CallDataBase for CallData { - fn zk_public_values(&self) -> Vec<(String, Vec)> { - let mut public_values = Vec::new(); - for input in &self.inputs { - public_values.push(("money-transfer-burn".to_string(), input.revealed.make_outputs())); - } - for output in &self.outputs { - public_values.push(("money-transfer-mint".to_string(), output.revealed.make_outputs())); - } - public_values - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn signature_public_keys(&self) -> Vec { - let mut signature_public_keys = Vec::new(); - for input in self.clear_inputs.clone() { - signature_public_keys.push(input.signature_public); - } - signature_public_keys - } - - fn encode_bytes( - &self, - mut writer: &mut dyn std::io::Write, - ) -> core::result::Result { - self.encode(&mut writer) - } -} -impl CallData { - /// Verify the transaction - pub fn verify(&self) -> VerifyResult<()> { - // must have minimum 1 clear or anon input, and 1 output - if self.clear_inputs.len() + self.inputs.len() == 0 { - error!("tx::verify(): Missing inputs"); - return Err(VerifyFailed::LackingInputs) - } - if self.outputs.is_empty() { - error!("tx::verify(): Missing outputs"); - return Err(VerifyFailed::LackingOutputs) - } - - // Accumulator for the value commitments - let mut valcom_total = DrkValueCommit::identity(); - - // Add values from the clear inputs - for input in &self.clear_inputs { - valcom_total += pedersen_commitment_u64(input.value, input.value_blind); - } - // Add values from the inputs - for input in &self.inputs { - valcom_total += &input.revealed.value_commit; - } - // Subtract values from the outputs - for output in &self.outputs { - valcom_total -= &output.revealed.value_commit; - } - - // If the accumulator is not back in its initial state, - // there's a value mismatch. - if valcom_total != DrkValueCommit::identity() { - error!("tx::verify(): Missing funds"); - return Err(VerifyFailed::MissingFunds) - } - - // Verify that the token commitments match - if !self.verify_token_commitments() { - error!("tx::verify(): Token ID mismatch"); - return Err(VerifyFailed::TokenMismatch) - } - - Ok(()) - } - - fn verify_token_commitments(&self) -> bool { - assert_ne!(self.outputs.len(), 0); - let token_commit_value = self.outputs[0].revealed.token_commit; - - let mut failed = - self.inputs.iter().any(|input| input.revealed.token_commit != token_commit_value); - - failed = failed || - self.outputs.iter().any(|output| output.revealed.token_commit != token_commit_value); - - failed = failed || - self.clear_inputs.iter().any(|input| { - pedersen_commitment_base(input.token_id, input.token_blind) != token_commit_value - }); - !failed - } -} - -/// A transaction's clear input -#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)] -pub struct ClearInput { - /// Input's value (amount) - pub value: u64, - /// Input's token ID - pub token_id: DrkTokenId, - /// Blinding factor for `value` - pub value_blind: DrkValueBlind, - /// Blinding factor for `token_id` - pub token_blind: DrkValueBlind, - /// Public key for the signature - pub signature_public: PublicKey, -} - -/// A transaction's anonymous input -#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)] -pub struct Input { - /// Public inputs for the zero-knowledge proof - pub revealed: BurnRevealedValues, -} - -/// A transaction's anonymous output -#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)] -pub struct Output { - /// Public inputs for the zero-knowledge proof - pub revealed: MintRevealedValues, - /// The encrypted note - pub enc_note: EncryptedNote2, -} - -#[derive(Debug, Clone, thiserror::Error)] -pub enum Error { - #[error(transparent)] - VerifyFailed(#[from] VerifyFailed), - - #[error("DarkFi error: {0}")] - DarkFiError(String), -} - -/// Transaction verification errors -#[derive(Debug, Clone, thiserror::Error)] -pub enum VerifyFailed { - #[error("Transaction has no inputs")] - LackingInputs, - - #[error("Transaction has no outputs")] - LackingOutputs, - - #[error("Invalid cashier/faucet public key for clear input {0}")] - InvalidCashierOrFaucetKey(usize), - - #[error("Invalid Merkle root for input {0}")] - InvalidMerkle(usize), - - #[error("Spend hook invoking function is not attached")] - SpendHookNotSatisfied, - - #[error("Nullifier already exists for input {0}")] - NullifierExists(usize), - - #[error("Token commitments in inputs or outputs to not match")] - TokenMismatch, - - #[error("Money in does not match money out (value commitments)")] - MissingFunds, - - #[error("Failed verifying zk proofs: {0}")] - ProofVerifyFailed(String), - - #[error("Internal error: {0}")] - InternalError(String), - - #[error("DarkFi error: {0}")] - DarkFiError(String), -} - -type Result = std::result::Result; - -impl From for VerifyFailed { - fn from(err: Error) -> Self { - Self::InternalError(err.to_string()) - } -} - -impl From for VerifyFailed { - fn from(err: DarkFiError) -> Self { - Self::DarkFiError(err.to_string()) - } -} - -impl From for Error { - fn from(err: DarkFiError) -> Self { - Self::DarkFiError(err.to_string()) - } -} -/// Result type used in transaction verifications -pub type VerifyResult = std::result::Result; diff --git a/example/dao/contract/money_contract/transfer/wallet.rs b/example/dao/contract/money_contract/transfer/wallet.rs deleted file mode 100644 index 74b33ec81..000000000 --- a/example/dao/contract/money_contract/transfer/wallet.rs +++ /dev/null @@ -1,219 +0,0 @@ -use darkfi_sdk::crypto::MerkleNode; -use darkfi_serial::{SerialDecodable, SerialEncodable}; -use pasta_curves::group::ff::Field; -use rand::rngs::OsRng; - -use darkfi::{ - crypto::{ - burn_proof::create_burn_proof, - keypair::{PublicKey, SecretKey}, - mint_proof::create_mint_proof, - types::{ - DrkCoinBlind, DrkSerial, DrkSpendHook, DrkTokenId, DrkUserData, DrkUserDataBlind, - DrkValueBlind, - }, - }, - Result, -}; - -use crate::{ - contract::money_contract::{ - 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: DrkTokenId, - 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, - pub inputs: Vec, - pub outputs: Vec, -} - -pub struct BuilderClearInputInfo { - pub value: u64, - pub token_id: DrkTokenId, - pub signature_secret: SecretKey, -} - -pub struct BuilderInputInfo { - pub leaf_position: incrementalmerkletree::Position, - pub merkle_path: Vec, - pub secret: SecretKey, - pub note: Note, - pub user_data_blind: DrkUserDataBlind, - pub value_blind: DrkValueBlind, - pub signature_secret: SecretKey, -} - -pub struct BuilderOutputInfo { - pub value: u64, - pub token_id: DrkTokenId, - pub public: PublicKey, - pub serial: DrkSerial, - pub coin_blind: DrkCoinBlind, - pub spend_hook: DrkSpendHook, - pub user_data: DrkUserData, -} - -impl Builder { - fn compute_remainder_blind( - clear_inputs: &[ClearInput], - input_blinds: &[DrkValueBlind], - output_blinds: &[DrkValueBlind], - ) -> DrkValueBlind { - let mut total = DrkValueBlind::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, zk_bins: &ZkContractTable) -> Result { - assert!(self.clear_inputs.len() + self.inputs.len() > 0); - - let mut clear_inputs = vec![]; - let token_blind = DrkValueBlind::random(&mut OsRng); - for input in &self.clear_inputs { - let signature_public = PublicKey::from_secret(input.signature_secret); - let value_blind = DrkValueBlind::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); - - let zk_info = zk_bins.lookup(&"money-transfer-burn".to_string()).unwrap(); - let zk_info = if let ZkContractInfo::Native(info) = zk_info { - info - } else { - panic!("Not native info") - }; - let burn_pk = &zk_info.proving_key; - - // Note from the previous output - let note = input.note.clone(); - - let (burn_proof, revealed) = create_burn_proof( - 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(burn_proof); - - let input = Input { revealed }; - 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 { - DrkValueBlind::random(&mut OsRng) - }; - output_blinds.push(value_blind); - - let serial = output.serial; - let coin_blind = output.coin_blind; - - let zk_info = zk_bins.lookup(&"money-transfer-mint".to_string()).unwrap(); - let zk_info = if let ZkContractInfo::Native(info) = zk_info { - info - } else { - panic!("Not native info") - }; - let mint_pk = &zk_info.proving_key; - - let (mint_proof, revealed) = create_mint_proof( - mint_pk, - output.value, - output.token_id, - value_blind, - token_blind, - serial, - output.spend_hook, - output.user_data, - coin_blind, - output.public, - )?; - proofs.push(mint_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, - }; - - let encrypted_note = note::encrypt(¬e, &output.public)?; - - let output = Output { revealed, enc_note: encrypted_note }; - outputs.push(output); - } - - let call_data = CallData { clear_inputs, inputs, outputs }; - - Ok(FuncCall { - contract_id: *CONTRACT_ID, - func_id: *super::FUNC_ID, - call_data: Box::new(call_data), - proofs, - }) - } -} diff --git a/example/dao/dao.rs b/example/dao/dao.rs deleted file mode 100644 index f6ba32e6c..000000000 --- a/example/dao/dao.rs +++ /dev/null @@ -1,1328 +0,0 @@ -use std::{ - any::{Any, TypeId}, - time::Instant, -}; - -use incrementalmerkletree::Tree; -use log::debug; -use pasta_curves::{ - arithmetic::CurveAffine, - group::{ff::Field, Curve, Group}, - pallas, -}; -use rand::rngs::OsRng; - -use darkfi::{ - crypto::{ - keypair::{Keypair, PublicKey, SecretKey}, - proof::{ProvingKey, VerifyingKey}, - types::{DrkSpendHook, DrkUserData, DrkValue}, - util::{pedersen_commitment_u64, poseidon_hash}, - }, - zk::circuit::{BurnContract, MintContract}, - zkas::decoder::ZkBinary, -}; - -mod contract; -mod note; -mod util; - -use crate::{ - contract::{dao_contract, example_contract, money_contract}, - util::{sign, StateRegistry, Transaction, ZkContractTable}, -}; - -// TODO: Anonymity leaks in this proof of concept: -// -// * Vote updates are linked to the proposal_bulla -// * Nullifier of vote will link vote with the coin when it's spent - -// TODO: strategize and cleanup Result/Error usage -// TODO: fix up code doc - -type Result = std::result::Result>; - -// #[derive(Eq, PartialEq)] -// pub struct HashableBase(pub pallas::Base); - -// impl std::hash::Hash for HashableBase { -// fn hash(&self, state: &mut H) { -// let bytes = self.0.to_repr(); -// bytes.hash(state); -// } -// } - -// pub struct ZkBinaryContractInfo { -// pub k_param: u32, -// pub bincode: ZkBinary, -// pub proving_key: ProvingKey, -// pub verifying_key: VerifyingKey, -// } -// pub struct ZkNativeContractInfo { -// pub proving_key: ProvingKey, -// pub verifying_key: VerifyingKey, -// } - -// pub enum ZkContractInfo { -// Binary(ZkBinaryContractInfo), -// Native(ZkNativeContractInfo), -// } - -// pub struct ZkContractTable { -// // Key will be a hash of zk binary contract on chain -// table: HashMap, -// } - -// impl ZkContractTable { -// fn new() -> Self { -// Self { table: HashMap::new() } -// } - -// fn add_contract(&mut self, key: String, bincode: ZkBinary, k_param: u32) { -// let witnesses = empty_witnesses(&bincode); -// let circuit = ZkCircuit::new(witnesses, bincode.clone()); -// let proving_key = ProvingKey::build(k_param, &circuit); -// let verifying_key = VerifyingKey::build(k_param, &circuit); -// let info = ZkContractInfo::Binary(ZkBinaryContractInfo { -// k_param, -// bincode, -// proving_key, -// verifying_key, -// }); -// self.table.insert(key, info); -// } - -// fn add_native(&mut self, key: String, proving_key: ProvingKey, verifying_key: VerifyingKey) { -// self.table.insert( -// key, -// ZkContractInfo::Native(ZkNativeContractInfo { proving_key, verifying_key }), -// ); -// } - -// pub fn lookup(&self, key: &String) -> Option<&ZkContractInfo> { -// self.table.get(key) -// } -// } - -// pub struct Transaction { -// pub func_calls: Vec, -// pub signatures: Vec, -// } - -// impl Transaction { -// /// Verify ZK contracts for the entire tx -// /// In real code, we could parallelize this for loop -// /// TODO: fix use of unwrap with Result type stuff -// fn zk_verify(&self, zk_bins: &ZkContractTable) { -// for func_call in &self.func_calls { -// let proofs_public_vals = &func_call.call_data.zk_public_values(); - -// assert_eq!( -// proofs_public_vals.len(), -// func_call.proofs.len(), -// "proof_public_vals.len()={} and func_call.proofs.len()={} do not match", -// proofs_public_vals.len(), -// func_call.proofs.len() -// ); -// for (i, (proof, (key, public_vals))) in -// func_call.proofs.iter().zip(proofs_public_vals.iter()).enumerate() -// { -// match zk_bins.lookup(key).unwrap() { -// ZkContractInfo::Binary(info) => { -// let verifying_key = &info.verifying_key; -// let verify_result = proof.verify(&verifying_key, public_vals); -// assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); -// } -// ZkContractInfo::Native(info) => { -// let verifying_key = &info.verifying_key; -// let verify_result = proof.verify(&verifying_key, public_vals); -// assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); -// } -// }; -// debug!(target: "demo", "zk_verify({}) passed [i={}]", key, i); -// } -// } -// } - -// fn verify_sigs(&self) { -// let mut unsigned_tx_data = vec![]; -// for (i, (func_call, signature)) in -// self.func_calls.iter().zip(self.signatures.clone()).enumerate() -// { -// func_call.encode(&mut unsigned_tx_data).expect("failed to encode data"); -// let signature_pub_keys = func_call.call_data.signature_public_keys(); -// for signature_pub_key in signature_pub_keys { -// let verify_result = signature_pub_key.verify(&unsigned_tx_data[..], &signature); -// assert!(verify_result, "verify sigs[{}] failed", i); -// } -// debug!(target: "demo", "verify_sigs({}) passed", i); -// } -// } -// } - -// fn sign(signature_secrets: Vec, func_calls: &Vec) -> Vec { -// let mut signatures = vec![]; -// let mut unsigned_tx_data = vec![]; -// for (_i, (signature_secret, func_call)) in -// signature_secrets.iter().zip(func_calls.iter()).enumerate() -// { -// func_call.encode(&mut unsigned_tx_data).expect("failed to encode data"); -// let signature = signature_secret.sign(&unsigned_tx_data[..]); -// signatures.push(signature); -// } -// signatures -// } - -// type ContractId = pallas::Base; -// type FuncId = pallas::Base; - -// pub struct FuncCall { -// pub contract_id: ContractId, -// pub func_id: FuncId, -// pub call_data: Box, -// pub proofs: Vec, -// } - -// impl Encodable for FuncCall { -// fn encode(&self, mut w: W) -> std::result::Result { -// let mut len = 0; -// len += self.contract_id.encode(&mut w)?; -// len += self.func_id.encode(&mut w)?; -// len += self.proofs.encode(&mut w)?; -// len += self.call_data.encode_bytes(&mut w)?; -// Ok(len) -// } -// } - -// pub trait CallDataBase { -// // Public values for verifying the proofs -// // Needed so we can convert internal types so they can be used in Proof::verify() -// fn zk_public_values(&self) -> Vec<(String, Vec)>; - -// // For upcasting to CallData itself so it can be read in state_transition() -// fn as_any(&self) -> &dyn Any; - -// // Public keys we will use to verify transaction signatures. -// fn signature_public_keys(&self) -> Vec; - -// fn encode_bytes( -// &self, -// writer: &mut dyn std::io::Write, -// ) -> std::result::Result; -// } - -// type GenericContractState = Box; - -// pub struct StateRegistry { -// pub states: HashMap, -// } - -// impl StateRegistry { -// fn new() -> Self { -// Self { states: HashMap::new() } -// } - -// fn register(&mut self, contract_id: ContractId, state: GenericContractState) { -// debug!(target: "StateRegistry::register()", "contract_id: {:?}", contract_id); -// self.states.insert(HashableBase(contract_id), state); -// } - -// pub fn lookup_mut<'a, S: 'static>(&'a mut self, contract_id: ContractId) -> Option<&'a mut S> { -// self.states.get_mut(&HashableBase(contract_id)).and_then(|state| state.downcast_mut()) -// } - -// pub fn lookup<'a, S: 'static>(&'a self, contract_id: ContractId) -> Option<&'a S> { -// self.states.get(&HashableBase(contract_id)).and_then(|state| state.downcast_ref()) -// } -// } - -// pub trait UpdateBase { -// fn apply(self: Box, states: &mut StateRegistry); -// } - -/////////////////////////////////////////////////// -///// Example contract -/////////////////////////////////////////////////// -pub async fn example() -> Result<()> { - debug!(target: "demo", "Stage 0. Example contract"); - // Lookup table for smart contract states - let mut states = StateRegistry::new(); - - // Initialize ZK binary table - let mut zk_bins = ZkContractTable::new(); - - let zk_example_foo_bincode = include_bytes!("proof/foo.zk.bin"); - let zk_example_foo_bin = ZkBinary::decode(zk_example_foo_bincode)?; - zk_bins.add_contract("example-foo".to_string(), zk_example_foo_bin, 13); - - let example_state = example_contract::state::State::new(); - states.register(*example_contract::CONTRACT_ID, example_state); - - //// Wallet - - let foo = example_contract::foo::wallet::Foo { a: 5, b: 10 }; - let signature_secret = SecretKey::random(&mut OsRng); - - let builder = example_contract::foo::wallet::Builder { foo, signature_secret }; - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *example_contract::foo::FUNC_ID { - debug!("example_contract::foo::state_transition()"); - - let update = example_contract::foo::validate::state_transition(&states, idx, &tx) - .expect("example_contract::foo::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - Ok(()) -} - -#[async_std::main] -async fn main() -> Result<()> { - // Example smart contract - //// TODO: this will be moved to a different file - example().await?; - - // Money parameters - let xdrk_supply = 1_000_000; - let xdrk_token_id = pallas::Base::random(&mut OsRng); - - // Governance token parameters - let gdrk_supply = 1_000_000; - let gdrk_token_id = pallas::Base::random(&mut OsRng); - - // DAO parameters - let dao_proposer_limit = 110; - let dao_quorum = 110; - let dao_approval_ratio_quot = 1; - let dao_approval_ratio_base = 2; - - // Lookup table for smart contract states - let mut states = StateRegistry::new(); - - // Initialize ZK binary table - let mut zk_bins = ZkContractTable::new(); - - debug!(target: "demo", "Loading dao-mint.zk"); - let zk_dao_mint_bincode = include_bytes!("proof/dao-mint.zk.bin"); - let zk_dao_mint_bin = ZkBinary::decode(zk_dao_mint_bincode)?; - zk_bins.add_contract("dao-mint".to_string(), zk_dao_mint_bin, 13); - - debug!(target: "demo", "Loading money-transfer contracts"); - { - let start = Instant::now(); - let mint_pk = ProvingKey::build(11, &MintContract::default()); - debug!("Mint PK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let burn_pk = ProvingKey::build(11, &BurnContract::default()); - debug!("Burn PK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let mint_vk = VerifyingKey::build(11, &MintContract::default()); - debug!("Mint VK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let burn_vk = VerifyingKey::build(11, &BurnContract::default()); - debug!("Burn VK: [{:?}]", start.elapsed()); - - zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk); - zk_bins.add_native("money-transfer-burn".to_string(), burn_pk, burn_vk); - } - debug!(target: "demo", "Loading dao-propose-main.zk"); - let zk_dao_propose_main_bincode = include_bytes!("proof/dao-propose-main.zk.bin"); - let zk_dao_propose_main_bin = ZkBinary::decode(zk_dao_propose_main_bincode)?; - zk_bins.add_contract("dao-propose-main".to_string(), zk_dao_propose_main_bin, 13); - debug!(target: "demo", "Loading dao-propose-burn.zk"); - let zk_dao_propose_burn_bincode = include_bytes!("proof/dao-propose-burn.zk.bin"); - let zk_dao_propose_burn_bin = ZkBinary::decode(zk_dao_propose_burn_bincode)?; - zk_bins.add_contract("dao-propose-burn".to_string(), zk_dao_propose_burn_bin, 13); - debug!(target: "demo", "Loading dao-vote-main.zk"); - let zk_dao_vote_main_bincode = include_bytes!("proof/dao-vote-main.zk.bin"); - let zk_dao_vote_main_bin = ZkBinary::decode(zk_dao_vote_main_bincode)?; - zk_bins.add_contract("dao-vote-main".to_string(), zk_dao_vote_main_bin, 13); - debug!(target: "demo", "Loading dao-vote-burn.zk"); - let zk_dao_vote_burn_bincode = include_bytes!("proof/dao-vote-burn.zk.bin"); - let zk_dao_vote_burn_bin = ZkBinary::decode(zk_dao_vote_burn_bincode)?; - zk_bins.add_contract("dao-vote-burn".to_string(), zk_dao_vote_burn_bin, 13); - let zk_dao_exec_bincode = include_bytes!("proof/dao-exec.zk.bin"); - let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?; - zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13); - - // State for money contracts - let cashier_signature_secret = SecretKey::random(&mut OsRng); - let cashier_signature_public = PublicKey::from_secret(cashier_signature_secret); - let faucet_signature_secret = SecretKey::random(&mut OsRng); - let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret); - - /////////////////////////////////////////////////// - - let money_state = - money_contract::state::State::new(cashier_signature_public, faucet_signature_public); - states.register(*money_contract::CONTRACT_ID, money_state); - - ///////////////////////////////////////////////////// - - let dao_state = dao_contract::State::new(); - states.register(*dao_contract::CONTRACT_ID, dao_state); - - ///////////////////////////////////////////////////// - ////// Create the DAO bulla - ///////////////////////////////////////////////////// - debug!(target: "demo", "Stage 1. Creating DAO bulla"); - - //// Wallet - - //// Setup the DAO - let dao_keypair = Keypair::random(&mut OsRng); - let dao_bulla_blind = pallas::Base::random(&mut OsRng); - - let signature_secret = SecretKey::random(&mut OsRng); - // Create DAO mint tx - let builder = dao_contract::mint::wallet::Builder { - dao_proposer_limit, - dao_quorum, - dao_approval_ratio_quot, - dao_approval_ratio_base, - gov_token_id: gdrk_token_id, - dao_pubkey: dao_keypair.public, - dao_bulla_blind, - _signature_secret: signature_secret, - }; - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - // So then the verifier will lookup the corresponding state_transition and apply - // functions based off the func_id - if func_call.func_id == *dao_contract::mint::FUNC_ID { - debug!("dao_contract::mint::state_transition()"); - - let update = dao_contract::mint::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::mint::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - // Wallet stuff - - // In your wallet, wait until you see the tx confirmed before doing anything below - // So for example keep track of tx hash - //assert_eq!(tx.hash(), tx_hash); - - // We need to witness() the value in our local merkle tree - // Must be called as soon as this DAO bulla is added to the state - let dao_leaf_position = { - let state = states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); - state.dao_tree.witness().unwrap() - }; - - // It might just be easier to hash it ourselves from keypair and blind... - let dao_bulla = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!((&*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::().unwrap(); - call_data.dao_bulla.clone() - }; - debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0); - - /////////////////////////////////////////////////// - //// Mint the initial supply of treasury token - //// and send it all to the DAO directly - /////////////////////////////////////////////////// - debug!(target: "demo", "Stage 2. Minting treasury token"); - - let state = states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - state.wallet_cache.track(dao_keypair.secret); - - //// Wallet - - // Address of deployed contract in our example is dao_contract::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" - // - let spend_hook = *dao_contract::exec::FUNC_ID; - // 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.0; - - let builder = money_contract::transfer::wallet::Builder { - clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { - value: xdrk_supply, - token_id: xdrk_token_id, - signature_secret: cashier_signature_secret, - }], - inputs: vec![], - outputs: vec![money_contract::transfer::wallet::BuilderOutputInfo { - value: xdrk_supply, - token_id: xdrk_token_id, - public: dao_keypair.public, - serial: pallas::Base::random(&mut OsRng), - coin_blind: pallas::Base::random(&mut OsRng), - spend_hook, - user_data, - }], - }; - - let func_call = builder.build(&zk_bins)?; - let func_calls = vec![func_call]; - - let signatures = sign(vec![cashier_signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - // So then the verifier will lookup the corresponding state_transition and apply - // functions based off the func_id - if func_call.func_id == *money_contract::transfer::FUNC_ID { - debug!("money_contract::transfer::state_transition()"); - - let update = money_contract::transfer::validate::state_transition(&states, idx, &tx) - .expect("money_contract::transfer::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - //// Wallet - // DAO reads the money received from the encrypted note - - let state = states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - let mut recv_coins = state.wallet_cache.get_received(&dao_keypair.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_keypair.public.0.to_affine().coordinates().unwrap(); - let coin = poseidon_hash::<8>([ - *coords.x(), - *coords.y(), - DrkValue::from(treasury_note.value), - treasury_note.token_id, - 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, *dao_contract::exec::FUNC_ID); - assert_eq!(treasury_note.user_data, dao_bulla.0); - - debug!("DAO received a coin worth {} xDRK", treasury_note.value); - - /////////////////////////////////////////////////// - //// Mint the governance token - //// Send it to three hodlers - /////////////////////////////////////////////////// - debug!(target: "demo", "Stage 3. Minting governance token"); - - //// Wallet - - // Hodler 1 - let gov_keypair_1 = Keypair::random(&mut OsRng); - // Hodler 2 - let gov_keypair_2 = Keypair::random(&mut OsRng); - // Hodler 3: the tiebreaker - let gov_keypair_3 = Keypair::random(&mut OsRng); - - let state = states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - state.wallet_cache.track(gov_keypair_1.secret); - state.wallet_cache.track(gov_keypair_2.secret); - state.wallet_cache.track(gov_keypair_3.secret); - - let gov_keypairs = vec![gov_keypair_1, gov_keypair_2, gov_keypair_3]; - - // Spend hook and user data disabled - let spend_hook = DrkSpendHook::from(0); - let user_data = DrkUserData::from(0); - - let output1 = money_contract::transfer::wallet::BuilderOutputInfo { - value: 400000, - token_id: gdrk_token_id, - public: gov_keypair_1.public, - serial: pallas::Base::random(&mut OsRng), - coin_blind: pallas::Base::random(&mut OsRng), - spend_hook, - user_data, - }; - - let output2 = money_contract::transfer::wallet::BuilderOutputInfo { - value: 400000, - token_id: gdrk_token_id, - public: gov_keypair_2.public, - serial: pallas::Base::random(&mut OsRng), - coin_blind: pallas::Base::random(&mut OsRng), - spend_hook, - user_data, - }; - - let output3 = money_contract::transfer::wallet::BuilderOutputInfo { - value: 200000, - token_id: gdrk_token_id, - public: gov_keypair_3.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_contract::transfer::wallet::Builder { - clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { - value: gdrk_supply, - token_id: gdrk_token_id, - signature_secret: cashier_signature_secret, - }], - inputs: vec![], - outputs: vec![output1, output2, output3], - }; - - let func_call = builder.build(&zk_bins)?; - let func_calls = vec![func_call]; - - let signatures = sign(vec![cashier_signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - // So then the verifier will lookup the corresponding state_transition and apply - // functions based off the func_id - if func_call.func_id == *money_contract::transfer::FUNC_ID { - debug!("money_contract::transfer::state_transition()"); - - let update = money_contract::transfer::validate::state_transition(&states, idx, &tx) - .expect("money_contract::transfer::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - //// Wallet - - 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 state = - states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - let mut recv_coins = state.wallet_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 coords = key.public.0.to_affine().coordinates().unwrap(); - let coin = poseidon_hash::<8>([ - *coords.x(), - *coords.y(), - DrkValue::from(note.value), - note.token_id, - 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 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 - /////////////////////////////////////////////////// - - /////////////////////////////////////////////////// - // Propose the vote - // In order to make a valid vote, first the proposer must - // meet a criteria for a minimum number of gov tokens - /////////////////////////////////////////////////// - debug!(target: "demo", "Stage 4. Propose the vote"); - - //// Wallet - - // TODO: look into proposal expiry once time for voting has finished - - let user_keypair = Keypair::random(&mut OsRng); - - let (money_leaf_position, money_merkle_path) = { - let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = gov_recv[0].leaf_position.clone(); - 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_contract::propose::wallet::BuilderInput { - secret: gov_keypair_1.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 state = states.lookup::(*dao_contract::CONTRACT_ID).unwrap(); - let tree = &state.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_contract::mint::wallet::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_keypair.public, - bulla_blind: dao_bulla_blind, - }; - - let proposal = dao_contract::propose::wallet::Proposal { - dest: user_keypair.public, - amount: 1000, - serial: pallas::Base::random(&mut OsRng), - token_id: xdrk_token_id, - blind: pallas::Base::random(&mut OsRng), - }; - - let builder = dao_contract::propose::wallet::Builder { - inputs: vec![input], - proposal: proposal.clone(), - dao: dao_params.clone(), - dao_leaf_position, - dao_merkle_path, - dao_merkle_root, - }; - - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::propose::FUNC_ID { - debug!(target: "demo", "dao_contract::propose::state_transition()"); - - let update = dao_contract::propose::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::propose::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - //// Wallet - - // Read received proposal - let (proposal, proposal_bulla) = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!( - (&*call_data).type_id(), - TypeId::of::() - ); - let call_data = - call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao_contract::propose::wallet::Note = - header.enc_note.decrypt(&dao_keypair.secret).unwrap(); - - // TODO: check it belongs to DAO bulla - - // Return the proposal info - (note.proposal, call_data.header.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.0); - 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 state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = gov_recv[0].leaf_position.clone(); - 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_contract::vote::wallet::BuilderInput { - secret: gov_keypair_1.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 == true || vote_option == false); - - // 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_contract::vote::wallet::Builder { - inputs: vec![input], - vote: dao_contract::vote::wallet::Vote { - vote_option, - vote_option_blind: pallas::Scalar::random(&mut OsRng), - }, - vote_keypair: vote_keypair_1, - proposal: proposal.clone(), - dao: dao_params.clone(), - }; - debug!(target: "demo", "build()..."); - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::vote::FUNC_ID { - debug!(target: "demo", "dao_contract::vote::state_transition()"); - - let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::vote::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - //// Wallet - - // Secret vote info. Needs to be revealed at some point. - // TODO: look into verifiable encryption for notes - // TODO: look into timelock puzzle as a possibility - let vote_note_1 = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!((&*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao_contract::vote::wallet::Note = - header.enc_note.decrypt(&vote_keypair_1.secret).unwrap(); - note - }; - debug!(target: "demo", "User 1 voted!"); - debug!(target: "demo", " vote_option: {}", vote_note_1.vote.vote_option); - debug!(target: "demo", " value: {}", vote_note_1.vote_value); - - // User 2: NO - - let (money_leaf_position, money_merkle_path) = { - let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = gov_recv[1].leaf_position.clone(); - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let signature_secret = SecretKey::random(&mut OsRng); - let input = dao_contract::vote::wallet::BuilderInput { - secret: gov_keypair_2.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 == true || vote_option == false); - - // We create a new keypair to encrypt the vote. - let vote_keypair_2 = Keypair::random(&mut OsRng); - - let builder = dao_contract::vote::wallet::Builder { - inputs: vec![input], - vote: dao_contract::vote::wallet::Vote { - vote_option, - vote_option_blind: pallas::Scalar::random(&mut OsRng), - }, - vote_keypair: vote_keypair_2, - proposal: proposal.clone(), - dao: dao_params.clone(), - }; - debug!(target: "demo", "build()..."); - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::vote::FUNC_ID { - debug!(target: "demo", "dao_contract::vote::state_transition()"); - - let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::vote::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - //// Wallet - - // Secret vote info. Needs to be revealed at some point. - // TODO: look into verifiable encryption for notes - // TODO: look into timelock puzzle as a possibility - let vote_note_2 = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!((&*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao_contract::vote::wallet::Note = - header.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 state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = gov_recv[2].leaf_position.clone(); - 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_contract::vote::wallet::BuilderInput { - secret: gov_keypair_3.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 == true || vote_option == false); - - // We create a new keypair to encrypt the vote. - let vote_keypair_3 = Keypair::random(&mut OsRng); - - let builder = dao_contract::vote::wallet::Builder { - inputs: vec![input], - vote: dao_contract::vote::wallet::Vote { - vote_option, - vote_option_blind: pallas::Scalar::random(&mut OsRng), - }, - vote_keypair: vote_keypair_3, - proposal: proposal.clone(), - dao: dao_params.clone(), - }; - debug!(target: "demo", "build()..."); - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::vote::FUNC_ID { - debug!(target: "demo", "dao_contract::vote::state_transition()"); - - let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::vote::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - //// Wallet - - // Secret vote info. Needs to be revealed at some point. - // TODO: look into verifiable encryption for notes - // TODO: look into timelock puzzle as a possibility - let vote_note_3 = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!((&*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao_contract::vote::wallet::Note = - header.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 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 - /////////////////////////////////////////////////// - - //// Wallet - - // 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 state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = dao_recv_coin.leaf_position.clone(); - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let input = money_contract::transfer::wallet::BuilderInputInfo { - leaf_position: treasury_leaf_position, - merkle_path: treasury_merkle_path, - secret: dao_keypair.secret, - note: treasury_note, - user_data_blind, - value_blind: input_value_blind, - signature_secret: tx_signature_secret, - }; - - let builder = money_contract::transfer::wallet::Builder { - clear_inputs: vec![], - inputs: vec![input], - outputs: vec![ - // Sending money - money_contract::transfer::wallet::BuilderOutputInfo { - value: 1000, - token_id: xdrk_token_id, - public: user_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_contract::transfer::wallet::BuilderOutputInfo { - value: xdrk_supply - 1000, - token_id: xdrk_token_id, - public: dao_keypair.public, - serial: dao_serial, - coin_blind: dao_coin_blind, - spend_hook: *dao_contract::exec::FUNC_ID, - user_data: proposal_bulla, - }, - ], - }; - - let transfer_func_call = builder.build(&zk_bins)?; - - let builder = dao_contract::exec::wallet::Builder { - proposal, - dao: dao_params, - 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: *dao_contract::exec::FUNC_ID, - signature_secret: exec_signature_secret, - }; - let exec_func_call = builder.build(&zk_bins); - let func_calls = vec![transfer_func_call, exec_func_call]; - - let signatures = sign(vec![tx_signature_secret, exec_signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - { - // Now the spend_hook field specifies the function DAO::exec() - // so Money::transfer() must also be combined with DAO::exec() - - assert_eq!(tx.func_calls.len(), 2); - let transfer_func_call = &tx.func_calls[0]; - let transfer_call_data = transfer_func_call.call_data.as_any(); - - assert_eq!( - (&*transfer_call_data).type_id(), - TypeId::of::() - ); - let transfer_call_data = - transfer_call_data.downcast_ref::(); - let transfer_call_data = transfer_call_data.unwrap(); - // At least one input has this field value which means DAO::exec() is invoked. - assert_eq!(transfer_call_data.inputs.len(), 1); - let input = &transfer_call_data.inputs[0]; - assert_eq!(input.revealed.spend_hook, *dao_contract::exec::FUNC_ID); - let user_data_enc = poseidon_hash::<2>([dao_bulla.0, user_data_blind]); - assert_eq!(input.revealed.user_data_enc, user_data_enc); - } - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::exec::FUNC_ID { - debug!("dao_contract::exec::state_transition()"); - - let update = dao_contract::exec::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::exec::validate::state_transition() failed!"); - updates.push(update); - } else if func_call.func_id == *money_contract::transfer::FUNC_ID { - debug!("money_contract::transfer::state_transition()"); - - let update = money_contract::transfer::validate::state_transition(&states, idx, &tx) - .expect("money_contract::transfer::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - // Other stuff - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - //// Wallet - - Ok(()) -} diff --git a/example/dao/src/contract/dao/propose/validate.rs b/example/dao/src/contract/dao/propose/validate.rs index 12d3bd375..227c2f5e5 100644 --- a/example/dao/src/contract/dao/propose/validate.rs +++ b/example/dao/src/contract/dao/propose/validate.rs @@ -1,5 +1,7 @@ use std::any::{Any, TypeId}; +use darkfi_sdk::crypto::MerkleNode; +use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable}; use log::error; use pasta_curves::{ arithmetic::CurveAffine, @@ -8,10 +10,9 @@ use pasta_curves::{ }; use darkfi::{ - crypto::{keypair::PublicKey, merkle_node::MerkleNode, types::DrkCircuitField}, + crypto::{keypair::PublicKey, types::DrkCircuitField}, Error as DarkFiError, }; -use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable}; use crate::{ contract::{dao, dao::State as DaoState, money, money::state::State as MoneyState}, @@ -65,7 +66,7 @@ impl CallDataBase for CallData { *value_coords.x(), *value_coords.y(), self.header.token_commit, - input.merkle_root.0, + input.merkle_root.inner(), *sigpub_coords.x(), *sigpub_coords.y(), ], @@ -77,7 +78,7 @@ impl CallDataBase for CallData { "dao-propose-main".to_string(), vec![ self.header.token_commit, - self.header.dao_merkle_root.0, + self.header.dao_merkle_root.inner(), self.header.proposal_bulla, *total_funds_coords.x(), *total_funds_coords.y(), diff --git a/example/dao/src/contract/dao/propose/wallet.rs b/example/dao/src/contract/dao/propose/wallet.rs index 0b637a6e8..a2d210bc8 100644 --- a/example/dao/src/contract/dao/propose/wallet.rs +++ b/example/dao/src/contract/dao/propose/wallet.rs @@ -1,3 +1,5 @@ +use darkfi_sdk::crypto::MerkleNode; +use darkfi_serial::{SerialDecodable, SerialEncodable}; use halo2_proofs::circuit::Value; use incrementalmerkletree::Hashable; use pasta_curves::{ @@ -10,13 +12,11 @@ use rand::rngs::OsRng; use darkfi::{ crypto::{ keypair::{PublicKey, SecretKey}, - merkle_node::MerkleNode, util::{pedersen_commitment_u64, poseidon_hash}, Proof, }, zk::vm::{Witness, ZkCircuit}, }; -use darkfi_serial::{SerialDecodable, SerialEncodable}; use crate::{ contract::{ @@ -122,7 +122,7 @@ impl Builder { let merkle_root = { let position: u64 = input.leaf_position.into(); - let mut current = MerkleNode(coin); + 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 { @@ -146,7 +146,7 @@ impl Builder { *value_coords.x(), *value_coords.y(), token_commit, - merkle_root.0, + merkle_root.inner(), *sigpub_coords.x(), *sigpub_coords.y(), ]; @@ -239,7 +239,7 @@ impl Builder { ]; let public_inputs = vec![ token_commit, - self.dao_merkle_root.0, + self.dao_merkle_root.inner(), proposal_bulla, *total_funds_coords.x(), *total_funds_coords.y(), diff --git a/example/dao/src/contract/dao/state.rs b/example/dao/src/contract/dao/state.rs index eb98b0e07..b4b77dd9a 100644 --- a/example/dao/src/contract/dao/state.rs +++ b/example/dao/src/contract/dao/state.rs @@ -1,11 +1,10 @@ use std::{any::Any, collections::HashMap}; +use darkfi_sdk::crypto::{constants::MERKLE_DEPTH, MerkleNode, Nullifier}; +use darkfi_serial::{SerialDecodable, SerialEncodable}; use incrementalmerkletree::{bridgetree::BridgeTree, Tree}; use pasta_curves::{group::Group, pallas}; -use darkfi::crypto::{constants::MERKLE_DEPTH, merkle_node::MerkleNode, nullifier::Nullifier}; -use darkfi_serial::{SerialDecodable, SerialEncodable}; - use crate::util::HashableBase; #[derive(Clone, SerialEncodable, SerialDecodable)] @@ -55,14 +54,14 @@ impl State { } pub fn add_dao_bulla(&mut self, bulla: DaoBulla) { - let node = MerkleNode(bulla.0); + let node = MerkleNode::from(bulla.0); self.dao_bullas.push(bulla); self.dao_tree.append(&node); self.dao_roots.push(self.dao_tree.root(0).unwrap()); } pub fn add_proposal_bulla(&mut self, bulla: pallas::Base) { - let node = MerkleNode(bulla); + let node = MerkleNode::from(bulla); //self.proposal_bullas.push(bulla); self.proposal_tree.append(&node); self.proposal_roots.push(self.proposal_tree.root(0).unwrap()); diff --git a/example/dao/src/contract/dao/vote/validate.rs b/example/dao/src/contract/dao/vote/validate.rs index 29132d6ce..3b5df07ca 100644 --- a/example/dao/src/contract/dao/vote/validate.rs +++ b/example/dao/src/contract/dao/vote/validate.rs @@ -1,5 +1,7 @@ use std::any::{Any, TypeId}; +use darkfi_sdk::crypto::{MerkleNode, Nullifier}; +use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable}; use log::error; use pasta_curves::{ arithmetic::CurveAffine, @@ -8,12 +10,9 @@ use pasta_curves::{ }; use darkfi::{ - crypto::{ - keypair::PublicKey, merkle_node::MerkleNode, nullifier::Nullifier, types::DrkCircuitField, - }, + crypto::{keypair::PublicKey, types::DrkCircuitField}, Error as DarkFiError, }; -use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable}; use crate::{ contract::{dao, dao::State as DaoState, money, money::state::State as MoneyState}, @@ -67,11 +66,11 @@ impl CallDataBase for CallData { zk_publics.push(( "dao-vote-burn".to_string(), vec![ - input.nullifier.0, + input.nullifier.inner(), *value_coords.x(), *value_coords.y(), self.header.token_commit, - input.merkle_root.0, + input.merkle_root.inner(), *sigpub_coords.x(), *sigpub_coords.y(), ], diff --git a/example/dao/src/contract/dao/vote/wallet.rs b/example/dao/src/contract/dao/vote/wallet.rs index 75f24944b..1f784ecc2 100644 --- a/example/dao/src/contract/dao/vote/wallet.rs +++ b/example/dao/src/contract/dao/vote/wallet.rs @@ -1,3 +1,5 @@ +use darkfi_sdk::crypto::{MerkleNode, Nullifier}; +use darkfi_serial::{SerialDecodable, SerialEncodable}; use halo2_proofs::circuit::Value; use incrementalmerkletree::Hashable; use log::debug; @@ -11,14 +13,11 @@ use rand::rngs::OsRng; use darkfi::{ crypto::{ keypair::{Keypair, PublicKey, SecretKey}, - merkle_node::MerkleNode, - nullifier::Nullifier, util::{pedersen_commitment_u64, poseidon_hash}, Proof, }, zk::vm::{Witness, ZkCircuit}, }; -use darkfi_serial::{SerialDecodable, SerialEncodable}; use crate::{ contract::{ @@ -128,7 +127,7 @@ impl Builder { let merkle_root = { let position: u64 = input.leaf_position.into(); - let mut current = MerkleNode(coin); + 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 { @@ -155,7 +154,7 @@ impl Builder { *vote_commit_coords.x(), *vote_commit_coords.y(), token_commit, - merkle_root.0, + merkle_root.inner(), *sigpub_coords.x(), *sigpub_coords.y(), ]; @@ -168,7 +167,7 @@ impl Builder { proofs.push(input_proof); let input = Input { - nullifier: Nullifier(nullifier), + nullifier: Nullifier::from(nullifier), vote_commit, merkle_root, signature_public, diff --git a/example/dao/src/contract/money/state.rs b/example/dao/src/contract/money/state.rs index dbc26a7e4..8a65b0797 100644 --- a/example/dao/src/contract/money/state.rs +++ b/example/dao/src/contract/money/state.rs @@ -1,11 +1,9 @@ +use darkfi_sdk::crypto::{constants::MERKLE_DEPTH, MerkleNode, Nullifier}; use incrementalmerkletree::{bridgetree::BridgeTree, Tree}; use darkfi::crypto::{ coin::Coin, - constants::MERKLE_DEPTH, keypair::{PublicKey, SecretKey}, - merkle_node::MerkleNode, - nullifier::Nullifier, }; use super::transfer; diff --git a/example/dao/src/contract/money/transfer/validate.rs b/example/dao/src/contract/money/transfer/validate.rs index 7537d48c3..a4d8011e8 100644 --- a/example/dao/src/contract/money/transfer/validate.rs +++ b/example/dao/src/contract/money/transfer/validate.rs @@ -1,5 +1,7 @@ use std::any::{Any, TypeId}; +use darkfi_sdk::crypto::{MerkleNode, Nullifier}; +use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable}; use incrementalmerkletree::Tree; use log::{debug, error}; use pasta_curves::{group::Group, pallas}; @@ -8,15 +10,12 @@ use darkfi::{ crypto::{ coin::Coin, keypair::PublicKey, - merkle_node::MerkleNode, - nullifier::Nullifier, types::{DrkCircuitField, DrkTokenId, DrkValueBlind, DrkValueCommit}, util::{pedersen_commitment_base, pedersen_commitment_u64}, BurnRevealedValues, MintRevealedValues, }, Error as DarkFiError, }; -use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable}; use crate::{ contract::{ @@ -51,7 +50,7 @@ impl UpdateBase for Update { //// Update merkle tree and witnesses for (coin, enc_note) in self.coins.into_iter().zip(self.enc_notes.into_iter()) { // Add the new coins to the Merkle tree - let node = MerkleNode(coin.0); + let node = MerkleNode::from(coin.0); state.tree.append(&node); // Keep track of all Merkle roots that have existed diff --git a/example/dao/src/contract/money/transfer/wallet.rs b/example/dao/src/contract/money/transfer/wallet.rs index afbb7f1d1..5cabb8baf 100644 --- a/example/dao/src/contract/money/transfer/wallet.rs +++ b/example/dao/src/contract/money/transfer/wallet.rs @@ -1,3 +1,5 @@ +use darkfi_sdk::crypto::MerkleNode; +use darkfi_serial::{SerialDecodable, SerialEncodable}; use pasta_curves::group::ff::Field; use rand::rngs::OsRng; @@ -5,7 +7,6 @@ use darkfi::{ crypto::{ burn_proof::create_burn_proof, keypair::{PublicKey, SecretKey}, - merkle_node::MerkleNode, mint_proof::create_mint_proof, types::{ DrkCoinBlind, DrkSerial, DrkSpendHook, DrkTokenId, DrkUserData, DrkUserDataBlind, @@ -14,7 +15,6 @@ use darkfi::{ }, Result, }; -use darkfi_serial::{SerialDecodable, SerialEncodable}; use crate::{ contract::money::{ diff --git a/example/dao/util.rs b/example/dao/util.rs deleted file mode 100644 index e5782a016..000000000 --- a/example/dao/util.rs +++ /dev/null @@ -1,236 +0,0 @@ -use std::{any::Any, collections::HashMap, hash::Hasher}; - -use darkfi_serial::Encodable; -use lazy_static::lazy_static; -use log::debug; -use pasta_curves::{ - group::ff::{Field, PrimeField}, - pallas, -}; -use rand::rngs::OsRng; - -use darkfi::{ - crypto::{ - keypair::{PublicKey, SecretKey}, - proof::{ProvingKey, VerifyingKey}, - schnorr::{SchnorrPublic, SchnorrSecret, Signature}, - types::DrkCircuitField, - Proof, - }, - zk::{vm::ZkCircuit, vm_stack::empty_witnesses}, - zkas::decoder::ZkBinary, -}; - -// TODO: base58 encoding/ decoding - -lazy_static! { - pub static ref XDRK_ID: pallas::Base = pallas::Base::random(&mut OsRng); -} - -lazy_static! { - pub static ref GDRK_ID: pallas::Base = pallas::Base::random(&mut OsRng); -} - -#[derive(Eq, PartialEq)] -pub struct HashableBase(pub pallas::Base); - -impl std::hash::Hash for HashableBase { - fn hash(&self, state: &mut H) { - let bytes = self.0.to_repr(); - bytes.hash(state); - } -} - -pub struct ZkBinaryContractInfo { - pub k_param: u32, - pub bincode: ZkBinary, - pub proving_key: ProvingKey, - pub verifying_key: VerifyingKey, -} -pub struct ZkNativeContractInfo { - pub proving_key: ProvingKey, - pub verifying_key: VerifyingKey, -} - -pub enum ZkContractInfo { - Binary(ZkBinaryContractInfo), - Native(ZkNativeContractInfo), -} - -pub struct ZkContractTable { - // Key will be a hash of zk binary contract on chain - table: HashMap, -} - -impl ZkContractTable { - pub fn new() -> Self { - Self { table: HashMap::new() } - } - - pub fn add_contract(&mut self, key: String, bincode: ZkBinary, k_param: u32) { - let witnesses = empty_witnesses(&bincode); - let circuit = ZkCircuit::new(witnesses, bincode.clone()); - let proving_key = ProvingKey::build(k_param, &circuit); - let verifying_key = VerifyingKey::build(k_param, &circuit); - let info = ZkContractInfo::Binary(ZkBinaryContractInfo { - k_param, - bincode, - proving_key, - verifying_key, - }); - self.table.insert(key, info); - } - - pub fn add_native( - &mut self, - key: String, - proving_key: ProvingKey, - verifying_key: VerifyingKey, - ) { - self.table.insert( - key, - ZkContractInfo::Native(ZkNativeContractInfo { proving_key, verifying_key }), - ); - } - - pub fn lookup(&self, key: &String) -> Option<&ZkContractInfo> { - self.table.get(key) - } -} - -pub struct Transaction { - pub func_calls: Vec, - pub signatures: Vec, -} - -impl Transaction { - /// Verify ZK contracts for the entire tx - /// In real code, we could parallelize this for loop - /// TODO: fix use of unwrap with Result type stuff - pub fn zk_verify(&self, zk_bins: &ZkContractTable) { - for func_call in &self.func_calls { - let proofs_public_vals = &func_call.call_data.zk_public_values(); - - assert_eq!( - proofs_public_vals.len(), - func_call.proofs.len(), - "proof_public_vals.len()={} and func_call.proofs.len()={} do not match", - proofs_public_vals.len(), - func_call.proofs.len() - ); - for (i, (proof, (key, public_vals))) in - func_call.proofs.iter().zip(proofs_public_vals.iter()).enumerate() - { - match zk_bins.lookup(key).unwrap() { - ZkContractInfo::Binary(info) => { - let verifying_key = &info.verifying_key; - let verify_result = proof.verify(&verifying_key, public_vals); - assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); - } - ZkContractInfo::Native(info) => { - let verifying_key = &info.verifying_key; - let verify_result = proof.verify(&verifying_key, public_vals); - assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); - } - }; - debug!(target: "demo", "zk_verify({}) passed [i={}]", key, i); - } - } - } - - pub fn verify_sigs(&self) { - let mut unsigned_tx_data = vec![]; - for (i, (func_call, signature)) in - self.func_calls.iter().zip(self.signatures.clone()).enumerate() - { - func_call.encode(&mut unsigned_tx_data).expect("failed to encode data"); - let signature_pub_keys = func_call.call_data.signature_public_keys(); - for signature_pub_key in signature_pub_keys { - let verify_result = signature_pub_key.verify(&unsigned_tx_data[..], &signature); - assert!(verify_result, "verify sigs[{}] failed", i); - } - debug!(target: "demo", "verify_sigs({}) passed", i); - } - } -} - -pub fn sign(signature_secrets: Vec, func_calls: &Vec) -> Vec { - let mut signatures = vec![]; - let mut unsigned_tx_data = vec![]; - for (_i, (signature_secret, func_call)) in - signature_secrets.iter().zip(func_calls.iter()).enumerate() - { - func_call.encode(&mut unsigned_tx_data).expect("failed to encode data"); - let signature = signature_secret.sign(&unsigned_tx_data[..]); - signatures.push(signature); - } - signatures -} - -type ContractId = pallas::Base; -type FuncId = pallas::Base; - -pub struct FuncCall { - pub contract_id: ContractId, - pub func_id: FuncId, - pub call_data: Box, - pub proofs: Vec, -} - -impl Encodable for FuncCall { - fn encode(&self, mut w: W) -> std::result::Result { - let mut len = 0; - len += self.contract_id.encode(&mut w)?; - len += self.func_id.encode(&mut w)?; - len += self.proofs.encode(&mut w)?; - len += self.call_data.encode_bytes(&mut w)?; - Ok(len) - } -} - -pub trait CallDataBase { - // Public values for verifying the proofs - // Needed so we can convert internal types so they can be used in Proof::verify() - fn zk_public_values(&self) -> Vec<(String, Vec)>; - - // For upcasting to CallData itself so it can be read in state_transition() - fn as_any(&self) -> &dyn Any; - - // Public keys we will use to verify transaction signatures. - fn signature_public_keys(&self) -> Vec; - - fn encode_bytes( - &self, - writer: &mut dyn std::io::Write, - ) -> std::result::Result; -} - -//type GenericContractState = Box; -type GenericContractState = Box; - -pub struct StateRegistry { - pub states: HashMap, -} - -impl StateRegistry { - pub fn new() -> Self { - Self { states: HashMap::new() } - } - - pub fn register(&mut self, contract_id: ContractId, state: GenericContractState) { - debug!(target: "StateRegistry::register()", "contract_id: {:?}", contract_id); - self.states.insert(HashableBase(contract_id), state); - } - - pub fn lookup_mut<'a, S: 'static>(&'a mut self, contract_id: ContractId) -> Option<&'a mut S> { - self.states.get_mut(&HashableBase(contract_id)).and_then(|state| state.downcast_mut()) - } - - pub fn lookup<'a, S: 'static>(&'a self, contract_id: ContractId) -> Option<&'a S> { - self.states.get(&HashableBase(contract_id)).and_then(|state| state.downcast_ref()) - } -} - -pub trait UpdateBase { - fn apply(self: Box, states: &mut StateRegistry); -}