| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540 |
- /* 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 <https://www.gnu.org/licenses/>.
- */
- 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<T> = std::result::Result<T, Box<dyn std::error::Error>>;
- 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<pallas::Base>)> = Decodable::decode(&mut decoder)?;
- let signature_public_keys: Vec<pallas::Point> = 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(())
- }
|