Zeth - Zerocash on Ethereum  0.8
Reference implementation of the Zeth protocol by Clearmatics
test_erc_token_mixing.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 # Copyright (c) 2015-2022 Clearmatics Technologies Ltd
4 #
5 # SPDX-License-Identifier: LGPL-3.0+
6 
8 import zeth.core.utils
9 from zeth.core import constants
10 from zeth.core.mimc import get_tree_hash_for_pairing
11 from zeth.core.prover_client import ProverClient
12 from zeth.core.zeth_address import ZethAddressPriv
13 from zeth.core.mixer_client import MixOutputEvents, MixerClient
14 from zeth.core.wallet import Wallet, ZethNoteDescription
15 from zeth.core.utils import EtherValue
16 from test_commands import mock
17 from test_commands import scenario
18 from test_commands.deploy_test_token import deploy_token, mint_token
19 from os.path import join, exists
20 import shutil
21 from web3 import Web3 # type: ignore
22 from typing import Dict, List, Any
23 
24 
26  token_instance: Any,
27  bob: str,
28  alice: str,
29  charlie: str,
30  mixer: str) -> None:
31  print("BALANCES:")
32  print(f" Alice : {token_instance.functions.balanceOf(alice).call()}")
33  print(f" Bob : {token_instance.functions.balanceOf(bob).call()}")
34  print(f" Charlie : {token_instance.functions.balanceOf(charlie).call()}")
35  print(f" Mixer : {token_instance.functions.balanceOf(mixer).call()}")
36 
37 
38 def approve(
39  token_instance: Any,
40  owner_address: str,
41  spender_address: str,
42  token_amount: int) -> str:
43  return token_instance.functions.approve(
44  spender_address,
45  Web3.toWei(token_amount, 'ether')).transact({'from': owner_address})
46 
47 
49  token_instance: Any,
50  owner_address: str,
51  spender_address: str) -> str:
52  return token_instance.functions.allowance(owner_address, spender_address) \
53  .call()
54 
55 
56 def main() -> None:
57 
58  zksnark_name = zeth.core.utils.parse_zksnark_arg()
59  zksnark = zeth.core.zksnark.get_zksnark_provider(zksnark_name)
60  web3, eth = mock.open_test_web3()
61 
62  # Ethereum addresses
63  deployer_eth_address = eth.accounts[0]
64  bob_eth_address = eth.accounts[1]
65  alice_eth_address = eth.accounts[2]
66  charlie_eth_address = eth.accounts[3]
67  # Zeth addresses
68  keystore = mock.init_test_keystore()
69 
70  # Deploy the token contract
71  token_instance = deploy_token(web3, deployer_eth_address, None, 4000000)
72 
73  # ProverClient
74  prover_client = ProverClient(mock.TEST_PROVER_SERVER_ENDPOINT)
75  prover_config = prover_client.get_configuration()
76  pp = prover_config.pairing_parameters
77  assert prover_client.get_configuration().zksnark_name == zksnark_name
78 
79  # Deploy Zeth contracts
80  tree_depth = constants.ZETH_MERKLE_TREE_DEPTH
81  zeth_client, _contract_desc = MixerClient.deploy(
82  web3,
83  prover_client,
84  deployer_eth_address,
85  None,
86  token_instance.address,
87  None)
88  tree_hash = get_tree_hash_for_pairing(pp.name)
90  tree_depth, tree_hash)
91  mixer_instance = zeth_client.mixer_instance
92 
93  # Keys and wallets
94  def _mk_wallet(name: str, sk: ZethAddressPriv) -> Wallet:
95  wallet_dir = join(mock.TEST_NOTE_DIR, name + "-erc")
96  if exists(wallet_dir):
97  # Note: symlink-attack resistance
98  # https://docs.python.org/3/library/shutil.html#shutil.rmtree.avoids_symlink_attacks
99  shutil.rmtree(wallet_dir)
100  return Wallet(mixer_instance, name, wallet_dir, sk, tree_hash)
101  sk_alice = keystore["Alice"].addr_sk
102  sk_bob = keystore["Bob"].addr_sk
103  sk_charlie = keystore["Charlie"].addr_sk
104  alice_wallet = _mk_wallet('alice', sk_alice)
105  bob_wallet = _mk_wallet('bob', sk_bob)
106  charlie_wallet = _mk_wallet('charlie', sk_charlie)
107  block_num = 1
108 
109  # Universal update function
110  def _receive_notes(
111  out_ev: List[MixOutputEvents]) \
112  -> Dict[str, List[ZethNoteDescription]]:
113  nonlocal block_num
114  notes = {
115  'alice': alice_wallet.receive_notes(out_ev, pp),
116  'bob': bob_wallet.receive_notes(out_ev, pp),
117  'charlie': charlie_wallet.receive_notes(out_ev, pp),
118  }
119  alice_wallet.update_and_save_state(block_num)
120  bob_wallet.update_and_save_state(block_num)
121  charlie_wallet.update_and_save_state(block_num)
122  block_num = block_num + 1
123  return notes
124 
125  print("[INFO] 4. Running tests (asset mixed: ERC20 token)...")
126  # We assign ETHToken to Bob
127  mint_token(
128  web3,
129  token_instance,
130  bob_eth_address,
131  deployer_eth_address,
132  None,
133  EtherValue(2*scenario.BOB_DEPOSIT_ETH, 'ether'))
134  print("- Initial balances: ")
136  token_instance,
137  bob_eth_address,
138  alice_eth_address,
139  charlie_eth_address,
140  zeth_client.mixer_instance.address)
141 
142  # Bob tries to deposit ETHToken, split in 2 notes on the mixer (without
143  # approving)
144  try:
145  result_deposit_bob_to_bob = scenario.bob_deposit(
146  zeth_client,
147  prover_client,
148  mk_tree,
149  bob_eth_address,
150  keystore,
152  except Exception as e:
153  allowance_mixer = allowance(
154  token_instance,
155  bob_eth_address,
156  zeth_client.mixer_instance.address)
157  print(f"[ERROR] Bob deposit failed! (msg: {e})")
158  print("The allowance for Mixer from Bob is: ", allowance_mixer)
159 
160  # Bob approves the transfer
161  print("- Bob approves the transfer of ETHToken to the Mixer")
162  tx_hash = approve(
163  token_instance,
164  bob_eth_address,
165  zeth_client.mixer_instance.address,
166  scenario.BOB_DEPOSIT_ETH)
167  eth.waitForTransactionReceipt(tx_hash)
168  allowance_mixer = allowance(
169  token_instance,
170  bob_eth_address,
171  zeth_client.mixer_instance.address)
172  print("- The allowance for the Mixer from Bob is:", allowance_mixer)
173  # Bob deposits ETHToken, split in 2 notes on the mixer
174  result_deposit_bob_to_bob = scenario.bob_deposit(
175  zeth_client, prover_client, mk_tree, bob_eth_address, keystore)
176 
177  print("- Balances after Bob's deposit: ")
179  token_instance,
180  bob_eth_address,
181  alice_eth_address,
182  charlie_eth_address,
183  zeth_client.mixer_instance.address
184  )
185 
186  # Alice sees a deposit and tries to decrypt the ciphertexts to see if she
187  # was the recipient, but Bob was the recipient so Alice fails to decrypt
188  received_notes = _receive_notes(
189  result_deposit_bob_to_bob.output_events)
190  recovered_notes_alice = received_notes['alice']
191  assert(len(recovered_notes_alice) == 0), \
192  "Alice decrypted a ciphertext that was not encrypted with her key!"
193 
194  # Bob does a transfer of ETHToken to Charlie on the mixer
195 
196  # Bob decrypts one of the note he previously received (useless here but
197  # useful if the payment came from someone else)
198  recovered_notes_bob = received_notes['bob']
199  assert(len(recovered_notes_bob) == 2), \
200  f"Bob recovered {len(recovered_notes_bob)} notes from deposit, expected 2"
201  input_bob_to_charlie = recovered_notes_bob[0].as_input()
202 
203  # Execution of the transfer
204  result_transfer_bob_to_charlie = scenario.bob_to_charlie(
205  zeth_client,
206  prover_client,
207  mk_tree,
208  input_bob_to_charlie,
209  bob_eth_address,
210  keystore)
211 
212  # Bob tries to spend `input_note_bob_to_charlie` twice
213  result_double_spending = None
214  try:
215  result_double_spending = scenario.bob_to_charlie(
216  zeth_client,
217  prover_client,
218  mk_tree,
219  input_bob_to_charlie,
220  bob_eth_address,
221  keystore)
222  except Exception as e:
223  print(f"Bob's double spending successfully rejected! (msg: {e})")
224  assert(result_double_spending is None), "Bob spent the same note twice!"
225 
226  print("- Balances after Bob's transfer to Charlie: ")
228  token_instance,
229  bob_eth_address,
230  alice_eth_address,
231  charlie_eth_address,
232  zeth_client.mixer_instance.address
233  )
234 
235  # Charlie tries to decrypt the notes from Bob's previous transaction.
236  received_notes = _receive_notes(
237  result_transfer_bob_to_charlie.output_events)
238  note_descs_charlie = received_notes['charlie']
239  assert(len(note_descs_charlie) == 1), \
240  f"Charlie decrypted {len(note_descs_charlie)}. Expected 1!"
241 
242  _ = scenario.charlie_withdraw(
243  zeth_client,
244  prover_client,
245  mk_tree,
246  note_descs_charlie[0].as_input(),
247  charlie_eth_address,
248  keystore)
249 
250  print("- Balances after Charlie's withdrawal: ")
252  token_instance,
253  bob_eth_address,
254  alice_eth_address,
255  charlie_eth_address,
256  zeth_client.mixer_instance.address
257  )
258 
259  # Charlie tries to carry out a double spend by withdrawing twice the same
260  # note
261  result_double_spending = None
262  try:
263  # New commitments are added in the tree at each withdraw so we
264  # recompute the path to have the updated nodes
265  result_double_spending = scenario.charlie_double_withdraw(
266  zeth_client,
267  prover_client,
268  zksnark,
269  mk_tree,
270  note_descs_charlie[0].as_input(),
271  charlie_eth_address,
272  keystore)
273  except Exception as e:
274  print(f"Charlie's double spending successfully rejected! (msg: {e})")
275  print("Balances after Charlie's double withdrawal attempt: ")
276  assert(result_double_spending is None), \
277  "Charlie managed to withdraw the same note twice!"
279  token_instance,
280  bob_eth_address,
281  alice_eth_address,
282  charlie_eth_address,
283  zeth_client.mixer_instance.address)
284 
285  # Bob deposits once again ETH, split in 2 notes on the mixer
286  # But Charlie attempts to corrupt the transaction (malleability attack)
287 
288  # Bob approves the transfer
289  print("- Bob approves the transfer of ETHToken to the Mixer")
290  tx_hash = approve(
291  token_instance,
292  bob_eth_address,
293  zeth_client.mixer_instance.address,
294  scenario.BOB_DEPOSIT_ETH)
295  eth.waitForTransactionReceipt(tx_hash)
296  allowance_mixer = allowance(
297  token_instance,
298  bob_eth_address,
299  zeth_client.mixer_instance.address)
300  print("- The allowance for the Mixer from Bob is:", allowance_mixer)
301 
302  result_deposit_bob_to_bob = scenario.charlie_corrupt_bob_deposit(
303  zeth_client,
304  prover_client,
305  zksnark,
306  mk_tree,
307  bob_eth_address,
308  charlie_eth_address,
309  keystore)
310 
311  # Bob decrypts one of the note he previously received (should fail if
312  # Charlie's attack succeeded)
313  received_notes = _receive_notes(
314  result_deposit_bob_to_bob.output_events)
315  recovered_notes_bob = received_notes['bob']
316  assert(len(recovered_notes_bob) == 2), \
317  f"Bob recovered {len(recovered_notes_bob)} notes from deposit, expected 2"
318 
319  print("- Balances after Bob's last deposit: ")
321  token_instance,
322  bob_eth_address,
323  alice_eth_address,
324  charlie_eth_address,
325  zeth_client.mixer_instance.address)
326 
327  print(
328  "========================================\n" +
329  " TESTS PASSED\n" +
330  "========================================\n")
331 
332 
333 if __name__ == '__main__':
334  main()
zeth.core.merkle_tree.MerkleTree.empty_with_depth
MerkleTree empty_with_depth(int depth, ITreeHash tree_hash)
Definition: merkle_tree.py:94
zeth.core.wallet.Wallet
Definition: wallet.py:111
zeth.core.merkle_tree
Definition: merkle_tree.py:1
test_commands.test_erc_token_mixing.allowance
str allowance(Any token_instance, str owner_address, str spender_address)
Definition: test_erc_token_mixing.py:48
zeth.core.wallet
Definition: wallet.py:1
zeth.core.mimc.get_tree_hash_for_pairing
ITreeHash get_tree_hash_for_pairing(str pairing_name)
Definition: mimc.py:138
test_commands.test_erc_token_mixing.main
None main()
Definition: test_erc_token_mixing.py:56
test_commands.deploy_test_token.mint_token
bytes mint_token(Any web3, Any token_instance, str spender_address, str deployer_address, Optional[bytes] deployer_private_key, EtherValue token_amount)
Definition: deploy_test_token.py:108
zeth.core.prover_client
Definition: prover_client.py:1
test_commands.deploy_test_token.deploy_token
Any deploy_token(Any web3, str deployer_address, Optional[bytes] deployer_private_key, Optional[int] deployment_gas)
Definition: deploy_test_token.py:80
zeth.core.mixer_client
Definition: mixer_client.py:1
test_commands.deploy_test_token
Definition: deploy_test_token.py:1
zeth.core.prover_client.ProverClient
Definition: prover_client.py:53
zeth.core.utils
Definition: utils.py:1
test_commands.test_erc_token_mixing.print_token_balances
None print_token_balances(Any token_instance, str bob, str alice, str charlie, str mixer)
Definition: test_erc_token_mixing.py:25
zeth.core.utils.EtherValue
Definition: utils.py:46
zeth.core
Definition: __init__.py:1
zeth.core.mimc
Definition: mimc.py:1
zeth.core.zeth_address
Definition: zeth_address.py:1
zeth.core.utils.parse_zksnark_arg
str parse_zksnark_arg()
Definition: utils.py:234
test_commands.test_erc_token_mixing.approve
str approve(Any token_instance, str owner_address, str spender_address, int token_amount)
Definition: test_erc_token_mixing.py:38
zeth.core.zksnark.get_zksnark_provider
IZKSnarkProvider get_zksnark_provider(str zksnark_name)
Definition: zksnark.py:486