main.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import sys
  2. from classnamespace import ClassNamespace
  3. from crypto import pallas_curve, ff_hash
  4. from tx import TransactionBuilder
  5. class State:
  6. def __init__(self):
  7. self.all_coins = set()
  8. self.nullifiers = set()
  9. def is_valid_merkle(self, all_coins):
  10. return all_coins.issubset(self.all_coins)
  11. def nullifier_exists(self, nullifier):
  12. return nullifier in self.nullifiers
  13. def apply(self, update):
  14. self.nullifiers = self.nullifiers.union(update.nullifiers)
  15. for coin, enc_note in zip(update.coins, update.enc_notes):
  16. self.all_coins.add(coin)
  17. # Try to decrypt notes here
  18. print(f"Received {enc_note.value} DRK")
  19. def state_transition(state, tx):
  20. for input in tx.clear_inputs:
  21. pk = input.signature_public
  22. # Check pk is correct
  23. for input in tx.inputs:
  24. if not state.is_valid_merkle(input.revealed.all_coins):
  25. print(f"invalid merkle root", file=sys.stderr)
  26. return None
  27. nullifier = input.revealed.nullifier
  28. if state.nullifier_exists(nullifier):
  29. print(f"duplicate nullifier found", file=sys.stderr)
  30. return None
  31. is_verify, reason = tx.verify()
  32. if not is_verify:
  33. print(f"tx verify failed: {reason}", file=sys.stderr)
  34. return None
  35. update = ClassNamespace()
  36. update.nullifiers = [input.revealed.nullifier for input in tx.inputs]
  37. update.coins = [output.revealed.coin for output in tx.outputs]
  38. update.enc_notes = [output.enc_note for output in tx.outputs]
  39. return update
  40. class DaoBuilder:
  41. def __init__(self, proposal_auth_public_key, threshold, quorum, ec):
  42. self.proposal_auth_public_key = proposal_auth_public_key
  43. self.threshold = threshold
  44. self.quorum = quorum
  45. self.ec = ec
  46. def build(self):
  47. mint_proof = DaoMintProof(
  48. self.proposal_auth_public_key,
  49. self.threshold,
  50. self.quorum,
  51. self.ec
  52. )
  53. revealed = mint_proof.get_revealed()
  54. dao = Dao(revealed, mint_proof, self.ec)
  55. return dao
  56. class Dao:
  57. def __init__(self, revealed, mint_proof, ec):
  58. self.revealed = revealed
  59. self.mint_proof = mint_proof
  60. self.ec = ec
  61. def verify(self):
  62. if not self.mint_proof.verify(self.revealed):
  63. return False, "mint proof failed to verify"
  64. return True, None
  65. # class DaoExec .etc
  66. class DaoMintProof:
  67. def __init__(self, proposal_auth_public_key, threshold, quorum, ec):
  68. self.proposal_auth_public_key = proposal_auth_public_key
  69. self.threshold = threshold
  70. self.quorum = quorum
  71. self.ec = ec
  72. def get_revealed(self):
  73. revealed = ClassNamespace()
  74. revealed.bulla = ff_hash(
  75. self.ec.p,
  76. self.proposal_auth_public_key[0],
  77. self.proposal_auth_public_key[1],
  78. self.threshold,
  79. self.quorum
  80. )
  81. return revealed
  82. def verify(self, public):
  83. revealed = self.get_revealed()
  84. return True
  85. # Shared between DaoMint and DaoExec
  86. class DaoState:
  87. def __init__(self):
  88. self.bullas = set()
  89. def apply(self, update):
  90. self.bullas.add(update.bulla)
  91. def apply_exec(self, update):
  92. pass
  93. # contract interface functions
  94. def dao_state_transition(state, tx):
  95. is_verify, reason = tx.verify()
  96. if not is_verify:
  97. print(f"dao tx verify failed: {reason}", file=sys.stderr)
  98. return None
  99. update = ClassNamespace()
  100. update.bulla = tx.revealed.bulla
  101. return update
  102. ###### DAO EXEC
  103. class DaoExecBuilder:
  104. def __init__(self):
  105. pass
  106. def build(self):
  107. tx = DaoExec()
  108. return tx
  109. class DaoExec:
  110. def __init__(self):
  111. pass
  112. class DaoExecProof:
  113. def __init__(self):
  114. pass
  115. def dao_exec_state_transition(state, tx):
  116. update = ClassNamespace()
  117. return update
  118. def main(argv):
  119. ec = pallas_curve()
  120. secret = ec.random_scalar()
  121. public = ec.multiply(secret, ec.G)
  122. initial_supply = 21000
  123. token_id = 110
  124. signature_secret = ec.random_scalar()
  125. # Setup the DAO
  126. proposal_auth_secret = ec.random_scalar()
  127. proposal_auth_public = ec.multiply(proposal_auth_secret, ec.G)
  128. threshold = 110
  129. quorum = 110
  130. builder = DaoBuilder(proposal_auth_public, threshold, quorum, ec)
  131. dao_tx = builder.build()
  132. # Each deployment of a contract has a unique state
  133. # associated with it.
  134. dao_state = DaoState()
  135. if (update := dao_state_transition(dao_state, dao_tx)) is None:
  136. return -1
  137. dao_state.apply(update)
  138. builder = TransactionBuilder(ec)
  139. builder.add_clear_input(initial_supply, token_id, signature_secret)
  140. # Address of deployed contract in our example is 0xdao_ruleset
  141. spend_hook = b"0xdao_ruleset"
  142. # This can be a simple hash of the items passed into the ZK proof
  143. # up to corresponding linked ZK proof to interpret however they need.
  144. # In out case, it's the bulla for the DAO
  145. user_data = dao_tx.revealed.bulla
  146. builder.add_output(initial_supply, token_id, public, spend_hook, user_data)
  147. tx = builder.build()
  148. state = State()
  149. if (update := state_transition(state, tx)) is None:
  150. return -1
  151. state.apply(update)
  152. # Now the spend_hook field specifies the function DaoExec
  153. # so the tx above must also be combined with a DaoExec tx
  154. for input in tx.inputs:
  155. assert input.revealed.spend_hook == [b"0xdao_ruleset"]
  156. builder = DaoExecBuilder()
  157. dao_tx = builder.build()
  158. if (update := dao_exec_state_transition(dao_state, dao_tx)) is None:
  159. return -1
  160. dao_state.apply_exec(update)
  161. # State
  162. # functions that can be called on state with params
  163. # functions return an update
  164. # optional encrypted values that can be read by wallets
  165. # --> (do this outside??)
  166. # --> penalized if fail
  167. # apply update to state
  168. # Every votes produces a semi-homomorphic encryption of their vote.
  169. # Which is either yes or no
  170. # We copy the state tree for the governance token so coins can be used
  171. # to vote on other proposals at the same time.
  172. # With their vote, they produce a ZK proof + nullifier
  173. # The votes are unblinded by MPC to a selected party at the end of the
  174. # voting period.
  175. # (that's if we want votes to be hidden during voting)
  176. votes_yes = 10
  177. votes_no = 5
  178. # payment state transition in coin specifies dependency
  179. # the tx exists and ruleset is applied
  180. assert len(tx.outputs) > 0
  181. note = tx.outputs[0].enc_note
  182. coin = ff_hash(
  183. ec.p,
  184. public[0],
  185. public[1],
  186. note.value,
  187. note.token_id,
  188. note.serial,
  189. note.coin_blind,
  190. spend_hook,
  191. user_data
  192. )
  193. assert coin == tx.outputs[0].mint_proof.get_revealed().coin
  194. all_coins = set([coin])
  195. # Used to export user_data from this coin so it can be accessed
  196. # by 0xdao_ruleset
  197. user_data_blind = ec.random_base()
  198. builder = TransactionBuilder(ec)
  199. builder.add_input(all_coins, secret, note, user_data_blind)
  200. secret2 = ec.random_scalar()
  201. public2 = ec.multiply(secret, ec.G)
  202. builder.add_output(1000, token_id, public2, spend_hook=[b"0x0000"],
  203. user_data=[])
  204. # Change
  205. builder.add_output(note.value - 1000, token_id, public, spend_hook, user_data)
  206. tx = builder.build()
  207. if (update := state_transition(state, tx)) is None:
  208. return -1
  209. state.apply(update)
  210. assert len(tx.inputs) == 1
  211. # At least one input has this field value which means the 0xdao_ruleset
  212. # is invoked.
  213. input = tx.inputs[0]
  214. assert input.revealed.spend_hook == b"0xdao_ruleset"
  215. assert input.revealed.enc_user_data == ff_hash(ec.p, user_data,
  216. user_data_blind)
  217. bulla = ff_hash(
  218. ec.p,
  219. proposal_auth_public[0],
  220. proposal_auth_public[1],
  221. threshold,
  222. quorum
  223. )
  224. assert user_data == bulla
  225. # Now enforce DAO rules:
  226. # 1. valid signed proposal
  227. # 2. positive number of votes
  228. return 0
  229. if __name__ == "__main__":
  230. sys.exit(main(sys.argv))