main.py 46 KB

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