Bladeren bron

daod: create WalletCache for money_contract::State, which keeps track of received coins, notes and witnesses them in the merkle tree.

narodnik 4 jaren geleden
bovenliggende
commit
577868e2ab

+ 1 - 1
bin/daod/src/dao_contract/mint/validate.rs

@@ -33,7 +33,7 @@ pub struct Update {
 
 pub fn apply(states: &mut StateRegistry, update: Update) {
     // Lookup dao_contract state from registry
-    let state = states.lookup_mut::<State>(&"dao_contract".to_string()).unwrap();
+    let state = states.lookup_mut::<State>(&"DAO".to_string()).unwrap();
     // Add dao_bulla to state.dao_bullas
     state.add_bulla(update.dao_bulla);
 }

+ 0 - 5
bin/daod/src/dao_contract/mint/wallet.rs

@@ -95,11 +95,6 @@ impl Builder {
         let mint_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
             .expect("DAO::mint() proving error!");
 
-        // [x] 1. move proving key to zkbins table (and k value)
-        // [x] 2. do verification of zk proofs in main code
-        // [ ] 3. implement apply(update) function
-
-        // Return call data
         let call_data = CallData { dao_bulla };
         FuncCall {
             contract_id: "DAO".to_string(),

+ 2 - 0
bin/daod/src/dao_contract/propose/mod.rs

@@ -0,0 +1,2 @@
+pub mod validate;
+pub mod wallet;

+ 43 - 0
bin/daod/src/dao_contract/propose/validate.rs

@@ -0,0 +1,43 @@
+use crate::{
+    dao_contract::{DaoBulla, State},
+    demo::{CallDataBase, StateRegistry, Transaction},
+};
+use darkfi::crypto::types::DrkCircuitField;
+use std::any::{Any, TypeId};
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum Error {
+    #[error("Malformed packet")]
+    MalformedPacket,
+}
+type Result<T> = std::result::Result<T, Error>;
+
+pub struct CallData {}
+
+impl CallDataBase for CallData {
+    fn zk_public_values(&self) -> Vec<Vec<DrkCircuitField>> {
+        vec![]
+    }
+
+    fn zk_proof_addrs(&self) -> Vec<String> {
+        vec![]
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+}
+
+pub fn state_transition(
+    states: &StateRegistry,
+    func_call_index: usize,
+    parent_tx: &Transaction,
+) -> Result<Update> {
+    Ok(Update {})
+}
+
+pub struct Update {}
+
+pub fn apply(states: &mut StateRegistry, update: Update) {
+    let state = states.lookup_mut::<State>(&"DAO".to_string()).unwrap();
+}

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

@@ -0,0 +1,71 @@
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{ff::Field, Curve},
+    pallas,
+};
+
+use darkfi::{
+    crypto::{
+        burn_proof::create_burn_proof,
+        keypair::{PublicKey, SecretKey},
+        merkle_node::MerkleNode,
+        mint_proof::create_mint_proof,
+        proof::ProvingKey,
+        schnorr::SchnorrSecret,
+        types::{
+            DrkCircuitField, DrkCoinBlind, DrkSerial, DrkSpendHook, DrkTokenId, DrkUserData,
+            DrkUserDataBlind, DrkValueBlind,
+        },
+        Proof,
+    },
+    util::serial::{Encodable, SerialDecodable, SerialEncodable},
+    zk::vm::{Witness, ZkCircuit},
+};
+
+use crate::{
+    dao_contract::propose::validate::CallData,
+    demo::{CallDataBase, FuncCall, ZkContractInfo, ZkContractTable},
+    money_contract,
+};
+
+pub struct Input {
+    //pub leaf_position: incrementalmerkletree::Position,
+    //pub merkle_path: Vec<MerkleNode>,
+    pub secret: SecretKey,
+    pub note: money_contract::transfer::wallet::Note,
+}
+
+pub struct Proposal {
+    pub dest: PublicKey,
+    pub amount: u64,
+    pub serial: pallas::Base,
+    pub token_id: pallas::Base,
+    pub blind: pallas::Base,
+}
+
+pub struct DaoParams {
+    pub dao_proposer_limit: u64,
+    pub dao_quorum: u64,
+    pub dao_approval_ratio: u64,
+    pub gov_token_id: pallas::Base,
+    pub dao_public_key: PublicKey,
+    pub dao_bulla_blind: pallas::Base,
+}
+
+pub struct Builder {
+    pub inputs: Vec<Input>,
+    pub proposal: Proposal,
+    pub dao: DaoParams,
+}
+
+impl Builder {
+    pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
+        let call_data = CallData {};
+        FuncCall {
+            contract_id: "DAO".to_string(),
+            func_id: "DAO::propose()".to_string(),
+            call_data: Box::new(call_data),
+            proofs: vec![],
+        }
+    }
+}

+ 129 - 107
bin/daod/src/demo.rs

@@ -240,19 +240,14 @@ pub async fn demo() -> Result<()> {
 
     ///////////////////////////////////////////////////
 
-    let money_state = Box::new(money_contract::state::State {
-        tree: BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(100),
-        merkle_roots: vec![],
-        nullifiers: vec![],
-        cashier_signature_public,
-        faucet_signature_public,
-    });
-    states.register("money_contract".to_string(), money_state);
+    let money_state =
+        money_contract::state::State::new(cashier_signature_public, faucet_signature_public);
+    states.register("Money".to_string(), money_state);
 
     /////////////////////////////////////////////////////
 
     let dao_state = dao_contract::State::new();
-    states.register("dao_contract".to_string(), dao_state);
+    states.register("DAO".to_string(), dao_state);
 
     // For this demo lets create 10 random preexisting DAO bullas
     for _ in 0..10 {
@@ -263,6 +258,8 @@ pub async fn demo() -> Result<()> {
     ////// Create the DAO bulla
     /////////////////////////////////////////////////////
 
+    //// Wallet
+
     //// Setup the DAO
     let dao_keypair = Keypair::random(&mut OsRng);
     let dao_bulla_blind = pallas::Base::random(&mut OsRng);
@@ -280,6 +277,8 @@ pub async fn demo() -> Result<()> {
 
     let tx = Transaction { func_calls: vec![func_call] };
 
+    //// Validator
+
     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
@@ -310,6 +309,8 @@ pub async fn demo() -> Result<()> {
     //// and send it all to the DAO directly
     ///////////////////////////////////////////////////
 
+    //// Wallet
+
     // Address of deployed contract in our example is hook_dao_exec
     // This field is public, you can see it's being sent to a DAO
     // but nothing else is visible.
@@ -345,11 +346,12 @@ pub async fn demo() -> Result<()> {
 
     let tx = Transaction { func_calls: vec![func_call] };
 
-    //    let _note = tx.outputs[0].enc_note.decrypt(&keypair.secret)?;
+    //// Validator
+
     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::transfer()" {
+        if func_call.func_id == "Money::transfer()" {
             debug!("money_contract::transfer::state_transition()");
 
             let update = money_contract::transfer::validate::state_transition(&states, idx, &tx)
@@ -360,60 +362,59 @@ pub async fn demo() -> Result<()> {
 
     tx.zk_verify(&zk_bins);
 
-    // Wallet stuff
+    //// Wallet
     // DAO reads the money received from the encrypted note
-    for (idx, func_call) in tx.func_calls.iter().enumerate() {
-        if func_call.func_id == "money::transfer()" {
-            let call_data = func_call.call_data.as_any();
-            assert_eq!(
-                (&*call_data).type_id(),
-                TypeId::of::<money_contract::transfer::validate::CallData>()
-            );
-            let call_data =
-                call_data.downcast_ref::<money_contract::transfer::validate::CallData>().unwrap();
-
-            assert_eq!(call_data.outputs.len(), 1);
-            let output = &call_data.outputs[0];
-            let enc_note = &output.enc_note;
-            // Try to decrypt the note
-            let note: money_contract::transfer::wallet::Note =
-                enc_note.decrypt(&dao_keypair.secret).unwrap();
-
-            // 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(note.value),
-                note.token_id,
-                note.serial,
-                note.spend_hook,
-                note.user_data,
-                note.coin_blind,
-            ]);
-            assert_eq!(coin, output.revealed.coin.0);
 
-            assert_eq!(note.spend_hook, hook_dao_exec);
-            assert_eq!(note.user_data, dao_bulla.0);
-
-            debug!("DAO received a coin worth {} xDRK", note.value);
-        }
-    }
+    let dao_recv = {
+        let state = states.lookup_mut::<money_contract::State>(&"Money".to_string()).unwrap();
+        let mut recv_coins = state.wallet_cache.get_received(&dao_keypair.secret);
+        assert_eq!(recv_coins.len(), 1);
+        let recv_coin = recv_coins.pop().unwrap();
+        let note = &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(note.value),
+            note.token_id,
+            note.serial,
+            note.spend_hook,
+            note.user_data,
+            note.coin_blind,
+        ]);
+        assert_eq!(coin, recv_coin.coin.0);
+
+        assert_eq!(note.spend_hook, hook_dao_exec);
+        assert_eq!(note.user_data, dao_bulla.0);
+
+        debug!("DAO received a coin worth {} xDRK", note.value);
+
+        recv_coin
+    };
 
     ///////////////////////////////////////////////////
     //// Mint the governance token
     //// Send it to three hodlers
     ///////////////////////////////////////////////////
 
+    //// Wallet
+
     // Hodler 1
-    let keypair1 = Keypair::random(&mut OsRng);
+    let gov_keypair_1 = Keypair::random(&mut OsRng);
     // Hodler 2
-    let keypair2 = Keypair::random(&mut OsRng);
+    let gov_keypair_2 = Keypair::random(&mut OsRng);
     // Hodler 3: the tiebreaker
-    let keypair3 = Keypair::random(&mut OsRng);
+    let gov_keypair_3 = Keypair::random(&mut OsRng);
+
+    let state = states.lookup_mut::<money_contract::State>(&"Money".to_string()).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 keypairs = vec![keypair1, keypair2, keypair3];
+    let gov_keypairs = vec![gov_keypair_1, gov_keypair_2, gov_keypair_3];
 
     // We don't use this because money-transfer expects a cashier.
     // let signature_secret = SecretKey::random(&mut OsRng);
@@ -425,7 +426,7 @@ pub async fn demo() -> Result<()> {
     let output1 = money_contract::transfer::wallet::BuilderOutputInfo {
         value: 400000,
         token_id: gdrk_token_id,
-        public: keypair1.public,
+        public: gov_keypair_1.public,
         spend_hook,
         user_data,
     };
@@ -433,7 +434,7 @@ pub async fn demo() -> Result<()> {
     let output2 = money_contract::transfer::wallet::BuilderOutputInfo {
         value: 400000,
         token_id: gdrk_token_id,
-        public: keypair2.public,
+        public: gov_keypair_2.public,
         spend_hook,
         user_data,
     };
@@ -441,7 +442,7 @@ pub async fn demo() -> Result<()> {
     let output3 = money_contract::transfer::wallet::BuilderOutputInfo {
         value: 200000,
         token_id: gdrk_token_id,
-        public: keypair3.public,
+        public: gov_keypair_3.public,
         spend_hook,
         user_data,
     };
@@ -462,10 +463,12 @@ pub async fn demo() -> Result<()> {
 
     let tx = Transaction { func_calls: vec![func_call] };
 
+    //// Validator
+
     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::transfer()" {
+        if func_call.func_id == "Money::transfer()" {
             debug!("money_contract::transfer::state_transition()");
 
             let update = money_contract::transfer::validate::state_transition(&states, idx, &tx)
@@ -476,57 +479,44 @@ pub async fn demo() -> Result<()> {
 
     tx.zk_verify(&zk_bins);
 
-    // We need this to keep track of Notes
-    let mut notes: [Option<money_contract::transfer::wallet::Note>; 3] = [None, None, None];
-
-    //// Wallet stuff
-    //// Holders read the money received from the encrypted note
-    for (i, key) in keypairs.iter().enumerate() {
-        for (idx, func_call) in tx.func_calls.iter().enumerate() {
-            if func_call.func_id == "money::transfer()" {
-                let call_data = func_call.call_data.as_any();
-                assert_eq!(
-                    (&*call_data).type_id(),
-                    TypeId::of::<money_contract::transfer::validate::CallData>()
-                );
-                let call_data = call_data
-                    .downcast_ref::<money_contract::transfer::validate::CallData>()
-                    .unwrap();
-
-                assert_eq!(call_data.outputs.len(), 3);
-
-                for output in &call_data.outputs {
-                    let enc_note = &output.enc_note;
-                    // Try to decrypt the note
-                    let note: darkfi::Result<money_contract::transfer::wallet::Note> =
-                        enc_note.decrypt(&key.secret);
-
-                    match note {
-                        Ok(note) => {
-                            // Check the actual coin received is valid before accepting it
-                            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, output.revealed.coin.0);
-
-                            debug!("Holder{} received a coin worth {} gDRK", i, note.value);
-
-                            notes[i] = Some(note);
-                        }
-                        Err(e) => continue,
-                    }
-                }
-            }
-        }
+    //// 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::State>(&"Money".to_string()).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:
@@ -545,8 +535,40 @@ pub async fn demo() -> Result<()> {
     // meet a criteria for a minimum number of gov tokens
     ///////////////////////////////////////////////////
 
+    //// Wallet
+
     // TODO: look into proposal expiry once time for voting has finished
 
+    let user_keypair = Keypair::random(&mut OsRng);
+
+    // TODO: is it possible for an invalid transfer() to be constructed on exec()?
+    //       need to look into this
+    let input = dao_contract::propose::wallet::Input {
+        secret: gov_keypair_1.secret,
+        note: gov_recv[0].note.clone(),
+    };
+
+    let builder = dao_contract::propose::wallet::Builder {
+        inputs: vec![input],
+        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),
+        },
+        dao: dao_contract::propose::wallet::DaoParams {
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio,
+            gov_token_id: gdrk_token_id,
+            dao_public_key: dao_keypair.public,
+            dao_bulla_blind,
+        },
+    };
+
+    let func_call = builder.build(&zk_bins);
+
     Ok(())
 }
 

+ 75 - 2
bin/daod/src/money_contract/state.rs

@@ -2,16 +2,73 @@ use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
 
 use darkfi::{
     crypto::{
-        constants::MERKLE_DEPTH, keypair::PublicKey, merkle_node::MerkleNode, nullifier::Nullifier,
+        coin::Coin,
+        constants::MERKLE_DEPTH,
+        keypair::{Keypair, PublicKey, SecretKey},
+        merkle_node::MerkleNode,
+        nullifier::Nullifier,
         proof::VerifyingKey,
     },
     node::state::{ProgramState, StateUpdate},
 };
 
+use super::transfer;
+use crate::note::EncryptedNote2;
+
+type MerkleTree = BridgeTree<MerkleNode, MERKLE_DEPTH>;
+
+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
+    cache: Vec<(SecretKey, Vec<OwnCoin>)>,
+}
+
+impl WalletCache {
+    pub fn new() -> Self {
+        Self { cache: Vec::new() }
+    }
+
+    pub fn track(&mut self, secret: SecretKey) {
+        self.cache.push((secret, Vec::new()));
+    }
+
+    /// Get all coins received by this secret key
+    pub fn get_received(&mut self, secret: &SecretKey) -> Vec<OwnCoin> {
+        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::replace(own_coins, Vec::new())
+            }
+        }
+        unreachable!();
+    }
+
+    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: BridgeTree<MerkleNode, MERKLE_DEPTH>,
+    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<MerkleNode>,
@@ -23,9 +80,25 @@ pub struct State {
 
     /// 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<Self> {
+        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
     }

+ 5 - 4
bin/daod/src/money_contract/transfer/validate.rs

@@ -52,7 +52,7 @@ pub struct Update {
 }
 
 pub fn apply(states: &mut StateRegistry, mut update: Update) {
-    let state = states.lookup_mut::<State>(&"money_contract".to_string()).unwrap();
+    let state = states.lookup_mut::<State>(&"Money".to_string()).unwrap();
 
     // Extend our list of nullifiers with the ones from the update
     state.nullifiers.append(&mut update.nullifiers);
@@ -65,6 +65,8 @@ pub fn apply(states: &mut StateRegistry, mut update: Update) {
 
         // 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);
     }
 }
 
@@ -85,9 +87,8 @@ pub fn state_transition(
     // This will be inside wasm so unwrap is fine.
     let call_data = call_data.unwrap();
 
-    let state = states
-        .lookup::<State>(&"money_contract".to_string())
-        .expect("Return type is not of type State");
+    let state =
+        states.lookup::<State>(&"Money".to_string()).expect("Return type is not of type State");
 
     // Code goes here
     for (i, input) in call_data.clear_inputs.iter().enumerate() {

+ 3 - 3
bin/daod/src/money_contract/transfer/wallet/builder.rs

@@ -25,7 +25,7 @@ use crate::{
     note,
 };
 
-#[derive(SerialEncodable, SerialDecodable)]
+#[derive(Clone, SerialEncodable, SerialDecodable)]
 pub struct Note {
     pub serial: DrkSerial,
     pub value: u64,
@@ -235,8 +235,8 @@ impl Builder {
         let call_data = CallData { clear_inputs, inputs, outputs: partial.outputs };
 
         Ok(FuncCall {
-            contract_id: "money".to_string(),
-            func_id: "money::transfer()".to_string(),
+            contract_id: "Money".to_string(),
+            func_id: "Money::transfer()".to_string(),
             call_data: Box::new(call_data),
             proofs: partial.proofs,
         })

+ 3 - 0
bin/daod/src/money_contract/transfer/wallet/mod.rs

@@ -0,0 +1,3 @@
+pub mod builder;
+pub mod partial;
+pub use builder::{Builder, BuilderClearInputInfo, BuilderInputInfo, BuilderOutputInfo, Note};