| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- import sys
- from classnamespace import ClassNamespace
- from crypto import pallas_curve, ff_hash
- from tx import TransactionBuilder
- class State:
- 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)
- # Try to decrypt notes here
- print(f"Received {enc_note.value} DRK")
- def 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 DaoBuilder:
- def __init__(self, proposal_auth_public_key, threshold, quorum, ec):
- self.proposal_auth_public_key = proposal_auth_public_key
- self.threshold = threshold
- self.quorum = quorum
- self.ec = ec
- def build(self):
- mint_proof = DaoMintProof(
- self.proposal_auth_public_key,
- self.threshold,
- self.quorum,
- 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, proposal_auth_public_key, threshold, quorum, ec):
- self.proposal_auth_public_key = proposal_auth_public_key
- self.threshold = threshold
- self.quorum = quorum
- self.ec = ec
- def get_revealed(self):
- revealed = ClassNamespace()
- revealed.bulla = ff_hash(
- self.ec.p,
- self.proposal_auth_public_key[0],
- self.proposal_auth_public_key[1],
- self.threshold,
- self.quorum
- )
- return revealed
- def verify(self, public):
- revealed = self.get_revealed()
- return True
- # Shared between DaoMint and DaoExec
- class DaoState:
- def __init__(self):
- self.bullas = set()
- def apply(self, update):
- self.bullas.add(update.bulla)
- def apply_exec(self, update):
- pass
- # 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):
- pass
- def build(self):
- tx = DaoExec()
- return tx
- class DaoExec:
- def __init__(self):
- pass
- class DaoExecProof:
- def __init__(self):
- pass
- def dao_exec_state_transition(state, tx):
- update = ClassNamespace()
- return update
- def main(argv):
- ec = pallas_curve()
- secret = ec.random_scalar()
- public = ec.multiply(secret, ec.G)
- initial_supply = 21000
- token_id = 110
- signature_secret = ec.random_scalar()
- # Setup the DAO
- proposal_auth_secret = ec.random_scalar()
- proposal_auth_public = ec.multiply(proposal_auth_secret, ec.G)
- threshold = 110
- quorum = 110
- builder = DaoBuilder(proposal_auth_public, threshold, quorum, ec)
- dao_tx = builder.build()
- # Each deployment of a contract has a unique state
- # associated with it.
- dao_state = DaoState()
- if (update := dao_state_transition(dao_state, dao_tx)) is None:
- return -1
- dao_state.apply(update)
- builder = TransactionBuilder(ec)
- builder.add_clear_input(initial_supply, token_id, signature_secret)
- # Address of deployed contract in our example is 0xdao_ruleset
- 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_tx.revealed.bulla
- builder.add_output(initial_supply, token_id, public, spend_hook, user_data)
- tx = builder.build()
- state = State()
- if (update := state_transition(state, tx)) is None:
- return -1
- state.apply(update)
- # Now the spend_hook field specifies the function DaoExec
- # so the tx above must also be combined with a DaoExec tx
- for input in tx.inputs:
- assert input.revealed.spend_hook == [b"0xdao_ruleset"]
- builder = DaoExecBuilder()
- dao_tx = builder.build()
- if (update := dao_exec_state_transition(dao_state, dao_tx)) is None:
- return -1
- dao_state.apply_exec(update)
- # 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)
- votes_yes = 10
- votes_no = 5
- # payment state transition in coin specifies dependency
- # the tx exists and ruleset is applied
- assert len(tx.outputs) > 0
- note = tx.outputs[0].enc_note
- coin = ff_hash(
- ec.p,
- public[0],
- public[1],
- note.value,
- note.token_id,
- note.serial,
- note.coin_blind,
- spend_hook,
- user_data
- )
- assert coin == tx.outputs[0].mint_proof.get_revealed().coin
- all_coins = set([coin])
- # Used to export user_data from this coin so it can be accessed
- # by 0xdao_ruleset
- user_data_blind = ec.random_base()
- builder = TransactionBuilder(ec)
- builder.add_input(all_coins, secret, note, user_data_blind)
- secret2 = ec.random_scalar()
- public2 = ec.multiply(secret, ec.G)
- builder.add_output(1000, token_id, public2, spend_hook=[b"0x0000"],
- user_data=[])
- # Change
- builder.add_output(note.value - 1000, token_id, public, spend_hook, user_data)
- tx = builder.build()
- if (update := state_transition(state, tx)) is None:
- return -1
- state.apply(update)
- 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 == ff_hash(ec.p, user_data,
- user_data_blind)
- bulla = ff_hash(
- ec.p,
- proposal_auth_public[0],
- proposal_auth_public[1],
- threshold,
- quorum
- )
- assert user_data == bulla
- # Now enforce DAO rules:
- # 1. valid signed proposal
- # 2. positive number of votes
- return 0
- if __name__ == "__main__":
- sys.exit(main(sys.argv))
|