main.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755
  1. import sys
  2. from classnamespace import ClassNamespace
  3. import crypto, money
  4. class MoneyState:
  5. def __init__(self):
  6. self.all_coins = set()
  7. self.nullifiers = set()
  8. def is_valid_merkle(self, all_coins):
  9. return all_coins.issubset(self.all_coins)
  10. def nullifier_exists(self, nullifier):
  11. return nullifier in self.nullifiers
  12. def apply(self, update):
  13. self.nullifiers = self.nullifiers.union(update.nullifiers)
  14. for coin, enc_note in zip(update.coins, update.enc_notes):
  15. self.all_coins.add(coin)
  16. def money_state_transition(state, tx):
  17. for input in tx.clear_inputs:
  18. pk = input.signature_public
  19. # Check pk is correct
  20. for input in tx.inputs:
  21. if not state.is_valid_merkle(input.revealed.all_coins):
  22. print(f"invalid merkle root", file=sys.stderr)
  23. return None
  24. nullifier = input.revealed.nullifier
  25. if state.nullifier_exists(nullifier):
  26. print(f"duplicate nullifier found", file=sys.stderr)
  27. return None
  28. is_verify, reason = tx.verify()
  29. if not is_verify:
  30. print(f"tx verify failed: {reason}", file=sys.stderr)
  31. return None
  32. update = ClassNamespace()
  33. update.nullifiers = [input.revealed.nullifier for input in tx.inputs]
  34. update.coins = [output.revealed.coin for output in tx.outputs]
  35. update.enc_notes = [output.enc_note for output in tx.outputs]
  36. return update
  37. class ProposerTxBuilder:
  38. def __init__(self, proposal, all_dao_bullas, ec):
  39. self.inputs = []
  40. self.proposal = proposal
  41. self.all_dao_bullas = all_dao_bullas
  42. self.ec = ec
  43. def add_input(self, all_coins, secret, note):
  44. input = ClassNamespace()
  45. input.all_coins = all_coins
  46. input.secret = secret
  47. input.note = note
  48. self.inputs.append(input)
  49. def set_dao(self, proposer_limit, quorum, approval_ratio,
  50. gov_token_id, dao_bulla_blind):
  51. self.dao_proposer_limit = proposer_limit
  52. self.dao_quorum = quorum
  53. self.dao_approval_ratio = approval_ratio
  54. self.gov_token_id = gov_token_id
  55. self.dao_bulla_blind = dao_bulla_blind
  56. def build(self):
  57. tx = ProposerTx(self.ec)
  58. token_blind = self.ec.random_scalar()
  59. enc_bulla_blind = self.ec.random_base()
  60. total_value = sum(input.note.value for input in self.inputs)
  61. input_value_blinds = [self.ec.random_scalar() for _ in self.inputs]
  62. total_value_blinds = sum(input_value_blinds)
  63. tx.dao = ClassNamespace()
  64. tx.dao.__name__ = "ProposerTxDao"
  65. # We export proposer_limit as an encrypted value from the DAO
  66. tx.dao.proof = ProposerTxDaoProof(
  67. # Value commit
  68. total_value,
  69. total_value_blinds,
  70. # DAO params
  71. self.dao_proposer_limit,
  72. self.dao_quorum,
  73. self.dao_approval_ratio,
  74. self.gov_token_id,
  75. self.dao_bulla_blind,
  76. # Token commit
  77. token_blind,
  78. # Used by other DAO members to verify the bulla
  79. # used in this proof is for the actual DAO
  80. enc_bulla_blind,
  81. # Proposal
  82. self.proposal.dest,
  83. self.proposal.amount,
  84. self.proposal.blind,
  85. self.proposal.serial,
  86. # Merkle witness
  87. self.all_dao_bullas,
  88. self.ec
  89. )
  90. tx.dao.revealed = tx.dao.proof.get_revealed()
  91. # Members of the DAO need to themselves verify this is the correct
  92. # bulla they are voting on, so we encrypt the blind to them
  93. tx.note = ClassNamespace()
  94. tx.note.enc_bulla_blind = enc_bulla_blind
  95. tx.note.proposal = self.proposal
  96. signature_secrets = []
  97. for input, value_blind in zip(self.inputs, input_value_blinds):
  98. signature_secret = self.ec.random_scalar()
  99. signature_secrets.append(signature_secret)
  100. tx_input = ClassNamespace()
  101. tx_input.__name__ = "TransactionInput"
  102. tx_input.proof = ProposerTxInputProof(
  103. input.note.value, input.note.token_id, value_blind,
  104. token_blind, input.note.serial, input.note.coin_blind,
  105. input.secret, input.note.spend_hook, input.note.user_data,
  106. input.all_coins, signature_secret, self.ec)
  107. tx_input.revealed = tx_input.proof.get_revealed()
  108. tx.inputs.append(tx_input)
  109. unsigned_tx_data = tx.partial_encode()
  110. for (input, signature_secret) in zip(tx.inputs, signature_secrets):
  111. signature = crypto.sign(unsigned_tx_data, signature_secret, self.ec)
  112. input.signature = signature
  113. return tx
  114. class ProposerTx:
  115. def __init__(self, ec):
  116. self.inputs = []
  117. self.dao = None
  118. self.note = None
  119. self.ec = ec
  120. def partial_encode(self):
  121. # There is no cake
  122. return b"hello"
  123. def verify(self):
  124. if not self._check_value_commits():
  125. return False, "value commits do not match"
  126. if not self._check_proofs():
  127. return False, "proofs failed to verify"
  128. if not self._verify_token_commitments():
  129. return False, "token ID mismatch"
  130. unsigned_tx_data = self.partial_encode()
  131. for input in self.inputs:
  132. public = input.revealed.signature_public
  133. if not crypto.verify(unsigned_tx_data, input.signature,
  134. public, self.ec):
  135. return False
  136. return True, None
  137. def _check_value_commits(self):
  138. valcom_total = (0, 1, 0)
  139. for input in self.inputs:
  140. value_commit = input.revealed.value_commit
  141. valcom_total = self.ec.add(valcom_total, value_commit)
  142. return valcom_total == self.dao.revealed.value_commit
  143. def _check_proofs(self):
  144. for input in self.inputs:
  145. if not input.proof.verify(input.revealed):
  146. return False
  147. if not self.dao.proof.verify(self.dao.revealed):
  148. return False
  149. return True
  150. def _verify_token_commitments(self):
  151. token_commit_value = self.dao.revealed.token_commit
  152. for input in self.inputs:
  153. if input.revealed.token_commit != token_commit_value:
  154. return False
  155. return True
  156. class ProposerTxInputProof:
  157. def __init__(self, value, token_id, value_blind, token_blind, serial,
  158. coin_blind, secret, spend_hook, user_data,
  159. all_coins, signature_secret, ec):
  160. self.value = value
  161. self.token_id = token_id
  162. self.value_blind = value_blind
  163. self.token_blind = token_blind
  164. self.serial = serial
  165. self.coin_blind = coin_blind
  166. self.secret = secret
  167. self.spend_hook = spend_hook
  168. self.user_data = user_data
  169. self.all_coins = all_coins
  170. self.signature_secret = signature_secret
  171. self.ec = ec
  172. def get_revealed(self):
  173. revealed = ClassNamespace()
  174. revealed.value_commit = crypto.pedersen_encrypt(
  175. self.value, self.value_blind, self.ec
  176. )
  177. revealed.token_commit = crypto.pedersen_encrypt(
  178. self.token_id, self.token_blind, self.ec
  179. )
  180. # is_valid_merkle_root()
  181. revealed.all_coins = self.all_coins
  182. revealed.signature_public = self.ec.multiply(self.signature_secret,
  183. self.ec.G)
  184. return revealed
  185. def verify(self, public):
  186. revealed = self.get_revealed()
  187. public_key = self.ec.multiply(self.secret, self.ec.G)
  188. coin = crypto.ff_hash(
  189. self.ec.p,
  190. public_key[0],
  191. public_key[1],
  192. self.value,
  193. self.token_id,
  194. self.serial,
  195. self.coin_blind,
  196. self.spend_hook,
  197. self.user_data,
  198. )
  199. # Merkle root check
  200. if coin not in self.all_coins:
  201. return False
  202. return all([
  203. revealed.value_commit == public.value_commit,
  204. revealed.token_commit == public.token_commit,
  205. revealed.all_coins == public.all_coins,
  206. revealed.signature_public == public.signature_public
  207. ])
  208. class ProposerTxDaoProof:
  209. def __init__(self, total_value, total_value_blinds,
  210. proposer_limit, quorum, approval_ratio,
  211. gov_token_id, dao_bulla_blind,
  212. token_blind, enc_bulla_blind,
  213. proposal_dest, proposal_amount, proposal_blind,
  214. proposal_serial,
  215. all_dao_bullas, ec):
  216. self.total_value = total_value
  217. self.total_value_blinds = total_value_blinds
  218. self.proposer_limit = proposer_limit
  219. self.quorum = quorum
  220. self.approval_ratio = approval_ratio
  221. self.gov_token_id = gov_token_id
  222. self.dao_bulla_blind = dao_bulla_blind
  223. self.token_blind = token_blind
  224. self.enc_bulla_blind = enc_bulla_blind
  225. self.proposal_dest = proposal_dest
  226. self.proposal_amount = proposal_amount
  227. self.proposal_blind = proposal_blind
  228. self.proposal_serial = proposal_serial
  229. self.all_dao_bullas = all_dao_bullas
  230. self.ec = ec
  231. def get_revealed(self):
  232. revealed = ClassNamespace()
  233. # Value commit
  234. revealed.value_commit = crypto.pedersen_encrypt(
  235. self.total_value, self.total_value_blinds, self.ec
  236. )
  237. # Token ID
  238. revealed.token_commit = crypto.pedersen_encrypt(
  239. self.gov_token_id, self.token_blind, self.ec
  240. )
  241. # encrypted DAO bulla
  242. bulla = crypto.ff_hash(
  243. self.ec.p,
  244. self.proposer_limit,
  245. self.quorum,
  246. self.approval_ratio,
  247. self.gov_token_id,
  248. self.dao_bulla_blind
  249. )
  250. revealed.enc_bulla = crypto.ff_hash(self.ec.p, bulla, self.enc_bulla_blind)
  251. # encrypted proposal
  252. revealed.proposal_bulla = crypto.ff_hash(
  253. self.ec.p,
  254. self.proposal_dest[0],
  255. self.proposal_dest[1],
  256. self.proposal_amount,
  257. self.proposal_blind,
  258. self.proposal_serial,
  259. bulla
  260. )
  261. # The merkle root
  262. revealed.all_dao_bullas = self.all_dao_bullas
  263. return revealed
  264. def verify(self, public):
  265. revealed = self.get_revealed()
  266. bulla = crypto.ff_hash(
  267. self.ec.p,
  268. self.proposer_limit,
  269. self.quorum,
  270. self.approval_ratio,
  271. self.gov_token_id,
  272. self.dao_bulla_blind
  273. )
  274. # Merkle root check
  275. if bulla not in self.all_dao_bullas:
  276. return False
  277. #
  278. # total_value >= proposer_limit
  279. #
  280. if not self.total_value >= self.proposer_limit:
  281. return False
  282. return all([
  283. revealed.value_commit == public.value_commit,
  284. revealed.token_commit == public.token_commit,
  285. revealed.enc_bulla == public.enc_bulla,
  286. revealed.proposal_bulla == public.proposal_bulla,
  287. revealed.all_dao_bullas == public.all_dao_bullas
  288. ])
  289. class DaoBuilder:
  290. def __init__(self, proposer_limit, quorum, approval_ratio,
  291. gov_token_id, dao_bulla_blind, ec):
  292. self.proposer_limit = proposer_limit
  293. self.quorum = quorum
  294. self.approval_ratio = approval_ratio
  295. self.gov_token_id = gov_token_id
  296. self.dao_bulla_blind = dao_bulla_blind
  297. self.ec = ec
  298. def build(self):
  299. mint_proof = DaoMintProof(
  300. self.proposer_limit,
  301. self.quorum,
  302. self.approval_ratio,
  303. self.gov_token_id,
  304. self.dao_bulla_blind,
  305. self.ec
  306. )
  307. revealed = mint_proof.get_revealed()
  308. dao = Dao(revealed, mint_proof, self.ec)
  309. return dao
  310. class Dao:
  311. def __init__(self, revealed, mint_proof, ec):
  312. self.revealed = revealed
  313. self.mint_proof = mint_proof
  314. self.ec = ec
  315. def verify(self):
  316. if not self.mint_proof.verify(self.revealed):
  317. return False, "mint proof failed to verify"
  318. return True, None
  319. # class DaoExec .etc
  320. class DaoMintProof:
  321. def __init__(self, proposer_limit, quorum, approval_ratio,
  322. gov_token_id, dao_bulla_blind, ec):
  323. self.proposer_limit = proposer_limit
  324. self.quorum = quorum
  325. self.approval_ratio = approval_ratio
  326. self.gov_token_id = gov_token_id
  327. self.dao_bulla_blind = dao_bulla_blind
  328. self.ec = ec
  329. def get_revealed(self):
  330. revealed = ClassNamespace()
  331. revealed.bulla = crypto.ff_hash(
  332. self.ec.p,
  333. self.proposer_limit,
  334. self.quorum,
  335. self.approval_ratio,
  336. self.gov_token_id,
  337. self.dao_bulla_blind
  338. )
  339. return revealed
  340. def verify(self, public):
  341. revealed = self.get_revealed()
  342. return revealed.bulla == public.bulla
  343. # Shared between DaoMint and DaoExec
  344. class DaoState:
  345. def __init__(self):
  346. self.dao_bullas = set()
  347. self.proposals = set()
  348. def is_valid_merkle(self, all_dao_bullas):
  349. return all_dao_bullas.issubset(self.dao_bullas)
  350. def apply_proposal_tx(self, update):
  351. self.proposals.add(update.proposal)
  352. def apply(self, update):
  353. self.dao_bullas.add(update.bulla)
  354. # contract interface functions
  355. def dao_state_transition(state, tx):
  356. is_verify, reason = tx.verify()
  357. if not is_verify:
  358. print(f"dao tx verify failed: {reason}", file=sys.stderr)
  359. return None
  360. update = ClassNamespace()
  361. update.bulla = tx.revealed.bulla
  362. return update
  363. ###### DAO EXEC
  364. class DaoExecBuilder:
  365. def __init__(self):
  366. pass
  367. def build(self):
  368. tx = DaoExec()
  369. return tx
  370. class DaoExec:
  371. def __init__(self):
  372. pass
  373. class DaoExecProof:
  374. def __init__(self):
  375. pass
  376. def dao_exec_state_transition(state, tx):
  377. update = ClassNamespace()
  378. return update
  379. # contract interface functions
  380. def proposal_state_transition(dao_state, gov_state, tx):
  381. is_verify, reason = tx.verify()
  382. if not is_verify:
  383. print(f"dao tx verify failed: {reason}", file=sys.stderr)
  384. return None
  385. if not dao_state.is_valid_merkle(tx.dao.revealed.all_dao_bullas):
  386. print(f"invalid merkle root dao", file=sys.stderr)
  387. return None
  388. for input in tx.inputs:
  389. if not gov_state.is_valid_merkle(input.revealed.all_coins):
  390. print(f"invalid merkle root", file=sys.stderr)
  391. return None
  392. update = ClassNamespace()
  393. update.proposal = tx.dao.revealed.proposal_bulla
  394. return update
  395. def main(argv):
  396. ec = crypto.pallas_curve()
  397. money_state = MoneyState()
  398. gov_state = MoneyState()
  399. dao_state = DaoState()
  400. # Money parameters
  401. money_initial_supply = 21000
  402. money_token_id = 110
  403. # Governance token parameters
  404. gov_initial_supply = 10000
  405. gov_token_id = 4
  406. # DAO parameters
  407. dao_proposer_limit = 110
  408. dao_quorum = 110
  409. dao_approval_ratio = 2
  410. ################################################
  411. # Create the DAO bulla
  412. ################################################
  413. # Setup the DAO
  414. dao_shared_secret = ec.random_scalar()
  415. dao_public_key = ec.multiply(dao_shared_secret, ec.G)
  416. dao_bulla_blind = ec.random_base()
  417. builder = DaoBuilder(
  418. dao_proposer_limit,
  419. dao_quorum,
  420. dao_approval_ratio,
  421. gov_token_id,
  422. dao_bulla_blind,
  423. ec
  424. )
  425. tx = builder.build()
  426. # Each deployment of a contract has a unique state
  427. # associated with it.
  428. if (update := dao_state_transition(dao_state, tx)) is None:
  429. return -1
  430. dao_state.apply(update)
  431. dao_bulla = tx.revealed.bulla
  432. ################################################
  433. # Mint the initial supply of treasury token
  434. # and send it all to the DAO directly
  435. ################################################
  436. # Only used for this tx. Discarded after
  437. signature_secret = ec.random_scalar()
  438. builder = money.SendPaymentTxBuilder(ec)
  439. builder.add_clear_input(money_initial_supply, money_token_id,
  440. signature_secret)
  441. # Address of deployed contract in our example is 0xdao_ruleset
  442. spend_hook = b"0xdao_ruleset"
  443. # This can be a simple hash of the items passed into the ZK proof
  444. # up to corresponding linked ZK proof to interpret however they need.
  445. # In out case, it's the bulla for the DAO
  446. user_data = dao_bulla
  447. builder.add_output(money_initial_supply, money_token_id, dao_public_key,
  448. spend_hook, user_data)
  449. tx = builder.build()
  450. # This state_transition function is the ruleset for anon payments
  451. if (update := money_state_transition(money_state, tx)) is None:
  452. return -1
  453. money_state.apply(update)
  454. # payment state transition in coin specifies dependency
  455. # the tx exists and ruleset is applied
  456. assert len(tx.outputs) > 0
  457. note = tx.outputs[0].enc_note
  458. coin = crypto.ff_hash(
  459. ec.p,
  460. dao_public_key[0],
  461. dao_public_key[1],
  462. note.value,
  463. note.token_id,
  464. note.serial,
  465. note.coin_blind,
  466. spend_hook,
  467. user_data
  468. )
  469. assert coin == tx.outputs[0].mint_proof.get_revealed().coin
  470. for coin, enc_note in zip(update.coins, update.enc_notes):
  471. # Try decrypt note here
  472. print(f"Received {enc_note.value} DRK")
  473. ################################################
  474. # Mint the governance token
  475. # Send it to two hodlers
  476. ################################################
  477. # Hodler 1
  478. gov_secret_1 = ec.random_scalar()
  479. gov_public_1 = ec.multiply(gov_secret_1, ec.G)
  480. # Hodler 2
  481. gov_secret_2 = ec.random_scalar()
  482. gov_public_2 = ec.multiply(gov_secret_2, ec.G)
  483. # Only used for this tx. Discarded after
  484. signature_secret = ec.random_scalar()
  485. builder = money.SendPaymentTxBuilder(ec)
  486. builder.add_clear_input(gov_initial_supply, gov_token_id,
  487. signature_secret)
  488. assert 2 * 5000 == gov_initial_supply
  489. builder.add_output(5000, gov_token_id, gov_public_1,
  490. b"0x0000", b"0x0000")
  491. builder.add_output(5000, gov_token_id, gov_public_1,
  492. b"0x0000", b"0x0000")
  493. tx = builder.build()
  494. # This state_transition function is the ruleset for anon payments
  495. if (update := money_state_transition(gov_state, tx)) is None:
  496. return -1
  497. gov_state.apply(update)
  498. # Decrypt output notes
  499. assert len(tx.outputs) == 2
  500. gov_user_1_note = tx.outputs[0].enc_note
  501. gov_user_2_note = tx.outputs[1].enc_note
  502. for coin, enc_note in zip(update.coins, update.enc_notes):
  503. # Try decrypt note here
  504. print(f"Received {enc_note.value} GOV")
  505. ################################################
  506. # Propose the vote
  507. # In order to make a valid vote, first the proposer must
  508. # meet a criteria for a minimum number of gov tokens
  509. ################################################
  510. user_secret = ec.random_scalar()
  511. user_public = ec.multiply(user_secret, ec.G)
  512. # There is a struct that corresponds to the configuration of this
  513. # particular vote.
  514. # For MVP, just use a single-option list of [destination, amount]
  515. # Send user 1000 DRK
  516. proposal = ClassNamespace()
  517. proposal.dest = user_public
  518. proposal.amount = 1000
  519. proposal.blind = ec.random_base()
  520. # Used to produce the nullifier when the vote is executed
  521. proposal.serial = ec.random_base()
  522. # For vote to become valid, the proposer must prove
  523. # that they own more than proposer_limit number of gov tokens.
  524. builder = ProposerTxBuilder(proposal, dao_state.dao_bullas, ec)
  525. witness = gov_state.all_coins
  526. builder.add_input(witness, gov_secret_1, gov_user_1_note)
  527. builder.set_dao(
  528. dao_proposer_limit,
  529. dao_quorum,
  530. dao_approval_ratio,
  531. gov_token_id,
  532. dao_bulla_blind
  533. )
  534. tx = builder.build()
  535. # No state changes actually happen so ignore the update
  536. # We just verify the tx is correct basically.
  537. if (update := proposal_state_transition(dao_state, gov_state, tx)) is None:
  538. return -1
  539. dao_state.apply_proposal_tx(update)
  540. # State
  541. # functions that can be called on state with params
  542. # functions return an update
  543. # optional encrypted values that can be read by wallets
  544. # --> (do this outside??)
  545. # --> penalized if fail
  546. # apply update to state
  547. # Every votes produces a semi-homomorphic encryption of their vote.
  548. # Which is either yes or no
  549. # We copy the state tree for the governance token so coins can be used
  550. # to vote on other proposals at the same time.
  551. # With their vote, they produce a ZK proof + nullifier
  552. # The votes are unblinded by MPC to a selected party at the end of the
  553. # voting period.
  554. # (that's if we want votes to be hidden during voting)
  555. votes_yes = 10
  556. votes_no = 5
  557. ################################################
  558. # Execute the vote
  559. ################################################
  560. # Used to export user_data from this coin so it can be accessed
  561. # by 0xdao_ruleset
  562. user_data_blind = ec.random_base()
  563. builder = money.SendPaymentTxBuilder(ec)
  564. witness = money_state.all_coins
  565. builder.add_input(witness, dao_shared_secret, note, user_data_blind)
  566. builder.add_output(1000, money_token_id, user_public,
  567. spend_hook=b"0x0000", user_data=b"0x0000")
  568. # Change
  569. builder.add_output(note.value - 1000, money_token_id, dao_public_key,
  570. spend_hook, user_data)
  571. tx = builder.build()
  572. if (update := money_state_transition(money_state, tx)) is None:
  573. return -1
  574. money_state.apply(update)
  575. # Now the spend_hook field specifies the function DaoExec
  576. # so the tx above must also be combined with a DaoExec tx
  577. assert len(tx.inputs) == 1
  578. # At least one input has this field value which means the 0xdao_ruleset
  579. # is invoked.
  580. input = tx.inputs[0]
  581. assert input.revealed.spend_hook == b"0xdao_ruleset"
  582. assert (input.revealed.enc_user_data ==
  583. crypto.ff_hash(
  584. ec.p,
  585. user_data,
  586. user_data_blind
  587. ))
  588. # Verifier cannot see DAO bulla
  589. # They see the enc_user_data which is also in the DAO exec contract
  590. assert user_data == crypto.ff_hash(
  591. ec.p,
  592. dao_proposer_limit,
  593. dao_quorum,
  594. dao_approval_ratio,
  595. gov_token_id,
  596. dao_bulla_blind
  597. ) # DAO bulla
  598. # proposer proof
  599. # Now enforce DAO rules:
  600. # 1. gov token IDs must match on all inputs
  601. # 2. proposals must be submitted by minimum amount
  602. # - need protection so can't collude? must be a single signer??
  603. # - stellar: doesn't have to be robust for this MVP
  604. # 4. number of votes >= quorum
  605. # - just positive votes or all votes?
  606. # - stellar: no that's all votes
  607. # 4. outcome > approval_ratio
  608. # 5. structure of outputs
  609. # output 0: value and address
  610. # output 1: change address
  611. builder = DaoExecBuilder()
  612. tx = builder.build()
  613. if (update := dao_exec_state_transition(dao_state, tx)) is None:
  614. return -1
  615. #dao_state.apply_exec(update)
  616. return 0
  617. if __name__ == "__main__":
  618. sys.exit(main(sys.argv))