Просмотр исходного кода

merge dao1 into dao2 ready for migration

x 3 лет назад
Родитель
Сommit
26637e2b6e

+ 74 - 0
example/dao2/Cargo.lock

@@ -38,6 +38,15 @@ dependencies = [
  "version_check",
 ]
 
+[[package]]
+name = "aho-corasick"
+version = "0.7.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "android_system_properties"
 version = "0.1.5"
@@ -838,13 +847,36 @@ dependencies = [
 name = "dao"
 version = "0.3.0"
 dependencies = [
+ "async-channel",
+ "async-executor",
+ "async-std",
+ "async-trait",
+ "bs58",
+ "chacha20poly1305",
  "dao-contract",
  "darkfi",
  "darkfi-sdk",
  "darkfi-serial",
+ "easy-parallel",
+ "env_logger",
+ "futures",
+ "fxhash",
+ "group",
+ "halo2_gadgets",
+ "halo2_proofs",
+ "incrementalmerkletree",
+ "lazy_static",
+ "log",
  "money-contract",
+ "num_cpus",
+ "pasta_curves",
+ "rand",
+ "serde_json",
  "simplelog",
  "sled",
+ "smol",
+ "thiserror",
+ "url",
 ]
 
 [[package]]
@@ -1089,6 +1121,12 @@ dependencies = [
  "memmap2",
 ]
 
+[[package]]
+name = "easy-parallel"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6907e25393cdcc1f4f3f513d9aac1e840eb1cc341a0fccb01171f7d14d10b946"
+
 [[package]]
 name = "ed25519-compact"
 version = "1.0.16"
@@ -1152,6 +1190,19 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "env_logger"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
 [[package]]
 name = "error-chain"
 version = "0.12.4"
@@ -1544,6 +1595,12 @@ version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
 [[package]]
 name = "iana-time-zone"
 version = "0.1.53"
@@ -2284,6 +2341,23 @@ dependencies = [
  "smallvec",
 ]
 
+[[package]]
+name = "regex"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
+
 [[package]]
 name = "region"
 version = "3.0.0"

+ 34 - 1
example/dao2/Cargo.toml

@@ -17,5 +17,38 @@ money-contract = {path = "contract/money"}
 darkfi-sdk = { path = "../../src/sdk" }
 darkfi-serial = { path = "../../src/serial" }
 darkfi = { path = "../../", features = ["wasm-runtime"] }
-simplelog = "0.12.0"
 sled = "0.34.7"
+
+# Async
+smol = "1.2.5"
+futures = "0.3.24"
+async-std = {version = "1.12.0", features = ["attributes"]}
+async-trait = "0.1.57"
+async-channel = "1.7.1"
+async-executor = "1.4.1"
+easy-parallel = "3.2.0"
+
+# Misc
+log = "0.4.17"
+num_cpus = "1.13.1"
+simplelog = "0.12.0"
+thiserror = "1.0.37"
+
+# Crypto
+incrementalmerkletree = "0.3.0"
+pasta_curves = "0.4.0"
+halo2_gadgets = "0.2.0"
+halo2_proofs = "0.2.0"
+rand = "0.8.5"
+chacha20poly1305 = "0.10.1"
+group = "0.12.0"
+
+# Encoding and parsing
+serde_json = "1.0.85"
+bs58 = "0.4.0"
+fxhash = "0.2.1"
+
+# Utilities
+lazy_static = "1.4.0"
+url = "2.3.1"
+env_logger = "0.9.1"

+ 13 - 24
example/dao2/contract/dao/src/lib.rs

@@ -5,7 +5,7 @@ use darkfi_sdk::{
     error::ContractResult,
     msg,
     pasta::pallas,
-    tx::FuncCall,
+    tx::ContractCall,
     util::{set_return_data, put_object_bytes, get_object_bytes, get_object_size},
 };
 use darkfi_serial::{deserialize, serialize, Encodable, SerialDecodable, SerialEncodable, WriteExt, ReadExt};
@@ -13,10 +13,13 @@ use darkfi_serial::{deserialize, serialize, Encodable, SerialDecodable, SerialEn
 #[repr(u8)]
 pub enum DaoFunction {
     Foo = 0x00,
+    Mint = 0x01,
 }
 
-fn foo() {
-    println!("foo");
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct DaoMintParams {
+    pub a: u32,
+    pub b: u32
 }
 
 define_contract!(
@@ -27,29 +30,13 @@ define_contract!(
 );
 
 fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
+    let db_handle = db_init(cid, "wagies")?;
+
     Ok(())
 }
 fn get_metadata(_cid: ContractId, ix: &[u8]) -> ContractResult {
-    let zk_public_values = vec![
-        (
-            "DaoProposeInput".to_string(),
-            vec![pallas::Base::from(110), pallas::Base::from(4)],
-        ),
-        ("DaoProposeInput".to_string(), vec![pallas::Base::from(7), pallas::Base::from(4)]),
-        (
-            "DaoProposeMain".to_string(),
-            vec![
-                pallas::Base::from(1),
-                pallas::Base::from(3),
-                pallas::Base::from(5),
-                pallas::Base::from(7),
-            ],
-        ),
-    ];
-
-    let signature_public_keys: Vec<pallas::Point> = vec![
-        //pallas::Point::identity()
-    ];
+    let zk_public_values: Vec<(String, Vec<pallas::Base>)> = Vec::new();
+    let signature_public_keys: Vec<pallas::Point> = Vec::new();
 
     let mut metadata = Vec::new();
     zk_public_values.encode(&mut metadata)?;
@@ -61,6 +48,8 @@ fn get_metadata(_cid: ContractId, ix: &[u8]) -> ContractResult {
 fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
     Ok(())
 }
-fn process_update(_cid: ContractId, update_data: &[u8]) -> ContractResult {
+fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
+    let db_handle = db_lookup(cid, "wagies")?;
+    db_set(db_handle, &serialize(&"jason_gulag".to_string()), &serialize(&110))?;
     Ok(())
 }

+ 168 - 0
example/dao2/proof/dao-exec.zk

@@ -0,0 +1,168 @@
+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
+}
+

+ 32 - 0
example/dao2/proof/dao-mint.zk

@@ -0,0 +1,32 @@
+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);
+}
+

+ 63 - 0
example/dao2/proof/dao-propose-burn.zk

@@ -0,0 +1,63 @@
+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.
+}
+
+

+ 88 - 0
example/dao2/proof/dao-propose-main.zk

@@ -0,0 +1,88 @@
+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);
+}
+

+ 64 - 0
example/dao2/proof/dao-vote-burn.zk

@@ -0,0 +1,64 @@
+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.
+}
+
+
+

+ 98 - 0
example/dao2/proof/dao-vote-main.zk

@@ -0,0 +1,98 @@
+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);
+}
+
+

+ 14 - 0
example/dao2/proof/foo.zk

@@ -0,0 +1,14 @@
+constant "DaoMint" {
+}
+
+contract "DaoMint" {
+    Base a,
+    Base b,
+}
+
+circuit "DaoMint" {
+    c = base_add(a, b);
+    constrain_instance(c);
+}
+
+

+ 28 - 0
example/dao2/src/contract/example/foo/mod.rs

@@ -0,0 +1,28 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use 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);
+}

+ 110 - 0
example/dao2/src/contract/example/foo/validate.rs

@@ -0,0 +1,110 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::any::{Any, TypeId};
+
+use pasta_curves::pallas;
+
+use darkfi::{
+    crypto::{keypair::PublicKey, types::DrkCircuitField},
+    Error as DarkFiError,
+};
+use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
+
+use crate::{
+    contract::example::{state::State, CONTRACT_ID},
+    util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
+};
+
+type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum Error {
+    #[error("ValueExists")]
+    ValueExists,
+    #[error("DarkFi error: {0}")]
+    DarkFiError(String),
+}
+
+impl From<DarkFiError> 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<DrkCircuitField>)> {
+        vec![("example-foo".to_string(), vec![self.public_value])]
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn signature_public_keys(&self) -> Vec<PublicKey> {
+        vec![self.signature_public]
+    }
+
+    fn encode_bytes(
+        &self,
+        mut writer: &mut dyn std::io::Write,
+    ) -> std::result::Result<usize, std::io::Error> {
+        self.encode(&mut writer)
+    }
+}
+
+pub fn state_transition(
+    states: &StateRegistry,
+    func_call_index: usize,
+    parent_tx: &Transaction,
+) -> Result<Box<dyn UpdateBase + Send>> {
+    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::<CallData>());
+    let call_data = call_data.downcast_ref::<CallData>();
+
+    // This will be inside wasm so unwrap is fine.
+    let call_data = call_data.unwrap();
+
+    let example_state = states.lookup::<State>(*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<Self>, states: &mut StateRegistry) {
+        let example_state = states.lookup_mut::<State>(*CONTRACT_ID).unwrap();
+        example_state.add_public_value(self.public_value);
+    }
+}

+ 92 - 0
example/dao2/src/contract/example/foo/wallet.rs

@@ -0,0 +1,92 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use log::debug;
+use rand::rngs::OsRng;
+
+use halo2_proofs::circuit::Value;
+use pasta_curves::pallas;
+
+use darkfi::{
+    crypto::{
+        keypair::{PublicKey, SecretKey},
+        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,
+        }
+    }
+}

+ 30 - 0
example/dao2/src/contract/example/mod.rs

@@ -0,0 +1,30 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use 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);
+}

+ 39 - 0
example/dao2/src/contract/example/state.rs

@@ -0,0 +1,39 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::any::Any;
+
+use pasta_curves::pallas;
+
+pub struct State {
+    pub public_values: Vec<pallas::Base>,
+}
+
+impl State {
+    pub fn new() -> Box<dyn Any + Send> {
+        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)
+    }
+}

+ 21 - 0
example/dao2/src/contract/mod.rs

@@ -0,0 +1,21 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+pub mod dao;
+pub mod example;
+pub mod money;

+ 31 - 0
example/dao2/src/contract/money/mod.rs

@@ -0,0 +1,31 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use 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);
+}

+ 71 - 0
example/dao2/src/contract/money/state.rs

@@ -0,0 +1,71 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use darkfi::crypto::keypair::PublicKey;
+use darkfi_sdk::crypto::{constants::MERKLE_DEPTH, MerkleNode, Nullifier};
+use incrementalmerkletree::bridgetree::BridgeTree;
+
+type MerkleTree = BridgeTree<MerkleNode, { MERKLE_DEPTH }>;
+
+/// 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<MerkleNode>,
+    /// Nullifiers prevent double spending
+    pub nullifiers: Vec<Nullifier>,
+
+    /// 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<Self> {
+        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)
+    }
+}

+ 29 - 0
example/dao2/src/contract/money/transfer/mod.rs

@@ -0,0 +1,29 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use 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);
+}

+ 390 - 0
example/dao2/src/contract/money/transfer/validate.rs

@@ -0,0 +1,390 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::any::{Any, TypeId};
+
+use darkfi_sdk::crypto::{MerkleNode, Nullifier};
+use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
+use incrementalmerkletree::Tree;
+use log::{debug, error};
+use pasta_curves::{group::Group, pallas};
+
+use darkfi::{
+    crypto::{
+        coin::Coin,
+        keypair::PublicKey,
+        types::{DrkCircuitField, DrkTokenId, DrkValueBlind, DrkValueCommit},
+        util::{pedersen_commitment_base, pedersen_commitment_u64},
+        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<Nullifier>,
+    /// All coins in a transaction
+    pub coins: Vec<Coin>,
+    /// All encrypted notes in a transaction
+    pub enc_notes: Vec<EncryptedNote2>,
+}
+
+impl UpdateBase for Update {
+    fn apply(mut self: Box<Self>, states: &mut StateRegistry) {
+        let state = states.lookup_mut::<State>(*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<Box<dyn UpdateBase + Send>> {
+    // 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::<CallData>());
+    let call_data = call_data.downcast_ref::<CallData>();
+
+    // This will be inside wasm so unwrap is fine.
+    let call_data = call_data.unwrap();
+
+    let state = states.lookup::<State>(*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<ClearInput>,
+    /// Anonymous inputs
+    pub inputs: Vec<Input>,
+    /// Anonymous outputs
+    pub outputs: Vec<Output>,
+}
+
+impl CallDataBase for CallData {
+    fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
+        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<PublicKey> {
+        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<usize, std::io::Error> {
+        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, 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: DrkTokenId,
+    /// 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<T> = std::result::Result<T, Error>;
+
+impl From<Error> for VerifyFailed {
+    fn from(err: Error) -> Self {
+        Self::InternalError(err.to_string())
+    }
+}
+
+impl From<DarkFiError> for VerifyFailed {
+    fn from(err: DarkFiError) -> Self {
+        Self::DarkFiError(err.to_string())
+    }
+}
+
+impl From<DarkFiError> for Error {
+    fn from(err: DarkFiError) -> Self {
+        Self::DarkFiError(err.to_string())
+    }
+}
+/// Result type used in transaction verifications
+pub type VerifyResult<T> = std::result::Result<T, VerifyFailed>;

+ 237 - 0
example/dao2/src/contract/money/transfer/wallet.rs

@@ -0,0 +1,237 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use darkfi_sdk::crypto::MerkleNode;
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+use pasta_curves::group::ff::Field;
+use rand::rngs::OsRng;
+
+use darkfi::{
+    crypto::{
+        burn_proof::create_burn_proof,
+        keypair::{PublicKey, SecretKey},
+        mint_proof::create_mint_proof,
+        types::{
+            DrkCoinBlind, DrkSerial, DrkSpendHook, DrkTokenId, DrkUserData, DrkUserDataBlind,
+            DrkValueBlind,
+        },
+    },
+    Result,
+};
+
+use crate::{
+    contract::money::{
+        transfer::validate::{CallData, ClearInput, Input, Output},
+        CONTRACT_ID,
+    },
+    note,
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
+};
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct Note {
+    pub serial: DrkSerial,
+    pub value: u64,
+    pub token_id: DrkTokenId,
+    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<BuilderClearInputInfo>,
+    pub inputs: Vec<BuilderInputInfo>,
+    pub outputs: Vec<BuilderOutputInfo>,
+}
+
+pub struct BuilderClearInputInfo {
+    pub value: u64,
+    pub token_id: DrkTokenId,
+    pub signature_secret: SecretKey,
+}
+
+pub struct BuilderInputInfo {
+    pub leaf_position: incrementalmerkletree::Position,
+    pub merkle_path: Vec<MerkleNode>,
+    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: DrkTokenId,
+    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<FuncCall> {
+        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,
+                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 { revealed };
+            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(&note, &output.public)?;
+
+            let output = Output { revealed, enc_note: encrypted_note };
+            outputs.push(output);
+        }
+
+        let call_data = CallData { clear_inputs, inputs, outputs };
+
+        Ok(FuncCall {
+            contract_id: *CONTRACT_ID,
+            func_id: *super::FUNC_ID,
+            call_data: Box::new(call_data),
+            proofs,
+        })
+    }
+}

+ 74 - 0
example/dao2/src/error.rs

@@ -0,0 +1,74 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+// use 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<T> = std::result::Result<T, DaoError>;
+
+// 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()
+// }

+ 45 - 18
example/dao2/src/main.rs

@@ -4,15 +4,33 @@ use darkfi::{
     runtime::vm_runtime::Runtime,
     Result,
 };
-use darkfi_sdk::{crypto::ContractId, pasta::pallas, tx::FuncCall};
-use darkfi_serial::{serialize, Decodable, Encodable, WriteExt};
+use darkfi_sdk::{crypto::ContractId, pasta::pallas, tx::ContractCall};
+use darkfi_serial::{serialize, deserialize, Decodable, Encodable, WriteExt};
 use std::io::Cursor;
 
-use dao_contract::DaoFunction;
+use dao_contract::{DaoFunction, DaoMintParams};
 
-fn main() -> Result<()> {
-    println!("wakie wakie young wagie");
+mod contract;
+mod error;
+mod note;
+mod schema;
+mod util;
+
+fn show_dao_state(chain: &Blockchain, contract_id: &ContractId) -> Result<()> {
+    let db = chain.contracts.lookup(&chain.sled_db, contract_id, "wagies")?;
+    for obj in db.iter() {
+        let (key, value) = obj.unwrap();
+        let name: String = deserialize(&key)?;
+        let age: u32 = deserialize(&value)?;
+        println!("{}: {}", name, age);
+    }
+    Ok(())
+}
+
+type BoxResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
 
+#[async_std::main]
+async fn main() -> BoxResult<()> {
     // Debug log configuration
     let mut cfg = simplelog::ConfigBuilder::new();
     cfg.add_filter_ignore("sled".to_string());
@@ -23,6 +41,10 @@ fn main() -> Result<()> {
         simplelog::ColorChoice::Auto,
     )?;
 
+    println!("wakie wakie young wagie");
+    schema::schema().await?;
+    return Ok(());
+
     // =============================
     // Initialize a dummy blockchain
     // =============================
@@ -34,33 +56,36 @@ fn main() -> Result<()> {
     // Load the wasm binary into memory and create an execution runtime
     // ================================================================
     let wasm_bytes = std::fs::read("dao_contract.wasm")?;
-    let contract_id = ContractId::from(pallas::Base::from(1));
-    let mut runtime = Runtime::new(&wasm_bytes, blockchain.clone(), contract_id)?;
+    let dao_contract_id = ContractId::from(pallas::Base::from(1));
+    let mut runtime = Runtime::new(&wasm_bytes, blockchain.clone(), dao_contract_id)?;
 
     // Deploy function to initialize the smart contract state.
     // Here we pass an empty payload, but it's possible to feed in arbitrary data.
     runtime.deploy(&[])?;
 
     // This is another call so we instantiate a new runtime.
-    let mut runtime = Runtime::new(&wasm_bytes, blockchain, contract_id)?;
+    let mut runtime = Runtime::new(&wasm_bytes, blockchain.clone(), dao_contract_id)?;
 
     // =============================================
     // Build some kind of payload to show an example
     // =============================================
-    let func_calls = vec![FuncCall {
-        contract_id: pallas::Base::from(110),
-        func_id: pallas::Base::from(4),
-        //call_data: serialize(&FooCallData { a: 777, b: 666 }),
-        call_data: Vec::new()
+    // Write the actual call data
+    let mut calldata = Vec::new();
+    // Selects which path executes in the contract.
+    calldata.write_u8(DaoFunction::Mint as u8)?;
+    let params = DaoMintParams { a: 777, b: 666 };
+    params.encode(&mut calldata)?;
+
+    let func_calls = vec![ContractCall {
+        contract_id: dao_contract_id,
+        calldata
     }];
-    let func_call_index: u32 = 0;
 
     let mut payload = Vec::new();
-    // Selects which path executes in the contract.
-    //payload.write_u8(Function::Foo as u8)?;
     //// Write the actual payload data
-    //payload.write_u32(func_call_index)?;
-    //func_calls.encode(&mut payload)?;
+    let call_index = 0;
+    payload.write_u32(call_index)?;
+    func_calls.encode(&mut payload)?;
 
     // ============================================================
     // Serialize the payload into the runtime format and execute it
@@ -80,5 +105,7 @@ fn main() -> Result<()> {
     let zk_public_values: Vec<(String, Vec<pallas::Base>)> = Decodable::decode(&mut decoder)?;
     let signature_public_keys: Vec<pallas::Point> = Decodable::decode(decoder)?;
 
+    show_dao_state(&blockchain, &dao_contract_id)?;
+
     Ok(())
 }

+ 121 - 0
example/dao2/src/note.rs

@@ -0,0 +1,121 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use chacha20poly1305::{AeadInPlace, ChaCha20Poly1305, KeyInit};
+use rand::rngs::OsRng;
+
+use darkfi::{
+    crypto::{
+        diffie_hellman::{kdf_sapling, sapling_ka_agree},
+        keypair::{PublicKey, SecretKey},
+    },
+    Error, Result,
+};
+use darkfi_serial::{Decodable, Encodable, SerialDecodable, SerialEncodable};
+
+pub const AEAD_TAG_SIZE: usize = 16;
+
+pub fn encrypt<T: Encodable>(note: &T, public: &PublicKey) -> Result<EncryptedNote2> {
+    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 {
+    ciphertext: Vec<u8>,
+    ephem_public: PublicKey,
+}
+
+impl EncryptedNote2 {
+    pub fn decrypt<T: Decodable>(&self, secret: &SecretKey) -> Result<T> {
+        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, DrkTokenId, DrkValueBlind},
+    };
+    use group::ff::Field;
+
+    #[test]
+    fn test_note_encdec() {
+        #[derive(SerialEncodable, SerialDecodable)]
+        struct MyNote {
+            serial: DrkSerial,
+            value: u64,
+            token_id: DrkTokenId,
+            coin_blind: DrkCoinBlind,
+            value_blind: DrkValueBlind,
+            token_blind: DrkValueBlind,
+            memo: Vec<u8>,
+        }
+        let note = MyNote {
+            serial: DrkSerial::random(&mut OsRng),
+            value: 110,
+            token_id: DrkTokenId::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(&note, &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);
+    }
+}

+ 1294 - 0
example/dao2/src/schema.rs

@@ -0,0 +1,1294 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::{
+    any::{Any, TypeId},
+    time::Instant,
+};
+
+use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
+use log::debug;
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{ff::Field, Curve, Group},
+    pallas,
+};
+use rand::rngs::OsRng;
+
+use darkfi::{
+    crypto::{
+        coin::Coin,
+        keypair::{Keypair, PublicKey, SecretKey},
+        proof::{ProvingKey, VerifyingKey},
+        types::{DrkSpendHook, DrkUserData, DrkValue},
+        util::{pedersen_commitment_u64, poseidon_hash},
+    },
+    zk::circuit::{BurnContract, MintContract},
+    zkas::decoder::ZkBinary,
+};
+use darkfi_sdk::crypto::{constants::MERKLE_DEPTH, MerkleNode};
+
+use crate::{
+    contract::{dao, example, money},
+    note::EncryptedNote2,
+    util::{sign, StateRegistry, Transaction, ZkContractTable},
+};
+
+type MerkleTree = BridgeTree<MerkleNode, { MERKLE_DEPTH }>;
+
+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<OwnCoin>)>,
+    /// 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<OwnCoin> {
+        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<T> = std::result::Result<T, Box<dyn std::error::Error>>;
+
+///////////////////////////////////////////////////
+///// 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;
+
+    // Lookup table for smart contract states
+    let mut states = StateRegistry::new();
+
+    // 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();
+
+    ///////////////////////////////////////////////////
+
+    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::State>(*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::<dao::mint::validate::CallData>());
+        let call_data = call_data.downcast_ref::<dao::mint::validate::CallData>().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::<money::transfer::validate::CallData>());
+        let call_data = call_data.downcast_ref::<money::transfer::validate::CallData>().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::<money::transfer::validate::CallData>());
+        let call_data = call_data.downcast_ref::<money::transfer::validate::CallData>().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::State>(*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::<dao::propose::validate::CallData>());
+        let call_data = call_data.downcast_ref::<dao::propose::validate::CallData>().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::<dao::vote::validate::CallData>());
+        let call_data = call_data.downcast_ref::<dao::vote::validate::CallData>().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::<dao::vote::validate::CallData>());
+        let call_data = call_data.downcast_ref::<dao::vote::validate::CallData>().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::<dao::vote::validate::CallData>());
+        let call_data = call_data.downcast_ref::<dao::vote::validate::CallData>().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<Update> 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::<money::transfer::validate::CallData>()
+        );
+        let transfer_call_data =
+            transfer_call_data.downcast_ref::<money::transfer::validate::CallData>();
+        let transfer_call_data = transfer_call_data.unwrap();
+        // At least one input has this field value which means DAO::exec() is invoked.
+        assert_eq!(transfer_call_data.inputs.len(), 1);
+        let input = &transfer_call_data.inputs[0];
+        assert_eq!(input.revealed.spend_hook, *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::<money::transfer::validate::CallData>();
+        let money_transfer_call_data = money_transfer_call_data.unwrap();
+        assert_eq!(
+            money_transfer_call_data.type_id(),
+            TypeId::of::<money::transfer::validate::CallData>()
+        );
+        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::<dao::exec::validate::CallData>();
+        let dao_exec_call_data = dao_exec_call_data.unwrap();
+        assert_eq!(dao_exec_call_data.type_id(), TypeId::of::<dao::exec::validate::CallData>());
+        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(())
+}
+

+ 280 - 0
example/dao2/src/util.rs

@@ -0,0 +1,280 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::{any::Any, collections::HashMap, hash::Hasher};
+
+use lazy_static::lazy_static;
+use log::debug;
+use pasta_curves::{
+    group::ff::{Field, PrimeField},
+    pallas,
+};
+use rand::rngs::OsRng;
+
+use darkfi::{
+    crypto::{
+        keypair::{PublicKey, SecretKey},
+        proof::{ProvingKey, VerifyingKey},
+        schnorr::{SchnorrPublic, SchnorrSecret, Signature},
+        types::DrkCircuitField,
+        Proof,
+    },
+    zk::{vm::ZkCircuit, vm_stack::empty_witnesses},
+    zkas::decoder::ZkBinary,
+};
+use darkfi_serial::Encodable;
+
+use crate::error::{DaoError, DaoResult};
+
+// /// Parse pallas::Base from a base58-encoded string
+// pub fn parse_b58(s: &str) -> std::result::Result<pallas::Base, darkfi::Error> {
+//     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<H: Hasher>(&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<String, ZkContractInfo>,
+}
+
+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<FuncCall>,
+    pub signatures: Vec<Vec<Signature>>,
+}
+
+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<SecretKey>, func_call: &FuncCall) -> Vec<Signature> {
+    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(&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<dyn CallDataBase + Send + Sync>,
+    pub proofs: Vec<Proof>,
+}
+
+impl Encodable for FuncCall {
+    fn encode<W: std::io::Write>(&self, mut w: W) -> std::result::Result<usize, std::io::Error> {
+        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<DrkCircuitField>)>;
+
+    // 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<PublicKey>;
+
+    fn encode_bytes(
+        &self,
+        writer: &mut dyn std::io::Write,
+    ) -> std::result::Result<usize, std::io::Error>;
+}
+
+type GenericContractState = Box<dyn Any + Send>;
+
+pub struct StateRegistry {
+    pub states: HashMap<HashableBase, GenericContractState>,
+}
+
+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<Self>, states: &mut StateRegistry);
+}

+ 3 - 5
src/sdk/src/tx.rs

@@ -1,12 +1,10 @@
 use darkfi_serial::{SerialDecodable, SerialEncodable};
 use pasta_curves::pallas;
 
-type ContractId = pallas::Base;
-type FuncId = pallas::Base;
+use super::crypto::ContractId;
 
 #[derive(SerialEncodable, SerialDecodable)]
-pub struct FuncCall {
+pub struct ContractCall {
     pub contract_id: ContractId,
-    pub func_id: FuncId,
-    pub call_data: Vec<u8>,
+    pub calldata: Vec<u8>,
 }