浏览代码

resolve conflicts merging dao_demo

Dastan-glitch 3 年之前
父节点
当前提交
c0c97337d7
共有 96 个文件被更改,包括 7336 次插入219 次删除
  1. 1 1
      .gitignore
  2. 4 1
      Cargo.lock
  3. 12 2
      Cargo.toml
  4. 2 3
      Makefile
  5. 0 77
      bin/dao-cli/src/main.rs
  6. 1 0
      bin/dao/README.md
  7. 3 2
      bin/dao/dao-cli/Cargo.toml
  8. 41 0
      bin/dao/dao-cli/run_demo.sh
  9. 241 0
      bin/dao/dao-cli/src/main.rs
  10. 112 0
      bin/dao/dao-cli/src/rpc.rs
  11. 0 0
      bin/dao/dao_config.toml
  12. 4 2
      bin/dao/daod/Cargo.toml
  13. 0 0
      bin/dao/daod/Makefile
  14. 16 0
      bin/dao/daod/TODO
  15. 0 0
      bin/dao/daod/demo-spec.md
  16. 168 0
      bin/dao/daod/proof/dao-exec.zk
  17. 0 0
      bin/dao/daod/proof/dao-mint.zk
  18. 0 0
      bin/dao/daod/proof/dao-propose-burn.zk
  19. 0 0
      bin/dao/daod/proof/dao-propose-main.zk
  20. 0 0
      bin/dao/daod/proof/dao-vote-burn.zk
  21. 0 0
      bin/dao/daod/proof/dao-vote-main.zk
  22. 0 0
      bin/dao/daod/proof/foo.zk
  23. 0 0
      bin/dao/daod/src/contract/dao_contract/exec/mod.rs
  24. 201 0
      bin/dao/daod/src/contract/dao_contract/exec/validate.rs
  25. 196 0
      bin/dao/daod/src/contract/dao_contract/exec/wallet.rs
  26. 0 0
      bin/dao/daod/src/contract/dao_contract/mint/mod.rs
  27. 71 0
      bin/dao/daod/src/contract/dao_contract/mint/validate.rs
  28. 5 6
      bin/dao/daod/src/contract/dao_contract/mint/wallet.rs
  29. 0 0
      bin/dao/daod/src/contract/dao_contract/mod.rs
  30. 0 0
      bin/dao/daod/src/contract/dao_contract/propose/mod.rs
  31. 173 0
      bin/dao/daod/src/contract/dao_contract/propose/validate.rs
  32. 272 0
      bin/dao/daod/src/contract/dao_contract/propose/wallet.rs
  33. 98 0
      bin/dao/daod/src/contract/dao_contract/state.rs
  34. 0 0
      bin/dao/daod/src/contract/dao_contract/vote/mod.rs
  35. 208 0
      bin/dao/daod/src/contract/dao_contract/vote/validate.rs
  36. 292 0
      bin/dao/daod/src/contract/dao_contract/vote/wallet.rs
  37. 0 0
      bin/dao/daod/src/contract/example_contract/foo/mod.rs
  38. 93 0
      bin/dao/daod/src/contract/example_contract/foo/validate.rs
  39. 74 0
      bin/dao/daod/src/contract/example_contract/foo/wallet.rs
  40. 0 0
      bin/dao/daod/src/contract/example_contract/mod.rs
  41. 21 0
      bin/dao/daod/src/contract/example_contract/state.rs
  42. 3 0
      bin/dao/daod/src/contract/mod.rs
  43. 0 0
      bin/dao/daod/src/contract/money_contract/mod.rs
  44. 116 0
      bin/dao/daod/src/contract/money_contract/state.rs
  45. 0 0
      bin/dao/daod/src/contract/money_contract/transfer/mod.rs
  46. 375 0
      bin/dao/daod/src/contract/money_contract/transfer/validate.rs
  47. 220 0
      bin/dao/daod/src/contract/money_contract/transfer/wallet.rs
  48. 56 0
      bin/dao/daod/src/error.rs
  49. 1156 0
      bin/dao/daod/src/main.rs
  50. 98 0
      bin/dao/daod/src/note.rs
  51. 402 0
      bin/dao/daod/src/rpc.rs
  52. 2 1
      bin/dao/daod/src/schema.rs
  53. 265 0
      bin/dao/daod/src/util.rs
  54. 0 72
      bin/daod/src/main.rs
  55. 10 0
      example/dao/contract/dao_contract/exec/mod.rs
  56. 3 5
      example/dao/contract/dao_contract/exec/validate.rs
  57. 2 2
      example/dao/contract/dao_contract/exec/wallet.rs
  58. 44 0
      example/dao/contract/dao_contract/mint/mod.rs
  59. 3 3
      example/dao/contract/dao_contract/mint/validate.rs
  60. 96 0
      example/dao/contract/dao_contract/mint/wallet.rs
  61. 20 0
      example/dao/contract/dao_contract/mod.rs
  62. 10 0
      example/dao/contract/dao_contract/propose/mod.rs
  63. 6 6
      example/dao/contract/dao_contract/propose/validate.rs
  64. 9 6
      example/dao/contract/dao_contract/propose/wallet.rs
  65. 2 2
      example/dao/contract/dao_contract/state.rs
  66. 10 0
      example/dao/contract/dao_contract/vote/mod.rs
  67. 6 6
      example/dao/contract/dao_contract/vote/validate.rs
  68. 10 7
      example/dao/contract/dao_contract/vote/wallet.rs
  69. 10 0
      example/dao/contract/example_contract/foo/mod.rs
  70. 3 3
      example/dao/contract/example_contract/foo/validate.rs
  71. 2 2
      example/dao/contract/example_contract/foo/wallet.rs
  72. 12 0
      example/dao/contract/example_contract/mod.rs
  73. 0 0
      example/dao/contract/example_contract/state.rs
  74. 3 0
      example/dao/contract/mod.rs
  75. 13 0
      example/dao/contract/money_contract/mod.rs
  76. 0 0
      example/dao/contract/money_contract/state.rs
  77. 11 0
      example/dao/contract/money_contract/transfer/mod.rs
  78. 6 4
      example/dao/contract/money_contract/transfer/validate.rs
  79. 2 2
      example/dao/contract/money_contract/transfer/wallet.rs
  80. 1327 0
      example/dao/dao.rs
  81. 0 0
      example/dao/note.rs
  82. 0 0
      example/dao/proof/dao-exec.zk
  83. 32 0
      example/dao/proof/dao-mint.zk
  84. 63 0
      example/dao/proof/dao-propose-burn.zk
  85. 88 0
      example/dao/proof/dao-propose-main.zk
  86. 64 0
      example/dao/proof/dao-vote-burn.zk
  87. 98 0
      example/dao/proof/dao-vote-main.zk
  88. 14 0
      example/dao/proof/foo.zk
  89. 0 0
      example/dao/schema/classnamespace.py
  90. 0 0
      example/dao/schema/crypto.py
  91. 0 0
      example/dao/schema/main.py
  92. 0 0
      example/dao/schema/money.py
  93. 234 0
      example/dao/util.rs
  94. 12 0
      example/derive_macro_example.rs
  95. 108 3
      src/crypto/keypair.rs
  96. 1 1
      src/crypto/nullifier.rs

+ 1 - 1
.gitignore

@@ -11,7 +11,7 @@
 /tmp/*
 
 /cashierd
-/dao-cli
+/dao
 /daod
 /darkfid
 /darkotc

+ 4 - 1
Cargo.lock

@@ -1100,7 +1100,7 @@ dependencies = [
 ]
 
 [[package]]
-name = "dao-cli"
+name = "dao"
 version = "0.3.0"
 dependencies = [
  "async-channel",
@@ -1112,6 +1112,7 @@ dependencies = [
  "futures",
  "log",
  "num_cpus",
+ "prettytable-rs",
  "serde_json",
  "simplelog",
  "smol",
@@ -1126,11 +1127,13 @@ dependencies = [
  "async-executor",
  "async-std",
  "async-trait",
+ "bs58",
  "crypto_api_chachapoly",
  "darkfi",
  "darkfi-serial",
  "easy-parallel",
  "futures",
+ "fxhash",
  "group",
  "halo2_gadgets",
  "halo2_proofs",

+ 12 - 2
Cargo.toml

@@ -29,8 +29,8 @@ members = [
     "bin/ircd",
     "bin/ircd2",
     "bin/dnetview",
-    "bin/daod",
-    "bin/dao-cli",
+    "bin/dao/daod",
+    "bin/dao/dao-cli",
     "bin/tau/taud",
     "bin/tau/tau-cli",
     "bin/darkwiki/darkwikid",
@@ -363,6 +363,16 @@ name = "zk"
 path = "example/zk.rs"
 required-features = ["crypto"]
 
+[[example]]
+name = "dao"
+path = "example/dao/dao.rs"
+required-features = ["crypto"]
+
+[[example]]
+name = "test"
+path = "example/derive_macro_example.rs"
+required-features = ["serial"]
+
 [[example]]
 name = "lead"
 path = "example/lead.rs"

+ 2 - 3
Makefile

@@ -10,8 +10,7 @@ CARGO = cargo
 #RUSTFLAGS = -C target-cpu=native
 
 # Binaries to be built
-
-BINS = drk darkfid tau taud ircd dnetview darkotc darkwikid darkwiki
+BINS = drk darkfid tau taud ircd dnetview darkotc darkwikid darkwiki dao
 
 # Common dependencies which should force the binaries to be rebuilt
 BINDEPS = \
@@ -24,7 +23,7 @@ BINDEPS = \
 
 # ZK proofs to compile with zkas
 PROOFS = \
-	$(shell find bin/daod/proof -type f -name '*.zk') \
+	$(shell find bin/dao/daod/proof -type f -name '*.zk') \
 	$(shell find proof -type f -name '*.zk') \
 	example/simple.zk
 

+ 0 - 77
bin/dao-cli/src/main.rs

@@ -1,77 +0,0 @@
-use clap::{IntoApp, Parser, Subcommand};
-use serde_json::{json, Value};
-use url::Url;
-
-use darkfi::{
-    rpc::{client::RpcClient, jsonrpc::JsonRequest},
-    Result,
-};
-
-#[derive(Subcommand)]
-pub enum CliDaoSubCommands {
-    /// Say hello to the RPC
-    Hello {},
-}
-
-/// DAO cli
-#[derive(Parser)]
-#[clap(name = "dao")]
-#[clap(arg_required_else_help(true))]
-pub struct CliDao {
-    /// Increase verbosity
-    #[clap(short, parse(from_occurrences))]
-    pub verbose: u8,
-    #[clap(subcommand)]
-    pub command: Option<CliDaoSubCommands>,
-}
-pub struct Rpc {
-    client: RpcClient,
-}
-
-impl Rpc {
-    // --> {"jsonrpc": "2.0", "method": "say_hello", "params": [], "id": 42}
-    // <-- {"jsonrpc": "2.0", "result": "hello world", "id": 42}
-    async fn say_hello(&self) -> Result<Value> {
-        let req = JsonRequest::new("say_hello", json!([]));
-        self.client.request(req).await
-    }
-}
-
-async fn start(options: CliDao) -> Result<()> {
-    let rpc_addr = "tcp://127.0.0.1:7777";
-    let client = Rpc { client: RpcClient::new(Url::parse(rpc_addr)?).await? };
-
-    #[allow(clippy::single_match)]
-    match options.command {
-        Some(CliDaoSubCommands::Hello {}) => {
-            let reply = client.say_hello().await?;
-            println!("Server replied: {}", &reply.to_string());
-            return Ok(())
-        }
-        None => {}
-    }
-
-    Ok(())
-}
-
-#[async_std::main]
-async fn main() -> Result<()> {
-    let args = CliDao::parse();
-    let _matches = CliDao::command().get_matches();
-
-    //let config_path = if args.config.is_some() {
-    //    expand_path(&args.config.clone().unwrap())?
-    //} else {
-    //    join_config_path(&PathBuf::from("drk.toml"))?
-    //};
-
-    // Spawn config file if it's not in place already.
-    //spawn_config(&config_path, CONFIG_FILE_CONTENTS)?;
-
-    //let (lvl, conf) = log_config(matches)?;
-    //TermLogger::init(lvl, conf, TerminalMode::Mixed, ColorChoice::Auto)?;
-
-    //let config = Config::<DrkConfig>::load(config_path)?;
-
-    start(args).await
-}

+ 1 - 0
bin/dao/README.md

@@ -0,0 +1 @@
+# DAO

+ 3 - 2
bin/dao-cli/Cargo.toml → bin/dao/dao-cli/Cargo.toml

@@ -1,10 +1,10 @@
 [package]
-name = "dao-cli"
+name = "dao"
 version = "0.3.0"
 edition = "2021"
 
 [dependencies.darkfi]
-path = "../../"
+path = "../../../"
 features = ["rpc"]
 
 [dependencies]
@@ -22,6 +22,7 @@ log = "0.4.17"
 num_cpus = "1.13.1"
 simplelog = "0.12.0"
 url = "2.3.1"
+prettytable-rs = "0.9.0"
 
 # Encoding and parsing
 serde_json = "1.0.85"

+ 41 - 0
bin/dao/dao-cli/run_demo.sh

@@ -0,0 +1,41 @@
+#!/bin/bash
+cargo run create 110 110 1 2
+addr=$(cargo run addr | cut -d " " -f 4)
+addr2=$(echo $addr | cut -c 2-)
+addr3=${addr2::-1}
+echo $addr3
+
+cargo run mint 1000000 $addr3
+
+alice=$(cargo run keygen)
+alice=$(cargo run keygen | cut -d " " -f 4)
+alice2=$(echo $alice | cut -c 2-)
+alice3=${alice2::-1}
+echo $alice3
+
+bob=$(cargo run keygen)
+bob=$(cargo run keygen | cut -d " " -f 4)
+bob2=$(echo $bob | cut -c 2-)
+bob3=${bob2::-1}
+echo $bob3
+
+charlie=$(cargo run keygen)
+charlie=$(cargo run keygen | cut -d " " -f 4)
+charlie2=$(echo $charlie | cut -c 2-)
+charlie3=${charlie2::-1}
+echo $charlie3
+
+cargo run airdrop $alice3 10000
+cargo run airdrop $bob3 100000
+cargo run airdrop $charlie3 10000
+
+proposal=$(cargo run propose $alice3 $charlie3 10000 | cut -d " " -f 3)
+proposal2=$(echo $proposal | cut -c 2-)
+proposal3=${proposal2::-1}
+echo $proposal3
+
+cargo run vote $alice3 yes
+cargo run vote $bob3 yes
+cargo run vote $charlie3 no
+
+cargo run exec $proposal3

+ 241 - 0
bin/dao/dao-cli/src/main.rs

@@ -0,0 +1,241 @@
+use std::process::exit;
+
+use clap::{IntoApp, Parser, Subcommand};
+use prettytable::{format, row, Table};
+use url::Url;
+
+use darkfi::{rpc::client::RpcClient, Result};
+
+mod rpc;
+
+#[derive(Subcommand)]
+pub enum CliDaoSubCommands {
+    /// Create DAO
+    Create {
+        /// Minium number of governance tokens a user must have to propose a vote.
+        dao_proposer_limit: u64,
+
+        /// Minimum number of governance tokens staked on a proposal for it to pass.
+        dao_quorum: u64,
+
+        /// Quotient value of minimum vote ratio of yes:no votes required for a proposal to pass.
+        dao_approval_ratio_quot: u64,
+
+        /// Base value of minimum vote ratio of yes:no votes required for a proposal to pass.
+        dao_approval_ratio_base: u64,
+    },
+    /// Mint tokens
+    Addr {},
+    GetVotes {},
+    GetProposals {},
+    Mint {
+        /// Number of treasury tokens to mint.
+        token_supply: u64,
+
+        /// Public key of the DAO treasury.
+        dao_addr: String,
+    },
+    UserBalance {
+        nym: String,
+    },
+    DaoBalance {},
+    DaoBulla {},
+    Keygen {},
+    /// Airdrop tokens
+    Airdrop {
+        nym: String,
+
+        value: u64,
+    },
+    /// Propose
+    Propose {
+        sender: String,
+
+        recipient: String,
+
+        amount: u64,
+    },
+    /// Vote
+    Vote {
+        nym: String,
+
+        vote: String,
+    },
+    /// Execute
+    Exec {
+        bulla: String,
+    },
+}
+
+/// DAO cli
+#[derive(Parser)]
+#[clap(name = "dao")]
+#[clap(arg_required_else_help(true))]
+pub struct CliDao {
+    /// Increase verbosity
+    #[clap(short, parse(from_occurrences))]
+    pub verbose: u8,
+    #[clap(subcommand)]
+    pub command: Option<CliDaoSubCommands>,
+}
+
+pub struct Rpc {
+    client: RpcClient,
+}
+
+async fn start(options: CliDao) -> Result<()> {
+    let rpc_addr = "tcp://127.0.0.1:7777";
+    let client = Rpc { client: RpcClient::new(Url::parse(rpc_addr)?).await? };
+    match options.command {
+        Some(CliDaoSubCommands::Create {
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_base,
+            dao_approval_ratio_quot,
+        }) => {
+            let reply = client
+                .create(
+                    dao_proposer_limit,
+                    dao_quorum,
+                    dao_approval_ratio_quot,
+                    dao_approval_ratio_base,
+                )
+                .await?;
+            println!("Created DAO bulla: {}", &reply.to_string());
+            return Ok(())
+        }
+        Some(CliDaoSubCommands::Addr {}) => {
+            let reply = client.addr().await?;
+            println!("DAO public address: {}", &reply.to_string());
+            return Ok(())
+        }
+        Some(CliDaoSubCommands::GetVotes {}) => {
+            let reply = client.get_votes().await?;
+            println!("{}", &reply.to_string());
+            return Ok(())
+        }
+        Some(CliDaoSubCommands::GetProposals {}) => {
+            let reply = client.get_proposals().await?;
+            println!("{}", &reply.to_string());
+            return Ok(())
+        }
+        Some(CliDaoSubCommands::Mint { token_supply, dao_addr }) => {
+            let reply = client.mint(token_supply, dao_addr).await?;
+            println!("{}", &reply.as_str().unwrap().to_string());
+            return Ok(())
+        }
+        Some(CliDaoSubCommands::Keygen {}) => {
+            let reply = client.keygen().await?;
+            println!("User public key: {}", &reply.to_string());
+            return Ok(())
+        }
+        Some(CliDaoSubCommands::Airdrop { nym, value }) => {
+            let reply = client.airdrop(nym, value).await?;
+            println!("{}", &reply.as_str().unwrap().to_string());
+            return Ok(())
+        }
+        Some(CliDaoSubCommands::DaoBalance {}) => {
+            let rep = client.dao_balance().await?;
+
+            if !rep.is_object() {
+                eprintln!("Invalid balance data received from darkfid RPC endpoint.");
+                exit(1);
+            }
+
+            let mut table = Table::new();
+            table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
+            table.set_titles(row!["Token", "Balance"]);
+
+            for i in rep.as_object().unwrap().keys() {
+                if let Some(balance) = rep[i].as_u64() {
+                    table.add_row(row![i, balance]);
+                    continue
+                }
+
+                eprintln!("Found invalid balance data for key \"{}\"", i);
+            }
+
+            if table.is_empty() {
+                println!("No balances.");
+            } else {
+                println!("{}", table);
+            }
+            // println!("DAO balance: {}", &reply.to_string());
+            return Ok(())
+        }
+        Some(CliDaoSubCommands::DaoBulla {}) => {
+            let reply = client.dao_bulla().await?;
+            println!("DAO bulla: {}", &reply.to_string());
+            return Ok(())
+        }
+        Some(CliDaoSubCommands::UserBalance { nym }) => {
+            let rep = client.user_balance(nym).await?;
+
+            if !rep.is_object() {
+                eprintln!("Invalid balance data received from darkfid RPC endpoint.");
+                exit(1);
+            }
+
+            let mut table = Table::new();
+            table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
+            table.set_titles(row!["Token", "Balance"]);
+
+            for i in rep.as_object().unwrap().keys() {
+                if let Some(balance) = rep[i].as_u64() {
+                    table.add_row(row![i, balance]);
+                    continue
+                }
+
+                eprintln!("Found invalid balance data for key \"{}\"", i);
+            }
+
+            if table.is_empty() {
+                println!("No balances.");
+            } else {
+                println!("{}", table);
+            }
+            // println!("User balance: {}", &reply.to_string());
+            return Ok(())
+        }
+        Some(CliDaoSubCommands::Propose { sender, recipient, amount }) => {
+            let reply = client.propose(sender, recipient, amount).await?;
+            println!("Proposal bulla: {}", &reply.to_string());
+            return Ok(())
+        }
+        Some(CliDaoSubCommands::Vote { nym, vote }) => {
+            let reply = client.vote(nym, vote).await?;
+            println!("{}", &reply.to_string());
+            return Ok(())
+        }
+        Some(CliDaoSubCommands::Exec { bulla }) => {
+            let reply = client.exec(bulla).await?;
+            println!("{}", &reply.to_string());
+            return Ok(())
+        }
+        None => {}
+    }
+
+    Ok(())
+}
+
+#[async_std::main]
+async fn main() -> Result<()> {
+    let args = CliDao::parse();
+    let _matches = CliDao::command().get_matches();
+
+    //let config_path = if args.config.is_some() {
+    //    expand_path(&args.config.clone().unwrap())?
+    //} else {
+    //    join_config_path(&PathBuf::from("drk.toml"))?
+    //};
+
+    // Spawn config file if it's not in place already.
+    //spawn_config(&config_path, CONFIG_FILE_CONTENTS)?;
+
+    //let (lvl, conf) = log_config(matches)?;
+    //TermLogger::init(lvl, conf, TerminalMode::Mixed, ColorChoice::Auto)?;
+
+    //let config = Config::<DrkConfig>::load(config_path)?;
+
+    start(args).await
+}

+ 112 - 0
bin/dao/dao-cli/src/rpc.rs

@@ -0,0 +1,112 @@
+use serde_json::{json, Value};
+
+use darkfi::{rpc::jsonrpc::JsonRequest, Result};
+
+use crate::Rpc;
+
+impl Rpc {
+    // --> {"jsonrpc": "2.0", "method": "create", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "creating dao...", "id": 42}
+    pub async fn create(
+        &self,
+        dao_proposer_limit: u64,
+        dao_quorum: u64,
+        dao_approval_ratio_quot: u64,
+        dao_approval_ratio_base: u64,
+    ) -> Result<Value> {
+        let req = JsonRequest::new(
+            "create",
+            json!([
+                dao_proposer_limit,
+                dao_quorum,
+                dao_approval_ratio_quot,
+                dao_approval_ratio_base,
+            ]),
+        );
+        self.client.request(req).await
+    }
+
+    // --> {"jsonrpc": "2.0", "method": "mint", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "minting tokens...", "id": 42}
+    pub async fn addr(&self) -> Result<Value> {
+        let req = JsonRequest::new("get_dao_addr", json!([]));
+        self.client.request(req).await
+    }
+
+    // --> {"jsonrpc": "2.0", "method": "mint", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "minting tokens...", "id": 42}
+    pub async fn mint(&self, token_supply: u64, dao_addr: String) -> Result<Value> {
+        let req = JsonRequest::new("mint", json!([token_supply, dao_addr]));
+        self.client.request(req).await
+    }
+
+    // --> {"jsonrpc": "2.0", "method": "airdrop", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "airdropping tokens...", "id": 42}
+    pub async fn airdrop(&self, nym: String, value: u64) -> Result<Value> {
+        let req = JsonRequest::new("airdrop", json!([nym, value]));
+        self.client.request(req).await
+    }
+
+    // --> {"jsonrpc": "2.0", "method": "airdrop", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "airdropping tokens...", "id": 42}
+    pub async fn keygen(&self) -> Result<Value> {
+        let req = JsonRequest::new("keygen", json!([]));
+        self.client.request(req).await
+    }
+
+    // --> {"jsonrpc": "2.0", "method": "airdrop", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "airdropping tokens...", "id": 42}
+    pub async fn dao_balance(&self) -> Result<Value> {
+        let req = JsonRequest::new("dao_balance", json!([]));
+        self.client.request(req).await
+    }
+
+    // --> {"jsonrpc": "2.0", "method": "airdrop", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "airdropping tokens...", "id": 42}
+    pub async fn dao_bulla(&self) -> Result<Value> {
+        let req = JsonRequest::new("dao_bulla", json!([]));
+        self.client.request(req).await
+    }
+
+    // --> {"jsonrpc": "2.0", "method": "airdrop", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "airdropping tokens...", "id": 42}
+    pub async fn user_balance(&self, nym: String) -> Result<Value> {
+        let req = JsonRequest::new("user_balance", json!([nym]));
+        self.client.request(req).await
+    }
+
+    // --> {"jsonrpc": "2.0", "method": "propose", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "creating proposal...", "id": 42}
+    pub async fn propose(&self, sender: String, recipient: String, amount: u64) -> Result<Value> {
+        let req = JsonRequest::new("propose", json!([sender, recipient, amount]));
+        self.client.request(req).await
+    }
+
+    // --> {"jsonrpc": "2.0", "method": "vote", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "voting...", "id": 42}
+    pub async fn vote(&self, nym: String, vote: String) -> Result<Value> {
+        let req = JsonRequest::new("vote", json!([nym, vote]));
+        self.client.request(req).await
+    }
+
+    // --> {"jsonrpc": "2.0", "method": "exec", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "executing...", "id": 42}
+    pub async fn get_votes(&self) -> Result<Value> {
+        let req = JsonRequest::new("get_votes", json!([]));
+        self.client.request(req).await
+    }
+
+    // --> {"jsonrpc": "2.0", "method": "exec", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "executing...", "id": 42}
+    pub async fn get_proposals(&self) -> Result<Value> {
+        let req = JsonRequest::new("get_proposals", json!([]));
+        self.client.request(req).await
+    }
+
+    // --> {"jsonrpc": "2.0", "method": "exec", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "executing...", "id": 42}
+    pub async fn exec(&self, bulla: String) -> Result<Value> {
+        let req = JsonRequest::new("exec", json!([bulla]));
+        self.client.request(req).await
+    }
+}

+ 0 - 0
bin/dao/dao_config.toml


+ 4 - 2
bin/daod/Cargo.toml → bin/dao/daod/Cargo.toml

@@ -4,8 +4,8 @@ version = "0.3.0"
 edition = "2021"
 
 [dependencies]
-darkfi = {path = "../../", features = ["rpc", "crypto", "tx", "node"]}
-darkfi-serial = {path = "../../src/serial"}
+darkfi = {path = "../../../", features = ["rpc", "crypto", "tx", "node"]}
+darkfi-serial = {path = "../../../src/serial"}
 
 # Async
 smol = "1.2.5"
@@ -33,6 +33,8 @@ 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"

+ 0 - 0
bin/daod/Makefile → bin/dao/daod/Makefile


+ 16 - 0
bin/dao/daod/TODO

@@ -0,0 +1,16 @@
+priority: immediate
+
+* move schema.rs to darkfi/example
+        * rename it to dao.rs
+        * we want to be able to run it like how we run example/tx.rs
+        * cargo run --example dao
+        * and it should compile and work
+
+priority: low
+
+* the things in util.rs are not all utils
+        * some of them should be moved to other modules eventually
+
+* rename [foo]_contract to just foo
+        * contract/dao_contract/ is redundant
+        * we can just have contract/dao

+ 0 - 0
bin/daod/demo-spec.md → bin/dao/daod/demo-spec.md


+ 168 - 0
bin/dao/daod/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
+}
+

+ 0 - 0
bin/daod/proof/dao-mint.zk → bin/dao/daod/proof/dao-mint.zk


+ 0 - 0
bin/daod/proof/dao-propose-burn.zk → bin/dao/daod/proof/dao-propose-burn.zk


+ 0 - 0
bin/daod/proof/dao-propose-main.zk → bin/dao/daod/proof/dao-propose-main.zk


+ 0 - 0
bin/daod/proof/dao-vote-burn.zk → bin/dao/daod/proof/dao-vote-burn.zk


+ 0 - 0
bin/daod/proof/dao-vote-main.zk → bin/dao/daod/proof/dao-vote-main.zk


+ 0 - 0
bin/daod/proof/foo.zk → bin/dao/daod/proof/foo.zk


+ 0 - 0
bin/daod/src/dao_contract/exec/mod.rs → bin/dao/daod/src/contract/dao_contract/exec/mod.rs


+ 201 - 0
bin/dao/daod/src/contract/dao_contract/exec/validate.rs

@@ -0,0 +1,201 @@
+use std::any::{Any, TypeId};
+
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{Curve, Group},
+    pallas,
+};
+
+use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
+
+use darkfi::{
+    crypto::{coin::Coin, keypair::PublicKey, types::DrkCircuitField},
+    Error as DarkFiError,
+};
+
+use crate::{
+    contract::{dao_contract, dao_contract::CONTRACT_ID, money_contract},
+    util::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase},
+};
+
+type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum Error {
+    #[error("DarkFi error: {0}")]
+    DarkFiError(String),
+
+    #[error("InvalidNumberOfFuncCalls")]
+    InvalidNumberOfFuncCalls,
+
+    #[error("InvalidIndex")]
+    InvalidIndex,
+
+    #[error("InvalidCallData")]
+    InvalidCallData,
+
+    #[error("InvalidNumberOfOutputs")]
+    InvalidNumberOfOutputs,
+
+    #[error("InvalidOutput")]
+    InvalidOutput,
+
+    #[error("InvalidValueCommit")]
+    InvalidValueCommit,
+
+    #[error("InvalidVoteCommit")]
+    InvalidVoteCommit,
+}
+
+impl From<DarkFiError> for Error {
+    fn from(err: DarkFiError) -> Self {
+        Self::DarkFiError(err.to_string())
+    }
+}
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct CallData {
+    pub proposal: pallas::Base,
+    pub coin_0: pallas::Base,
+    pub coin_1: pallas::Base,
+    pub yes_votes_commit: pallas::Point,
+    pub all_votes_commit: pallas::Point,
+    pub input_value_commit: pallas::Point,
+}
+
+impl CallDataBase for CallData {
+    fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
+        let yes_votes_commit_coords = self.yes_votes_commit.to_affine().coordinates().unwrap();
+
+        let all_votes_commit_coords = self.all_votes_commit.to_affine().coordinates().unwrap();
+
+        let input_value_commit_coords = self.input_value_commit.to_affine().coordinates().unwrap();
+
+        vec![(
+            "dao-exec".to_string(),
+            vec![
+                self.proposal,
+                self.coin_0,
+                self.coin_1,
+                *yes_votes_commit_coords.x(),
+                *yes_votes_commit_coords.y(),
+                *all_votes_commit_coords.x(),
+                *all_votes_commit_coords.y(),
+                *input_value_commit_coords.x(),
+                *input_value_commit_coords.y(),
+                *super::FUNC_ID,
+                pallas::Base::from(0),
+                pallas::Base::from(0),
+            ],
+        )]
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn signature_public_keys(&self) -> Vec<PublicKey> {
+        vec![]
+    }
+
+    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();
+
+    // Enforce tx has correct format:
+    // 1. There should only be 2 func_call's
+    if parent_tx.func_calls.len() != 2 {
+        return Err(Error::InvalidNumberOfFuncCalls)
+    }
+
+    // 2. func_call_index == 1
+    if func_call_index != 1 {
+        return Err(Error::InvalidIndex)
+    }
+
+    // 3. First item should be a Money::transfer() calldata
+    if parent_tx.func_calls[0].func_id != *money_contract::transfer::FUNC_ID {
+        return Err(Error::InvalidCallData)
+    }
+
+    let money_transfer_call_data = parent_tx.func_calls[0].call_data.as_any();
+    let money_transfer_call_data =
+        money_transfer_call_data.downcast_ref::<money_contract::transfer::validate::CallData>();
+    let money_transfer_call_data = money_transfer_call_data.unwrap();
+    assert_eq!(
+        money_transfer_call_data.type_id(),
+        TypeId::of::<money_contract::transfer::validate::CallData>()
+    );
+
+    // 4. Money::transfer() has exactly 2 outputs
+    if money_transfer_call_data.outputs.len() != 2 {
+        return Err(Error::InvalidNumberOfOutputs)
+    }
+
+    // Checks:
+    // 1. Check both coins in Money::transfer() are equal to our coin_0, coin_1
+    if money_transfer_call_data.outputs[0].revealed.coin != Coin(call_data.coin_0) {
+        return Err(Error::InvalidOutput)
+    }
+    if money_transfer_call_data.outputs[1].revealed.coin != Coin(call_data.coin_1) {
+        return Err(Error::InvalidOutput)
+    }
+
+    // 2. sum of Money::transfer() calldata input_value_commits == our input value commit
+    let mut input_value_commits = pallas::Point::identity();
+    for input in &money_transfer_call_data.inputs {
+        input_value_commits += input.revealed.value_commit;
+    }
+    if input_value_commits != call_data.input_value_commit {
+        return Err(Error::InvalidValueCommit)
+    }
+
+    // 3. get the ProposalVote from DAO::State
+    let state = states
+        .lookup::<dao_contract::State>(*CONTRACT_ID)
+        .expect("Return type is not of type State");
+    let proposal_votes = state.proposal_votes.get(&HashableBase(call_data.proposal)).unwrap();
+
+    // 4. check yes_votes_commit is the same as in ProposalVote
+    if proposal_votes.yes_votes_commit != call_data.yes_votes_commit {
+        return Err(Error::InvalidVoteCommit)
+    }
+    // 5. also check all_votes_commit
+    if proposal_votes.all_votes_commit != call_data.all_votes_commit {
+        return Err(Error::InvalidVoteCommit)
+    }
+
+    Ok(Box::new(Update { proposal: call_data.proposal }))
+}
+
+#[derive(Clone)]
+pub struct Update {
+    pub proposal: pallas::Base,
+}
+
+impl UpdateBase for Update {
+    fn apply(self: Box<Self>, states: &mut StateRegistry) {
+        let state = states
+            .lookup_mut::<dao_contract::State>(*CONTRACT_ID)
+            .expect("Return type is not of type State");
+        state.proposal_votes.remove(&HashableBase(self.proposal)).unwrap();
+    }
+}

+ 196 - 0
bin/dao/daod/src/contract/dao_contract/exec/wallet.rs

@@ -0,0 +1,196 @@
+use halo2_proofs::circuit::Value;
+use log::debug;
+use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas};
+use rand::rngs::OsRng;
+
+use darkfi::{
+    crypto::{
+        keypair::SecretKey,
+        util::{pedersen_commitment_u64, poseidon_hash},
+        Proof,
+    },
+    zk::vm::{Witness, ZkCircuit},
+};
+
+use crate::{
+    contract::dao_contract::{
+        exec::validate::CallData, mint::wallet::DaoParams, propose::wallet::Proposal, CONTRACT_ID,
+    },
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
+};
+
+pub struct Builder {
+    pub proposal: Proposal,
+    pub dao: DaoParams,
+    pub yes_votes_value: u64,
+    pub all_votes_value: u64,
+    pub yes_votes_blind: pallas::Scalar,
+    pub all_votes_blind: pallas::Scalar,
+    pub user_serial: pallas::Base,
+    pub user_coin_blind: pallas::Base,
+    pub dao_serial: pallas::Base,
+    pub dao_coin_blind: pallas::Base,
+    pub input_value: u64,
+    pub input_value_blind: pallas::Scalar,
+    pub hook_dao_exec: pallas::Base,
+    pub signature_secret: SecretKey,
+}
+
+impl Builder {
+    pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
+        debug!(target: "dao_contract::exec::wallet::Builder", "build()");
+        let mut proofs = vec![];
+
+        let proposal_dest_coords = self.proposal.dest.0.to_affine().coordinates().unwrap();
+
+        let proposal_amount = pallas::Base::from(self.proposal.amount);
+
+        let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
+        let dao_quorum = pallas::Base::from(self.dao.quorum);
+        let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot);
+        let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
+
+        let dao_pubkey_coords = self.dao.public_key.0.to_affine().coordinates().unwrap();
+
+        let user_spend_hook = pallas::Base::from(0);
+        let user_data = pallas::Base::from(0);
+        let input_value = pallas::Base::from(self.input_value);
+        let change = input_value - proposal_amount;
+
+        let dao_bulla = poseidon_hash::<8>([
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_quot,
+            dao_approval_ratio_base,
+            self.dao.gov_token_id,
+            *dao_pubkey_coords.x(),
+            *dao_pubkey_coords.y(),
+            self.dao.bulla_blind,
+        ]);
+
+        let proposal_bulla = poseidon_hash::<8>([
+            *proposal_dest_coords.x(),
+            *proposal_dest_coords.y(),
+            proposal_amount,
+            self.proposal.serial,
+            self.proposal.token_id,
+            dao_bulla,
+            self.proposal.blind,
+            // @tmp-workaround
+            self.proposal.blind,
+        ]);
+
+        let coin_0 = poseidon_hash::<8>([
+            *proposal_dest_coords.x(),
+            *proposal_dest_coords.y(),
+            proposal_amount,
+            self.proposal.token_id,
+            self.proposal.serial,
+            user_spend_hook,
+            user_data,
+            self.proposal.blind,
+        ]);
+
+        let coin_1 = poseidon_hash::<8>([
+            *dao_pubkey_coords.x(),
+            *dao_pubkey_coords.y(),
+            change,
+            self.proposal.token_id,
+            self.dao_serial,
+            self.hook_dao_exec,
+            dao_bulla,
+            self.dao_coin_blind,
+        ]);
+
+        let yes_votes_commit = pedersen_commitment_u64(self.yes_votes_value, self.yes_votes_blind);
+        let yes_votes_commit_coords = yes_votes_commit.to_affine().coordinates().unwrap();
+
+        let all_votes_commit = pedersen_commitment_u64(self.all_votes_value, self.all_votes_blind);
+        let all_votes_commit_coords = all_votes_commit.to_affine().coordinates().unwrap();
+
+        let input_value_commit = pedersen_commitment_u64(self.input_value, self.input_value_blind);
+        let input_value_commit_coords = input_value_commit.to_affine().coordinates().unwrap();
+
+        let zk_info = zk_bins.lookup(&"dao-exec".to_string()).unwrap();
+        let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
+            info
+        } else {
+            panic!("Not binary info")
+        };
+
+        let zk_bin = zk_info.bincode.clone();
+
+        let prover_witnesses = vec![
+            //
+            // proposal params
+            Witness::Base(Value::known(*proposal_dest_coords.x())),
+            Witness::Base(Value::known(*proposal_dest_coords.y())),
+            Witness::Base(Value::known(proposal_amount)),
+            Witness::Base(Value::known(self.proposal.serial)),
+            Witness::Base(Value::known(self.proposal.token_id)),
+            Witness::Base(Value::known(self.proposal.blind)),
+            // DAO params
+            Witness::Base(Value::known(dao_proposer_limit)),
+            Witness::Base(Value::known(dao_quorum)),
+            Witness::Base(Value::known(dao_approval_ratio_quot)),
+            Witness::Base(Value::known(dao_approval_ratio_base)),
+            Witness::Base(Value::known(self.dao.gov_token_id)),
+            Witness::Base(Value::known(*dao_pubkey_coords.x())),
+            Witness::Base(Value::known(*dao_pubkey_coords.y())),
+            Witness::Base(Value::known(self.dao.bulla_blind)),
+            // votes
+            Witness::Base(Value::known(pallas::Base::from(self.yes_votes_value))),
+            Witness::Base(Value::known(pallas::Base::from(self.all_votes_value))),
+            Witness::Scalar(Value::known(self.yes_votes_blind)),
+            Witness::Scalar(Value::known(self.all_votes_blind)),
+            // outputs + inputs
+            Witness::Base(Value::known(self.user_serial)),
+            Witness::Base(Value::known(self.user_coin_blind)),
+            Witness::Base(Value::known(self.dao_serial)),
+            Witness::Base(Value::known(self.dao_coin_blind)),
+            Witness::Base(Value::known(input_value)),
+            Witness::Scalar(Value::known(self.input_value_blind)),
+            // misc
+            Witness::Base(Value::known(self.hook_dao_exec)),
+            Witness::Base(Value::known(user_spend_hook)),
+            Witness::Base(Value::known(user_data)),
+        ];
+
+        let public_inputs = vec![
+            proposal_bulla,
+            coin_0,
+            coin_1,
+            *yes_votes_commit_coords.x(),
+            *yes_votes_commit_coords.y(),
+            *all_votes_commit_coords.x(),
+            *all_votes_commit_coords.y(),
+            *input_value_commit_coords.x(),
+            *input_value_commit_coords.y(),
+            self.hook_dao_exec,
+            user_spend_hook,
+            user_data,
+        ];
+
+        let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
+        let proving_key = &zk_info.proving_key;
+        let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
+            .expect("DAO::exec() proving error!)");
+        proofs.push(input_proof);
+
+        let call_data = CallData {
+            proposal: proposal_bulla,
+            coin_0,
+            coin_1,
+            yes_votes_commit,
+            all_votes_commit,
+            input_value_commit,
+        };
+
+        FuncCall {
+            contract_id: *CONTRACT_ID,
+            func_id: *super::FUNC_ID,
+            call_data: Box::new(call_data),
+            proofs,
+        }
+    }
+}

+ 0 - 0
bin/daod/src/dao_contract/mint/mod.rs → bin/dao/daod/src/contract/dao_contract/mint/mod.rs


+ 71 - 0
bin/dao/daod/src/contract/dao_contract/mint/validate.rs

@@ -0,0 +1,71 @@
+use std::any::{Any, TypeId};
+
+use darkfi::crypto::{keypair::PublicKey, types::DrkCircuitField};
+use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
+
+use crate::{
+    contract::dao_contract::{DaoBulla, State, CONTRACT_ID},
+    util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
+};
+
+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();
+
+    Ok(Box::new(Update { dao_bulla: call_data.dao_bulla.clone() }))
+}
+
+#[derive(Clone)]
+pub struct Update {
+    pub dao_bulla: DaoBulla,
+}
+
+impl UpdateBase for Update {
+    fn apply(self: Box<Self>, states: &mut StateRegistry) {
+        // Lookup dao_contract state from registry
+        let state = states.lookup_mut::<State>(*CONTRACT_ID).unwrap();
+        // Add dao_bulla to state.dao_bullas
+        state.add_dao_bulla(self.dao_bulla);
+    }
+}
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum Error {}
+
+type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct CallData {
+    pub dao_bulla: DaoBulla,
+}
+
+impl CallDataBase for CallData {
+    fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
+        vec![("dao-mint".to_string(), vec![self.dao_bulla.0])]
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn signature_public_keys(&self) -> Vec<PublicKey> {
+        vec![]
+    }
+
+    fn encode_bytes(
+        &self,
+        mut writer: &mut dyn std::io::Write,
+    ) -> std::result::Result<usize, std::io::Error> {
+        self.encode(&mut writer)
+    }
+}

+ 5 - 6
bin/daod/src/dao_contract/mint/wallet.rs → bin/dao/daod/src/contract/dao_contract/mint/wallet.rs

@@ -1,4 +1,6 @@
-use crate::dao_contract::state::DaoBulla;
+use halo2_proofs::circuit::Value;
+use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas};
+use rand::rngs::OsRng;
 
 use darkfi::{
     crypto::{
@@ -8,13 +10,10 @@ use darkfi::{
     },
     zk::vm::{Witness, ZkCircuit},
 };
-use halo2_proofs::circuit::Value;
-use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas};
-use rand::rngs::OsRng;
 
 use crate::{
-    dao_contract::{mint::validate::CallData, CONTRACT_ID},
-    demo::{FuncCall, ZkContractInfo, ZkContractTable},
+    contract::dao_contract::{mint::validate::CallData, state::DaoBulla, CONTRACT_ID},
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
 };
 
 #[derive(Clone)]

+ 0 - 0
bin/daod/src/dao_contract/mod.rs → bin/dao/daod/src/contract/dao_contract/mod.rs


+ 0 - 0
bin/daod/src/dao_contract/propose/mod.rs → bin/dao/daod/src/contract/dao_contract/propose/mod.rs


+ 173 - 0
bin/dao/daod/src/contract/dao_contract/propose/validate.rs

@@ -0,0 +1,173 @@
+use std::any::{Any, TypeId};
+
+use log::error;
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{Curve, Group},
+    pallas,
+};
+
+use darkfi::{
+    crypto::{keypair::PublicKey, merkle_node::MerkleNode, types::DrkCircuitField},
+    Error as DarkFiError,
+};
+use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
+
+use crate::{
+    contract::{
+        dao_contract, dao_contract::State as DaoState, money_contract,
+        money_contract::state::State as MoneyState,
+    },
+    note::EncryptedNote2,
+    util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
+};
+
+// used for debugging
+// const TARGET: &str = "dao_contract::propose::validate::state_transition()";
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum Error {
+    #[error("Invalid input merkle root")]
+    InvalidInputMerkleRoot,
+
+    #[error("Invalid DAO merkle root")]
+    InvalidDaoMerkleRoot,
+
+    #[error("DarkFi error: {0}")]
+    DarkFiError(String),
+}
+type Result<T> = std::result::Result<T, Error>;
+
+impl From<DarkFiError> for Error {
+    fn from(err: DarkFiError) -> Self {
+        Self::DarkFiError(err.to_string())
+    }
+}
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct CallData {
+    pub header: Header,
+    pub inputs: Vec<Input>,
+}
+
+impl CallDataBase for CallData {
+    fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
+        let mut zk_publics = Vec::new();
+        let mut total_funds_commit = pallas::Point::identity();
+
+        assert!(self.inputs.len() > 0, "inputs length cannot be zero");
+        for input in &self.inputs {
+            total_funds_commit += input.value_commit;
+            let value_coords = input.value_commit.to_affine().coordinates().unwrap();
+
+            let sigpub_coords = input.signature_public.0.to_affine().coordinates().unwrap();
+
+            zk_publics.push((
+                "dao-propose-burn".to_string(),
+                vec![
+                    *value_coords.x(),
+                    *value_coords.y(),
+                    self.header.token_commit,
+                    input.merkle_root.0,
+                    *sigpub_coords.x(),
+                    *sigpub_coords.y(),
+                ],
+            ));
+        }
+
+        let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap();
+        zk_publics.push((
+            "dao-propose-main".to_string(),
+            vec![
+                self.header.token_commit,
+                self.header.dao_merkle_root.0,
+                self.header.proposal_bulla,
+                *total_funds_coords.x(),
+                *total_funds_coords.y(),
+            ],
+        ));
+
+        zk_publics
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn signature_public_keys(&self) -> Vec<PublicKey> {
+        let mut signature_public_keys = vec![];
+        for input in self.inputs.clone() {
+            signature_public_keys.push(input.signature_public);
+        }
+        signature_public_keys
+    }
+
+    fn encode_bytes(
+        &self,
+        mut writer: &mut dyn std::io::Write,
+    ) -> std::result::Result<usize, std::io::Error> {
+        self.encode(&mut writer)
+    }
+}
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct Header {
+    pub dao_merkle_root: MerkleNode,
+    pub token_commit: pallas::Base,
+    pub proposal_bulla: pallas::Base,
+    pub enc_note: EncryptedNote2,
+}
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct Input {
+    pub value_commit: pallas::Point,
+    pub merkle_root: MerkleNode,
+    pub signature_public: PublicKey,
+}
+
+pub fn state_transition(
+    states: &StateRegistry,
+    func_call_index: usize,
+    parent_tx: &Transaction,
+) -> Result<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();
+
+    // Check the merkle roots for the input coins are valid
+    for input in &call_data.inputs {
+        let money_state = states.lookup::<MoneyState>(*money_contract::CONTRACT_ID).unwrap();
+        if !money_state.is_valid_merkle(&input.merkle_root) {
+            return Err(Error::InvalidInputMerkleRoot)
+        }
+    }
+
+    let state = states.lookup::<DaoState>(*dao_contract::CONTRACT_ID).unwrap();
+
+    // Is the DAO bulla generated in the ZK proof valid
+    if !state.is_valid_dao_merkle(&call_data.header.dao_merkle_root) {
+        return Err(Error::InvalidDaoMerkleRoot)
+    }
+
+    // TODO: look at gov tokens avoid using already spent ones
+    // Need to spend original coin and generate 2 nullifiers?
+
+    Ok(Box::new(Update { proposal_bulla: call_data.header.proposal_bulla }))
+}
+
+#[derive(Clone)]
+pub struct Update {
+    pub proposal_bulla: pallas::Base,
+}
+
+impl UpdateBase for Update {
+    fn apply(self: Box<Self>, states: &mut StateRegistry) {
+        let state = states.lookup_mut::<DaoState>(*dao_contract::CONTRACT_ID).unwrap();
+        state.add_proposal_bulla(self.proposal_bulla);
+    }
+}

+ 272 - 0
bin/dao/daod/src/contract/dao_contract/propose/wallet.rs

@@ -0,0 +1,272 @@
+use halo2_proofs::circuit::Value;
+use incrementalmerkletree::Hashable;
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{ff::Field, Curve},
+    pallas,
+};
+use rand::rngs::OsRng;
+
+use darkfi::{
+    crypto::{
+        keypair::{PublicKey, SecretKey},
+        merkle_node::MerkleNode,
+        util::{pedersen_commitment_u64, poseidon_hash},
+        Proof,
+    },
+    zk::vm::{Witness, ZkCircuit},
+};
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+
+use crate::{
+    contract::{
+        dao_contract::{
+            mint::wallet::DaoParams,
+            propose::validate::{CallData, Header, Input},
+            CONTRACT_ID,
+        },
+        money_contract,
+    },
+    note,
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
+};
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct Note {
+    pub proposal: Proposal,
+}
+
+pub struct BuilderInput {
+    pub secret: SecretKey,
+    pub note: money_contract::transfer::wallet::Note,
+    pub leaf_position: incrementalmerkletree::Position,
+    pub merkle_path: Vec<MerkleNode>,
+    pub signature_secret: SecretKey,
+}
+
+#[derive(SerialEncodable, SerialDecodable, Clone)]
+pub struct Proposal {
+    pub dest: PublicKey,
+    pub amount: u64,
+    pub serial: pallas::Base,
+    pub token_id: pallas::Base,
+    pub blind: pallas::Base,
+}
+
+pub struct Builder {
+    pub inputs: Vec<BuilderInput>,
+    pub proposal: Proposal,
+    pub dao: DaoParams,
+    pub dao_leaf_position: incrementalmerkletree::Position,
+    pub dao_merkle_path: Vec<MerkleNode>,
+    pub dao_merkle_root: MerkleNode,
+}
+
+impl Builder {
+    pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
+        let mut proofs = vec![];
+
+        let gov_token_blind = pallas::Base::random(&mut OsRng);
+
+        let mut inputs = vec![];
+        let mut total_funds = 0;
+        let mut total_funds_blinds = pallas::Scalar::from(0);
+
+        for input in self.inputs {
+            let funds_blind = pallas::Scalar::random(&mut OsRng);
+            total_funds += input.note.value;
+            total_funds_blinds += funds_blind;
+
+            let signature_public = PublicKey::from_secret(input.signature_secret);
+
+            let zk_info = zk_bins.lookup(&"dao-propose-burn".to_string()).unwrap();
+            let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
+                info
+            } else {
+                panic!("Not binary info")
+            };
+            let zk_bin = zk_info.bincode.clone();
+
+            // Note from the previous output
+            let note = input.note;
+            let leaf_pos: u64 = input.leaf_position.into();
+
+            let prover_witnesses = vec![
+                Witness::Base(Value::known(input.secret.0)),
+                Witness::Base(Value::known(note.serial)),
+                Witness::Base(Value::known(pallas::Base::from(0))),
+                Witness::Base(Value::known(pallas::Base::from(0))),
+                Witness::Base(Value::known(pallas::Base::from(note.value))),
+                Witness::Base(Value::known(note.token_id)),
+                Witness::Base(Value::known(note.coin_blind)),
+                Witness::Scalar(Value::known(funds_blind)),
+                Witness::Base(Value::known(gov_token_blind)),
+                Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())),
+                Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
+                Witness::Base(Value::known(input.signature_secret.0)),
+            ];
+
+            let public_key = PublicKey::from_secret(input.secret);
+            let coords = public_key.0.to_affine().coordinates().unwrap();
+
+            let coin = poseidon_hash::<8>([
+                *coords.x(),
+                *coords.y(),
+                pallas::Base::from(note.value),
+                note.token_id,
+                note.serial,
+                pallas::Base::from(0),
+                pallas::Base::from(0),
+                note.coin_blind,
+            ]);
+
+            let merkle_root = {
+                let position: u64 = input.leaf_position.into();
+                let mut current = MerkleNode(coin);
+                for (level, sibling) in input.merkle_path.iter().enumerate() {
+                    let level = level as u8;
+                    current = if position & (1 << level) == 0 {
+                        MerkleNode::combine(level.into(), &current, sibling)
+                    } else {
+                        MerkleNode::combine(level.into(), sibling, &current)
+                    };
+                }
+                current
+            };
+
+            let token_commit = poseidon_hash::<2>([note.token_id, gov_token_blind]);
+            assert_eq!(self.dao.gov_token_id, note.token_id);
+
+            let value_commit = pedersen_commitment_u64(note.value, funds_blind);
+            let value_coords = value_commit.to_affine().coordinates().unwrap();
+
+            let sigpub_coords = signature_public.0.to_affine().coordinates().unwrap();
+
+            let public_inputs = vec![
+                *value_coords.x(),
+                *value_coords.y(),
+                token_commit,
+                merkle_root.0,
+                *sigpub_coords.x(),
+                *sigpub_coords.y(),
+            ];
+            let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
+
+            let proving_key = &zk_info.proving_key;
+            let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
+                .expect("DAO::propose() proving error!");
+            proofs.push(input_proof);
+
+            let input = Input { value_commit, merkle_root, signature_public };
+            inputs.push(input);
+        }
+
+        let total_funds_commit = pedersen_commitment_u64(total_funds, total_funds_blinds);
+        let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap();
+        let total_funds = pallas::Base::from(total_funds);
+
+        let token_commit = poseidon_hash::<2>([self.dao.gov_token_id, gov_token_blind]);
+
+        let proposal_dest_coords = self.proposal.dest.0.to_affine().coordinates().unwrap();
+        let proposal_dest_x = *proposal_dest_coords.x();
+        let proposal_dest_y = *proposal_dest_coords.y();
+
+        let proposal_amount = pallas::Base::from(self.proposal.amount);
+
+        let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
+        let dao_quorum = pallas::Base::from(self.dao.quorum);
+        let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot);
+        let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
+
+        let dao_pubkey_coords = self.dao.public_key.0.to_affine().coordinates().unwrap();
+
+        let dao_bulla = poseidon_hash::<8>([
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_quot,
+            dao_approval_ratio_base,
+            self.dao.gov_token_id,
+            *dao_pubkey_coords.x(),
+            *dao_pubkey_coords.y(),
+            self.dao.bulla_blind,
+        ]);
+
+        let dao_leaf_position: u64 = self.dao_leaf_position.into();
+
+        let proposal_bulla = poseidon_hash::<8>([
+            proposal_dest_x,
+            proposal_dest_y,
+            proposal_amount,
+            self.proposal.serial,
+            self.proposal.token_id,
+            dao_bulla,
+            self.proposal.blind,
+            // @tmp-workaround
+            self.proposal.blind,
+        ]);
+
+        let zk_info = zk_bins.lookup(&"dao-propose-main".to_string()).unwrap();
+        let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
+            info
+        } else {
+            panic!("Not binary info")
+        };
+        let zk_bin = zk_info.bincode.clone();
+        let prover_witnesses = vec![
+            // Proposers total number of gov tokens
+            Witness::Base(Value::known(total_funds)),
+            Witness::Scalar(Value::known(total_funds_blinds)),
+            // Used for blinding exported gov token ID
+            Witness::Base(Value::known(gov_token_blind)),
+            // proposal params
+            Witness::Base(Value::known(proposal_dest_x)),
+            Witness::Base(Value::known(proposal_dest_y)),
+            Witness::Base(Value::known(proposal_amount)),
+            Witness::Base(Value::known(self.proposal.serial)),
+            Witness::Base(Value::known(self.proposal.token_id)),
+            Witness::Base(Value::known(self.proposal.blind)),
+            // DAO params
+            Witness::Base(Value::known(dao_proposer_limit)),
+            Witness::Base(Value::known(dao_quorum)),
+            Witness::Base(Value::known(dao_approval_ratio_quot)),
+            Witness::Base(Value::known(dao_approval_ratio_base)),
+            Witness::Base(Value::known(self.dao.gov_token_id)),
+            Witness::Base(Value::known(*dao_pubkey_coords.x())),
+            Witness::Base(Value::known(*dao_pubkey_coords.y())),
+            Witness::Base(Value::known(self.dao.bulla_blind)),
+            Witness::Uint32(Value::known(dao_leaf_position.try_into().unwrap())),
+            Witness::MerklePath(Value::known(self.dao_merkle_path.try_into().unwrap())),
+        ];
+        let public_inputs = vec![
+            token_commit,
+            self.dao_merkle_root.0,
+            proposal_bulla,
+            *total_funds_coords.x(),
+            *total_funds_coords.y(),
+        ];
+        let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
+
+        let proving_key = &zk_info.proving_key;
+        let main_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
+            .expect("DAO::propose() proving error!");
+        proofs.push(main_proof);
+
+        let note = Note { proposal: self.proposal };
+        let enc_note = note::encrypt(&note, &self.dao.public_key).unwrap();
+        let header = Header {
+            dao_merkle_root: self.dao_merkle_root,
+            proposal_bulla,
+            token_commit,
+            enc_note,
+        };
+
+        let call_data = CallData { header, inputs };
+
+        FuncCall {
+            contract_id: *CONTRACT_ID,
+            func_id: *super::FUNC_ID,
+            call_data: Box::new(call_data),
+            proofs,
+        }
+    }
+}

+ 98 - 0
bin/dao/daod/src/contract/dao_contract/state.rs

@@ -0,0 +1,98 @@
+use std::{any::Any, collections::HashMap};
+
+use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
+use pasta_curves::{group::Group, pallas};
+
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+
+use darkfi::crypto::{constants::MERKLE_DEPTH, merkle_node::MerkleNode, nullifier::Nullifier};
+
+use crate::util::HashableBase;
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct DaoBulla(pub pallas::Base);
+
+type MerkleTree = BridgeTree<MerkleNode, MERKLE_DEPTH>;
+
+pub struct ProposalVotes {
+    // TODO: might be more logical to have 'yes_votes_commit' and 'no_votes_commit'
+    /// Weighted vote commit
+    pub yes_votes_commit: pallas::Point,
+    /// All value staked in the vote
+    pub all_votes_commit: pallas::Point,
+    /// Vote nullifiers
+    pub vote_nulls: Vec<Nullifier>,
+}
+
+impl ProposalVotes {
+    pub fn nullifier_exists(&self, nullifier: &Nullifier) -> bool {
+        self.vote_nulls.iter().any(|n| n == nullifier)
+    }
+}
+
+/// This DAO state is for all DAOs on the network. There should only be a single instance.
+pub struct State {
+    dao_bullas: Vec<DaoBulla>,
+    pub dao_tree: MerkleTree,
+    pub dao_roots: Vec<MerkleNode>,
+
+    //proposal_bullas: Vec<pallas::Base>,
+    pub proposal_tree: MerkleTree,
+    pub proposal_roots: Vec<MerkleNode>,
+    pub proposal_votes: HashMap<HashableBase, ProposalVotes>,
+}
+
+impl State {
+    pub fn new() -> Box<dyn Any + Send> {
+        Box::new(Self {
+            dao_bullas: Vec::new(),
+            dao_tree: MerkleTree::new(100),
+            dao_roots: Vec::new(),
+            //proposal_bullas: Vec::new(),
+            proposal_tree: MerkleTree::new(100),
+            proposal_roots: Vec::new(),
+            proposal_votes: HashMap::new(),
+        })
+    }
+
+    pub fn add_dao_bulla(&mut self, bulla: DaoBulla) {
+        let node = MerkleNode(bulla.0);
+        self.dao_bullas.push(bulla);
+        self.dao_tree.append(&node);
+        self.dao_roots.push(self.dao_tree.root(0).unwrap());
+    }
+
+    pub fn add_proposal_bulla(&mut self, bulla: pallas::Base) {
+        let node = MerkleNode(bulla);
+        //self.proposal_bullas.push(bulla);
+        self.proposal_tree.append(&node);
+        self.proposal_roots.push(self.proposal_tree.root(0).unwrap());
+        self.proposal_votes.insert(
+            HashableBase(bulla),
+            ProposalVotes {
+                yes_votes_commit: pallas::Point::identity(),
+                all_votes_commit: pallas::Point::identity(),
+                vote_nulls: Vec::new(),
+            },
+        );
+    }
+
+    pub fn lookup_proposal_votes(&self, proposal_bulla: pallas::Base) -> Option<&ProposalVotes> {
+        self.proposal_votes.get(&HashableBase(proposal_bulla))
+    }
+    pub fn lookup_proposal_votes_mut(
+        &mut self,
+        proposal_bulla: pallas::Base,
+    ) -> Option<&mut ProposalVotes> {
+        self.proposal_votes.get_mut(&HashableBase(proposal_bulla))
+    }
+
+    pub fn is_valid_dao_merkle(&self, root: &MerkleNode) -> bool {
+        self.dao_roots.iter().any(|m| m == root)
+    }
+
+    // TODO: This never gets called.
+    pub fn _is_valid_proposal_merkle(&self, root: &MerkleNode) -> bool {
+        self.proposal_roots.iter().any(|m| m == root)
+    }
+}

+ 0 - 0
bin/daod/src/dao_contract/vote/mod.rs → bin/dao/daod/src/contract/dao_contract/vote/mod.rs


+ 208 - 0
bin/dao/daod/src/contract/dao_contract/vote/validate.rs

@@ -0,0 +1,208 @@
+use std::any::{Any, TypeId};
+
+use log::error;
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{Curve, Group},
+    pallas,
+};
+
+use darkfi::{
+    crypto::{
+        keypair::PublicKey, merkle_node::MerkleNode, nullifier::Nullifier, types::DrkCircuitField,
+    },
+    Error as DarkFiError,
+};
+use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
+
+use crate::{
+    contract::{
+        dao_contract, dao_contract::State as DaoState, money_contract,
+        money_contract::state::State as MoneyState,
+    },
+    note::EncryptedNote2,
+    util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
+};
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum Error {
+    #[error("Invalid proposal")]
+    InvalidProposal,
+
+    #[error("Voting with already spent coinage")]
+    SpentCoin,
+
+    #[error("Double voting")]
+    DoubleVote,
+
+    #[error("Invalid input merkle root")]
+    InvalidInputMerkleRoot,
+
+    #[error("DarkFi error: {0}")]
+    DarkFiError(String),
+}
+type Result<T> = std::result::Result<T, Error>;
+
+impl From<DarkFiError> for Error {
+    fn from(err: DarkFiError) -> Self {
+        Self::DarkFiError(err.to_string())
+    }
+}
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct CallData {
+    pub header: Header,
+    pub inputs: Vec<Input>,
+}
+
+impl CallDataBase for CallData {
+    fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
+        let mut zk_publics = Vec::new();
+        let mut all_votes_commit = pallas::Point::identity();
+
+        assert!(self.inputs.len() > 0, "inputs length cannot be zero");
+        for input in &self.inputs {
+            all_votes_commit += input.vote_commit;
+            let value_coords = input.vote_commit.to_affine().coordinates().unwrap();
+
+            let sigpub_coords = input.signature_public.0.to_affine().coordinates().unwrap();
+
+            zk_publics.push((
+                "dao-vote-burn".to_string(),
+                vec![
+                    input.nullifier.0,
+                    *value_coords.x(),
+                    *value_coords.y(),
+                    self.header.token_commit,
+                    input.merkle_root.0,
+                    *sigpub_coords.x(),
+                    *sigpub_coords.y(),
+                ],
+            ));
+        }
+
+        let yes_vote_commit_coords = self.header.yes_vote_commit.to_affine().coordinates().unwrap();
+
+        let vote_commit_coords = all_votes_commit.to_affine().coordinates().unwrap();
+
+        zk_publics.push((
+            "dao-vote-main".to_string(),
+            vec![
+                self.header.token_commit,
+                self.header.proposal_bulla,
+                *yes_vote_commit_coords.x(),
+                *yes_vote_commit_coords.y(),
+                *vote_commit_coords.x(),
+                *vote_commit_coords.y(),
+            ],
+        ));
+
+        zk_publics
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn signature_public_keys(&self) -> Vec<PublicKey> {
+        let mut signature_public_keys = vec![];
+        for input in self.inputs.clone() {
+            signature_public_keys.push(input.signature_public);
+        }
+        signature_public_keys
+    }
+
+    fn encode_bytes(
+        &self,
+        mut writer: &mut dyn std::io::Write,
+    ) -> std::result::Result<usize, std::io::Error> {
+        self.encode(&mut writer)
+    }
+}
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct Header {
+    pub token_commit: pallas::Base,
+    pub proposal_bulla: pallas::Base,
+    pub yes_vote_commit: pallas::Point,
+    pub enc_note: EncryptedNote2,
+}
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct Input {
+    pub nullifier: Nullifier,
+    pub vote_commit: pallas::Point,
+    pub merkle_root: MerkleNode,
+    pub signature_public: PublicKey,
+}
+
+pub fn state_transition(
+    states: &StateRegistry,
+    func_call_index: usize,
+    parent_tx: &Transaction,
+) -> Result<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 dao_state = states.lookup::<DaoState>(*dao_contract::CONTRACT_ID).unwrap();
+
+    // Check proposal_bulla exists
+    let votes_info = dao_state.lookup_proposal_votes(call_data.header.proposal_bulla);
+    if votes_info.is_none() {
+        return Err(Error::InvalidProposal)
+    }
+    let votes_info = votes_info.unwrap();
+
+    // Check the merkle roots for the input coins are valid
+    let mut vote_nulls = Vec::new();
+    let mut all_vote_commit = pallas::Point::identity();
+    for input in &call_data.inputs {
+        let money_state = states.lookup::<MoneyState>(*money_contract::CONTRACT_ID).unwrap();
+        if !money_state.is_valid_merkle(&input.merkle_root) {
+            return Err(Error::InvalidInputMerkleRoot)
+        }
+
+        if money_state.nullifier_exists(&input.nullifier) {
+            return Err(Error::SpentCoin)
+        }
+
+        if votes_info.nullifier_exists(&input.nullifier) {
+            return Err(Error::DoubleVote)
+        }
+
+        all_vote_commit += input.vote_commit;
+
+        vote_nulls.push(input.nullifier);
+    }
+
+    Ok(Box::new(Update {
+        proposal_bulla: call_data.header.proposal_bulla,
+        vote_nulls,
+        yes_vote_commit: call_data.header.yes_vote_commit,
+        all_vote_commit,
+    }))
+}
+
+#[derive(Clone)]
+pub struct Update {
+    proposal_bulla: pallas::Base,
+    vote_nulls: Vec<Nullifier>,
+    pub yes_vote_commit: pallas::Point,
+    pub all_vote_commit: pallas::Point,
+}
+
+impl UpdateBase for Update {
+    fn apply(mut self: Box<Self>, states: &mut StateRegistry) {
+        let state = states.lookup_mut::<DaoState>(*dao_contract::CONTRACT_ID).unwrap();
+        let votes_info = state.lookup_proposal_votes_mut(self.proposal_bulla).unwrap();
+        votes_info.yes_votes_commit += self.yes_vote_commit;
+        votes_info.all_votes_commit += self.all_vote_commit;
+        votes_info.vote_nulls.append(&mut self.vote_nulls);
+    }
+}

+ 292 - 0
bin/dao/daod/src/contract/dao_contract/vote/wallet.rs

@@ -0,0 +1,292 @@
+use halo2_proofs::circuit::Value;
+use incrementalmerkletree::Hashable;
+use log::debug;
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{ff::Field, Curve},
+    pallas,
+};
+use rand::rngs::OsRng;
+
+use darkfi::{
+    crypto::{
+        keypair::{Keypair, PublicKey, SecretKey},
+        merkle_node::MerkleNode,
+        nullifier::Nullifier,
+        util::{pedersen_commitment_u64, poseidon_hash},
+        Proof,
+    },
+    zk::vm::{Witness, ZkCircuit},
+};
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+
+use crate::{
+    contract::{
+        dao_contract::{
+            mint::wallet::DaoParams,
+            propose::wallet::Proposal,
+            vote::validate::{CallData, Header, Input},
+            CONTRACT_ID,
+        },
+        money_contract,
+    },
+    note,
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
+};
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct Note {
+    pub vote: Vote,
+    pub vote_value: u64,
+    pub vote_value_blind: pallas::Scalar,
+}
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct Vote {
+    pub vote_option: bool,
+    pub vote_option_blind: pallas::Scalar,
+}
+
+pub struct BuilderInput {
+    pub secret: SecretKey,
+    pub note: money_contract::transfer::wallet::Note,
+    pub leaf_position: incrementalmerkletree::Position,
+    pub merkle_path: Vec<MerkleNode>,
+    pub signature_secret: SecretKey,
+}
+
+// TODO: should be token locking voting?
+// Inside ZKproof, check proposal is correct.
+pub struct Builder {
+    pub inputs: Vec<BuilderInput>,
+    pub vote: Vote,
+    pub vote_keypair: Keypair,
+    pub proposal: Proposal,
+    pub dao: DaoParams,
+}
+
+impl Builder {
+    pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
+        debug!(target: "dao_contract::vote::wallet::Builder", "build()");
+        let mut proofs = vec![];
+
+        let gov_token_blind = pallas::Base::random(&mut OsRng);
+
+        let mut inputs = vec![];
+        let mut vote_value = 0;
+        let mut vote_value_blind = pallas::Scalar::from(0);
+
+        for input in self.inputs {
+            let value_blind = pallas::Scalar::random(&mut OsRng);
+
+            vote_value += input.note.value;
+            vote_value_blind += value_blind;
+
+            let signature_public = PublicKey::from_secret(input.signature_secret);
+
+            let zk_info = zk_bins.lookup(&"dao-vote-burn".to_string()).unwrap();
+
+            let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
+                info
+            } else {
+                panic!("Not binary info")
+            };
+            let zk_bin = zk_info.bincode.clone();
+
+            // Note from the previous output
+            let note = input.note;
+            let leaf_pos: u64 = input.leaf_position.into();
+
+            let prover_witnesses = vec![
+                Witness::Base(Value::known(input.secret.0)),
+                Witness::Base(Value::known(note.serial)),
+                Witness::Base(Value::known(pallas::Base::from(0))),
+                Witness::Base(Value::known(pallas::Base::from(0))),
+                Witness::Base(Value::known(pallas::Base::from(note.value))),
+                Witness::Base(Value::known(note.token_id)),
+                Witness::Base(Value::known(note.coin_blind)),
+                Witness::Scalar(Value::known(vote_value_blind)),
+                Witness::Base(Value::known(gov_token_blind)),
+                Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())),
+                Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
+                Witness::Base(Value::known(input.signature_secret.0)),
+            ];
+
+            let public_key = PublicKey::from_secret(input.secret);
+            let coords = public_key.0.to_affine().coordinates().unwrap();
+
+            let coin = poseidon_hash::<8>([
+                *coords.x(),
+                *coords.y(),
+                pallas::Base::from(note.value),
+                note.token_id,
+                note.serial,
+                pallas::Base::from(0),
+                pallas::Base::from(0),
+                note.coin_blind,
+            ]);
+
+            let merkle_root = {
+                let position: u64 = input.leaf_position.into();
+                let mut current = MerkleNode(coin);
+                for (level, sibling) in input.merkle_path.iter().enumerate() {
+                    let level = level as u8;
+                    current = if position & (1 << level) == 0 {
+                        MerkleNode::combine(level.into(), &current, sibling)
+                    } else {
+                        MerkleNode::combine(level.into(), sibling, &current)
+                    };
+                }
+                current
+            };
+
+            let token_commit = poseidon_hash::<2>([note.token_id, gov_token_blind]);
+            assert_eq!(self.dao.gov_token_id, note.token_id);
+
+            let nullifier = poseidon_hash::<2>([input.secret.0, note.serial]);
+
+            let vote_commit = pedersen_commitment_u64(note.value, vote_value_blind);
+            let vote_commit_coords = vote_commit.to_affine().coordinates().unwrap();
+
+            let sigpub_coords = signature_public.0.to_affine().coordinates().unwrap();
+
+            let public_inputs = vec![
+                nullifier,
+                *vote_commit_coords.x(),
+                *vote_commit_coords.y(),
+                token_commit,
+                merkle_root.0,
+                *sigpub_coords.x(),
+                *sigpub_coords.y(),
+            ];
+
+            let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
+            let proving_key = &zk_info.proving_key;
+            debug!(target: "dao_contract::vote::wallet::Builder", "input_proof Proof::create()");
+            let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
+                .expect("DAO::vote() proving error!");
+            proofs.push(input_proof);
+
+            let input = Input {
+                nullifier: Nullifier(nullifier),
+                vote_commit,
+                merkle_root,
+                signature_public,
+            };
+            inputs.push(input);
+        }
+
+        let token_commit = poseidon_hash::<2>([self.dao.gov_token_id, gov_token_blind]);
+
+        let proposal_dest_coords = self.proposal.dest.0.to_affine().coordinates().unwrap();
+
+        let proposal_amount = pallas::Base::from(self.proposal.amount);
+
+        let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
+        let dao_quorum = pallas::Base::from(self.dao.quorum);
+        let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot);
+        let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
+
+        let dao_pubkey_coords = self.dao.public_key.0.to_affine().coordinates().unwrap();
+
+        let dao_bulla = poseidon_hash::<8>([
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_quot,
+            dao_approval_ratio_base,
+            self.dao.gov_token_id,
+            *dao_pubkey_coords.x(),
+            *dao_pubkey_coords.y(),
+            self.dao.bulla_blind,
+        ]);
+
+        let proposal_bulla = poseidon_hash::<8>([
+            *proposal_dest_coords.x(),
+            *proposal_dest_coords.y(),
+            proposal_amount,
+            self.proposal.serial,
+            self.proposal.token_id,
+            dao_bulla,
+            self.proposal.blind,
+            // @tmp-workaround
+            self.proposal.blind,
+        ]);
+
+        let vote_option = self.vote.vote_option as u64;
+        assert!(vote_option == 0 || vote_option == 1);
+
+        let yes_vote_commit =
+            pedersen_commitment_u64(vote_option * vote_value, self.vote.vote_option_blind);
+        let yes_vote_commit_coords = yes_vote_commit.to_affine().coordinates().unwrap();
+
+        let all_vote_commit = pedersen_commitment_u64(vote_value, vote_value_blind);
+        let all_vote_commit_coords = all_vote_commit.to_affine().coordinates().unwrap();
+
+        let zk_info = zk_bins.lookup(&"dao-vote-main".to_string()).unwrap();
+        let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
+            info
+        } else {
+            panic!("Not binary info")
+        };
+        let zk_bin = zk_info.bincode.clone();
+
+        let prover_witnesses = vec![
+            // proposal params
+            Witness::Base(Value::known(*proposal_dest_coords.x())),
+            Witness::Base(Value::known(*proposal_dest_coords.y())),
+            Witness::Base(Value::known(proposal_amount)),
+            Witness::Base(Value::known(self.proposal.serial)),
+            Witness::Base(Value::known(self.proposal.token_id)),
+            Witness::Base(Value::known(self.proposal.blind)),
+            // DAO params
+            Witness::Base(Value::known(dao_proposer_limit)),
+            Witness::Base(Value::known(dao_quorum)),
+            Witness::Base(Value::known(dao_approval_ratio_quot)),
+            Witness::Base(Value::known(dao_approval_ratio_base)),
+            Witness::Base(Value::known(self.dao.gov_token_id)),
+            Witness::Base(Value::known(*dao_pubkey_coords.x())),
+            Witness::Base(Value::known(*dao_pubkey_coords.y())),
+            Witness::Base(Value::known(self.dao.bulla_blind)),
+            // Vote
+            Witness::Base(Value::known(pallas::Base::from(vote_option))),
+            Witness::Scalar(Value::known(self.vote.vote_option_blind)),
+            // Total number of gov tokens allocated
+            Witness::Base(Value::known(pallas::Base::from(vote_value))),
+            Witness::Scalar(Value::known(vote_value_blind)),
+            // gov token
+            Witness::Base(Value::known(gov_token_blind)),
+        ];
+
+        let public_inputs = vec![
+            token_commit,
+            proposal_bulla,
+            // this should be a value commit??
+            *yes_vote_commit_coords.x(),
+            *yes_vote_commit_coords.y(),
+            *all_vote_commit_coords.x(),
+            *all_vote_commit_coords.y(),
+        ];
+
+        let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
+
+        let proving_key = &zk_info.proving_key;
+        debug!(target: "dao_contract::vote::wallet::Builder", "main_proof = Proof::create()");
+        let main_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
+            .expect("DAO::vote() proving error!");
+        proofs.push(main_proof);
+
+        let note = Note { vote: self.vote, vote_value, vote_value_blind };
+        let enc_note = note::encrypt(&note, &self.vote_keypair.public).unwrap();
+
+        let header = Header { token_commit, proposal_bulla, yes_vote_commit, enc_note };
+
+        let call_data = CallData { header, inputs };
+
+        FuncCall {
+            contract_id: *CONTRACT_ID,
+            func_id: *super::FUNC_ID,
+            call_data: Box::new(call_data),
+            proofs,
+        }
+    }
+}

+ 0 - 0
bin/daod/src/example_contract/foo/mod.rs → bin/dao/daod/src/contract/example_contract/foo/mod.rs


+ 93 - 0
bin/dao/daod/src/contract/example_contract/foo/validate.rs

@@ -0,0 +1,93 @@
+use std::any::Any;
+
+use pasta_curves::pallas;
+
+use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
+
+use darkfi::{
+    crypto::{keypair::PublicKey, types::DrkCircuitField},
+    Error as DarkFiError,
+};
+
+use crate::{
+    contract::example_contract::{state::State, CONTRACT_ID},
+    util::{CallDataBase, StateRegistry, 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);
+    }
+}

+ 74 - 0
bin/dao/daod/src/contract/example_contract/foo/wallet.rs

@@ -0,0 +1,74 @@
+// 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_contract::{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,
+//         }
+//     }
+// }

+ 0 - 0
bin/daod/src/example_contract/mod.rs → bin/dao/daod/src/contract/example_contract/mod.rs


+ 21 - 0
bin/dao/daod/src/contract/example_contract/state.rs

@@ -0,0 +1,21 @@
+// 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> {
+    //     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)
+    // }
+}

+ 3 - 0
bin/dao/daod/src/contract/mod.rs

@@ -0,0 +1,3 @@
+pub mod dao_contract;
+pub mod example_contract;
+pub mod money_contract;

+ 0 - 0
bin/daod/src/money_contract/mod.rs → bin/dao/daod/src/contract/money_contract/mod.rs


+ 116 - 0
bin/dao/daod/src/contract/money_contract/state.rs

@@ -0,0 +1,116 @@
+use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
+
+use darkfi::crypto::{
+    coin::Coin,
+    constants::MERKLE_DEPTH,
+    keypair::{PublicKey, SecretKey},
+    merkle_node::MerkleNode,
+    nullifier::Nullifier,
+};
+
+use super::transfer;
+use crate::note::EncryptedNote2;
+
+type MerkleTree = BridgeTree<MerkleNode, MERKLE_DEPTH>;
+
+pub struct OwnCoin {
+    pub coin: Coin,
+    pub note: 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>)>,
+}
+
+impl WalletCache {
+    pub fn new() -> Self {
+        Self { cache: Vec::new() }
+    }
+
+    /// 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,
+        tree: &mut MerkleTree,
+    ) {
+        // 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 = tree.witness().expect("coin should be in tree");
+                own_coins.push(OwnCoin { coin, note, leaf_position });
+            }
+        }
+    }
+}
+
+/// 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,
+
+    pub wallet_cache: WalletCache,
+}
+
+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,
+            wallet_cache: WalletCache::new(),
+        })
+    }
+
+    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)
+    }
+}

+ 0 - 0
bin/daod/src/money_contract/transfer/mod.rs → bin/dao/daod/src/contract/money_contract/transfer/mod.rs


+ 375 - 0
bin/dao/daod/src/contract/money_contract/transfer/validate.rs

@@ -0,0 +1,375 @@
+use std::any::{Any, TypeId};
+
+use incrementalmerkletree::Tree;
+use log::{debug, error};
+use pasta_curves::{group::Group, pallas};
+
+use darkfi::{
+    crypto::{
+        coin::Coin,
+        keypair::PublicKey,
+        merkle_node::MerkleNode,
+        nullifier::Nullifier,
+        types::{DrkCircuitField, DrkTokenId, DrkValueBlind, DrkValueCommit},
+        util::{pedersen_commitment_base, pedersen_commitment_u64},
+        BurnRevealedValues, MintRevealedValues,
+    },
+    Error as DarkFiError,
+};
+use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
+
+use crate::{
+    contract::{
+        dao_contract,
+        money_contract::{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, enc_note) in self.coins.into_iter().zip(self.enc_notes.into_iter()) {
+            // Add the new coins to the Merkle tree
+            let node = MerkleNode(coin.0);
+            state.tree.append(&node);
+
+            // Keep track of all Merkle roots that have existed
+            state.merkle_roots.push(state.tree.root(0).unwrap());
+
+            state.wallet_cache.try_decrypt_note(coin, enc_note, &mut state.tree);
+        }
+    }
+}
+
+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_contract::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>;

+ 220 - 0
bin/dao/daod/src/contract/money_contract/transfer/wallet.rs

@@ -0,0 +1,220 @@
+use pasta_curves::group::ff::Field;
+use rand::rngs::OsRng;
+
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+
+use darkfi::{
+    crypto::{
+        burn_proof::create_burn_proof,
+        keypair::{PublicKey, SecretKey},
+        merkle_node::MerkleNode,
+        mint_proof::create_mint_proof,
+        types::{
+            DrkCoinBlind, DrkSerial, DrkSpendHook, DrkTokenId, DrkUserData, DrkUserDataBlind,
+            DrkValueBlind,
+        },
+    },
+    Result,
+};
+
+use crate::{
+    contract::money_contract::{
+        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,
+        })
+    }
+}

+ 56 - 0
bin/dao/daod/src/error.rs

@@ -0,0 +1,56 @@
+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()
+}

+ 1156 - 0
bin/dao/daod/src/main.rs

@@ -0,0 +1,1156 @@
+use std::{sync::Arc, time::Instant};
+
+use fxhash::FxHashMap;
+use incrementalmerkletree::{Position, Tree};
+use log::debug;
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{ff::Field, Curve, Group},
+    pallas,
+};
+use rand::rngs::OsRng;
+use simplelog::{ColorChoice, LevelFilter, TermLogger, TerminalMode};
+use url::Url;
+
+use darkfi::{
+    crypto::{
+        keypair::{Keypair, PublicKey, SecretKey},
+        merkle_node::MerkleNode,
+        proof::{ProvingKey, VerifyingKey},
+        types::{DrkSpendHook, DrkUserData, DrkValue},
+        util::{pedersen_commitment_u64, poseidon_hash},
+    },
+    rpc::server::listen_and_serve,
+    zk::circuit::{BurnContract, MintContract},
+    zkas::ZkBinary,
+    Result,
+};
+
+mod contract;
+mod error;
+mod note;
+mod rpc;
+mod util;
+
+use crate::{
+    contract::{
+        dao_contract::{self, mint::wallet::DaoParams, propose::wallet::Proposal, DaoBulla},
+        money_contract::{self, state::OwnCoin},
+    },
+    error::{DaoError, DaoResult},
+    rpc::JsonRpcInterface,
+    util::{sign, StateRegistry, Transaction, ZkContractTable, DRK_ID, GOV_ID},
+};
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+//// dao-demo 0.1
+////
+//// This is a very early prototype intended to demonstrate the underlying
+//// crypto of fully anonymous DAOs. DAO participants can own and operate
+//// a collective treasury according to rules set by the DAO. Communities
+//// can coordinate financially in the cover of a protective darkness,
+//// free from surveillance and persecution.
+////
+//// The following information is completely hidden:
+////
+//// * DAO treasury
+//// * DAO parameters
+//// * DAO participants
+//// * Proposals
+//// * Votes
+////
+//// The DAO enables participants to make proposals, cast votes, and spend
+//// money from the DAO treasury if a proposal passes. The basic operation
+//// involves transferring money from a treasury to a public key specified
+//// in a Proposal. This operation can only happen if several conditions are
+//// met.
+////
+//// At its basis, the DAO is a treasury that is owned by everyone who holds
+//// the DAO governance token. These constraints, also known as DAO parameters,
+//// are configured by DAO participants and enforced by ZK cryptography.
+////
+//// In this demo, the constraints are:
+////
+//// 1. DAO quorum: the number of governance tokens that must be allocated
+////    to a proposal in order for a proposal to pass.
+//// 2. Proposer limit: the number of governance tokens required to make a
+////    proposal.
+//// 3. DAO approval ratio: The ratio of yes/ no votes required for a
+////    proposal to pass.
+////
+//// In addition, DAO participants must prove ownership of governance tokens
+//// order to vote. Their vote is weighted according to the number of governance
+//// tokens in their wallet. In this current implementation, users do not spend
+//// or lock up these coins in order to vote- they simply prove ownership of them.
+////
+//// In the current prototype, the following information is exposed:
+////
+//// * Encrypted votes are publicly linked to the proposal identifier hash,
+////   meaning that it is possible to see that there is voting activity associated
+////   with a particular proposal identifier, but the contents of the proposal,
+////   how one has voted, and the associated DAO is fully private.
+//// * In the burn phase of casting a vote, we reveal a public value called a
+////   nullifier. The same public value is revealed when we spend the coins we
+////   used to vote, meaning you can link a vote with a user when they spend
+////   governance tokens. This is bad but is easily fixable. We will update the
+////   code to use different values in the vote (by creating an intermediate Coin
+////   used for voting).
+//// * Votes are currently encrypted to the DAO public key. This means that
+////   any DAO participant can decrypt votes as they come in. In the future,
+////   we can delay the decryption so that you cannot read votes until the final
+////   tally.
+////
+//// Additionally, the dao-demo app shown below is highly limited. Namely, we use
+//// a single God daemon to operate all the wallets. In the next version, every user
+//// wallet will be a seperate daemon connecting over a network and running on a
+//// blockchain.
+////
+//// /////////////////////////////////////////////////////////////////////
+////
+//// dao-demo 0.1 TODOs:
+////
+//// High priority:
+////
+//// 5. vote() should pass a ProposalBulla
+////
+//// Less priority:
+////
+//// 1. Better document CLI/ CLI help.
+////
+//// 2. Token id is hardcoded rn. Change this so users can specify token_id
+////    as either xdrk or gdrk. In dao-cli we run a match statement to link to
+////    the corresponding static values XDRK_ID and GDRK_ID. Note: xdrk is used
+////    only for the DAO treasury. gdrk is the governance token used to operate
+////    the DAO.
+////
+//// 3. Implement money transfer between MoneyWallets so users can send tokens to
+////    eachother.
+////
+//// 4. Make CLI usage more interactive. Example: when I cast a vote, output:
+////   "You voted {} with value {}." where value is the number of gDRK in a users
+////    wallet (and the same for making a proposal etc).
+////
+//// 5. Currently, DaoWallet stores DaoParams, DaoBulla's and Proposal's in a
+////    Vector. We retrieve values through indexing, meaning that we
+////    cannot currently support multiple DAOs and multiple proposals.
+////
+////    Instead, dao_wallet.create_dao() should create a struct called Dao
+////    which stores dao_info: HashMap<DaoBulla, DaoParams> and proposals:
+////    HashMap<ProposalBulla, Proposal>. Users pass the DaoBulla and
+////    ProposalBulla and we lookup the corresponding data. struct Dao should
+////    be owned by DaoWallet.
+////
+//// 6. Error handling :)
+////
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+pub struct Client {
+    dao_wallet: DaoWallet,
+    money_wallets: FxHashMap<PublicKey, MoneyWallet>,
+    cashier_wallet: CashierWallet,
+    states: StateRegistry,
+    zk_bins: ZkContractTable,
+}
+
+impl Client {
+    fn new() -> Self {
+        // For this early demo we store all wallets in a single Client.
+        let dao_wallet = DaoWallet::new();
+        let money_wallets = FxHashMap::default();
+        let cashier_wallet = CashierWallet::new();
+
+        // Lookup table for smart contract states
+        let states = StateRegistry::new();
+
+        // Initialize ZK binary table
+        let zk_bins = ZkContractTable::new();
+
+        Self { dao_wallet, money_wallets, cashier_wallet, states, zk_bins }
+    }
+
+    // Load ZK contracts into the ZkContractTable and initialize the StateRegistry.
+    fn init(&mut self) -> Result<()> {
+        //We use these to initialize the money state.
+        let faucet_signature_secret = SecretKey::random(&mut OsRng);
+        let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret);
+
+        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)?;
+        self.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());
+
+        self.zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk);
+        self.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)?;
+        self.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)?;
+        self.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)?;
+        self.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)?;
+        self.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)?;
+        self.zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13);
+
+        let cashier_signature_public = self.cashier_wallet.signature_public();
+
+        let money_state =
+            money_contract::state::State::new(cashier_signature_public, faucet_signature_public);
+        self.states.register(*money_contract::CONTRACT_ID, money_state);
+
+        let dao_state = dao_contract::State::new();
+        self.states.register(*dao_contract::CONTRACT_ID, dao_state);
+
+        Ok(())
+    }
+
+    fn create_dao(
+        &mut self,
+        dao_proposer_limit: u64,
+        dao_quorum: u64,
+        dao_approval_ratio_quot: u64,
+        dao_approval_ratio_base: u64,
+        token_id: pallas::Base,
+    ) -> DaoResult<pallas::Base> {
+        let tx = self.dao_wallet.mint_tx(
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_quot,
+            dao_approval_ratio_base,
+            token_id,
+            &self.zk_bins,
+        );
+
+        self.validate(&tx)?;
+        // Only witness the value once the transaction is confirmed.
+        self.dao_wallet.update_witness(&mut self.states).unwrap();
+
+        // Retrieve DAO bulla from the state.
+        let dao_bulla = {
+            let func_call = &tx.func_calls[0];
+            let call_data = func_call.call_data.as_any();
+            let call_data =
+                call_data.downcast_ref::<dao_contract::mint::validate::CallData>().unwrap();
+            call_data.dao_bulla.clone()
+        };
+
+        debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0);
+
+        // We store these values in a vector we can easily retrieve DAO values for the demo.
+        let dao_params = DaoParams {
+            proposer_limit: dao_proposer_limit,
+            quorum: dao_quorum,
+            approval_ratio_quot: dao_approval_ratio_quot,
+            approval_ratio_base: dao_approval_ratio_base,
+            gov_token_id: token_id,
+            public_key: self.dao_wallet.keypair.public,
+            bulla_blind: self.dao_wallet.bulla_blind,
+        };
+
+        self.dao_wallet.params.push(dao_params);
+        self.dao_wallet.bullas.push(dao_bulla.clone());
+
+        Ok(dao_bulla.0)
+    }
+
+    fn mint_treasury(
+        &mut self,
+        token_id: pallas::Base,
+        token_supply: u64,
+        recipient: PublicKey,
+    ) -> DaoResult<()> {
+        self.dao_wallet.track(&mut self.states)?;
+
+        let tx = self
+            .cashier_wallet
+            .mint(token_id, token_supply, self.dao_wallet.bullas[0].0, recipient, &self.zk_bins)
+            .unwrap();
+
+        self.validate(&tx)?;
+        self.update_wallets()?;
+
+        Ok(())
+    }
+
+    fn airdrop_user(
+        &mut self,
+        value: u64,
+        token_id: pallas::Base,
+        addr: PublicKey,
+    ) -> DaoResult<()> {
+        // let wallet = self.money_wallets.get(&nym).unwrap();
+        // let addr = wallet.get_public_key();
+
+        let tx = self.cashier_wallet.airdrop(value, token_id, addr, &self.zk_bins).unwrap();
+        self.validate(&tx)?;
+        self.update_wallets()?;
+
+        Ok(())
+    }
+
+    // TODO: Change these into errors instead of expects.
+    fn validate(&mut self, tx: &Transaction) -> DaoResult<()> {
+        debug!(target: "dao_demo::client::validate()", "commencing validate sequence");
+        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_contract::transfer::FUNC_ID {
+                debug!("money_contract::transfer::state_transition()");
+                match money_contract::transfer::validate::state_transition(&self.states, idx, &tx) {
+                    Ok(update) => {
+                        updates.push(update);
+                    }
+                    Err(e) => return Err(DaoError::StateTransitionFailed(e.to_string())),
+                }
+            } else if func_call.func_id == *dao_contract::mint::FUNC_ID {
+                debug!("dao_contract::mint::state_transition()");
+                match dao_contract::mint::validate::state_transition(&self.states, idx, &tx) {
+                    Ok(update) => {
+                        updates.push(update);
+                    }
+                    Err(e) => return Err(DaoError::StateTransitionFailed(e.to_string())),
+                }
+            } else if func_call.func_id == *dao_contract::propose::FUNC_ID {
+                debug!(target: "demo", "dao_contract::propose::state_transition()");
+                match dao_contract::propose::validate::state_transition(&self.states, idx, &tx) {
+                    Ok(update) => {
+                        updates.push(update);
+                    }
+                    Err(e) => return Err(DaoError::StateTransitionFailed(e.to_string())),
+                }
+            } else if func_call.func_id == *dao_contract::vote::FUNC_ID {
+                debug!(target: "demo", "dao_contract::vote::state_transition()");
+                match dao_contract::vote::validate::state_transition(&self.states, idx, &tx) {
+                    Ok(update) => {
+                        updates.push(update);
+                    }
+                    Err(e) => return Err(DaoError::StateTransitionFailed(e.to_string())),
+                }
+            } else if func_call.func_id == *dao_contract::exec::FUNC_ID {
+                debug!("dao_contract::exec::state_transition()");
+                match dao_contract::exec::validate::state_transition(&self.states, idx, &tx) {
+                    Ok(update) => {
+                        updates.push(update);
+                    }
+                    Err(e) => return Err(DaoError::StateTransitionFailed(e.to_string())),
+                }
+            }
+        }
+
+        // Atomically apply all changes
+        for update in updates {
+            update.apply(&mut self.states);
+        }
+
+        tx.zk_verify(&self.zk_bins)?;
+        tx.verify_sigs();
+
+        Ok(())
+    }
+
+    fn update_wallets(&mut self) -> DaoResult<()> {
+        let state = self.states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID);
+        if state.is_none() {
+            return Err(DaoError::StateNotFound)
+        }
+        let state = state.unwrap();
+
+        let dao_coins = state.wallet_cache.get_received(&self.dao_wallet.keypair.secret);
+        for coin in dao_coins {
+            let note = coin.note.clone();
+            let coords = self.dao_wallet.keypair.public.0.to_affine().coordinates().unwrap();
+
+            let coin_hash = 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_hash, coin.coin.0);
+            assert_eq!(note.spend_hook, *dao_contract::exec::FUNC_ID);
+            assert_eq!(note.user_data, self.dao_wallet.bullas[0].0);
+
+            self.dao_wallet.own_coins.push((coin, false));
+            debug!("DAO received a coin worth {} xDRK", note.value);
+        }
+
+        for (_key, wallet) in &mut self.money_wallets {
+            let coins = state.wallet_cache.get_received(&wallet.keypair.secret);
+            for coin in coins {
+                let note = coin.note.clone();
+                let coords = wallet.keypair.public.0.to_affine().coordinates().unwrap();
+
+                let coin_hash = 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_hash, coin.coin.0);
+                wallet.own_coins.push((coin, false));
+            }
+        }
+
+        Ok(())
+    }
+
+    fn propose(
+        &mut self,
+        recipient: PublicKey,
+        token_id: pallas::Base,
+        amount: u64,
+        sender: PublicKey,
+    ) -> DaoResult<pallas::Base> {
+        let params = self.dao_wallet.params[0].clone();
+
+        let dao_leaf_position = self.dao_wallet.leaf_position;
+
+        // To be able to make a proposal, we must prove we have ownership
+        // of governance tokens, and that the quantity of governance
+        // tokens is within the accepted proposer limit.
+        let sender_wallet = self.money_wallets.get_mut(&sender);
+        if sender_wallet.is_none() {
+            return Err(DaoError::NoWalletFound)
+        }
+        let sender_wallet = sender_wallet.unwrap();
+
+        let tx = sender_wallet.propose_tx(
+            params.clone(),
+            recipient,
+            token_id,
+            amount,
+            dao_leaf_position,
+            &self.zk_bins,
+            &mut self.states,
+        )?;
+
+        self.validate(&tx)?;
+        self.update_wallets()?;
+
+        let proposal_bulla = self.dao_wallet.store_proposal(&tx)?;
+
+        Ok(proposal_bulla)
+    }
+
+    fn cast_vote(&mut self, pubkey: PublicKey, vote: bool) -> DaoResult<()> {
+        let dao_key = self.dao_wallet.keypair;
+        if self.dao_wallet.proposals.is_empty() {
+            return Err(DaoError::NoProposals)
+        }
+        let proposal = self.dao_wallet.proposals[0].clone();
+
+        if self.dao_wallet.params.is_empty() {
+            return Err(DaoError::DaoNotConfigured)
+        }
+        let dao_params = self.dao_wallet.params[0].clone();
+        let dao_keypair = self.dao_wallet.keypair;
+
+        let voter_wallet = self.money_wallets.get_mut(&pubkey);
+        if voter_wallet.is_none() {
+            return Err(DaoError::NoWalletFound)
+        }
+        let voter_wallet = voter_wallet.unwrap();
+
+        let tx = voter_wallet.vote_tx(
+            vote,
+            dao_key,
+            proposal,
+            dao_params,
+            dao_keypair,
+            &self.zk_bins,
+            &mut self.states,
+        )?;
+
+        self.validate(&tx)?;
+        self.update_wallets()?;
+
+        self.dao_wallet.store_vote(&tx).unwrap();
+
+        Ok(())
+    }
+
+    fn exec_proposal(&mut self, bulla: pallas::Base) -> DaoResult<()> {
+        if self.dao_wallet.proposals.is_empty() {
+            return Err(DaoError::NoProposals)
+        }
+        let proposal = self.dao_wallet.proposals[0].clone();
+
+        if self.dao_wallet.params.is_empty() {
+            return Err(DaoError::DaoNotConfigured)
+        }
+        let dao_params = self.dao_wallet.params[0].clone();
+
+        let tx = self
+            .dao_wallet
+            .exec_tx(proposal, bulla, dao_params, &self.zk_bins, &mut self.states)
+            .unwrap();
+
+        self.validate(&tx)?;
+        self.update_wallets()?;
+
+        Ok(())
+    }
+}
+
+struct DaoWallet {
+    keypair: Keypair,
+    signature_secret: SecretKey,
+    bulla_blind: pallas::Base,
+    leaf_position: Position,
+    proposal_bullas: Vec<pallas::Base>,
+    bullas: Vec<DaoBulla>,
+    params: Vec<DaoParams>,
+    own_coins: Vec<(OwnCoin, bool)>,
+    proposals: Vec<Proposal>,
+    vote_notes: Vec<dao_contract::vote::wallet::Note>,
+}
+impl DaoWallet {
+    fn new() -> Self {
+        let keypair = Keypair::random(&mut OsRng);
+        let signature_secret = SecretKey::random(&mut OsRng);
+        let bulla_blind = pallas::Base::random(&mut OsRng);
+        let leaf_position = Position::zero();
+        let proposal_bullas = Vec::new();
+        let bullas = Vec::new();
+        let params = Vec::new();
+        let own_coins: Vec<(OwnCoin, bool)> = Vec::new();
+        let proposals: Vec<Proposal> = Vec::new();
+        let vote_notes = Vec::new();
+
+        Self {
+            keypair,
+            signature_secret,
+            bulla_blind,
+            leaf_position,
+            proposal_bullas,
+            bullas,
+            params,
+            own_coins,
+            proposals,
+            vote_notes,
+        }
+    }
+
+    fn get_public_key(&self) -> PublicKey {
+        self.keypair.public
+    }
+
+    fn track(&self, states: &mut StateRegistry) -> DaoResult<()> {
+        let state = states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID);
+        if state.is_none() {
+            return Err(DaoError::StateNotFound)
+        }
+        let state = state.unwrap();
+        state.wallet_cache.track(self.keypair.secret);
+        Ok(())
+    }
+
+    // Mint the DAO bulla.
+    fn mint_tx(
+        &mut self,
+        dao_proposer_limit: u64,
+        dao_quorum: u64,
+        dao_approval_ratio_quot: u64,
+        dao_approval_ratio_base: u64,
+        token_id: pallas::Base,
+        zk_bins: &ZkContractTable,
+    ) -> Transaction {
+        debug!(target: "dao-demo::dao::mint_tx()", "START");
+        let builder = dao_contract::mint::wallet::Builder {
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_quot,
+            dao_approval_ratio_base,
+            gov_token_id: token_id,
+            dao_pubkey: self.keypair.public,
+            dao_bulla_blind: self.bulla_blind,
+            _signature_secret: self.signature_secret,
+        };
+        let func_call = builder.build(zk_bins);
+        let func_calls = vec![func_call];
+
+        let signatures = sign(vec![self.signature_secret], &func_calls);
+        Transaction { func_calls, signatures }
+    }
+
+    fn update_witness(&mut self, states: &mut StateRegistry) -> DaoResult<()> {
+        let state = states.lookup_mut::<dao_contract::State>(*dao_contract::CONTRACT_ID);
+        if state.is_none() {
+            return Err(DaoError::StateNotFound)
+        }
+        let state = state.unwrap();
+        let path = state.dao_tree.witness().unwrap();
+        self.leaf_position = path;
+        Ok(())
+    }
+
+    // TODO: Make this a HashMap<TokenID, u64>
+    // only parse it to a String in the cli.
+    fn balances(&self) -> Result<FxHashMap<String, u64>> {
+        let mut ret: FxHashMap<String, u64> = FxHashMap::default();
+        for (coin, is_spent) in &self.own_coins {
+            if *is_spent {}
+            if coin.note.token_id == *DRK_ID {
+                let id = "DRK".to_owned();
+                ret.insert(id, coin.note.value);
+            } else if coin.note.token_id == *GOV_ID {
+                let id = "GOV".to_owned();
+                ret.insert(id, coin.note.value);
+            }
+        }
+        Ok(ret)
+    }
+
+    fn store_proposal(&mut self, tx: &Transaction) -> Result<pallas::Base> {
+        let (proposal, proposal_bulla) = {
+            let func_call = &tx.func_calls[0];
+            let call_data = func_call.call_data.as_any();
+            let call_data =
+                call_data.downcast_ref::<dao_contract::propose::validate::CallData>().unwrap();
+
+            let header = &call_data.header;
+            let note: dao_contract::propose::wallet::Note =
+                header.enc_note.decrypt(&self.keypair.secret).unwrap();
+
+            // 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", "Proposal bulla: {:?}", proposal_bulla);
+
+        self.proposals.push(proposal);
+        self.proposal_bullas.push(proposal_bulla);
+
+        Ok(proposal_bulla)
+    }
+
+    // We decrypt the votes in a transaction and add it to the wallet.
+    fn store_vote(&mut self, tx: &Transaction) -> Result<()> {
+        let vote_note = {
+            let func_call = &tx.func_calls[0];
+            let call_data = func_call.call_data.as_any();
+            let call_data =
+                call_data.downcast_ref::<dao_contract::vote::validate::CallData>().unwrap();
+
+            let header = &call_data.header;
+            let note: dao_contract::vote::wallet::Note =
+                header.enc_note.decrypt(&self.keypair.secret).unwrap();
+            note
+        };
+
+        self.vote_notes.push(vote_note);
+
+        Ok(())
+    }
+
+    fn get_proposals(&self) -> &Vec<Proposal> {
+        &self.proposals
+    }
+
+    fn get_votes(&self) -> &Vec<dao_contract::vote::wallet::Note> {
+        &self.vote_notes
+    }
+
+    // TODO: Explicit error handling.
+    fn get_treasury_path(
+        &self,
+        own_coin: &OwnCoin,
+        states: &StateRegistry,
+    ) -> Result<(Position, Vec<MerkleNode>)> {
+        let (money_leaf_position, money_merkle_path) = {
+            let state =
+                states.lookup::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+            let tree = &state.tree;
+            let leaf_position = own_coin.leaf_position.clone();
+            let root = tree.root(0).unwrap();
+            let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+            (leaf_position, merkle_path)
+        };
+
+        Ok((money_leaf_position, money_merkle_path))
+    }
+
+    fn exec_tx(
+        &self,
+        proposal: Proposal,
+        _proposal_bulla: pallas::Base,
+        dao_params: DaoParams,
+        zk_bins: &ZkContractTable,
+        states: &mut StateRegistry,
+    ) -> Result<Transaction> {
+        let dao_bulla = self.bullas[0].clone();
+
+        let mut inputs = Vec::new();
+        let mut total_input_value = 0;
+
+        let tx_signature_secret = SecretKey::random(&mut OsRng);
+        let exec_signature_secret = SecretKey::random(&mut OsRng);
+
+        let user_serial = pallas::Base::random(&mut OsRng);
+        let user_coin_blind = pallas::Base::random(&mut OsRng);
+        let user_data_blind = pallas::Base::random(&mut OsRng);
+        let input_value_blind = pallas::Scalar::random(&mut OsRng);
+        let dao_serial = pallas::Base::random(&mut OsRng);
+        let dao_coin_blind = pallas::Base::random(&mut OsRng);
+        // disabled
+        let user_spend_hook = pallas::Base::from(0);
+        let user_data = pallas::Base::from(0);
+
+        for (coin, is_spent) in &self.own_coins {
+            let is_spent = is_spent.clone();
+            if is_spent {
+                continue
+            }
+            let (treasury_leaf_position, treasury_merkle_path) =
+                self.get_treasury_path(&coin, states)?;
+
+            let input_value = coin.note.value;
+
+            let input = {
+                money_contract::transfer::wallet::BuilderInputInfo {
+                    leaf_position: treasury_leaf_position,
+                    merkle_path: treasury_merkle_path,
+                    secret: self.keypair.secret,
+                    note: coin.note.clone(),
+                    user_data_blind,
+                    value_blind: input_value_blind,
+                    signature_secret: tx_signature_secret,
+                }
+            };
+            total_input_value += input_value;
+            inputs.push(input);
+        }
+
+        let builder = {
+            money_contract::transfer::wallet::Builder {
+                clear_inputs: vec![],
+                inputs,
+                outputs: vec![
+                    // Sending money
+                    money_contract::transfer::wallet::BuilderOutputInfo {
+                        value: proposal.amount,
+                        token_id: proposal.token_id,
+                        public: proposal.dest,
+                        serial: proposal.serial,
+                        coin_blind: proposal.blind,
+                        spend_hook: user_spend_hook,
+                        user_data,
+                    },
+                    // Change back to DAO
+                    money_contract::transfer::wallet::BuilderOutputInfo {
+                        value: total_input_value - proposal.amount,
+                        token_id: *DRK_ID,
+                        public: self.keypair.public,
+                        serial: dao_serial,
+                        coin_blind: dao_coin_blind,
+                        spend_hook: *dao_contract::exec::FUNC_ID,
+                        user_data: dao_bulla.0,
+                    },
+                ],
+            }
+        };
+
+        let transfer_func_call = builder.build(zk_bins).unwrap();
+
+        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();
+
+        for (i, note) in self.vote_notes.iter().enumerate() {
+            let vote_commit = pedersen_commitment_u64(note.vote_value, note.vote_value_blind);
+
+            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,
+            );
+
+            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));
+
+        let builder = {
+            dao_contract::exec::wallet::Builder {
+                proposal: proposal.clone(),
+                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: total_input_value,
+                input_value_blind,
+                hook_dao_exec: *dao_contract::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 signatures = sign(vec![tx_signature_secret, exec_signature_secret], &func_calls);
+        Ok(Transaction { func_calls, signatures })
+    }
+}
+
+// Stores governance tokens and related secret values.
+struct MoneyWallet {
+    keypair: Keypair,
+    signature_secret: SecretKey,
+    own_coins: Vec<(OwnCoin, bool)>,
+}
+
+impl MoneyWallet {
+    // fn signature_public(&self) -> PublicKey {
+    //     PublicKey::from_secret(self.signature_secret)
+    // }
+
+    // fn get_public_key(&self) -> PublicKey {
+    //     self.keypair.public
+    // }
+
+    fn track(&self, states: &mut StateRegistry) -> DaoResult<()> {
+        let state = states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID);
+        if state.is_none() {
+            return Err(DaoError::StateNotFound)
+        }
+        let state = state.unwrap();
+        state.wallet_cache.track(self.keypair.secret);
+        Ok(())
+    }
+
+    // TODO: Make this a HashMap<TokenID, u64>
+    // only parse it to a String in the cli.
+    fn balances(&self) -> Result<FxHashMap<String, u64>> {
+        let mut ret: FxHashMap<String, u64> = FxHashMap::default();
+        for (coin, is_spent) in &self.own_coins {
+            if *is_spent {}
+            if coin.note.token_id == *DRK_ID {
+                let id = "DRK".to_owned();
+                ret.insert(id, coin.note.value);
+            } else if coin.note.token_id == *GOV_ID {
+                let id = "GOV".to_owned();
+                ret.insert(id, coin.note.value);
+            }
+        }
+        Ok(ret)
+    }
+
+    fn propose_tx(
+        &mut self,
+        params: DaoParams,
+        recipient: PublicKey,
+        token_id: pallas::Base,
+        amount: u64,
+        dao_leaf_position: Position,
+        zk_bins: &ZkContractTable,
+        states: &mut StateRegistry,
+    ) -> Result<Transaction> {
+        let mut inputs = Vec::new();
+
+        for (coin, is_spent) in &self.own_coins {
+            let is_spent = is_spent.clone();
+            if is_spent {
+                continue
+            }
+            let (money_leaf_position, money_merkle_path) = self.get_path(&states, &coin).unwrap();
+
+            let input = {
+                dao_contract::propose::wallet::BuilderInput {
+                    secret: self.keypair.secret,
+                    note: coin.note.clone(),
+                    leaf_position: money_leaf_position,
+                    merkle_path: money_merkle_path,
+                    signature_secret: self.signature_secret,
+                }
+            };
+            inputs.push(input);
+        }
+
+        let (dao_merkle_path, dao_merkle_root) = {
+            let state = states.lookup::<dao_contract::State>(*dao_contract::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 proposal = {
+            dao_contract::propose::wallet::Proposal {
+                dest: recipient,
+                amount,
+                serial: pallas::Base::random(&mut OsRng),
+                token_id,
+                blind: pallas::Base::random(&mut OsRng),
+            }
+        };
+
+        let builder = dao_contract::propose::wallet::Builder {
+            inputs,
+            proposal,
+            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 signatures = sign(vec![self.signature_secret], &func_calls);
+        Ok(Transaction { func_calls, signatures })
+    }
+
+    fn get_path(
+        &self,
+        states: &StateRegistry,
+        own_coin: &OwnCoin,
+    ) -> Result<(Position, Vec<MerkleNode>)> {
+        let (money_leaf_position, money_merkle_path) = {
+            let state =
+                states.lookup::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+            let tree = &state.tree;
+            let leaf_position = own_coin.leaf_position.clone();
+            let root = tree.root(0).unwrap();
+            let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+            (leaf_position, merkle_path)
+        };
+
+        Ok((money_leaf_position, money_merkle_path))
+    }
+
+    fn vote_tx(
+        &mut self,
+        vote_option: bool,
+        _dao_key: Keypair,
+        proposal: Proposal,
+        dao_params: DaoParams,
+        dao_keypair: Keypair,
+        zk_bins: &ZkContractTable,
+        states: &mut StateRegistry,
+    ) -> Result<Transaction> {
+        let mut inputs = Vec::new();
+
+        // We must prove we have sufficient governance tokens in order to vote.
+        for (coin, _is_spent) in &self.own_coins {
+            let (money_leaf_position, money_merkle_path) = self.get_path(states, &coin).unwrap();
+
+            let input = {
+                dao_contract::vote::wallet::BuilderInput {
+                    secret: self.keypair.secret,
+                    note: coin.note.clone(),
+                    leaf_position: money_leaf_position,
+                    merkle_path: money_merkle_path,
+                    signature_secret: self.signature_secret,
+                }
+            };
+            inputs.push(input);
+        }
+
+        let builder = {
+            dao_contract::vote::wallet::Builder {
+                inputs,
+                vote: dao_contract::vote::wallet::Vote {
+                    vote_option,
+                    vote_option_blind: pallas::Scalar::random(&mut OsRng),
+                },
+                // For this demo votes are encrypted for the DAO.
+                vote_keypair: dao_keypair,
+                proposal: proposal.clone(),
+                dao: dao_params.clone(),
+            }
+        };
+
+        let func_call = builder.build(zk_bins);
+        let func_calls = vec![func_call];
+
+        let signatures = sign(vec![self.signature_secret], &func_calls);
+        Ok(Transaction { func_calls, signatures })
+    }
+}
+
+async fn start_rpc(client: Client) -> Result<()> {
+    let rpc_addr = Url::parse("tcp://127.0.0.1:7777").unwrap();
+
+    let rpc_client = JsonRpcInterface::new(client);
+
+    let rpc_interface = Arc::new(rpc_client);
+
+    listen_and_serve(rpc_addr, rpc_interface).await.unwrap();
+    Ok(())
+}
+
+// Mint authority that mints the DAO treasury and airdrops governance tokens.
+#[derive(Clone)]
+struct CashierWallet {
+    // keypair: Keypair,
+    signature_secret: SecretKey,
+}
+
+impl CashierWallet {
+    fn new() -> Self {
+        // let keypair = Keypair::random(&mut OsRng);
+        let signature_secret = SecretKey::random(&mut OsRng);
+        // Self { keypair, signature_secret }
+        Self { signature_secret }
+    }
+
+    fn signature_public(&self) -> PublicKey {
+        PublicKey::from_secret(self.signature_secret)
+    }
+
+    fn mint(
+        &mut self,
+        token_id: pallas::Base,
+        token_supply: u64,
+        dao_bulla: pallas::Base,
+        recipient: PublicKey,
+        zk_bins: &ZkContractTable,
+    ) -> Result<Transaction> {
+        let spend_hook = *dao_contract::exec::FUNC_ID;
+        let user_data = dao_bulla;
+        let value = token_supply;
+
+        let tx =
+            self.transfer_tx(value, token_id, spend_hook, user_data, recipient, zk_bins).unwrap();
+
+        Ok(tx)
+    }
+
+    fn transfer_tx(
+        &self,
+        value: u64,
+        token_id: pallas::Base,
+        spend_hook: pallas::Base,
+        user_data: pallas::Base,
+        recipient: PublicKey,
+        zk_bins: &ZkContractTable,
+    ) -> Result<Transaction> {
+        let builder = {
+            money_contract::transfer::wallet::Builder {
+                clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo {
+                    value,
+                    token_id,
+                    signature_secret: self.signature_secret,
+                }],
+                inputs: vec![],
+                outputs: vec![money_contract::transfer::wallet::BuilderOutputInfo {
+                    value,
+                    token_id,
+                    public: recipient,
+                    serial: pallas::Base::random(&mut OsRng),
+                    coin_blind: pallas::Base::random(&mut OsRng),
+                    spend_hook,
+                    user_data,
+                }],
+            }
+        };
+        let func_call = builder.build(zk_bins).unwrap();
+        let func_calls = vec![func_call];
+
+        let signatures = sign(vec![self.signature_secret], &func_calls);
+        Ok(Transaction { func_calls, signatures })
+    }
+
+    fn airdrop(
+        &mut self,
+        value: u64,
+        token_id: pallas::Base,
+        recipient: PublicKey,
+        zk_bins: &ZkContractTable,
+    ) -> Result<Transaction> {
+        // Spend hook and user data disabled
+        let spend_hook = DrkSpendHook::from(0);
+        let user_data = DrkUserData::from(0);
+
+        let tx =
+            self.transfer_tx(value, token_id, spend_hook, user_data, recipient, zk_bins).unwrap();
+
+        Ok(tx)
+    }
+}
+
+#[async_std::main]
+async fn main() -> Result<()> {
+    TermLogger::init(
+        LevelFilter::Debug,
+        simplelog::Config::default(),
+        TerminalMode::Mixed,
+        ColorChoice::Auto,
+    )
+    .unwrap();
+
+    let mut client = Client::new();
+    client.init()?;
+
+    start_rpc(client).await.unwrap();
+
+    Ok(())
+}

+ 98 - 0
bin/dao/daod/src/note.rs

@@ -0,0 +1,98 @@
+use crypto_api_chachapoly::ChachaPolyIetf;
+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 mut ciphertext = vec![0; input.len() + AEAD_TAG_SIZE];
+    assert_eq!(
+        ChachaPolyIetf::aead_cipher()
+            .seal_to(&mut ciphertext, &input, &[], key.as_ref(), &[0u8; 12])
+            .unwrap(),
+        input.len() + AEAD_TAG_SIZE
+    );
+
+    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 mut plaintext = vec![0; self.ciphertext.len()];
+        assert_eq!(
+            ChachaPolyIetf::aead_cipher()
+                .open_to(&mut plaintext, &self.ciphertext, &[], key.as_ref(), &[0u8; 12])
+                .map_err(|_| Error::NoteDecryptionFailed)?,
+            self.ciphertext.len() - AEAD_TAG_SIZE
+        );
+
+        T::decode(&plaintext[..]).map_err(Error::from)
+    }
+}
+
+#[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);
+    }
+}

+ 402 - 0
bin/dao/daod/src/rpc.rs

@@ -0,0 +1,402 @@
+use async_std::sync::Mutex;
+use std::{str::FromStr, sync::Arc};
+
+use async_trait::async_trait;
+use log::{debug, error};
+use pasta_curves::group::ff::PrimeField;
+use rand::rngs::OsRng;
+use serde_json::{json, Value};
+
+use darkfi::{
+    crypto::keypair::{Keypair, PublicKey, SecretKey},
+    rpc::{
+        jsonrpc::{ErrorCode::*, JsonError, JsonRequest, JsonResponse, JsonResult},
+        server::RequestHandler,
+    },
+};
+
+use crate::{
+    contract::money_contract::state::OwnCoin,
+    error::{server_error, RpcError},
+    util::{parse_b58, DRK_ID, GOV_ID},
+    Client, MoneyWallet,
+};
+
+pub struct JsonRpcInterface {
+    client: Arc<Mutex<Client>>,
+}
+
+#[async_trait]
+impl RequestHandler for JsonRpcInterface {
+    async fn handle_request(&self, req: JsonRequest) -> JsonResult {
+        if !req.params.is_array() {
+            return JsonError::new(InvalidParams, None, req.id).into()
+        }
+
+        let params = req.params.as_array().unwrap();
+
+        debug!(target: "RPC", "--> {}", serde_json::to_string(&req).unwrap());
+
+        match req.method.as_str() {
+            Some("create") => return self.create_dao(req.id, params).await,
+            Some("get_dao_addr") => return self.get_dao_addr(req.id, params).await,
+            Some("get_votes") => return self.get_votes(req.id, params).await,
+            Some("get_proposals") => return self.get_proposals(req.id, params).await,
+            Some("dao_balance") => return self.dao_balance(req.id, params).await,
+            Some("dao_bulla") => return self.dao_bulla(req.id, params).await,
+            Some("user_balance") => return self.user_balance(req.id, params).await,
+            Some("mint") => return self.mint_treasury(req.id, params).await,
+            Some("keygen") => return self.keygen(req.id, params).await,
+            Some("airdrop") => return self.airdrop_tokens(req.id, params).await,
+            Some("propose") => return self.create_proposal(req.id, params).await,
+            Some("vote") => return self.vote(req.id, params).await,
+            Some("exec") => return self.execute(req.id, params).await,
+            Some(_) | None => return JsonError::new(MethodNotFound, None, req.id).into(),
+        }
+    }
+}
+
+impl JsonRpcInterface {
+    pub fn new(client: Client) -> Self {
+        let client = Arc::new(Mutex::new(client));
+        Self { client }
+    }
+
+    // --> {"method": "create", "params": []}
+    // <-- {"result": "creating dao..."}
+    async fn create_dao(&self, id: Value, params: &[Value]) -> JsonResult {
+        let dao_proposer_limit = params[0].as_u64();
+        if dao_proposer_limit.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let dao_proposer_limit = dao_proposer_limit.unwrap();
+
+        let dao_quorum = params[1].as_u64();
+        if dao_quorum.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let dao_quorum = dao_quorum.unwrap();
+
+        let dao_approval_ratio_quot = params[2].as_u64();
+        if dao_approval_ratio_quot.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let dao_approval_ratio_quot = dao_approval_ratio_quot.unwrap();
+
+        let dao_approval_ratio_base = params[3].as_u64();
+        if dao_approval_ratio_base.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let dao_approval_ratio_base = dao_approval_ratio_base.unwrap();
+
+        let mut client = self.client.lock().await;
+
+        match client.create_dao(
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_quot,
+            dao_approval_ratio_base,
+            *GOV_ID,
+        ) {
+            Ok(bulla) => {
+                let bulla: String = bs58::encode(bulla.to_repr()).into_string();
+                JsonResponse::new(json!(bulla), id).into()
+            }
+            Err(e) => {
+                error!("Failed to create DAO: {}", e);
+                return server_error(RpcError::Create, id)
+            }
+        }
+    }
+
+    // --> {"method": "get_dao_addr", "params": []}
+    // <-- {"result": "getting dao public addr..."}
+    async fn get_dao_addr(&self, id: Value, _params: &[Value]) -> JsonResult {
+        let client = self.client.lock().await;
+        let pubkey = client.dao_wallet.get_public_key();
+        let addr: String = bs58::encode(pubkey.to_bytes()).into_string();
+        JsonResponse::new(json!(addr), id).into()
+    }
+
+    // --> {"method": "get_dao_addr", "params": []}
+    // <-- {"result": "getting dao public addr..."}
+    async fn get_votes(&self, id: Value, _params: &[Value]) -> JsonResult {
+        let client = self.client.lock().await;
+        let vote_notes = client.dao_wallet.get_votes();
+        let mut vote_data = vec![];
+
+        for note in vote_notes {
+            let vote_option = note.vote.vote_option;
+            let vote_value = note.vote_value;
+            vote_data.push((vote_option, vote_value));
+        }
+
+        JsonResponse::new(json!(vote_data), id).into()
+    }
+
+    // --> {"method": "get_dao_addr", "params": []}
+    // <-- {"result": "getting dao public addr..."}
+    async fn get_proposals(&self, id: Value, _params: &[Value]) -> JsonResult {
+        let client = self.client.lock().await;
+        let proposals = client.dao_wallet.get_proposals();
+        let mut proposal_data = vec![];
+
+        for proposal in proposals {
+            let dest = proposal.dest;
+            let amount = proposal.amount;
+            let token_id = proposal.token_id;
+            let token_id: String = bs58::encode(token_id.to_repr()).into_string();
+            proposal_data.push((dest, amount, token_id));
+        }
+
+        JsonResponse::new(json!(proposal_data), id).into()
+    }
+
+    async fn dao_balance(&self, id: Value, _params: &[Value]) -> JsonResult {
+        let client = self.client.lock().await;
+        let balance = client.dao_wallet.balances().unwrap();
+        JsonResponse::new(json!(balance), id).into()
+    }
+
+    async fn dao_bulla(&self, id: Value, _params: &[Value]) -> JsonResult {
+        let client = self.client.lock().await;
+        let dao_bullas = client.dao_wallet.bullas.clone();
+        let mut bulla_vec = Vec::new();
+
+        for bulla in dao_bullas {
+            let dao_bulla: String = bs58::encode(bulla.0.to_repr()).into_string();
+            bulla_vec.push(dao_bulla);
+        }
+
+        JsonResponse::new(json!(bulla_vec), id).into()
+    }
+
+    async fn user_balance(&self, id: Value, params: &[Value]) -> JsonResult {
+        let client = self.client.lock().await;
+        let nym = params[0].as_str();
+        if nym.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let nym = nym.unwrap();
+
+        match PublicKey::from_str(nym) {
+            Ok(key) => match client.money_wallets.get(&key) {
+                Some(wallet) => {
+                    let balance = wallet.balances().unwrap();
+                    JsonResponse::new(json!(balance), id).into()
+                }
+                None => {
+                    error!("No wallet found for provided key");
+                    return server_error(RpcError::Balance, id)
+                }
+            },
+            Err(_) => {
+                error!("Could not parse PublicKey from string");
+                return server_error(RpcError::Parse, id)
+            }
+        }
+    }
+
+    // --> {"method": "mint_treasury", "params": []}
+    // <-- {"result": "minting treasury..."}
+    async fn mint_treasury(&self, id: Value, params: &[Value]) -> JsonResult {
+        let mut client = self.client.lock().await;
+
+        let token_supply = params[0].as_u64();
+        if token_supply.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let token_supply = token_supply.unwrap();
+
+        let addr = params[1].as_str();
+        if addr.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let addr = addr.unwrap();
+
+        match PublicKey::from_str(addr) {
+            Ok(dao_addr) => match client.mint_treasury(*DRK_ID, token_supply, dao_addr) {
+                Ok(_) => JsonResponse::new(json!("DAO treasury minted successfully."), id).into(),
+                Err(e) => {
+                    error!("Failed to mint treasury: {}", e);
+                    return server_error(RpcError::Mint, id)
+                }
+            },
+            Err(_) => {
+                error!("Failed to parse PublicKey from String");
+                return server_error(RpcError::Parse, id)
+            }
+        }
+    }
+
+    // Create a new wallet for governance tokens.
+    async fn keygen(&self, id: Value, _params: &[Value]) -> JsonResult {
+        let mut client = self.client.lock().await;
+        // let nym = params[0].as_str().unwrap().to_string();
+
+        let keypair = Keypair::random(&mut OsRng);
+        let signature_secret = SecretKey::random(&mut OsRng);
+        let own_coins: Vec<(OwnCoin, bool)> = Vec::new();
+        let money_wallet = MoneyWallet { keypair, signature_secret, own_coins };
+
+        match money_wallet.track(&mut client.states) {
+            Ok(_) => {
+                client.money_wallets.insert(keypair.public, money_wallet);
+                let addr: String = bs58::encode(keypair.public.to_bytes()).into_string();
+                JsonResponse::new(json!(addr), id).into()
+            }
+            Err(e) => {
+                error!("Failed to airdrop tokens: {}", e);
+                return server_error(RpcError::Keygen, id)
+            }
+        }
+    }
+
+    // --> {"method": "airdrop_tokens", "params": []}
+    // <-- {"result": "airdropping tokens..."}
+    async fn airdrop_tokens(&self, id: Value, params: &[Value]) -> JsonResult {
+        let mut client = self.client.lock().await;
+        // let zk_bins = &client.zk_bins;
+
+        let addr = params[0].as_str();
+        if addr.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let addr = addr.unwrap();
+
+        let value = params[1].as_u64();
+        if value.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let value = value.unwrap();
+
+        match PublicKey::from_str(addr) {
+            Ok(key) => match client.airdrop_user(value, *GOV_ID, key) {
+                Ok(_) => JsonResponse::new(json!("Tokens airdropped successfully."), id).into(),
+                Err(e) => {
+                    error!("Failed to airdrop tokens: {}", e);
+                    return server_error(RpcError::Airdrop, id)
+                }
+            },
+            Err(_) => {
+                error!("Failed parsing PublicKey from String");
+                return server_error(RpcError::Parse, id)
+            }
+        }
+    }
+    // --> {"method": "create_proposal", "params": []}
+    // <-- {"result": "creating proposal..."}
+    async fn create_proposal(&self, id: Value, params: &[Value]) -> JsonResult {
+        let mut client = self.client.lock().await;
+
+        if params.is_empty() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let sender = params[0].as_str();
+        if sender.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let sender = sender.unwrap();
+
+        let recipient = params[1].as_str();
+        if recipient.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let recipient = recipient.unwrap();
+
+        let amount = params[2].as_u64();
+        if amount.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let amount = amount.unwrap();
+
+        let recv_addr = PublicKey::from_str(recipient);
+        if recv_addr.is_err() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let recv_addr = recv_addr.unwrap();
+
+        let sndr_addr = PublicKey::from_str(sender);
+        if sndr_addr.is_err() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let sndr_addr = sndr_addr.unwrap();
+
+        match client.propose(recv_addr, *DRK_ID, amount, sndr_addr) {
+            Ok(bulla) => {
+                let bulla: String = bs58::encode(bulla.to_repr()).into_string();
+
+                JsonResponse::new(json!(bulla), id).into()
+            }
+            Err(e) => {
+                error!("Failed to make Proposal: {}", e);
+                return server_error(RpcError::Propose, id)
+            }
+        }
+    }
+    // --> {"method": "vote", "params": []}
+    // <-- {"result": "voting..."}
+    async fn vote(&self, id: Value, params: &[Value]) -> JsonResult {
+        let mut client = self.client.lock().await;
+        let mut vote_bool = true;
+
+        let addr = params[0].as_str();
+        if addr.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let addr = addr.unwrap();
+
+        let vote_str = params[1].as_str();
+        if vote_str.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let vote_str = vote_str.unwrap();
+
+        match vote_str {
+            "yes" => {}
+            "no" => vote_bool = false,
+            _ => return JsonError::new(InvalidParams, None, id).into(),
+        }
+
+        match PublicKey::from_str(addr) {
+            Ok(key) => match client.cast_vote(key, vote_bool) {
+                Ok(_) => JsonResponse::new(json!("Vote cast successfully."), id).into(),
+                Err(e) => {
+                    error!("Failed casting vote: {}", e);
+                    return server_error(RpcError::Vote, id)
+                }
+            },
+            Err(_) => {
+                error!("Failed parsing PublicKey from String");
+                return server_error(RpcError::Parse, id)
+            }
+        }
+    }
+    // --> {"method": "execute", "params": []}
+    // <-- {"result": "executing..."}
+    async fn execute(&self, id: Value, params: &[Value]) -> JsonResult {
+        let mut client = self.client.lock().await;
+
+        let bulla_str = params[0].as_str();
+        if bulla_str.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let bulla_str = bulla_str.unwrap();
+
+        let bulla = parse_b58(bulla_str);
+        match bulla {
+            Ok(bulla) => match client.exec_proposal(bulla) {
+                Ok(_) => JsonResponse::new(json!("Proposal executed successfully."), id).into(),
+                Err(e) => {
+                    error!("Failed executing proposal: {}", e);
+                    return server_error(RpcError::Exec, id)
+                }
+            },
+            Err(e) => {
+                error!("Failed parsing bulla: {}", e);
+                return server_error(RpcError::Parse, id)
+            }
+        }
+    }
+}

+ 2 - 1
bin/daod/src/demo.rs → bin/dao/daod/src/schema.rs

@@ -36,7 +36,7 @@ use darkfi::{
     zkas::decoder::ZkBinary,
 };
 
-use crate::{dao_contract, example_contract, money_contract};
+use crate::contract::{dao_contract, example_contract, money_contract};
 
 // TODO: Anonymity leaks in this proof of concept:
 //
@@ -1255,6 +1255,7 @@ pub async fn demo() -> Result<()> {
                 serial: dao_serial,
                 coin_blind: dao_coin_blind,
                 spend_hook: *dao_contract::exec::FUNC_ID,
+                // TODO: should be DAO bulla
                 user_data: proposal_bulla,
             },
         ],

+ 265 - 0
bin/dao/daod/src/util.rs

@@ -0,0 +1,265 @@
+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,
+    Error,
+};
+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<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, signature)) 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 in signature_pub_keys {
+                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_calls: &Vec<FuncCall>) -> Vec<Signature> {
+    let mut signatures = vec![];
+    let mut unsigned_tx_data = vec![];
+    for (_i, (signature_secret, func_call)) in
+        signature_secrets.iter().zip(func_calls.iter()).enumerate()
+    {
+        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);
+}

+ 0 - 72
bin/daod/src/main.rs

@@ -1,72 +0,0 @@
-use std::sync::Arc;
-
-use async_trait::async_trait;
-use log::debug;
-use serde_json::{json, Value};
-use simplelog::{ColorChoice, LevelFilter, TermLogger, TerminalMode};
-use url::Url;
-
-use darkfi::{
-    rpc::{
-        jsonrpc::{ErrorCode::*, JsonError, JsonRequest, JsonResponse, JsonResult},
-        server::{listen_and_serve, RequestHandler},
-    },
-    Result,
-};
-
-mod dao_contract;
-mod example_contract;
-mod money_contract;
-
-mod demo;
-mod note;
-
-use crate::demo::demo;
-
-async fn _start() -> Result<()> {
-    let rpc_addr = Url::parse("tcp://127.0.0.1:7777")?;
-    let rpc_interface = Arc::new(JsonRpcInterface {});
-
-    listen_and_serve(rpc_addr, rpc_interface).await?;
-    Ok(())
-}
-
-struct JsonRpcInterface {}
-
-#[async_trait]
-impl RequestHandler for JsonRpcInterface {
-    async fn handle_request(&self, req: JsonRequest) -> JsonResult {
-        if req.params.as_array().is_none() {
-            return JsonError::new(InvalidParams, None, req.id).into()
-        }
-
-        debug!(target: "RPC", "--> {}", serde_json::to_string(&req).unwrap());
-
-        match req.method.as_str() {
-            Some("say_hello") => return self.say_hello(req.id, req.params).await,
-            Some(_) | None => return JsonError::new(MethodNotFound, None, req.id).into(),
-        }
-    }
-}
-
-impl JsonRpcInterface {
-    // --> {"method": "say_hello", "params": []}
-    // <-- {"result": "hello world"}
-    async fn say_hello(&self, id: Value, _params: Value) -> JsonResult {
-        JsonResponse::new(json!("hello world"), id).into()
-    }
-}
-
-#[async_std::main]
-async fn main() -> Result<()> {
-    TermLogger::init(
-        LevelFilter::Debug,
-        simplelog::Config::default(),
-        TerminalMode::Mixed,
-        ColorChoice::Auto,
-    )?;
-
-    //start().await?;
-    demo().await.unwrap();
-    Ok(())
-}

+ 10 - 0
example/dao/contract/dao_contract/exec/mod.rs

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

+ 3 - 5
bin/daod/src/dao_contract/exec/validate.rs → example/dao/contract/dao_contract/exec/validate.rs

@@ -13,10 +13,8 @@ use darkfi::{
 use std::any::{Any, TypeId};
 
 use crate::{
-    dao_contract,
-    dao_contract::CONTRACT_ID,
-    demo::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase},
-    money_contract,
+    contract::{dao_contract, dao_contract::CONTRACT_ID, money_contract},
+    util::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase},
 };
 
 type Result<T> = std::result::Result<T, Error>;
@@ -111,7 +109,7 @@ pub fn state_transition(
     states: &StateRegistry,
     func_call_index: usize,
     parent_tx: &Transaction,
-) -> Result<Box<dyn UpdateBase>> {
+) -> Result<Box<dyn UpdateBase + Send>> {
     let func_call = &parent_tx.func_calls[func_call_index];
     let call_data = func_call.call_data.as_any();
 

+ 2 - 2
bin/daod/src/dao_contract/exec/wallet.rs → example/dao/contract/dao_contract/exec/wallet.rs

@@ -14,10 +14,10 @@ use darkfi::{
 };
 
 use crate::{
-    dao_contract::{
+    contract::dao_contract::{
         exec::validate::CallData, mint::wallet::DaoParams, propose::wallet::Proposal, CONTRACT_ID,
     },
-    demo::{FuncCall, ZkContractInfo, ZkContractTable},
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
 };
 
 pub struct Builder {

+ 44 - 0
example/dao/contract/dao_contract/mint/mod.rs

@@ -0,0 +1,44 @@
+use lazy_static::lazy_static;
+use pasta_curves::{group::ff::Field, pallas};
+use rand::rngs::OsRng;
+
+pub mod validate;
+/// This is an anonymous contract function that mutates the internal DAO state.
+///
+/// Corresponds to `mint(proposer_limit, quorum, approval_ratio, dao_pubkey, dao_blind)`
+///
+/// The prover creates a `Builder`, which then constructs the `Tx` that the verifier can
+/// check using `state_transition()`.
+///
+/// # Arguments
+///
+/// * `proposer_limit` - Number of governance tokens that holder must possess in order to
+///   propose a new vote.
+/// * `quorum` - Number of minimum votes that must be met for a proposal to pass.
+/// * `approval_ratio` - Ratio of winning to total votes for a proposal to pass.
+/// * `dao_pubkey` - Public key of the DAO for permissioned access. This can also be
+///   shared publicly if you want a full decentralized DAO.
+/// * `dao_blind` - Blinding factor for the DAO bulla.
+///
+/// # Example
+///
+/// ```rust
+/// let dao_proposer_limit = 110;
+/// let dao_quorum = 110;
+/// let dao_approval_ratio = 2;
+///
+/// let builder = dao_contract::Mint::Builder(
+///     dao_proposer_limit,
+///     dao_quorum,
+///     dao_approval_ratio,
+///     gov_token_id,
+///     dao_pubkey,
+///     dao_blind
+/// );
+/// let tx = builder.build();
+/// ```
+pub mod wallet;
+
+lazy_static! {
+    pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng);
+}

+ 3 - 3
bin/daod/src/dao_contract/mint/validate.rs → example/dao/contract/dao_contract/mint/validate.rs

@@ -4,15 +4,15 @@ use darkfi::crypto::{keypair::PublicKey, types::DrkCircuitField};
 use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
 
 use crate::{
-    dao_contract::{DaoBulla, State, CONTRACT_ID},
-    demo::{CallDataBase, StateRegistry, Transaction, UpdateBase},
+    contract::dao_contract::{DaoBulla, State, CONTRACT_ID},
+    util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
 };
 
 pub fn state_transition(
     _states: &StateRegistry,
     func_call_index: usize,
     parent_tx: &Transaction,
-) -> Result<Box<dyn UpdateBase>> {
+) -> Result<Box<dyn UpdateBase + Send>> {
     let func_call = &parent_tx.func_calls[func_call_index];
     let call_data = func_call.call_data.as_any();
 

+ 96 - 0
example/dao/contract/dao_contract/mint/wallet.rs

@@ -0,0 +1,96 @@
+use darkfi::{
+    crypto::{
+        keypair::{PublicKey, SecretKey},
+        util::poseidon_hash,
+        Proof,
+    },
+    zk::vm::{Witness, ZkCircuit},
+};
+use halo2_proofs::circuit::Value;
+use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas};
+use rand::rngs::OsRng;
+
+use crate::{
+    contract::dao_contract::{mint::validate::CallData, state::DaoBulla, CONTRACT_ID},
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
+};
+
+#[derive(Clone)]
+pub struct DaoParams {
+    pub proposer_limit: u64,
+    pub quorum: u64,
+    pub approval_ratio_quot: u64,
+    pub approval_ratio_base: u64,
+    pub gov_token_id: pallas::Base,
+    pub public_key: PublicKey,
+    pub bulla_blind: pallas::Base,
+}
+
+pub struct Builder {
+    pub dao_proposer_limit: u64,
+    pub dao_quorum: u64,
+    pub dao_approval_ratio_quot: u64,
+    pub dao_approval_ratio_base: u64,
+    pub gov_token_id: pallas::Base,
+    pub dao_pubkey: PublicKey,
+    pub dao_bulla_blind: pallas::Base,
+    pub _signature_secret: SecretKey,
+}
+
+impl Builder {
+    /// Consumes self, and produces the function call
+    pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
+        // Dao bulla
+        let dao_proposer_limit = pallas::Base::from(self.dao_proposer_limit);
+        let dao_quorum = pallas::Base::from(self.dao_quorum);
+        let dao_approval_ratio_quot = pallas::Base::from(self.dao_approval_ratio_quot);
+        let dao_approval_ratio_base = pallas::Base::from(self.dao_approval_ratio_base);
+
+        let dao_pubkey_coords = self.dao_pubkey.0.to_affine().coordinates().unwrap();
+
+        let dao_bulla = poseidon_hash::<8>([
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_quot,
+            dao_approval_ratio_base,
+            self.gov_token_id,
+            *dao_pubkey_coords.x(),
+            *dao_pubkey_coords.y(),
+            self.dao_bulla_blind,
+        ]);
+        let dao_bulla = DaoBulla(dao_bulla);
+
+        // Now create the mint proof
+        let zk_info = zk_bins.lookup(&"dao-mint".to_string()).unwrap();
+        let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
+            info
+        } else {
+            panic!("Not binary info")
+        };
+        let zk_bin = zk_info.bincode.clone();
+        let prover_witnesses = vec![
+            Witness::Base(Value::known(dao_proposer_limit)),
+            Witness::Base(Value::known(dao_quorum)),
+            Witness::Base(Value::known(dao_approval_ratio_quot)),
+            Witness::Base(Value::known(dao_approval_ratio_base)),
+            Witness::Base(Value::known(self.gov_token_id)),
+            Witness::Base(Value::known(*dao_pubkey_coords.x())),
+            Witness::Base(Value::known(*dao_pubkey_coords.y())),
+            Witness::Base(Value::known(self.dao_bulla_blind)),
+        ];
+        let public_inputs = vec![dao_bulla.0];
+        let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
+
+        let proving_key = &zk_info.proving_key;
+        let mint_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
+            .expect("DAO::mint() proving error!");
+
+        let call_data = CallData { dao_bulla };
+        FuncCall {
+            contract_id: *CONTRACT_ID,
+            func_id: *super::FUNC_ID,
+            call_data: Box::new(call_data),
+            proofs: vec![mint_proof],
+        }
+    }
+}

+ 20 - 0
example/dao/contract/dao_contract/mod.rs

@@ -0,0 +1,20 @@
+use lazy_static::lazy_static;
+use pasta_curves::{group::ff::Field, pallas};
+use rand::rngs::OsRng;
+
+// mint()
+pub mod mint;
+// propose()
+pub mod propose;
+// vote{}
+pub mod vote;
+// exec{}
+pub mod exec;
+
+pub mod state;
+
+pub use state::{DaoBulla, State};
+
+lazy_static! {
+    pub static ref CONTRACT_ID: pallas::Base = pallas::Base::random(&mut OsRng);
+}

+ 10 - 0
example/dao/contract/dao_contract/propose/mod.rs

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

+ 6 - 6
bin/daod/src/dao_contract/propose/validate.rs → example/dao/contract/dao_contract/propose/validate.rs

@@ -14,12 +14,12 @@ use darkfi::{
 };
 
 use crate::{
-    dao_contract,
-    dao_contract::State as DaoState,
-    demo::{CallDataBase, StateRegistry, Transaction, UpdateBase},
-    money_contract,
-    money_contract::state::State as MoneyState,
+    contract::{
+        dao_contract, dao_contract::State as DaoState, money_contract,
+        money_contract::state::State as MoneyState,
+    },
     note::EncryptedNote2,
+    util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
 };
 
 // used for debugging
@@ -129,7 +129,7 @@ pub fn state_transition(
     states: &StateRegistry,
     func_call_index: usize,
     parent_tx: &Transaction,
-) -> Result<Box<dyn UpdateBase>> {
+) -> Result<Box<dyn UpdateBase + Send>> {
     let func_call = &parent_tx.func_calls[func_call_index];
     let call_data = func_call.call_data.as_any();
 

+ 9 - 6
bin/daod/src/dao_contract/propose/wallet.rs → example/dao/contract/dao_contract/propose/wallet.rs

@@ -19,13 +19,16 @@ use darkfi::{
 };
 
 use crate::{
-    dao_contract::{
-        mint::wallet::DaoParams,
-        propose::validate::{CallData, Header, Input},
-        CONTRACT_ID,
+    contract::{
+        dao_contract::{
+            mint::wallet::DaoParams,
+            propose::validate::{CallData, Header, Input},
+            CONTRACT_ID,
+        },
+        money_contract,
     },
-    demo::{FuncCall, ZkContractInfo, ZkContractTable},
-    money_contract, note,
+    note,
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
 };
 
 #[derive(SerialEncodable, SerialDecodable)]

+ 2 - 2
bin/daod/src/dao_contract/state.rs → example/dao/contract/dao_contract/state.rs

@@ -6,7 +6,7 @@ use pasta_curves::{group::Group, pallas};
 
 use darkfi::crypto::{constants::MERKLE_DEPTH, merkle_node::MerkleNode, nullifier::Nullifier};
 
-use crate::demo::HashableBase;
+use crate::util::HashableBase;
 
 #[derive(Clone, SerialEncodable, SerialDecodable)]
 pub struct DaoBulla(pub pallas::Base);
@@ -42,7 +42,7 @@ pub struct State {
 }
 
 impl State {
-    pub fn new() -> Box<dyn Any> {
+    pub fn new() -> Box<dyn Any + Send> {
         Box::new(Self {
             dao_bullas: Vec::new(),
             dao_tree: MerkleTree::new(100),

+ 10 - 0
example/dao/contract/dao_contract/vote/mod.rs

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

+ 6 - 6
bin/daod/src/dao_contract/vote/validate.rs → example/dao/contract/dao_contract/vote/validate.rs

@@ -16,12 +16,12 @@ use darkfi::{
 };
 
 use crate::{
-    dao_contract,
-    dao_contract::State as DaoState,
-    demo::{CallDataBase, StateRegistry, Transaction, UpdateBase},
-    money_contract,
-    money_contract::state::State as MoneyState,
+    contract::{
+        dao_contract, dao_contract::State as DaoState, money_contract,
+        money_contract::state::State as MoneyState,
+    },
     note::EncryptedNote2,
+    util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
 };
 
 #[derive(Debug, Clone, thiserror::Error)]
@@ -140,7 +140,7 @@ pub fn state_transition(
     states: &StateRegistry,
     func_call_index: usize,
     parent_tx: &Transaction,
-) -> Result<Box<dyn UpdateBase>> {
+) -> Result<Box<dyn UpdateBase + Send>> {
     let func_call = &parent_tx.func_calls[func_call_index];
     let call_data = func_call.call_data.as_any();
 

+ 10 - 7
bin/daod/src/dao_contract/vote/wallet.rs → example/dao/contract/dao_contract/vote/wallet.rs

@@ -21,14 +21,17 @@ use darkfi::{
 };
 
 use crate::{
-    dao_contract::{
-        mint::wallet::DaoParams,
-        propose::wallet::Proposal,
-        vote::validate::{CallData, Header, Input},
-        CONTRACT_ID,
+    contract::{
+        dao_contract::{
+            mint::wallet::DaoParams,
+            propose::wallet::Proposal,
+            vote::validate::{CallData, Header, Input},
+            CONTRACT_ID,
+        },
+        money_contract,
     },
-    demo::{FuncCall, ZkContractInfo, ZkContractTable},
-    money_contract, note,
+    note,
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
 };
 
 #[derive(SerialEncodable, SerialDecodable)]

+ 10 - 0
example/dao/contract/example_contract/foo/mod.rs

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

+ 3 - 3
bin/daod/src/example_contract/foo/validate.rs → example/dao/contract/example_contract/foo/validate.rs

@@ -9,8 +9,8 @@ use darkfi::{
 use std::any::{Any, TypeId};
 
 use crate::{
-    demo::{CallDataBase, StateRegistry, Transaction, UpdateBase},
-    example_contract::{state::State, CONTRACT_ID},
+    contract::example_contract::{state::State, CONTRACT_ID},
+    util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
 };
 
 type Result<T> = std::result::Result<T, Error>;
@@ -61,7 +61,7 @@ pub fn state_transition(
     states: &StateRegistry,
     func_call_index: usize,
     parent_tx: &Transaction,
-) -> Result<Box<dyn UpdateBase>> {
+) -> Result<Box<dyn UpdateBase + Send>> {
     let func_call = &parent_tx.func_calls[func_call_index];
     let call_data = func_call.call_data.as_any();
 

+ 2 - 2
bin/daod/src/example_contract/foo/wallet.rs → example/dao/contract/example_contract/foo/wallet.rs

@@ -13,8 +13,8 @@ use darkfi::{
 };
 
 use crate::{
-    demo::{FuncCall, ZkContractInfo, ZkContractTable},
-    example_contract::{foo::validate::CallData, CONTRACT_ID},
+    contract::example_contract::{foo::validate::CallData, CONTRACT_ID},
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
 };
 
 pub struct Foo {

+ 12 - 0
example/dao/contract/example_contract/mod.rs

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

+ 0 - 0
bin/daod/src/example_contract/state.rs → example/dao/contract/example_contract/state.rs


+ 3 - 0
example/dao/contract/mod.rs

@@ -0,0 +1,3 @@
+pub mod dao_contract;
+pub mod example_contract;
+pub mod money_contract;

+ 13 - 0
example/dao/contract/money_contract/mod.rs

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

+ 0 - 0
bin/daod/src/money_contract/state.rs → example/dao/contract/money_contract/state.rs


+ 11 - 0
example/dao/contract/money_contract/transfer/mod.rs

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

+ 6 - 4
bin/daod/src/money_contract/transfer/validate.rs → example/dao/contract/money_contract/transfer/validate.rs

@@ -19,10 +19,12 @@ use darkfi::{
 };
 
 use crate::{
-    dao_contract,
-    demo::{CallDataBase, StateRegistry, Transaction, UpdateBase},
-    money_contract::{state::State, CONTRACT_ID},
+    contract::{
+        dao_contract,
+        money_contract::{state::State, CONTRACT_ID},
+    },
     note::EncryptedNote2,
+    util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
 };
 
 const TARGET: &str = "money_contract::transfer::validate::state_transition()";
@@ -64,7 +66,7 @@ pub fn state_transition(
     states: &StateRegistry,
     func_call_index: usize,
     parent_tx: &Transaction,
-) -> Result<Box<dyn UpdateBase>> {
+) -> 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");

+ 2 - 2
bin/daod/src/money_contract/transfer/wallet.rs → example/dao/contract/money_contract/transfer/wallet.rs

@@ -17,12 +17,12 @@ use darkfi::{
 };
 
 use crate::{
-    demo::{FuncCall, ZkContractInfo, ZkContractTable},
-    money_contract::{
+    contract::money_contract::{
         transfer::validate::{CallData, ClearInput, Input, Output},
         CONTRACT_ID,
     },
     note,
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
 };
 
 #[derive(Clone, SerialEncodable, SerialDecodable)]

+ 1327 - 0
example/dao/dao.rs

@@ -0,0 +1,1327 @@
+use incrementalmerkletree::Tree;
+use log::debug;
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{ff::Field, Curve, Group},
+    pallas,
+};
+use rand::rngs::OsRng;
+use std::{
+    any::{Any, TypeId},
+    time::Instant,
+};
+
+use darkfi::{
+    crypto::{
+        keypair::{Keypair, PublicKey, SecretKey},
+        proof::{ProvingKey, VerifyingKey},
+        types::{DrkSpendHook, DrkUserData, DrkValue},
+        util::{pedersen_commitment_u64, poseidon_hash},
+    },
+    zk::circuit::{BurnContract, MintContract},
+    zkas::decoder::ZkBinary,
+};
+
+mod contract;
+mod note;
+mod util;
+
+use crate::{
+    contract::{dao_contract, example_contract, money_contract},
+    util::{sign, StateRegistry, Transaction, ZkContractTable},
+};
+
+// 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>>;
+
+// #[derive(Eq, PartialEq)]
+// 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);
+//     }
+// }
+
+// pub struct ZkBinaryContractInfo {
+//     pub k_param: u32,
+//     pub bincode: ZkBinary,
+//     pub proving_key: ProvingKey,
+//     pub verifying_key: VerifyingKey,
+// }
+// pub struct ZkNativeContractInfo {
+//     pub proving_key: ProvingKey,
+//     pub verifying_key: VerifyingKey,
+// }
+
+// pub enum ZkContractInfo {
+//     Binary(ZkBinaryContractInfo),
+//     Native(ZkNativeContractInfo),
+// }
+
+// pub struct ZkContractTable {
+//     // Key will be a hash of zk binary contract on chain
+//     table: HashMap<String, ZkContractInfo>,
+// }
+
+// impl ZkContractTable {
+//     fn new() -> Self {
+//         Self { table: HashMap::new() }
+//     }
+
+//     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);
+//     }
+
+//     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<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
+//     fn zk_verify(&self, zk_bins: &ZkContractTable) {
+//         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);
+//                         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);
+//                         assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key);
+//                     }
+//                 };
+//                 debug!(target: "demo", "zk_verify({}) passed [i={}]", key, i);
+//             }
+//         }
+//     }
+
+//     fn verify_sigs(&self) {
+//         let mut unsigned_tx_data = vec![];
+//         for (i, (func_call, signature)) 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 in signature_pub_keys {
+//                 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);
+//         }
+//     }
+// }
+
+// fn sign(signature_secrets: Vec<SecretKey>, func_calls: &Vec<FuncCall>) -> Vec<Signature> {
+//     let mut signatures = vec![];
+//     let mut unsigned_tx_data = vec![];
+//     for (_i, (signature_secret, func_call)) in
+//         signature_secrets.iter().zip(func_calls.iter()).enumerate()
+//     {
+//         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>,
+//     pub proofs: Vec<Proof>,
+// }
+
+// impl Encodable for FuncCall {
+//     fn encode<W: std::io::Write>(&self, mut w: W) -> std::result::Result<usize, darkfi::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, darkfi::Error>;
+// }
+
+// type GenericContractState = Box<dyn Any>;
+
+// pub struct StateRegistry {
+//     pub states: HashMap<HashableBase, GenericContractState>,
+// }
+
+// impl StateRegistry {
+//     fn new() -> Self {
+//         Self { states: HashMap::new() }
+//     }
+
+//     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);
+// }
+
+///////////////////////////////////////////////////
+///// 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_contract::state::State::new();
+    states.register(*example_contract::CONTRACT_ID, example_state);
+
+    //// Wallet
+
+    let foo = example_contract::foo::wallet::Foo { a: 5, b: 10 };
+    let signature_secret = SecretKey::random(&mut OsRng);
+
+    let builder = example_contract::foo::wallet::Builder { foo, signature_secret };
+    let func_call = builder.build(&zk_bins);
+    let func_calls = vec![func_call];
+
+    let signatures = sign(vec![signature_secret], &func_calls);
+    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_contract::foo::FUNC_ID {
+            debug!("example_contract::foo::state_transition()");
+
+            let update = example_contract::foo::validate::state_transition(&states, idx, &tx)
+                .expect("example_contract::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);
+    tx.verify_sigs();
+
+    Ok(())
+}
+
+#[async_std::main]
+async fn main() -> 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);
+
+    ///////////////////////////////////////////////////
+
+    let money_state =
+        money_contract::state::State::new(cashier_signature_public, faucet_signature_public);
+    states.register(*money_contract::CONTRACT_ID, money_state);
+
+    /////////////////////////////////////////////////////
+
+    let dao_state = dao_contract::State::new();
+    states.register(*dao_contract::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_contract::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 signatures = sign(vec![signature_secret], &func_calls);
+    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_contract::mint::FUNC_ID {
+            debug!("dao_contract::mint::state_transition()");
+
+            let update = dao_contract::mint::validate::state_transition(&states, idx, &tx)
+                .expect("dao_contract::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);
+    tx.verify_sigs();
+
+    // Wallet stuff
+
+    // In your wallet, wait until you see the tx confirmed before doing anything below
+    // So for example keep track of tx hash
+    //assert_eq!(tx.hash(), tx_hash);
+
+    // We need to witness() the value in our local merkle tree
+    // Must be called as soon as this DAO bulla is added to the state
+    let dao_leaf_position = {
+        let state = states.lookup_mut::<dao_contract::State>(*dao_contract::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_contract::mint::validate::CallData>());
+        let call_data = call_data.downcast_ref::<dao_contract::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");
+
+    let state = states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+    state.wallet_cache.track(dao_keypair.secret);
+
+    //// Wallet
+
+    // Address of deployed contract in our example is dao_contract::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_contract::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_contract::transfer::wallet::Builder {
+        clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo {
+            value: xdrk_supply,
+            token_id: xdrk_token_id,
+            signature_secret: cashier_signature_secret,
+        }],
+        inputs: vec![],
+        outputs: vec![money_contract::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 signatures = sign(vec![cashier_signature_secret], &func_calls);
+    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_contract::transfer::FUNC_ID {
+            debug!("money_contract::transfer::state_transition()");
+
+            let update = money_contract::transfer::validate::state_transition(&states, idx, &tx)
+                .expect("money_contract::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);
+    tx.verify_sigs();
+
+    //// Wallet
+    // DAO reads the money received from the encrypted note
+
+    let state = states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+    let mut recv_coins = state.wallet_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_contract::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);
+
+    let state = states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+    state.wallet_cache.track(gov_keypair_1.secret);
+    state.wallet_cache.track(gov_keypair_2.secret);
+    state.wallet_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_contract::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_contract::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_contract::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_contract::transfer::wallet::Builder {
+        clear_inputs: vec![money_contract::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 signatures = sign(vec![cashier_signature_secret], &func_calls);
+    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_contract::transfer::FUNC_ID {
+            debug!("money_contract::transfer::state_transition()");
+
+            let update = money_contract::transfer::validate::state_transition(&states, idx, &tx)
+                .expect("money_contract::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);
+    tx.verify_sigs();
+
+    //// Wallet
+
+    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 state =
+                states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+            let mut recv_coins = state.wallet_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 state = states.lookup::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+        let tree = &state.tree;
+        let leaf_position = gov_recv[0].leaf_position.clone();
+        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_contract::propose::wallet::BuilderInput {
+        secret: gov_keypair_1.secret,
+        note: gov_recv[0].note.clone(),
+        leaf_position: money_leaf_position,
+        merkle_path: money_merkle_path,
+        signature_secret,
+    };
+
+    let (dao_merkle_path, dao_merkle_root) = {
+        let state = states.lookup::<dao_contract::State>(*dao_contract::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_contract::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_contract::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_contract::propose::wallet::Builder {
+        inputs: vec![input],
+        proposal: proposal.clone(),
+        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 signatures = sign(vec![signature_secret], &func_calls);
+    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_contract::propose::FUNC_ID {
+            debug!(target: "demo", "dao_contract::propose::state_transition()");
+
+            let update = dao_contract::propose::validate::state_transition(&states, idx, &tx)
+                .expect("dao_contract::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);
+    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_contract::propose::validate::CallData>()
+        );
+        let call_data =
+            call_data.downcast_ref::<dao_contract::propose::validate::CallData>().unwrap();
+
+        let header = &call_data.header;
+        let note: dao_contract::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 state = states.lookup::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+        let tree = &state.tree;
+        let leaf_position = gov_recv[0].leaf_position.clone();
+        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_contract::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 == true || vote_option == false);
+
+    // 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_contract::vote::wallet::Builder {
+        inputs: vec![input],
+        vote: dao_contract::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 signatures = sign(vec![signature_secret], &func_calls);
+    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_contract::vote::FUNC_ID {
+            debug!(target: "demo", "dao_contract::vote::state_transition()");
+
+            let update = dao_contract::vote::validate::state_transition(&states, idx, &tx)
+                .expect("dao_contract::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);
+    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_contract::vote::validate::CallData>());
+        let call_data = call_data.downcast_ref::<dao_contract::vote::validate::CallData>().unwrap();
+
+        let header = &call_data.header;
+        let note: dao_contract::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 state = states.lookup::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+        let tree = &state.tree;
+        let leaf_position = gov_recv[1].leaf_position.clone();
+        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_contract::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 == true || vote_option == false);
+
+    // We create a new keypair to encrypt the vote.
+    let vote_keypair_2 = Keypair::random(&mut OsRng);
+
+    let builder = dao_contract::vote::wallet::Builder {
+        inputs: vec![input],
+        vote: dao_contract::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 signatures = sign(vec![signature_secret], &func_calls);
+    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_contract::vote::FUNC_ID {
+            debug!(target: "demo", "dao_contract::vote::state_transition()");
+
+            let update = dao_contract::vote::validate::state_transition(&states, idx, &tx)
+                .expect("dao_contract::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);
+    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_contract::vote::validate::CallData>());
+        let call_data = call_data.downcast_ref::<dao_contract::vote::validate::CallData>().unwrap();
+
+        let header = &call_data.header;
+        let note: dao_contract::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 state = states.lookup::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+        let tree = &state.tree;
+        let leaf_position = gov_recv[2].leaf_position.clone();
+        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_contract::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 == true || vote_option == false);
+
+    // We create a new keypair to encrypt the vote.
+    let vote_keypair_3 = Keypair::random(&mut OsRng);
+
+    let builder = dao_contract::vote::wallet::Builder {
+        inputs: vec![input],
+        vote: dao_contract::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 signatures = sign(vec![signature_secret], &func_calls);
+    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_contract::vote::FUNC_ID {
+            debug!(target: "demo", "dao_contract::vote::state_transition()");
+
+            let update = dao_contract::vote::validate::state_transition(&states, idx, &tx)
+                .expect("dao_contract::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);
+    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_contract::vote::validate::CallData>());
+        let call_data = call_data.downcast_ref::<dao_contract::vote::validate::CallData>().unwrap();
+
+        let header = &call_data.header;
+        let note: dao_contract::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 state = states.lookup::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+        let tree = &state.tree;
+        let leaf_position = dao_recv_coin.leaf_position.clone();
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+        (leaf_position, merkle_path)
+    };
+
+    let input = money_contract::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_contract::transfer::wallet::Builder {
+        clear_inputs: vec![],
+        inputs: vec![input],
+        outputs: vec![
+            // Sending money
+            money_contract::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_contract::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_contract::exec::FUNC_ID,
+                user_data: proposal_bulla,
+            },
+        ],
+    };
+
+    let transfer_func_call = builder.build(&zk_bins)?;
+
+    let builder = dao_contract::exec::wallet::Builder {
+        proposal,
+        dao: dao_params,
+        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_contract::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 signatures = sign(vec![tx_signature_secret, exec_signature_secret], &func_calls);
+    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_contract::transfer::validate::CallData>()
+        );
+        let transfer_call_data =
+            transfer_call_data.downcast_ref::<money_contract::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_contract::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);
+    }
+
+    //// 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_contract::exec::FUNC_ID {
+            debug!("dao_contract::exec::state_transition()");
+
+            let update = dao_contract::exec::validate::state_transition(&states, idx, &tx)
+                .expect("dao_contract::exec::validate::state_transition() failed!");
+            updates.push(update);
+        } else if func_call.func_id == *money_contract::transfer::FUNC_ID {
+            debug!("money_contract::transfer::state_transition()");
+
+            let update = money_contract::transfer::validate::state_transition(&states, idx, &tx)
+                .expect("money_contract::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);
+    tx.verify_sigs();
+
+    //// Wallet
+
+    Ok(())
+}

+ 0 - 0
bin/daod/src/note.rs → example/dao/note.rs


+ 0 - 0
bin/daod/proof/dao-exec.zk → example/dao/proof/dao-exec.zk


+ 32 - 0
example/dao/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/dao/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/dao/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/dao/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/dao/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/dao/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);
+}
+
+

+ 0 - 0
bin/daod/demo/classnamespace.py → example/dao/schema/classnamespace.py


+ 0 - 0
bin/daod/demo/crypto.py → example/dao/schema/crypto.py


+ 0 - 0
bin/daod/demo/main.py → example/dao/schema/main.py


+ 0 - 0
bin/daod/demo/money.py → example/dao/schema/money.py


+ 234 - 0
example/dao/util.rs

@@ -0,0 +1,234 @@
+use lazy_static::lazy_static;
+use log::debug;
+use pasta_curves::{
+    group::ff::{Field, PrimeField},
+    pallas,
+};
+use rand::rngs::OsRng;
+use std::{any::Any, collections::HashMap, hash::Hasher};
+
+use darkfi::{
+    crypto::{
+        keypair::{PublicKey, SecretKey},
+        proof::{ProvingKey, VerifyingKey},
+        schnorr::{SchnorrPublic, SchnorrSecret, Signature},
+        types::DrkCircuitField,
+        Proof,
+    },
+    util::serial::Encodable,
+    zk::{vm::ZkCircuit, vm_stack::empty_witnesses},
+    zkas::decoder::ZkBinary,
+};
+
+// TODO: base58 encoding/ decoding
+
+lazy_static! {
+    pub static ref XDRK_ID: pallas::Base = pallas::Base::random(&mut OsRng);
+}
+
+lazy_static! {
+    pub static ref GDRK_ID: pallas::Base = pallas::Base::random(&mut OsRng);
+}
+
+#[derive(Eq, PartialEq)]
+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);
+    }
+}
+
+pub struct ZkBinaryContractInfo {
+    pub k_param: u32,
+    pub bincode: ZkBinary,
+    pub proving_key: ProvingKey,
+    pub verifying_key: VerifyingKey,
+}
+pub struct ZkNativeContractInfo {
+    pub proving_key: ProvingKey,
+    pub verifying_key: VerifyingKey,
+}
+
+pub enum ZkContractInfo {
+    Binary(ZkBinaryContractInfo),
+    Native(ZkNativeContractInfo),
+}
+
+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<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) {
+        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);
+                        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);
+                        assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key);
+                    }
+                };
+                debug!(target: "demo", "zk_verify({}) passed [i={}]", key, i);
+            }
+        }
+    }
+
+    pub fn verify_sigs(&self) {
+        let mut unsigned_tx_data = vec![];
+        for (i, (func_call, signature)) 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 in signature_pub_keys {
+                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_calls: &Vec<FuncCall>) -> Vec<Signature> {
+    let mut signatures = vec![];
+    let mut unsigned_tx_data = vec![];
+    for (_i, (signature_secret, func_call)) in
+        signature_secrets.iter().zip(func_calls.iter()).enumerate()
+    {
+        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, darkfi::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, darkfi::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);
+}

+ 12 - 0
example/derive_macro_example.rs

@@ -0,0 +1,12 @@
+use darkfi::serial::SerialEncodable;
+
+#[derive(Debug, SerialEncodable)]
+struct Test {
+    one: u64,
+    two: u64,
+}
+
+fn main() {
+    let test = Test { one: 1, two: 2 };
+    println!("Test: {:?}", test);
+}

+ 108 - 3
src/crypto/keypair.rs

@@ -1,6 +1,10 @@
-use std::{convert::TryFrom, str::FromStr};
+use std::{
+    convert::TryFrom,
+    hash::{Hash, Hasher},
+    str::FromStr,
+};
 
-use darkfi_serial::{SerialDecodable, SerialEncodable};
+use darkfi_serial::{Decodable, Encodable, SerialDecodable, SerialEncodable};
 use halo2_gadgets::ecc::chip::FixedPoint;
 use pasta_curves::{
     arithmetic::CurveAffine,
@@ -18,6 +22,8 @@ use crate::{
 };
 
 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
+#[cfg(feature = "serde")]
+#[derive(serde::Deserialize, serde::Serialize)]
 pub struct Keypair {
     pub secret: SecretKey,
     pub public: PublicKey,
@@ -36,7 +42,7 @@ impl Keypair {
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Debug, SerialDecodable, SerialEncodable)]
-pub struct SecretKey(pallas::Base);
+pub struct SecretKey(pub pallas::Base);
 
 impl SecretKey {
     pub fn random(mut rng: impl RngCore) -> Self {
@@ -119,6 +125,13 @@ impl PublicKey {
     }
 }
 
+impl Hash for PublicKey {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        let bytes = self.0.to_affine().to_bytes();
+        bytes.hash(state);
+    }
+}
+
 impl FromStr for PublicKey {
     type Err = crate::Error;
 
@@ -133,6 +146,98 @@ impl FromStr for PublicKey {
     }
 }
 
+#[cfg(feature = "serde")]
+impl serde::Serialize for SecretKey {
+    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        let mut bytes = vec![];
+        self.encode(&mut bytes).unwrap();
+        let hex_repr = hex::encode(&bytes);
+        serializer.serialize_str(&hex_repr)
+    }
+}
+
+#[cfg(feature = "serde")]
+struct SecretKeyVisitor;
+
+#[cfg(feature = "serde")]
+impl<'de> serde::de::Visitor<'de> for SecretKeyVisitor {
+    type Value = SecretKey;
+
+    fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
+        formatter.write_str("hex string")
+    }
+
+    fn visit_str<E>(self, value: &str) -> core::result::Result<SecretKey, E>
+    where
+        E: serde::de::Error,
+    {
+        let bytes = hex::decode(value).unwrap();
+        let mut r = std::io::Cursor::new(bytes);
+        let decoded: SecretKey = SecretKey::decode(&mut r).unwrap();
+        Ok(decoded)
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for SecretKey {
+    fn deserialize<D>(deserializer: D) -> core::result::Result<SecretKey, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let bytes = deserializer.deserialize_str(SecretKeyVisitor).unwrap();
+        Ok(bytes)
+    }
+}
+
+#[cfg(feature = "serde")]
+impl serde::Serialize for PublicKey {
+    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        let mut bytes = vec![];
+        self.encode(&mut bytes).unwrap();
+        let hex_repr = hex::encode(&bytes);
+        serializer.serialize_str(&hex_repr)
+    }
+}
+
+#[cfg(feature = "serde")]
+struct PublicKeyVisitor;
+
+#[cfg(feature = "serde")]
+impl<'de> serde::de::Visitor<'de> for PublicKeyVisitor {
+    type Value = PublicKey;
+
+    fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
+        formatter.write_str("hex string")
+    }
+
+    fn visit_str<E>(self, value: &str) -> core::result::Result<PublicKey, E>
+    where
+        E: serde::de::Error,
+    {
+        let bytes = hex::decode(value).unwrap();
+        let mut r = std::io::Cursor::new(bytes);
+        let decoded: PublicKey = PublicKey::decode(&mut r).unwrap();
+        Ok(decoded)
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for PublicKey {
+    fn deserialize<D>(deserializer: D) -> core::result::Result<PublicKey, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let bytes = deserializer.deserialize_str(PublicKeyVisitor).unwrap();
+        Ok(bytes)
+    }
+}
+
 impl TryFrom<Address> for PublicKey {
     type Error = Error;
     fn try_from(address: Address) -> Result<Self> {

+ 1 - 1
src/crypto/nullifier.rs

@@ -2,7 +2,7 @@ use darkfi_serial::{SerialDecodable, SerialEncodable};
 use pasta_curves::{group::ff::PrimeField, pallas};
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, SerialEncodable, SerialDecodable)]
-pub struct Nullifier(pallas::Base);
+pub struct Nullifier(pub pallas::Base);
 
 impl Nullifier {
     pub fn from_bytes(bytes: [u8; 32]) -> Option<Self> {