From 119c5ebe56c37556399bb971848cc2f0a1a5b771 Mon Sep 17 00:00:00 2001 From: narodnik Date: Thu, 25 Aug 2022 11:15:20 +0200 Subject: [PATCH] first part of dao exec, create the tx and apply spend_hook rule --- bin/daod/src/dao_contract/mod.rs | 2 + bin/daod/src/demo.rs | 168 ++++++++++++++++-- .../src/money_contract/transfer/validate.rs | 63 +++++-- .../money_contract/transfer/wallet/builder.rs | 6 +- 4 files changed, 205 insertions(+), 34 deletions(-) diff --git a/bin/daod/src/dao_contract/mod.rs b/bin/daod/src/dao_contract/mod.rs index d40b130a2..6ba2e61a5 100644 --- a/bin/daod/src/dao_contract/mod.rs +++ b/bin/daod/src/dao_contract/mod.rs @@ -4,6 +4,8 @@ pub mod mint; pub mod propose; // vote{} pub mod vote; +// exec{} +pub mod exec; pub mod state; diff --git a/bin/daod/src/demo.rs b/bin/daod/src/demo.rs index 1d62020c6..630a317dc 100644 --- a/bin/daod/src/demo.rs +++ b/bin/daod/src/demo.rs @@ -114,7 +114,6 @@ impl Transaction { for (i, (proof, (key, public_vals))) in func_call.proofs.iter().zip(proofs_public_vals.iter()).enumerate() { - debug!(target: "demo", "Tranaction::zk_verify i: {}, key: {}", i, key); match zk_bins.lookup(key).unwrap() { ZkContractInfo::Binary(info) => { let verifying_key = &info.verifying_key; @@ -127,7 +126,7 @@ impl Transaction { assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); } }; - debug!("zk_verify({}) passed", key); + debug!(target: "demo", "zk_verify({}) passed [i={}]", key, i); } } } @@ -178,6 +177,10 @@ impl StateRegistry { } } +pub trait UpdateBase { + fn apply(self: Box, states: &mut StateRegistry); +} + /////////////////////////////////////////////////// ///// Example contract /////////////////////////////////////////////////// @@ -196,16 +199,21 @@ pub async fn example() -> Result<()> { let example_state = example_contract::state::State::new(); states.register("Example".to_string(), example_state); + //// Wallet + let foo = example_contract::foo::wallet::Foo { a: 5, b: 10 }; let builder = example_contract::foo::wallet::Builder { foo }; let func_call = builder.build(&zk_bins); let tx = Transaction { func_calls: vec![func_call] }; + //// Validator + for (idx, func_call) in tx.func_calls.iter().enumerate() { if func_call.func_id == "Example::foo()" { debug!("example_contract::foo::state_transition()"); + // TODO: separate this 2 things let update = example_contract::foo::validate::state_transition(&states, idx, &tx) .expect("example_contract::foo::validate::state_transition() failed!"); example_contract::foo::validate::apply(&mut states, update); @@ -216,6 +224,7 @@ pub async fn example() -> Result<()> { Ok(()) } + pub async fn demo() -> Result<()> { // Example smart contract //// TODO: this will be moved to a different file @@ -398,6 +407,8 @@ pub async fn demo() -> Result<()> { 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, }], @@ -417,7 +428,7 @@ pub async fn demo() -> Result<()> { let update = money_contract::transfer::validate::state_transition(&states, idx, &tx) .expect("money_contract::transfer::validate::state_transition() failed!"); - money_contract::transfer::validate::apply(&mut states, update); + update.apply(&mut states); } } @@ -429,8 +440,8 @@ pub async fn demo() -> Result<()> { let state = states.lookup_mut::(&"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; + 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 @@ -438,19 +449,19 @@ pub async fn demo() -> Result<()> { 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, + 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, recv_coin.coin.0); + assert_eq!(coin, dao_recv_coin.coin.0); - assert_eq!(note.spend_hook, hook_dao_exec); - assert_eq!(note.user_data, dao_bulla.0); + assert_eq!(treasury_note.spend_hook, hook_dao_exec); + assert_eq!(treasury_note.user_data, dao_bulla.0); - debug!("DAO received a coin worth {} xDRK", note.value); + debug!("DAO received a coin worth {} xDRK", treasury_note.value); /////////////////////////////////////////////////// //// Mint the governance token @@ -485,6 +496,8 @@ pub async fn demo() -> Result<()> { 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, }; @@ -493,6 +506,8 @@ pub async fn demo() -> Result<()> { 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, }; @@ -501,6 +516,8 @@ pub async fn demo() -> Result<()> { 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, }; @@ -531,7 +548,7 @@ pub async fn demo() -> Result<()> { let update = money_contract::transfer::validate::state_transition(&states, idx, &tx) .expect("money_contract::transfer::validate::state_transition() failed!"); - money_contract::transfer::validate::apply(&mut states, update); + update.apply(&mut states); } } @@ -1006,5 +1023,124 @@ pub async fn demo() -> Result<()> { assert!(total_value_commit == pedersen_commitment_u64(total_votes, total_value_blinds)); assert!(total_vote_commit == pedersen_commitment_u64(win_votes, total_vote_blinds)); + /////////////////////////////////////////////////// + // 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 (treasury_leaf_position, treasury_merkle_path) = { + let state = states.lookup::(&"Money".to_string()).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 builder = money_contract::transfer::wallet::Builder { + clear_inputs: vec![], + inputs: vec![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, + }], + outputs: vec![ + // Sending money + money_contract::transfer::wallet::BuilderOutputInfo { + value: 1000, + token_id: xdrk_token_id, + public: user_keypair.public, + serial: user_serial, + coin_blind: user_coin_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: hook_dao_exec, + user_data: dao_bulla.0, + }, + ], + }; + + let transfer_func_call = builder.build(&zk_bins)?; + + let foo = dao_contract::exec::wallet::Foo { a: 5, b: 10 }; + + let builder = dao_contract::exec::wallet::Builder { foo }; + let exec_func_call = builder.build(&zk_bins); + + let tx = Transaction { func_calls: vec![transfer_func_call, exec_func_call] }; + + { + // 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, hook_dao_exec); + 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::exec()" { + 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::transfer()" { + 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); + // TODO: signature verification + + //// Wallet + Ok(()) } diff --git a/bin/daod/src/money_contract/transfer/validate.rs b/bin/daod/src/money_contract/transfer/validate.rs index d8a11eb79..ba9eabe88 100644 --- a/bin/daod/src/money_contract/transfer/validate.rs +++ b/bin/daod/src/money_contract/transfer/validate.rs @@ -3,7 +3,7 @@ use std::any::{Any, TypeId}; use incrementalmerkletree::Tree; use log::{debug, error}; -use pasta_curves::group::Group; +use pasta_curves::{group::Group, pallas}; use darkfi::{ crypto::{ @@ -22,7 +22,7 @@ use darkfi::{ }; use crate::{ - demo::{CallDataBase, StateRegistry, Transaction}, + demo::{CallDataBase, StateRegistry, Transaction, UpdateBase}, money_contract::state::State, note::EncryptedNote2, }; @@ -41,22 +41,24 @@ pub struct Update { pub enc_notes: Vec, } -pub fn apply(states: &mut StateRegistry, mut update: Update) { - let state = states.lookup_mut::(&"Money".to_string()).unwrap(); +impl UpdateBase for Update { + fn apply(mut self: Box, states: &mut StateRegistry) { + let state = states.lookup_mut::(&"Money".to_string()).unwrap(); - // Extend our list of nullifiers with the ones from the update - state.nullifiers.append(&mut update.nullifiers); + // 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 update.coins.into_iter().zip(update.enc_notes.into_iter()) { - // Add the new coins to the Merkle tree - let node = MerkleNode(coin.0); - state.tree.append(&node); + //// 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); + state.tree.append(&node); - // Keep track of all Merkle roots that have existed - state.merkle_roots.push(state.tree.root(0).unwrap()); + // 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); + state.wallet_cache.try_decrypt_note(coin, enc_note, &mut state.tree); + } } } @@ -64,7 +66,7 @@ pub fn state_transition( states: &StateRegistry, func_call_index: usize, parent_tx: &Transaction, -) -> Result { +) -> 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"); @@ -105,6 +107,32 @@ pub fn state_transition( 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::exec()" { + 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; @@ -140,7 +168,7 @@ pub fn state_transition( enc_notes.push(output.enc_note.clone()); } - Ok(Update { nullifiers, coins, enc_notes }) + Ok(Box::new(Update { nullifiers, coins, enc_notes })) } /// A DarkFi transaction @@ -316,6 +344,9 @@ pub enum VerifyFailed { #[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), diff --git a/bin/daod/src/money_contract/transfer/wallet/builder.rs b/bin/daod/src/money_contract/transfer/wallet/builder.rs index 288127533..3bd1a13ac 100644 --- a/bin/daod/src/money_contract/transfer/wallet/builder.rs +++ b/bin/daod/src/money_contract/transfer/wallet/builder.rs @@ -59,6 +59,8 @@ 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, } @@ -165,8 +167,8 @@ impl Builder { }; output_blinds.push(value_blind); - let serial = DrkSerial::random(&mut OsRng); - let coin_blind = DrkCoinBlind::random(&mut OsRng); + 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 {