Zeth - Zerocash on Ethereum  0.8
Reference implementation of the Zeth protocol by Clearmatics
test_ether_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 
10 import zeth.core.utils
11 import zeth.core.zksnark
12 from zeth.core.mimc import get_tree_hash_for_pairing
13 from zeth.core.prover_client import ProverClient
14 from zeth.core.zeth_address import ZethAddressPriv
15 from zeth.core.mixer_client import MixOutputEvents, MixerClient
16 from zeth.core.wallet import Wallet, ZethNoteDescription
17 from test_commands import mock
18 from test_commands import scenario
19 
20 from os.path import join, exists
21 import shutil
22 from typing import Dict, List, Any
23 
24 
26  web3: Any, bob: str, alice: str, charlie: str, mixer: str) -> None:
27  print("BALANCES:")
28  print(f" Alice : {web3.eth.getBalance(alice)}")
29  print(f" Bob : {web3.eth.getBalance(bob)}")
30  print(f" Charlie : {web3.eth.getBalance(charlie)}")
31  print(f" Mixer : {web3.eth.getBalance(mixer)}")
32 
33 
34 def main() -> None:
35  zksnark_name = zeth.core.utils.parse_zksnark_arg()
36  zksnark = zeth.core.zksnark.get_zksnark_provider(zksnark_name)
37 
38  web3, eth = mock.open_test_web3()
39 
40  # Zeth addresses
41  keystore = mock.init_test_keystore()
42  # Ethereum addresses
43  deployer_eth_address = eth.accounts[0]
44  bob_eth_address = eth.accounts[1]
45  alice_eth_address = eth.accounts[2]
46  charlie_eth_address = eth.accounts[3]
47 
48  # ProverClient
49  prover_client = ProverClient(mock.TEST_PROVER_SERVER_ENDPOINT)
50  prover_config = prover_client.get_configuration()
51  pp = prover_config.pairing_parameters
52  assert prover_client.get_configuration().zksnark_name == zksnark_name
53 
54  # Deploy Zeth contracts
55  tree_depth = zeth.core.constants.ZETH_MERKLE_TREE_DEPTH
56  zeth_client, _contract_desc = MixerClient.deploy(
57  web3,
58  prover_client,
59  deployer_eth_address,
60  None,
61  None,
62  None)
63 
64  # Set up Merkle tree and Wallets. Note that each wallet holds an internal
65  # Merkle Tree, unused in this test. Instead, we keep an in-memory version
66  # shared by all virtual users. This avoids having to pass all mix results
67  # to all wallets, and allows some of the methods in the scenario module,
68  # which must update the tree directly.
69  tree_hash = get_tree_hash_for_pairing(pp.name)
71  tree_depth, tree_hash)
72  mixer_instance = zeth_client.mixer_instance
73 
74  # Keys and wallets
75  def _mk_wallet(name: str, sk: ZethAddressPriv) -> Wallet:
76  wallet_dir = join(mock.TEST_NOTE_DIR, name + "-eth")
77  if exists(wallet_dir):
78  # Note: symlink-attack resistance
79  # https://docs.python.org/3/library/shutil.html#shutil.rmtree.avoids_symlink_attacks
80  shutil.rmtree(wallet_dir)
81  return Wallet(mixer_instance, name, wallet_dir, sk, tree_hash)
82 
83  sk_alice = keystore['Alice'].addr_sk
84  sk_bob = keystore['Bob'].addr_sk
85  sk_charlie = keystore['Charlie'].addr_sk
86  alice_wallet = _mk_wallet('alice', sk_alice)
87  bob_wallet = _mk_wallet('bob', sk_bob)
88  charlie_wallet = _mk_wallet('charlie', sk_charlie)
89  block_num = 1
90 
91  # Universal update function
92  def _receive_notes(
93  out_ev: List[MixOutputEvents]) \
94  -> Dict[str, List[ZethNoteDescription]]:
95  nonlocal block_num
96  notes = {
97  'alice': alice_wallet.receive_notes(out_ev, pp),
98  'bob': bob_wallet.receive_notes(out_ev, pp),
99  'charlie': charlie_wallet.receive_notes(out_ev, pp),
100  }
101  alice_wallet.update_and_save_state(block_num)
102  bob_wallet.update_and_save_state(block_num)
103  charlie_wallet.update_and_save_state(block_num)
104  block_num = block_num + 1
105  return notes
106 
107  print("[INFO] 4. Running tests (asset mixed: Ether)...")
108  print("- Initial balances: ")
110  web3,
111  bob_eth_address,
112  alice_eth_address,
113  charlie_eth_address,
114  zeth_client.mixer_instance.address)
115 
116  # Bob deposits ETH, split in 2 notes on the mixer
117  result_deposit_bob_to_bob = scenario.bob_deposit(
118  zeth_client,
119  prover_client,
120  mk_tree,
121  bob_eth_address,
122  keystore)
123 
124  print("- Balances after Bob's deposit: ")
126  web3,
127  bob_eth_address,
128  alice_eth_address,
129  charlie_eth_address,
130  zeth_client.mixer_instance.address
131  )
132 
133  # Alice sees a deposit and tries to decrypt the ciphertexts to see if she
134  # was the recipient but she wasn't the recipient (Bob was), so she fails to
135  # decrypt
136  recovered_notes = _receive_notes(result_deposit_bob_to_bob.output_events)
137  assert(len(recovered_notes['alice']) == 0), \
138  "Alice decrypted a ciphertext that was not encrypted with her key!"
139 
140  # Bob does a transfer to Charlie on the mixer
141 
142  # Bob decrypts one of the note he previously received (useless here but
143  # useful if the payment came from someone else)
144  assert(len(recovered_notes['bob']) == 2), \
145  f"Bob recovered {len(recovered_notes['bob'])} notes, expected 2"
146 
147  # Execution of the transfer
148  result_transfer_bob_to_charlie = scenario.bob_to_charlie(
149  zeth_client,
150  prover_client,
151  mk_tree,
152  recovered_notes['bob'][0].as_input(),
153  bob_eth_address,
154  keystore)
155 
156  # Bob tries to spend `input_note_bob_to_charlie` twice
157  result_double_spending = None
158  try:
159  result_double_spending = scenario.bob_to_charlie(
160  zeth_client,
161  prover_client,
162  mk_tree,
163  recovered_notes['bob'][0].as_input(),
164  bob_eth_address,
165  keystore)
166  except Exception as e:
167  print(f"Bob's double spending successfully rejected! (msg: {e})")
168  assert(result_double_spending is None), \
169  "Bob managed to spend the same note twice!"
170 
171  print("- Balances after Bob's transfer to Charlie: ")
173  web3,
174  bob_eth_address,
175  alice_eth_address,
176  charlie_eth_address,
177  zeth_client.mixer_instance.address
178  )
179 
180  # Charlie recovers his notes and attempts to withdraw them.
181  recovered_notes = _receive_notes(
182  result_transfer_bob_to_charlie.output_events)
183  notes_charlie = recovered_notes['charlie']
184  assert(len(notes_charlie) == 1), \
185  f"Charlie decrypted {len(notes_charlie)}. Expected 1!"
186 
187  input_charlie_withdraw = notes_charlie[0]
188 
189  charlie_balance_before_withdrawal = eth.getBalance(charlie_eth_address)
190  _ = scenario.charlie_withdraw(
191  zeth_client,
192  prover_client,
193  mk_tree,
194  input_charlie_withdraw.as_input(),
195  charlie_eth_address,
196  keystore)
197  charlie_balance_after_withdrawal = eth.getBalance(charlie_eth_address)
198  print("Balances after Charlie's withdrawal: ")
200  web3,
201  bob_eth_address,
202  alice_eth_address,
203  charlie_eth_address,
204  zeth_client.mixer_instance.address)
205  if charlie_balance_after_withdrawal <= charlie_balance_before_withdrawal:
206  raise Exception("Charlie's balance did not increase after withdrawal")
207 
208  # Charlie tries to double-spend by withdrawing twice the same note
209  result_double_spending = None
210  try:
211  # New commitments are added in the tree at each withdraw so we
212  # recompiute the path to have the updated nodes
213  result_double_spending = scenario.charlie_double_withdraw(
214  zeth_client,
215  prover_client,
216  zksnark,
217  mk_tree,
218  input_charlie_withdraw.as_input(),
219  charlie_eth_address,
220  keystore)
221  except Exception as e:
222  print(f"Charlie's double spending successfully rejected! (msg: {e})")
223  print("Balances after Charlie's double withdrawal attempt: ")
224  assert(result_double_spending is None), \
225  "Charlie managed to withdraw the same note twice!"
227  web3,
228  bob_eth_address,
229  alice_eth_address,
230  charlie_eth_address,
231  zeth_client.mixer_instance.address)
232 
233  # Bob deposits once again ETH, split in 2 notes on the mixer
234  # But Charlie attempts to corrupt the transaction (malleability attack)
235  result_deposit_bob_to_bob = scenario.charlie_corrupt_bob_deposit(
236  zeth_client,
237  prover_client,
238  zksnark,
239  mk_tree,
240  bob_eth_address,
241  charlie_eth_address,
242  keystore)
243 
244  # Bob decrypts one of the note he previously received (should fail if
245  # Charlie's attack succeeded)
246  recovered_notes = _receive_notes(
247  result_deposit_bob_to_bob.output_events)
248  assert(len(recovered_notes['bob']) == 2), \
249  f"Bob recovered {len(recovered_notes['bob'])} notes, expected 2"
250 
251  print("- Balances after Bob's last deposit: ")
253  web3,
254  bob_eth_address,
255  alice_eth_address,
256  charlie_eth_address,
257  zeth_client.mixer_instance.address)
258 
259  print(
260  "========================================\n" +
261  " TESTS PASSED\n" +
262  "========================================\n")
263 
264 
265 if __name__ == '__main__':
266  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
zeth.core.constants
Definition: constants.py:1
zeth.core.wallet
Definition: wallet.py:1
test_commands.test_ether_mixing.main
None main()
Definition: test_ether_mixing.py:34
zeth.core.mimc.get_tree_hash_for_pairing
ITreeHash get_tree_hash_for_pairing(str pairing_name)
Definition: mimc.py:138
zeth.core.prover_client
Definition: prover_client.py:1
zeth.core.mixer_client
Definition: mixer_client.py:1
zeth.core.prover_client.ProverClient
Definition: prover_client.py:53
zeth.core.utils
Definition: utils.py:1
zeth.core.contracts
Definition: contracts.py:1
test_commands.test_ether_mixing.print_balances
None print_balances(Any web3, str bob, str alice, str charlie, str mixer)
Definition: test_ether_mixing.py:25
zeth.core.zksnark
Definition: zksnark.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
zeth.core.zksnark.get_zksnark_provider
IZKSnarkProvider get_zksnark_provider(str zksnark_name)
Definition: zksnark.py:486