diff --git a/bin/daod/demo/main.py b/bin/daod/demo/main.py index bdcfac5fd..95ed4f7c2 100644 --- a/bin/daod/demo/main.py +++ b/bin/daod/demo/main.py @@ -49,8 +49,9 @@ def money_state_transition(state, tx): class ProposerTxBuilder: - def __init__(self, all_dao_bullas, ec): + def __init__(self, proposal, all_dao_bullas, ec): self.inputs = [] + self.proposal = proposal self.all_dao_bullas = all_dao_bullas self.ec = ec @@ -73,7 +74,6 @@ class ProposerTxBuilder: def build(self): tx = ProposerTx(self.ec) token_blind = self.ec.random_scalar() - proposer_limit_blind = self.ec.random_base() enc_bulla_blind = self.ec.random_base() total_value = sum(input.note.value for input in self.inputs) @@ -84,11 +84,28 @@ class ProposerTxBuilder: tx.dao.__name__ = "ProposerTxDao" # We export proposer_limit as an encrypted value from the DAO tx.dao.proof = ProposerTxDaoProof( - total_value, total_value_blinds, - self.dao_proposer_limit, self.dao_quorum, self.dao_approval_ratio, - self.gov_token_id, self.dao_bulla_blind, - token_blind, proposer_limit_blind, enc_bulla_blind, - self.all_dao_bullas, self.ec + # Value commit + total_value, + total_value_blinds, + # DAO params + self.dao_proposer_limit, + self.dao_quorum, + self.dao_approval_ratio, + self.gov_token_id, + 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.blind, + self.proposal.serial, + # Merkle witness + self.all_dao_bullas, + self.ec ) tx.dao.revealed = tx.dao.proof.get_revealed() @@ -96,6 +113,7 @@ class ProposerTxBuilder: # 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): @@ -243,7 +261,9 @@ class ProposerTxDaoProof: def __init__(self, total_value, total_value_blinds, proposer_limit, quorum, approval_ratio, gov_token_id, dao_bulla_blind, - token_blind, proposer_limit_blind, enc_bulla_blind, + token_blind, enc_bulla_blind, + proposal_dest, proposal_amount, proposal_blind, + proposal_serial, all_dao_bullas, ec): self.total_value = total_value self.total_value_blinds = total_value_blinds @@ -253,8 +273,11 @@ class ProposerTxDaoProof: self.gov_token_id = gov_token_id self.dao_bulla_blind = dao_bulla_blind self.token_blind = token_blind - self.proposer_limit_blind = proposer_limit_blind self.enc_bulla_blind = enc_bulla_blind + self.proposal_dest = proposal_dest + self.proposal_amount = proposal_amount + self.proposal_blind = proposal_blind + self.proposal_serial = proposal_serial self.all_dao_bullas = all_dao_bullas self.ec = ec @@ -278,6 +301,16 @@ class ProposerTxDaoProof: 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_blind, + self.proposal_serial, + bulla + ) # The merkle root revealed.all_dao_bullas = self.all_dao_bullas return revealed @@ -307,19 +340,10 @@ class ProposerTxDaoProof: 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 ]) -# contract interface functions -def proposer_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() - return update - class DaoBuilder: def __init__(self, proposer_limit, quorum, approval_ratio, @@ -393,13 +417,17 @@ class DaoMintProof: class DaoState: def __init__(self): - self.bullas = set() + self.dao_bullas = set() + self.proposals = set() + + def is_valid_merkle(self, all_dao_bullas): + return all_dao_bullas.issubset(self.dao_bullas) + + def apply_proposal_tx(self, update): + self.proposals.add(update.proposal) def apply(self, update): - self.bullas.add(update.bulla) - - def apply_exec(self, update): - pass + self.dao_bullas.add(update.bulla) # contract interface functions def dao_state_transition(state, tx): @@ -437,6 +465,26 @@ def dao_exec_state_transition(state, tx): update = ClassNamespace() 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 + def main(argv): ec = crypto.pallas_curve() @@ -588,18 +636,13 @@ def main(argv): proposal.dest = user_public proposal.amount = 1000 proposal.blind = ec.random_base() + # Used to produce the nullifier when the vote is executed + proposal.serial = ec.random_base() # For vote to become valid, the proposer must prove # that they own more than proposer_limit number of gov tokens. - enc_proposal = crypto.ff_hash( - ec.p, - proposal.dest[0], - proposal.dest[1], - proposal.amount, - proposal.blind - ) - builder = ProposerTxBuilder(dao_state.bullas, ec) + 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( @@ -612,8 +655,10 @@ def main(argv): tx = builder.build() # No state changes actually happen so ignore the update - if (_ := proposer_state_transition(gov_state, tx)) is None: + # 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) # State # functions that can be called on state with params @@ -701,7 +746,7 @@ def main(argv): tx = builder.build() if (update := dao_exec_state_transition(dao_state, tx)) is None: return -1 - dao_state.apply_exec(update) + #dao_state.apply_exec(update) return 0