main.py 40 KB

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