daod: finish final execution step

This commit is contained in:
narodnik
2022-05-19 12:19:42 +02:00
parent 85b601b121
commit 30ac704038
2 changed files with 208 additions and 41 deletions

View File

@@ -63,13 +63,8 @@ class ProposerTxBuilder:
input.note = note
self.inputs.append(input)
def set_dao(self, proposer_limit, quorum, approval_ratio,
gov_token_id, dao_bulla_blind):
self.dao_proposer_limit = proposer_limit
self.dao_quorum = quorum
self.dao_approval_ratio = approval_ratio
self.gov_token_id = gov_token_id
self.dao_bulla_blind = dao_bulla_blind
def set_dao(self, dao):
self.dao = dao
def build(self):
tx = ProposerTx(self.ec)
@@ -88,11 +83,12 @@ class ProposerTxBuilder:
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,
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
@@ -261,7 +257,7 @@ class ProposerTxDaoProof:
def __init__(self, total_value, total_value_blinds,
proposer_limit, quorum, approval_ratio,
gov_token_id, dao_bulla_blind,
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,
@@ -272,6 +268,7 @@ class ProposerTxDaoProof:
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
@@ -300,6 +297,8 @@ class ProposerTxDaoProof:
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)
@@ -327,12 +326,16 @@ class ProposerTxDaoProof:
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
assert self.proposal_amount > 0
#
# total_value >= proposer_limit
#
@@ -587,11 +590,12 @@ class VoteTx:
class DaoBuilder:
def __init__(self, proposer_limit, quorum, approval_ratio,
gov_token_id, dao_bulla_blind, ec):
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
@@ -602,6 +606,7 @@ class DaoBuilder:
self.quorum,
self.approval_ratio,
self.gov_token_id,
self.dao_public_key,
self.dao_bulla_blind,
self.ec
)
@@ -627,11 +632,12 @@ class Dao:
class DaoMintProof:
def __init__(self, proposer_limit, quorum, approval_ratio,
gov_token_id, dao_bulla_blind, ec):
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
@@ -644,6 +650,8 @@ class DaoMintProof:
self.quorum,
self.approval_ratio,
self.gov_token_id,
self.dao_public_key[0],
self.dao_public_key[1],
self.dao_bulla_blind
)
@@ -668,11 +676,14 @@ class DaoState:
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):
pass
self.proposal_nullifiers.add(update.proposal_nullifier)
def apply(self, update):
self.dao_bullas.add(update.bulla)
@@ -700,6 +711,12 @@ class DaoExecBuilder:
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
@@ -709,6 +726,12 @@ class DaoExecBuilder:
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
@@ -722,6 +745,12 @@ class DaoExecBuilder:
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()
@@ -750,6 +779,12 @@ class DaoExecProof:
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
@@ -759,6 +794,12 @@ class DaoExecProof:
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
@@ -766,10 +807,6 @@ class DaoExecProof:
revealed = ClassNamespace()
# Corresponds to proposals merkle root
revealed.all_proposals = self.all_proposals
return revealed
def verify(self, public):
revealed = self.get_revealed()
dao_bulla = crypto.ff_hash(
self.ec.p,
@@ -777,6 +814,76 @@ class DaoExecProof:
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(
@@ -792,10 +899,21 @@ class DaoExecProof:
# This being true also implies the DAO is valid
assert proposal_bulla in self.all_proposals
assert self.total_votes >= self.dao.quorum
assert self.win_votes >= self.dao.approval_ratio
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):
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)
@@ -805,8 +923,33 @@ def dao_exec_state_transition(state, tx):
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 = ...
update.proposal_nullifier = tx.revealed.proposal_nullifier
return update
# contract interface functions
@@ -901,6 +1044,7 @@ def main(argv):
dao_quorum,
dao_approval_ratio,
gov_token_id,
dao_public_key,
dao_bulla_blind,
ec
)
@@ -940,6 +1084,10 @@ def main(argv):
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
@@ -1046,16 +1194,18 @@ def main(argv):
# 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_proposer_limit,
dao_quorum,
dao_approval_ratio,
gov_token_id,
dao_bulla_blind
)
builder.set_dao(dao)
tx = builder.build()
# No state changes actually happen so ignore the update
@@ -1223,9 +1373,13 @@ def main(argv):
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
@@ -1249,12 +1403,13 @@ def main(argv):
# - verifier: check sum of vote_commits is correct
# - win_votes / total_votes >= approval_ratio
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.bulla_blind = dao_bulla_blind
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,
@@ -1264,13 +1419,23 @@ def main(argv):
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)) is None:
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__":

View File

@@ -8,6 +8,8 @@ class SendPaymentTxBuilder:
self.clear_inputs = []
self.inputs = []
self.outputs = []
self.input_blinds = []
self.output_blinds = []
self.ec = ec
@@ -58,11 +60,11 @@ class SendPaymentTxBuilder:
input.signature_secret, self.ec.G)
tx.clear_inputs.append(tx_clear_input)
input_blinds = []
self.input_blinds = []
signature_secrets = []
for input in self.inputs:
value_blind = self.ec.random_scalar()
input_blinds.append(value_blind)
self.input_blinds.append(value_blind)
signature_secret = self.ec.random_scalar()
signature_secrets.append(signature_secret)
@@ -79,14 +81,14 @@ class SendPaymentTxBuilder:
tx.inputs.append(tx_input)
assert self.outputs
output_blinds = []
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, input_blinds, output_blinds)
tx.clear_inputs, self.input_blinds, self.output_blinds)
else:
value_blind = self.ec.random_scalar()
output_blinds.append(value_blind)
self.output_blinds.append(value_blind)
note = ClassNamespace()
note.serial = self.ec.random_base()