tx.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. from classnamespace import ClassNamespace
  2. from crypto import ff_hash, pedersen_encrypt, sign, verify
  3. class TransactionBuilder:
  4. def __init__(self, ec):
  5. self.clear_inputs = []
  6. self.inputs = []
  7. self.outputs = []
  8. self.ec = ec
  9. def add_clear_input(self, value, token_id, signature_secret):
  10. clear_input = ClassNamespace()
  11. clear_input.value = value
  12. clear_input.token_id = token_id
  13. clear_input.signature_secret = signature_secret
  14. self.clear_inputs.append(clear_input)
  15. def add_input(self, all_coins, secret, note):
  16. input = ClassNamespace()
  17. input.all_coins = all_coins
  18. input.secret = secret
  19. input.note = note
  20. self.inputs.append(input)
  21. def add_output(self, value, token_id, public):
  22. output = ClassNamespace()
  23. output.value = value
  24. output.token_id = token_id
  25. output.public = public
  26. self.outputs.append(output)
  27. def compute_remainder_blind(self, clear_inputs, input_blinds,
  28. output_blinds):
  29. total = 0
  30. total += sum(input.value_blind for input in clear_inputs)
  31. total += sum(input_blinds)
  32. total -= sum(output_blinds)
  33. return total % self.ec.order
  34. def build(self):
  35. tx = Transaction(self.ec)
  36. token_blind = self.ec.random_scalar()
  37. for input in self.clear_inputs:
  38. tx_clear_input = ClassNamespace()
  39. tx_clear_input.__name__ = "TransactionClearInput"
  40. tx_clear_input.value = input.value
  41. tx_clear_input.token_id = input.token_id
  42. tx_clear_input.value_blind = self.ec.random_scalar()
  43. tx_clear_input.token_blind = token_blind
  44. tx_clear_input.signature_public = self.ec.multiply(
  45. input.signature_secret, self.ec.G)
  46. tx.clear_inputs.append(tx_clear_input)
  47. input_blinds = []
  48. signature_secrets = []
  49. for input in self.inputs:
  50. # FIXME: BUG - see corresponding builder.rs file
  51. input_blinds.append(input.note.value_blind)
  52. signature_secret = self.ec.random_scalar()
  53. signature_secrets.append(signature_secret)
  54. tx_input = ClassNamespace()
  55. tx_input.__name__ = "TransactionInput"
  56. tx_input.burn_proof = BurnProof(
  57. input.note.value, input.note.token_id, input.note.value_blind,
  58. token_blind, input.note.serial, input.note.coin_blind,
  59. input.secret, input.all_coins, signature_secret, self.ec)
  60. tx_input.revealed = tx_input.burn_proof.get_revealed()
  61. tx.inputs.append(tx_input)
  62. assert self.outputs
  63. output_blinds = []
  64. for i, output in enumerate(self.outputs):
  65. if i == len(self.outputs) - 1:
  66. value_blind = self.compute_remainder_blind(
  67. tx.clear_inputs, input_blinds, output_blinds)
  68. else:
  69. value_blind = self.ec.random_scalar()
  70. output_blinds.append(value_blind)
  71. note = ClassNamespace()
  72. note.serial = self.ec.random_base()
  73. note.value = output.value
  74. note.token_id = output.token_id
  75. note.coin_blind = self.ec.random_base()
  76. note.value_blind = value_blind
  77. note.token_blind = token_blind
  78. tx_output = ClassNamespace()
  79. tx_output.__name__ = "TransactionOutput"
  80. tx_output.mint_proof = MintProof(
  81. note.value, note.token_id, note.value_blind,
  82. note.token_blind, note.serial, note.coin_blind,
  83. output.public, self.ec)
  84. tx_output.revealed = tx_output.mint_proof.get_revealed()
  85. assert tx_output.mint_proof.verify(tx_output.revealed)
  86. # Is normally encrypted
  87. tx_output.enc_note = note
  88. tx_output.enc_note.__name__ = "TransactionOutputEncryptedNote"
  89. tx.outputs.append(tx_output)
  90. unsigned_tx_data = tx.partial_encode()
  91. for (input, info) in zip(tx.clear_inputs, self.clear_inputs):
  92. secret = info.signature_secret
  93. signature = sign(unsigned_tx_data, secret, self.ec)
  94. input.signature = signature
  95. for (input, signature_secret) in zip(tx.inputs, signature_secrets):
  96. signature = sign(unsigned_tx_data, signature_secret, self.ec)
  97. input.signature = signature
  98. return tx
  99. class Transaction:
  100. def __init__(self, ec):
  101. self.clear_inputs = []
  102. self.inputs = []
  103. self.outputs = []
  104. self.ec = ec
  105. def partial_encode(self):
  106. return b"hello"
  107. def verify(self):
  108. if not self._check_value_commits():
  109. return False, "value commits do not match"
  110. if not self._check_proofs():
  111. return False, "proofs failed to verify"
  112. if not self._verify_token_commitments():
  113. return False, "token ID mismatch"
  114. unsigned_tx_data = self.partial_encode()
  115. for input in self.clear_inputs:
  116. public = input.signature_public
  117. if not verify(unsigned_tx_data, input.signature, public, self.ec):
  118. return False
  119. for input in self.inputs:
  120. public = input.revealed.signature_public
  121. if not verify(unsigned_tx_data, input.signature, public, self.ec):
  122. return False
  123. return True, None
  124. def _check_value_commits(self):
  125. valcom_total = (0, 1, 0)
  126. for input in self.clear_inputs:
  127. value_commit = pedersen_encrypt(input.value, input.value_blind,
  128. self.ec)
  129. valcom_total = self.ec.add(valcom_total, value_commit)
  130. for input in self.inputs:
  131. value_commit = input.revealed.value_commit
  132. valcom_total = self.ec.add(valcom_total, value_commit)
  133. for output in self.outputs:
  134. v = output.revealed.value_commit
  135. value_commit = (v[0], -v[1], v[2])
  136. valcom_total = self.ec.add(valcom_total, value_commit)
  137. return valcom_total == (0, 1, 0)
  138. def _check_proofs(self):
  139. for input in self.inputs:
  140. if not input.burn_proof.verify(input.revealed):
  141. return False
  142. for output in self.outputs:
  143. if not output.mint_proof.verify(output.revealed):
  144. return False
  145. return True
  146. def _verify_token_commitments(self):
  147. assert len(self.outputs) > 0
  148. token_commit_value = self.outputs[0].revealed.token_commit
  149. for input in self.clear_inputs:
  150. token_commit = pedersen_encrypt(input.token_id, input.token_blind,
  151. self.ec)
  152. if token_commit != token_commit_value:
  153. return False
  154. for input in self.inputs:
  155. if input.revealed.token_commit != token_commit_value:
  156. return False
  157. for output in self.outputs:
  158. if output.revealed.token_commit != token_commit_value:
  159. return False
  160. return True
  161. class BurnProof:
  162. def __init__(self, value, token_id, value_blind, token_blind, serial,
  163. coin_blind, secret, all_coins, signature_secret, ec):
  164. self.value = value
  165. self.token_id = token_id
  166. self.value_blind = value_blind
  167. self.token_blind = token_blind
  168. self.serial = serial
  169. self.coin_blind = coin_blind
  170. self.secret = secret
  171. self.all_coins = all_coins
  172. self.signature_secret = signature_secret
  173. self.ec = ec
  174. def get_revealed(self):
  175. revealed = ClassNamespace()
  176. revealed.nullifier = ff_hash(self.secret, self.serial)
  177. revealed.value_commit = pedersen_encrypt(
  178. self.value, self.value_blind, self.ec
  179. )
  180. revealed.token_commit = pedersen_encrypt(
  181. self.token_id, self.token_blind, self.ec
  182. )
  183. revealed.all_coins = self.all_coins
  184. revealed.signature_public = self.ec.multiply(self.signature_secret,
  185. self.ec.G)
  186. return revealed
  187. def verify(self, public):
  188. revealed = self.get_revealed()
  189. public_key = self.ec.multiply(self.secret, self.ec.G)
  190. coin = ff_hash(
  191. self.ec.p,
  192. public_key[0],
  193. public_key[1],
  194. self.value,
  195. self.token_id,
  196. self.serial,
  197. self.coin_blind
  198. )
  199. # Merkle root check
  200. if coin not in self.all_coins:
  201. return False
  202. return all([
  203. revealed.nullifier == public.nullifier,
  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 MintProof:
  210. def __init__(self, value, token_id, value_blind, token_blind, serial,
  211. coin_blind, public, ec):
  212. self.value = value
  213. self.token_id = token_id
  214. self.value_blind = value_blind
  215. self.token_blind = token_blind
  216. self.serial = serial
  217. self.coin_blind = coin_blind
  218. self.public = public
  219. self.ec = ec
  220. def get_revealed(self):
  221. revealed = ClassNamespace()
  222. revealed.coin = ff_hash(
  223. self.ec.p,
  224. self.public[0],
  225. self.public[1],
  226. self.value,
  227. self.token_id,
  228. self.serial,
  229. self.coin_blind
  230. )
  231. revealed.value_commit = pedersen_encrypt(
  232. self.value, self.value_blind, self.ec
  233. )
  234. revealed.token_commit = pedersen_encrypt(
  235. self.token_id, self.token_blind, self.ec
  236. )
  237. return revealed
  238. def verify(self, public):
  239. revealed = self.get_revealed()
  240. return all([
  241. revealed.coin == public.coin,
  242. revealed.value_commit == public.value_commit,
  243. revealed.token_commit == public.token_commit
  244. ])