diff --git a/example/dao2/.gitignore b/example/dao2/.gitignore deleted file mode 100644 index 899fdcca8..000000000 --- a/example/dao2/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -/dao -*.wasm -*.zk.bin -target/ -Cargo.lock diff --git a/example/dao2/Cargo.toml b/example/dao2/Cargo.toml deleted file mode 100644 index 6fb814b92..000000000 --- a/example/dao2/Cargo.toml +++ /dev/null @@ -1,54 +0,0 @@ -[package] -name = "dao" -version = "0.4.1" -authors = ["Dyne.org foundation "] -license = "AGPL-3.0-only" -edition = "2021" - -[workspace] -members = [ - "contract/money", - "contract/dao", -] - -[dependencies] -dao-contract = {path = "contract/dao"} -money-contract = {path = "contract/money"} -darkfi-sdk = { path = "../../src/sdk" } -darkfi-serial = { path = "../../src/serial" } -darkfi = { path = "../../", features = ["wasm-runtime"] } -sled = "0.34.7" - -# Async -smol = "1.3.0" -futures = "0.3.28" -async-std = {version = "1.12.0", features = ["attributes"]} -async-trait = "0.1.68" -async-channel = "1.8.0" -async-executor = "1.5.1" -easy-parallel = "3.3.0" - -# Misc -log = "0.4.17" -num_cpus = "1.15.0" -simplelog = "0.12.1" -thiserror = "1.0.40" - -# Crypto -incrementalmerkletree = "0.3.1" -pasta_curves = "0.5.1" -halo2_gadgets = "0.3.0" -halo2_proofs = "0.3.0" -rand = "0.8.5" -chacha20poly1305 = "0.10.1" -group = "0.13.0" - -# Encoding and parsing -serde_json = "1.0.96" -bs58 = "0.4.0" -fxhash = "0.2.1" - -# Utilities -lazy_static = "1.4.0" -url = "2.3.1" -env_logger = "0.10.0" diff --git a/example/dao2/Makefile b/example/dao2/Makefile deleted file mode 100644 index 607c183e0..000000000 --- a/example/dao2/Makefile +++ /dev/null @@ -1,38 +0,0 @@ -.POSIX: - -SRC = $(shell find src -type f) -WASM_SRC = $(shell find contract -type f) - -# Cargo binary -CARGO = cargo - -# Contract WASM binaries -WASM_BIN = \ - dao_contract.wasm \ - money_contract.wasm - -# Host binaries -BIN = dao - -all: $(WASM_BIN) $(BIN) - @./dao - -dao: $(WASM_SRC) $(SRC) - $(CARGO) build --release --bin $@ - cp -f target/release/$@ $@ - -dao_contract.wasm: $(WASM_SRC) - $(CARGO) build --release --package dao-contract --target wasm32-unknown-unknown - cp -f target/wasm32-unknown-unknown/release/$@ $@ - -money_contract.wasm: $(WASM_SRC) - $(CARGO) build --release --package money-contract --target wasm32-unknown-unknown - cp -f target/wasm32-unknown-unknown/release/$@ $@ - -test: all - $(CARGO) test --release -- --nocapture - -clean: - rm -f $(BIN) $(WASM_BIN) - -.PHONY: all test clean diff --git a/example/dao2/contract/dao/Cargo.toml b/example/dao2/contract/dao/Cargo.toml deleted file mode 100644 index 8b3552b30..000000000 --- a/example/dao2/contract/dao/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "dao-contract" -version = "0.4.1" -authors = ["Dyne.org foundation "] -license = "AGPL-3.0-only" -edition = "2021" - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -darkfi-sdk = { path = "../../../../src/sdk" } -darkfi-serial = { path = "../../../../src/serial", features = ["crypto"] } - -# We need to disable random using "custom" which makes the crate a noop -# so the wasm32-unknown-unknown target is enabled. -[target.'cfg(target_arch = "wasm32")'.dependencies] -getrandom = { version = "0.2.8", features = ["custom"] } diff --git a/example/dao2/contract/dao/proof/foo.zk b/example/dao2/contract/dao/proof/foo.zk deleted file mode 100644 index e69de29bb..000000000 diff --git a/example/dao2/contract/dao/src/lib.rs b/example/dao2/contract/dao/src/lib.rs deleted file mode 100644 index a979b658d..000000000 --- a/example/dao2/contract/dao/src/lib.rs +++ /dev/null @@ -1,153 +0,0 @@ -/* 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_sdk::{ - crypto::{ContractId, MerkleNode, MerkleTree}, - db::{db_init, db_lookup, db_set}, - define_contract, - error::ContractResult, - merkle::merkle_add, - msg, - pasta::pallas, - tx::ContractCall, - util::set_return_data, -}; -use darkfi_serial::{ - deserialize, serialize, Encodable, SerialDecodable, SerialEncodable, WriteExt, -}; - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct DaoBulla(pub pallas::Base); - -#[repr(u8)] -pub enum DaoFunction { - Foo = 0x00, - Mint = 0x01, -} - -impl From for DaoFunction { - fn from(b: u8) -> Self { - match b { - 0x00 => Self::Foo, - 0x01 => Self::Mint, - _ => panic!("Invalid function ID: {:#04x?}", b), - } - } -} - -#[derive(SerialEncodable, SerialDecodable)] -pub struct DaoMintParams { - pub dao_bulla: DaoBulla, -} -#[derive(SerialEncodable, SerialDecodable)] -pub struct DaoMintUpdate { - pub dao_bulla: DaoBulla, -} - -define_contract!( - init: init_contract, - exec: process_instruction, - apply: process_update, - metadata: get_metadata -); - -fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult { - let info_db = db_init(cid, "info")?; - let _ = db_init(cid, "dao_roots")?; - - let dao_tree = MerkleTree::new(100); - let mut dao_tree_data = Vec::new(); - dao_tree_data.write_u32(0)?; - dao_tree.encode(&mut dao_tree_data)?; - db_set(info_db, &serialize(&"dao_tree".to_string()), &dao_tree_data)?; - - Ok(()) -} -fn get_metadata(_cid: ContractId, ix: &[u8]) -> ContractResult { - let (call_idx, call): (u32, Vec) = deserialize(ix)?; - - assert!(call_idx < call.len() as u32); - let self_ = &call[call_idx as usize]; - - match DaoFunction::from(self_.data[0]) { - DaoFunction::Mint => { - let data = &self_.data[1..]; - let params: DaoMintParams = deserialize(data)?; - - let zk_public_values: Vec<(String, Vec)> = - vec![("dao-mint".to_string(), vec![params.dao_bulla.0])]; - let signature_public_keys: Vec = Vec::new(); - - let mut metadata = Vec::new(); - zk_public_values.encode(&mut metadata)?; - signature_public_keys.encode(&mut metadata)?; - set_return_data(&metadata)?; - } - DaoFunction::Foo => { - unimplemented!(); - } - } - - Ok(()) -} -fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult { - let (call_idx, call): (u32, Vec) = deserialize(ix)?; - - assert!(call_idx < call.len() as u32); - let self_ = &call[call_idx as usize]; - - match DaoFunction::from(self_.data[0]) { - DaoFunction::Mint => { - let data = &self_.data[1..]; - let params: DaoMintParams = deserialize(data)?; - - // No checks in Mint. Just return the update. - - let update = DaoMintUpdate { dao_bulla: params.dao_bulla }; - - let mut update_data = Vec::new(); - update_data.write_u8(DaoFunction::Mint as u8)?; - update.encode(&mut update_data)?; - set_return_data(&update_data)?; - msg!("update is set!"); - } - DaoFunction::Foo => { - unimplemented!(); - } - } - - Ok(()) -} -fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult { - match DaoFunction::from(update_data[0]) { - DaoFunction::Mint => { - let data = &update_data[1..]; - let update: DaoMintUpdate = deserialize(data)?; - - let db_info = db_lookup(cid, "info")?; - let db_roots = db_lookup(cid, "dao_roots")?; - let node = MerkleNode::new(update.dao_bulla.0); - merkle_add(db_info, db_roots, &serialize(&"dao_tree".to_string()), &node)?; - } - DaoFunction::Foo => { - unimplemented!(); - } - } - - Ok(()) -} diff --git a/example/dao2/contract/money/Cargo.toml b/example/dao2/contract/money/Cargo.toml deleted file mode 100644 index 49da3c79e..000000000 --- a/example/dao2/contract/money/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "money-contract" -version = "0.4.1" -authors = ["Dyne.org foundation "] -license = "AGPL-3.0-only" -edition = "2021" - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -darkfi-sdk = { path = "../../../../src/sdk" } -darkfi-serial = { path = "../../../../src/serial", features = ["crypto"] } - -# We need to disable random using "custom" which makes the crate a noop -# so the wasm32-unknown-unknown target is enabled. -[target.'cfg(target_arch = "wasm32")'.dependencies] -getrandom = { version = "0.2.8", features = ["custom"] } diff --git a/example/dao2/contract/money/proof/bar.zk b/example/dao2/contract/money/proof/bar.zk deleted file mode 100644 index e69de29bb..000000000 diff --git a/example/dao2/contract/money/src/lib.rs b/example/dao2/contract/money/src/lib.rs deleted file mode 100644 index 87d27eb84..000000000 --- a/example/dao2/contract/money/src/lib.rs +++ /dev/null @@ -1,238 +0,0 @@ -/* 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_sdk::{ - crypto::{ContractId, MerkleNode, MerkleTree, PublicKey}, - db::{db_init, db_lookup, db_set}, - define_contract, - error::ContractResult, - merkle::merkle_add, - msg, - pasta::{arithmetic::CurveAffine, group::Curve, pallas}, - tx::ContractCall, - util::set_return_data, -}; -use darkfi_serial::{ - deserialize, serialize, Encodable, SerialDecodable, SerialEncodable, WriteExt, -}; - -#[repr(u8)] -pub enum MoneyFunction { - Transfer = 0x00, -} - -impl From for MoneyFunction { - fn from(b: u8) -> Self { - match b { - 0x00 => Self::Transfer, - _ => panic!("Invalid function ID: {:#04x?}", b), - } - } -} - -#[derive(SerialEncodable, SerialDecodable)] -pub struct MoneyTransferParams { - /// Clear inputs - pub clear_inputs: Vec, - /// Anonymous inputs - pub inputs: Vec, - /// Anonymous outputs - pub outputs: Vec, -} -#[derive(SerialEncodable, SerialDecodable)] -pub struct MoneyTransferUpdate { - /// Nullifiers - pub nullifiers: Vec, - /// Coins - pub coins: Vec, -} - -/// A transaction's clear input -#[derive(SerialEncodable, SerialDecodable)] -pub struct ClearInput { - /// Input's value (amount) - pub value: u64, - /// Input's token ID - pub token_id: pallas::Base, - /// Blinding factor for `value` - pub value_blind: pallas::Scalar, - /// Blinding factor for `token_id` - pub token_blind: pallas::Scalar, - /// Public key for the signature - pub signature_public: PublicKey, -} - -/// A transaction's anonymous input -#[derive(SerialEncodable, SerialDecodable)] -pub struct Input { - // Public inputs for the zero-knowledge proof - pub value_commit: pallas::Point, - pub token_commit: pallas::Point, - pub nullifier: pallas::Base, - pub merkle_root: pallas::Base, - pub spend_hook: pallas::Base, - pub user_data_enc: pallas::Base, - pub signature_public: PublicKey, -} - -/// A transaction's anonymous output -#[derive(SerialEncodable, SerialDecodable)] -pub struct Output { - // Public inputs for the zero-knowledge proof - pub value_commit: pallas::Point, - pub token_commit: pallas::Point, - pub coin: pallas::Base, - /// The encrypted note ciphertext - pub ciphertext: Vec, - pub ephem_public: PublicKey, -} - -define_contract!( - init: init_contract, - exec: process_instruction, - apply: process_update, - metadata: get_metadata -); - -fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult { - let info_db = db_init(cid, "info")?; - let _ = db_init(cid, "coin_roots")?; - - let coin_tree = MerkleTree::new(100); - let mut coin_tree_data = Vec::new(); - coin_tree_data.write_u32(0)?; - coin_tree.encode(&mut coin_tree_data)?; - db_set(info_db, &serialize(&"coin_tree".to_string()), &coin_tree_data)?; - - let _ = db_init(cid, "nulls")?; - - Ok(()) -} -fn get_metadata(_cid: ContractId, ix: &[u8]) -> ContractResult { - let (call_idx, call): (u32, Vec) = deserialize(ix)?; - - assert!(call_idx < call.len() as u32); - let self_ = &call[call_idx as usize]; - - match MoneyFunction::from(self_.data[0]) { - MoneyFunction::Transfer => { - let data = &self_.data[1..]; - let params: MoneyTransferParams = deserialize(data)?; - - let mut zk_public_values: Vec<(String, Vec)> = Vec::new(); - let mut signature_public_keys: Vec = Vec::new(); - - for input in ¶ms.clear_inputs { - signature_public_keys.push(input.signature_public.inner()); - } - for input in ¶ms.inputs { - let value_coords = input.value_commit.to_affine().coordinates().unwrap(); - let token_coords = input.token_commit.to_affine().coordinates().unwrap(); - let (sig_x, sig_y) = input.signature_public.xy(); - - zk_public_values.push(( - "money-transfer-burn".to_string(), - vec![ - input.nullifier, - *value_coords.x(), - *value_coords.y(), - *token_coords.x(), - *token_coords.y(), - input.merkle_root, - input.user_data_enc, - sig_x, - sig_y, - ], - )); - - signature_public_keys.push(input.signature_public.inner()); - } - for output in ¶ms.outputs { - let value_coords = output.value_commit.to_affine().coordinates().unwrap(); - let token_coords = output.token_commit.to_affine().coordinates().unwrap(); - - zk_public_values.push(( - "money-transfer-mint".to_string(), - vec![ - output.coin, - *value_coords.x(), - *value_coords.y(), - *token_coords.x(), - *token_coords.y(), - ], - )); - } - - let mut metadata = Vec::new(); - zk_public_values.encode(&mut metadata)?; - signature_public_keys.encode(&mut metadata)?; - set_return_data(&metadata)?; - } - } - Ok(()) -} -fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult { - let (call_idx, call): (u32, Vec) = deserialize(ix)?; - - assert!(call_idx < call.len() as u32); - let self_ = &call[call_idx as usize]; - - match MoneyFunction::from(self_.data[0]) { - MoneyFunction::Transfer => { - let data = &self_.data[1..]; - let params: MoneyTransferParams = deserialize(data)?; - - // TODO: implement state_transition() checks here - - let update = MoneyTransferUpdate { - nullifiers: params.inputs.iter().map(|input| input.nullifier).collect(), - coins: params.outputs.iter().map(|output| output.coin).collect(), - }; - - let mut update_data = Vec::new(); - update_data.write_u8(MoneyFunction::Transfer as u8)?; - update.encode(&mut update_data)?; - set_return_data(&update_data)?; - msg!("update is set!"); - } - } - Ok(()) -} -fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult { - match MoneyFunction::from(update_data[0]) { - MoneyFunction::Transfer => { - let data = &update_data[1..]; - let update: MoneyTransferUpdate = deserialize(data)?; - - let db_info = db_lookup(cid, "info")?; - let db_nulls = db_lookup(cid, "nulls")?; - for nullifier in update.nullifiers { - db_set(db_nulls, &serialize(&nullifier), &[])?; - } - let db_roots = db_lookup(cid, "coin_roots")?; - for coin in update.coins { - let node = MerkleNode::new(coin); - // TODO: merkle_add() should take a list of coins and batch add them - // for efficiency - merkle_add(db_info, db_roots, &serialize(&"coin_tree".to_string()), &node)?; - } - } - } - - Ok(()) -} diff --git a/example/dao2/proof/dao-exec.zk b/example/dao2/proof/dao-exec.zk deleted file mode 100644 index 9fa9c199e..000000000 --- a/example/dao2/proof/dao-exec.zk +++ /dev/null @@ -1,168 +0,0 @@ -constant "DaoExec" { - EcFixedPointShort VALUE_COMMIT_VALUE, - EcFixedPoint VALUE_COMMIT_RANDOM, -} - -contract "DaoExec" { - # proposal params - Base proposal_dest_x, - Base proposal_dest_y, - Base proposal_amount, - Base proposal_serial, - Base proposal_token_id, - Base proposal_blind, - - # DAO params - Base dao_proposer_limit, - Base dao_quorum, - Base dao_approval_ratio_quot, - Base dao_approval_ratio_base, - Base gov_token_id, - Base dao_public_x, - Base dao_public_y, - Base dao_bulla_blind, - - # votes - Base yes_votes_value, - Base all_votes_value, - Scalar yes_votes_blind, - Scalar all_votes_blind, - - # outputs + inputs - Base user_serial, - Base user_coin_blind, - Base dao_serial, - Base dao_coin_blind, - Base input_value, - Scalar input_value_blind, - - # misc - Base dao_spend_hook, - Base user_spend_hook, - Base user_data, -} - -circuit "DaoExec" { - dao_bulla = poseidon_hash( - dao_proposer_limit, - dao_quorum, - dao_approval_ratio_quot, - dao_approval_ratio_base, - gov_token_id, - dao_public_x, - dao_public_y, - dao_bulla_blind, - ); - # Proposal bulla is valid means DAO bulla is also valid - # because of dao-propose-main.zk, already checks that when - # we first create the proposal. So it is redundant here. - - proposal_bulla = poseidon_hash( - proposal_dest_x, - proposal_dest_y, - proposal_amount, - proposal_serial, - proposal_token_id, - dao_bulla, - proposal_blind, - # @tmp-workaround - proposal_blind, - ); - constrain_instance(proposal_bulla); - - coin_0 = poseidon_hash( - proposal_dest_x, - proposal_dest_y, - proposal_amount, - proposal_token_id, - proposal_serial, - user_spend_hook, - user_data, - proposal_blind, - ); - constrain_instance(coin_0); - - change = base_sub(input_value, proposal_amount); - - coin_1 = poseidon_hash( - dao_public_x, - dao_public_y, - change, - proposal_token_id, - dao_serial, - dao_spend_hook, - dao_bulla, - dao_coin_blind, - ); - constrain_instance(coin_1); - - # Create pedersen commits for win_votes, and total_votes - # and make public - yes_votes_value_c = ec_mul_short(yes_votes_value, VALUE_COMMIT_VALUE); - yes_votes_blind_c = ec_mul(yes_votes_blind, VALUE_COMMIT_RANDOM); - yes_votes_commit = ec_add(yes_votes_value_c, yes_votes_blind_c); - - # get curve points and constrain - yes_votes_commit_x = ec_get_x(yes_votes_commit); - yes_votes_commit_y = ec_get_y(yes_votes_commit); - constrain_instance(yes_votes_commit_x); - constrain_instance(yes_votes_commit_y); - - all_votes_c = ec_mul_short(all_votes_value, VALUE_COMMIT_VALUE); - all_votes_blind_c = ec_mul(all_votes_blind, VALUE_COMMIT_RANDOM); - all_votes_commit = ec_add(all_votes_c, all_votes_blind_c); - - # get curve points and constrain - all_votes_commit_x = ec_get_x(all_votes_commit); - all_votes_commit_y = ec_get_y(all_votes_commit); - constrain_instance(all_votes_commit_x); - constrain_instance(all_votes_commit_y); - - # Create pedersen commit for input_value and make public - - input_value_v = ec_mul_short(input_value, VALUE_COMMIT_VALUE); - input_value_r = ec_mul(input_value_blind, VALUE_COMMIT_RANDOM); - input_value_commit = ec_add(input_value_v, input_value_r); - - # get curve points and constrain - input_value_x = ec_get_x(input_value_commit); - input_value_y = ec_get_y(input_value_commit); - constrain_instance(input_value_x); - constrain_instance(input_value_y); - - constrain_instance(dao_spend_hook); - constrain_instance(user_spend_hook); - constrain_instance(user_data); - - # Check that dao_quorum is less than or equal to all_votes_value - one = witness_base(1); - all_votes_value_1 = base_add(all_votes_value, one); - less_than(dao_quorum, all_votes_value_1); - - # approval_ratio_quot / approval_ratio_base <= yes_votes / all_votes - # - # The above is also equivalent to this: - # - # all_votes * approval_ratio_quot <= yes_votes * approval_ratio_base - - rhs = base_mul(all_votes_value, dao_approval_ratio_quot); - lhs = base_mul(yes_votes_value, dao_approval_ratio_base); - - lhs_1 = base_add(lhs, one); - less_than(rhs, lhs_1); - - #### - - # Create coin 0 - # Create coin 1 - # Check values of coin 0 + coin 1 == input value - # Check value of coin 0 == proposal_amount - # Check public key matches too - # Create the input value commit - # Create the value commits - - # NOTE: there is a vulnerability here where someone can create the exec - # transaction with a bad note so it cannot be decrypted by the receiver - # TODO: research verifiable encryption inside ZK -} - diff --git a/example/dao2/proof/dao-mint.zk b/example/dao2/proof/dao-mint.zk deleted file mode 100644 index f08955394..000000000 --- a/example/dao2/proof/dao-mint.zk +++ /dev/null @@ -1,32 +0,0 @@ -constant "DaoMint" { -} - -contract "DaoMint" { - Base dao_proposer_limit, - Base dao_quorum, - Base dao_approval_ratio_quot, - Base dao_approval_ratio_base, - Base gdrk_token_id, - Base dao_public_x, - Base dao_public_y, - Base dao_bulla_blind, -} - -circuit "DaoMint" { - # This circuit is not that interesting. - # It just states the bulla is a hash of 8 values. - - # BullaMint subroutine - bulla = poseidon_hash( - dao_proposer_limit, - dao_quorum, - dao_approval_ratio_quot, - dao_approval_ratio_base, - gdrk_token_id, - dao_public_x, - dao_public_y, - dao_bulla_blind, - ); - constrain_instance(bulla); -} - diff --git a/example/dao2/proof/dao-propose-burn.zk b/example/dao2/proof/dao-propose-burn.zk deleted file mode 100644 index 41502a212..000000000 --- a/example/dao2/proof/dao-propose-burn.zk +++ /dev/null @@ -1,63 +0,0 @@ -constant "DaoProposeInput" { - EcFixedPointShort VALUE_COMMIT_VALUE, - EcFixedPoint VALUE_COMMIT_RANDOM, - EcFixedPointBase NULLIFIER_K, -} - -contract "DaoProposeInput" { - Base secret, - Base serial, - Base spend_hook, - Base user_data, - Base value, - Base token, - Base coin_blind, - Scalar value_blind, - Base token_blind, - Uint32 leaf_pos, - MerklePath path, - Base signature_secret, -} - -circuit "DaoProposeInput" { - # Poseidon hash of the nullifier - #nullifier = poseidon_hash(secret, serial); - #constrain_instance(nullifier); - - # Pedersen commitment for coin's value - vcv = ec_mul_short(value, VALUE_COMMIT_VALUE); - vcr = ec_mul(value_blind, VALUE_COMMIT_RANDOM); - value_commit = ec_add(vcv, vcr); - # Since value_commit is a curve point, we fetch its coordinates - # and constrain them: - value_commit_x = ec_get_x(value_commit); - value_commit_y = ec_get_y(value_commit); - constrain_instance(value_commit_x); - constrain_instance(value_commit_y); - - # Commitment for coin's token ID - token_commit = poseidon_hash(token, token_blind); - constrain_instance(token_commit); - - # Coin hash - pub = ec_mul_base(secret, NULLIFIER_K); - pub_x = ec_get_x(pub); - pub_y = ec_get_y(pub); - C = poseidon_hash(pub_x, pub_y, value, token, serial, spend_hook, user_data, coin_blind); - - # Merkle root - root = merkle_root(leaf_pos, path, C); - constrain_instance(root); - - # Finally, we derive a public key for the signature and - # constrain its coordinates: - signature_public = ec_mul_base(signature_secret, NULLIFIER_K); - signature_x = ec_get_x(signature_public); - signature_y = ec_get_y(signature_public); - constrain_instance(signature_x); - constrain_instance(signature_y); - - # At this point we've enforced all of our public inputs. -} - - diff --git a/example/dao2/proof/dao-propose-main.zk b/example/dao2/proof/dao-propose-main.zk deleted file mode 100644 index 2d7db4d02..000000000 --- a/example/dao2/proof/dao-propose-main.zk +++ /dev/null @@ -1,88 +0,0 @@ -constant "DaoProposeMain" { - EcFixedPointShort VALUE_COMMIT_VALUE, - EcFixedPoint VALUE_COMMIT_RANDOM, -} - -contract "DaoProposeMain" { - # Proposers total number of gov tokens - Base total_funds, - Scalar total_funds_blind, - - # Check the inputs and this proof are for the same token - Base gov_token_blind, - - # proposal params - Base proposal_dest_x, - Base proposal_dest_y, - Base proposal_amount, - Base proposal_serial, - Base proposal_token_id, - Base proposal_blind, - - # DAO params - Base dao_proposer_limit, - Base dao_quorum, - Base dao_approval_ratio_quot, - Base dao_approval_ratio_base, - Base gov_token_id, - Base dao_public_x, - Base dao_public_y, - Base dao_bulla_blind, - - Uint32 dao_leaf_pos, - MerklePath dao_path, -} - -circuit "DaoProposeMain" { - token_commit = poseidon_hash(gov_token_id, gov_token_blind); - constrain_instance(token_commit); - - dao_bulla = poseidon_hash( - dao_proposer_limit, - dao_quorum, - dao_approval_ratio_quot, - dao_approval_ratio_base, - gov_token_id, - dao_public_x, - dao_public_y, - dao_bulla_blind, - ); - dao_root = merkle_root(dao_leaf_pos, dao_path, dao_bulla); - constrain_instance(dao_root); - # Proves this DAO is valid - - proposal_bulla = poseidon_hash( - proposal_dest_x, - proposal_dest_y, - proposal_amount, - proposal_serial, - proposal_token_id, - dao_bulla, - proposal_blind, - # @tmp-workaround - proposal_blind, - ); - constrain_instance(proposal_bulla); - - # Rangeproof check for proposal amount - zero = witness_base(0); - less_than(zero, proposal_amount); - - # This is the main check - # We check that dao_proposer_limit <= total_funds - one = witness_base(1); - total_funds_1 = base_add(total_funds, one); - less_than(dao_proposer_limit, total_funds_1); - - # Pedersen commitment for coin's value - vcv = ec_mul_short(total_funds, VALUE_COMMIT_VALUE); - vcr = ec_mul(total_funds_blind, VALUE_COMMIT_RANDOM); - total_funds_commit = ec_add(vcv, vcr); - # Since total_funds_commit is a curve point, we fetch its coordinates - # and constrain them: - total_funds_commit_x = ec_get_x(total_funds_commit); - total_funds_commit_y = ec_get_y(total_funds_commit); - constrain_instance(total_funds_commit_x); - constrain_instance(total_funds_commit_y); -} - diff --git a/example/dao2/proof/dao-vote-burn.zk b/example/dao2/proof/dao-vote-burn.zk deleted file mode 100644 index b9b096ef2..000000000 --- a/example/dao2/proof/dao-vote-burn.zk +++ /dev/null @@ -1,64 +0,0 @@ -constant "DaoVoteInput" { - EcFixedPointShort VALUE_COMMIT_VALUE, - EcFixedPoint VALUE_COMMIT_RANDOM, - EcFixedPointBase NULLIFIER_K, -} - -contract "DaoVoteInput" { - Base secret, - Base serial, - Base spend_hook, - Base user_data, - Base value, - Base gov_token_id, - Base coin_blind, - Scalar value_blind, - Base gov_token_blind, - Uint32 leaf_pos, - MerklePath path, - Base signature_secret, -} - -circuit "DaoVoteInput" { - # Poseidon hash of the nullifier - nullifier = poseidon_hash(secret, serial); - constrain_instance(nullifier); - - # Pedersen commitment for coin's value - vcv = ec_mul_short(value, VALUE_COMMIT_VALUE); - vcr = ec_mul(value_blind, VALUE_COMMIT_RANDOM); - value_commit = ec_add(vcv, vcr); - # Since value_commit is a curve point, we fetch its coordinates - # and constrain them: - value_commit_x = ec_get_x(value_commit); - value_commit_y = ec_get_y(value_commit); - constrain_instance(value_commit_x); - constrain_instance(value_commit_y); - - # Commitment for coin's token ID - token_commit = poseidon_hash(gov_token_id, gov_token_blind); - constrain_instance(token_commit); - - # Coin hash - pub = ec_mul_base(secret, NULLIFIER_K); - pub_x = ec_get_x(pub); - pub_y = ec_get_y(pub); - C = poseidon_hash(pub_x, pub_y, value, gov_token_id, serial, spend_hook, user_data, coin_blind); - - # Merkle root - root = merkle_root(leaf_pos, path, C); - constrain_instance(root); - - # Finally, we derive a public key for the signature and - # constrain its coordinates: - signature_public = ec_mul_base(signature_secret, NULLIFIER_K); - signature_x = ec_get_x(signature_public); - signature_y = ec_get_y(signature_public); - constrain_instance(signature_x); - constrain_instance(signature_y); - - # At this point we've enforced all of our public inputs. -} - - - diff --git a/example/dao2/proof/dao-vote-main.zk b/example/dao2/proof/dao-vote-main.zk deleted file mode 100644 index f7a9b1ba2..000000000 --- a/example/dao2/proof/dao-vote-main.zk +++ /dev/null @@ -1,98 +0,0 @@ -constant "DaoVoteMain" { - EcFixedPointShort VALUE_COMMIT_VALUE, - EcFixedPoint VALUE_COMMIT_RANDOM, -} - -contract "DaoVoteMain" { - # proposal params - Base proposal_dest_x, - Base proposal_dest_y, - Base proposal_amount, - Base proposal_serial, - Base proposal_token_id, - Base proposal_blind, - - # DAO params - Base dao_proposer_limit, - Base dao_quorum, - Base dao_approval_ratio_quot, - Base dao_approval_ratio_base, - Base gov_token_id, - Base dao_public_x, - Base dao_public_y, - Base dao_bulla_blind, - - # Is the vote yes or no - Base vote_option, - Scalar yes_vote_blind, - - # Total amount of capital allocated to vote - Base all_votes_value, - Scalar all_votes_blind, - - # Check the inputs and this proof are for the same token - Base gov_token_blind, -} - -circuit "DaoVoteMain" { - token_commit = poseidon_hash(gov_token_id, gov_token_blind); - constrain_instance(token_commit); - - dao_bulla = poseidon_hash( - dao_proposer_limit, - dao_quorum, - dao_approval_ratio_quot, - dao_approval_ratio_base, - gov_token_id, - dao_public_x, - dao_public_y, - dao_bulla_blind, - ); - # Proposal bulla is valid means DAO bulla is also valid - # because of dao-propose-main.zk, already checks that when - # we first create the proposal. So it is redundant here. - - proposal_bulla = poseidon_hash( - proposal_dest_x, - proposal_dest_y, - proposal_amount, - proposal_serial, - proposal_token_id, - dao_bulla, - proposal_blind, - # @tmp-workaround - proposal_blind, - ); - constrain_instance(proposal_bulla); - # TODO: we need to check the proposal isn't invalidated - # that is expired or already executed. - - # normally we call this yes vote - # Pedersen commitment for vote option - yes_votes_value = base_mul(vote_option, all_votes_value); - yes_votes_value_c = ec_mul_short(yes_votes_value, VALUE_COMMIT_VALUE); - yes_votes_blind_c = ec_mul(yes_vote_blind, VALUE_COMMIT_RANDOM); - yes_votes_commit = ec_add(yes_votes_value_c, yes_votes_blind_c); - - # get curve points and constrain - yes_votes_commit_x = ec_get_x(yes_votes_commit); - yes_votes_commit_y = ec_get_y(yes_votes_commit); - constrain_instance(yes_votes_commit_x); - constrain_instance(yes_votes_commit_y); - - # Pedersen commitment for vote value - all_votes_c = ec_mul_short(all_votes_value, VALUE_COMMIT_VALUE); - all_votes_blind_c = ec_mul(all_votes_blind, VALUE_COMMIT_RANDOM); - all_votes_commit = ec_add(all_votes_c, all_votes_blind_c); - - # get curve points and constrain - all_votes_commit_x = ec_get_x(all_votes_commit); - all_votes_commit_y = ec_get_y(all_votes_commit); - constrain_instance(all_votes_commit_x); - constrain_instance(all_votes_commit_y); - - # Vote option should be 0 or 1 - bool_check(vote_option); -} - - diff --git a/example/dao2/proof/foo.zk b/example/dao2/proof/foo.zk deleted file mode 100644 index 21c42991d..000000000 --- a/example/dao2/proof/foo.zk +++ /dev/null @@ -1,14 +0,0 @@ -constant "DaoMint" { -} - -contract "DaoMint" { - Base a, - Base b, -} - -circuit "DaoMint" { - c = base_add(a, b); - constrain_instance(c); -} - - diff --git a/example/dao2/src/contract/dao/exec/mod.rs b/example/dao2/src/contract/dao/exec/mod.rs deleted file mode 100644 index c377c014d..000000000 --- a/example/dao2/src/contract/dao/exec/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -/* 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 lazy_static::lazy_static; -use pasta_curves::{group::ff::Field, pallas}; -use rand::rngs::OsRng; - -pub mod validate; -pub mod wallet; - -lazy_static! { - pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng); -} diff --git a/example/dao2/src/contract/dao/exec/validate.rs b/example/dao2/src/contract/dao/exec/validate.rs deleted file mode 100644 index 7c280dbe1..000000000 --- a/example/dao2/src/contract/dao/exec/validate.rs +++ /dev/null @@ -1,218 +0,0 @@ -/* 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 std::any::{Any, TypeId}; - -use darkfi_sdk::crypto::PublicKey; -use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable}; -use pasta_curves::{ - arithmetic::CurveAffine, - group::{Curve, Group}, - pallas, -}; - -use darkfi::{ - crypto::{coin::Coin, types::DrkCircuitField}, - Error as DarkFiError, -}; - -use crate::{ - contract::{dao, dao::CONTRACT_ID, money}, - util::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase}, -}; - -type Result = std::result::Result; - -#[derive(Debug, Clone, thiserror::Error)] -pub enum Error { - #[error("DarkFi error: {0}")] - DarkFiError(String), - - #[error("InvalidNumberOfFuncCalls")] - InvalidNumberOfFuncCalls, - - #[error("InvalidIndex")] - InvalidIndex, - - #[error("InvalidCallData")] - InvalidCallData, - - #[error("InvalidNumberOfOutputs")] - InvalidNumberOfOutputs, - - #[error("InvalidOutput")] - InvalidOutput, - - #[error("InvalidValueCommit")] - InvalidValueCommit, - - #[error("InvalidVoteCommit")] - InvalidVoteCommit, -} - -impl From for Error { - fn from(err: DarkFiError) -> Self { - Self::DarkFiError(err.to_string()) - } -} - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct CallData { - pub proposal: pallas::Base, - pub coin_0: pallas::Base, - pub coin_1: pallas::Base, - pub yes_votes_commit: pallas::Point, - pub all_votes_commit: pallas::Point, - pub input_value_commit: pallas::Point, -} - -impl CallDataBase for CallData { - fn zk_public_values(&self) -> Vec<(String, Vec)> { - let yes_votes_commit_coords = self.yes_votes_commit.to_affine().coordinates().unwrap(); - - let all_votes_commit_coords = self.all_votes_commit.to_affine().coordinates().unwrap(); - - let input_value_commit_coords = self.input_value_commit.to_affine().coordinates().unwrap(); - - vec![( - "dao-exec".to_string(), - vec![ - self.proposal, - self.coin_0, - self.coin_1, - *yes_votes_commit_coords.x(), - *yes_votes_commit_coords.y(), - *all_votes_commit_coords.x(), - *all_votes_commit_coords.y(), - *input_value_commit_coords.x(), - *input_value_commit_coords.y(), - *super::FUNC_ID, - pallas::Base::from(0), - pallas::Base::from(0), - ], - )] - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn signature_public_keys(&self) -> Vec { - vec![] - } - - fn encode_bytes( - &self, - mut writer: &mut dyn std::io::Write, - ) -> std::result::Result { - self.encode(&mut writer) - } -} - -pub fn state_transition( - states: &StateRegistry, - func_call_index: usize, - parent_tx: &Transaction, -) -> Result> { - let func_call = &parent_tx.func_calls[func_call_index]; - let call_data = func_call.call_data.as_any(); - - assert_eq!((*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::(); - - // This will be inside wasm so unwrap is fine. - let call_data = call_data.unwrap(); - - // Enforce tx has correct format: - // 1. There should only be 2 func_call's - if parent_tx.func_calls.len() != 2 { - return Err(Error::InvalidNumberOfFuncCalls) - } - - // 2. func_call_index == 1 - if func_call_index != 1 { - return Err(Error::InvalidIndex) - } - - // 3. First item should be a Money::transfer() calldata - if parent_tx.func_calls[0].func_id != *money::transfer::FUNC_ID { - return Err(Error::InvalidCallData) - } - - let money_transfer_call_data = parent_tx.func_calls[0].call_data.as_any(); - let money_transfer_call_data = - money_transfer_call_data.downcast_ref::(); - let money_transfer_call_data = money_transfer_call_data.unwrap(); - assert_eq!( - money_transfer_call_data.type_id(), - TypeId::of::() - ); - - // 4. Money::transfer() has exactly 2 outputs - if money_transfer_call_data.outputs.len() != 2 { - return Err(Error::InvalidNumberOfOutputs) - } - - // Checks: - // 1. Check both coins in Money::transfer() are equal to our coin_0, coin_1 - if money_transfer_call_data.outputs[0].revealed.coin != Coin(call_data.coin_0) { - return Err(Error::InvalidOutput) - } - //if money_transfer_call_data.outputs[1].revealed.coin != Coin(call_data.coin_1) { - // return Err(Error::InvalidOutput) - //} - - // 2. sum of Money::transfer() calldata input_value_commits == our input value commit - let mut input_value_commits = pallas::Point::identity(); - for input in &money_transfer_call_data.inputs { - input_value_commits += input.revealed.value_commit; - } - if input_value_commits != call_data.input_value_commit { - return Err(Error::InvalidValueCommit) - } - - // 3. get the ProposalVote from DAO::State - let state = - states.lookup::(*CONTRACT_ID).expect("Return type is not of type State"); - let proposal_votes = state.proposal_votes.get(&HashableBase(call_data.proposal)).unwrap(); - - // 4. check yes_votes_commit is the same as in ProposalVote - if proposal_votes.yes_votes_commit != call_data.yes_votes_commit { - return Err(Error::InvalidVoteCommit) - } - // 5. also check all_votes_commit - if proposal_votes.all_votes_commit != call_data.all_votes_commit { - return Err(Error::InvalidVoteCommit) - } - - Ok(Box::new(Update { proposal: call_data.proposal })) -} - -#[derive(Clone)] -pub struct Update { - pub proposal: pallas::Base, -} - -impl UpdateBase for Update { - fn apply(self: Box, states: &mut StateRegistry) { - let state = states - .lookup_mut::(*CONTRACT_ID) - .expect("Return type is not of type State"); - state.proposal_votes.remove(&HashableBase(self.proposal)).unwrap(); - } -} diff --git a/example/dao2/src/contract/dao/exec/wallet.rs b/example/dao2/src/contract/dao/exec/wallet.rs deleted file mode 100644 index 51963252b..000000000 --- a/example/dao2/src/contract/dao/exec/wallet.rs +++ /dev/null @@ -1,211 +0,0 @@ -/* 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_sdk::crypto::{pedersen::pedersen_commitment_u64, poseidon_hash, SecretKey}; -use halo2_proofs::circuit::Value; -use log::debug; -use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas}; -use rand::rngs::OsRng; - -use darkfi::{ - crypto::Proof, - zk::vm::{Witness, ZkCircuit}, -}; - -use crate::{ - contract::dao::{ - exec::validate::CallData, mint::wallet::DaoParams, propose::wallet::Proposal, CONTRACT_ID, - }, - util::{FuncCall, ZkContractInfo, ZkContractTable}, -}; - -pub struct Builder { - pub proposal: Proposal, - pub dao: DaoParams, - pub yes_votes_value: u64, - pub all_votes_value: u64, - pub yes_votes_blind: pallas::Scalar, - pub all_votes_blind: pallas::Scalar, - pub user_serial: pallas::Base, - pub user_coin_blind: pallas::Base, - pub dao_serial: pallas::Base, - pub dao_coin_blind: pallas::Base, - pub input_value: u64, - pub input_value_blind: pallas::Scalar, - pub hook_dao_exec: pallas::Base, - pub signature_secret: SecretKey, -} - -impl Builder { - pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall { - debug!(target: "dao_contract::exec::wallet::Builder", "build()"); - let mut proofs = vec![]; - - let (proposal_dest_x, proposal_dest_y) = self.proposal.dest.xy(); - - let proposal_amount = pallas::Base::from(self.proposal.amount); - - let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit); - let dao_quorum = pallas::Base::from(self.dao.quorum); - let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot); - let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base); - - let (dao_pub_x, dao_pub_y) = self.dao.public_key.xy(); - - let user_spend_hook = pallas::Base::from(0); - let user_data = pallas::Base::from(0); - let input_value = pallas::Base::from(self.input_value); - let change = input_value - proposal_amount; - - let dao_bulla = poseidon_hash::<8>([ - dao_proposer_limit, - dao_quorum, - dao_approval_ratio_quot, - dao_approval_ratio_base, - self.dao.gov_token_id.inner(), - dao_pub_x, - dao_pub_y, - self.dao.bulla_blind, - ]); - - let proposal_bulla = poseidon_hash::<8>([ - proposal_dest_x, - proposal_dest_y, - proposal_amount, - self.proposal.serial, - self.proposal.token_id.inner(), - dao_bulla, - self.proposal.blind, - // @tmp-workaround - self.proposal.blind, - ]); - - let coin_0 = poseidon_hash::<8>([ - proposal_dest_x, - proposal_dest_y, - proposal_amount, - self.proposal.token_id.inner(), - self.proposal.serial, - user_spend_hook, - user_data, - self.proposal.blind, - ]); - - let coin_1 = poseidon_hash::<8>([ - dao_pub_x, - dao_pub_y, - change, - self.proposal.token_id.inner(), - self.dao_serial, - self.hook_dao_exec, - dao_bulla, - self.dao_coin_blind, - ]); - - let yes_votes_commit = pedersen_commitment_u64(self.yes_votes_value, self.yes_votes_blind); - let yes_votes_commit_coords = yes_votes_commit.to_affine().coordinates().unwrap(); - - let all_votes_commit = pedersen_commitment_u64(self.all_votes_value, self.all_votes_blind); - let all_votes_commit_coords = all_votes_commit.to_affine().coordinates().unwrap(); - - let input_value_commit = pedersen_commitment_u64(self.input_value, self.input_value_blind); - let input_value_commit_coords = input_value_commit.to_affine().coordinates().unwrap(); - - let zk_info = zk_bins.lookup(&"dao-exec".to_string()).unwrap(); - let zk_info = if let ZkContractInfo::Binary(info) = zk_info { - info - } else { - panic!("Not binary info") - }; - - let zk_bin = zk_info.bincode.clone(); - - let prover_witnesses = vec![ - // - // proposal params - Witness::Base(Value::known(proposal_dest_x)), - Witness::Base(Value::known(proposal_dest_y)), - Witness::Base(Value::known(proposal_amount)), - Witness::Base(Value::known(self.proposal.serial)), - Witness::Base(Value::known(self.proposal.token_id.inner())), - Witness::Base(Value::known(self.proposal.blind)), - // DAO params - Witness::Base(Value::known(dao_proposer_limit)), - Witness::Base(Value::known(dao_quorum)), - Witness::Base(Value::known(dao_approval_ratio_quot)), - Witness::Base(Value::known(dao_approval_ratio_base)), - Witness::Base(Value::known(self.dao.gov_token_id.inner())), - Witness::Base(Value::known(dao_pub_x)), - Witness::Base(Value::known(dao_pub_y)), - Witness::Base(Value::known(self.dao.bulla_blind)), - // votes - Witness::Base(Value::known(pallas::Base::from(self.yes_votes_value))), - Witness::Base(Value::known(pallas::Base::from(self.all_votes_value))), - Witness::Scalar(Value::known(self.yes_votes_blind)), - Witness::Scalar(Value::known(self.all_votes_blind)), - // outputs + inputs - Witness::Base(Value::known(self.user_serial)), - Witness::Base(Value::known(self.user_coin_blind)), - Witness::Base(Value::known(self.dao_serial)), - Witness::Base(Value::known(self.dao_coin_blind)), - Witness::Base(Value::known(input_value)), - Witness::Scalar(Value::known(self.input_value_blind)), - // misc - Witness::Base(Value::known(self.hook_dao_exec)), - Witness::Base(Value::known(user_spend_hook)), - Witness::Base(Value::known(user_data)), - ]; - - let public_inputs = vec![ - proposal_bulla, - coin_0, - coin_1, - *yes_votes_commit_coords.x(), - *yes_votes_commit_coords.y(), - *all_votes_commit_coords.x(), - *all_votes_commit_coords.y(), - *input_value_commit_coords.x(), - *input_value_commit_coords.y(), - self.hook_dao_exec, - user_spend_hook, - user_data, - ]; - - let circuit = ZkCircuit::new(prover_witnesses, zk_bin); - let proving_key = &zk_info.proving_key; - let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) - .expect("DAO::exec() proving error!)"); - proofs.push(input_proof); - - let call_data = CallData { - proposal: proposal_bulla, - coin_0, - coin_1, - yes_votes_commit, - all_votes_commit, - input_value_commit, - }; - - FuncCall { - contract_id: *CONTRACT_ID, - func_id: *super::FUNC_ID, - call_data: Box::new(call_data), - proofs, - } - } -} diff --git a/example/dao2/src/contract/dao/mint/mod.rs b/example/dao2/src/contract/dao/mint/mod.rs deleted file mode 100644 index 462e5089c..000000000 --- a/example/dao2/src/contract/dao/mint/mod.rs +++ /dev/null @@ -1,62 +0,0 @@ -/* 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 lazy_static::lazy_static; -use pasta_curves::{group::ff::Field, pallas}; -use rand::rngs::OsRng; - -pub mod validate; -/// This is an anonymous contract function that mutates the internal DAO state. -/// -/// Corresponds to `mint(proposer_limit, quorum, approval_ratio, dao_pubkey, dao_blind)` -/// -/// The prover creates a `Builder`, which then constructs the `Tx` that the verifier can -/// check using `state_transition()`. -/// -/// # Arguments -/// -/// * `proposer_limit` - Number of governance tokens that holder must possess in order to -/// propose a new vote. -/// * `quorum` - Number of minimum votes that must be met for a proposal to pass. -/// * `approval_ratio` - Ratio of winning to total votes for a proposal to pass. -/// * `dao_pubkey` - Public key of the DAO for permissioned access. This can also be -/// shared publicly if you want a full decentralized DAO. -/// * `dao_blind` - Blinding factor for the DAO bulla. -/// -/// # Example -/// -/// ```rust -/// let dao_proposer_limit = 110; -/// let dao_quorum = 110; -/// let dao_approval_ratio = 2; -/// -/// let builder = dao_contract::Mint::Builder( -/// dao_proposer_limit, -/// dao_quorum, -/// dao_approval_ratio, -/// gov_token_id, -/// dao_pubkey, -/// dao_blind -/// ); -/// let tx = builder.build(); -/// ``` -pub mod wallet; - -lazy_static! { - pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng); -} diff --git a/example/dao2/src/contract/dao/mint/validate.rs b/example/dao2/src/contract/dao/mint/validate.rs deleted file mode 100644 index c76e81551..000000000 --- a/example/dao2/src/contract/dao/mint/validate.rs +++ /dev/null @@ -1,90 +0,0 @@ -/* 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 std::any::{Any, TypeId}; - -use darkfi::crypto::types::DrkCircuitField; -use darkfi_sdk::crypto::PublicKey; -use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable}; - -use crate::{ - contract::dao::{DaoBulla, State, CONTRACT_ID}, - util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, -}; - -pub fn state_transition( - _states: &StateRegistry, - func_call_index: usize, - parent_tx: &Transaction, -) -> Result> { - let func_call = &parent_tx.func_calls[func_call_index]; - let call_data = func_call.call_data.as_any(); - - assert_eq!((*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::(); - - // This will be inside wasm so unwrap is fine. - let call_data = call_data.unwrap(); - - Ok(Box::new(Update { dao_bulla: call_data.dao_bulla.clone() })) -} - -#[derive(Clone)] -pub struct Update { - pub dao_bulla: DaoBulla, -} - -impl UpdateBase for Update { - fn apply(self: Box, states: &mut StateRegistry) { - // Lookup dao_contract state from registry - let state = states.lookup_mut::(*CONTRACT_ID).unwrap(); - // Add dao_bulla to state.dao_bullas - state.add_dao_bulla(self.dao_bulla); - } -} - -#[derive(Debug, Clone, thiserror::Error)] -pub enum Error {} - -type Result = std::result::Result; - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct CallData { - pub dao_bulla: DaoBulla, -} - -impl CallDataBase for CallData { - fn zk_public_values(&self) -> Vec<(String, Vec)> { - vec![("dao-mint".to_string(), vec![self.dao_bulla.0])] - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn signature_public_keys(&self) -> Vec { - vec![] - } - - fn encode_bytes( - &self, - mut writer: &mut dyn std::io::Write, - ) -> std::result::Result { - self.encode(&mut writer) - } -} diff --git a/example/dao2/src/contract/dao/mint/wallet.rs b/example/dao2/src/contract/dao/mint/wallet.rs deleted file mode 100644 index 382b2b831..000000000 --- a/example/dao2/src/contract/dao/mint/wallet.rs +++ /dev/null @@ -1,117 +0,0 @@ -/* 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_sdk::crypto::{poseidon_hash, PublicKey, SecretKey, TokenId}; -use halo2_proofs::circuit::Value; -use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas}; -use rand::rngs::OsRng; - -use darkfi::{ - crypto::Proof, - zk::vm::{Witness, ZkCircuit}, -}; - -use dao_contract::{DaoBulla, DaoFunction, DaoMintParams}; - -use crate::{ - contract::dao::CONTRACT_ID, - util::{ZkContractInfo, ZkContractTable}, -}; - -#[derive(Clone)] -pub struct DaoParams { - pub proposer_limit: u64, - pub quorum: u64, - pub approval_ratio_quot: u64, - pub approval_ratio_base: u64, - pub gov_token_id: TokenId, - pub public_key: PublicKey, - pub bulla_blind: pallas::Base, -} - -pub struct Builder { - pub dao_proposer_limit: u64, - pub dao_quorum: u64, - pub dao_approval_ratio_quot: u64, - pub dao_approval_ratio_base: u64, - pub gov_token_id: TokenId, - pub dao_pubkey: PublicKey, - pub dao_bulla_blind: pallas::Base, - pub signature_secret: SecretKey, -} - -impl Builder { - /// Consumes self, and produces the function call - pub fn build(self, zk_bins: &ZkContractTable) -> (DaoMintParams, Vec) { - // Dao bulla - let dao_proposer_limit = pallas::Base::from(self.dao_proposer_limit); - let dao_quorum = pallas::Base::from(self.dao_quorum); - let dao_approval_ratio_quot = pallas::Base::from(self.dao_approval_ratio_quot); - let dao_approval_ratio_base = pallas::Base::from(self.dao_approval_ratio_base); - - let (dao_pub_x, dao_pub_y) = self.dao_pubkey.xy(); - - let dao_bulla = poseidon_hash::<8>([ - dao_proposer_limit, - dao_quorum, - dao_approval_ratio_quot, - dao_approval_ratio_base, - self.gov_token_id.inner(), - dao_pub_x, - dao_pub_y, - self.dao_bulla_blind, - ]); - let dao_bulla = DaoBulla(dao_bulla); - - // Now create the mint proof - let zk_info = zk_bins.lookup(&"dao-mint".to_string()).unwrap(); - let zk_info = if let ZkContractInfo::Binary(info) = zk_info { - info - } else { - panic!("Not binary info") - }; - let zk_bin = zk_info.bincode.clone(); - let prover_witnesses = vec![ - Witness::Base(Value::known(dao_proposer_limit)), - Witness::Base(Value::known(dao_quorum)), - Witness::Base(Value::known(dao_approval_ratio_quot)), - Witness::Base(Value::known(dao_approval_ratio_base)), - Witness::Base(Value::known(self.gov_token_id.inner())), - Witness::Base(Value::known(dao_pub_x)), - Witness::Base(Value::known(dao_pub_y)), - Witness::Base(Value::known(self.dao_bulla_blind)), - ]; - let public_inputs = vec![dao_bulla.0]; - let circuit = ZkCircuit::new(prover_witnesses, zk_bin); - - let proving_key = &zk_info.proving_key; - let mint_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) - .expect("DAO::mint() proving error!"); - - /* - let call_data = CallData { dao_bulla }; - FuncCall { - contract_id: *CONTRACT_ID, - func_id: *super::FUNC_ID, - call_data: Box::new(call_data), - proofs: vec![mint_proof], - } - */ - (DaoMintParams { dao_bulla }, vec![mint_proof]) - } -} diff --git a/example/dao2/src/contract/dao/mod.rs b/example/dao2/src/contract/dao/mod.rs deleted file mode 100644 index aa51914d8..000000000 --- a/example/dao2/src/contract/dao/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -/* 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 lazy_static::lazy_static; -use pasta_curves::{group::ff::Field, pallas}; -use rand::rngs::OsRng; - -// mint() -pub mod mint; -// propose() -pub mod propose; -// vote{} -pub mod vote; -// exec{} -pub mod exec; - -pub mod state; - -pub use state::{DaoBulla, State}; - -lazy_static! { - pub static ref CONTRACT_ID: pallas::Base = pallas::Base::random(&mut OsRng); -} diff --git a/example/dao2/src/contract/dao/propose/mod.rs b/example/dao2/src/contract/dao/propose/mod.rs deleted file mode 100644 index c377c014d..000000000 --- a/example/dao2/src/contract/dao/propose/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -/* 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 lazy_static::lazy_static; -use pasta_curves::{group::ff::Field, pallas}; -use rand::rngs::OsRng; - -pub mod validate; -pub mod wallet; - -lazy_static! { - pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng); -} diff --git a/example/dao2/src/contract/dao/propose/validate.rs b/example/dao2/src/contract/dao/propose/validate.rs deleted file mode 100644 index 12816cd23..000000000 --- a/example/dao2/src/contract/dao/propose/validate.rs +++ /dev/null @@ -1,186 +0,0 @@ -/* 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 std::any::{Any, TypeId}; - -use darkfi_sdk::crypto::{MerkleNode, PublicKey}; -use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable}; -use log::error; -use pasta_curves::{ - arithmetic::CurveAffine, - group::{Curve, Group}, - pallas, -}; - -use darkfi::{crypto::types::DrkCircuitField, Error as DarkFiError}; - -use crate::{ - contract::{dao, dao::State as DaoState, money, money::state::State as MoneyState}, - note::EncryptedNote2, - util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, -}; - -// used for debugging -// const TARGET: &str = "dao_contract::propose::validate::state_transition()"; - -#[derive(Debug, Clone, thiserror::Error)] -pub enum Error { - #[error("Invalid input merkle root")] - InvalidInputMerkleRoot, - - #[error("Invalid DAO merkle root")] - InvalidDaoMerkleRoot, - - #[error("DarkFi error: {0}")] - DarkFiError(String), -} -type Result = std::result::Result; - -impl From for Error { - fn from(err: DarkFiError) -> Self { - Self::DarkFiError(err.to_string()) - } -} - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct CallData { - pub header: Header, - pub inputs: Vec, -} - -impl CallDataBase for CallData { - fn zk_public_values(&self) -> Vec<(String, Vec)> { - let mut zk_publics = Vec::new(); - let mut total_funds_commit = pallas::Point::identity(); - - assert!(!self.inputs.is_empty(), "inputs length cannot be zero"); - for input in &self.inputs { - total_funds_commit += input.value_commit; - let value_coords = input.value_commit.to_affine().coordinates().unwrap(); - - let (sig_x, sig_y) = input.signature_public.xy(); - - zk_publics.push(( - "dao-propose-burn".to_string(), - vec![ - *value_coords.x(), - *value_coords.y(), - self.header.token_commit, - input.merkle_root.inner(), - sig_x, - sig_y, - ], - )); - } - - let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap(); - zk_publics.push(( - "dao-propose-main".to_string(), - vec![ - self.header.token_commit, - self.header.dao_merkle_root.inner(), - self.header.proposal_bulla, - *total_funds_coords.x(), - *total_funds_coords.y(), - ], - )); - - zk_publics - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn signature_public_keys(&self) -> Vec { - let mut signature_public_keys = vec![]; - for input in self.inputs.clone() { - signature_public_keys.push(input.signature_public); - } - signature_public_keys - } - - fn encode_bytes( - &self, - mut writer: &mut dyn std::io::Write, - ) -> std::result::Result { - self.encode(&mut writer) - } -} - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct Header { - pub dao_merkle_root: MerkleNode, - pub token_commit: pallas::Base, - pub proposal_bulla: pallas::Base, - pub enc_note: EncryptedNote2, -} - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct Input { - pub value_commit: pallas::Point, - pub merkle_root: MerkleNode, - pub signature_public: PublicKey, -} - -pub fn state_transition( - states: &StateRegistry, - func_call_index: usize, - parent_tx: &Transaction, -) -> Result> { - let func_call = &parent_tx.func_calls[func_call_index]; - let call_data = func_call.call_data.as_any(); - - assert_eq!((*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::(); - - // This will be inside wasm so unwrap is fine. - let call_data = call_data.unwrap(); - - // Check the merkle roots for the input coins are valid - for input in &call_data.inputs { - let money_state = states.lookup::(*money::CONTRACT_ID).unwrap(); - if !money_state.is_valid_merkle(&input.merkle_root) { - return Err(Error::InvalidInputMerkleRoot) - } - } - - let state = states.lookup::(*dao::CONTRACT_ID).unwrap(); - - // Is the DAO bulla generated in the ZK proof valid - if !state.is_valid_dao_merkle(&call_data.header.dao_merkle_root) { - return Err(Error::InvalidDaoMerkleRoot) - } - - // TODO: look at gov tokens avoid using already spent ones - // Need to spend original coin and generate 2 nullifiers? - - Ok(Box::new(Update { proposal_bulla: call_data.header.proposal_bulla })) -} - -#[derive(Clone)] -pub struct Update { - pub proposal_bulla: pallas::Base, -} - -impl UpdateBase for Update { - fn apply(self: Box, states: &mut StateRegistry) { - let state = states.lookup_mut::(*dao::CONTRACT_ID).unwrap(); - state.add_proposal_bulla(self.proposal_bulla); - } -} diff --git a/example/dao2/src/contract/dao/propose/wallet.rs b/example/dao2/src/contract/dao/propose/wallet.rs deleted file mode 100644 index 3a9613439..000000000 --- a/example/dao2/src/contract/dao/propose/wallet.rs +++ /dev/null @@ -1,286 +0,0 @@ -/* 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_sdk::crypto::{ - pedersen::pedersen_commitment_u64, poseidon_hash, MerkleNode, PublicKey, SecretKey, TokenId, -}; -use darkfi_serial::{SerialDecodable, SerialEncodable}; -use halo2_proofs::circuit::Value; -use incrementalmerkletree::Hashable; -use pasta_curves::{ - arithmetic::CurveAffine, - group::{ff::Field, Curve}, - pallas, -}; -use rand::rngs::OsRng; - -use darkfi::{ - crypto::Proof, - zk::vm::{Witness, ZkCircuit}, -}; - -use crate::{ - contract::{ - dao::{ - mint::wallet::DaoParams, - propose::validate::{CallData, Header, Input}, - CONTRACT_ID, - }, - money, - }, - note, - util::{FuncCall, ZkContractInfo, ZkContractTable}, -}; - -#[derive(SerialEncodable, SerialDecodable)] -pub struct Note { - pub proposal: Proposal, -} - -pub struct BuilderInput { - pub secret: SecretKey, - pub note: money::transfer::wallet::Note, - pub leaf_position: incrementalmerkletree::Position, - pub merkle_path: Vec, - pub signature_secret: SecretKey, -} - -#[derive(SerialEncodable, SerialDecodable, Clone)] -pub struct Proposal { - pub dest: PublicKey, - pub amount: u64, - pub serial: pallas::Base, - pub token_id: TokenId, - pub blind: pallas::Base, -} - -pub struct Builder { - pub inputs: Vec, - pub proposal: Proposal, - pub dao: DaoParams, - pub dao_leaf_position: incrementalmerkletree::Position, - pub dao_merkle_path: Vec, - pub dao_merkle_root: MerkleNode, -} - -impl Builder { - pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall { - let mut proofs = vec![]; - - let gov_token_blind = pallas::Base::random(&mut OsRng); - - let mut inputs = vec![]; - let mut total_funds = 0; - let mut total_funds_blinds = pallas::Scalar::from(0); - - for input in self.inputs { - let funds_blind = pallas::Scalar::random(&mut OsRng); - total_funds += input.note.value; - total_funds_blinds += funds_blind; - - let signature_public = PublicKey::from_secret(input.signature_secret); - - let zk_info = zk_bins.lookup(&"dao-propose-burn".to_string()).unwrap(); - let zk_info = if let ZkContractInfo::Binary(info) = zk_info { - info - } else { - panic!("Not binary info") - }; - let zk_bin = zk_info.bincode.clone(); - - // Note from the previous output - let note = input.note; - let leaf_pos: u64 = input.leaf_position.into(); - - let prover_witnesses = vec![ - Witness::Base(Value::known(input.secret.inner())), - Witness::Base(Value::known(note.serial)), - Witness::Base(Value::known(pallas::Base::from(0))), - Witness::Base(Value::known(pallas::Base::from(0))), - Witness::Base(Value::known(pallas::Base::from(note.value))), - Witness::Base(Value::known(note.token_id.inner())), - Witness::Base(Value::known(note.coin_blind)), - Witness::Scalar(Value::known(funds_blind)), - Witness::Base(Value::known(gov_token_blind)), - Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())), - Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())), - Witness::Base(Value::known(input.signature_secret.inner())), - ]; - - let public_key = PublicKey::from_secret(input.secret); - let (pub_x, pub_y) = public_key.xy(); - - let coin = poseidon_hash::<8>([ - pub_x, - pub_y, - pallas::Base::from(note.value), - note.token_id.inner(), - note.serial, - pallas::Base::from(0), - pallas::Base::from(0), - note.coin_blind, - ]); - - let merkle_root = { - let position: u64 = input.leaf_position.into(); - let mut current = MerkleNode::from(coin); - for (level, sibling) in input.merkle_path.iter().enumerate() { - let level = level as u8; - current = if position & (1 << level) == 0 { - MerkleNode::combine(level.into(), ¤t, sibling) - } else { - MerkleNode::combine(level.into(), sibling, ¤t) - }; - } - current - }; - - let token_commit = poseidon_hash::<2>([note.token_id.inner(), gov_token_blind]); - assert_eq!(self.dao.gov_token_id, note.token_id); - - let value_commit = pedersen_commitment_u64(note.value, funds_blind); - let value_coords = value_commit.to_affine().coordinates().unwrap(); - - let (sig_x, sig_y) = signature_public.xy(); - - let public_inputs = vec![ - *value_coords.x(), - *value_coords.y(), - token_commit, - merkle_root.inner(), - sig_x, - sig_y, - ]; - let circuit = ZkCircuit::new(prover_witnesses, zk_bin); - - let proving_key = &zk_info.proving_key; - let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) - .expect("DAO::propose() proving error!"); - proofs.push(input_proof); - - let input = Input { value_commit, merkle_root, signature_public }; - inputs.push(input); - } - - let total_funds_commit = pedersen_commitment_u64(total_funds, total_funds_blinds); - let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap(); - let total_funds = pallas::Base::from(total_funds); - - let token_commit = poseidon_hash::<2>([self.dao.gov_token_id.inner(), gov_token_blind]); - - let (proposal_dest_x, proposal_dest_y) = self.proposal.dest.xy(); - - let proposal_amount = pallas::Base::from(self.proposal.amount); - - let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit); - let dao_quorum = pallas::Base::from(self.dao.quorum); - let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot); - let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base); - - let (dao_pub_x, dao_pub_y) = self.dao.public_key.xy(); - - let dao_bulla = poseidon_hash::<8>([ - dao_proposer_limit, - dao_quorum, - dao_approval_ratio_quot, - dao_approval_ratio_base, - self.dao.gov_token_id.inner(), - dao_pub_x, - dao_pub_y, - self.dao.bulla_blind, - ]); - - let dao_leaf_position: u64 = self.dao_leaf_position.into(); - - let proposal_bulla = poseidon_hash::<8>([ - proposal_dest_x, - proposal_dest_y, - proposal_amount, - self.proposal.serial, - self.proposal.token_id.inner(), - dao_bulla, - self.proposal.blind, - // @tmp-workaround - self.proposal.blind, - ]); - - let zk_info = zk_bins.lookup(&"dao-propose-main".to_string()).unwrap(); - let zk_info = if let ZkContractInfo::Binary(info) = zk_info { - info - } else { - panic!("Not binary info") - }; - let zk_bin = zk_info.bincode.clone(); - let prover_witnesses = vec![ - // Proposers total number of gov tokens - Witness::Base(Value::known(total_funds)), - Witness::Scalar(Value::known(total_funds_blinds)), - // Used for blinding exported gov token ID - Witness::Base(Value::known(gov_token_blind)), - // proposal params - Witness::Base(Value::known(proposal_dest_x)), - Witness::Base(Value::known(proposal_dest_y)), - Witness::Base(Value::known(proposal_amount)), - Witness::Base(Value::known(self.proposal.serial)), - Witness::Base(Value::known(self.proposal.token_id.inner())), - Witness::Base(Value::known(self.proposal.blind)), - // DAO params - Witness::Base(Value::known(dao_proposer_limit)), - Witness::Base(Value::known(dao_quorum)), - Witness::Base(Value::known(dao_approval_ratio_quot)), - Witness::Base(Value::known(dao_approval_ratio_base)), - Witness::Base(Value::known(self.dao.gov_token_id.inner())), - Witness::Base(Value::known(dao_pub_x)), - Witness::Base(Value::known(dao_pub_y)), - Witness::Base(Value::known(self.dao.bulla_blind)), - Witness::Uint32(Value::known(dao_leaf_position.try_into().unwrap())), - Witness::MerklePath(Value::known(self.dao_merkle_path.try_into().unwrap())), - ]; - let public_inputs = vec![ - token_commit, - self.dao_merkle_root.inner(), - proposal_bulla, - *total_funds_coords.x(), - *total_funds_coords.y(), - ]; - let circuit = ZkCircuit::new(prover_witnesses, zk_bin); - - let proving_key = &zk_info.proving_key; - let main_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) - .expect("DAO::propose() proving error!"); - proofs.push(main_proof); - - let note = Note { proposal: self.proposal }; - let enc_note = note::encrypt(¬e, &self.dao.public_key).unwrap(); - let header = Header { - dao_merkle_root: self.dao_merkle_root, - proposal_bulla, - token_commit, - enc_note, - }; - - let call_data = CallData { header, inputs }; - - FuncCall { - contract_id: *CONTRACT_ID, - func_id: *super::FUNC_ID, - call_data: Box::new(call_data), - proofs, - } - } -} diff --git a/example/dao2/src/contract/dao/state.rs b/example/dao2/src/contract/dao/state.rs deleted file mode 100644 index 7ad5562c9..000000000 --- a/example/dao2/src/contract/dao/state.rs +++ /dev/null @@ -1,114 +0,0 @@ -/* 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 std::{any::Any, collections::HashMap}; - -use darkfi_sdk::crypto::{constants::MERKLE_DEPTH, MerkleNode, Nullifier}; -use darkfi_serial::{SerialDecodable, SerialEncodable}; -use incrementalmerkletree::{bridgetree::BridgeTree, Tree}; -use pasta_curves::{group::Group, pallas}; - -use crate::util::HashableBase; - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct DaoBulla(pub pallas::Base); - -type MerkleTree = BridgeTree; - -pub struct ProposalVotes { - // TODO: might be more logical to have 'yes_votes_commit' and 'no_votes_commit' - /// Weighted vote commit - pub yes_votes_commit: pallas::Point, - /// All value staked in the vote - pub all_votes_commit: pallas::Point, - /// Vote nullifiers - pub vote_nulls: Vec, -} - -impl ProposalVotes { - pub fn nullifier_exists(&self, nullifier: &Nullifier) -> bool { - self.vote_nulls.iter().any(|n| n == nullifier) - } -} - -/// This DAO state is for all DAOs on the network. There should only be a single instance. -pub struct State { - dao_bullas: Vec, - pub dao_tree: MerkleTree, - pub dao_roots: Vec, - - //proposal_bullas: Vec, - pub proposal_tree: MerkleTree, - pub proposal_roots: Vec, - pub proposal_votes: HashMap, -} - -impl State { - pub fn new() -> Box { - Box::new(Self { - dao_bullas: Vec::new(), - dao_tree: MerkleTree::new(100), - dao_roots: Vec::new(), - //proposal_bullas: Vec::new(), - proposal_tree: MerkleTree::new(100), - proposal_roots: Vec::new(), - proposal_votes: HashMap::new(), - }) - } - - pub fn add_dao_bulla(&mut self, bulla: DaoBulla) { - let node = MerkleNode::from(bulla.0); - self.dao_bullas.push(bulla); - self.dao_tree.append(&node); - self.dao_roots.push(self.dao_tree.root(0).unwrap()); - } - - pub fn add_proposal_bulla(&mut self, bulla: pallas::Base) { - let node = MerkleNode::from(bulla); - //self.proposal_bullas.push(bulla); - self.proposal_tree.append(&node); - self.proposal_roots.push(self.proposal_tree.root(0).unwrap()); - self.proposal_votes.insert( - HashableBase(bulla), - ProposalVotes { - yes_votes_commit: pallas::Point::identity(), - all_votes_commit: pallas::Point::identity(), - vote_nulls: Vec::new(), - }, - ); - } - - pub fn lookup_proposal_votes(&self, proposal_bulla: pallas::Base) -> Option<&ProposalVotes> { - self.proposal_votes.get(&HashableBase(proposal_bulla)) - } - pub fn lookup_proposal_votes_mut( - &mut self, - proposal_bulla: pallas::Base, - ) -> Option<&mut ProposalVotes> { - self.proposal_votes.get_mut(&HashableBase(proposal_bulla)) - } - - pub fn is_valid_dao_merkle(&self, root: &MerkleNode) -> bool { - self.dao_roots.iter().any(|m| m == root) - } - - // TODO: This never gets called. - pub fn _is_valid_proposal_merkle(&self, root: &MerkleNode) -> bool { - self.proposal_roots.iter().any(|m| m == root) - } -} diff --git a/example/dao2/src/contract/dao/vote/mod.rs b/example/dao2/src/contract/dao/vote/mod.rs deleted file mode 100644 index c377c014d..000000000 --- a/example/dao2/src/contract/dao/vote/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -/* 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 lazy_static::lazy_static; -use pasta_curves::{group::ff::Field, pallas}; -use rand::rngs::OsRng; - -pub mod validate; -pub mod wallet; - -lazy_static! { - pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng); -} diff --git a/example/dao2/src/contract/dao/vote/validate.rs b/example/dao2/src/contract/dao/vote/validate.rs deleted file mode 100644 index 3215ddc10..000000000 --- a/example/dao2/src/contract/dao/vote/validate.rs +++ /dev/null @@ -1,219 +0,0 @@ -/* 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 std::any::{Any, TypeId}; - -use darkfi_sdk::crypto::{MerkleNode, Nullifier, PublicKey}; -use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable}; -use log::error; -use pasta_curves::{ - arithmetic::CurveAffine, - group::{Curve, Group}, - pallas, -}; - -use darkfi::{crypto::types::DrkCircuitField, Error as DarkFiError}; - -use crate::{ - contract::{dao, dao::State as DaoState, money, money::state::State as MoneyState}, - note::EncryptedNote2, - util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, -}; - -#[derive(Debug, Clone, thiserror::Error)] -pub enum Error { - #[error("Invalid proposal")] - InvalidProposal, - - #[error("Voting with already spent coinage")] - SpentCoin, - - #[error("Double voting")] - DoubleVote, - - #[error("Invalid input merkle root")] - InvalidInputMerkleRoot, - - #[error("DarkFi error: {0}")] - DarkFiError(String), -} -type Result = std::result::Result; - -impl From for Error { - fn from(err: DarkFiError) -> Self { - Self::DarkFiError(err.to_string()) - } -} - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct CallData { - pub header: Header, - pub inputs: Vec, -} - -impl CallDataBase for CallData { - fn zk_public_values(&self) -> Vec<(String, Vec)> { - let mut zk_publics = Vec::new(); - let mut all_votes_commit = pallas::Point::identity(); - - assert!(!self.inputs.is_empty(), "inputs length cannot be zero"); - for input in &self.inputs { - all_votes_commit += input.vote_commit; - let value_coords = input.vote_commit.to_affine().coordinates().unwrap(); - - let (sig_x, sig_y) = input.signature_public.xy(); - - zk_publics.push(( - "dao-vote-burn".to_string(), - vec![ - input.nullifier.inner(), - *value_coords.x(), - *value_coords.y(), - self.header.token_commit, - input.merkle_root.inner(), - sig_x, - sig_y, - ], - )); - } - - let yes_vote_commit_coords = self.header.yes_vote_commit.to_affine().coordinates().unwrap(); - - let vote_commit_coords = all_votes_commit.to_affine().coordinates().unwrap(); - - zk_publics.push(( - "dao-vote-main".to_string(), - vec![ - self.header.token_commit, - self.header.proposal_bulla, - *yes_vote_commit_coords.x(), - *yes_vote_commit_coords.y(), - *vote_commit_coords.x(), - *vote_commit_coords.y(), - ], - )); - - zk_publics - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn signature_public_keys(&self) -> Vec { - let mut signature_public_keys = vec![]; - for input in self.inputs.clone() { - signature_public_keys.push(input.signature_public); - } - signature_public_keys - } - - fn encode_bytes( - &self, - mut writer: &mut dyn std::io::Write, - ) -> std::result::Result { - self.encode(&mut writer) - } -} - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct Header { - pub token_commit: pallas::Base, - pub proposal_bulla: pallas::Base, - pub yes_vote_commit: pallas::Point, - pub enc_note: EncryptedNote2, -} - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct Input { - pub nullifier: Nullifier, - pub vote_commit: pallas::Point, - pub merkle_root: MerkleNode, - pub signature_public: PublicKey, -} - -pub fn state_transition( - states: &StateRegistry, - func_call_index: usize, - parent_tx: &Transaction, -) -> Result> { - let func_call = &parent_tx.func_calls[func_call_index]; - let call_data = func_call.call_data.as_any(); - - assert_eq!((*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::(); - - // This will be inside wasm so unwrap is fine. - let call_data = call_data.unwrap(); - - let dao_state = states.lookup::(*dao::CONTRACT_ID).unwrap(); - - // Check proposal_bulla exists - let votes_info = dao_state.lookup_proposal_votes(call_data.header.proposal_bulla); - if votes_info.is_none() { - return Err(Error::InvalidProposal) - } - let votes_info = votes_info.unwrap(); - - // Check the merkle roots for the input coins are valid - let mut vote_nulls = Vec::new(); - let mut all_vote_commit = pallas::Point::identity(); - for input in &call_data.inputs { - let money_state = states.lookup::(*money::CONTRACT_ID).unwrap(); - if !money_state.is_valid_merkle(&input.merkle_root) { - return Err(Error::InvalidInputMerkleRoot) - } - - if money_state.nullifier_exists(&input.nullifier) { - return Err(Error::SpentCoin) - } - - if votes_info.nullifier_exists(&input.nullifier) { - return Err(Error::DoubleVote) - } - - all_vote_commit += input.vote_commit; - - vote_nulls.push(input.nullifier); - } - - Ok(Box::new(Update { - proposal_bulla: call_data.header.proposal_bulla, - vote_nulls, - yes_vote_commit: call_data.header.yes_vote_commit, - all_vote_commit, - })) -} - -#[derive(Clone)] -pub struct Update { - proposal_bulla: pallas::Base, - vote_nulls: Vec, - pub yes_vote_commit: pallas::Point, - pub all_vote_commit: pallas::Point, -} - -impl UpdateBase for Update { - fn apply(mut self: Box, states: &mut StateRegistry) { - let state = states.lookup_mut::(*dao::CONTRACT_ID).unwrap(); - let votes_info = state.lookup_proposal_votes_mut(self.proposal_bulla).unwrap(); - votes_info.yes_votes_commit += self.yes_vote_commit; - votes_info.all_votes_commit += self.all_vote_commit; - votes_info.vote_nulls.append(&mut self.vote_nulls); - } -} diff --git a/example/dao2/src/contract/dao/vote/wallet.rs b/example/dao2/src/contract/dao/vote/wallet.rs deleted file mode 100644 index 34e74f8fa..000000000 --- a/example/dao2/src/contract/dao/vote/wallet.rs +++ /dev/null @@ -1,308 +0,0 @@ -/* 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_sdk::crypto::{ - pedersen::pedersen_commitment_u64, poseidon_hash, Keypair, MerkleNode, Nullifier, PublicKey, - SecretKey, -}; -use darkfi_serial::{SerialDecodable, SerialEncodable}; -use halo2_proofs::circuit::Value; -use incrementalmerkletree::Hashable; -use log::debug; -use pasta_curves::{ - arithmetic::CurveAffine, - group::{ff::Field, Curve}, - pallas, -}; -use rand::rngs::OsRng; - -use darkfi::{ - crypto::Proof, - zk::vm::{Witness, ZkCircuit}, -}; - -use crate::{ - contract::{ - dao::{ - mint::wallet::DaoParams, - propose::wallet::Proposal, - vote::validate::{CallData, Header, Input}, - CONTRACT_ID, - }, - money, - }, - note, - util::{FuncCall, ZkContractInfo, ZkContractTable}, -}; - -#[derive(SerialEncodable, SerialDecodable)] -pub struct Note { - pub vote: Vote, - pub vote_value: u64, - pub vote_value_blind: pallas::Scalar, -} - -#[derive(SerialEncodable, SerialDecodable)] -pub struct Vote { - pub vote_option: bool, - pub vote_option_blind: pallas::Scalar, -} - -pub struct BuilderInput { - pub secret: SecretKey, - pub note: money::transfer::wallet::Note, - pub leaf_position: incrementalmerkletree::Position, - pub merkle_path: Vec, - pub signature_secret: SecretKey, -} - -// TODO: should be token locking voting? -// Inside ZKproof, check proposal is correct. -pub struct Builder { - pub inputs: Vec, - pub vote: Vote, - pub vote_keypair: Keypair, - pub proposal: Proposal, - pub dao: DaoParams, -} - -impl Builder { - pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall { - debug!(target: "dao_contract::vote::wallet::Builder", "build()"); - let mut proofs = vec![]; - - let gov_token_blind = pallas::Base::random(&mut OsRng); - - let mut inputs = vec![]; - let mut vote_value = 0; - let mut vote_value_blind = pallas::Scalar::from(0); - - for input in self.inputs { - let value_blind = pallas::Scalar::random(&mut OsRng); - - vote_value += input.note.value; - vote_value_blind += value_blind; - - let signature_public = PublicKey::from_secret(input.signature_secret); - - let zk_info = zk_bins.lookup(&"dao-vote-burn".to_string()).unwrap(); - - let zk_info = if let ZkContractInfo::Binary(info) = zk_info { - info - } else { - panic!("Not binary info") - }; - let zk_bin = zk_info.bincode.clone(); - - // Note from the previous output - let note = input.note; - let leaf_pos: u64 = input.leaf_position.into(); - - let prover_witnesses = vec![ - Witness::Base(Value::known(input.secret.inner())), - Witness::Base(Value::known(note.serial)), - Witness::Base(Value::known(pallas::Base::from(0))), - Witness::Base(Value::known(pallas::Base::from(0))), - Witness::Base(Value::known(pallas::Base::from(note.value))), - Witness::Base(Value::known(note.token_id.inner())), - Witness::Base(Value::known(note.coin_blind)), - Witness::Scalar(Value::known(vote_value_blind)), - Witness::Base(Value::known(gov_token_blind)), - Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())), - Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())), - Witness::Base(Value::known(input.signature_secret.inner())), - ]; - - let public_key = PublicKey::from_secret(input.secret); - let (pub_x, pub_y) = public_key.xy(); - - let coin = poseidon_hash::<8>([ - pub_x, - pub_y, - pallas::Base::from(note.value), - note.token_id.inner(), - note.serial, - pallas::Base::from(0), - pallas::Base::from(0), - note.coin_blind, - ]); - - let merkle_root = { - let position: u64 = input.leaf_position.into(); - let mut current = MerkleNode::from(coin); - for (level, sibling) in input.merkle_path.iter().enumerate() { - let level = level as u8; - current = if position & (1 << level) == 0 { - MerkleNode::combine(level.into(), ¤t, sibling) - } else { - MerkleNode::combine(level.into(), sibling, ¤t) - }; - } - current - }; - - let token_commit = poseidon_hash::<2>([note.token_id.inner(), gov_token_blind]); - assert_eq!(self.dao.gov_token_id, note.token_id); - - let nullifier = poseidon_hash::<2>([input.secret.inner(), note.serial]); - - let vote_commit = pedersen_commitment_u64(note.value, vote_value_blind); - let vote_commit_coords = vote_commit.to_affine().coordinates().unwrap(); - - let (sig_x, sig_y) = signature_public.xy(); - - let public_inputs = vec![ - nullifier, - *vote_commit_coords.x(), - *vote_commit_coords.y(), - token_commit, - merkle_root.inner(), - sig_x, - sig_y, - ]; - - let circuit = ZkCircuit::new(prover_witnesses, zk_bin); - let proving_key = &zk_info.proving_key; - debug!(target: "dao_contract::vote::wallet::Builder", "input_proof Proof::create()"); - let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) - .expect("DAO::vote() proving error!"); - proofs.push(input_proof); - - let input = Input { - nullifier: Nullifier::from(nullifier), - vote_commit, - merkle_root, - signature_public, - }; - inputs.push(input); - } - - let token_commit = poseidon_hash::<2>([self.dao.gov_token_id.inner(), gov_token_blind]); - - let (proposal_dest_x, proposal_dest_y) = self.proposal.dest.xy(); - - let proposal_amount = pallas::Base::from(self.proposal.amount); - - let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit); - let dao_quorum = pallas::Base::from(self.dao.quorum); - let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot); - let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base); - - let (dao_pub_x, dao_pub_y) = self.dao.public_key.xy(); - - let dao_bulla = poseidon_hash::<8>([ - dao_proposer_limit, - dao_quorum, - dao_approval_ratio_quot, - dao_approval_ratio_base, - self.dao.gov_token_id.inner(), - dao_pub_x, - dao_pub_y, - self.dao.bulla_blind, - ]); - - let proposal_bulla = poseidon_hash::<8>([ - proposal_dest_x, - proposal_dest_y, - proposal_amount, - self.proposal.serial, - self.proposal.token_id.inner(), - dao_bulla, - self.proposal.blind, - // @tmp-workaround - self.proposal.blind, - ]); - - let vote_option = self.vote.vote_option as u64; - assert!(vote_option == 0 || vote_option == 1); - - let yes_vote_commit = - pedersen_commitment_u64(vote_option * vote_value, self.vote.vote_option_blind); - let yes_vote_commit_coords = yes_vote_commit.to_affine().coordinates().unwrap(); - - let all_vote_commit = pedersen_commitment_u64(vote_value, vote_value_blind); - let all_vote_commit_coords = all_vote_commit.to_affine().coordinates().unwrap(); - - let zk_info = zk_bins.lookup(&"dao-vote-main".to_string()).unwrap(); - let zk_info = if let ZkContractInfo::Binary(info) = zk_info { - info - } else { - panic!("Not binary info") - }; - let zk_bin = zk_info.bincode.clone(); - - let prover_witnesses = vec![ - // proposal params - Witness::Base(Value::known(proposal_dest_x)), - Witness::Base(Value::known(proposal_dest_y)), - Witness::Base(Value::known(proposal_amount)), - Witness::Base(Value::known(self.proposal.serial)), - Witness::Base(Value::known(self.proposal.token_id.inner())), - Witness::Base(Value::known(self.proposal.blind)), - // DAO params - Witness::Base(Value::known(dao_proposer_limit)), - Witness::Base(Value::known(dao_quorum)), - Witness::Base(Value::known(dao_approval_ratio_quot)), - Witness::Base(Value::known(dao_approval_ratio_base)), - Witness::Base(Value::known(self.dao.gov_token_id.inner())), - Witness::Base(Value::known(dao_pub_x)), - Witness::Base(Value::known(dao_pub_y)), - Witness::Base(Value::known(self.dao.bulla_blind)), - // Vote - Witness::Base(Value::known(pallas::Base::from(vote_option))), - Witness::Scalar(Value::known(self.vote.vote_option_blind)), - // Total number of gov tokens allocated - Witness::Base(Value::known(pallas::Base::from(vote_value))), - Witness::Scalar(Value::known(vote_value_blind)), - // gov token - Witness::Base(Value::known(gov_token_blind)), - ]; - - let public_inputs = vec![ - token_commit, - proposal_bulla, - // this should be a value commit?? - *yes_vote_commit_coords.x(), - *yes_vote_commit_coords.y(), - *all_vote_commit_coords.x(), - *all_vote_commit_coords.y(), - ]; - - let circuit = ZkCircuit::new(prover_witnesses, zk_bin); - - let proving_key = &zk_info.proving_key; - debug!(target: "dao_contract::vote::wallet::Builder", "main_proof = Proof::create()"); - let main_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) - .expect("DAO::vote() proving error!"); - proofs.push(main_proof); - - let note = Note { vote: self.vote, vote_value, vote_value_blind }; - let enc_note = note::encrypt(¬e, &self.vote_keypair.public).unwrap(); - - let header = Header { token_commit, proposal_bulla, yes_vote_commit, enc_note }; - - let call_data = CallData { header, inputs }; - - FuncCall { - contract_id: *CONTRACT_ID, - func_id: *super::FUNC_ID, - call_data: Box::new(call_data), - proofs, - } - } -} diff --git a/example/dao2/src/contract/example/foo/mod.rs b/example/dao2/src/contract/example/foo/mod.rs deleted file mode 100644 index c377c014d..000000000 --- a/example/dao2/src/contract/example/foo/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -/* 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 lazy_static::lazy_static; -use pasta_curves::{group::ff::Field, pallas}; -use rand::rngs::OsRng; - -pub mod validate; -pub mod wallet; - -lazy_static! { - pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng); -} diff --git a/example/dao2/src/contract/example/foo/validate.rs b/example/dao2/src/contract/example/foo/validate.rs deleted file mode 100644 index 34a9ecb04..000000000 --- a/example/dao2/src/contract/example/foo/validate.rs +++ /dev/null @@ -1,107 +0,0 @@ -/* 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 std::any::{Any, TypeId}; - -use darkfi::{crypto::types::DrkCircuitField, Error as DarkFiError}; -use darkfi_sdk::crypto::PublicKey; -use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable}; -use pasta_curves::pallas; - -use crate::{ - contract::example::{state::State, CONTRACT_ID}, - util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, -}; - -type Result = std::result::Result; - -#[derive(Debug, Clone, thiserror::Error)] -pub enum Error { - #[error("ValueExists")] - ValueExists, - #[error("DarkFi error: {0}")] - DarkFiError(String), -} - -impl From for Error { - fn from(err: DarkFiError) -> Self { - Self::DarkFiError(err.to_string()) - } -} - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct CallData { - pub public_value: pallas::Base, - pub signature_public: PublicKey, -} - -impl CallDataBase for CallData { - fn zk_public_values(&self) -> Vec<(String, Vec)> { - vec![("example-foo".to_string(), vec![self.public_value])] - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn signature_public_keys(&self) -> Vec { - vec![self.signature_public] - } - - fn encode_bytes( - &self, - mut writer: &mut dyn std::io::Write, - ) -> std::result::Result { - self.encode(&mut writer) - } -} - -pub fn state_transition( - states: &StateRegistry, - func_call_index: usize, - parent_tx: &Transaction, -) -> Result> { - let func_call = &parent_tx.func_calls[func_call_index]; - let call_data = func_call.call_data.as_any(); - - assert_eq!((&*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::(); - - // This will be inside wasm so unwrap is fine. - let call_data = call_data.unwrap(); - - let example_state = states.lookup::(*CONTRACT_ID).unwrap(); - - if example_state.public_exists(&call_data.public_value) { - return Err(Error::ValueExists) - } - - Ok(Box::new(Update { public_value: call_data.public_value })) -} - -#[derive(Clone)] -pub struct Update { - public_value: pallas::Base, -} - -impl UpdateBase for Update { - fn apply(self: Box, states: &mut StateRegistry) { - let example_state = states.lookup_mut::(*CONTRACT_ID).unwrap(); - example_state.add_public_value(self.public_value); - } -} diff --git a/example/dao2/src/contract/example/foo/wallet.rs b/example/dao2/src/contract/example/foo/wallet.rs deleted file mode 100644 index 8b336a5f8..000000000 --- a/example/dao2/src/contract/example/foo/wallet.rs +++ /dev/null @@ -1,89 +0,0 @@ -/* 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_sdk::crypto::{PublicKey, SecretKey}; -use halo2_proofs::circuit::Value; -use log::debug; -use pasta_curves::pallas; -use rand::rngs::OsRng; - -use darkfi::{ - crypto::Proof, - zk::vm::{Witness, ZkCircuit}, -}; - -use crate::{ - contract::example::{foo::validate::CallData, CONTRACT_ID}, - util::{FuncCall, ZkContractInfo, ZkContractTable}, -}; - -pub struct Foo { - pub a: u64, - pub b: u64, -} - -pub struct Builder { - pub foo: Foo, - pub signature_secret: SecretKey, -} - -impl Builder { - pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall { - debug!(target: "example_contract::foo::wallet::Builder", "build()"); - let mut proofs = vec![]; - - let zk_info = zk_bins.lookup(&"example-foo".to_string()).unwrap(); - let zk_info = if let ZkContractInfo::Binary(info) = zk_info { - info - } else { - panic!("Not binary info") - }; - - let zk_bin = zk_info.bincode.clone(); - - let prover_witnesses = vec![ - Witness::Base(Value::known(pallas::Base::from(self.foo.a))), - Witness::Base(Value::known(pallas::Base::from(self.foo.b))), - ]; - - let a = pallas::Base::from(self.foo.a); - let b = pallas::Base::from(self.foo.b); - - let c = a + b; - - let public_inputs = vec![c]; - - let circuit = ZkCircuit::new(prover_witnesses, zk_bin); - debug!(target: "example_contract::foo::wallet::Builder", "input_proof Proof::create()"); - let proving_key = &zk_info.proving_key; - let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) - .expect("Example::foo() proving error!)"); - proofs.push(input_proof); - - let signature_public = PublicKey::from_secret(self.signature_secret); - - let call_data = CallData { public_value: c, signature_public }; - - FuncCall { - contract_id: *CONTRACT_ID, - func_id: *super::FUNC_ID, - call_data: Box::new(call_data), - proofs, - } - } -} diff --git a/example/dao2/src/contract/example/mod.rs b/example/dao2/src/contract/example/mod.rs deleted file mode 100644 index b4169b1d8..000000000 --- a/example/dao2/src/contract/example/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -/* 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 lazy_static::lazy_static; -use pasta_curves::{group::ff::Field, pallas}; -use rand::rngs::OsRng; - -// foo() -pub mod foo; - -pub mod state; - -lazy_static! { - pub static ref CONTRACT_ID: pallas::Base = pallas::Base::random(&mut OsRng); -} diff --git a/example/dao2/src/contract/example/state.rs b/example/dao2/src/contract/example/state.rs deleted file mode 100644 index 0a5d9b434..000000000 --- a/example/dao2/src/contract/example/state.rs +++ /dev/null @@ -1,39 +0,0 @@ -/* 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 std::any::Any; - -use pasta_curves::pallas; - -pub struct State { - pub public_values: Vec, -} - -impl State { - pub fn new() -> Box { - Box::new(Self { public_values: Vec::new() }) - } - - pub fn add_public_value(&mut self, public_value: pallas::Base) { - self.public_values.push(public_value) - } - // - pub fn public_exists(&self, public_value: &pallas::Base) -> bool { - self.public_values.iter().any(|v| v == public_value) - } -} diff --git a/example/dao2/src/contract/mod.rs b/example/dao2/src/contract/mod.rs deleted file mode 100644 index e6f3f0469..000000000 --- a/example/dao2/src/contract/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -/* 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 . - */ - -pub mod dao; -pub mod example; -pub mod money; diff --git a/example/dao2/src/contract/money/mod.rs b/example/dao2/src/contract/money/mod.rs deleted file mode 100644 index 8283793a5..000000000 --- a/example/dao2/src/contract/money/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* 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 lazy_static::lazy_static; -use pasta_curves::{group::ff::Field, pallas}; -use rand::rngs::OsRng; - -// transfer() -pub mod transfer; - -pub mod state; -pub use state::State; - -lazy_static! { - pub static ref CONTRACT_ID: pallas::Base = pallas::Base::random(&mut OsRng); -} diff --git a/example/dao2/src/contract/money/state.rs b/example/dao2/src/contract/money/state.rs deleted file mode 100644 index 057851ef9..000000000 --- a/example/dao2/src/contract/money/state.rs +++ /dev/null @@ -1,70 +0,0 @@ -/* 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_sdk::crypto::{constants::MERKLE_DEPTH, MerkleNode, Nullifier, PublicKey}; -use incrementalmerkletree::bridgetree::BridgeTree; - -type MerkleTree = BridgeTree; - -/// The state machine, held in memory. -pub struct State { - /// The entire Merkle tree state - pub tree: MerkleTree, - /// List of all previous and the current Merkle roots. - /// This is the hashed value of all the children. - pub merkle_roots: Vec, - /// Nullifiers prevent double spending - pub nullifiers: Vec, - - /// Public key of the cashier - pub cashier_signature_public: PublicKey, - - /// Public key of the faucet - pub faucet_signature_public: PublicKey, -} - -impl State { - pub fn new( - cashier_signature_public: PublicKey, - faucet_signature_public: PublicKey, - ) -> Box { - Box::new(Self { - tree: MerkleTree::new(100), - merkle_roots: vec![], - nullifiers: vec![], - cashier_signature_public, - faucet_signature_public, - }) - } - - pub fn is_valid_cashier_public_key(&self, public: &PublicKey) -> bool { - public == &self.cashier_signature_public - } - - pub fn is_valid_faucet_public_key(&self, public: &PublicKey) -> bool { - public == &self.faucet_signature_public - } - - pub fn is_valid_merkle(&self, merkle_root: &MerkleNode) -> bool { - self.merkle_roots.iter().any(|m| m == merkle_root) - } - - pub fn nullifier_exists(&self, nullifier: &Nullifier) -> bool { - self.nullifiers.iter().any(|n| n == nullifier) - } -} diff --git a/example/dao2/src/contract/money/transfer/mod.rs b/example/dao2/src/contract/money/transfer/mod.rs deleted file mode 100644 index 142277ff1..000000000 --- a/example/dao2/src/contract/money/transfer/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -/* 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 lazy_static::lazy_static; -use pasta_curves::{group::ff::Field, pallas}; -use rand::rngs::OsRng; - -pub mod validate; -pub mod wallet; -pub use wallet::{Builder, BuilderClearInputInfo, BuilderInputInfo, BuilderOutputInfo, Note}; - -lazy_static! { - pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng); -} diff --git a/example/dao2/src/contract/money/transfer/validate.rs b/example/dao2/src/contract/money/transfer/validate.rs deleted file mode 100644 index baa039cb6..000000000 --- a/example/dao2/src/contract/money/transfer/validate.rs +++ /dev/null @@ -1,392 +0,0 @@ -/* 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 std::any::{Any, TypeId}; - -use darkfi_sdk::crypto::{ - pedersen::{pedersen_commitment_base, pedersen_commitment_u64}, - MerkleNode, Nullifier, PublicKey, TokenId, -}; -use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable}; -use incrementalmerkletree::Tree; -use log::{debug, error}; -use pasta_curves::{group::Group, pallas}; - -use darkfi::{ - crypto::{ - coin::Coin, - types::{DrkCircuitField, DrkValueBlind, DrkValueCommit}, - BurnRevealedValues, MintRevealedValues, - }, - Error as DarkFiError, -}; - -use crate::{ - contract::{ - dao, - money::{state::State, CONTRACT_ID}, - }, - note::EncryptedNote2, - util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, -}; - -const TARGET: &str = "money_contract::transfer::validate::state_transition()"; - -/// A struct representing a state update. -/// This gets applied on top of an existing state. -#[derive(Clone)] -pub struct Update { - /// All nullifiers in a transaction - pub nullifiers: Vec, - /// All coins in a transaction - pub coins: Vec, - /// All encrypted notes in a transaction - pub enc_notes: Vec, -} - -impl UpdateBase for Update { - fn apply(mut self: Box, states: &mut StateRegistry) { - let state = states.lookup_mut::(*CONTRACT_ID).unwrap(); - - // Extend our list of nullifiers with the ones from the update - state.nullifiers.append(&mut self.nullifiers); - - //// Update merkle tree and witnesses - for coin in self.coins { - // Add the new coins to the Merkle tree - let node = MerkleNode::from(coin.0); - state.tree.append(&node); - - // Keep track of all Merkle roots that have existed - state.merkle_roots.push(state.tree.root(0).unwrap()); - } - } -} - -pub fn state_transition( - states: &StateRegistry, - func_call_index: usize, - parent_tx: &Transaction, -) -> 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"); - let func_call = &parent_tx.func_calls[func_call_index]; - let call_data = func_call.call_data.as_any(); - - assert_eq!((&*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::(); - - // This will be inside wasm so unwrap is fine. - let call_data = call_data.unwrap(); - - let state = states.lookup::(*CONTRACT_ID).expect("Return type is not of type State"); - - // Code goes here - for (i, input) in call_data.clear_inputs.iter().enumerate() { - let pk = &input.signature_public; - // TODO: this depends on the token ID - if !state.is_valid_cashier_public_key(pk) && !state.is_valid_faucet_public_key(pk) { - error!(target: TARGET, "Invalid pubkey for clear input: {:?}", pk); - return Err(Error::VerifyFailed(VerifyFailed::InvalidCashierOrFaucetKey(i))) - } - } - - // Nullifiers in the transaction - let mut nullifiers = Vec::with_capacity(call_data.inputs.len()); - - debug!(target: TARGET, "Iterate inputs"); - for (i, input) in call_data.inputs.iter().enumerate() { - let merkle = &input.revealed.merkle_root; - - // The Merkle root is used to know whether this is a coin that - // existed in a previous state. - if !state.is_valid_merkle(merkle) { - error!(target: TARGET, "Invalid Merkle root (input {})", i); - debug!(target: TARGET, "root: {:?}", merkle); - 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::FUNC_ID { - 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; - if state.nullifier_exists(nullifier) || - (1..nullifiers.len()).any(|i| nullifiers[i..].contains(&nullifiers[i - 1])) - { - error!(target: TARGET, "Duplicate nullifier found (input {})", i); - debug!(target: TARGET, "nullifier: {:?}", nullifier); - return Err(Error::VerifyFailed(VerifyFailed::NullifierExists(i))) - } - - nullifiers.push(input.revealed.nullifier); - } - - debug!(target: TARGET, "Verifying call data"); - match call_data.verify() { - Ok(()) => { - debug!(target: TARGET, "Verified successfully") - } - Err(e) => { - error!(target: TARGET, "Failed verifying zk proofs: {}", e); - return Err(Error::VerifyFailed(VerifyFailed::ProofVerifyFailed(e.to_string()))) - } - } - - // Newly created coins for this transaction - let mut coins = Vec::with_capacity(call_data.outputs.len()); - let mut enc_notes = Vec::with_capacity(call_data.outputs.len()); - - for output in &call_data.outputs { - // Gather all the coins - coins.push(output.revealed.coin); - enc_notes.push(output.enc_note.clone()); - } - - Ok(Box::new(Update { nullifiers, coins, enc_notes })) -} - -/// A DarkFi transaction -#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)] -pub struct CallData { - /// Clear inputs - pub clear_inputs: Vec, - /// Anonymous inputs - pub inputs: Vec, - /// Anonymous outputs - pub outputs: Vec, -} - -impl CallDataBase for CallData { - fn zk_public_values(&self) -> Vec<(String, Vec)> { - let mut public_values = Vec::new(); - for input in &self.inputs { - public_values.push(("money-transfer-burn".to_string(), input.revealed.make_outputs())); - } - for output in &self.outputs { - public_values.push(("money-transfer-mint".to_string(), output.revealed.make_outputs())); - } - public_values - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn signature_public_keys(&self) -> Vec { - let mut signature_public_keys = Vec::new(); - for input in self.clear_inputs.clone() { - signature_public_keys.push(input.signature_public); - } - signature_public_keys - } - - fn encode_bytes( - &self, - mut writer: &mut dyn std::io::Write, - ) -> std::result::Result { - self.encode(&mut writer) - } -} -impl CallData { - /// Verify the transaction - pub fn verify(&self) -> VerifyResult<()> { - // must have minimum 1 clear or anon input, and 1 output - if self.clear_inputs.len() + self.inputs.len() == 0 { - error!("tx::verify(): Missing inputs"); - return Err(VerifyFailed::LackingInputs) - } - if self.outputs.len() == 0 { - error!("tx::verify(): Missing outputs"); - return Err(VerifyFailed::LackingOutputs) - } - - // Accumulator for the value commitments - let mut valcom_total = DrkValueCommit::identity(); - - // Add values from the clear inputs - for input in &self.clear_inputs { - valcom_total += pedersen_commitment_u64(input.value, input.value_blind); - } - // Add values from the inputs - for input in &self.inputs { - valcom_total += &input.revealed.value_commit; - } - // Subtract values from the outputs - for output in &self.outputs { - valcom_total -= &output.revealed.value_commit; - } - - // If the accumulator is not back in its initial state, - // there's a value mismatch. - if valcom_total != DrkValueCommit::identity() { - error!("tx::verify(): Missing funds"); - return Err(VerifyFailed::MissingFunds) - } - - // Verify that the token commitments match - if !self.verify_token_commitments() { - error!("tx::verify(): Token ID mismatch"); - return Err(VerifyFailed::TokenMismatch) - } - - Ok(()) - } - - fn verify_token_commitments(&self) -> bool { - assert_ne!(self.outputs.len(), 0); - let token_commit_value = self.outputs[0].revealed.token_commit; - - let mut failed = - self.inputs.iter().any(|input| input.revealed.token_commit != token_commit_value); - - failed = failed || - self.outputs.iter().any(|output| output.revealed.token_commit != token_commit_value); - - failed = failed || - self.clear_inputs.iter().any(|input| { - pedersen_commitment_base(input.token_id.inner(), input.token_blind) != - token_commit_value - }); - !failed - } -} - -/// A transaction's clear input -#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)] -pub struct ClearInput { - /// Input's value (amount) - pub value: u64, - /// Input's token ID - pub token_id: TokenId, - /// Blinding factor for `value` - pub value_blind: DrkValueBlind, - /// Blinding factor for `token_id` - pub token_blind: DrkValueBlind, - /// Public key for the signature - pub signature_public: PublicKey, -} - -/// A transaction's anonymous input -#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)] -pub struct Input { - /// Public inputs for the zero-knowledge proof - pub revealed: BurnRevealedValues, -} - -/// A transaction's anonymous output -#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)] -pub struct Output { - /// Public inputs for the zero-knowledge proof - pub revealed: MintRevealedValues, - /// The encrypted note - pub enc_note: EncryptedNote2, -} - -#[derive(Debug, Clone, thiserror::Error)] -pub enum Error { - #[error(transparent)] - VerifyFailed(#[from] VerifyFailed), - - #[error("DarkFi error: {0}")] - DarkFiError(String), -} - -/// Transaction verification errors -#[derive(Debug, Clone, thiserror::Error)] -pub enum VerifyFailed { - #[error("Transaction has no inputs")] - LackingInputs, - - #[error("Transaction has no outputs")] - LackingOutputs, - - #[error("Invalid cashier/faucet public key for clear input {0}")] - InvalidCashierOrFaucetKey(usize), - - #[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), - - #[error("Token commitments in inputs or outputs to not match")] - TokenMismatch, - - #[error("Money in does not match money out (value commitments)")] - MissingFunds, - - #[error("Failed verifying zk proofs: {0}")] - ProofVerifyFailed(String), - - #[error("Internal error: {0}")] - InternalError(String), - - #[error("DarkFi error: {0}")] - DarkFiError(String), -} - -type Result = std::result::Result; - -impl From for VerifyFailed { - fn from(err: Error) -> Self { - Self::InternalError(err.to_string()) - } -} - -impl From for VerifyFailed { - fn from(err: DarkFiError) -> Self { - Self::DarkFiError(err.to_string()) - } -} - -impl From for Error { - fn from(err: DarkFiError) -> Self { - Self::DarkFiError(err.to_string()) - } -} -/// Result type used in transaction verifications -pub type VerifyResult = std::result::Result; diff --git a/example/dao2/src/contract/money/transfer/wallet.rs b/example/dao2/src/contract/money/transfer/wallet.rs deleted file mode 100644 index 64d35d677..000000000 --- a/example/dao2/src/contract/money/transfer/wallet.rs +++ /dev/null @@ -1,241 +0,0 @@ -/* 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_sdk::crypto::{MerkleNode, PublicKey, SecretKey, TokenId}; -use darkfi_serial::{SerialDecodable, SerialEncodable}; -use pasta_curves::group::ff::Field; -use rand::rngs::OsRng; - -use darkfi::{ - crypto::{ - burn_proof::create_burn_proof, - mint_proof::create_mint_proof, - types::{ - DrkCoinBlind, DrkSerial, DrkSpendHook, DrkUserData, DrkUserDataBlind, DrkValueBlind, - }, - Proof, - }, - Result, -}; - -use money_contract::{ClearInput, Input, MoneyFunction, MoneyTransferParams, Output}; - -use crate::{ - note, - util::{ZkContractInfo, ZkContractTable}, -}; - -#[derive(Clone, SerialEncodable, SerialDecodable)] -pub struct Note { - pub serial: DrkSerial, - pub value: u64, - pub token_id: TokenId, - pub spend_hook: DrkSpendHook, - pub user_data: DrkUserData, - pub coin_blind: DrkCoinBlind, - pub value_blind: DrkValueBlind, - pub token_blind: DrkValueBlind, -} - -pub struct Builder { - pub clear_inputs: Vec, - pub inputs: Vec, - pub outputs: Vec, -} - -pub struct BuilderClearInputInfo { - pub value: u64, - pub token_id: TokenId, - pub signature_secret: SecretKey, -} - -pub struct BuilderInputInfo { - pub leaf_position: incrementalmerkletree::Position, - pub merkle_path: Vec, - pub secret: SecretKey, - pub note: Note, - pub user_data_blind: DrkUserDataBlind, - pub value_blind: DrkValueBlind, - pub signature_secret: SecretKey, -} - -pub struct BuilderOutputInfo { - pub value: u64, - pub token_id: TokenId, - pub public: PublicKey, - pub serial: DrkSerial, - pub coin_blind: DrkCoinBlind, - pub spend_hook: DrkSpendHook, - pub user_data: DrkUserData, -} - -impl Builder { - fn compute_remainder_blind( - clear_inputs: &[ClearInput], - input_blinds: &[DrkValueBlind], - output_blinds: &[DrkValueBlind], - ) -> DrkValueBlind { - let mut total = DrkValueBlind::zero(); - - for input in clear_inputs { - total += input.value_blind; - } - - for input_blind in input_blinds { - total += input_blind; - } - - for output_blind in output_blinds { - total -= output_blind; - } - - total - } - - pub fn build(self, zk_bins: &ZkContractTable) -> Result<(MoneyTransferParams, Vec)> { - assert!(self.clear_inputs.len() + self.inputs.len() > 0); - - let mut clear_inputs = vec![]; - let token_blind = DrkValueBlind::random(&mut OsRng); - for input in &self.clear_inputs { - let signature_public = PublicKey::from_secret(input.signature_secret); - let value_blind = DrkValueBlind::random(&mut OsRng); - - let clear_input = ClearInput { - value: input.value, - token_id: input.token_id.inner(), - value_blind, - token_blind, - signature_public, - }; - clear_inputs.push(clear_input); - } - - let mut proofs = vec![]; - let mut inputs = vec![]; - let mut input_blinds = vec![]; - - for input in self.inputs { - let value_blind = input.value_blind; - input_blinds.push(value_blind); - - let zk_info = zk_bins.lookup(&"money-transfer-burn".to_string()).unwrap(); - let zk_info = if let ZkContractInfo::Native(info) = zk_info { - info - } else { - panic!("Not native info") - }; - let burn_pk = &zk_info.proving_key; - - // Note from the previous output - let note = input.note.clone(); - - let (burn_proof, revealed) = create_burn_proof( - burn_pk, - note.value, - note.token_id, - value_blind, - token_blind, - note.serial, - note.spend_hook, - note.user_data, - input.user_data_blind, - note.coin_blind, - input.secret, - input.leaf_position, - input.merkle_path.clone(), - input.signature_secret, - )?; - proofs.push(burn_proof); - - let input = Input { - value_commit: revealed.value_commit, - token_commit: revealed.token_commit, - nullifier: revealed.nullifier.inner(), - merkle_root: revealed.merkle_root.inner(), - spend_hook: revealed.spend_hook, - user_data_enc: revealed.user_data_enc, - signature_public: revealed.signature_public, - }; - inputs.push(input); - } - - let mut outputs = vec![]; - let mut output_blinds = vec![]; - // This value_blind calc assumes there will always be at least a single output - assert!(self.outputs.len() > 0); - - for (i, output) in self.outputs.iter().enumerate() { - let value_blind = if i == self.outputs.len() - 1 { - Self::compute_remainder_blind(&clear_inputs, &input_blinds, &output_blinds) - } else { - DrkValueBlind::random(&mut OsRng) - }; - output_blinds.push(value_blind); - - 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 { - info - } else { - panic!("Not native info") - }; - let mint_pk = &zk_info.proving_key; - - let (mint_proof, revealed) = create_mint_proof( - mint_pk, - output.value, - output.token_id, - value_blind, - token_blind, - serial, - output.spend_hook, - output.user_data, - coin_blind, - output.public, - )?; - proofs.push(mint_proof); - - let note = Note { - serial, - value: output.value, - token_id: output.token_id, - spend_hook: output.spend_hook, - user_data: output.user_data, - coin_blind, - value_blind, - token_blind, - }; - - let encrypted_note = note::encrypt(¬e, &output.public)?; - - let output = Output { - value_commit: revealed.value_commit, - token_commit: revealed.token_commit, - coin: revealed.coin.0, - ciphertext: encrypted_note.ciphertext, - ephem_public: encrypted_note.ephem_public, - }; - outputs.push(output); - } - - Ok((MoneyTransferParams { clear_inputs, inputs, outputs }, proofs)) - } -} diff --git a/example/dao2/src/error.rs b/example/dao2/src/error.rs deleted file mode 100644 index 7b7538287..000000000 --- a/example/dao2/src/error.rs +++ /dev/null @@ -1,74 +0,0 @@ -/* 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 serde_json::Value; - -// use darkfi::rpc::jsonrpc::{ErrorCode::ServerError, JsonError, JsonResult}; - -#[derive(Debug, thiserror::Error)] -pub enum DaoError { - // #[error("No Proposals found")] - // NoProposals, - // #[error("No DAO params found")] - // DaoNotConfigured, - // #[error("State transition failed: '{0}'")] - // StateTransitionFailed(String), - // #[error("Wallet does not exist")] - // NoWalletFound, - // #[error("State not found")] - // StateNotFound, - #[error("InternalError")] - Darkfi(#[from] darkfi::error::Error), - #[error("Verify proof failed: '{0}', '{0}'")] - VerifyProofFailed(usize, String), -} - -pub type DaoResult = std::result::Result; - -// pub enum RpcError { -// Vote = -32101, -// Propose = -32102, -// Exec = -32103, -// Airdrop = -32104, -// Mint = -32105, -// Keygen = -32106, -// Create = -32107, -// Parse = -32108, -// Balance = -32109, -// } - -// fn to_tuple(e: RpcError) -> (i64, String) { -// let msg = match e { -// RpcError::Vote => "Failed to cast a Vote", -// RpcError::Propose => "Failed to generate a Proposal", -// RpcError::Airdrop => "Failed to transfer an airdrop", -// RpcError::Keygen => "Failed to generate keypair", -// RpcError::Create => "Failed to create DAO", -// RpcError::Exec => "Failed to execute Proposal", -// RpcError::Mint => "Failed to mint DAO treasury", -// RpcError::Parse => "Generic parsing error", -// RpcError::Balance => "Failed to get balance", -// }; - -// (e as i64, msg.to_string()) -// } - -// pub fn server_error(e: RpcError, id: Value) -> JsonResult { -// let (code, msg) = to_tuple(e); -// JsonError::new(ServerError(code), Some(msg), id).into() -// } diff --git a/example/dao2/src/main.rs b/example/dao2/src/main.rs deleted file mode 100644 index 54451a246..000000000 --- a/example/dao2/src/main.rs +++ /dev/null @@ -1,540 +0,0 @@ -/* 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(()) -} diff --git a/example/dao2/src/note.rs b/example/dao2/src/note.rs deleted file mode 100644 index 476a34fa7..000000000 --- a/example/dao2/src/note.rs +++ /dev/null @@ -1,122 +0,0 @@ -/* 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 chacha20poly1305::{AeadInPlace, ChaCha20Poly1305, KeyInit}; -use darkfi_sdk::crypto::{PublicKey, SecretKey}; -use rand::rngs::OsRng; - -use darkfi::{ - crypto::diffie_hellman::{kdf_sapling, sapling_ka_agree}, - Error, Result, -}; -use darkfi_serial::{Decodable, Encodable, SerialDecodable, SerialEncodable}; - -pub const AEAD_TAG_SIZE: usize = 16; - -pub fn encrypt(note: &T, public: &PublicKey) -> Result { - let ephem_secret = SecretKey::random(&mut OsRng); - let ephem_public = PublicKey::from_secret(ephem_secret); - let shared_secret = sapling_ka_agree(&ephem_secret, public); - let key = kdf_sapling(&shared_secret, &ephem_public); - - let mut input = Vec::new(); - note.encode(&mut input)?; - let input_len = input.len(); - - let mut ciphertext = vec![0_u8; input_len + AEAD_TAG_SIZE]; - ciphertext[..input_len].copy_from_slice(&input); - - ChaCha20Poly1305::new(key.as_ref().into()) - .encrypt_in_place([0u8; 12][..].into(), &[], &mut ciphertext) - .unwrap(); - - Ok(EncryptedNote2 { ciphertext, ephem_public }) -} - -#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)] -pub struct EncryptedNote2 { - pub ciphertext: Vec, - pub ephem_public: PublicKey, -} - -impl EncryptedNote2 { - pub fn decrypt(&self, secret: &SecretKey) -> Result { - let shared_secret = sapling_ka_agree(secret, &self.ephem_public); - let key = kdf_sapling(&shared_secret, &self.ephem_public); - - let ciphertext_len = self.ciphertext.len(); - let mut plaintext = vec![0_u8; ciphertext_len]; - plaintext.copy_from_slice(&self.ciphertext); - - match ChaCha20Poly1305::new(key.as_ref().into()).decrypt_in_place( - [0u8; 12][..].into(), - &[], - &mut plaintext, - ) { - Ok(()) => { - Ok(T::decode(&plaintext[..ciphertext_len - AEAD_TAG_SIZE]).map_err(Error::from)?) - } - Err(e) => Err(Error::NoteDecryptionFailed(e.to_string())), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use darkfi::crypto::{ - keypair::Keypair, - types::{DrkCoinBlind, DrkSerial, DrkValueBlind}, - }; - use darkfi_sdk::{ - crypto::TokenId, - pasta::{group::ff::Field, pallas}, - }; - - #[test] - fn test_note_encdec() { - #[derive(SerialEncodable, SerialDecodable)] - struct MyNote { - serial: DrkSerial, - value: u64, - token_id: TokenId, - coin_blind: DrkCoinBlind, - value_blind: DrkValueBlind, - token_blind: DrkValueBlind, - memo: Vec, - } - let note = MyNote { - serial: DrkSerial::random(&mut OsRng), - value: 110, - token_id: TokenId::from(pallas::Base::random(&mut OsRng)), - coin_blind: DrkCoinBlind::random(&mut OsRng), - value_blind: DrkValueBlind::random(&mut OsRng), - token_blind: DrkValueBlind::random(&mut OsRng), - memo: vec![32, 223, 231, 3, 1, 1], - }; - - let keypair = Keypair::random(&mut OsRng); - - let encrypted_note = encrypt(¬e, &keypair.public).unwrap(); - let note2: MyNote = encrypted_note.decrypt(&keypair.secret).unwrap(); - assert_eq!(note.value, note2.value); - assert_eq!(note.token_id, note2.token_id); - assert_eq!(note.token_blind, note2.token_blind); - assert_eq!(note.memo, note2.memo); - } -} diff --git a/example/dao2/src/schema.rs b/example/dao2/src/schema.rs deleted file mode 100644 index e0e7ec659..000000000 --- a/example/dao2/src/schema.rs +++ /dev/null @@ -1,1292 +0,0 @@ -/* 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 std::{ - any::{Any, TypeId}, - time::Instant, -}; - -use darkfi_sdk::crypto::{ - constants::MERKLE_DEPTH, pedersen::pedersen_commitment_u64, poseidon_hash, Keypair, MerkleNode, - PublicKey, SecretKey, -}; -use incrementalmerkletree::{bridgetree::BridgeTree, Tree}; -use log::debug; -use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas}; -use rand::rngs::OsRng; - -use darkfi::{ - crypto::{ - coin::Coin, - proof::{ProvingKey, VerifyingKey}, - types::{DrkSpendHook, DrkUserData, DrkValue}, - }, - zk::circuit::{BurnContract, MintContract}, - zkas::decoder::ZkBinary, -}; - -use crate::{ - contract::{dao, example, money}, - note::EncryptedNote2, - util::{sign, StateRegistry, Transaction, ZkContractTable}, -}; - -type MerkleTree = BridgeTree; - -pub struct OwnCoin { - pub coin: Coin, - pub note: money::transfer::wallet::Note, - pub leaf_position: incrementalmerkletree::Position, -} - -pub struct WalletCache { - // Normally this would be a HashMap, but SecretKey is not Hash-able - // TODO: This can be HashableBase - cache: Vec<(SecretKey, Vec)>, - /// The entire Merkle tree state - tree: MerkleTree, -} - -impl WalletCache { - pub fn new() -> Self { - Self { cache: Vec::new(), tree: MerkleTree::new(100) } - } - - /// Must be called at the start to begin tracking received coins for this secret. - pub fn track(&mut self, secret: SecretKey) { - self.cache.push((secret, Vec::new())); - } - - /// Get all coins received by this secret key - /// track() must be called on this secret before calling this or the function will panic. - pub fn get_received(&mut self, secret: &SecretKey) -> Vec { - for (other_secret, own_coins) in self.cache.iter_mut() { - if *secret == *other_secret { - // clear own_coins vec, and return current contents - return std::mem::replace(own_coins, Vec::new()) - } - } - panic!("you forget to track() this secret!"); - } - - pub fn try_decrypt_note(&mut self, coin: Coin, ciphertext: &EncryptedNote2) { - // Add the new coins to the Merkle tree - let node = MerkleNode::from(coin.0); - self.tree.append(&node); - - // Loop through all our secret keys... - for (secret, own_coins) in self.cache.iter_mut() { - // .. attempt to decrypt the note ... - if let Ok(note) = ciphertext.decrypt(secret) { - let leaf_position = self.tree.witness().expect("coin should be in tree"); - own_coins.push(OwnCoin { coin, note, leaf_position }); - } - } - } -} - -// TODO: Anonymity leaks in this proof of concept: -// -// * Vote updates are linked to the proposal_bulla -// * Nullifier of vote will link vote with the coin when it's spent - -// TODO: strategize and cleanup Result/Error usage -// TODO: fix up code doc - -type Result = std::result::Result>; - -/////////////////////////////////////////////////// -///// Example contract -/////////////////////////////////////////////////// -pub async fn example() -> Result<()> { - debug!(target: "demo", "Stage 0. Example contract"); - // Lookup table for smart contract states - let mut states = StateRegistry::new(); - - // Initialize ZK binary table - let mut zk_bins = ZkContractTable::new(); - - let zk_example_foo_bincode = include_bytes!("../proof/foo.zk.bin"); - let zk_example_foo_bin = ZkBinary::decode(zk_example_foo_bincode)?; - zk_bins.add_contract("example-foo".to_string(), zk_example_foo_bin, 13); - - let example_state = example::state::State::new(); - states.register(*example::CONTRACT_ID, example_state); - - //// Wallet - - let foo_w = example::foo::wallet::Foo { a: 5, b: 10 }; - let signature_secret = SecretKey::random(&mut OsRng); - - let builder = example::foo::wallet::Builder { foo: foo_w, signature_secret }; - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let mut signatures = vec![]; - for func_call in &func_calls { - let sign = sign([signature_secret].to_vec(), func_call); - signatures.push(sign); - } - - let tx = Transaction { func_calls, signatures }; - - //// 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 == *example::foo::FUNC_ID { - debug!("example::foo::state_transition()"); - - let update = example::foo::validate::state_transition(&states, idx, &tx) - .expect("example::foo::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - tx.zk_verify(&zk_bins).unwrap(); - tx.verify_sigs(); - - Ok(()) -} - -pub async fn schema() -> Result<()> { - /* - // Example smart contract - //// TODO: this will be moved to a different file - example().await?; - - // Money parameters - let xdrk_supply = 1_000_000; - let xdrk_token_id = pallas::Base::random(&mut OsRng); - - // Governance token parameters - let gdrk_supply = 1_000_000; - let gdrk_token_id = 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(); - - /////////////////////////////////////////////////// - - // Lookup table for smart contract states - let mut states = StateRegistry::new(); - - let money_state = money::state::State::new(cashier_signature_public, faucet_signature_public); - states.register(*money::CONTRACT_ID, money_state); - - let dao_state = dao::State::new(); - states.register(*dao::CONTRACT_ID, dao_state); - - ///////////////////////////////////////////////////// - - ///////////////////////////////////////////////////// - ////// Create the DAO bulla - ///////////////////////////////////////////////////// - debug!(target: "demo", "Stage 1. Creating DAO bulla"); - - //// Wallet - - //// Setup the DAO - let dao_keypair = Keypair::random(&mut OsRng); - let dao_bulla_blind = pallas::Base::random(&mut OsRng); - - 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: signature_secret, - }; - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let mut signatures = vec![]; - for func_call in &func_calls { - let sign = sign([signature_secret].to_vec(), func_call); - signatures.push(sign); - } - - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - // So then the verifier will lookup the corresponding state_transition and apply - // functions based off the func_id - if func_call.func_id == *dao::mint::FUNC_ID { - debug!("dao::mint::state_transition()"); - - let update = dao::mint::validate::state_transition(&states, idx, &tx) - .expect("dao::mint::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - tx.zk_verify(&zk_bins).unwrap(); - tx.verify_sigs(); - - // Wallet stuff - - // In your wallet, wait until you see the tx confirmed before doing anything below - // So for example keep track of tx hash - //assert_eq!(tx.hash(), tx_hash); - - // We need to witness() the value in our local merkle tree - // Must be called as soon as this DAO bulla is added to the state - let dao_leaf_position = { - let state = states.lookup_mut::(*dao::CONTRACT_ID).unwrap(); - state.dao_tree.witness().unwrap() - }; - - // It might just be easier to hash it ourselves from keypair and blind... - let dao_bulla = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!((*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::().unwrap(); - call_data.dao_bulla.clone() - }; - 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; - // 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 func_call = builder.build(&zk_bins)?; - let func_calls = vec![func_call]; - - let mut signatures = vec![]; - for func_call in &func_calls { - let sign = sign([cashier_signature_secret].to_vec(), func_call); - signatures.push(sign); - } - - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - // So then the verifier will lookup the corresponding state_transition and apply - // functions based off the func_id - if func_call.func_id == *money::transfer::FUNC_ID { - debug!("money::transfer::state_transition()"); - - let update = money::transfer::validate::state_transition(&states, idx, &tx) - .expect("money::transfer::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - tx.zk_verify(&zk_bins).unwrap(); - tx.verify_sigs(); - - //// Wallet - // DAO reads the money received from the encrypted note - { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!((*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::().unwrap(); - - for output in &call_data.outputs { - let coin = &output.revealed.coin; - let enc_note = &output.enc_note; - - cache.try_decrypt_note(coin.clone(), 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.0.to_affine().coordinates().unwrap(); - let coin = poseidon_hash::<8>([ - *coords.x(), - *coords.y(), - 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, 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); - - /////////////////////////////////////////////////// - //// Mint the governance token - //// Send it to three hodlers - /////////////////////////////////////////////////// - debug!(target: "demo", "Stage 3. Minting governance token"); - - //// Wallet - - // Hodler 1 - let gov_keypair_1 = Keypair::random(&mut OsRng); - // Hodler 2 - let gov_keypair_2 = Keypair::random(&mut OsRng); - // Hodler 3: the tiebreaker - let gov_keypair_3 = Keypair::random(&mut OsRng); - - cache.track(gov_keypair_1.secret); - cache.track(gov_keypair_2.secret); - cache.track(gov_keypair_3.secret); - - let gov_keypairs = vec![gov_keypair_1, gov_keypair_2, gov_keypair_3]; - - // Spend hook and user data disabled - let spend_hook = DrkSpendHook::from(0); - let user_data = DrkUserData::from(0); - - let output1 = money::transfer::wallet::BuilderOutputInfo { - 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, - }; - - let output2 = money::transfer::wallet::BuilderOutputInfo { - 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, - }; - - let output3 = money::transfer::wallet::BuilderOutputInfo { - 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, - }; - - assert!(2 * 400000 + 200000 == gdrk_supply); - - let builder = money::transfer::wallet::Builder { - clear_inputs: vec![money::transfer::wallet::BuilderClearInputInfo { - value: gdrk_supply, - token_id: gdrk_token_id, - signature_secret: cashier_signature_secret, - }], - inputs: vec![], - outputs: vec![output1, output2, output3], - }; - - let func_call = builder.build(&zk_bins)?; - let func_calls = vec![func_call]; - - let mut signatures = vec![]; - for func_call in &func_calls { - let sign = sign([cashier_signature_secret].to_vec(), func_call); - signatures.push(sign); - } - - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - // So then the verifier will lookup the corresponding state_transition and apply - // functions based off the func_id - if func_call.func_id == *money::transfer::FUNC_ID { - debug!("money::transfer::state_transition()"); - - let update = money::transfer::validate::state_transition(&states, idx, &tx) - .expect("money::transfer::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - tx.zk_verify(&zk_bins).unwrap(); - tx.verify_sigs(); - - //// Wallet - { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!((*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::().unwrap(); - - for output in &call_data.outputs { - let coin = &output.revealed.coin; - let enc_note = &output.enc_note; - - cache.try_decrypt_note(coin.clone(), enc_note); - } - } - - let mut gov_recv = vec![None, None, None]; - // Check that each person received one coin - for (i, key) in gov_keypairs.iter().enumerate() { - let gov_recv_coin = { - let mut recv_coins = cache.get_received(&key.secret); - assert_eq!(recv_coins.len(), 1); - let recv_coin = recv_coins.pop().unwrap(); - let note = &recv_coin.note; - - assert_eq!(note.token_id, gdrk_token_id); - // Normal payment - assert_eq!(note.spend_hook, pallas::Base::from(0)); - assert_eq!(note.user_data, pallas::Base::from(0)); - - let coords = key.public.0.to_affine().coordinates().unwrap(); - 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, - ]); - assert_eq!(coin, recv_coin.coin.0); - - debug!("Holder{} received a coin worth {} gDRK", i, note.value); - - recv_coin - }; - gov_recv[i] = Some(gov_recv_coin); - } - // unwrap them for this demo - let gov_recv: Vec<_> = gov_recv.into_iter().map(|r| r.unwrap()).collect(); - - /////////////////////////////////////////////////// - // DAO rules: - // 1. gov token IDs must match on all inputs - // 2. proposals must be submitted by minimum amount - // 3. all votes >= quorum - // 4. outcome > approval_ratio - // 5. structure of outputs - // output 0: value and address - // output 1: change address - /////////////////////////////////////////////////// - - /////////////////////////////////////////////////// - // Propose the vote - // In order to make a valid vote, first the proposer must - // meet a criteria for a minimum number of gov tokens - /////////////////////////////////////////////////// - debug!(target: "demo", "Stage 4. Propose the vote"); - - //// Wallet - - // TODO: look into proposal expiry once time for voting has finished - - let user_keypair = Keypair::random(&mut OsRng); - - let (money_leaf_position, money_merkle_path) = { - let tree = &cache.tree; - let leaf_position = gov_recv[0].leaf_position; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - // TODO: is it possible for an invalid transfer() to be constructed on exec()? - // need to look into this - let signature_secret = SecretKey::random(&mut OsRng); - let input = dao::propose::wallet::BuilderInput { - secret: gov_keypair_1.secret, - note: gov_recv[0].note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, - }; - - let (dao_merkle_path, dao_merkle_root) = { - let state = states.lookup::(*dao::CONTRACT_ID).unwrap(); - let tree = &state.dao_tree; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(dao_leaf_position, &root).unwrap(); - (merkle_path, root) - }; - - let dao_params = dao::mint::wallet::DaoParams { - proposer_limit: dao_proposer_limit, - quorum: dao_quorum, - approval_ratio_base: dao_approval_ratio_base, - approval_ratio_quot: dao_approval_ratio_quot, - gov_token_id: gdrk_token_id, - public_key: dao_keypair.public, - bulla_blind: dao_bulla_blind, - }; - - let proposal = dao::propose::wallet::Proposal { - dest: user_keypair.public, - amount: 1000, - serial: pallas::Base::random(&mut OsRng), - token_id: xdrk_token_id, - blind: pallas::Base::random(&mut OsRng), - }; - - let builder = dao::propose::wallet::Builder { - inputs: vec![input], - proposal, - dao: dao_params.clone(), - dao_leaf_position, - dao_merkle_path, - dao_merkle_root, - }; - - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let mut signatures = vec![]; - for func_call in &func_calls { - let sign = sign([signature_secret].to_vec(), func_call); - signatures.push(sign); - } - - let tx = Transaction { func_calls, signatures }; - - //// 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::propose::FUNC_ID { - debug!(target: "demo", "dao::propose::state_transition()"); - - let update = dao::propose::validate::state_transition(&states, idx, &tx) - .expect("dao::propose::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - tx.zk_verify(&zk_bins).unwrap(); - tx.verify_sigs(); - - //// Wallet - - // Read received proposal - let (proposal, proposal_bulla) = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!((*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao::propose::wallet::Note = - header.enc_note.decrypt(&dao_keypair.secret).unwrap(); - - // TODO: check it belongs to DAO bulla - - // Return the proposal info - (note.proposal, call_data.header.proposal_bulla) - }; - debug!(target: "demo", "Proposal now active!"); - debug!(target: "demo", " destination: {:?}", proposal.dest); - debug!(target: "demo", " amount: {}", proposal.amount); - debug!(target: "demo", " token_id: {:?}", proposal.token_id); - debug!(target: "demo", " dao_bulla: {:?}", dao_bulla.0); - debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla); - - /////////////////////////////////////////////////// - // Proposal is accepted! - // Start the voting - /////////////////////////////////////////////////// - - // Copying these schizo comments from python code: - // Lets the voting begin - // Voters have access to the proposal and dao data - // vote_state = VoteState() - // We don't need to copy nullifier set because it is checked from gov_state - // in vote_state_transition() anyway - // - // TODO: what happens if voters don't unblind their vote - // Answer: - // 1. there is a time limit - // 2. both the MPC or users can unblind - // - // TODO: bug if I vote then send money, then we can double vote - // TODO: all timestamps missing - // - timelock (future voting starts in 2 days) - // Fix: use nullifiers from money gov state only from - // beginning of gov period - // Cannot use nullifiers from before voting period - - debug!(target: "demo", "Stage 5. Start voting"); - - // We were previously saving updates here for testing - // let mut updates = vec![]; - - // User 1: YES - - let (money_leaf_position, money_merkle_path) = { - let tree = &cache.tree; - let leaf_position = gov_recv[0].leaf_position; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let signature_secret = SecretKey::random(&mut OsRng); - let input = dao::vote::wallet::BuilderInput { - secret: gov_keypair_1.secret, - note: gov_recv[0].note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, - }; - - let vote_option: bool = true; - // assert!(vote_option || !vote_option); // wtf - - // We create a new keypair to encrypt the vote. - // For the demo MVP, you can just use the dao_keypair secret - let vote_keypair_1 = Keypair::random(&mut OsRng); - - let builder = dao::vote::wallet::Builder { - inputs: vec![input], - vote: dao::vote::wallet::Vote { - vote_option, - vote_option_blind: pallas::Scalar::random(&mut OsRng), - }, - vote_keypair: vote_keypair_1, - proposal: proposal.clone(), - dao: dao_params.clone(), - }; - debug!(target: "demo", "build()..."); - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let mut signatures = vec![]; - for func_call in &func_calls { - let sign = sign([signature_secret].to_vec(), func_call); - signatures.push(sign); - } - - let tx = Transaction { func_calls, signatures }; - - //// 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::vote::FUNC_ID { - debug!(target: "demo", "dao::vote::state_transition()"); - - let update = dao::vote::validate::state_transition(&states, idx, &tx) - .expect("dao::vote::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - tx.zk_verify(&zk_bins).unwrap(); - tx.verify_sigs(); - - //// Wallet - - // Secret vote info. Needs to be revealed at some point. - // TODO: look into verifiable encryption for notes - // TODO: look into timelock puzzle as a possibility - let vote_note_1 = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!((*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao::vote::wallet::Note = - header.enc_note.decrypt(&vote_keypair_1.secret).unwrap(); - note - }; - debug!(target: "demo", "User 1 voted!"); - debug!(target: "demo", " vote_option: {}", vote_note_1.vote.vote_option); - debug!(target: "demo", " value: {}", vote_note_1.vote_value); - - // User 2: NO - - let (money_leaf_position, money_merkle_path) = { - let tree = &cache.tree; - let leaf_position = gov_recv[1].leaf_position; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let signature_secret = SecretKey::random(&mut OsRng); - let input = dao::vote::wallet::BuilderInput { - secret: gov_keypair_2.secret, - note: gov_recv[1].note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, - }; - - let vote_option: bool = false; - // assert!(vote_option || !vote_option); // wtf - - // We create a new keypair to encrypt the vote. - let vote_keypair_2 = Keypair::random(&mut OsRng); - - let builder = dao::vote::wallet::Builder { - inputs: vec![input], - vote: dao::vote::wallet::Vote { - vote_option, - vote_option_blind: pallas::Scalar::random(&mut OsRng), - }, - vote_keypair: vote_keypair_2, - proposal: proposal.clone(), - dao: dao_params.clone(), - }; - debug!(target: "demo", "build()..."); - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let mut signatures = vec![]; - for func_call in &func_calls { - let sign = sign([signature_secret].to_vec(), func_call); - signatures.push(sign); - } - - let tx = Transaction { func_calls, signatures }; - - //// 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::vote::FUNC_ID { - debug!(target: "demo", "dao::vote::state_transition()"); - - let update = dao::vote::validate::state_transition(&states, idx, &tx) - .expect("dao::vote::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - tx.zk_verify(&zk_bins).unwrap(); - tx.verify_sigs(); - - //// Wallet - - // Secret vote info. Needs to be revealed at some point. - // TODO: look into verifiable encryption for notes - // TODO: look into timelock puzzle as a possibility - let vote_note_2 = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!((*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao::vote::wallet::Note = - header.enc_note.decrypt(&vote_keypair_2.secret).unwrap(); - note - }; - debug!(target: "demo", "User 2 voted!"); - debug!(target: "demo", " vote_option: {}", vote_note_2.vote.vote_option); - debug!(target: "demo", " value: {}", vote_note_2.vote_value); - - // User 3: YES - - let (money_leaf_position, money_merkle_path) = { - let tree = &cache.tree; - let leaf_position = gov_recv[2].leaf_position; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let signature_secret = SecretKey::random(&mut OsRng); - let input = dao::vote::wallet::BuilderInput { - secret: gov_keypair_3.secret, - note: gov_recv[2].note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, - }; - - let vote_option: bool = true; - // assert!(vote_option || !vote_option); // wtf - - // We create a new keypair to encrypt the vote. - let vote_keypair_3 = Keypair::random(&mut OsRng); - - let builder = dao::vote::wallet::Builder { - inputs: vec![input], - vote: dao::vote::wallet::Vote { - vote_option, - vote_option_blind: pallas::Scalar::random(&mut OsRng), - }, - vote_keypair: vote_keypair_3, - proposal: proposal.clone(), - dao: dao_params.clone(), - }; - debug!(target: "demo", "build()..."); - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let mut signatures = vec![]; - for func_call in &func_calls { - let sign = sign([signature_secret].to_vec(), func_call); - signatures.push(sign); - } - - let tx = Transaction { func_calls, signatures }; - - //// 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::vote::FUNC_ID { - debug!(target: "demo", "dao::vote::state_transition()"); - - let update = dao::vote::validate::state_transition(&states, idx, &tx) - .expect("dao::vote::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - - tx.zk_verify(&zk_bins).unwrap(); - tx.verify_sigs(); - - //// Wallet - - // Secret vote info. Needs to be revealed at some point. - // TODO: look into verifiable encryption for notes - // TODO: look into timelock puzzle as a possibility - let vote_note_3 = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!((*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao::vote::wallet::Note = - header.enc_note.decrypt(&vote_keypair_3.secret).unwrap(); - note - }; - debug!(target: "demo", "User 3 voted!"); - debug!(target: "demo", " vote_option: {}", vote_note_3.vote.vote_option); - debug!(target: "demo", " value: {}", vote_note_3.vote_value); - - // Every votes produces a semi-homomorphic encryption of their vote. - // Which is either yes or no - // We copy the state tree for the governance token so coins can be used - // to vote on other proposals at the same time. - // With their vote, they produce a ZK proof + nullifier - // The votes are unblinded by MPC to a selected party at the end of the - // voting period. - // (that's if we want votes to be hidden during voting) - - let mut yes_votes_value = 0; - let mut yes_votes_blind = pallas::Scalar::from(0); - let mut yes_votes_commit = pallas::Point::identity(); - - let mut all_votes_value = 0; - let mut all_votes_blind = pallas::Scalar::from(0); - let mut all_votes_commit = pallas::Point::identity(); - - // We were previously saving votes to a Vec for testing. - // However since Update is now UpdateBase it gets moved into update.apply(). - // So we need to think of another way to run these tests. - //assert!(updates.len() == 3); - - for (i, note /* update*/) in [vote_note_1, vote_note_2, vote_note_3] - .iter() /*.zip(updates)*/ - .enumerate() - { - let vote_commit = pedersen_commitment_u64(note.vote_value, note.vote_value_blind); - //assert!(update.value_commit == all_vote_value_commit); - all_votes_commit += vote_commit; - all_votes_blind += note.vote_value_blind; - - let yes_vote_commit = pedersen_commitment_u64( - note.vote.vote_option as u64 * note.vote_value, - note.vote.vote_option_blind, - ); - //assert!(update.yes_vote_commit == yes_vote_commit); - - yes_votes_commit += yes_vote_commit; - yes_votes_blind += note.vote.vote_option_blind; - - let vote_option = note.vote.vote_option; - - if vote_option { - yes_votes_value += note.vote_value; - } - all_votes_value += note.vote_value; - let vote_result: String = if vote_option { "yes".to_string() } else { "no".to_string() }; - - debug!("Voter {} voted {}", i, vote_result); - } - - debug!("Outcome = {} / {}", yes_votes_value, all_votes_value); - - assert!(all_votes_commit == pedersen_commitment_u64(all_votes_value, all_votes_blind)); - assert!(yes_votes_commit == pedersen_commitment_u64(yes_votes_value, yes_votes_blind)); - - /////////////////////////////////////////////////// - // 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 input_value = treasury_note.value; - let input_value_blind = pallas::Scalar::random(&mut OsRng); - let tx_signature_secret = SecretKey::random(&mut OsRng); - let exec_signature_secret = SecretKey::random(&mut OsRng); - - let (treasury_leaf_position, treasury_merkle_path) = { - let tree = &cache.tree; - let leaf_position = dao_recv_coin.leaf_position; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let input = money::transfer::wallet::BuilderInputInfo { - leaf_position: treasury_leaf_position, - merkle_path: treasury_merkle_path, - secret: dao_keypair.secret, - note: treasury_note, - user_data_blind, - value_blind: input_value_blind, - signature_secret: tx_signature_secret, - }; - - let builder = money::transfer::wallet::Builder { - clear_inputs: vec![], - inputs: vec![input], - outputs: vec![ - // Sending money - money::transfer::wallet::BuilderOutputInfo { - value: 1000, - token_id: xdrk_token_id, - public: user_keypair.public, - serial: proposal.serial, - coin_blind: proposal.blind, - spend_hook: pallas::Base::from(0), - user_data: pallas::Base::from(0), - }, - // Change back to DAO - money::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: *dao::exec::FUNC_ID, - user_data: dao_bulla.0, - }, - ], - }; - - let transfer_func_call = builder.build(&zk_bins)?; - - let builder = dao::exec::wallet::Builder { - proposal, - dao: dao_params.clone(), - yes_votes_value, - all_votes_value, - yes_votes_blind, - all_votes_blind, - user_serial, - user_coin_blind, - dao_serial, - dao_coin_blind, - input_value, - input_value_blind, - hook_dao_exec: *dao::exec::FUNC_ID, - signature_secret: exec_signature_secret, - }; - let exec_func_call = builder.build(&zk_bins); - let func_calls = vec![transfer_func_call, exec_func_call]; - - let mut signatures = vec![]; - for func_call in &func_calls { - let sign = sign([signature_secret].to_vec(), func_call); - signatures.push(sign); - } - - let tx = Transaction { func_calls, signatures }; - - { - // 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, *dao::exec::FUNC_ID); - let user_data_enc = poseidon_hash::<2>([dao_bulla.0, user_data_blind]); - assert_eq!(input.revealed.user_data_enc, user_data_enc); - - let dao_pubkey_coords = dao_params.public_key.0.to_affine().coordinates().unwrap(); - let coin_1 = Coin(poseidon_hash::<8>([ - *dao_pubkey_coords.x(), - *dao_pubkey_coords.y(), - pallas::Base::from(xdrk_supply - 1000), - xdrk_token_id, - dao_serial, - *dao::exec::FUNC_ID, - dao_bulla.0, - dao_coin_blind, - ])); - debug!("coin_1: {:?}", coin_1); - - let money_transfer_call_data = tx.func_calls[0].call_data.as_any(); - let money_transfer_call_data = - money_transfer_call_data.downcast_ref::(); - let money_transfer_call_data = money_transfer_call_data.unwrap(); - assert_eq!( - money_transfer_call_data.type_id(), - TypeId::of::() - ); - assert_eq!(money_transfer_call_data.outputs.len(), 2); - let money_transfer_coin_1 = &money_transfer_call_data.outputs[1].revealed.coin; - debug!("money::transfer() coin 1 = {:?}", money_transfer_coin_1); - - let dao_exec_call_data = tx.func_calls[1].call_data.as_any(); - let dao_exec_call_data = dao_exec_call_data.downcast_ref::(); - let dao_exec_call_data = dao_exec_call_data.unwrap(); - assert_eq!(dao_exec_call_data.type_id(), TypeId::of::()); - let dao_exec_coin_1 = &dao_exec_call_data.coin_1; - debug!("dao::exec() coin 1 = {:?}", dao_exec_coin_1); - - assert_eq!(coin_1, *money_transfer_coin_1); - assert_eq!(coin_1, Coin(*dao_exec_coin_1)); - } - - //// 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::FUNC_ID { - debug!("dao::exec::state_transition()"); - - let update = dao::exec::validate::state_transition(&states, idx, &tx) - .expect("dao::exec::validate::state_transition() failed!"); - updates.push(update); - } else if func_call.func_id == *money::transfer::FUNC_ID { - debug!("money::transfer::state_transition()"); - - let update = money::transfer::validate::state_transition(&states, idx, &tx) - .expect("money::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).unwrap(); - tx.verify_sigs(); - - //// Wallet - */ - - Ok(()) -} diff --git a/example/dao2/src/tx.rs b/example/dao2/src/tx.rs deleted file mode 100644 index 7aaa88338..000000000 --- a/example/dao2/src/tx.rs +++ /dev/null @@ -1,128 +0,0 @@ -/* 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::{crypto::Proof, Result, VerifyFailed::ProofVerifyFailed}; -use darkfi_sdk::{ - crypto::{ - schnorr::{SchnorrPublic, Signature}, - PublicKey, - }, - pasta::pallas, - tx::ContractCall, -}; -use darkfi_serial::Encodable; -use log::debug; - -use crate::{ - contract::{dao, example, money}, - note::EncryptedNote2, - schema::WalletCache, - util::{sign, StateRegistry, ZkContractInfo, ZkContractTable}, -}; - -macro_rules! zip { - ($x: expr) => ($x); - ($x: expr, $($y: expr), +) => ( - $x.iter().zip( - zip!($($y), +)) - ) -} - -pub struct Transaction { - pub calls: Vec, - pub proofs: Vec>, - pub signatures: Vec>, -} - -impl Transaction { - /// Verify ZK contracts for the entire tx - /// In real code, we could parallelize this for loop - /// TODO: fix use of unwrap with Result type stuff - pub fn zk_verify( - &self, - zk_bins: &ZkContractTable, - zkpub_table: &Vec)>>, - ) -> Result<()> { - assert_eq!( - self.calls.len(), - self.proofs.len(), - "calls.len()={} and proofs.len()={} do not match", - self.calls.len(), - self.proofs.len() - ); - assert_eq!( - self.calls.len(), - zkpub_table.len(), - "calls.len()={} and zkpub_table.len()={} do not match", - self.calls.len(), - zkpub_table.len() - ); - for (call, (proofs, pubvals)) in zip!(self.calls, self.proofs, zkpub_table) { - assert_eq!( - proofs.len(), - pubvals.len(), - "proofs.len()={} and pubvals.len()={} do not match", - proofs.len(), - pubvals.len() - ); - - for (i, (proof, (key, public_vals))) in proofs.iter().zip(pubvals.iter()).enumerate() { - match zk_bins.lookup(key).unwrap() { - ZkContractInfo::Binary(info) => { - let verifying_key = &info.verifying_key; - let verify_result = proof.verify(verifying_key, public_vals); - if verify_result.is_err() { - return Err(ProofVerifyFailed(key.to_string()).into()) - } - //assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); - } - ZkContractInfo::Native(info) => { - let verifying_key = &info.verifying_key; - let verify_result = proof.verify(verifying_key, public_vals); - if verify_result.is_err() { - return Err(ProofVerifyFailed(key.to_string()).into()) - } - //assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); - } - }; - debug!(target: "demo", "zk_verify({}) passed [i={}]", key, i); - } - } - Ok(()) - } - - pub fn verify_sigs(&self, sigpub_table: &Vec>) -> Result<()> { - let mut tx_data = Vec::new(); - self.calls.encode(&mut tx_data)?; - self.proofs.encode(&mut tx_data)?; - // TODO: Hash it and use the hash as the signing data - // let sighash = ... - - for (i, (signatures, signature_public_keys)) in - self.signatures.iter().zip(sigpub_table.iter()).enumerate() - { - for (signature_pub_key, signature) in signature_public_keys.iter().zip(signatures) { - let signature_pub_key = PublicKey::from(*signature_pub_key); - let verify_result = signature_pub_key.verify(&tx_data[..], &signature); - assert!(verify_result, "verify sigs[{}] failed", i); - } - debug!(target: "demo", "verify_sigs({}) passed", i); - } - Ok(()) - } -} diff --git a/example/dao2/src/util.rs b/example/dao2/src/util.rs deleted file mode 100644 index 98cddcc52..000000000 --- a/example/dao2/src/util.rs +++ /dev/null @@ -1,282 +0,0 @@ -/* 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 std::{any::Any, collections::HashMap, hash::Hasher}; - -use darkfi_sdk::crypto::{ - schnorr::{SchnorrPublic, SchnorrSecret, Signature}, - PublicKey, SecretKey, -}; -use darkfi_serial::Encodable; -use lazy_static::lazy_static; -use log::debug; -use pasta_curves::{ - group::ff::{Field, PrimeField}, - pallas, -}; -use rand::rngs::OsRng; - -use darkfi::{ - crypto::{ - proof::{ProvingKey, VerifyingKey}, - types::DrkCircuitField, - Proof, - }, - zk::{vm::ZkCircuit, vm_stack::empty_witnesses}, - zkas::decoder::ZkBinary, -}; - -use crate::error::{DaoError, DaoResult}; - -// /// Parse pallas::Base from a base58-encoded string -// pub fn parse_b58(s: &str) -> std::result::Result { -// let bytes = bs58::decode(s).into_vec()?; -// if bytes.len() != 32 { -// return Err(Error::ParseFailed("Failed parsing DrkTokenId from base58 string")) -// } - -// let ret = pallas::Base::from_repr(bytes.try_into().unwrap()); -// if ret.is_some().unwrap_u8() == 1 { -// return Ok(ret.unwrap()) -// } - -// Err(Error::ParseFailed("Failed parsing DrkTokenId from base58 string")) -// } - -// The token of the DAO treasury. -lazy_static! { - pub static ref DRK_ID: pallas::Base = pallas::Base::random(&mut OsRng); -} - -// Governance tokens that are airdropped to users to operate the DAO. -lazy_static! { - pub static ref GOV_ID: pallas::Base = pallas::Base::random(&mut OsRng); -} - -#[derive(Eq, PartialEq, Debug)] -pub struct HashableBase(pub pallas::Base); - -impl std::hash::Hash for HashableBase { - fn hash(&self, state: &mut H) { - let bytes = self.0.to_repr(); - bytes.hash(state); - } -} - -#[derive(Clone)] -pub struct ZkBinaryContractInfo { - pub k_param: u32, - pub bincode: ZkBinary, - pub proving_key: ProvingKey, - pub verifying_key: VerifyingKey, -} - -#[derive(Clone)] -pub struct ZkNativeContractInfo { - pub proving_key: ProvingKey, - pub verifying_key: VerifyingKey, -} - -#[derive(Clone)] -pub enum ZkContractInfo { - Binary(ZkBinaryContractInfo), - Native(ZkNativeContractInfo), -} - -#[derive(Clone)] -pub struct ZkContractTable { - // Key will be a hash of zk binary contract on chain - table: HashMap, -} - -impl ZkContractTable { - pub fn new() -> Self { - Self { table: HashMap::new() } - } - - pub fn add_contract(&mut self, key: String, bincode: ZkBinary, k_param: u32) { - let witnesses = empty_witnesses(&bincode); - let circuit = ZkCircuit::new(witnesses, bincode.clone()); - let proving_key = ProvingKey::build(k_param, &circuit); - let verifying_key = VerifyingKey::build(k_param, &circuit); - let info = ZkContractInfo::Binary(ZkBinaryContractInfo { - k_param, - bincode, - proving_key, - verifying_key, - }); - self.table.insert(key, info); - } - - pub fn add_native( - &mut self, - key: String, - proving_key: ProvingKey, - verifying_key: VerifyingKey, - ) { - self.table.insert( - key, - ZkContractInfo::Native(ZkNativeContractInfo { proving_key, verifying_key }), - ); - } - - pub fn lookup(&self, key: &String) -> Option<&ZkContractInfo> { - self.table.get(key) - } -} - -pub struct Transaction { - pub func_calls: Vec, - pub signatures: Vec>, -} - -impl Transaction { - /// Verify ZK contracts for the entire tx - /// In real code, we could parallelize this for loop - /// TODO: fix use of unwrap with Result type stuff - pub fn zk_verify(&self, zk_bins: &ZkContractTable) -> DaoResult<()> { - for func_call in &self.func_calls { - let proofs_public_vals = &func_call.call_data.zk_public_values(); - - assert_eq!( - proofs_public_vals.len(), - func_call.proofs.len(), - "proof_public_vals.len()={} and func_call.proofs.len()={} do not match", - proofs_public_vals.len(), - func_call.proofs.len() - ); - for (i, (proof, (key, public_vals))) in - func_call.proofs.iter().zip(proofs_public_vals.iter()).enumerate() - { - match zk_bins.lookup(key).unwrap() { - ZkContractInfo::Binary(info) => { - let verifying_key = &info.verifying_key; - let verify_result = proof.verify(&verifying_key, public_vals); - if verify_result.is_err() { - return Err(DaoError::VerifyProofFailed(i, key.to_string())) - } - //assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); - } - ZkContractInfo::Native(info) => { - let verifying_key = &info.verifying_key; - let verify_result = proof.verify(&verifying_key, public_vals); - if verify_result.is_err() { - return Err(DaoError::VerifyProofFailed(i, key.to_string())) - } - //assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); - } - }; - debug!(target: "demo", "zk_verify({}) passed [i={}]", key, i); - } - } - Ok(()) - } - - pub fn verify_sigs(&self) { - let mut unsigned_tx_data = vec![]; - for (i, (func_call, signatures)) in - self.func_calls.iter().zip(self.signatures.clone()).enumerate() - { - func_call.encode(&mut unsigned_tx_data).expect("failed to encode data"); - let signature_pub_keys = func_call.call_data.signature_public_keys(); - for (signature_pub_key, signature) in signature_pub_keys.iter().zip(signatures) { - let verify_result = signature_pub_key.verify(&unsigned_tx_data[..], &signature); - assert!(verify_result, "verify sigs[{}] failed", i); - } - debug!(target: "demo", "verify_sigs({}) passed", i); - } - } -} - -pub fn sign(signature_secrets: Vec, func_call: &FuncCall) -> Vec { - let mut signatures = vec![]; - let mut unsigned_tx_data = vec![]; - for signature_secret in signature_secrets { - func_call.encode(&mut unsigned_tx_data).expect("failed to encode data"); - let signature = signature_secret.sign(&mut OsRng, &unsigned_tx_data[..]); - signatures.push(signature); - } - signatures -} - -type ContractId = pallas::Base; -type FuncId = pallas::Base; - -pub struct FuncCall { - pub contract_id: ContractId, - pub func_id: FuncId, - pub call_data: Box, - pub proofs: Vec, -} - -impl Encodable for FuncCall { - fn encode(&self, mut w: W) -> std::result::Result { - let mut len = 0; - len += self.contract_id.encode(&mut w)?; - len += self.func_id.encode(&mut w)?; - len += self.proofs.encode(&mut w)?; - len += self.call_data.encode_bytes(&mut w)?; - Ok(len) - } -} - -pub trait CallDataBase { - // Public values for verifying the proofs - // Needed so we can convert internal types so they can be used in Proof::verify() - fn zk_public_values(&self) -> Vec<(String, Vec)>; - - // For upcasting to CallData itself so it can be read in state_transition() - fn as_any(&self) -> &dyn Any; - - // Public keys we will use to verify transaction signatures. - fn signature_public_keys(&self) -> Vec; - - fn encode_bytes( - &self, - writer: &mut dyn std::io::Write, - ) -> std::result::Result; -} - -type GenericContractState = Box; - -pub struct StateRegistry { - pub states: HashMap, -} - -impl StateRegistry { - pub fn new() -> Self { - Self { states: HashMap::new() } - } - - pub fn register(&mut self, contract_id: ContractId, state: GenericContractState) { - debug!(target: "StateRegistry::register()", "contract_id: {:?}", contract_id); - self.states.insert(HashableBase(contract_id), state); - } - - pub fn lookup_mut<'a, S: 'static>(&'a mut self, contract_id: ContractId) -> Option<&'a mut S> { - self.states.get_mut(&HashableBase(contract_id)).and_then(|state| state.downcast_mut()) - } - - pub fn lookup<'a, S: 'static>(&'a self, contract_id: ContractId) -> Option<&'a S> { - self.states.get(&HashableBase(contract_id)).and_then(|state| state.downcast_ref()) - } -} - -pub trait UpdateBase { - fn apply(self: Box, states: &mut StateRegistry); -}