Преглед изворни кода

move dao/schema.rs to example (dao)

Dastan-glitch пре 3 година
родитељ
комит
05f554fe4d
37 измењених фајлова са 3289 додато и 2044 уклоњено
  1. 2 2
      Cargo.toml
  2. 41 0
      example/dao/Cargo.toml
  3. 0 3
      example/dao/contract/mod.rs
  4. 1 1
      example/dao/proof/dao-exec.zk
  5. 0 46
      example/dao/schema/classnamespace.py
  6. 0 158
      example/dao/schema/crypto.py
  7. 0 1463
      example/dao/schema/main.py
  8. 0 331
      example/dao/schema/money.py
  9. 0 0
      example/dao/src/contract/dao/exec/mod.rs
  10. 12 13
      example/dao/src/contract/dao/exec/validate.rs
  11. 5 7
      example/dao/src/contract/dao/exec/wallet.rs
  12. 0 0
      example/dao/src/contract/dao/mint/mod.rs
  13. 3 3
      example/dao/src/contract/dao/mint/validate.rs
  14. 5 4
      example/dao/src/contract/dao/mint/wallet.rs
  15. 0 0
      example/dao/src/contract/dao/mod.rs
  16. 0 0
      example/dao/src/contract/dao/propose/mod.rs
  17. 170 0
      example/dao/src/contract/dao/propose/validate.rs
  18. 272 0
      example/dao/src/contract/dao/propose/wallet.rs
  19. 97 0
      example/dao/src/contract/dao/state.rs
  20. 0 0
      example/dao/src/contract/dao/vote/mod.rs
  21. 205 0
      example/dao/src/contract/dao/vote/validate.rs
  22. 292 0
      example/dao/src/contract/dao/vote/wallet.rs
  23. 0 0
      example/dao/src/contract/example/foo/mod.rs
  24. 6 7
      example/dao/src/contract/example/foo/validate.rs
  25. 1 1
      example/dao/src/contract/example/foo/wallet.rs
  26. 0 0
      example/dao/src/contract/example/mod.rs
  27. 2 2
      example/dao/src/contract/example/state.rs
  28. 3 0
      example/dao/src/contract/mod.rs
  29. 0 0
      example/dao/src/contract/money/mod.rs
  30. 116 0
      example/dao/src/contract/money/state.rs
  31. 0 0
      example/dao/src/contract/money/transfer/mod.rs
  32. 375 0
      example/dao/src/contract/money/transfer/validate.rs
  33. 219 0
      example/dao/src/contract/money/transfer/wallet.rs
  34. 1139 0
      example/dao/src/dao.rs
  35. 56 0
      example/dao/src/error.rs
  36. 2 3
      example/dao/src/note.rs
  37. 265 0
      example/dao/src/util.rs

+ 2 - 2
Cargo.toml

@@ -360,8 +360,8 @@ required-features = ["crypto"]
 
 [[example]]
 name = "dao"
-path = "example/dao/dao.rs"
-required-features = ["crypto"]
+path = "example/dao/src/dao.rs"
+required-features = ["crypto", "rpc"]
 
 [[example]]
 name = "lead"

+ 41 - 0
example/dao/Cargo.toml

@@ -0,0 +1,41 @@
+[package]
+name = "dao"
+version = "0.3.0"
+edition = "2021"
+
+[dependencies]
+darkfi = {path = "../../", features = ["rpc", "crypto", "tx", "node"]}
+darkfi-serial = {path = "../../src/serial"}
+
+# Async
+smol = "1.2.5"
+futures = "0.3.24"
+async-std = {version = "1.12.0", features = ["attributes"]}
+async-trait = "0.1.57"
+async-channel = "1.7.1"
+async-executor = "1.4.1"
+easy-parallel = "3.2.0"
+
+# Misc
+log = "0.4.17"
+num_cpus = "1.13.1"
+simplelog = "0.12.0"
+thiserror = "1.0.37"
+
+# Crypto
+incrementalmerkletree = "0.3.0"
+pasta_curves = "0.4.0"
+halo2_gadgets = "0.2.0"
+halo2_proofs = "0.2.0"
+rand = "0.8.5"
+crypto_api_chachapoly = "0.5.0"
+group = "0.12.0"
+
+# Encoding and parsing
+serde_json = "1.0.85"
+bs58 = "0.4.0"
+fxhash = "0.2.1"
+
+# Utilities
+lazy_static = "1.4.0"
+url = "2.3.1"

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

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

+ 1 - 1
example/dao/proof/dao-exec.zk

@@ -91,7 +91,7 @@ circuit "DaoExec" {
        proposal_token_id,
        dao_serial,
        dao_spend_hook,
-       proposal_bulla,
+       dao_bulla,
        dao_coin_blind,
     );
     constrain_instance(coin_1);

+ 0 - 46
example/dao/schema/classnamespace.py

@@ -1,46 +0,0 @@
-from types import SimpleNamespace
-
-class ClassNamespace(SimpleNamespace):
-    def __init__(self, dic=None):
-        if dic is None:
-            return
-        # if type(dic) is dict:
-        for key in dic:
-            setattr(self, key, self.envelop(dic[key]))
-        # else:
-        #     raise CatalogError("ClassNamespace AIUTO!")
-
-    def envelop(self, elem):
-        if type(elem) is dict:
-            return ClassNamespace(elem)
-        elif type(elem) is list:
-            return [self.envelop(x) for x in elem]
-        else:
-            return elem
-
-        # if d is not None:
-        #     for key in d:
-        #         if type(d[key]) is dict:
-        #             setattr(self, key, ClassNamespace(d[key]))
-        #         else:
-        #             setattr(self, key, d[key])
-
-    def __contains__(self, x):
-        return x in self.__dict__
-
-    def __json__(self, x):
-        return self.__dict__
-
-    def copy(self):
-        return self.__dict__.copy()
-
-    def classcopy(self):
-        dummy = ClassNamespace()
-        dummy.__dict__.update(self.__dict__)
-        return dummy
-
-    def dictcopy(self):
-        return self.__dict__.copy()
-
-    def update(self, oth):
-        self.__dict__.update(oth.__dict__)

+ 0 - 158
example/dao/schema/crypto.py

@@ -1,158 +0,0 @@
-import hashlib
-import random
-
-def ff_inv(a, p):
-    a %= p
-
-    # extended euclidean algorithm
-    # ps + at = 1
-    t = 0
-    new_t = 1
-    r = p
-    new_r = a
-
-    while new_r != 0:
-        quotient = r // new_r
-        t, new_t = new_t, t - quotient * new_t
-        r, new_r = new_r, r - quotient * new_r
-
-    assert r == 1
-    if t < 0:
-        t += p
-
-    return t
-
-class EllipticCurve:
-
-    def __init__(self, p, A, B, order, G, H, J):
-        self.p = p
-        self.A = A
-        self.B = B
-        self.order = order
-        self.G = G
-        self.H = H
-        self.J = J
-        assert self.is_valid(G)
-        assert self.is_valid(H)
-
-    def is_valid(self, P):
-        x, y, z = P
-        if z == 0:
-            return x != 0 or y != 0
-        z_inv = ff_inv(z, self.p)
-        x, y = x * z_inv, y * z_inv
-        return y**2 % self.p == (x**3 + self.A * x + self.B) % self.p
-
-    def add(self, p1, p2):
-        x1, y1, z1 = p1
-        x2, y2, z2 = p2
-
-        if z1 == 0:
-            return (x2, y2, z2)
-        elif z2 == 0:
-            return (x1, y1, z1)
-
-        if x1 == x2:
-            if y1 != y2:
-                return (0, 1, 0)
-
-            assert y1 != 0
-            m = (3 * x1**2 + self.A) * ff_inv(2*y1, self.p)
-        else:
-            m = (y2 - y1) * ff_inv(x2 - x1, self.p)
-
-        x3 = (m**2 - x1 - x2) % self.p
-        y3 = (m * (x1 - x3) - y1) % self.p
-        return (x3, y3, 1)
-
-    def multiply(self, m, p):
-        bits = f"{m:b}"
-        result = (0, 1, 0)
-        temp = p
-        for bit in bits[::-1]:
-            if bit == "1":
-                result = self.add(result, temp)
-            temp = self.add(temp, temp)
-        return result
-
-    def random_point(self):
-        m = self.random_scalar()
-        return self.multiply(m, self.G)
-
-    def random_scalar(self):
-        m = random.randrange(0, self.order - 1)
-        return m
-
-    def random_base(self):
-        m = random.randrange(0, self.p - 1)
-        return m
-
-def pallas_curve():
-    # Pallas
-    p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001
-    q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001
-    G = (5, 5392431450607408583390510508521091931943415030464003135511088002453056875732, 1)
-    H = (9762257241998025279988087154025308614062019274413483967640476725944341089207,
-         12058632856930756995627167820351407063813260358041446014729496773111030695755, 1)
-    J = (7795559447963065356059848000022900528974048197507738248625163674930282081839,
-         5156492880772775379342191094371887365795329446468828588866320184016504353483, 1)
-    ec = EllipticCurve(p, 0, 5, q, G, H, J)
-    A = (144931808354919915876542440378319484704499556634959420306426167479163065488,
-         2699682121356767698440748624399854659825391162912545787181017961871465868196, 1)
-    B = (16017037670495191561606513965775243786961447026019262496667491008912834496943,
-         20395164507282344548629891414360366999207473153143014512687861307997120664849, 1)
-    assert ec.add(A, B) == (2414658659502531855741199170408914396997834981355655923471364687102714431309, 21133344194418979683767005688724798091220515434220043854575260979109407444719, 1)
-    m = 26322809409216846271933211244226061368157231119725763192402071651286829040466
-    assert ec.multiply(m, G) == (15862887453366837597569434439063150886012590021428640083047997467990450633825, 25887284719793568129480941070850220101898092026705204234126448799557008384178, 1)
-    return ec
-
-def pedersen_encrypt(x, y, ec):
-    vcv = ec.multiply(x, ec.G)
-    vcr = ec.multiply(y, ec.H)
-    return ec.add(vcv, vcr)
-
-def _add_to_hasher(hasher, args):
-    for arg in args:
-        match arg:
-            case int() as arg:
-                hasher.update(arg.to_bytes(32, byteorder="little"))
-            case bytes() as arg:
-                hasher.update(arg)
-            case list() as arg:
-                _add_to_hasher(hasher, arg)
-            case _:
-                raise Exception(f"unknown hash arg '{arg}' type: {type(arg)}")
-
-def ff_hash(p, *args):
-    hasher = hashlib.sha256()
-    _add_to_hasher(hasher, args)
-    value = int.from_bytes(hasher.digest(), byteorder="little")
-    return value % p
-
-def hash_point(point, message=None):
-    hasher = hashlib.sha256()
-    for x_i in point:
-        hasher.update(x_i.to_bytes(32, byteorder="little"))
-    # Optional message
-    if message is not None:
-        hasher.update(message)
-    value = int.from_bytes(hasher.digest(), byteorder="little")
-    return value
-
-def sign(message, secret, ec):
-    ephem_secret = ec.random_scalar()
-    ephem_public = ec.multiply(ephem_secret, ec.G)
-    challenge = hash_point(ephem_public, message) % ec.order
-    response = (ephem_secret + challenge * secret) % ec.order
-    return ephem_public, response
-
-def verify(message, signature, public, ec):
-    ephem_public, response = signature
-    challenge = hash_point(ephem_public, message) % ec.order
-    # sG
-    lhs = ec.multiply(response, ec.G)
-    # R + cP
-    rhs_cP = ec.multiply(challenge, public)
-    rhs = ec.add(ephem_public, rhs_cP)
-    return lhs == rhs
-

+ 0 - 1463
example/dao/schema/main.py

@@ -1,1463 +0,0 @@
-import sys
-from classnamespace import ClassNamespace
-
-import crypto, money
-
-class MoneyState:
-
-    def __init__(self):
-        self.all_coins = set()
-        self.nullifiers = set()
-
-    def is_valid_merkle(self, all_coins):
-        return all_coins.issubset(self.all_coins)
-
-    def nullifier_exists(self, nullifier):
-        return nullifier in self.nullifiers
-
-    def apply(self, update):
-        self.nullifiers = self.nullifiers.union(update.nullifiers)
-
-        for coin, enc_note in zip(update.coins, update.enc_notes):
-            self.all_coins.add(coin)
-
-def money_state_transition(state, tx):
-    for input in tx.clear_inputs:
-        pk = input.signature_public
-        # Check pk is correct
-
-    for input in tx.inputs:
-        if not state.is_valid_merkle(input.revealed.all_coins):
-            print(f"invalid merkle root", file=sys.stderr)
-            return None
-
-        nullifier = input.revealed.nullifier
-        if state.nullifier_exists(nullifier):
-            print(f"duplicate nullifier found", file=sys.stderr)
-            return None
-
-    is_verify, reason = tx.verify()
-    if not is_verify:
-        print(f"tx verify failed: {reason}", file=sys.stderr)
-        return None
-
-    update = ClassNamespace()
-    update.nullifiers = [input.revealed.nullifier for input in tx.inputs]
-    update.coins = [output.revealed.coin for output in tx.outputs]
-    update.enc_notes = [output.enc_note for output in tx.outputs]
-    return update
-
-class ProposerTxBuilder:
-
-    def __init__(self, proposal, all_dao_bullas, ec):
-        self.inputs = []
-        self.proposal = proposal
-        self.all_dao_bullas = all_dao_bullas
-
-        self.ec = ec
-
-    def add_input(self, all_coins, secret, note):
-        input = ClassNamespace()
-        input.all_coins = all_coins
-        input.secret = secret
-        input.note = note
-        self.inputs.append(input)
-
-    def set_dao(self, dao):
-        self.dao = dao
-
-    def build(self):
-        tx = ProposerTx(self.ec)
-        token_blind = self.ec.random_scalar()
-        enc_bulla_blind = self.ec.random_base()
-
-        total_value = sum(input.note.value for input in self.inputs)
-        input_value_blinds = [self.ec.random_scalar() for _ in self.inputs]
-        total_value_blinds = sum(input_value_blinds)
-
-        tx.dao = ClassNamespace()
-        tx.dao.__name__ = "ProposerTxDao"
-        # We export proposer_limit as an encrypted value from the DAO
-        tx.dao.proof = ProposerTxDaoProof(
-            # Value commit
-            total_value,
-            total_value_blinds,
-            # DAO params
-            self.dao.proposer_limit,
-            self.dao.quorum,
-            self.dao.approval_ratio,
-            self.dao.gov_token_id,
-            self.dao.public_key,
-            self.dao.bulla_blind,
-            # Token commit
-            token_blind,
-            # Used by other DAO members to verify the bulla
-            # used in this proof is for the actual DAO
-            enc_bulla_blind,
-            # Proposal
-            self.proposal.dest,
-            self.proposal.amount,
-            self.proposal.serial,
-            self.proposal.token_id,
-            self.proposal.blind,
-            # Merkle witness
-            self.all_dao_bullas,
-            self.ec
-        )
-        tx.dao.revealed = tx.dao.proof.get_revealed()
-
-        # Members of the DAO need to themselves verify this is the correct
-        # bulla they are voting on, so we encrypt the blind to them
-        tx.note = ClassNamespace()
-        tx.note.enc_bulla_blind = enc_bulla_blind
-        tx.note.proposal = self.proposal
-
-        signature_secrets = []
-        for input, value_blind in zip(self.inputs, input_value_blinds):
-            signature_secret = self.ec.random_scalar()
-            signature_secrets.append(signature_secret)
-
-            tx_input = ClassNamespace()
-            tx_input.__name__ = "TransactionInput"
-            tx_input.proof = ProposerTxInputProof(
-                input.note.value, input.note.token_id, value_blind,
-                token_blind, input.note.serial, input.note.coin_blind,
-                input.secret, input.note.spend_hook, input.note.user_data,
-                input.all_coins, signature_secret, self.ec)
-            tx_input.revealed = tx_input.proof.get_revealed()
-            tx.inputs.append(tx_input)
-
-        unsigned_tx_data = tx.partial_encode()
-        for (input, signature_secret) in zip(tx.inputs, signature_secrets):
-            signature = crypto.sign(unsigned_tx_data, signature_secret, self.ec)
-            input.signature = signature
-
-        return tx
-
-class ProposerTx:
-
-    def __init__(self, ec):
-        self.inputs = []
-        self.dao = None
-        self.note = None
-
-        self.ec = ec
-
-    def partial_encode(self):
-        # There is no cake
-        return b"hello"
-
-    def verify(self):
-        if not self._check_value_commits():
-            return False, "value commits do not match"
-
-        if not self._check_proofs():
-            return False, "proofs failed to verify"
-
-        if not self._verify_token_commitments():
-            return False, "token ID mismatch"
-
-        unsigned_tx_data = self.partial_encode()
-        for input in self.inputs:
-            public = input.revealed.signature_public
-            if not crypto.verify(unsigned_tx_data, input.signature,
-                                 public, self.ec):
-                return False
-
-        return True, None
-
-    def _check_value_commits(self):
-        valcom_total = (0, 1, 0)
-
-        for input in self.inputs:
-            value_commit = input.revealed.value_commit
-            valcom_total = self.ec.add(valcom_total, value_commit)
-
-        return valcom_total == self.dao.revealed.value_commit
-
-    def _check_proofs(self):
-        for input in self.inputs:
-            if not input.proof.verify(input.revealed):
-                return False
-        if not self.dao.proof.verify(self.dao.revealed):
-            return False
-        return True
-
-    def _verify_token_commitments(self):
-        token_commit_value = self.dao.revealed.token_commit
-        for input in self.inputs:
-            if input.revealed.token_commit != token_commit_value:
-                return False
-        return True
-
-class ProposerTxInputProof:
-
-    def __init__(self, value, token_id, value_blind, token_blind, serial,
-                 coin_blind, secret, spend_hook, user_data,
-                 all_coins, signature_secret, ec):
-        self.value = value
-        self.token_id = token_id
-        self.value_blind = value_blind
-        self.token_blind = token_blind
-        self.serial = serial
-        self.coin_blind = coin_blind
-        self.secret = secret
-        self.spend_hook = spend_hook
-        self.user_data = user_data
-        self.all_coins = all_coins
-        self.signature_secret = signature_secret
-
-        self.ec = ec
-
-    def get_revealed(self):
-        revealed = ClassNamespace()
-
-        revealed.value_commit = crypto.pedersen_encrypt(
-            self.value, self.value_blind, self.ec
-        )
-        revealed.token_commit = crypto.pedersen_encrypt(
-            self.token_id, self.token_blind, self.ec
-        )
-
-        # is_valid_merkle_root()
-        revealed.all_coins = self.all_coins
-
-        revealed.signature_public = self.ec.multiply(self.signature_secret,
-                                                     self.ec.G)
-
-        return revealed
-
-    def verify(self, public):
-        revealed = self.get_revealed()
-
-        public_key = self.ec.multiply(self.secret, self.ec.G)
-        coin = crypto.ff_hash(
-            self.ec.p,
-            public_key[0],
-            public_key[1],
-            self.value,
-            self.token_id,
-            self.serial,
-            self.coin_blind,
-            self.spend_hook,
-            self.user_data,
-        )
-        # Merkle root check
-        if coin not in self.all_coins:
-            return False
-
-        return all([
-            revealed.value_commit == public.value_commit,
-            revealed.token_commit == public.token_commit,
-            revealed.all_coins == public.all_coins,
-            revealed.signature_public == public.signature_public
-        ])
-
-class ProposerTxDaoProof:
-
-    def __init__(self, total_value, total_value_blinds,
-                 proposer_limit, quorum, approval_ratio,
-                 gov_token_id, dao_public_key, dao_bulla_blind,
-                 token_blind, enc_bulla_blind,
-                 proposal_dest, proposal_amount, proposal_serial,
-                 proposal_token_id, proposal_blind,
-                 all_dao_bullas, ec):
-        self.total_value = total_value
-        self.total_value_blinds = total_value_blinds
-        self.proposer_limit = proposer_limit
-        self.quorum = quorum
-        self.approval_ratio = approval_ratio
-        self.gov_token_id = gov_token_id
-        self.dao_public_key = dao_public_key
-        self.dao_bulla_blind = dao_bulla_blind
-        self.token_blind = token_blind
-        self.enc_bulla_blind = enc_bulla_blind
-        self.proposal_dest = proposal_dest
-        self.proposal_amount = proposal_amount
-        self.proposal_serial = proposal_serial
-        self.proposal_token_id = proposal_token_id
-        self.proposal_blind = proposal_blind
-        self.all_dao_bullas = all_dao_bullas
-        self.ec = ec
-
-    def get_revealed(self):
-        revealed = ClassNamespace()
-        # Value commit
-        revealed.value_commit = crypto.pedersen_encrypt(
-            self.total_value, self.total_value_blinds, self.ec
-        )
-        # Token ID
-        revealed.token_commit = crypto.pedersen_encrypt(
-            self.gov_token_id, self.token_blind, self.ec
-        )
-        # encrypted DAO bulla
-        bulla = crypto.ff_hash(
-            self.ec.p,
-            self.proposer_limit,
-            self.quorum,
-            self.approval_ratio,
-            self.gov_token_id,
-            self.dao_public_key[0],
-            self.dao_public_key[1],
-            self.dao_bulla_blind
-        )
-        revealed.enc_bulla = crypto.ff_hash(self.ec.p, bulla, self.enc_bulla_blind)
-        # encrypted proposal
-        revealed.proposal_bulla = crypto.ff_hash(
-            self.ec.p,
-            self.proposal_dest[0],
-            self.proposal_dest[1],
-            self.proposal_amount,
-            self.proposal_serial,
-            self.proposal_token_id,
-            self.proposal_blind,
-            bulla
-        )
-        # The merkle root
-        revealed.all_dao_bullas = self.all_dao_bullas
-        return revealed
-
-    def verify(self, public):
-        revealed = self.get_revealed()
-
-        bulla = crypto.ff_hash(
-            self.ec.p,
-            self.proposer_limit,
-            self.quorum,
-            self.approval_ratio,
-            self.gov_token_id,
-            self.dao_public_key[0],
-            self.dao_public_key[1],
-            self.dao_bulla_blind
-        )
-        # Merkle root check
-        if bulla not in self.all_dao_bullas:
-            return False
-
-        # This should not be able to be bigger than 2^64
-        assert self.proposal_amount > 0
-
-        #
-        #   total_value >= proposer_limit
-        #
-        if not self.total_value >= self.proposer_limit:
-            return False
-
-        return all([
-            revealed.value_commit == public.value_commit,
-            revealed.token_commit == public.token_commit,
-            revealed.enc_bulla == public.enc_bulla,
-            revealed.proposal_bulla == public.proposal_bulla,
-            revealed.all_dao_bullas == public.all_dao_bullas
-        ])
-
-class VoteTxBuilder:
-
-    def __init__(self, ec):
-        self.inputs = []
-        self.vote_option = None
-
-        self.ec = ec
-
-    def add_input(self, all_coins, secret, note):
-        input = ClassNamespace()
-        input.all_coins = all_coins
-        input.secret = secret
-        input.note = note
-        self.inputs.append(input)
-
-    def set_vote_option(self, vote_option):
-        assert vote_option == 0 or vote_option == 1
-        self.vote_option = vote_option
-
-    def build(self):
-        tx = VoteTx(self.ec)
-        token_blind = self.ec.random_scalar()
-
-        assert self.vote_option is not None
-        vote_option_blind = self.ec.random_base()
-
-        total_value, total_blind = 0, 0
-        signature_secrets = []
-        for input in self.inputs:
-            value_blind = self.ec.random_scalar()
-            total_blind = (total_blind + value_blind) % self.ec.order
-            total_value = (total_value + input.note.value) % self.ec.order
-
-            signature_secret = self.ec.random_scalar()
-            signature_secrets.append(signature_secret)
-
-            tx_input = ClassNamespace()
-            tx_input.__name__ = "TransactionInput"
-            tx_input.burn_proof = VoteBurnProof(
-                input.note.value, input.note.token_id, value_blind,
-                token_blind, input.note.serial, input.note.coin_blind,
-                input.secret, input.note.spend_hook, input.note.user_data,
-                input.all_coins, signature_secret,
-                self.ec)
-            tx_input.revealed = tx_input.burn_proof.get_revealed()
-            tx.inputs.append(tx_input)
-
-        assert len(self.inputs) > 0
-        token_id = self.inputs[0].note.token_id
-
-        vote_blind = self.ec.random_scalar()
-
-        # This whole tx is like just burning tokens
-        # except we produce an output commitment to the total value in
-        tx.vote = ClassNamespace()
-        tx.vote.__name__ = "Vote"
-        tx.vote.proof = VoteProof(total_value, token_id,
-                                  total_blind, token_blind, vote_blind,
-                                  self.vote_option, vote_option_blind,
-                                  self.ec)
-        tx.vote.revealed = tx.vote.proof.get_revealed()
-
-        # We can use Shamir's Secret Sharing to unlock this at the end
-        # of the voting, or even with a time delay to avoid timing attacks
-        tx.note = ClassNamespace()
-        tx.note.__name__ = "EncryptedNoteForDaoMembers"
-        tx.note.value = total_value
-        tx.note.token_id = token_id
-        tx.note.vote_option = self.vote_option
-        tx.note.value_blind = total_blind
-        tx.note.token_blind = token_blind
-        tx.note.vote_blind = vote_blind
-        tx.note.vote_option_blind = vote_option_blind
-
-        unsigned_tx_data = tx.partial_encode()
-        for (input, signature_secret) in zip(tx.inputs, signature_secrets):
-            signature = crypto.sign(unsigned_tx_data, signature_secret, self.ec)
-            input.signature = signature
-
-        return tx
-
-class VoteBurnProof:
-
-    def __init__(self, value, token_id,
-                 value_blind, token_blind, serial,
-                 coin_blind, secret, spend_hook, user_data,
-                 all_coins, signature_secret, ec):
-        self.value = value
-        self.token_id = token_id
-        self.value_blind = value_blind
-        self.token_blind = token_blind
-        self.serial = serial
-        self.coin_blind = coin_blind
-        self.secret = secret
-        self.spend_hook = spend_hook
-        self.user_data = user_data
-        self.all_coins = all_coins
-        self.signature_secret = signature_secret
-
-        self.ec = ec
-
-    def get_revealed(self):
-        revealed = ClassNamespace()
-        revealed.nullifier = crypto.ff_hash(self.ec.p, self.secret, self.serial)
-
-        revealed.value_commit = crypto.pedersen_encrypt(
-            self.value, self.value_blind, self.ec
-        )
-
-        revealed.token_commit = crypto.pedersen_encrypt(
-            self.token_id, self.token_blind, self.ec
-        )
-
-        # is_valid_merkle_root()
-        revealed.all_coins = self.all_coins
-
-        revealed.signature_public = self.ec.multiply(self.signature_secret,
-                                                     self.ec.G)
-
-        return revealed
-
-    def verify(self, public):
-        revealed = self.get_revealed()
-
-        public_key = self.ec.multiply(self.secret, self.ec.G)
-        coin = crypto.ff_hash(
-            self.ec.p,
-            public_key[0],
-            public_key[1],
-            self.value,
-            self.token_id,
-            self.serial,
-            self.coin_blind,
-            self.spend_hook,
-            self.user_data,
-        )
-        # Merkle root check
-        if coin not in self.all_coins:
-            return False
-
-        return all([
-            revealed.nullifier == public.nullifier,
-            revealed.value_commit == public.value_commit,
-            revealed.token_commit == public.token_commit,
-            revealed.all_coins == public.all_coins,
-            revealed.signature_public == public.signature_public,
-        ])
-
-class VoteProof:
-
-    def __init__(self, value, token_id,
-                 value_blind, token_blind, vote_blind,
-                 vote_option, vote_option_blind, ec):
-        self.value = value
-        self.token_id = token_id
-        self.value_blind = value_blind
-        self.token_blind = token_blind
-        self.vote_blind = vote_blind
-        self.vote_option = vote_option
-        self.vote_option_blind = vote_option_blind
-        self.ec = ec
-
-    def get_revealed(self):
-        revealed = ClassNamespace()
-        # Multiply the point by vote_option
-        revealed.value_commit = crypto.pedersen_encrypt(
-            self.value, self.value_blind, self.ec
-        )
-        revealed.vote_commit = crypto.pedersen_encrypt(
-            self.vote_option * self.value, self.vote_blind, self.ec
-        )
-        revealed.token_commit = crypto.pedersen_encrypt(
-            self.token_id, self.token_blind, self.ec
-        )
-        #revealed.vote_option_commit = crypto.ff_hash(
-        #    self.ec.p, self.vote_option, self.vote_option_blind
-        #)
-        return revealed
-
-    def verify(self, public):
-        revealed = self.get_revealed()
-        # vote option should be 0 or 1
-        if ((self.vote_option - 0) * (self.vote_option - 1)) % self.ec.p != 0:
-            return False
-        return all([
-            revealed.value_commit == public.value_commit,
-            revealed.vote_commit == public.vote_commit,
-            revealed.token_commit == public.token_commit,
-            #revealed.vote_option_commit == public.vote_option_commit
-        ])
-
-class VoteTx:
-
-    def __init__(self, ec):
-        self.inputs = []
-        self.vote = None
-
-        self.ec = ec
-
-    def partial_encode(self):
-        # There is no cake
-        return b"hello"
-
-    def verify(self):
-        if not self._check_value_commits():
-            return False, "value commits do not match"
-
-        if not self._check_proofs():
-            return False, "proofs failed to verify"
-
-        if not self._verify_token_commitments():
-            return False, "token ID mismatch"
-
-        return True, None
-
-    def _check_value_commits(self):
-        valcom_total = (0, 1, 0)
-        for input in self.inputs:
-            value_commit = input.revealed.value_commit
-            valcom_total = self.ec.add(valcom_total, value_commit)
-
-        return valcom_total == self.vote.revealed.value_commit
-
-    def _check_proofs(self):
-        for input in self.inputs:
-            if not input.burn_proof.verify(input.revealed):
-                return False
-        if not self.vote.proof.verify(self.vote.revealed):
-            return False
-        return True
-
-    def _verify_token_commitments(self):
-        token_commit_value = self.vote.revealed.token_commit
-        for input in self.inputs:
-            if input.revealed.token_commit != token_commit_value:
-                return False
-        return True
-
-class DaoBuilder:
-
-    def __init__(self, proposer_limit, quorum, approval_ratio,
-                 gov_token_id, dao_public_key, dao_bulla_blind, ec):
-        self.proposer_limit = proposer_limit
-        self.quorum = quorum
-        self.approval_ratio = approval_ratio
-        self.gov_token_id = gov_token_id
-        self.dao_public_key = dao_public_key
-        self.dao_bulla_blind = dao_bulla_blind
-
-        self.ec = ec
-
-    def build(self):
-        mint_proof = DaoMintProof(
-            self.proposer_limit,
-            self.quorum,
-            self.approval_ratio,
-            self.gov_token_id,
-            self.dao_public_key,
-            self.dao_bulla_blind,
-            self.ec
-        )
-        revealed = mint_proof.get_revealed()
-
-        dao = Dao(revealed, mint_proof, self.ec)
-        return dao
-
-class Dao:
-
-    def __init__(self, revealed, mint_proof, ec):
-        self.revealed = revealed
-        self.mint_proof = mint_proof
-        self.ec = ec
-
-    def verify(self):
-        if not self.mint_proof.verify(self.revealed):
-            return False, "mint proof failed to verify"
-        return True, None
-
-# class DaoExec .etc
-
-class DaoMintProof:
-
-    def __init__(self, proposer_limit, quorum, approval_ratio,
-                 gov_token_id, dao_public_key, dao_bulla_blind, ec):
-        self.proposer_limit = proposer_limit
-        self.quorum = quorum
-        self.approval_ratio = approval_ratio
-        self.gov_token_id = gov_token_id
-        self.dao_public_key = dao_public_key
-        self.dao_bulla_blind = dao_bulla_blind
-        self.ec = ec
-
-    def get_revealed(self):
-        revealed = ClassNamespace()
-
-        revealed.bulla = crypto.ff_hash(
-            self.ec.p,
-            self.proposer_limit,
-            self.quorum,
-            self.approval_ratio,
-            self.gov_token_id,
-            self.dao_public_key[0],
-            self.dao_public_key[1],
-            self.dao_bulla_blind
-        )
-
-        return revealed
-
-    def verify(self, public):
-        revealed = self.get_revealed()
-        return revealed.bulla == public.bulla
-
-# Shared between DaoMint and DaoExec
-class DaoState:
-
-    def __init__(self):
-        self.dao_bullas = set()
-        self.proposals = set()
-        # Closed proposals
-        self.proposal_nullifiers = set()
-
-    def is_valid_merkle(self, all_dao_bullas):
-        return all_dao_bullas.issubset(self.dao_bullas)
-
-    def is_valid_merkle_proposals(self, all_proposal_bullas):
-        return all_proposal_bullas.issubset(self.proposals)
-
-    def proposal_nullifier_exists(self, nullifier):
-        return nullifier in self.proposal_nullifiers
-
-    def apply_proposal_tx(self, update):
-        self.proposals.add(update.proposal)
-
-    def apply_exec_tx(self, update):
-        self.proposal_nullifiers.add(update.proposal_nullifier)
-
-    # Apply DAO mint tx update
-    def apply(self, update):
-        self.dao_bullas.add(update.bulla)
-
-# contract interface functions
-def dao_state_transition(state, tx):
-    is_verify, reason = tx.verify()
-    if not is_verify:
-        print(f"dao tx verify failed: {reason}", file=sys.stderr)
-        return None
-
-    update = ClassNamespace()
-    update.bulla = tx.revealed.bulla
-    return update
-
-###### DAO EXEC
-
-class DaoExecBuilder:
-
-    def __init__(self,
-        proposal,
-        all_proposals,
-        dao,
-        win_votes,
-        total_votes,
-        total_value_blinds,
-        total_vote_blinds,
-        pay_tx_serial_0,
-        pay_tx_serial_1,
-        pay_tx_coin_blind_0,
-        pay_tx_coin_blind_1,
-        pay_tx_input_value,
-        pay_tx_input_blinds,
-        ec
-    ):
-        self.proposal = proposal
-        self.all_proposals = all_proposals
-        self.dao = dao
-        self.win_votes = win_votes
-        self.total_votes = total_votes
-        self.total_value_blinds = total_value_blinds
-        self.total_vote_blinds = total_vote_blinds
-        self.pay_tx_serial_0 = pay_tx_serial_0
-        self.pay_tx_serial_1 = pay_tx_serial_1
-        self.pay_tx_coin_blind_0 = pay_tx_coin_blind_0
-        self.pay_tx_coin_blind_1 = pay_tx_coin_blind_1
-        self.pay_tx_input_value = pay_tx_input_value
-        self.pay_tx_input_blinds = pay_tx_input_blinds
-
-        self.ec = ec
-
-    def build(self):
-        tx = DaoExecTx()
-        tx.proof = DaoExecProof(
-            self.proposal,
-            self.all_proposals,
-            self.dao,
-            self.win_votes,
-            self.total_votes,
-            self.total_value_blinds,
-            self.total_vote_blinds,
-            self.pay_tx_serial_0,
-            self.pay_tx_serial_1,
-            self.pay_tx_coin_blind_0,
-            self.pay_tx_coin_blind_1,
-            self.pay_tx_input_value,
-            self.pay_tx_input_blinds,
-            self.ec
-        )
-        tx.revealed = tx.proof.get_revealed()
-        return tx
-
-class DaoExecTx:
-
-    def verify(self):
-        if not self._check_proofs():
-            return False, "proofs failed to verify"
-
-        return True, None
-
-    def _check_proofs(self):
-        if not self.proof.verify(self.revealed):
-            return False
-        return True
-
-class DaoExecProof:
-
-    def __init__(self,
-        proposal,
-        all_proposals,
-        dao,
-        win_votes,
-        total_votes,
-        total_value_blinds,
-        total_vote_blinds,
-        pay_tx_serial_0,
-        pay_tx_serial_1,
-        pay_tx_coin_blind_0,
-        pay_tx_coin_blind_1,
-        pay_tx_input_value,
-        pay_tx_input_blinds,
-        ec
-    ):
-        self.proposal = proposal
-        self.all_proposals = all_proposals
-        self.dao = dao
-        self.win_votes = win_votes
-        self.total_votes = total_votes
-        self.total_value_blinds = total_value_blinds
-        self.total_vote_blinds = total_vote_blinds
-        self.pay_tx_serial_0 = pay_tx_serial_0
-        self.pay_tx_serial_1 = pay_tx_serial_1
-        self.pay_tx_coin_blind_0 = pay_tx_coin_blind_0
-        self.pay_tx_coin_blind_1 = pay_tx_coin_blind_1
-        self.pay_tx_input_value = pay_tx_input_value
-        self.pay_tx_input_blinds = pay_tx_input_blinds
-
-        self.ec = ec
-
-    def get_revealed(self):
-        revealed = ClassNamespace()
-        # Corresponds to proposals merkle root
-        revealed.all_proposals = self.all_proposals
-
-        dao_bulla = crypto.ff_hash(
-            self.ec.p,
-            self.dao.proposer_limit,
-            self.dao.quorum,
-            self.dao.approval_ratio,
-            self.dao.gov_token_id,
-            self.dao.public_key[0],
-            self.dao.public_key[1],
-            self.dao.bulla_blind
-        )
-        proposal_bulla = crypto.ff_hash(
-            self.ec.p,
-            self.proposal.dest[0],
-            self.proposal.dest[1],
-            self.proposal.amount,
-            self.proposal.serial,
-            self.proposal.token_id,
-            self.proposal.blind,
-            dao_bulla
-        )
-        revealed.proposal_nullifier = crypto.ff_hash(
-            self.ec.p, self.proposal.serial)
-
-        revealed.coin_0 = crypto.ff_hash(
-            self.ec.p,
-            self.proposal.dest[0],
-            self.proposal.dest[1],
-            self.proposal.amount,
-            self.proposal.token_id,
-            self.pay_tx_serial_0,
-            self.pay_tx_coin_blind_0,
-            b"0x0000",
-            b"0x0000"
-        )
-
-        change_amount = self.pay_tx_input_value - self.proposal.amount
-        assert change_amount > 0
-
-        # Need the same DAO public key
-        # Need the input amount for pay_tx for treasury
-        # Need user_data blind
-        revealed.coin_1 = crypto.ff_hash(
-            self.ec.p,
-            self.dao.public_key[0],
-            self.dao.public_key[1],
-            change_amount,
-            self.proposal.token_id,
-            self.pay_tx_serial_1,
-            self.pay_tx_coin_blind_1,
-            b"0xdao_ruleset",
-            dao_bulla
-        )
-
-        # Money that went into the pay tx
-        revealed.inputs_value_commit = crypto.pedersen_encrypt(
-            self.pay_tx_input_value, self.pay_tx_input_blinds, self.ec)
-
-        revealed.total_value_commit = crypto.pedersen_encrypt(
-            self.total_votes, self.total_value_blinds, self.ec)
-        revealed.total_vote_commit = crypto.pedersen_encrypt(
-            self.win_votes, self.total_vote_blinds, self.ec)
-
-        return revealed
-
-    def verify(self, public):
-        revealed = self.get_revealed()
-
-        # Check proposal exists
-        dao_bulla = crypto.ff_hash(
-            self.ec.p,
-            self.dao.proposer_limit,
-            self.dao.quorum,
-            self.dao.approval_ratio,
-            self.dao.gov_token_id,
-            self.dao.public_key[0],
-            self.dao.public_key[1],
-            self.dao.bulla_blind
-        )
-        proposal_bulla = crypto.ff_hash(
-            self.ec.p,
-            self.proposal.dest[0],
-            self.proposal.dest[1],
-            self.proposal.amount,
-            self.proposal.serial,
-            self.proposal.token_id,
-            self.proposal.blind,
-            dao_bulla
-        )
-        # This being true also implies the DAO is valid
-        assert proposal_bulla in self.all_proposals
-
-        assert self.total_votes >= self.dao.quorum
-
-        # Approval ratio should be actually 2 values ffs
-        #assert self.win_votes / self.total_votes >= self.dao.approval_ratio
-        assert self.win_votes >= self.dao.approval_ratio * self.total_votes
-
-        return all([
-            revealed.all_proposals == public.all_proposals,
-            revealed.proposal_nullifier == public.proposal_nullifier,
-            revealed.coin_0 == public.coin_0,
-            revealed.coin_1 == public.coin_1,
-            revealed.inputs_value_commit == public.inputs_value_commit,
-            revealed.total_value_commit == public.total_value_commit,
-            revealed.total_vote_commit == public.total_vote_commit,
-        ])
-
-def dao_exec_state_transition(state, tx, pay_tx, ec):
-    is_verify, reason = tx.verify()
-    if not is_verify:
-        print(f"dao exec tx verify failed: {reason}", file=sys.stderr)
-        return None
-
-    if not state.is_valid_merkle_proposals(tx.revealed.all_proposals):
-        print(f"invalid merkle root proposals", file=sys.stderr)
-        return None
-
-    nullifier = tx.revealed.proposal_nullifier
-    if state.proposal_nullifier_exists(nullifier):
-        print(f"duplicate nullifier found", file=sys.stderr)
-        return None
-
-    # Check the structure of the payment tx is correct
-    if len(pay_tx.outputs) != 2:
-        print(f"only 2 outputs allowed", file=sys.stderr)
-        return None
-    if tx.revealed.coin_0 != pay_tx.outputs[0].revealed.coin:
-        print(f"coin0 incorrectly formed", file=sys.stderr)
-        return None
-
-    inputs_value_commit = (0, 1, 0)
-    for input in pay_tx.inputs:
-        value_commit = input.revealed.value_commit
-        inputs_value_commit = ec.add(inputs_value_commit, value_commit)
-    if inputs_value_commit != tx.revealed.inputs_value_commit:
-        print(f"value commitment for inputs doesn't match", file=sys.stderr)
-        return None
-
-    if tx.revealed.coin_1 != pay_tx.outputs[1].revealed.coin:
-        print(f"coin1 incorrectly formed", file=sys.stderr)
-        return None
-
-    update = ClassNamespace()
-    update.proposal_nullifier = tx.revealed.proposal_nullifier
-    return update
-
-# contract interface functions
-def proposal_state_transition(dao_state, gov_state, tx):
-    is_verify, reason = tx.verify()
-    if not is_verify:
-        print(f"dao tx verify failed: {reason}", file=sys.stderr)
-        return None
-
-    if not dao_state.is_valid_merkle(tx.dao.revealed.all_dao_bullas):
-        print(f"invalid merkle root dao", file=sys.stderr)
-        return None
-
-    for input in tx.inputs:
-        if not gov_state.is_valid_merkle(input.revealed.all_coins):
-            print(f"invalid merkle root", file=sys.stderr)
-            return None
-
-    update = ClassNamespace()
-    update.proposal = tx.dao.revealed.proposal_bulla
-    return update
-
-class VoteState:
-
-    def __init__(self):
-        self.votes = set()
-        self.nullifiers = set()
-
-    def nullifier_exists(self, nullifier):
-        return nullifier in self.nullifiers
-
-    def apply(self, update):
-        self.nullifiers = self.nullifiers.union(update.nullifiers)
-        self.votes.add(update.vote)
-
-def vote_state_transition(vote_state, gov_state, tx):
-    for input in tx.inputs:
-        if not gov_state.is_valid_merkle(input.revealed.all_coins):
-            print(f"invalid merkle root", file=sys.stderr)
-            return None
-
-        nullifier = input.revealed.nullifier
-        if gov_state.nullifier_exists(nullifier):
-            print(f"duplicate nullifier found", file=sys.stderr)
-            return None
-
-        if vote_state.nullifier_exists(nullifier):
-            print(f"duplicate nullifier found (already voted)", file=sys.stderr)
-            return None
-
-    is_verify, reason = tx.verify()
-    if not is_verify:
-        print(f"dao tx verify failed: {reason}", file=sys.stderr)
-        return None
-
-    update = ClassNamespace()
-    update.nullifiers = [input.revealed.nullifier for input in tx.inputs]
-    update.vote = tx.vote.revealed.value_commit
-    return update
-
-def main(argv):
-    ec = crypto.pallas_curve()
-
-    money_state = MoneyState()
-    gov_state = MoneyState()
-    dao_state = DaoState()
-
-    # Money parameters
-    money_initial_supply = 21000
-    money_token_id = 110
-
-    # Governance token parameters
-    gov_initial_supply = 10000
-    gov_token_id = 4
-
-    # DAO parameters
-    dao_proposer_limit = 110
-    dao_quorum = 110
-    dao_approval_ratio = 2
-
-    ################################################
-    # Create the DAO bulla
-    ################################################
-    # Setup the DAO
-    dao_shared_secret = ec.random_scalar()
-    dao_public_key = ec.multiply(dao_shared_secret, ec.G)
-
-    dao_bulla_blind = ec.random_base()
-
-    builder = DaoBuilder(
-        dao_proposer_limit,
-        dao_quorum,
-        dao_approval_ratio,
-        gov_token_id,
-        dao_public_key,
-        dao_bulla_blind,
-        ec
-    )
-    tx = builder.build()
-
-    # Each deployment of a contract has a unique state
-    # associated with it.
-    if (update := dao_state_transition(dao_state, tx)) is None:
-        return -1
-    dao_state.apply(update)
-
-    dao_bulla = tx.revealed.bulla
-
-    ################################################
-    # Mint the initial supply of treasury token
-    # and send it all to the DAO directly
-    ################################################
-
-    # Only used for this tx. Discarded after
-    signature_secret = ec.random_scalar()
-
-    builder = money.SendPaymentTxBuilder(ec)
-    builder.add_clear_input(money_initial_supply, money_token_id,
-                            signature_secret)
-    # Address of deployed contract in our example is 0xdao_ruleset
-    # This field is public, you can see it's being sent to a DAO
-    # but nothing else is visible.
-    spend_hook = b"0xdao_ruleset"
-    # This 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
-    user_data = dao_bulla
-    builder.add_output(money_initial_supply, money_token_id, dao_public_key,
-                       spend_hook, user_data)
-    tx = builder.build()
-
-    # This state_transition function is the ruleset for anon payments
-    if (update := money_state_transition(money_state, tx)) is None:
-        return -1
-    money_state.apply(update)
-
-    # NOTE: maybe we want to add additional zk proof here that the tx
-    #       sending money to the DAO was constructed correctly.
-    #       For example that the user_data is set correctly
-
-    # payment state transition in coin specifies dependency
-    # the tx exists and ruleset is applied
-
-    assert len(tx.outputs) > 0
-    coin_note = tx.outputs[0].enc_note
-    coin = crypto.ff_hash(
-        ec.p,
-        dao_public_key[0],
-        dao_public_key[1],
-        coin_note.value,
-        coin_note.token_id,
-        coin_note.serial,
-        coin_note.coin_blind,
-        spend_hook,
-        user_data
-    )
-    assert coin == tx.outputs[0].mint_proof.get_revealed().coin
-
-    for coin, enc_note in zip(update.coins, update.enc_notes):
-        # Try decrypt note here
-        print(f"Received {enc_note.value} DRK")
-
-    ################################################
-    # Mint the governance token
-    # Send it to three hodlers
-    ################################################
-
-    # Hodler 1
-    gov_secret_1 = ec.random_scalar()
-    gov_public_1 = ec.multiply(gov_secret_1, ec.G)
-    # Hodler 2
-    gov_secret_2 = ec.random_scalar()
-    gov_public_2 = ec.multiply(gov_secret_2, ec.G)
-    # Hodler 3: the tiebreaker
-    gov_secret_3 = ec.random_scalar()
-    gov_public_3 = ec.multiply(gov_secret_3, ec.G)
-
-    # Only used for this tx. Discarded after
-    signature_secret = ec.random_scalar()
-
-    builder = money.SendPaymentTxBuilder(ec)
-    builder.add_clear_input(gov_initial_supply, gov_token_id,
-                            signature_secret)
-    assert 2 * 4000 + 2000 == gov_initial_supply
-    builder.add_output(4000, gov_token_id, gov_public_1,
-                       b"0x0000", b"0x0000")
-    builder.add_output(4000, gov_token_id, gov_public_2,
-                       b"0x0000", b"0x0000")
-    builder.add_output(2000, gov_token_id, gov_public_3,
-                       b"0x0000", b"0x0000")
-    tx = builder.build()
-
-    # This state_transition function is the ruleset for anon payments
-    if (update := money_state_transition(gov_state, tx)) is None:
-        return -1
-    gov_state.apply(update)
-
-    # Decrypt output notes
-    assert len(tx.outputs) == 3
-    gov_user_1_note = tx.outputs[0].enc_note
-    gov_user_2_note = tx.outputs[1].enc_note
-    gov_user_3_note = tx.outputs[2].enc_note
-
-    for coin, enc_note in zip(update.coins, update.enc_notes):
-        # Try decrypt note here
-        print(f"Received {enc_note.value} GOV")
-
-    ################################################
-    # DAO rules:
-    # 1. gov token IDs must match on all inputs
-    # 2. proposals must be submitted by minimum amount
-    #       - need protection so can't collude? must be a single signer??
-    #         - stellar: doesn't have to be robust for this MVP
-    # 3. number of votes >= quorum
-    #       - just positive votes or all votes?
-    #         - stellar: no that's all votes
-    # 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
-    ################################################
-
-    user_secret = ec.random_scalar()
-    user_public = ec.multiply(user_secret, ec.G)
-
-    # There is a struct that corresponds to the configuration of this
-    # particular vote.
-    # For MVP, just use a single-option list of [destination, amount]
-    # Send user 1000 DRK
-    proposal = ClassNamespace()
-    proposal.dest = user_public
-    proposal.amount = 1000
-    # Used to produce the nullifier when the vote is executed
-    proposal.serial = ec.random_base()
-    proposal.token_id = money_token_id
-    proposal.blind = ec.random_base()
-
-    # For vote to become valid, the proposer must prove
-    # that they own more than proposer_limit number of gov tokens.
-
-    dao = ClassNamespace()
-    dao.proposer_limit = dao_proposer_limit
-    dao.quorum = dao_quorum
-    dao.approval_ratio = dao_approval_ratio
-    dao.gov_token_id = gov_token_id
-    dao.public_key = dao_public_key
-    dao.bulla_blind = dao_bulla_blind
-
-    builder = ProposerTxBuilder(proposal, dao_state.dao_bullas, ec)
-    witness = gov_state.all_coins
-    builder.add_input(witness, gov_secret_1, gov_user_1_note)
-    builder.set_dao(dao)
-    tx = builder.build()
-
-    # No state changes actually happen so ignore the update
-    # We just verify the tx is correct basically.
-    if (update := proposal_state_transition(dao_state, gov_state, tx)) is None:
-        return -1
-    dao_state.apply_proposal_tx(update)
-
-    ################################################
-    # Proposal is accepted!
-    # Start the voting
-    ################################################
-
-    # 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
-
-    # User 1: YES
-    builder = VoteTxBuilder(ec)
-    builder.add_input(witness, gov_secret_1, gov_user_1_note)
-    builder.set_vote_option(1)
-    tx1 = builder.build()
-
-    if (update := vote_state_transition(vote_state, gov_state, tx1)) is None:
-        return -1
-    vote_state.apply(update)
-
-    note_vote_1 = tx1.note
-
-    # User 2: NO
-    builder = VoteTxBuilder(ec)
-    builder.add_input(witness, gov_secret_2, gov_user_2_note)
-    builder.set_vote_option(0)
-    tx2 = builder.build()
-
-    if (update := vote_state_transition(vote_state, gov_state, tx2)) is None:
-        return -1
-    vote_state.apply(update)
-
-    note_vote_2 = tx2.note
-
-    # User 3: YES
-    builder = VoteTxBuilder(ec)
-    builder.add_input(witness, gov_secret_3, gov_user_3_note)
-    builder.set_vote_option(1)
-    tx3 = builder.build()
-
-    if (update := vote_state_transition(vote_state, gov_state, tx3)) is None:
-        return -1
-    vote_state.apply(update)
-
-    note_vote_3 = tx3.note
-
-    # State
-    # functions that can be called on state with params
-    # functions return an update
-    # optional encrypted values that can be read by wallets
-    # --> (do this outside??)
-    # --> penalized if fail
-    # apply update to state
-
-    # 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)
-
-    win_votes = 0
-    total_votes = 0
-    total_vote_blinds = 0
-    total_value_blinds = 0
-    total_value_commit = (0, 1, 0)
-    total_vote_commit = (0, 1, 0)
-    for i, (note, tx) in enumerate(
-        zip([note_vote_1, note_vote_2, note_vote_3], [tx1, tx2, tx3])):
-
-        assert note.token_id == gov_token_id
-        token_commit = crypto.pedersen_encrypt(
-            gov_token_id, note.token_blind, ec)
-        assert tx.vote.revealed.token_commit == token_commit
-
-        #vote_option_commit = crypto.ff_hash(
-        #    ec.p, note.vote_option, note.vote_option_blind)
-        #assert tx.vote.revealed.vote_option_commit == vote_option_commit
-
-        value_commit = crypto.pedersen_encrypt(
-            note.value, note.value_blind, ec)
-        assert tx.vote.revealed.value_commit == value_commit
-        total_value_commit = ec.add(total_value_commit, value_commit)
-        total_value_blinds += note.value_blind
-        
-        vote_commit = crypto.pedersen_encrypt(
-            note.vote_option * note.value, note.vote_blind, ec)
-        assert tx.vote.revealed.vote_commit == vote_commit
-        total_vote_commit = ec.add(total_vote_commit, vote_commit)
-        total_vote_blinds += note.vote_blind
-
-        vote_option = note.vote_option
-        assert vote_option == 0 or vote_option == 1
-
-        if vote_option == 1:
-            win_votes += note.value
-
-        total_votes += note.value
-
-        if vote_option == 1:
-            vote_result = "yes"
-        else:
-            vote_result = "no"
-        print(f"Voter {i} voted {vote_result}")
-
-    print(f"Outcome = {win_votes} / {total_votes}")
-
-    assert total_value_commit == crypto.pedersen_encrypt(
-        total_votes, total_value_blinds, ec)
-    assert total_vote_commit == crypto.pedersen_encrypt(
-        win_votes, total_vote_blinds, ec)
-
-    ################################################
-    # Execute the vote
-    ################################################
-
-    # Used to export user_data from this coin so it can be accessed
-    # by 0xdao_ruleset
-    user_data_blind = ec.random_base()
-
-    builder = money.SendPaymentTxBuilder(ec)
-    witness = money_state.all_coins
-    builder.add_input(witness, dao_shared_secret, coin_note, user_data_blind)
-
-    builder.add_output(1000, money_token_id, user_public,
-                       spend_hook=b"0x0000", user_data=b"0x0000")
-    # Change
-    builder.add_output(coin_note.value - 1000, money_token_id, dao_public_key,
-                       spend_hook, user_data)
-
-    tx = builder.build()
-
-    if (update := money_state_transition(money_state, tx)) is None:
-        return -1
-    money_state.apply(update)
-
-    # Now the spend_hook field specifies the function DaoExec
-    # so the tx above must also be combined with a DaoExec tx
-    assert len(tx.inputs) == 1
-    # At least one input has this field value which means the 0xdao_ruleset
-    # is invoked.
-    input = tx.inputs[0]
-    assert input.revealed.spend_hook == b"0xdao_ruleset"
-    assert (input.revealed.enc_user_data ==
-        crypto.ff_hash(
-            ec.p,
-            user_data,
-            user_data_blind
-        ))
-    # Verifier cannot see DAO bulla
-    # They see the enc_user_data which is also in the DAO exec contract
-    assert user_data == crypto.ff_hash(
-        ec.p,
-        dao_proposer_limit,
-        dao_quorum,
-        dao_approval_ratio,
-        gov_token_id,
-        dao_public_key[0],
-        dao_public_key[1],
-        dao_bulla_blind
-    ) # DAO bulla
-
-    pay_tx = tx
-
-    # execution proof
-    # 1. total votes >= quorum
-    # 2. win_votes / total_votes >= approval_ratio
-    # 3. structure of outputs
-    #   output 0: value and address
-    #   output 1: change address
-
-    # - check proposal exists
-    # - create proposal nullifier
-    #     - verifier: check it doesn't already exist
-    # - check dest, amount, token_id match
-    #     - export both output value_commits
-    #     - export token_id commit used in send_payment tx
-    #     - export output 0 and 1 dest
-    #     - check all these fields match the tx
-    # - is linked to DAO
-    #     - read DAO params
-    #     - re-export as enc_user_data
-    #     - verifier: check it matches the tx
-    # - total_votes >= quorum
-    #     - verifier: check sum of vote_commits is correct
-    # - win_votes / total_votes >= approval_ratio
-
-    assert len(pay_tx.outputs) == 2
-    pay_tx_serial_0 = pay_tx.outputs[0].enc_note.serial
-    pay_tx_serial_1 = pay_tx.outputs[1].enc_note.serial
-    pay_tx_coin_blind_0 = pay_tx.outputs[0].enc_note.coin_blind
-    pay_tx_coin_blind_1 = pay_tx.outputs[1].enc_note.coin_blind
-    pay_tx_input_value = coin_note.value
-    pay_tx_input_blinds = sum(builder.input_blinds) % ec.order
-
-    builder = DaoExecBuilder(
-        proposal,
-        dao_state.proposals,
-        dao,
-        win_votes,
-        total_votes,
-        total_value_blinds,
-        total_vote_blinds,
-        pay_tx_serial_0,
-        pay_tx_serial_1,
-        pay_tx_coin_blind_0,
-        pay_tx_coin_blind_1,
-        pay_tx_input_value,
-        pay_tx_input_blinds,
-        ec
-    )
-    tx = builder.build()
-    if (update := dao_exec_state_transition(dao_state, tx, pay_tx, ec)) is None:
-        return -1
-    dao_state.apply_exec_tx(update)
-
-    # These checks are also run by the verifier
-    assert tx.revealed.total_value_commit == total_value_commit
-    assert tx.revealed.total_vote_commit == total_vote_commit
-
-    return 0
-
-if __name__ == "__main__":
-    sys.exit(main(sys.argv))
-

+ 0 - 331
example/dao/schema/money.py

@@ -1,331 +0,0 @@
-from classnamespace import ClassNamespace
-from crypto import ff_hash, pedersen_encrypt, sign, verify
-
-# Tx representing send_payment() contract call
-class SendPaymentTxBuilder:
-
-    def __init__(self, ec):
-        self.clear_inputs = []
-        self.inputs = []
-        self.outputs = []
-        self.input_blinds = []
-        self.output_blinds = []
-
-        self.ec = ec
-
-    def add_clear_input(self, value, token_id, signature_secret):
-        clear_input = ClassNamespace()
-        clear_input.value = value
-        clear_input.token_id = token_id
-        clear_input.signature_secret = signature_secret
-        self.clear_inputs.append(clear_input)
-
-    def add_input(self, all_coins, secret, note, user_data_blind):
-        input = ClassNamespace()
-        input.all_coins = all_coins
-        input.secret = secret
-        input.note = note
-        input.user_data_blind = user_data_blind
-        self.inputs.append(input)
-
-    def add_output(self, value, token_id, public, spend_hook, user_data):
-        output = ClassNamespace()
-        output.value = value
-        output.token_id = token_id
-        output.public = public
-        output.spend_hook = spend_hook
-        output.user_data = user_data
-        self.outputs.append(output)
-
-    def compute_remainder_blind(self, clear_inputs, input_blinds,
-                                output_blinds):
-        total = 0
-        total += sum(input.value_blind for input in clear_inputs)
-        total += sum(input_blinds)
-        total -= sum(output_blinds)
-        return total % self.ec.order
-
-    def build(self):
-        tx = SendPaymentTx(self.ec)
-        token_blind = self.ec.random_scalar()
-
-        for input in self.clear_inputs:
-            tx_clear_input = ClassNamespace()
-            tx_clear_input.__name__ = "TransactionClearInput"
-            tx_clear_input.value = input.value
-            tx_clear_input.token_id = input.token_id
-            tx_clear_input.value_blind = self.ec.random_scalar()
-            tx_clear_input.token_blind = token_blind
-            tx_clear_input.signature_public = self.ec.multiply(
-                input.signature_secret, self.ec.G)
-            tx.clear_inputs.append(tx_clear_input)
-
-        self.input_blinds = []
-        signature_secrets = []
-        for input in self.inputs:
-            value_blind = self.ec.random_scalar()
-            self.input_blinds.append(value_blind)
-
-            signature_secret = self.ec.random_scalar()
-            signature_secrets.append(signature_secret)
-
-            tx_input = ClassNamespace()
-            tx_input.__name__ = "TransactionInput"
-            tx_input.burn_proof = BurnProof(
-                input.note.value, input.note.token_id, value_blind,
-                token_blind, input.note.serial, input.note.coin_blind,
-                input.secret, input.note.spend_hook, input.note.user_data,
-                input.user_data_blind, input.all_coins, signature_secret,
-                self.ec)
-            tx_input.revealed = tx_input.burn_proof.get_revealed()
-            tx.inputs.append(tx_input)
-
-        assert self.outputs
-        self.output_blinds = []
-        for i, output in enumerate(self.outputs):
-            if i == len(self.outputs) - 1:
-                value_blind = self.compute_remainder_blind(
-                    tx.clear_inputs, self.input_blinds, self.output_blinds)
-            else:
-                value_blind = self.ec.random_scalar()
-            self.output_blinds.append(value_blind)
-
-            note = ClassNamespace()
-            note.serial = self.ec.random_base()
-            note.value = output.value
-            note.token_id = output.token_id
-            note.coin_blind = self.ec.random_base()
-            note.value_blind = value_blind
-            note.token_blind = token_blind
-            note.spend_hook = output.spend_hook
-            note.user_data = output.user_data
-
-            tx_output = ClassNamespace()
-            tx_output.__name__ = "TransactionOutput"
-
-            tx_output.mint_proof = MintProof(
-                note.value, note.token_id, note.value_blind,
-                note.token_blind, note.serial, note.coin_blind,
-                output.public, output.spend_hook, output.user_data, self.ec)
-            tx_output.revealed = tx_output.mint_proof.get_revealed()
-            assert tx_output.mint_proof.verify(tx_output.revealed)
-
-            # Is normally encrypted
-            tx_output.enc_note = note
-            tx_output.enc_note.__name__ = "TransactionOutputEncryptedNote"
-
-            tx.outputs.append(tx_output)
-
-        unsigned_tx_data = tx.partial_encode()
-        for (input, info) in zip(tx.clear_inputs, self.clear_inputs):
-            secret = info.signature_secret
-            signature = sign(unsigned_tx_data, secret, self.ec)
-            input.signature = signature
-        for (input, signature_secret) in zip(tx.inputs, signature_secrets):
-            signature = sign(unsigned_tx_data, signature_secret, self.ec)
-            input.signature = signature
-
-        return tx
-
-# Transaction representing Money::send_payment() function call
-class SendPaymentTx:
-
-    def __init__(self, ec):
-        self.clear_inputs = []
-        self.inputs = []
-        self.outputs = []
-
-        self.ec = ec
-
-    def partial_encode(self):
-        # There is no cake
-        return b"hello"
-
-    def verify(self):
-        if not self._check_value_commits():
-            return False, "value commits do not match"
-
-        if not self._check_proofs():
-            return False, "proofs failed to verify"
-
-        if not self._verify_token_commitments():
-            return False, "token ID mismatch"
-
-        unsigned_tx_data = self.partial_encode()
-        for input in self.clear_inputs:
-            public = input.signature_public
-            if not verify(unsigned_tx_data, input.signature, public, self.ec):
-                return False
-        for input in self.inputs:
-            public = input.revealed.signature_public
-            if not verify(unsigned_tx_data, input.signature, public, self.ec):
-                return False
-
-        return True, None
-
-    def _check_value_commits(self):
-        valcom_total = (0, 1, 0)
-
-        for input in self.clear_inputs:
-            value_commit = pedersen_encrypt(input.value, input.value_blind,
-                                            self.ec)
-            valcom_total = self.ec.add(valcom_total, value_commit)
-        for input in self.inputs:
-            value_commit = input.revealed.value_commit
-            valcom_total = self.ec.add(valcom_total, value_commit)
-        for output in self.outputs:
-            v = output.revealed.value_commit
-            value_commit = (v[0], -v[1], v[2])
-            valcom_total = self.ec.add(valcom_total, value_commit)
-
-        return valcom_total == (0, 1, 0)
-
-    def _check_proofs(self):
-        for input in self.inputs:
-            if not input.burn_proof.verify(input.revealed):
-                return False
-        for output in self.outputs:
-            if not output.mint_proof.verify(output.revealed):
-                return False
-        return True
-
-    def _verify_token_commitments(self):
-        assert len(self.outputs) > 0
-        token_commit_value = self.outputs[0].revealed.token_commit
-        for input in self.clear_inputs:
-            token_commit = pedersen_encrypt(input.token_id, input.token_blind,
-                                            self.ec)
-            if token_commit != token_commit_value:
-                return False
-        for input in self.inputs:
-            if input.revealed.token_commit != token_commit_value:
-                return False
-        for output in self.outputs:
-            if output.revealed.token_commit != token_commit_value:
-                return False
-        return True
-
-class BurnProof:
-
-    def __init__(self, value, token_id, value_blind, token_blind, serial,
-                 coin_blind, secret, spend_hook, user_data, user_data_blind,
-                 all_coins, signature_secret, ec):
-        self.value = value
-        self.token_id = token_id
-        self.value_blind = value_blind
-        self.token_blind = token_blind
-        self.serial = serial
-        self.coin_blind = coin_blind
-        self.secret = secret
-        self.spend_hook = spend_hook
-        self.user_data = user_data
-        self.user_data_blind = user_data_blind
-        self.all_coins = all_coins
-        self.signature_secret = signature_secret
-
-        self.ec = ec
-
-    def get_revealed(self):
-        revealed = ClassNamespace()
-        revealed.nullifier = ff_hash(self.ec.p, self.secret, self.serial)
-
-        revealed.value_commit = pedersen_encrypt(
-            self.value, self.value_blind, self.ec
-        )
-        revealed.token_commit = pedersen_encrypt(
-            self.token_id, self.token_blind, self.ec
-        )
-
-        # is_valid_merkle_root()
-        revealed.all_coins = self.all_coins
-
-        revealed.signature_public = self.ec.multiply(self.signature_secret,
-                                                     self.ec.G)
-
-        # This is fully public, no merkle tree or anything
-        revealed.spend_hook = self.spend_hook
-
-        # Re-export user_data field for access by other contracts
-        revealed.enc_user_data = ff_hash(
-            self.ec.p,
-            self.user_data,
-            self.user_data_blind
-        )
-
-        return revealed
-
-    def verify(self, public):
-        revealed = self.get_revealed()
-
-        public_key = self.ec.multiply(self.secret, self.ec.G)
-        coin = ff_hash(
-            self.ec.p,
-            public_key[0],
-            public_key[1],
-            self.value,
-            self.token_id,
-            self.serial,
-            self.coin_blind,
-            self.spend_hook,
-            self.user_data,
-        )
-        # Merkle root check
-        if coin not in self.all_coins:
-            return False
-
-        return all([
-            revealed.nullifier == public.nullifier,
-            revealed.value_commit == public.value_commit,
-            revealed.token_commit == public.token_commit,
-            revealed.all_coins == public.all_coins,
-            revealed.signature_public == public.signature_public,
-            revealed.enc_user_data == public.enc_user_data
-        ])
-
-class MintProof:
-
-    def __init__(self, value, token_id, value_blind, token_blind, serial,
-                 coin_blind, public, spend_hook, user_data, ec):
-        self.value = value
-        self.token_id = token_id
-        self.value_blind = value_blind
-        self.token_blind = token_blind
-        self.serial = serial
-        self.coin_blind = coin_blind
-        self.public = public
-        self.spend_hook = spend_hook
-        self.user_data = user_data
-
-        self.ec = ec
-
-    def get_revealed(self):
-        revealed = ClassNamespace()
-        revealed.coin = ff_hash(
-            self.ec.p,
-            self.public[0],
-            self.public[1],
-            self.value,
-            self.token_id,
-            self.serial,
-            self.coin_blind,
-            self.spend_hook,
-            self.user_data
-        )
-
-        revealed.value_commit = pedersen_encrypt(
-            self.value, self.value_blind, self.ec
-        )
-        revealed.token_commit = pedersen_encrypt(
-            self.token_id, self.token_blind, self.ec
-        )
-
-        return revealed
-
-    def verify(self, public):
-        revealed = self.get_revealed()
-        return all([
-            revealed.coin == public.coin,
-            revealed.value_commit == public.value_commit,
-            revealed.token_commit == public.token_commit,
-        ])
-

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


+ 12 - 13
example/dao/contract/dao_contract/exec/validate.rs → example/dao/src/contract/dao/exec/validate.rs

@@ -1,4 +1,5 @@
-use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
+use std::any::{Any, TypeId};
+
 use pasta_curves::{
     arithmetic::CurveAffine,
     group::{Curve, Group},
@@ -9,11 +10,10 @@ use darkfi::{
     crypto::{coin::Coin, keypair::PublicKey, types::DrkCircuitField},
     Error as DarkFiError,
 };
-
-use std::any::{Any, TypeId};
+use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
 
 use crate::{
-    contract::{dao_contract, dao_contract::CONTRACT_ID, money_contract},
+    contract::{dao, dao::CONTRACT_ID, money},
     util::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase},
 };
 
@@ -100,7 +100,7 @@ impl CallDataBase for CallData {
     fn encode_bytes(
         &self,
         mut writer: &mut dyn std::io::Write,
-    ) -> core::result::Result<usize, std::io::Error> {
+    ) -> std::result::Result<usize, std::io::Error> {
         self.encode(&mut writer)
     }
 }
@@ -113,7 +113,7 @@ pub fn state_transition(
     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>());
+    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.
@@ -131,17 +131,17 @@ pub fn state_transition(
     }
 
     // 3. First item should be a Money::transfer() calldata
-    if parent_tx.func_calls[0].func_id != *money_contract::transfer::FUNC_ID {
+    if parent_tx.func_calls[0].func_id != *money::transfer::FUNC_ID {
         return Err(Error::InvalidCallData)
     }
 
     let money_transfer_call_data = parent_tx.func_calls[0].call_data.as_any();
     let money_transfer_call_data =
-        money_transfer_call_data.downcast_ref::<money_contract::transfer::validate::CallData>();
+        money_transfer_call_data.downcast_ref::<money::transfer::validate::CallData>();
     let money_transfer_call_data = money_transfer_call_data.unwrap();
     assert_eq!(
         money_transfer_call_data.type_id(),
-        TypeId::of::<money_contract::transfer::validate::CallData>()
+        TypeId::of::<money::transfer::validate::CallData>()
     );
 
     // 4. Money::transfer() has exactly 2 outputs
@@ -168,9 +168,8 @@ pub fn state_transition(
     }
 
     // 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 state =
+        states.lookup::<dao::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
@@ -193,7 +192,7 @@ pub struct Update {
 impl UpdateBase for Update {
     fn apply(self: Box<Self>, states: &mut StateRegistry) {
         let state = states
-            .lookup_mut::<dao_contract::State>(*CONTRACT_ID)
+            .lookup_mut::<dao::State>(*CONTRACT_ID)
             .expect("Return type is not of type State");
         state.proposal_votes.remove(&HashableBase(self.proposal)).unwrap();
     }

+ 5 - 7
example/dao/contract/dao_contract/exec/wallet.rs → example/dao/src/contract/dao/exec/wallet.rs

@@ -1,8 +1,7 @@
-use log::debug;
-use rand::rngs::OsRng;
-
 use halo2_proofs::circuit::Value;
+use log::debug;
 use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas};
+use rand::rngs::OsRng;
 
 use darkfi::{
     crypto::{
@@ -14,7 +13,7 @@ use darkfi::{
 };
 
 use crate::{
-    contract::dao_contract::{
+    contract::dao::{
         exec::validate::CallData, mint::wallet::DaoParams, propose::wallet::Proposal, CONTRACT_ID,
     },
     util::{FuncCall, ZkContractInfo, ZkContractTable},
@@ -40,7 +39,6 @@ pub struct Builder {
 impl Builder {
     pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
         debug!(target: "dao_contract::exec::wallet::Builder", "build()");
-        debug!(target: "dao_contract::exec::wallet", "proposalserial{:?}", self.proposal.serial);
         let mut proofs = vec![];
 
         let proposal_dest_coords = self.proposal.dest.0.to_affine().coordinates().unwrap();
@@ -100,7 +98,7 @@ impl Builder {
             self.proposal.token_id,
             self.dao_serial,
             self.hook_dao_exec,
-            proposal_bulla,
+            dao_bulla,
             self.dao_coin_blind,
         ]);
 
@@ -123,6 +121,7 @@ impl Builder {
         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())),
@@ -173,7 +172,6 @@ impl Builder {
         ];
 
         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("DAO::exec() proving error!)");

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


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

@@ -4,7 +4,7 @@ use darkfi::crypto::{keypair::PublicKey, types::DrkCircuitField};
 use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
 
 use crate::{
-    contract::dao_contract::{DaoBulla, State, CONTRACT_ID},
+    contract::dao::{DaoBulla, State, CONTRACT_ID},
     util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
 };
 
@@ -16,7 +16,7 @@ pub fn state_transition(
     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>());
+    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.
@@ -65,7 +65,7 @@ impl CallDataBase for CallData {
     fn encode_bytes(
         &self,
         mut writer: &mut dyn std::io::Write,
-    ) -> core::result::Result<usize, std::io::Error> {
+    ) -> std::result::Result<usize, std::io::Error> {
         self.encode(&mut writer)
     }
 }

+ 5 - 4
example/dao/contract/dao_contract/mint/wallet.rs → example/dao/src/contract/dao/mint/wallet.rs

@@ -1,3 +1,7 @@
+use halo2_proofs::circuit::Value;
+use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas};
+use rand::rngs::OsRng;
+
 use darkfi::{
     crypto::{
         keypair::{PublicKey, SecretKey},
@@ -6,12 +10,9 @@ 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::{
-    contract::dao_contract::{mint::validate::CallData, state::DaoBulla, CONTRACT_ID},
+    contract::dao::{mint::validate::CallData, state::DaoBulla, CONTRACT_ID},
     util::{FuncCall, ZkContractInfo, ZkContractTable},
 };
 

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


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


+ 170 - 0
example/dao/src/contract/dao/propose/validate.rs

@@ -0,0 +1,170 @@
+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, dao::State as DaoState, money, money::state::State as MoneyState},
+    note::EncryptedNote2,
+    util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
+};
+
+// used for debugging
+// const TARGET: &str = "dao_contract::propose::validate::state_transition()";
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum Error {
+    #[error("Invalid input merkle root")]
+    InvalidInputMerkleRoot,
+
+    #[error("Invalid DAO merkle root")]
+    InvalidDaoMerkleRoot,
+
+    #[error("DarkFi error: {0}")]
+    DarkFiError(String),
+}
+type Result<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_ID).unwrap();
+        if !money_state.is_valid_merkle(&input.merkle_root) {
+            return Err(Error::InvalidInputMerkleRoot)
+        }
+    }
+
+    let state = states.lookup::<DaoState>(*dao::CONTRACT_ID).unwrap();
+
+    // Is the DAO bulla generated in the ZK proof valid
+    if !state.is_valid_dao_merkle(&call_data.header.dao_merkle_root) {
+        return Err(Error::InvalidDaoMerkleRoot)
+    }
+
+    // TODO: look at gov tokens avoid using already spent ones
+    // Need to spend original coin and generate 2 nullifiers?
+
+    Ok(Box::new(Update { proposal_bulla: call_data.header.proposal_bulla }))
+}
+
+#[derive(Clone)]
+pub struct Update {
+    pub proposal_bulla: pallas::Base,
+}
+
+impl UpdateBase for Update {
+    fn apply(self: Box<Self>, states: &mut StateRegistry) {
+        let state = states.lookup_mut::<DaoState>(*dao::CONTRACT_ID).unwrap();
+        state.add_proposal_bulla(self.proposal_bulla);
+    }
+}

+ 272 - 0
example/dao/src/contract/dao/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::{
+            mint::wallet::DaoParams,
+            propose::validate::{CallData, Header, Input},
+            CONTRACT_ID,
+        },
+        money,
+    },
+    note,
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
+};
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct Note {
+    pub proposal: Proposal,
+}
+
+pub struct BuilderInput {
+    pub secret: SecretKey,
+    pub note: money::transfer::wallet::Note,
+    pub leaf_position: incrementalmerkletree::Position,
+    pub merkle_path: Vec<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,
+        }
+    }
+}

+ 97 - 0
example/dao/src/contract/dao/state.rs

@@ -0,0 +1,97 @@
+use std::{any::Any, collections::HashMap};
+
+use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
+use pasta_curves::{group::Group, pallas};
+
+use darkfi::crypto::{constants::MERKLE_DEPTH, merkle_node::MerkleNode, nullifier::Nullifier};
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+
+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
example/dao/contract/dao_contract/vote/mod.rs → example/dao/src/contract/dao/vote/mod.rs


+ 205 - 0
example/dao/src/contract/dao/vote/validate.rs

@@ -0,0 +1,205 @@
+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, dao::State as DaoState, money, money::state::State as MoneyState},
+    note::EncryptedNote2,
+    util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
+};
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum Error {
+    #[error("Invalid proposal")]
+    InvalidProposal,
+
+    #[error("Voting with already spent coinage")]
+    SpentCoin,
+
+    #[error("Double voting")]
+    DoubleVote,
+
+    #[error("Invalid input merkle root")]
+    InvalidInputMerkleRoot,
+
+    #[error("DarkFi error: {0}")]
+    DarkFiError(String),
+}
+type Result<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_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_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_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
example/dao/src/contract/dao/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::{
+            mint::wallet::DaoParams,
+            propose::wallet::Proposal,
+            vote::validate::{CallData, Header, Input},
+            CONTRACT_ID,
+        },
+        money,
+    },
+    note,
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
+};
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct Note {
+    pub vote: Vote,
+    pub vote_value: u64,
+    pub vote_value_blind: pallas::Scalar,
+}
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct Vote {
+    pub vote_option: bool,
+    pub vote_option_blind: pallas::Scalar,
+}
+
+pub struct BuilderInput {
+    pub secret: SecretKey,
+    pub note: money::transfer::wallet::Note,
+    pub leaf_position: incrementalmerkletree::Position,
+    pub merkle_path: Vec<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
example/dao/contract/example_contract/foo/mod.rs → example/dao/src/contract/example/foo/mod.rs


+ 6 - 7
example/dao/contract/example_contract/foo/validate.rs → example/dao/src/contract/example/foo/validate.rs

@@ -1,15 +1,15 @@
-use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
+use std::any::{Any, TypeId};
+
 use pasta_curves::pallas;
 
 use darkfi::{
     crypto::{keypair::PublicKey, types::DrkCircuitField},
     Error as DarkFiError,
 };
-
-use std::any::{Any, TypeId};
+use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
 
 use crate::{
-    contract::example_contract::{state::State, CONTRACT_ID},
+    contract::example::{state::State, CONTRACT_ID},
     util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
 };
 
@@ -19,7 +19,6 @@ type Result<T> = std::result::Result<T, Error>;
 pub enum Error {
     #[error("ValueExists")]
     ValueExists,
-
     #[error("DarkFi error: {0}")]
     DarkFiError(String),
 }
@@ -52,7 +51,7 @@ impl CallDataBase for CallData {
     fn encode_bytes(
         &self,
         mut writer: &mut dyn std::io::Write,
-    ) -> core::result::Result<usize, std::io::Error> {
+    ) -> std::result::Result<usize, std::io::Error> {
         self.encode(&mut writer)
     }
 }
@@ -65,7 +64,7 @@ pub fn state_transition(
     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>());
+    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.

+ 1 - 1
example/dao/contract/example_contract/foo/wallet.rs → example/dao/src/contract/example/foo/wallet.rs

@@ -13,7 +13,7 @@ use darkfi::{
 };
 
 use crate::{
-    contract::example_contract::{foo::validate::CallData, CONTRACT_ID},
+    contract::example::{foo::validate::CallData, CONTRACT_ID},
     util::{FuncCall, ZkContractInfo, ZkContractTable},
 };
 

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


+ 2 - 2
example/dao/contract/example_contract/state.rs → example/dao/src/contract/example/state.rs

@@ -7,14 +7,14 @@ pub struct State {
 }
 
 impl State {
-    pub fn new() -> Box<dyn Any> {
+    pub fn new() -> Box<dyn Any + Send> {
         Box::new(Self { public_values: Vec::new() })
     }
 
     pub fn add_public_value(&mut self, public_value: pallas::Base) {
         self.public_values.push(public_value)
     }
-
+    //
     pub fn public_exists(&self, public_value: &pallas::Base) -> bool {
         self.public_values.iter().any(|v| v == public_value)
     }

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

@@ -0,0 +1,3 @@
+pub mod dao;
+pub mod example;
+pub mod money;

+ 0 - 0
example/dao/contract/money_contract/mod.rs → example/dao/src/contract/money/mod.rs


+ 116 - 0
example/dao/src/contract/money/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
example/dao/contract/money_contract/transfer/mod.rs → example/dao/src/contract/money/transfer/mod.rs


+ 375 - 0
example/dao/src/contract/money/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,
+        money::{state::State, CONTRACT_ID},
+    },
+    note::EncryptedNote2,
+    util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
+};
+
+const TARGET: &str = "money_contract::transfer::validate::state_transition()";
+
+/// A struct representing a state update.
+/// This gets applied on top of an existing state.
+#[derive(Clone)]
+pub struct Update {
+    /// All nullifiers in a transaction
+    pub nullifiers: Vec<Nullifier>,
+    /// All coins in a transaction
+    pub coins: Vec<Coin>,
+    /// All encrypted notes in a transaction
+    pub enc_notes: Vec<EncryptedNote2>,
+}
+
+impl UpdateBase for Update {
+    fn apply(mut self: Box<Self>, states: &mut StateRegistry) {
+        let state = states.lookup_mut::<State>(*CONTRACT_ID).unwrap();
+
+        // Extend our list of nullifiers with the ones from the update
+        state.nullifiers.append(&mut self.nullifiers);
+
+        //// Update merkle tree and witnesses
+        for (coin, 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::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>;

+ 219 - 0
example/dao/src/contract/money/transfer/wallet.rs

@@ -0,0 +1,219 @@
+use pasta_curves::group::ff::Field;
+use rand::rngs::OsRng;
+
+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 darkfi_serial::{SerialDecodable, SerialEncodable};
+
+use crate::{
+    contract::money::{
+        transfer::validate::{CallData, ClearInput, Input, Output},
+        CONTRACT_ID,
+    },
+    note,
+    util::{FuncCall, ZkContractInfo, ZkContractTable},
+};
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct Note {
+    pub serial: DrkSerial,
+    pub value: u64,
+    pub token_id: DrkTokenId,
+    pub spend_hook: DrkSpendHook,
+    pub user_data: DrkUserData,
+    pub coin_blind: DrkCoinBlind,
+    pub value_blind: DrkValueBlind,
+    pub token_blind: DrkValueBlind,
+}
+
+pub struct Builder {
+    pub clear_inputs: Vec<BuilderClearInputInfo>,
+    pub inputs: Vec<BuilderInputInfo>,
+    pub outputs: Vec<BuilderOutputInfo>,
+}
+
+pub struct BuilderClearInputInfo {
+    pub value: u64,
+    pub token_id: DrkTokenId,
+    pub signature_secret: SecretKey,
+}
+
+pub struct BuilderInputInfo {
+    pub leaf_position: incrementalmerkletree::Position,
+    pub merkle_path: Vec<MerkleNode>,
+    pub secret: SecretKey,
+    pub note: Note,
+    pub user_data_blind: DrkUserDataBlind,
+    pub value_blind: DrkValueBlind,
+    pub signature_secret: SecretKey,
+}
+
+pub struct BuilderOutputInfo {
+    pub value: u64,
+    pub token_id: DrkTokenId,
+    pub public: PublicKey,
+    pub serial: DrkSerial,
+    pub coin_blind: DrkCoinBlind,
+    pub spend_hook: DrkSpendHook,
+    pub user_data: DrkUserData,
+}
+
+impl Builder {
+    fn compute_remainder_blind(
+        clear_inputs: &[ClearInput],
+        input_blinds: &[DrkValueBlind],
+        output_blinds: &[DrkValueBlind],
+    ) -> DrkValueBlind {
+        let mut total = DrkValueBlind::zero();
+
+        for input in clear_inputs {
+            total += input.value_blind;
+        }
+
+        for input_blind in input_blinds {
+            total += input_blind;
+        }
+
+        for output_blind in output_blinds {
+            total -= output_blind;
+        }
+
+        total
+    }
+
+    pub fn build(self, zk_bins: &ZkContractTable) -> Result<FuncCall> {
+        assert!(self.clear_inputs.len() + self.inputs.len() > 0);
+
+        let mut clear_inputs = vec![];
+        let token_blind = DrkValueBlind::random(&mut OsRng);
+        for input in &self.clear_inputs {
+            let signature_public = PublicKey::from_secret(input.signature_secret);
+            let value_blind = DrkValueBlind::random(&mut OsRng);
+
+            let clear_input = ClearInput {
+                value: input.value,
+                token_id: input.token_id,
+                value_blind,
+                token_blind,
+                signature_public,
+            };
+            clear_inputs.push(clear_input);
+        }
+
+        let mut proofs = vec![];
+        let mut inputs = vec![];
+        let mut input_blinds = vec![];
+
+        for input in self.inputs {
+            let value_blind = input.value_blind;
+            input_blinds.push(value_blind);
+
+            let zk_info = zk_bins.lookup(&"money-transfer-burn".to_string()).unwrap();
+            let zk_info = if let ZkContractInfo::Native(info) = zk_info {
+                info
+            } else {
+                panic!("Not native info")
+            };
+            let burn_pk = &zk_info.proving_key;
+
+            // Note from the previous output
+            let note = input.note.clone();
+
+            let (burn_proof, revealed) = create_burn_proof(
+                burn_pk,
+                note.value,
+                note.token_id,
+                value_blind,
+                token_blind,
+                note.serial,
+                note.spend_hook,
+                note.user_data,
+                input.user_data_blind,
+                note.coin_blind,
+                input.secret,
+                input.leaf_position,
+                input.merkle_path.clone(),
+                input.signature_secret,
+            )?;
+            proofs.push(burn_proof);
+
+            let input = Input { revealed };
+            inputs.push(input);
+        }
+
+        let mut outputs = vec![];
+        let mut output_blinds = vec![];
+        // This value_blind calc assumes there will always be at least a single output
+        assert!(self.outputs.len() > 0);
+
+        for (i, output) in self.outputs.iter().enumerate() {
+            let value_blind = if i == self.outputs.len() - 1 {
+                Self::compute_remainder_blind(&clear_inputs, &input_blinds, &output_blinds)
+            } else {
+                DrkValueBlind::random(&mut OsRng)
+            };
+            output_blinds.push(value_blind);
+
+            let serial = output.serial;
+            let coin_blind = output.coin_blind;
+
+            let zk_info = zk_bins.lookup(&"money-transfer-mint".to_string()).unwrap();
+            let zk_info = if let ZkContractInfo::Native(info) = zk_info {
+                info
+            } else {
+                panic!("Not native info")
+            };
+            let mint_pk = &zk_info.proving_key;
+
+            let (mint_proof, revealed) = create_mint_proof(
+                mint_pk,
+                output.value,
+                output.token_id,
+                value_blind,
+                token_blind,
+                serial,
+                output.spend_hook,
+                output.user_data,
+                coin_blind,
+                output.public,
+            )?;
+            proofs.push(mint_proof);
+
+            let note = Note {
+                serial,
+                value: output.value,
+                token_id: output.token_id,
+                spend_hook: output.spend_hook,
+                user_data: output.user_data,
+                coin_blind,
+                value_blind,
+                token_blind,
+            };
+
+            let encrypted_note = note::encrypt(&note, &output.public)?;
+
+            let output = Output { revealed, enc_note: encrypted_note };
+            outputs.push(output);
+        }
+
+        let call_data = CallData { clear_inputs, inputs, outputs };
+
+        Ok(FuncCall {
+            contract_id: *CONTRACT_ID,
+            func_id: *super::FUNC_ID,
+            call_data: Box::new(call_data),
+            proofs,
+        })
+    }
+}

+ 1139 - 0
example/dao/src/dao.rs

@@ -0,0 +1,1139 @@
+use async_std::sync::{Arc, Mutex};
+use std::{
+    any::{Any, TypeId},
+    collections::HashMap,
+    hash::Hasher,
+    io,
+    time::Instant,
+};
+
+use darkfi_serial::Encodable;
+use incrementalmerkletree::Tree;
+use log::{debug, info};
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{
+        ff::{Field, PrimeField},
+        Curve, Group,
+    },
+    pallas,
+};
+use rand::rngs::OsRng;
+
+use darkfi::{
+    crypto::{
+        keypair::{Keypair, PublicKey, SecretKey},
+        proof::{ProvingKey, VerifyingKey},
+        schnorr::{SchnorrPublic, SchnorrSecret, Signature},
+        types::{DrkCircuitField, DrkSpendHook, DrkUserData, DrkValue},
+        util::{pedersen_commitment_u64, poseidon_hash},
+        Proof,
+    },
+    zk::{
+        circuit::{BurnContract, MintContract},
+        vm::ZkCircuit,
+        vm_stack::empty_witnesses,
+    },
+    zkas::decoder::ZkBinary,
+};
+
+mod contract;
+mod error;
+mod note;
+mod util;
+
+use crate::{
+    contract::{dao, example, money},
+    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>>;
+
+///////////////////////////////////////////////////
+///// Example contract
+///////////////////////////////////////////////////
+pub async fn example() -> Result<()> {
+    debug!(target: "demo", "Stage 0. Example contract");
+    // Lookup table for smart contract states
+    let mut states = StateRegistry::new();
+
+    // Initialize ZK binary table
+    let mut zk_bins = ZkContractTable::new();
+
+    let zk_example_foo_bincode = include_bytes!("../proof/foo.zk.bin");
+    let zk_example_foo_bin = ZkBinary::decode(zk_example_foo_bincode)?;
+    zk_bins.add_contract("example-foo".to_string(), zk_example_foo_bin, 13);
+
+    let example_state = example::state::State::new();
+    states.register(*example::CONTRACT_ID, example_state);
+
+    //// Wallet
+
+    let foo_w = example::foo::wallet::Foo { a: 5, b: 10 };
+    let signature_secret = SecretKey::random(&mut OsRng);
+
+    let builder = example::foo::wallet::Builder { foo: foo_w, signature_secret };
+    let func_call = builder.build(&zk_bins);
+    let func_calls = vec![func_call];
+
+    let signatures = sign([signature_secret].to_vec(), &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::foo::FUNC_ID {
+            debug!("example::foo::state_transition()");
+
+            let update = example::foo::validate::state_transition(&states, idx, &tx)
+                .expect("example::foo::validate::state_transition() failed!");
+            updates.push(update);
+        }
+    }
+
+    // Atomically apply all changes
+    for update in updates {
+        update.apply(&mut states);
+    }
+
+    tx.zk_verify(&zk_bins);
+    tx.verify_sigs();
+
+    Ok(())
+}
+
+#[async_std::main]
+async fn main() -> Result<()> {
+    env_logger::init();
+
+    // 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::state::State::new(cashier_signature_public, faucet_signature_public);
+    states.register(*money::CONTRACT_ID, money_state);
+
+    /////////////////////////////////////////////////////
+
+    let dao_state = dao::State::new();
+    states.register(*dao::CONTRACT_ID, dao_state);
+
+    /////////////////////////////////////////////////////
+    ////// Create the DAO bulla
+    /////////////////////////////////////////////////////
+    debug!(target: "demo", "Stage 1. Creating DAO bulla");
+
+    //// Wallet
+
+    //// Setup the DAO
+    let dao_keypair = Keypair::random(&mut OsRng);
+    let dao_bulla_blind = pallas::Base::random(&mut OsRng);
+
+    let signature_secret = SecretKey::random(&mut OsRng);
+    // Create DAO mint tx
+    let builder = dao::mint::wallet::Builder {
+        dao_proposer_limit,
+        dao_quorum,
+        dao_approval_ratio_quot,
+        dao_approval_ratio_base,
+        gov_token_id: gdrk_token_id,
+        dao_pubkey: dao_keypair.public,
+        dao_bulla_blind,
+        _signature_secret: signature_secret,
+    };
+    let func_call = builder.build(&zk_bins);
+    let func_calls = vec![func_call];
+
+    let signatures = sign([signature_secret].to_vec(), &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::mint::FUNC_ID {
+            debug!("dao::mint::state_transition()");
+
+            let update = dao::mint::validate::state_transition(&states, idx, &tx)
+                .expect("dao::mint::validate::state_transition() failed!");
+            updates.push(update);
+        }
+    }
+
+    // Atomically apply all changes
+    for update in updates {
+        update.apply(&mut states);
+    }
+
+    tx.zk_verify(&zk_bins);
+    tx.verify_sigs();
+
+    // Wallet stuff
+
+    // In your wallet, wait until you see the tx confirmed before doing anything below
+    // So for example keep track of tx hash
+    //assert_eq!(tx.hash(), tx_hash);
+
+    // We need to witness() the value in our local merkle tree
+    // Must be called as soon as this DAO bulla is added to the state
+    let dao_leaf_position = {
+        let state = states.lookup_mut::<dao::State>(*dao::CONTRACT_ID).unwrap();
+        state.dao_tree.witness().unwrap()
+    };
+
+    // It might just be easier to hash it ourselves from keypair and blind...
+    let dao_bulla = {
+        assert_eq!(tx.func_calls.len(), 1);
+        let func_call = &tx.func_calls[0];
+        let call_data = func_call.call_data.as_any();
+        assert_eq!((*call_data).type_id(), TypeId::of::<dao::mint::validate::CallData>());
+        let call_data = call_data.downcast_ref::<dao::mint::validate::CallData>().unwrap();
+        call_data.dao_bulla.clone()
+    };
+    debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0);
+
+    ///////////////////////////////////////////////////
+    //// Mint the initial supply of treasury token
+    //// and send it all to the DAO directly
+    ///////////////////////////////////////////////////
+    debug!(target: "demo", "Stage 2. Minting treasury token");
+
+    let state = states.lookup_mut::<money::State>(*money::CONTRACT_ID).unwrap();
+    state.wallet_cache.track(dao_keypair.secret);
+
+    //// Wallet
+
+    // Address of deployed contract in our example is dao::exec::FUNC_ID
+    // This field is public, you can see it's being sent to a DAO
+    // but nothing else is visible.
+    //
+    // In the python code we wrote:
+    //
+    //   spend_hook = b"0xdao_ruleset"
+    //
+    let spend_hook = *dao::exec::FUNC_ID;
+    // The user_data can be a simple hash of the items passed into the ZK proof
+    // up to corresponding linked ZK proof to interpret however they need.
+    // In out case, it's the bulla for the DAO
+    let user_data = dao_bulla.0;
+
+    let builder = money::transfer::wallet::Builder {
+        clear_inputs: vec![money::transfer::wallet::BuilderClearInputInfo {
+            value: xdrk_supply,
+            token_id: xdrk_token_id,
+            signature_secret: cashier_signature_secret,
+        }],
+        inputs: vec![],
+        outputs: vec![money::transfer::wallet::BuilderOutputInfo {
+            value: xdrk_supply,
+            token_id: xdrk_token_id,
+            public: dao_keypair.public,
+            serial: pallas::Base::random(&mut OsRng),
+            coin_blind: pallas::Base::random(&mut OsRng),
+            spend_hook,
+            user_data,
+        }],
+    };
+
+    let func_call = builder.build(&zk_bins)?;
+    let func_calls = vec![func_call];
+
+    let signatures = sign([cashier_signature_secret].to_vec(), &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::transfer::FUNC_ID {
+            debug!("money::transfer::state_transition()");
+
+            let update = money::transfer::validate::state_transition(&states, idx, &tx)
+                .expect("money::transfer::validate::state_transition() failed!");
+            updates.push(update);
+        }
+    }
+
+    // Atomically apply all changes
+    for update in updates {
+        update.apply(&mut states);
+    }
+
+    tx.zk_verify(&zk_bins);
+    tx.verify_sigs();
+
+    //// Wallet
+    // DAO reads the money received from the encrypted note
+
+    let state = states.lookup_mut::<money::State>(*money::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::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::State>(*money::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::transfer::wallet::BuilderOutputInfo {
+        value: 400000,
+        token_id: gdrk_token_id,
+        public: gov_keypair_1.public,
+        serial: pallas::Base::random(&mut OsRng),
+        coin_blind: pallas::Base::random(&mut OsRng),
+        spend_hook,
+        user_data,
+    };
+
+    let output2 = money::transfer::wallet::BuilderOutputInfo {
+        value: 400000,
+        token_id: gdrk_token_id,
+        public: gov_keypair_2.public,
+        serial: pallas::Base::random(&mut OsRng),
+        coin_blind: pallas::Base::random(&mut OsRng),
+        spend_hook,
+        user_data,
+    };
+
+    let output3 = money::transfer::wallet::BuilderOutputInfo {
+        value: 200000,
+        token_id: gdrk_token_id,
+        public: gov_keypair_3.public,
+        serial: pallas::Base::random(&mut OsRng),
+        coin_blind: pallas::Base::random(&mut OsRng),
+        spend_hook,
+        user_data,
+    };
+
+    assert!(2 * 400000 + 200000 == gdrk_supply);
+
+    let builder = money::transfer::wallet::Builder {
+        clear_inputs: vec![money::transfer::wallet::BuilderClearInputInfo {
+            value: gdrk_supply,
+            token_id: gdrk_token_id,
+            signature_secret: cashier_signature_secret,
+        }],
+        inputs: vec![],
+        outputs: vec![output1, output2, output3],
+    };
+
+    let func_call = builder.build(&zk_bins)?;
+    let func_calls = vec![func_call];
+
+    let signatures = sign([cashier_signature_secret].to_vec(), &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::transfer::FUNC_ID {
+            debug!("money::transfer::state_transition()");
+
+            let update = money::transfer::validate::state_transition(&states, idx, &tx)
+                .expect("money::transfer::validate::state_transition() failed!");
+            updates.push(update);
+        }
+    }
+
+    // Atomically apply all changes
+    for update in updates {
+        update.apply(&mut states);
+    }
+
+    tx.zk_verify(&zk_bins);
+    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::State>(*money::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::State>(*money::CONTRACT_ID).unwrap();
+        let tree = &state.tree;
+        let leaf_position = gov_recv[0].leaf_position;
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+        (leaf_position, merkle_path)
+    };
+
+    // TODO: is it possible for an invalid transfer() to be constructed on exec()?
+    //       need to look into this
+    let signature_secret = SecretKey::random(&mut OsRng);
+    let input = dao::propose::wallet::BuilderInput {
+        secret: gov_keypair_1.secret,
+        note: gov_recv[0].note.clone(),
+        leaf_position: money_leaf_position,
+        merkle_path: money_merkle_path,
+        signature_secret,
+    };
+
+    let (dao_merkle_path, dao_merkle_root) = {
+        let state = states.lookup::<dao::State>(*dao::CONTRACT_ID).unwrap();
+        let tree = &state.dao_tree;
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(dao_leaf_position, &root).unwrap();
+        (merkle_path, root)
+    };
+
+    let dao_params = dao::mint::wallet::DaoParams {
+        proposer_limit: dao_proposer_limit,
+        quorum: dao_quorum,
+        approval_ratio_base: dao_approval_ratio_base,
+        approval_ratio_quot: dao_approval_ratio_quot,
+        gov_token_id: gdrk_token_id,
+        public_key: dao_keypair.public,
+        bulla_blind: dao_bulla_blind,
+    };
+
+    let proposal = dao::propose::wallet::Proposal {
+        dest: user_keypair.public,
+        amount: 1000,
+        serial: pallas::Base::random(&mut OsRng),
+        token_id: xdrk_token_id,
+        blind: pallas::Base::random(&mut OsRng),
+    };
+
+    let builder = dao::propose::wallet::Builder {
+        inputs: vec![input],
+        proposal,
+        dao: dao_params.clone(),
+        dao_leaf_position,
+        dao_merkle_path,
+        dao_merkle_root,
+    };
+
+    let func_call = builder.build(&zk_bins);
+    let func_calls = vec![func_call];
+
+    let signatures = sign([signature_secret].to_vec(), &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::propose::FUNC_ID {
+            debug!(target: "demo", "dao::propose::state_transition()");
+
+            let update = dao::propose::validate::state_transition(&states, idx, &tx)
+                .expect("dao::propose::validate::state_transition() failed!");
+            updates.push(update);
+        }
+    }
+
+    // Atomically apply all changes
+    for update in updates {
+        update.apply(&mut states);
+    }
+
+    tx.zk_verify(&zk_bins);
+    tx.verify_sigs();
+
+    //// Wallet
+
+    // Read received proposal
+    let (proposal, proposal_bulla) = {
+        assert_eq!(tx.func_calls.len(), 1);
+        let func_call = &tx.func_calls[0];
+        let call_data = func_call.call_data.as_any();
+        assert_eq!((*call_data).type_id(), TypeId::of::<dao::propose::validate::CallData>());
+        let call_data = call_data.downcast_ref::<dao::propose::validate::CallData>().unwrap();
+
+        let header = &call_data.header;
+        let note: dao::propose::wallet::Note =
+            header.enc_note.decrypt(&dao_keypair.secret).unwrap();
+
+        // TODO: check it belongs to DAO bulla
+
+        // Return the proposal info
+        (note.proposal, call_data.header.proposal_bulla)
+    };
+    debug!(target: "demo", "Proposal now active!");
+    debug!(target: "demo", "  destination: {:?}", proposal.dest);
+    debug!(target: "demo", "  amount: {}", proposal.amount);
+    debug!(target: "demo", "  token_id: {:?}", proposal.token_id);
+    debug!(target: "demo", "  dao_bulla: {:?}", dao_bulla.0);
+    debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla);
+
+    ///////////////////////////////////////////////////
+    // Proposal is accepted!
+    // Start the voting
+    ///////////////////////////////////////////////////
+
+    // Copying these schizo comments from python code:
+    // Lets the voting begin
+    // Voters have access to the proposal and dao data
+    //   vote_state = VoteState()
+    // We don't need to copy nullifier set because it is checked from gov_state
+    // in vote_state_transition() anyway
+    //
+    // TODO: what happens if voters don't unblind their vote
+    // Answer:
+    //   1. there is a time limit
+    //   2. both the MPC or users can unblind
+    //
+    // TODO: bug if I vote then send money, then we can double vote
+    // TODO: all timestamps missing
+    //       - timelock (future voting starts in 2 days)
+    // Fix: use nullifiers from money gov state only from
+    // beginning of gov period
+    // Cannot use nullifiers from before voting period
+
+    debug!(target: "demo", "Stage 5. Start voting");
+
+    // We were previously saving updates here for testing
+    // let mut updates = vec![];
+
+    // User 1: YES
+
+    let (money_leaf_position, money_merkle_path) = {
+        let state = states.lookup::<money::State>(*money::CONTRACT_ID).unwrap();
+        let tree = &state.tree;
+        let leaf_position = gov_recv[0].leaf_position;
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+        (leaf_position, merkle_path)
+    };
+
+    let signature_secret = SecretKey::random(&mut OsRng);
+    let input = dao::vote::wallet::BuilderInput {
+        secret: gov_keypair_1.secret,
+        note: gov_recv[0].note.clone(),
+        leaf_position: money_leaf_position,
+        merkle_path: money_merkle_path,
+        signature_secret,
+    };
+
+    let vote_option: bool = true;
+    // assert!(vote_option || !vote_option); // wtf
+
+    // We create a new keypair to encrypt the vote.
+    // For the demo MVP, you can just use the dao_keypair secret
+    let vote_keypair_1 = Keypair::random(&mut OsRng);
+
+    let builder = dao::vote::wallet::Builder {
+        inputs: vec![input],
+        vote: dao::vote::wallet::Vote {
+            vote_option,
+            vote_option_blind: pallas::Scalar::random(&mut OsRng),
+        },
+        vote_keypair: vote_keypair_1,
+        proposal: proposal.clone(),
+        dao: dao_params.clone(),
+    };
+    debug!(target: "demo", "build()...");
+    let func_call = builder.build(&zk_bins);
+    let func_calls = vec![func_call];
+
+    let signatures = sign([signature_secret].to_vec(), &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::vote::FUNC_ID {
+            debug!(target: "demo", "dao::vote::state_transition()");
+
+            let update = dao::vote::validate::state_transition(&states, idx, &tx)
+                .expect("dao::vote::validate::state_transition() failed!");
+            updates.push(update);
+        }
+    }
+
+    // Atomically apply all changes
+    for update in updates {
+        update.apply(&mut states);
+    }
+
+    tx.zk_verify(&zk_bins);
+    tx.verify_sigs();
+
+    //// Wallet
+
+    // Secret vote info. Needs to be revealed at some point.
+    // TODO: look into verifiable encryption for notes
+    // TODO: look into timelock puzzle as a possibility
+    let vote_note_1 = {
+        assert_eq!(tx.func_calls.len(), 1);
+        let func_call = &tx.func_calls[0];
+        let call_data = func_call.call_data.as_any();
+        assert_eq!((*call_data).type_id(), TypeId::of::<dao::vote::validate::CallData>());
+        let call_data = call_data.downcast_ref::<dao::vote::validate::CallData>().unwrap();
+
+        let header = &call_data.header;
+        let note: dao::vote::wallet::Note =
+            header.enc_note.decrypt(&vote_keypair_1.secret).unwrap();
+        note
+    };
+    debug!(target: "demo", "User 1 voted!");
+    debug!(target: "demo", "  vote_option: {}", vote_note_1.vote.vote_option);
+    debug!(target: "demo", "  value: {}", vote_note_1.vote_value);
+
+    // User 2: NO
+
+    let (money_leaf_position, money_merkle_path) = {
+        let state = states.lookup::<money::State>(*money::CONTRACT_ID).unwrap();
+        let tree = &state.tree;
+        let leaf_position = gov_recv[1].leaf_position;
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+        (leaf_position, merkle_path)
+    };
+
+    let signature_secret = SecretKey::random(&mut OsRng);
+    let input = dao::vote::wallet::BuilderInput {
+        secret: gov_keypair_2.secret,
+        note: gov_recv[1].note.clone(),
+        leaf_position: money_leaf_position,
+        merkle_path: money_merkle_path,
+        signature_secret,
+    };
+
+    let vote_option: bool = false;
+    // assert!(vote_option || !vote_option); // wtf
+
+    // We create a new keypair to encrypt the vote.
+    let vote_keypair_2 = Keypair::random(&mut OsRng);
+
+    let builder = dao::vote::wallet::Builder {
+        inputs: vec![input],
+        vote: dao::vote::wallet::Vote {
+            vote_option,
+            vote_option_blind: pallas::Scalar::random(&mut OsRng),
+        },
+        vote_keypair: vote_keypair_2,
+        proposal: proposal.clone(),
+        dao: dao_params.clone(),
+    };
+    debug!(target: "demo", "build()...");
+    let func_call = builder.build(&zk_bins);
+    let func_calls = vec![func_call];
+
+    let signatures = sign([signature_secret].to_vec(), &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::vote::FUNC_ID {
+            debug!(target: "demo", "dao::vote::state_transition()");
+
+            let update = dao::vote::validate::state_transition(&states, idx, &tx)
+                .expect("dao::vote::validate::state_transition() failed!");
+            updates.push(update);
+        }
+    }
+
+    // Atomically apply all changes
+    for update in updates {
+        update.apply(&mut states);
+    }
+
+    tx.zk_verify(&zk_bins);
+    tx.verify_sigs();
+
+    //// Wallet
+
+    // Secret vote info. Needs to be revealed at some point.
+    // TODO: look into verifiable encryption for notes
+    // TODO: look into timelock puzzle as a possibility
+    let vote_note_2 = {
+        assert_eq!(tx.func_calls.len(), 1);
+        let func_call = &tx.func_calls[0];
+        let call_data = func_call.call_data.as_any();
+        assert_eq!((*call_data).type_id(), TypeId::of::<dao::vote::validate::CallData>());
+        let call_data = call_data.downcast_ref::<dao::vote::validate::CallData>().unwrap();
+
+        let header = &call_data.header;
+        let note: dao::vote::wallet::Note =
+            header.enc_note.decrypt(&vote_keypair_2.secret).unwrap();
+        note
+    };
+    debug!(target: "demo", "User 2 voted!");
+    debug!(target: "demo", "  vote_option: {}", vote_note_2.vote.vote_option);
+    debug!(target: "demo", "  value: {}", vote_note_2.vote_value);
+
+    // User 3: YES
+
+    let (money_leaf_position, money_merkle_path) = {
+        let state = states.lookup::<money::State>(*money::CONTRACT_ID).unwrap();
+        let tree = &state.tree;
+        let leaf_position = gov_recv[2].leaf_position;
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+        (leaf_position, merkle_path)
+    };
+
+    let signature_secret = SecretKey::random(&mut OsRng);
+    let input = dao::vote::wallet::BuilderInput {
+        secret: gov_keypair_3.secret,
+        note: gov_recv[2].note.clone(),
+        leaf_position: money_leaf_position,
+        merkle_path: money_merkle_path,
+        signature_secret,
+    };
+
+    let vote_option: bool = true;
+    // assert!(vote_option || !vote_option); // wtf
+
+    // We create a new keypair to encrypt the vote.
+    let vote_keypair_3 = Keypair::random(&mut OsRng);
+
+    let builder = dao::vote::wallet::Builder {
+        inputs: vec![input],
+        vote: dao::vote::wallet::Vote {
+            vote_option,
+            vote_option_blind: pallas::Scalar::random(&mut OsRng),
+        },
+        vote_keypair: vote_keypair_3,
+        proposal: proposal.clone(),
+        dao: dao_params.clone(),
+    };
+    debug!(target: "demo", "build()...");
+    let func_call = builder.build(&zk_bins);
+    let func_calls = vec![func_call];
+
+    let signatures = sign([signature_secret].to_vec(), &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::vote::FUNC_ID {
+            debug!(target: "demo", "dao::vote::state_transition()");
+
+            let update = dao::vote::validate::state_transition(&states, idx, &tx)
+                .expect("dao::vote::validate::state_transition() failed!");
+            updates.push(update);
+        }
+    }
+
+    // Atomically apply all changes
+    for update in updates {
+        update.apply(&mut states);
+    }
+
+    tx.zk_verify(&zk_bins);
+    tx.verify_sigs();
+
+    //// Wallet
+
+    // Secret vote info. Needs to be revealed at some point.
+    // TODO: look into verifiable encryption for notes
+    // TODO: look into timelock puzzle as a possibility
+    let vote_note_3 = {
+        assert_eq!(tx.func_calls.len(), 1);
+        let func_call = &tx.func_calls[0];
+        let call_data = func_call.call_data.as_any();
+        assert_eq!((*call_data).type_id(), TypeId::of::<dao::vote::validate::CallData>());
+        let call_data = call_data.downcast_ref::<dao::vote::validate::CallData>().unwrap();
+
+        let header = &call_data.header;
+        let note: dao::vote::wallet::Note =
+            header.enc_note.decrypt(&vote_keypair_3.secret).unwrap();
+        note
+    };
+    debug!(target: "demo", "User 3 voted!");
+    debug!(target: "demo", "  vote_option: {}", vote_note_3.vote.vote_option);
+    debug!(target: "demo", "  value: {}", vote_note_3.vote_value);
+
+    // Every votes produces a semi-homomorphic encryption of their vote.
+    // Which is either yes or no
+    // We copy the state tree for the governance token so coins can be used
+    // to vote on other proposals at the same time.
+    // With their vote, they produce a ZK proof + nullifier
+    // The votes are unblinded by MPC to a selected party at the end of the
+    // voting period.
+    // (that's if we want votes to be hidden during voting)
+
+    let mut yes_votes_value = 0;
+    let mut yes_votes_blind = pallas::Scalar::from(0);
+    let mut yes_votes_commit = pallas::Point::identity();
+
+    let mut all_votes_value = 0;
+    let mut all_votes_blind = pallas::Scalar::from(0);
+    let mut all_votes_commit = pallas::Point::identity();
+
+    // We were previously saving votes to a Vec<Update> for testing.
+    // However since Update is now UpdateBase it gets moved into update.apply().
+    // So we need to think of another way to run these tests.
+    //assert!(updates.len() == 3);
+
+    for (i, note /* update*/) in [vote_note_1, vote_note_2, vote_note_3]
+        .iter() /*.zip(updates)*/
+        .enumerate()
+    {
+        let vote_commit = pedersen_commitment_u64(note.vote_value, note.vote_value_blind);
+        //assert!(update.value_commit == all_vote_value_commit);
+        all_votes_commit += vote_commit;
+        all_votes_blind += note.vote_value_blind;
+
+        let yes_vote_commit = pedersen_commitment_u64(
+            note.vote.vote_option as u64 * note.vote_value,
+            note.vote.vote_option_blind,
+        );
+        //assert!(update.yes_vote_commit == yes_vote_commit);
+
+        yes_votes_commit += yes_vote_commit;
+        yes_votes_blind += note.vote.vote_option_blind;
+
+        let vote_option = note.vote.vote_option;
+
+        if vote_option {
+            yes_votes_value += note.vote_value;
+        }
+        all_votes_value += note.vote_value;
+        let vote_result: String = if vote_option { "yes".to_string() } else { "no".to_string() };
+
+        debug!("Voter {} voted {}", i, vote_result);
+    }
+
+    debug!("Outcome = {} / {}", yes_votes_value, all_votes_value);
+
+    assert!(all_votes_commit == pedersen_commitment_u64(all_votes_value, all_votes_blind));
+    assert!(yes_votes_commit == pedersen_commitment_u64(yes_votes_value, yes_votes_blind));
+
+    ///////////////////////////////////////////////////
+    // Execute the vote
+    ///////////////////////////////////////////////////
+
+    //// Wallet
+
+    // Used to export user_data from this coin so it can be accessed by DAO::exec()
+    let user_data_blind = pallas::Base::random(&mut OsRng);
+
+    let user_serial = pallas::Base::random(&mut OsRng);
+    let user_coin_blind = pallas::Base::random(&mut OsRng);
+    let dao_serial = pallas::Base::random(&mut OsRng);
+    let dao_coin_blind = pallas::Base::random(&mut OsRng);
+    let input_value = treasury_note.value;
+    let input_value_blind = pallas::Scalar::random(&mut OsRng);
+    let tx_signature_secret = SecretKey::random(&mut OsRng);
+    let exec_signature_secret = SecretKey::random(&mut OsRng);
+
+    let (treasury_leaf_position, treasury_merkle_path) = {
+        let state = states.lookup::<money::State>(*money::CONTRACT_ID).unwrap();
+        let tree = &state.tree;
+        let leaf_position = dao_recv_coin.leaf_position;
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+        (leaf_position, merkle_path)
+    };
+
+    let input = money::transfer::wallet::BuilderInputInfo {
+        leaf_position: treasury_leaf_position,
+        merkle_path: treasury_merkle_path,
+        secret: dao_keypair.secret,
+        note: treasury_note,
+        user_data_blind,
+        value_blind: input_value_blind,
+        signature_secret: tx_signature_secret,
+    };
+
+    let builder = money::transfer::wallet::Builder {
+        clear_inputs: vec![],
+        inputs: vec![input],
+        outputs: vec![
+            // Sending money
+            money::transfer::wallet::BuilderOutputInfo {
+                value: 1000,
+                token_id: xdrk_token_id,
+                public: user_keypair.public,
+                serial: proposal.serial,
+                coin_blind: proposal.blind,
+                spend_hook: pallas::Base::from(0),
+                user_data: pallas::Base::from(0),
+            },
+            // Change back to DAO
+            money::transfer::wallet::BuilderOutputInfo {
+                value: xdrk_supply - 1000,
+                token_id: xdrk_token_id,
+                public: dao_keypair.public,
+                serial: dao_serial,
+                coin_blind: dao_coin_blind,
+                spend_hook: *dao::exec::FUNC_ID,
+                // TODO: should be DAO bulla
+                user_data: proposal_bulla,
+            },
+        ],
+    };
+
+    let transfer_func_call = builder.build(&zk_bins)?;
+
+    let builder = dao::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::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([tx_signature_secret, exec_signature_secret].to_vec(), &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::transfer::validate::CallData>()
+        );
+        let transfer_call_data =
+            transfer_call_data.downcast_ref::<money::transfer::validate::CallData>();
+        let transfer_call_data = transfer_call_data.unwrap();
+        // At least one input has this field value which means DAO::exec() is invoked.
+        assert_eq!(transfer_call_data.inputs.len(), 1);
+        let input = &transfer_call_data.inputs[0];
+        assert_eq!(input.revealed.spend_hook, *dao::exec::FUNC_ID);
+        let user_data_enc = poseidon_hash::<2>([dao_bulla.0, user_data_blind]);
+        assert_eq!(input.revealed.user_data_enc, user_data_enc);
+    }
+
+    //// Validator
+
+    let mut updates = vec![];
+    // Validate all function calls in the tx
+    for (idx, func_call) in tx.func_calls.iter().enumerate() {
+        if func_call.func_id == *dao::exec::FUNC_ID {
+            debug!("dao::exec::state_transition()");
+
+            let update = dao::exec::validate::state_transition(&states, idx, &tx)
+                .expect("dao::exec::validate::state_transition() failed!");
+            updates.push(update);
+        } else if func_call.func_id == *money::transfer::FUNC_ID {
+            debug!("money::transfer::state_transition()");
+
+            let update = money::transfer::validate::state_transition(&states, idx, &tx)
+                .expect("money::transfer::validate::state_transition() failed!");
+            updates.push(update);
+        }
+    }
+
+    // Atomically apply all changes
+    for update in updates {
+        update.apply(&mut states);
+    }
+
+    // Other stuff
+    tx.zk_verify(&zk_bins);
+    tx.verify_sigs();
+
+    //// Wallet
+
+    Ok(())
+}

+ 56 - 0
example/dao/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()
+}

+ 2 - 3
example/dao/note.rs → example/dao/src/note.rs

@@ -1,5 +1,4 @@
 use crypto_api_chachapoly::ChachaPolyIetf;
-use darkfi_serial::{Decodable, Encodable, SerialDecodable, SerialEncodable};
 use rand::rngs::OsRng;
 
 use darkfi::{
@@ -9,6 +8,7 @@ use darkfi::{
     },
     Error, Result,
 };
+use darkfi_serial::{Decodable, Encodable, SerialDecodable, SerialEncodable};
 
 pub const AEAD_TAG_SIZE: usize = 16;
 
@@ -51,8 +51,7 @@ impl EncryptedNote2 {
             self.ciphertext.len() - AEAD_TAG_SIZE
         );
 
-        let t = T::decode(&plaintext[..])?;
-        Ok(t)
+        T::decode(&plaintext[..]).map_err(Error::from)
     }
 }
 

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