/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
use darkfi::{
blockchain::Blockchain,
consensus::{TESTNET_GENESIS_HASH_BYTES, TESTNET_GENESIS_TIMESTAMP},
crypto::{
coin::Coin,
proof::{ProvingKey, VerifyingKey},
types::{DrkSpendHook, DrkUserData, DrkValue},
},
runtime::vm_runtime::Runtime,
zk::circuit::{BurnContract, MintContract},
zkas::decoder::ZkBinary,
Result,
};
use darkfi_sdk::{
crypto::{
constants::MERKLE_DEPTH, pedersen::pedersen_commitment_u64, poseidon_hash,
schnorr::SchnorrSecret, ContractId, Keypair, MerkleNode, MerkleTree, PublicKey, SecretKey,
TokenId,
},
tx::ContractCall,
};
use darkfi_serial::{deserialize, serialize, Decodable, Encodable, WriteExt};
use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
use log::{debug, error};
use pasta_curves::{
arithmetic::CurveAffine,
group::{ff::Field, Curve},
pallas,
};
use rand::rngs::OsRng;
use std::{
any::{Any, TypeId},
io::Cursor,
time::Instant,
};
use dao_contract::{DaoFunction, DaoMintParams};
use money_contract::{MoneyFunction, MoneyTransferParams};
use crate::{
contract::{dao, example, money},
note::EncryptedNote2,
schema::WalletCache,
tx::Transaction,
util::{StateRegistry, ZkContractTable},
};
mod contract;
mod error;
mod note;
mod schema;
mod tx;
mod util;
fn show_dao_state(chain: &Blockchain, contract_id: &ContractId) -> Result<()> {
let db_info = chain.contracts.lookup(&chain.sled_db, contract_id, "info")?;
let value = db_info.get(&serialize(&"dao_tree".to_string())).expect("dao_tree").unwrap();
let mut decoder = Cursor::new(&value);
let set_size: u32 = Decodable::decode(&mut decoder)?;
let tree: MerkleTree = Decodable::decode(decoder)?;
debug!(target: "demo", "DAO state:");
debug!(target: "demo", " tree: {} bytes", value.len());
debug!(target: "demo", " set size: {}", set_size);
let db_roots = chain.contracts.lookup(&chain.sled_db, contract_id, "dao_roots")?;
for i in 0..set_size {
let root = db_roots.get(&serialize(&i)).expect("dao_roots").unwrap();
let root: MerkleNode = deserialize(&root)?;
debug!(target: "demo", " root {}: {:?}", i, root);
}
Ok(())
}
fn show_money_state(chain: &Blockchain, contract_id: &ContractId) -> Result<()> {
let db_info = chain.contracts.lookup(&chain.sled_db, contract_id, "info")?;
let value = db_info.get(&serialize(&"coin_tree".to_string())).expect("coin_tree").unwrap();
let mut decoder = Cursor::new(&value);
let set_size: u32 = Decodable::decode(&mut decoder)?;
let tree: MerkleTree = Decodable::decode(decoder)?;
debug!(target: "demo", "Money state:");
debug!(target: "demo", " tree: {} bytes", value.len());
debug!(target: "demo", " set size: {}", set_size);
let db_roots = chain.contracts.lookup(&chain.sled_db, contract_id, "coin_roots")?;
for i in 0..set_size {
let root = db_roots.get(&serialize(&i)).expect("coin_roots").unwrap();
let root: MerkleNode = deserialize(&root)?;
debug!(target: "demo", " root {}: {:?}", i, root);
}
let db_nulls = chain.contracts.lookup(&chain.sled_db, contract_id, "info")?;
debug!(target: "demo", " nullifiers:");
for obj in db_nulls.iter() {
let (key, value) = obj.unwrap();
debug!(target: "demo", " {:02x?}", &key[..]);
}
Ok(())
}
type BoxResult = std::result::Result>;
fn validate(
tx: &Transaction,
dao_wasm_bytes: &[u8],
dao_contract_id: ContractId,
money_wasm_bytes: &[u8],
money_contract_id: ContractId,
blockchain: &Blockchain,
zk_bins: &ZkContractTable,
) -> Result<()> {
// ContractId is not Hashable so put them in a Vec and do linear scan
let wasm_bytes_lookup = vec![
(dao_contract_id, "DAO", dao_wasm_bytes),
(money_contract_id, "Money", money_wasm_bytes),
];
// We can do all exec(), zk proof checks and signature verifies in parallel.
let mut updates = vec![];
let mut zkpublic_table = vec![];
let mut sigpub_table = vec![];
// Validate all function calls in the tx
for (idx, call) in tx.calls.iter().enumerate() {
// So then the verifier will lookup the corresponding state_transition and apply
// functions based off the func_id
// Write the actual payload data
let mut payload = Vec::new();
// Call index
payload.write_u32(idx as u32)?;
// Actuall calldata
tx.calls.encode(&mut payload)?;
// Lookup the wasm bytes
let (_, contract_name, wasm_bytes) =
wasm_bytes_lookup.iter().find(|(id, _name, _bytes)| *id == call.contract_id).unwrap();
debug!(target: "demo", "{}::exec() contract called", contract_name);
let mut runtime = Runtime::new(wasm_bytes, blockchain.clone(), call.contract_id)?;
let update = runtime.exec(&payload)?;
updates.push(update);
let metadata = runtime.metadata(&payload)?;
let mut decoder = Cursor::new(&metadata);
let zk_public_values: Vec<(String, Vec)> = Decodable::decode(&mut decoder)?;
let signature_public_keys: Vec = Decodable::decode(&mut decoder)?;
zkpublic_table.push(zk_public_values);
sigpub_table.push(signature_public_keys);
}
tx.zk_verify(&zk_bins, &zkpublic_table)?;
tx.verify_sigs(&sigpub_table)?;
// Now we finished verification stage, just apply all changes
assert_eq!(tx.calls.len(), updates.len());
for (call, update) in tx.calls.iter().zip(updates.iter()) {
// Lookup the wasm bytes
let (_, contract_name, wasm_bytes) =
wasm_bytes_lookup.iter().find(|(id, _name, _bytes)| *id == call.contract_id).unwrap();
debug!(target: "demo", "{}::apply() contract called", contract_name);
let mut runtime = Runtime::new(wasm_bytes, blockchain.clone(), call.contract_id)?;
runtime.apply(&update)?;
}
Ok(())
}
#[async_std::main]
async fn main() -> BoxResult<()> {
// Debug log configuration
let mut cfg = simplelog::ConfigBuilder::new();
cfg.add_filter_ignore("sled".to_string());
simplelog::TermLogger::init(
simplelog::LevelFilter::Debug,
cfg.build(),
simplelog::TerminalMode::Mixed,
simplelog::ColorChoice::Auto,
)?;
println!("wakie wakie young wagie");
//return Ok(());
//schema::schema().await?;
//return Ok(());
// =============================
// Setup initial program parameters
// =============================
// Money parameters
let xdrk_supply = 1_000_000;
let xdrk_token_id = TokenId::from(pallas::Base::random(&mut OsRng));
// Governance token parameters
let gdrk_supply = 1_000_000;
let gdrk_token_id = TokenId::from(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;
// 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);
// We use this to receive coins
let mut cache = WalletCache::new();
// Initialize a dummy blockchain
// TODO: This blockchain interface should perhaps be ValidatorState and Mutex/RwLock.
let db = sled::Config::new().temporary(true).open()?;
let blockchain = Blockchain::new(&db, *TESTNET_GENESIS_TIMESTAMP, *TESTNET_GENESIS_HASH_BYTES)?;
// ================================================================
// Deploy the wasm contracts
// ================================================================
let dao_wasm_bytes = std::fs::read("dao_contract.wasm")?;
let dao_contract_id = ContractId::from(pallas::Base::from(1));
let money_wasm_bytes = std::fs::read("money_contract.wasm")?;
let money_contract_id = ContractId::from(pallas::Base::from(2));
// Block 1
// This has 2 transaction deploying the DAO and Money wasm contracts
// together with their ZK proofs.
{
let mut dao_runtime = Runtime::new(&dao_wasm_bytes, blockchain.clone(), dao_contract_id)?;
let mut money_runtime =
Runtime::new(&money_wasm_bytes, blockchain.clone(), money_contract_id)?;
// 1. exec() - zk and sig verify also
// ... none in this block
// 2. commit() - all apply() and deploy()
// Deploy function to initialize the smart contract state.
// Here we pass an empty payload, but it's possible to feed in arbitrary data.
dao_runtime.deploy(&[])?;
money_runtime.deploy(&[])?;
debug!(target: "demo", "Deployed DAO and money contracts");
}
// ================================================================
// DAO::mint()
// ================================================================
// Wallet
let dao_keypair = Keypair::random(&mut OsRng);
let dao_bulla_blind = pallas::Base::random(&mut OsRng);
let tx = {
let signature_secret = SecretKey::random(&mut OsRng);
// Create DAO mint tx
let builder = dao::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,
};
let (params, dao_mint_proofs) = builder.build(&zk_bins);
// Write the actual call data
let mut calldata = Vec::new();
// Selects which path executes in the contract.
calldata.write_u8(DaoFunction::Mint as u8)?;
params.encode(&mut calldata)?;
let calls = vec![ContractCall { contract_id: dao_contract_id, data: calldata }];
let signatures = vec![];
//for func_call in &func_calls {
// let sign = sign([signature_secret].to_vec(), func_call);
// signatures.push(sign);
//}
let proofs = vec![dao_mint_proofs];
Transaction { calls, proofs, signatures }
};
//// Validator
validate(
&tx,
&dao_wasm_bytes,
dao_contract_id,
&money_wasm_bytes,
money_contract_id,
&blockchain,
&zk_bins,
)
.expect("validate failed");
// Wallet stuff
// In your wallet, wait until you see the tx confirmed before doing anything below
// So for example keep track of tx hash
//
// We also need to loop through all newly added items to the validator node
// and repeat the same for our local merkle tree. The order of added items
// to local merkle trees must be the same.
//
// One way to do this would be that .apply() keeps an in-memory per block
// list of the order txs were applied. So then we can repeat the same order
// for our local wallet trees.
//
// [ tx1, tx2, ... ]
//
// So the wallets know these are the new txs and this was the order they
// were applied to the state in.
// State updates are atomic so this will always be linear.
//
// When we see our DAO bulla, we call .witness()
// We need to witness() the value in our local merkle tree
let dao_bulla = {
assert_eq!(tx.calls.len(), 1);
let calldata = &tx.calls[0].data;
let params_data = &calldata[1..];
let params: DaoMintParams = Decodable::decode(params_data)?;
params.dao_bulla.clone()
};
let mut dao_tree = MerkleTree::new(100);
let dao_leaf_position = {
let node = MerkleNode::from(dao_bulla.0);
dao_tree.append(&node);
dao_tree.witness().unwrap()
};
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");
cache.track(dao_keypair.secret);
//// Wallet
// Address of deployed contract in our example is dao::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::exec::FUNC_ID;
let tx = {
// 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::transfer::wallet::Builder {
clear_inputs: vec![money::transfer::wallet::BuilderClearInputInfo {
value: xdrk_supply,
token_id: xdrk_token_id,
signature_secret: cashier_signature_secret,
}],
inputs: vec![],
outputs: vec![money::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 (params, proofs) = builder.build(&zk_bins)?;
// Write the actual call data
let mut calldata = Vec::new();
// Selects which path executes in the contract.
calldata.write_u8(MoneyFunction::Transfer as u8)?;
params.encode(&mut calldata)?;
let calls = vec![ContractCall { contract_id: money_contract_id, data: calldata }];
let proofs = vec![proofs];
// We sign everything
let mut unsigned_tx_data = vec![];
calls.encode(&mut unsigned_tx_data)?;
proofs.encode(&mut unsigned_tx_data)?;
let signature = cashier_signature_secret.sign(&mut OsRng, &unsigned_tx_data[..]);
// Our tx has a single contract call which itself has a single input
let signatures = vec![vec![signature]];
Transaction { calls, proofs, signatures }
};
//// Validator
validate(
&tx,
&dao_wasm_bytes,
dao_contract_id,
&money_wasm_bytes,
money_contract_id,
&blockchain,
&zk_bins,
)
.expect("validate failed");
// Wallet stuff
// DAO reads the money received from the encrypted note
{
assert_eq!(tx.calls.len(), 1);
let calldata = &tx.calls[0].data;
let params_data = &calldata[1..];
let params: MoneyTransferParams = Decodable::decode(params_data)?;
for output in params.outputs {
let coin = output.coin;
let enc_note = note::EncryptedNote2 {
ciphertext: output.ciphertext,
ephem_public: output.ephem_public,
};
let coin = Coin(coin);
cache.try_decrypt_note(coin, &enc_note);
}
}
let mut recv_coins = 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.inner().to_affine().coordinates().unwrap();
let coin = poseidon_hash::<8>([
*coords.x(),
*coords.y(),
DrkValue::from(treasury_note.value),
treasury_note.token_id.inner(),
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::exec::FUNC_ID);
assert_eq!(treasury_note.user_data, dao_bulla.0);
debug!("DAO received a coin worth {} xDRK", treasury_note.value);
///////////////////////////////////////////////////
show_dao_state(&blockchain, &dao_contract_id)?;
show_money_state(&blockchain, &money_contract_id)?;
Ok(())
}