main.py 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443
  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, dao):
  50. self.dao = dao
  51. def build(self):
  52. tx = ProposerTx(self.ec)
  53. token_blind = self.ec.random_scalar()
  54. enc_bulla_blind = self.ec.random_base()
  55. total_value = sum(input.note.value for input in self.inputs)
  56. input_value_blinds = [self.ec.random_scalar() for _ in self.inputs]
  57. total_value_blinds = sum(input_value_blinds)
  58. tx.dao = ClassNamespace()
  59. tx.dao.__name__ = "ProposerTxDao"
  60. # We export proposer_limit as an encrypted value from the DAO
  61. tx.dao.proof = ProposerTxDaoProof(
  62. # Value commit
  63. total_value,
  64. total_value_blinds,
  65. # DAO params
  66. self.dao.proposer_limit,
  67. self.dao.quorum,
  68. self.dao.approval_ratio,
  69. self.dao.gov_token_id,
  70. self.dao.public_key,
  71. self.dao.bulla_blind,
  72. # Token commit
  73. token_blind,
  74. # Used by other DAO members to verify the bulla
  75. # used in this proof is for the actual DAO
  76. enc_bulla_blind,
  77. # Proposal
  78. self.proposal.dest,
  79. self.proposal.amount,
  80. self.proposal.serial,
  81. self.proposal.token_id,
  82. self.proposal.blind,
  83. # Merkle witness
  84. self.all_dao_bullas,
  85. self.ec
  86. )
  87. tx.dao.revealed = tx.dao.proof.get_revealed()
  88. # Members of the DAO need to themselves verify this is the correct
  89. # bulla they are voting on, so we encrypt the blind to them
  90. tx.note = ClassNamespace()
  91. tx.note.enc_bulla_blind = enc_bulla_blind
  92. tx.note.proposal = self.proposal
  93. signature_secrets = []
  94. for input, value_blind in zip(self.inputs, input_value_blinds):
  95. signature_secret = self.ec.random_scalar()
  96. signature_secrets.append(signature_secret)
  97. tx_input = ClassNamespace()
  98. tx_input.__name__ = "TransactionInput"
  99. tx_input.proof = ProposerTxInputProof(
  100. input.note.value, input.note.token_id, value_blind,
  101. token_blind, input.note.serial, input.note.coin_blind,
  102. input.secret, input.note.spend_hook, input.note.user_data,
  103. input.all_coins, signature_secret, self.ec)
  104. tx_input.revealed = tx_input.proof.get_revealed()
  105. tx.inputs.append(tx_input)
  106. unsigned_tx_data = tx.partial_encode()
  107. for (input, signature_secret) in zip(tx.inputs, signature_secrets):
  108. signature = crypto.sign(unsigned_tx_data, signature_secret, self.ec)
  109. input.signature = signature
  110. return tx
  111. class ProposerTx:
  112. def __init__(self, ec):
  113. self.inputs = []
  114. self.dao = None
  115. self.note = None
  116. self.ec = ec
  117. def partial_encode(self):
  118. # There is no cake
  119. return b"hello"
  120. def verify(self):
  121. if not self._check_value_commits():
  122. return False, "value commits do not match"
  123. if not self._check_proofs():
  124. return False, "proofs failed to verify"
  125. if not self._verify_token_commitments():
  126. return False, "token ID mismatch"
  127. unsigned_tx_data = self.partial_encode()
  128. for input in self.inputs:
  129. public = input.revealed.signature_public
  130. if not crypto.verify(unsigned_tx_data, input.signature,
  131. public, self.ec):
  132. return False
  133. return True, None
  134. def _check_value_commits(self):
  135. valcom_total = (0, 1, 0)
  136. for input in self.inputs:
  137. value_commit = input.revealed.value_commit
  138. valcom_total = self.ec.add(valcom_total, value_commit)
  139. return valcom_total == self.dao.revealed.value_commit
  140. def _check_proofs(self):
  141. for input in self.inputs:
  142. if not input.proof.verify(input.revealed):
  143. return False
  144. if not self.dao.proof.verify(self.dao.revealed):
  145. return False
  146. return True
  147. def _verify_token_commitments(self):
  148. token_commit_value = self.dao.revealed.token_commit
  149. for input in self.inputs:
  150. if input.revealed.token_commit != token_commit_value:
  151. return False
  152. return True
  153. class ProposerTxInputProof:
  154. def __init__(self, value, token_id, value_blind, token_blind, serial,
  155. coin_blind, secret, spend_hook, user_data,
  156. all_coins, signature_secret, ec):
  157. self.value = value
  158. self.token_id = token_id
  159. self.value_blind = value_blind
  160. self.token_blind = token_blind
  161. self.serial = serial
  162. self.coin_blind = coin_blind
  163. self.secret = secret
  164. self.spend_hook = spend_hook
  165. self.user_data = user_data
  166. self.all_coins = all_coins
  167. self.signature_secret = signature_secret
  168. self.ec = ec
  169. def get_revealed(self):
  170. revealed = ClassNamespace()
  171. revealed.value_commit = crypto.pedersen_encrypt(
  172. self.value, self.value_blind, self.ec
  173. )
  174. revealed.token_commit = crypto.pedersen_encrypt(
  175. self.token_id, self.token_blind, self.ec
  176. )
  177. # is_valid_merkle_root()
  178. revealed.all_coins = self.all_coins
  179. revealed.signature_public = self.ec.multiply(self.signature_secret,
  180. self.ec.G)
  181. return revealed
  182. def verify(self, public):
  183. revealed = self.get_revealed()
  184. public_key = self.ec.multiply(self.secret, self.ec.G)
  185. coin = crypto.ff_hash(
  186. self.ec.p,
  187. public_key[0],
  188. public_key[1],
  189. self.value,
  190. self.token_id,
  191. self.serial,
  192. self.coin_blind,
  193. self.spend_hook,
  194. self.user_data,
  195. )
  196. # Merkle root check
  197. if coin not in self.all_coins:
  198. return False
  199. return all([
  200. revealed.value_commit == public.value_commit,
  201. revealed.token_commit == public.token_commit,
  202. revealed.all_coins == public.all_coins,
  203. revealed.signature_public == public.signature_public
  204. ])
  205. class ProposerTxDaoProof:
  206. def __init__(self, total_value, total_value_blinds,
  207. proposer_limit, quorum, approval_ratio,
  208. gov_token_id, dao_public_key, dao_bulla_blind,
  209. token_blind, enc_bulla_blind,
  210. proposal_dest, proposal_amount, proposal_serial,
  211. proposal_token_id, proposal_blind,
  212. all_dao_bullas, ec):
  213. self.total_value = total_value
  214. self.total_value_blinds = total_value_blinds
  215. self.proposer_limit = proposer_limit
  216. self.quorum = quorum
  217. self.approval_ratio = approval_ratio
  218. self.gov_token_id = gov_token_id
  219. self.dao_public_key = dao_public_key
  220. self.dao_bulla_blind = dao_bulla_blind
  221. self.token_blind = token_blind
  222. self.enc_bulla_blind = enc_bulla_blind
  223. self.proposal_dest = proposal_dest
  224. self.proposal_amount = proposal_amount
  225. self.proposal_serial = proposal_serial
  226. self.proposal_token_id = proposal_token_id
  227. self.proposal_blind = proposal_blind
  228. self.all_dao_bullas = all_dao_bullas
  229. self.ec = ec
  230. def get_revealed(self):
  231. revealed = ClassNamespace()
  232. # Value commit
  233. revealed.value_commit = crypto.pedersen_encrypt(
  234. self.total_value, self.total_value_blinds, self.ec
  235. )
  236. # Token ID
  237. revealed.token_commit = crypto.pedersen_encrypt(
  238. self.gov_token_id, self.token_blind, self.ec
  239. )
  240. # encrypted DAO bulla
  241. bulla = crypto.ff_hash(
  242. self.ec.p,
  243. self.proposer_limit,
  244. self.quorum,
  245. self.approval_ratio,
  246. self.gov_token_id,
  247. self.dao_public_key[0],
  248. self.dao_public_key[1],
  249. self.dao_bulla_blind
  250. )
  251. revealed.enc_bulla = crypto.ff_hash(self.ec.p, bulla, self.enc_bulla_blind)
  252. # encrypted proposal
  253. revealed.proposal_bulla = crypto.ff_hash(
  254. self.ec.p,
  255. self.proposal_dest[0],
  256. self.proposal_dest[1],
  257. self.proposal_amount,
  258. self.proposal_serial,
  259. self.proposal_token_id,
  260. self.proposal_blind,
  261. bulla
  262. )
  263. # The merkle root
  264. revealed.all_dao_bullas = self.all_dao_bullas
  265. return revealed
  266. def verify(self, public):
  267. revealed = self.get_revealed()
  268. bulla = crypto.ff_hash(
  269. self.ec.p,
  270. self.proposer_limit,
  271. self.quorum,
  272. self.approval_ratio,
  273. self.gov_token_id,
  274. self.dao_public_key[0],
  275. self.dao_public_key[1],
  276. self.dao_bulla_blind
  277. )
  278. # Merkle root check
  279. if bulla not in self.all_dao_bullas:
  280. return False
  281. assert self.proposal_amount > 0
  282. #
  283. # total_value >= proposer_limit
  284. #
  285. if not self.total_value >= self.proposer_limit:
  286. return False
  287. return all([
  288. revealed.value_commit == public.value_commit,
  289. revealed.token_commit == public.token_commit,
  290. revealed.enc_bulla == public.enc_bulla,
  291. revealed.proposal_bulla == public.proposal_bulla,
  292. revealed.all_dao_bullas == public.all_dao_bullas
  293. ])
  294. class VoteTxBuilder:
  295. def __init__(self, ec):
  296. self.inputs = []
  297. self.vote_option = None
  298. self.ec = ec
  299. def add_input(self, all_coins, secret, note):
  300. input = ClassNamespace()
  301. input.all_coins = all_coins
  302. input.secret = secret
  303. input.note = note
  304. self.inputs.append(input)
  305. def set_vote_option(self, vote_option):
  306. assert vote_option == 0 or vote_option == 1
  307. self.vote_option = vote_option
  308. def build(self):
  309. tx = VoteTx(self.ec)
  310. token_blind = self.ec.random_scalar()
  311. assert self.vote_option is not None
  312. vote_option_blind = self.ec.random_base()
  313. total_value, total_blind = 0, 0
  314. signature_secrets = []
  315. for input in self.inputs:
  316. value_blind = self.ec.random_scalar()
  317. total_blind = (total_blind + value_blind) % self.ec.order
  318. total_value = (total_value + input.note.value) % self.ec.order
  319. signature_secret = self.ec.random_scalar()
  320. signature_secrets.append(signature_secret)
  321. tx_input = ClassNamespace()
  322. tx_input.__name__ = "TransactionInput"
  323. tx_input.burn_proof = VoteBurnProof(
  324. input.note.value, input.note.token_id, value_blind,
  325. token_blind, input.note.serial, input.note.coin_blind,
  326. input.secret, input.note.spend_hook, input.note.user_data,
  327. input.all_coins, signature_secret,
  328. self.ec)
  329. tx_input.revealed = tx_input.burn_proof.get_revealed()
  330. tx.inputs.append(tx_input)
  331. assert len(self.inputs) > 0
  332. token_id = self.inputs[0].note.token_id
  333. vote_blind = self.ec.random_scalar()
  334. # This whole tx is like just burning tokens
  335. # except we produce an output commitment to the total value in
  336. tx.vote = ClassNamespace()
  337. tx.vote.__name__ = "Vote"
  338. tx.vote.proof = VoteProof(total_value, token_id,
  339. total_blind, token_blind, vote_blind,
  340. self.vote_option, vote_option_blind,
  341. self.ec)
  342. tx.vote.revealed = tx.vote.proof.get_revealed()
  343. # We can use Shamir's Secret Sharing to unlock this at the end
  344. # of the voting, or even with a time delay to avoid timing attacks
  345. tx.note = ClassNamespace()
  346. tx.note.__name__ = "EncryptedNoteForDaoMembers"
  347. tx.note.value = total_value
  348. tx.note.token_id = token_id
  349. tx.note.vote_option = self.vote_option
  350. tx.note.value_blind = total_blind
  351. tx.note.token_blind = token_blind
  352. tx.note.vote_blind = vote_blind
  353. tx.note.vote_option_blind = vote_option_blind
  354. unsigned_tx_data = tx.partial_encode()
  355. for (input, signature_secret) in zip(tx.inputs, signature_secrets):
  356. signature = crypto.sign(unsigned_tx_data, signature_secret, self.ec)
  357. input.signature = signature
  358. return tx
  359. class VoteBurnProof:
  360. def __init__(self, value, token_id,
  361. value_blind, token_blind, serial,
  362. coin_blind, secret, spend_hook, user_data,
  363. all_coins, signature_secret, ec):
  364. self.value = value
  365. self.token_id = token_id
  366. self.value_blind = value_blind
  367. self.token_blind = token_blind
  368. self.serial = serial
  369. self.coin_blind = coin_blind
  370. self.secret = secret
  371. self.spend_hook = spend_hook
  372. self.user_data = user_data
  373. self.all_coins = all_coins
  374. self.signature_secret = signature_secret
  375. self.ec = ec
  376. def get_revealed(self):
  377. revealed = ClassNamespace()
  378. revealed.nullifier = crypto.ff_hash(self.ec.p, self.secret, self.serial)
  379. revealed.value_commit = crypto.pedersen_encrypt(
  380. self.value, self.value_blind, self.ec
  381. )
  382. revealed.token_commit = crypto.pedersen_encrypt(
  383. self.token_id, self.token_blind, self.ec
  384. )
  385. # is_valid_merkle_root()
  386. revealed.all_coins = self.all_coins
  387. revealed.signature_public = self.ec.multiply(self.signature_secret,
  388. self.ec.G)
  389. return revealed
  390. def verify(self, public):
  391. revealed = self.get_revealed()
  392. public_key = self.ec.multiply(self.secret, self.ec.G)
  393. coin = crypto.ff_hash(
  394. self.ec.p,
  395. public_key[0],
  396. public_key[1],
  397. self.value,
  398. self.token_id,
  399. self.serial,
  400. self.coin_blind,
  401. self.spend_hook,
  402. self.user_data,
  403. )
  404. # Merkle root check
  405. if coin not in self.all_coins:
  406. return False
  407. return all([
  408. revealed.nullifier == public.nullifier,
  409. revealed.value_commit == public.value_commit,
  410. revealed.token_commit == public.token_commit,
  411. revealed.all_coins == public.all_coins,
  412. revealed.signature_public == public.signature_public,
  413. ])
  414. class VoteProof:
  415. def __init__(self, value, token_id,
  416. value_blind, token_blind, vote_blind,
  417. vote_option, vote_option_blind, ec):
  418. self.value = value
  419. self.token_id = token_id
  420. self.value_blind = value_blind
  421. self.token_blind = token_blind
  422. self.vote_blind = vote_blind
  423. self.vote_option = vote_option
  424. self.vote_option_blind = vote_option_blind
  425. self.ec = ec
  426. def get_revealed(self):
  427. revealed = ClassNamespace()
  428. # Multiply the point by vote_option
  429. revealed.value_commit = crypto.pedersen_encrypt(
  430. self.value, self.value_blind, self.ec
  431. )
  432. revealed.vote_commit = crypto.pedersen_encrypt(
  433. self.vote_option * self.value, self.vote_blind, self.ec
  434. )
  435. revealed.token_commit = crypto.pedersen_encrypt(
  436. self.token_id, self.token_blind, self.ec
  437. )
  438. revealed.vote_option_commit = crypto.ff_hash(
  439. self.ec.p, self.vote_option, self.vote_option_blind
  440. )
  441. return revealed
  442. def verify(self, public):
  443. revealed = self.get_revealed()
  444. # vote option should be 0 or 1
  445. if ((self.vote_option - 0) * (self.vote_option - 1)) % self.ec.p != 0:
  446. return False
  447. return all([
  448. revealed.value_commit == public.value_commit,
  449. revealed.token_commit == public.token_commit,
  450. revealed.vote_option_commit == public.vote_option_commit
  451. ])
  452. class VoteTx:
  453. def __init__(self, ec):
  454. self.inputs = []
  455. self.vote = None
  456. self.ec = ec
  457. def partial_encode(self):
  458. # There is no cake
  459. return b"hello"
  460. def verify(self):
  461. if not self._check_value_commits():
  462. return False, "value commits do not match"
  463. if not self._check_proofs():
  464. return False, "proofs failed to verify"
  465. if not self._verify_token_commitments():
  466. return False, "token ID mismatch"
  467. return True, None
  468. def _check_value_commits(self):
  469. valcom_total = (0, 1, 0)
  470. for input in self.inputs:
  471. value_commit = input.revealed.value_commit
  472. valcom_total = self.ec.add(valcom_total, value_commit)
  473. return valcom_total == self.vote.revealed.value_commit
  474. def _check_proofs(self):
  475. for input in self.inputs:
  476. if not input.burn_proof.verify(input.revealed):
  477. return False
  478. if not self.vote.proof.verify(self.vote.revealed):
  479. return False
  480. return True
  481. def _verify_token_commitments(self):
  482. token_commit_value = self.vote.revealed.token_commit
  483. for input in self.inputs:
  484. if input.revealed.token_commit != token_commit_value:
  485. return False
  486. return True
  487. class DaoBuilder:
  488. def __init__(self, proposer_limit, quorum, approval_ratio,
  489. gov_token_id, dao_public_key, dao_bulla_blind, ec):
  490. self.proposer_limit = proposer_limit
  491. self.quorum = quorum
  492. self.approval_ratio = approval_ratio
  493. self.gov_token_id = gov_token_id
  494. self.dao_public_key = dao_public_key
  495. self.dao_bulla_blind = dao_bulla_blind
  496. self.ec = ec
  497. def build(self):
  498. mint_proof = DaoMintProof(
  499. self.proposer_limit,
  500. self.quorum,
  501. self.approval_ratio,
  502. self.gov_token_id,
  503. self.dao_public_key,
  504. self.dao_bulla_blind,
  505. self.ec
  506. )
  507. revealed = mint_proof.get_revealed()
  508. dao = Dao(revealed, mint_proof, self.ec)
  509. return dao
  510. class Dao:
  511. def __init__(self, revealed, mint_proof, ec):
  512. self.revealed = revealed
  513. self.mint_proof = mint_proof
  514. self.ec = ec
  515. def verify(self):
  516. if not self.mint_proof.verify(self.revealed):
  517. return False, "mint proof failed to verify"
  518. return True, None
  519. # class DaoExec .etc
  520. class DaoMintProof:
  521. def __init__(self, proposer_limit, quorum, approval_ratio,
  522. gov_token_id, dao_public_key, dao_bulla_blind, ec):
  523. self.proposer_limit = proposer_limit
  524. self.quorum = quorum
  525. self.approval_ratio = approval_ratio
  526. self.gov_token_id = gov_token_id
  527. self.dao_public_key = dao_public_key
  528. self.dao_bulla_blind = dao_bulla_blind
  529. self.ec = ec
  530. def get_revealed(self):
  531. revealed = ClassNamespace()
  532. revealed.bulla = crypto.ff_hash(
  533. self.ec.p,
  534. self.proposer_limit,
  535. self.quorum,
  536. self.approval_ratio,
  537. self.gov_token_id,
  538. self.dao_public_key[0],
  539. self.dao_public_key[1],
  540. self.dao_bulla_blind
  541. )
  542. return revealed
  543. def verify(self, public):
  544. revealed = self.get_revealed()
  545. return revealed.bulla == public.bulla
  546. # Shared between DaoMint and DaoExec
  547. class DaoState:
  548. def __init__(self):
  549. self.dao_bullas = set()
  550. self.proposals = set()
  551. # Closed proposals
  552. self.proposal_nullifiers = set()
  553. def is_valid_merkle(self, all_dao_bullas):
  554. return all_dao_bullas.issubset(self.dao_bullas)
  555. def is_valid_merkle_proposals(self, all_proposal_bullas):
  556. return all_proposal_bullas.issubset(self.proposals)
  557. def proposal_nullifier_exists(self, nullifier):
  558. return nullifier in self.proposal_nullifiers
  559. def apply_proposal_tx(self, update):
  560. self.proposals.add(update.proposal)
  561. def apply_exec_tx(self, update):
  562. self.proposal_nullifiers.add(update.proposal_nullifier)
  563. def apply(self, update):
  564. self.dao_bullas.add(update.bulla)
  565. # contract interface functions
  566. def dao_state_transition(state, tx):
  567. is_verify, reason = tx.verify()
  568. if not is_verify:
  569. print(f"dao tx verify failed: {reason}", file=sys.stderr)
  570. return None
  571. update = ClassNamespace()
  572. update.bulla = tx.revealed.bulla
  573. return update
  574. ###### DAO EXEC
  575. class DaoExecBuilder:
  576. def __init__(self,
  577. proposal,
  578. all_proposals,
  579. dao,
  580. win_votes,
  581. total_votes,
  582. total_value_blinds,
  583. total_vote_blinds,
  584. pay_tx_serial_0,
  585. pay_tx_serial_1,
  586. pay_tx_coin_blind_0,
  587. pay_tx_coin_blind_1,
  588. pay_tx_input_value,
  589. pay_tx_input_blinds,
  590. ec
  591. ):
  592. self.proposal = proposal
  593. self.all_proposals = all_proposals
  594. self.dao = dao
  595. self.win_votes = win_votes
  596. self.total_votes = total_votes
  597. self.total_value_blinds = total_value_blinds
  598. self.total_vote_blinds = total_vote_blinds
  599. self.pay_tx_serial_0 = pay_tx_serial_0
  600. self.pay_tx_serial_1 = pay_tx_serial_1
  601. self.pay_tx_coin_blind_0 = pay_tx_coin_blind_0
  602. self.pay_tx_coin_blind_1 = pay_tx_coin_blind_1
  603. self.pay_tx_input_value = pay_tx_input_value
  604. self.pay_tx_input_blinds = pay_tx_input_blinds
  605. self.ec = ec
  606. def build(self):
  607. tx = DaoExecTx()
  608. tx.proof = DaoExecProof(
  609. self.proposal,
  610. self.all_proposals,
  611. self.dao,
  612. self.win_votes,
  613. self.total_votes,
  614. self.total_value_blinds,
  615. self.total_vote_blinds,
  616. self.pay_tx_serial_0,
  617. self.pay_tx_serial_1,
  618. self.pay_tx_coin_blind_0,
  619. self.pay_tx_coin_blind_1,
  620. self.pay_tx_input_value,
  621. self.pay_tx_input_blinds,
  622. self.ec
  623. )
  624. tx.revealed = tx.proof.get_revealed()
  625. return tx
  626. class DaoExecTx:
  627. def verify(self):
  628. if not self._check_proofs():
  629. return False, "proofs failed to verify"
  630. return True, None
  631. def _check_proofs(self):
  632. if not self.proof.verify(self.revealed):
  633. return False
  634. return True
  635. class DaoExecProof:
  636. def __init__(self,
  637. proposal,
  638. all_proposals,
  639. dao,
  640. win_votes,
  641. total_votes,
  642. total_value_blinds,
  643. total_vote_blinds,
  644. pay_tx_serial_0,
  645. pay_tx_serial_1,
  646. pay_tx_coin_blind_0,
  647. pay_tx_coin_blind_1,
  648. pay_tx_input_value,
  649. pay_tx_input_blinds,
  650. ec
  651. ):
  652. self.proposal = proposal
  653. self.all_proposals = all_proposals
  654. self.dao = dao
  655. self.win_votes = win_votes
  656. self.total_votes = total_votes
  657. self.total_value_blinds = total_value_blinds
  658. self.total_vote_blinds = total_vote_blinds
  659. self.pay_tx_serial_0 = pay_tx_serial_0
  660. self.pay_tx_serial_1 = pay_tx_serial_1
  661. self.pay_tx_coin_blind_0 = pay_tx_coin_blind_0
  662. self.pay_tx_coin_blind_1 = pay_tx_coin_blind_1
  663. self.pay_tx_input_value = pay_tx_input_value
  664. self.pay_tx_input_blinds = pay_tx_input_blinds
  665. self.ec = ec
  666. def get_revealed(self):
  667. revealed = ClassNamespace()
  668. # Corresponds to proposals merkle root
  669. revealed.all_proposals = self.all_proposals
  670. dao_bulla = crypto.ff_hash(
  671. self.ec.p,
  672. self.dao.proposer_limit,
  673. self.dao.quorum,
  674. self.dao.approval_ratio,
  675. self.dao.gov_token_id,
  676. self.dao.public_key[0],
  677. self.dao.public_key[1],
  678. self.dao.bulla_blind
  679. )
  680. proposal_bulla = crypto.ff_hash(
  681. self.ec.p,
  682. self.proposal.dest[0],
  683. self.proposal.dest[1],
  684. self.proposal.amount,
  685. self.proposal.serial,
  686. self.proposal.token_id,
  687. self.proposal.blind,
  688. dao_bulla
  689. )
  690. revealed.proposal_nullifier = crypto.ff_hash(
  691. self.ec.p, self.proposal.serial)
  692. revealed.coin_0 = crypto.ff_hash(
  693. self.ec.p,
  694. self.proposal.dest[0],
  695. self.proposal.dest[1],
  696. self.proposal.amount,
  697. self.proposal.token_id,
  698. self.pay_tx_serial_0,
  699. self.pay_tx_coin_blind_0,
  700. b"0x0000",
  701. b"0x0000"
  702. )
  703. change_amount = self.pay_tx_input_value - self.proposal.amount
  704. assert change_amount > 0
  705. # Need the same DAO public key
  706. # Need the input amount for pay_tx for treasury
  707. # Need user_data blind
  708. revealed.coin_1 = crypto.ff_hash(
  709. self.ec.p,
  710. self.dao.public_key[0],
  711. self.dao.public_key[1],
  712. change_amount,
  713. self.proposal.token_id,
  714. self.pay_tx_serial_1,
  715. self.pay_tx_coin_blind_1,
  716. b"0xdao_ruleset",
  717. dao_bulla
  718. )
  719. # Money that went into the pay tx
  720. revealed.inputs_value_commit = crypto.pedersen_encrypt(
  721. self.pay_tx_input_value, self.pay_tx_input_blinds, self.ec)
  722. revealed.total_value_commit = crypto.pedersen_encrypt(
  723. self.total_votes, self.total_value_blinds, self.ec)
  724. revealed.total_vote_commit = crypto.pedersen_encrypt(
  725. self.win_votes, self.total_vote_blinds, self.ec)
  726. return revealed
  727. def verify(self, public):
  728. revealed = self.get_revealed()
  729. # Check proposal exists
  730. dao_bulla = crypto.ff_hash(
  731. self.ec.p,
  732. self.dao.proposer_limit,
  733. self.dao.quorum,
  734. self.dao.approval_ratio,
  735. self.dao.gov_token_id,
  736. self.dao.public_key[0],
  737. self.dao.public_key[1],
  738. self.dao.bulla_blind
  739. )
  740. proposal_bulla = crypto.ff_hash(
  741. self.ec.p,
  742. self.proposal.dest[0],
  743. self.proposal.dest[1],
  744. self.proposal.amount,
  745. self.proposal.serial,
  746. self.proposal.token_id,
  747. self.proposal.blind,
  748. dao_bulla
  749. )
  750. # This being true also implies the DAO is valid
  751. assert proposal_bulla in self.all_proposals
  752. assert self.total_votes >= self.dao.quorum
  753. assert self.win_votes >= self.dao.approval_ratio
  754. return all([
  755. revealed.all_proposals == public.all_proposals,
  756. revealed.proposal_nullifier == public.proposal_nullifier,
  757. revealed.coin_0 == public.coin_0,
  758. revealed.coin_1 == public.coin_1,
  759. revealed.inputs_value_commit == public.inputs_value_commit,
  760. revealed.total_value_commit == public.total_value_commit,
  761. revealed.total_vote_commit == public.total_vote_commit,
  762. ])
  763. def dao_exec_state_transition(state, tx, pay_tx, ec):
  764. is_verify, reason = tx.verify()
  765. if not is_verify:
  766. print(f"dao exec tx verify failed: {reason}", file=sys.stderr)
  767. return None
  768. if not state.is_valid_merkle_proposals(tx.revealed.all_proposals):
  769. print(f"invalid merkle root proposals", file=sys.stderr)
  770. return None
  771. nullifier = tx.revealed.proposal_nullifier
  772. if state.proposal_nullifier_exists(nullifier):
  773. print(f"duplicate nullifier found", file=sys.stderr)
  774. return None
  775. # Check the structure of the payment tx is correct
  776. if len(pay_tx.outputs) != 2:
  777. print(f"only 2 outputs allowed", file=sys.stderr)
  778. return None
  779. if tx.revealed.coin_0 != pay_tx.outputs[0].revealed.coin:
  780. print(f"coin0 incorrectly formed", file=sys.stderr)
  781. return None
  782. inputs_value_commit = (0, 1, 0)
  783. for input in pay_tx.inputs:
  784. value_commit = input.revealed.value_commit
  785. inputs_value_commit = ec.add(inputs_value_commit, value_commit)
  786. if inputs_value_commit != tx.revealed.inputs_value_commit:
  787. print(f"value commitment for inputs doesn't match", file=sys.stderr)
  788. return None
  789. if tx.revealed.coin_1 != pay_tx.outputs[1].revealed.coin:
  790. print(f"coin1 incorrectly formed", file=sys.stderr)
  791. return None
  792. update = ClassNamespace()
  793. update.proposal_nullifier = tx.revealed.proposal_nullifier
  794. return update
  795. # contract interface functions
  796. def proposal_state_transition(dao_state, gov_state, tx):
  797. is_verify, reason = tx.verify()
  798. if not is_verify:
  799. print(f"dao tx verify failed: {reason}", file=sys.stderr)
  800. return None
  801. if not dao_state.is_valid_merkle(tx.dao.revealed.all_dao_bullas):
  802. print(f"invalid merkle root dao", file=sys.stderr)
  803. return None
  804. for input in tx.inputs:
  805. if not gov_state.is_valid_merkle(input.revealed.all_coins):
  806. print(f"invalid merkle root", file=sys.stderr)
  807. return None
  808. update = ClassNamespace()
  809. update.proposal = tx.dao.revealed.proposal_bulla
  810. return update
  811. class VoteState:
  812. def __init__(self):
  813. self.votes = set()
  814. self.nullifiers = set()
  815. def nullifier_exists(self, nullifier):
  816. return nullifier in self.nullifiers
  817. def apply(self, update):
  818. self.nullifiers = self.nullifiers.union(update.nullifiers)
  819. self.votes.add(update.vote)
  820. def vote_state_transition(vote_state, gov_state, tx):
  821. for input in tx.inputs:
  822. if not gov_state.is_valid_merkle(input.revealed.all_coins):
  823. print(f"invalid merkle root", file=sys.stderr)
  824. return None
  825. nullifier = input.revealed.nullifier
  826. if gov_state.nullifier_exists(nullifier):
  827. print(f"duplicate nullifier found", file=sys.stderr)
  828. return None
  829. if vote_state.nullifier_exists(nullifier):
  830. print(f"duplicate nullifier found (already voted)", file=sys.stderr)
  831. return None
  832. is_verify, reason = tx.verify()
  833. if not is_verify:
  834. print(f"dao tx verify failed: {reason}", file=sys.stderr)
  835. return None
  836. update = ClassNamespace()
  837. update.nullifiers = [input.revealed.nullifier for input in tx.inputs]
  838. update.vote = tx.vote.revealed.value_commit
  839. return update
  840. def main(argv):
  841. ec = crypto.pallas_curve()
  842. money_state = MoneyState()
  843. gov_state = MoneyState()
  844. dao_state = DaoState()
  845. # Money parameters
  846. money_initial_supply = 21000
  847. money_token_id = 110
  848. # Governance token parameters
  849. gov_initial_supply = 10000
  850. gov_token_id = 4
  851. # DAO parameters
  852. dao_proposer_limit = 110
  853. dao_quorum = 110
  854. dao_approval_ratio = 2
  855. ################################################
  856. # Create the DAO bulla
  857. ################################################
  858. # Setup the DAO
  859. dao_shared_secret = ec.random_scalar()
  860. dao_public_key = ec.multiply(dao_shared_secret, ec.G)
  861. dao_bulla_blind = ec.random_base()
  862. builder = DaoBuilder(
  863. dao_proposer_limit,
  864. dao_quorum,
  865. dao_approval_ratio,
  866. gov_token_id,
  867. dao_public_key,
  868. dao_bulla_blind,
  869. ec
  870. )
  871. tx = builder.build()
  872. # Each deployment of a contract has a unique state
  873. # associated with it.
  874. if (update := dao_state_transition(dao_state, tx)) is None:
  875. return -1
  876. dao_state.apply(update)
  877. dao_bulla = tx.revealed.bulla
  878. ################################################
  879. # Mint the initial supply of treasury token
  880. # and send it all to the DAO directly
  881. ################################################
  882. # Only used for this tx. Discarded after
  883. signature_secret = ec.random_scalar()
  884. builder = money.SendPaymentTxBuilder(ec)
  885. builder.add_clear_input(money_initial_supply, money_token_id,
  886. signature_secret)
  887. # Address of deployed contract in our example is 0xdao_ruleset
  888. spend_hook = b"0xdao_ruleset"
  889. # This can be a simple hash of the items passed into the ZK proof
  890. # up to corresponding linked ZK proof to interpret however they need.
  891. # In out case, it's the bulla for the DAO
  892. user_data = dao_bulla
  893. builder.add_output(money_initial_supply, money_token_id, dao_public_key,
  894. spend_hook, user_data)
  895. tx = builder.build()
  896. # This state_transition function is the ruleset for anon payments
  897. if (update := money_state_transition(money_state, tx)) is None:
  898. return -1
  899. money_state.apply(update)
  900. # NOTE: maybe we want to add additional zk proof here that the tx
  901. # sending money to the DAO was constructed correctly.
  902. # For example that the user_data is set correctly
  903. # payment state transition in coin specifies dependency
  904. # the tx exists and ruleset is applied
  905. assert len(tx.outputs) > 0
  906. coin_note = tx.outputs[0].enc_note
  907. coin = crypto.ff_hash(
  908. ec.p,
  909. dao_public_key[0],
  910. dao_public_key[1],
  911. coin_note.value,
  912. coin_note.token_id,
  913. coin_note.serial,
  914. coin_note.coin_blind,
  915. spend_hook,
  916. user_data
  917. )
  918. assert coin == tx.outputs[0].mint_proof.get_revealed().coin
  919. for coin, enc_note in zip(update.coins, update.enc_notes):
  920. # Try decrypt note here
  921. print(f"Received {enc_note.value} DRK")
  922. ################################################
  923. # Mint the governance token
  924. # Send it to two hodlers
  925. ################################################
  926. # Hodler 1
  927. gov_secret_1 = ec.random_scalar()
  928. gov_public_1 = ec.multiply(gov_secret_1, ec.G)
  929. # Hodler 2
  930. gov_secret_2 = ec.random_scalar()
  931. gov_public_2 = ec.multiply(gov_secret_2, ec.G)
  932. # Hodler 3: the tiebreaker
  933. gov_secret_3 = ec.random_scalar()
  934. gov_public_3 = ec.multiply(gov_secret_3, ec.G)
  935. # Only used for this tx. Discarded after
  936. signature_secret = ec.random_scalar()
  937. builder = money.SendPaymentTxBuilder(ec)
  938. builder.add_clear_input(gov_initial_supply, gov_token_id,
  939. signature_secret)
  940. assert 2 * 5000 == gov_initial_supply
  941. builder.add_output(4000, gov_token_id, gov_public_1,
  942. b"0x0000", b"0x0000")
  943. builder.add_output(4000, gov_token_id, gov_public_2,
  944. b"0x0000", b"0x0000")
  945. builder.add_output(2000, gov_token_id, gov_public_3,
  946. b"0x0000", b"0x0000")
  947. tx = builder.build()
  948. # This state_transition function is the ruleset for anon payments
  949. if (update := money_state_transition(gov_state, tx)) is None:
  950. return -1
  951. gov_state.apply(update)
  952. # Decrypt output notes
  953. assert len(tx.outputs) == 3
  954. gov_user_1_note = tx.outputs[0].enc_note
  955. gov_user_2_note = tx.outputs[1].enc_note
  956. gov_user_3_note = tx.outputs[2].enc_note
  957. for coin, enc_note in zip(update.coins, update.enc_notes):
  958. # Try decrypt note here
  959. print(f"Received {enc_note.value} GOV")
  960. ################################################
  961. # DAO rules:
  962. # 1. gov token IDs must match on all inputs
  963. # 2. proposals must be submitted by minimum amount
  964. # - need protection so can't collude? must be a single signer??
  965. # - stellar: doesn't have to be robust for this MVP
  966. # 3. number of votes >= quorum
  967. # - just positive votes or all votes?
  968. # - stellar: no that's all votes
  969. # 4. outcome > approval_ratio
  970. # 5. structure of outputs
  971. # output 0: value and address
  972. # output 1: change address
  973. ################################################
  974. ################################################
  975. # Propose the vote
  976. # In order to make a valid vote, first the proposer must
  977. # meet a criteria for a minimum number of gov tokens
  978. ################################################
  979. user_secret = ec.random_scalar()
  980. user_public = ec.multiply(user_secret, ec.G)
  981. # There is a struct that corresponds to the configuration of this
  982. # particular vote.
  983. # For MVP, just use a single-option list of [destination, amount]
  984. # Send user 1000 DRK
  985. proposal = ClassNamespace()
  986. proposal.dest = user_public
  987. proposal.amount = 1000
  988. # Used to produce the nullifier when the vote is executed
  989. proposal.serial = ec.random_base()
  990. proposal.token_id = money_token_id
  991. proposal.blind = ec.random_base()
  992. # For vote to become valid, the proposer must prove
  993. # that they own more than proposer_limit number of gov tokens.
  994. dao = ClassNamespace()
  995. dao.proposer_limit = dao_proposer_limit
  996. dao.quorum = dao_quorum
  997. dao.approval_ratio = dao_approval_ratio
  998. dao.gov_token_id = gov_token_id
  999. dao.public_key = dao_public_key
  1000. dao.bulla_blind = dao_bulla_blind
  1001. builder = ProposerTxBuilder(proposal, dao_state.dao_bullas, ec)
  1002. witness = gov_state.all_coins
  1003. builder.add_input(witness, gov_secret_1, gov_user_1_note)
  1004. builder.set_dao(dao)
  1005. tx = builder.build()
  1006. # No state changes actually happen so ignore the update
  1007. # We just verify the tx is correct basically.
  1008. if (update := proposal_state_transition(dao_state, gov_state, tx)) is None:
  1009. return -1
  1010. dao_state.apply_proposal_tx(update)
  1011. ################################################
  1012. # Proposal is accepted!
  1013. ################################################
  1014. # Lets the voting begin
  1015. # Voters have access to the proposal and dao data
  1016. vote_state = VoteState()
  1017. # TODO: what happens if voters don't unblind their vote
  1018. # User 1: YES
  1019. builder = VoteTxBuilder(ec)
  1020. builder.add_input(witness, gov_secret_1, gov_user_1_note)
  1021. builder.set_vote_option(1)
  1022. tx1 = builder.build()
  1023. if (update := vote_state_transition(vote_state, gov_state, tx1)) is None:
  1024. return -1
  1025. vote_state.apply(update)
  1026. note_vote_1 = tx1.note
  1027. # User 2: NO
  1028. builder = VoteTxBuilder(ec)
  1029. builder.add_input(witness, gov_secret_2, gov_user_2_note)
  1030. builder.set_vote_option(0)
  1031. tx2 = builder.build()
  1032. if (update := vote_state_transition(vote_state, gov_state, tx2)) is None:
  1033. return -1
  1034. vote_state.apply(update)
  1035. note_vote_2 = tx2.note
  1036. # User 3: YES
  1037. builder = VoteTxBuilder(ec)
  1038. builder.add_input(witness, gov_secret_3, gov_user_3_note)
  1039. builder.set_vote_option(1)
  1040. tx3 = builder.build()
  1041. if (update := vote_state_transition(vote_state, gov_state, tx3)) is None:
  1042. return -1
  1043. vote_state.apply(update)
  1044. note_vote_3 = tx3.note
  1045. # State
  1046. # functions that can be called on state with params
  1047. # functions return an update
  1048. # optional encrypted values that can be read by wallets
  1049. # --> (do this outside??)
  1050. # --> penalized if fail
  1051. # apply update to state
  1052. # Every votes produces a semi-homomorphic encryption of their vote.
  1053. # Which is either yes or no
  1054. # We copy the state tree for the governance token so coins can be used
  1055. # to vote on other proposals at the same time.
  1056. # With their vote, they produce a ZK proof + nullifier
  1057. # The votes are unblinded by MPC to a selected party at the end of the
  1058. # voting period.
  1059. # (that's if we want votes to be hidden during voting)
  1060. win_votes = 0
  1061. total_votes = 0
  1062. total_vote_blinds = 0
  1063. total_value_blinds = 0
  1064. total_value_commit = (0, 1, 0)
  1065. total_vote_commit = (0, 1, 0)
  1066. for i, (note, tx) in enumerate(
  1067. zip([note_vote_1, note_vote_2, note_vote_3], [tx1, tx2, tx3])):
  1068. assert note.token_id == gov_token_id
  1069. token_commit = crypto.pedersen_encrypt(
  1070. gov_token_id, note.token_blind, ec)
  1071. assert tx.vote.revealed.token_commit == token_commit
  1072. vote_option_commit = crypto.ff_hash(
  1073. ec.p, note.vote_option, note.vote_option_blind)
  1074. assert tx.vote.revealed.vote_option_commit == vote_option_commit
  1075. value_commit = crypto.pedersen_encrypt(
  1076. note.value, note.value_blind, ec)
  1077. assert tx.vote.revealed.value_commit == value_commit
  1078. total_value_commit = ec.add(total_value_commit, value_commit)
  1079. total_value_blinds += note.value_blind
  1080. vote_commit = crypto.pedersen_encrypt(
  1081. note.vote_option * note.value, note.vote_blind, ec)
  1082. assert tx.vote.revealed.vote_commit == vote_commit
  1083. total_vote_commit = ec.add(total_vote_commit, vote_commit)
  1084. total_vote_blinds += note.vote_blind
  1085. vote_option = note.vote_option
  1086. assert vote_option == 0 or vote_option == 1
  1087. if vote_option == 1:
  1088. win_votes += note.value
  1089. total_votes += note.value
  1090. if vote_option == 1:
  1091. vote_result = "yes"
  1092. else:
  1093. vote_result = "no"
  1094. print(f"Voter {i} voted {vote_result}")
  1095. print(f"Outcome = {win_votes} / {total_votes}")
  1096. assert total_value_commit == crypto.pedersen_encrypt(
  1097. total_votes, total_value_blinds, ec)
  1098. assert total_vote_commit == crypto.pedersen_encrypt(
  1099. win_votes, total_vote_blinds, ec)
  1100. ################################################
  1101. # Execute the vote
  1102. ################################################
  1103. # Used to export user_data from this coin so it can be accessed
  1104. # by 0xdao_ruleset
  1105. user_data_blind = ec.random_base()
  1106. builder = money.SendPaymentTxBuilder(ec)
  1107. witness = money_state.all_coins
  1108. builder.add_input(witness, dao_shared_secret, coin_note, user_data_blind)
  1109. builder.add_output(1000, money_token_id, user_public,
  1110. spend_hook=b"0x0000", user_data=b"0x0000")
  1111. # Change
  1112. builder.add_output(coin_note.value - 1000, money_token_id, dao_public_key,
  1113. spend_hook, user_data)
  1114. tx = builder.build()
  1115. if (update := money_state_transition(money_state, tx)) is None:
  1116. return -1
  1117. money_state.apply(update)
  1118. # Now the spend_hook field specifies the function DaoExec
  1119. # so the tx above must also be combined with a DaoExec tx
  1120. assert len(tx.inputs) == 1
  1121. # At least one input has this field value which means the 0xdao_ruleset
  1122. # is invoked.
  1123. input = tx.inputs[0]
  1124. assert input.revealed.spend_hook == b"0xdao_ruleset"
  1125. assert (input.revealed.enc_user_data ==
  1126. crypto.ff_hash(
  1127. ec.p,
  1128. user_data,
  1129. user_data_blind
  1130. ))
  1131. # Verifier cannot see DAO bulla
  1132. # They see the enc_user_data which is also in the DAO exec contract
  1133. assert user_data == crypto.ff_hash(
  1134. ec.p,
  1135. dao_proposer_limit,
  1136. dao_quorum,
  1137. dao_approval_ratio,
  1138. gov_token_id,
  1139. dao_public_key[0],
  1140. dao_public_key[1],
  1141. dao_bulla_blind
  1142. ) # DAO bulla
  1143. pay_tx = tx
  1144. # execution proof
  1145. # 1. total votes >= quorum
  1146. # 2. win_votes / total_votes >= approval_ratio
  1147. # 3. structure of outputs
  1148. # output 0: value and address
  1149. # output 1: change address
  1150. # - check proposal exists
  1151. # - create proposal nullifier
  1152. # - verifier: check it doesn't already exist
  1153. # - check dest, amount, token_id match
  1154. # - export both output value_commits
  1155. # - export token_id commit used in send_payment tx
  1156. # - export output 0 and 1 dest
  1157. # - check all these fields match the tx
  1158. # - is linked to DAO
  1159. # - read DAO params
  1160. # - re-export as enc_user_data
  1161. # - verifier: check it matches the tx
  1162. # - total_votes >= quorum
  1163. # - verifier: check sum of vote_commits is correct
  1164. # - win_votes / total_votes >= approval_ratio
  1165. assert len(pay_tx.outputs) == 2
  1166. pay_tx_serial_0 = pay_tx.outputs[0].enc_note.serial
  1167. pay_tx_serial_1 = pay_tx.outputs[1].enc_note.serial
  1168. pay_tx_coin_blind_0 = pay_tx.outputs[0].enc_note.coin_blind
  1169. pay_tx_coin_blind_1 = pay_tx.outputs[1].enc_note.coin_blind
  1170. pay_tx_input_value = coin_note.value
  1171. pay_tx_input_blinds = sum(builder.input_blinds) % ec.order
  1172. builder = DaoExecBuilder(
  1173. proposal,
  1174. dao_state.proposals,
  1175. dao,
  1176. win_votes,
  1177. total_votes,
  1178. total_value_blinds,
  1179. total_vote_blinds,
  1180. pay_tx_serial_0,
  1181. pay_tx_serial_1,
  1182. pay_tx_coin_blind_0,
  1183. pay_tx_coin_blind_1,
  1184. pay_tx_input_value,
  1185. pay_tx_input_blinds,
  1186. ec
  1187. )
  1188. tx = builder.build()
  1189. if (update := dao_exec_state_transition(dao_state, tx, pay_tx, ec)) is None:
  1190. return -1
  1191. dao_state.apply_exec_tx(update)
  1192. # These checks are also run by the verifier
  1193. assert tx.revealed.total_value_commit == total_value_commit
  1194. assert tx.revealed.total_vote_commit == total_vote_commit
  1195. return 0
  1196. if __name__ == "__main__":
  1197. sys.exit(main(sys.argv))