main.py 47 KB

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