/* 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(()) }