first part of dao exec, create the tx and apply spend_hook rule

This commit is contained in:
narodnik
2022-08-25 11:15:20 +02:00
parent a6b9620b48
commit 119c5ebe56
4 changed files with 205 additions and 34 deletions

View File

@@ -4,6 +4,8 @@ pub mod mint;
pub mod propose;
// vote{}
pub mod vote;
// exec{}
pub mod exec;
pub mod state;

View File

@@ -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<Self>, 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_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;
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_contract::State>(&"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::<money_contract::transfer::validate::CallData>()
);
let transfer_call_data =
transfer_call_data.downcast_ref::<money_contract::transfer::validate::CallData>();
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(())
}

View File

@@ -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<EncryptedNote2>,
}
pub fn apply(states: &mut StateRegistry, mut update: Update) {
let state = states.lookup_mut::<State>(&"Money".to_string()).unwrap();
impl UpdateBase for Update {
fn apply(mut self: Box<Self>, states: &mut StateRegistry) {
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);
// 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<Update> {
) -> Result<Box<dyn UpdateBase>> {
// 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),

View File

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