def main() -> None: zksnark_name = zeth.core.utils.parse_zksnark_arg() zksnark = zeth.core.zksnark.get_zksnark_provider(zksnark_name) web3, eth = mock.open_test_web3() # Zeth addresses keystore = mock.init_test_keystore() # Ethereum addresses deployer_eth_address = eth.accounts[0] bob_eth_address = eth.accounts[1] alice_eth_address = eth.accounts[2] charlie_eth_address = eth.accounts[3] # ProverClient prover_client = ProverClient(mock.TEST_PROVER_SERVER_ENDPOINT) prover_config = prover_client.get_configuration() pp = prover_config.pairing_parameters assert prover_client.get_configuration().zksnark_name == zksnark_name # Deploy Zeth contracts tree_depth = zeth.core.constants.ZETH_MERKLE_TREE_DEPTH zeth_client, _contract_desc = MixerClient.deploy( web3, prover_client, deployer_eth_address, None, None, None) # Set up Merkle tree and Wallets. Note that each wallet holds an internal # Merkle Tree, unused in this test. Instead, we keep an in-memory version # shared by all virtual users. This avoids having to pass all mix results # to all wallets, and allows some of the methods in the scenario module, # which must update the tree directly. tree_hash = get_tree_hash_for_pairing(pp.name) mk_tree = zeth.core.merkle_tree.MerkleTree.empty_with_depth( tree_depth, tree_hash) mixer_instance = zeth_client.mixer_instance # Keys and wallets def _mk_wallet(name: str, sk: ZethAddressPriv) -> Wallet: wallet_dir = join(mock.TEST_NOTE_DIR, name + "-eth") if exists(wallet_dir): # Note: symlink-attack resistance # https://docs.python.org/3/library/shutil.html#shutil.rmtree.avoids_symlink_attacks shutil.rmtree(wallet_dir) return Wallet(mixer_instance, name, wallet_dir, sk, tree_hash) sk_alice = keystore['Alice'].addr_sk sk_bob = keystore['Bob'].addr_sk sk_charlie = keystore['Charlie'].addr_sk alice_wallet = _mk_wallet('alice', sk_alice) bob_wallet = _mk_wallet('bob', sk_bob) charlie_wallet = _mk_wallet('charlie', sk_charlie) block_num = 1 # Universal update function def _receive_notes( out_ev: List[MixOutputEvents]) \ -> Dict[str, List[ZethNoteDescription]]: nonlocal block_num notes = { 'alice': alice_wallet.receive_notes(out_ev, pp), 'bob': bob_wallet.receive_notes(out_ev, pp), 'charlie': charlie_wallet.receive_notes(out_ev, pp), } alice_wallet.update_and_save_state(block_num) bob_wallet.update_and_save_state(block_num) charlie_wallet.update_and_save_state(block_num) block_num = block_num + 1 return notes print("[INFO] 4. Running tests (asset mixed: Ether)...") print("- Initial balances: ") print_balances( web3, bob_eth_address, alice_eth_address, charlie_eth_address, zeth_client.mixer_instance.address) # Bob deposits ETH, split in 2 notes on the mixer result_deposit_bob_to_bob = scenario.bob_deposit( zeth_client, prover_client, mk_tree, bob_eth_address, keystore) print("- Balances after Bob's deposit: ") print_balances( web3, bob_eth_address, alice_eth_address, charlie_eth_address, zeth_client.mixer_instance.address ) # Alice sees a deposit and tries to decrypt the ciphertexts to see if she # was the recipient but she wasn't the recipient (Bob was), so she fails to # decrypt recovered_notes = _receive_notes(result_deposit_bob_to_bob.output_events) assert(len(recovered_notes['alice']) == 0), \ "Alice decrypted a ciphertext that was not encrypted with her key!" # Bob does a transfer to Charlie on the mixer # Bob decrypts one of the note he previously received (useless here but # useful if the payment came from someone else) assert(len(recovered_notes['bob']) == 2), \ f"Bob recovered {len(recovered_notes['bob'])} notes, expected 2" # Execution of the transfer result_transfer_bob_to_charlie = scenario.bob_to_charlie( zeth_client, prover_client, mk_tree, recovered_notes['bob'][0].as_input(), bob_eth_address, keystore) # Bob tries to spend `input_note_bob_to_charlie` twice result_double_spending = None try: result_double_spending = scenario.bob_to_charlie( zeth_client, prover_client, mk_tree, recovered_notes['bob'][0].as_input(), bob_eth_address, keystore) except Exception as e: print(f"Bob's double spending successfully rejected! (msg: {e})") assert(result_double_spending is None), \ "Bob managed to spend the same note twice!" print("- Balances after Bob's transfer to Charlie: ") print_balances( web3, bob_eth_address, alice_eth_address, charlie_eth_address, zeth_client.mixer_instance.address ) # Charlie recovers his notes and attempts to withdraw them. recovered_notes = _receive_notes( result_transfer_bob_to_charlie.output_events) notes_charlie = recovered_notes['charlie'] assert(len(notes_charlie) == 1), \ f"Charlie decrypted {len(notes_charlie)}. Expected 1!" input_charlie_withdraw = notes_charlie[0] charlie_balance_before_withdrawal = eth.getBalance(charlie_eth_address) _ = scenario.charlie_withdraw( zeth_client, prover_client, mk_tree, input_charlie_withdraw.as_input(), charlie_eth_address, keystore) charlie_balance_after_withdrawal = eth.getBalance(charlie_eth_address) print("Balances after Charlie's withdrawal: ") print_balances( web3, bob_eth_address, alice_eth_address, charlie_eth_address, zeth_client.mixer_instance.address) if charlie_balance_after_withdrawal <= charlie_balance_before_withdrawal: raise Exception("Charlie's balance did not increase after withdrawal") # Charlie tries to double-spend by withdrawing twice the same note result_double_spending = None try: # New commitments are added in the tree at each withdraw so we # recompiute the path to have the updated nodes result_double_spending = scenario.charlie_double_withdraw( zeth_client, prover_client, zksnark, mk_tree, input_charlie_withdraw.as_input(), charlie_eth_address, keystore) except Exception as e: print(f"Charlie's double spending successfully rejected! (msg: {e})") print("Balances after Charlie's double withdrawal attempt: ") assert(result_double_spending is None), \ "Charlie managed to withdraw the same note twice!" print_balances( web3, bob_eth_address, alice_eth_address, charlie_eth_address, zeth_client.mixer_instance.address) # Bob deposits once again ETH, split in 2 notes on the mixer # But Charlie attempts to corrupt the transaction (malleability attack) result_deposit_bob_to_bob = scenario.charlie_corrupt_bob_deposit( zeth_client, prover_client, zksnark, mk_tree, bob_eth_address, charlie_eth_address, keystore) # Bob decrypts one of the note he previously received (should fail if # Charlie's attack succeeded) recovered_notes = _receive_notes( result_deposit_bob_to_bob.output_events) assert(len(recovered_notes['bob']) == 2), \ f"Bob recovered {len(recovered_notes['bob'])} notes, expected 2" print("- Balances after Bob's last deposit: ") print_balances( web3, bob_eth_address, alice_eth_address, charlie_eth_address, zeth_client.mixer_instance.address) print( "========================================\n" + " TESTS PASSED\n" + "========================================\n")
def main() -> None: zksnark = zeth.zksnark.get_zksnark_provider(zeth.utils.parse_zksnark_arg()) web3, eth = mock.open_test_web3() # Ethereum addresses deployer_eth_address = eth.accounts[0] bob_eth_address = eth.accounts[1] alice_eth_address = eth.accounts[2] charlie_eth_address = eth.accounts[3] # Zeth addresses keystore = mock.init_test_keystore() # Deploy the token contract token_instance = deploy_token(eth, deployer_eth_address, 4000000) # Deploy Zeth contracts tree_depth = constants.ZETH_MERKLE_TREE_DEPTH zeth_client, _contract_desc = MixerClient.deploy( web3, mock.TEST_PROVER_SERVER_ENDPOINT, deployer_eth_address, token_instance.address, None, zksnark) mk_tree = zeth.merkle_tree.MerkleTree.empty_with_depth(tree_depth) mixer_instance = zeth_client.mixer_instance # Keys and wallets def _mk_wallet(name: str, sk: ZethAddressPriv) -> Wallet: wallet_dir = join(mock.TEST_NOTE_DIR, name + "-erc") if exists(wallet_dir): # Note: symlink-attack resistance # https://docs.python.org/3/library/shutil.html#shutil.rmtree.avoids_symlink_attacks shutil.rmtree(wallet_dir) return Wallet(mixer_instance, name, wallet_dir, sk) sk_alice = keystore["Alice"].addr_sk sk_bob = keystore["Bob"].addr_sk sk_charlie = keystore["Charlie"].addr_sk alice_wallet = _mk_wallet('alice', sk_alice) bob_wallet = _mk_wallet('bob', sk_bob) charlie_wallet = _mk_wallet('charlie', sk_charlie) block_num = 1 # Universal update function def _receive_notes( out_ev: List[MixOutputEvents]) \ -> Dict[str, List[ZethNoteDescription]]: nonlocal block_num notes = { 'alice': alice_wallet.receive_notes(out_ev), 'bob': bob_wallet.receive_notes(out_ev), 'charlie': charlie_wallet.receive_notes(out_ev), } alice_wallet.update_and_save_state(block_num) bob_wallet.update_and_save_state(block_num) charlie_wallet.update_and_save_state(block_num) block_num = block_num + 1 return notes print("[INFO] 4. Running tests (asset mixed: ERC20 token)...") # We assign ETHToken to Bob mint_token(token_instance, bob_eth_address, deployer_eth_address, 2 * scenario.BOB_DEPOSIT_ETH) print("- Initial balances: ") print_token_balances(token_instance, bob_eth_address, alice_eth_address, charlie_eth_address, zeth_client.mixer_instance.address) # Bob tries to deposit ETHToken, split in 2 notes on the mixer (without # approving) try: result_deposit_bob_to_bob = scenario.bob_deposit( zeth_client, mk_tree, bob_eth_address, keystore, zeth.utils.EtherValue(0)) except Exception as e: allowance_mixer = allowance(token_instance, bob_eth_address, zeth_client.mixer_instance.address) print(f"[ERROR] Bob deposit failed! (msg: {e})") print("The allowance for Mixer from Bob is: ", allowance_mixer) # Bob approves the transfer print("- Bob approves the transfer of ETHToken to the Mixer") tx_hash = approve(token_instance, bob_eth_address, zeth_client.mixer_instance.address, scenario.BOB_DEPOSIT_ETH) eth.waitForTransactionReceipt(tx_hash) allowance_mixer = allowance(token_instance, bob_eth_address, zeth_client.mixer_instance.address) print("- The allowance for the Mixer from Bob is:", allowance_mixer) # Bob deposits ETHToken, split in 2 notes on the mixer result_deposit_bob_to_bob = scenario.bob_deposit(zeth_client, mk_tree, bob_eth_address, keystore) print("- Balances after Bob's deposit: ") print_token_balances(token_instance, bob_eth_address, alice_eth_address, charlie_eth_address, zeth_client.mixer_instance.address) # Alice sees a deposit and tries to decrypt the ciphertexts to see if she # was the recipient, but Bob was the recipient so Alice fails to decrypt received_notes = _receive_notes(result_deposit_bob_to_bob.output_events) recovered_notes_alice = received_notes['alice'] assert(len(recovered_notes_alice) == 0), \ "Alice decrypted a ciphertext that was not encrypted with her key!" # Bob does a transfer of ETHToken to Charlie on the mixer # Bob decrypts one of the note he previously received (useless here but # useful if the payment came from someone else) recovered_notes_bob = received_notes['bob'] assert(len(recovered_notes_bob) == 2), \ f"Bob recovered {len(recovered_notes_bob)} notes from deposit, expected 2" input_bob_to_charlie = recovered_notes_bob[0].as_input() # Execution of the transfer result_transfer_bob_to_charlie = scenario.bob_to_charlie( zeth_client, mk_tree, input_bob_to_charlie, bob_eth_address, keystore) # Bob tries to spend `input_note_bob_to_charlie` twice result_double_spending = None try: result_double_spending = scenario.bob_to_charlie( zeth_client, mk_tree, input_bob_to_charlie, bob_eth_address, keystore) except Exception as e: print(f"Bob's double spending successfully rejected! (msg: {e})") assert (result_double_spending is None), "Bob spent the same note twice!" print("- Balances after Bob's transfer to Charlie: ") print_token_balances(token_instance, bob_eth_address, alice_eth_address, charlie_eth_address, zeth_client.mixer_instance.address) # Charlie tries to decrypt the notes from Bob's previous transaction. received_notes = _receive_notes( result_transfer_bob_to_charlie.output_events) note_descs_charlie = received_notes['charlie'] assert(len(note_descs_charlie) == 1), \ f"Charlie decrypted {len(note_descs_charlie)}. Expected 1!" _ = scenario.charlie_withdraw(zeth_client, mk_tree, note_descs_charlie[0].as_input(), charlie_eth_address, keystore) print("- Balances after Charlie's withdrawal: ") print_token_balances(token_instance, bob_eth_address, alice_eth_address, charlie_eth_address, zeth_client.mixer_instance.address) # Charlie tries to carry out a double spend by withdrawing twice the same # note result_double_spending = None try: # New commitments are added in the tree at each withdraw so we # recompute the path to have the updated nodes result_double_spending = scenario.charlie_double_withdraw( zeth_client, mk_tree, note_descs_charlie[0].as_input(), charlie_eth_address, keystore) except Exception as e: print(f"Charlie's double spending successfully rejected! (msg: {e})") print("Balances after Charlie's double withdrawal attempt: ") assert(result_double_spending is None), \ "Charlie managed to withdraw the same note twice!" print_token_balances(token_instance, bob_eth_address, alice_eth_address, charlie_eth_address, zeth_client.mixer_instance.address) # Bob deposits once again ETH, split in 2 notes on the mixer # But Charlie attempts to corrupt the transaction (malleability attack) # Bob approves the transfer print("- Bob approves the transfer of ETHToken to the Mixer") tx_hash = approve(token_instance, bob_eth_address, zeth_client.mixer_instance.address, scenario.BOB_DEPOSIT_ETH) eth.waitForTransactionReceipt(tx_hash) allowance_mixer = allowance(token_instance, bob_eth_address, zeth_client.mixer_instance.address) print("- The allowance for the Mixer from Bob is:", allowance_mixer) result_deposit_bob_to_bob = scenario.charlie_corrupt_bob_deposit( zeth_client, mk_tree, bob_eth_address, charlie_eth_address, keystore) # Bob decrypts one of the note he previously received (should fail if # Charlie's attack succeeded) received_notes = _receive_notes(result_deposit_bob_to_bob.output_events) recovered_notes_bob = received_notes['bob'] assert(len(recovered_notes_bob) == 2), \ f"Bob recovered {len(recovered_notes_bob)} notes from deposit, expected 2" print("- Balances after Bob's last deposit: ") print_token_balances(token_instance, bob_eth_address, alice_eth_address, charlie_eth_address, zeth_client.mixer_instance.address) print("========================================\n" + " TESTS PASSED\n" + "========================================\n")
def main() -> None: print("***********************") zksnark = zeth.zksnark.get_zksnark_provider("GROTH16") #web3, eth = mock.open_test_web3() ''' # Ethereum addresses deployer_eth_address = eth.accounts[0] bob_eth_address = eth.accounts[1] alice_eth_address = eth.accounts[2] charlie_eth_address = eth.accounts[3] ''' account_keyfile_path = "python_web3/bin/accounts" # 保存keystore文件的路径,在此路径下,keystore文件以 [name].keystore命名 account_keyfile = "pyaccount.keystore" keystore_file = "{}/{}".format(account_keyfile_path, account_keyfile) with open(keystore_file, "r") as dump_f: keytext = json.load(dump_f) privkey = Account.decrypt(keytext, "123456") deployer_ac = Account.from_key(privkey) # Fisco-bcos addresses bob_password = "******" alice_password = "******" charlie_password = "******" bob_ac = Account.create(bob_password) alice_ac = Account.create(alice_password) charlie_ac = Account.create(charlie_password) #keypair deployer_keypair = BcosKeyPair() deployer_keypair.private_key = deployer_ac.privateKey deployer_keypair.public_key = deployer_ac.publickey deployer_keypair.address = deployer_ac.address bob_keypair = BcosKeyPair() bob_keypair.private_key = bob_ac.privateKey bob_keypair.public_key = bob_ac.publickey bob_keypair.address = bob_ac.address # alice_keypair = BcosKeyPair() # alice_keypair.private_key = alice_ac.privateKey # alice_keypair.public_key = alice_ac.publickey # alice_keypair.address = alice_ac.address charlie_keypair = BcosKeyPair() charlie_keypair.private_key = charlie_ac.privateKey charlie_keypair.public_key = charlie_ac.publickey charlie_keypair.address = charlie_ac.address # Zeth addresses keystore = mock.init_test_keystore() # Deploy Zeth contracts tree_depth = constants.ZETH_MERKLE_TREE_DEPTH asset_address = deploy_asset("AAAA", "AAA", 18, 100000000) token_instance = BAC001(asset_address) mixer_address = deploy(asset_address) mixer_instance = Groth16Mixer(mixer_address) mixer_instance.client.ecdsa_account = deployer_ac mixer_instance.client.keypair = deployer_keypair print("token address: ", mixer_instance.token()) zeth_client = MixerClient.open(PROVER_SERVER_ENDPOINT_DEFAULT, mixer_instance) mk_tree = zeth.merkle_tree.MerkleTree.empty_with_depth(tree_depth) #mixer_instance = zeth_client.mixer_instance # Keys and wallets def _mk_wallet(name: str, sk: ZethAddressPriv) -> Wallet: wallet_dir = join(mock.TEST_NOTE_DIR, name + "-erc") if exists(wallet_dir): # Note: symlink-attack resistance # https://docs.python.org/3/library/shutil.html#shutil.rmtree.avoids_symlink_attacks shutil.rmtree(wallet_dir) return Wallet(mixer_instance, name, wallet_dir, sk) sk_alice = keystore["Alice"].addr_sk sk_bob = keystore["Bob"].addr_sk sk_charlie = keystore["Charlie"].addr_sk # alice_wallet = _mk_wallet('alice', sk_alice) # bob_wallet = _mk_wallet('bob', sk_bob) # charlie_wallet = _mk_wallet('charlie', sk_charlie) #block_num = 1 # Universal update function # def _receive_notes( # out_ev: List[MixOutputEvents]) \ # -> Dict[str, List[ZethNoteDescription]]: # #nonlocal block_num # notes = { # 'alice': alice_wallet.receive_notes(out_ev), # 'bob': bob_wallet.receive_notes(out_ev), # 'charlie': charlie_wallet.receive_notes(out_ev), # } # alice_wallet.update_and_save_state() # bob_wallet.update_and_save_state() # charlie_wallet.update_and_save_state() # #block_num = block_num + 1 # return notes print("[INFO] 4. Running tests (asset mixed: ERC20 token)...") # We assign ETHToken to Bob print("- Initial balances: ") print_token_balances(token_instance, bob_ac.address, alice_ac.address, charlie_ac.address, mixer_address) # Bob tries to deposit ETHToken, split in 2 notes on the mixer (without # approving) token_instance.client.ecdsa_account = bob_ac token_instance.client.keypair = bob_keypair zeth_client.mixer_instance.client.ecdsa_account = bob_ac zeth_client.mixer_instance.client.keypair = bob_keypair # Bob approves the transfer print("- Bob approves the transfer of ETHToken to the Mixer") token_instance.client.ecdsa_account = bob_ac token_instance.client.keypair = bob_keypair (outputresult, receipt) = token_instance.send(bob_ac.address, Web3.toWei(10000, 'ether'), '') print("send *****", receipt['status']) (outputresult, receipt) = token_instance.approve(mixer_address, scenario.BOB_DEPOSIT_ETH) # eth.waitForTransactionReceipt(tx_hash) outputresult = token_instance.allowance(deployer_ac.address, mixer_address) print("- The allowance for the Mixer from Bob is:", outputresult) # # Bob deposits ETHToken, split in 2 notes on the mixer # result_deposit_bob_to_bob = scenario.bob_deposit( # zeth_client, mk_tree, bob_ac.address, keystore, zeth.utils.EtherValue(0)) # # print("- Balances after Bob's deposit: ") # print_token_balances( # token_instance, # bob_ac.address, # alice_ac.address, # charlie_ac.address, # mixer_address # ) # # # Alice sees a deposit and tries to decrypt the ciphertexts to see if she # # was the recipient, but Bob was the recipient so Alice fails to decrypt # received_notes = _receive_notes( # result_deposit_bob_to_bob.output_events) # recovered_notes_alice = received_notes['alice'] # assert(len(recovered_notes_alice) == 0), \ # "Alice decrypted a ciphertext that was not encrypted with her key!" # # # Bob does a transfer of ETHToken to Charlie on the mixer # # # Bob decrypts one of the note he previously received (useless here but # # useful if the payment came from someone else) # recovered_notes_bob = received_notes['bob'] # assert(len(recovered_notes_bob) == 2), \ # f"Bob recovered {len(recovered_notes_bob)} notes from deposit, expected 2" # input_bob_to_charlie = recovered_notes_bob[0].as_input() ''' # Execution of the transfer result_transfer_bob_to_charlie = scenario.bob_to_charlie( zeth_client, mk_tree, input_bob_to_charlie, bob_eth_address, keystore) # Bob tries to spend `input_note_bob_to_charlie` twice result_double_spending = None try: result_double_spending = scenario.bob_to_charlie( zeth_client, mk_tree, input_bob_to_charlie, bob_eth_address, keystore) except Exception as e: print(f"Bob's double spending successfully rejected! (msg: {e})") assert(result_double_spending is None), "Bob spent the same note twice!" print("- Balances after Bob's transfer to Charlie: ") print_token_balances( token_instance, bob_eth_address, alice_eth_address, charlie_eth_address, zeth_client.mixer_instance.address ) # Charlie tries to decrypt the notes from Bob's previous transaction. received_notes = _receive_notes( result_transfer_bob_to_charlie.output_events) note_descs_charlie = received_notes['charlie'] assert(len(note_descs_charlie) == 1), \ f"Charlie decrypted {len(note_descs_charlie)}. Expected 1!" _ = scenario.charlie_withdraw( zeth_client, mk_tree, note_descs_charlie[0].as_input(), charlie_eth_address, keystore) print("- Balances after Charlie's withdrawal: ") print_token_balances( token_instance, bob_eth_address, alice_eth_address, charlie_eth_address, zeth_client.mixer_instance.address ) # Charlie tries to carry out a double spend by withdrawing twice the same # note result_double_spending = None try: # New commitments are added in the tree at each withdraw so we # recompute the path to have the updated nodes result_double_spending = scenario.charlie_double_withdraw( zeth_client, mk_tree, note_descs_charlie[0].as_input(), charlie_eth_address, keystore) except Exception as e: print(f"Charlie's double spending successfully rejected! (msg: {e})") print("Balances after Charlie's double withdrawal attempt: ") assert(result_double_spending is None), \ "Charlie managed to withdraw the same note twice!" print_token_balances( token_instance, bob_eth_address, alice_eth_address, charlie_eth_address, zeth_client.mixer_instance.address) # Bob deposits once again ETH, split in 2 notes on the mixer # But Charlie attempts to corrupt the transaction (malleability attack) # Bob approves the transfer print("- Bob approves the transfer of ETHToken to the Mixer") tx_hash = approve( token_instance, bob_eth_address, zeth_client.mixer_instance.address, scenario.BOB_DEPOSIT_ETH) eth.waitForTransactionReceipt(tx_hash) allowance_mixer = allowance( token_instance, bob_eth_address, zeth_client.mixer_instance.address) print("- The allowance for the Mixer from Bob is:", allowance_mixer) result_deposit_bob_to_bob = scenario.charlie_corrupt_bob_deposit( zeth_client, mk_tree, bob_eth_address, charlie_eth_address, keystore) # Bob decrypts one of the note he previously received (should fail if # Charlie's attack succeeded) received_notes = _receive_notes( result_deposit_bob_to_bob.output_events) recovered_notes_bob = received_notes['bob'] assert(len(recovered_notes_bob) == 2), \ f"Bob recovered {len(recovered_notes_bob)} notes from deposit, expected 2" print("- Balances after Bob's last deposit: ") print_token_balances( token_instance, bob_eth_address, alice_eth_address, charlie_eth_address, zeth_client.mixer_instance.address) ''' print("========================================\n" + " TESTS PASSED\n" + "========================================\n")
def main() -> None: zksnark = zeth.zksnark.get_zksnark_provider(zeth.utils.parse_zksnark_arg()) # Zeth addresses keystore = mock.init_test_keystore() # Depth of the merkle tree (need to match the one used in the cpp prover) mk_tree_depth = constants.ZETH_MERKLE_TREE_DEPTH # Ethereum addresses deployer_eth_address = eth.accounts[0] bob_eth_address = eth.accounts[1] alice_eth_address = eth.accounts[2] charlie_eth_address = eth.accounts[3] prover_client = ProverClient(TEST_GRPC_ENDPOINT) coinstore_dir = os.environ['ZETH_COINSTORE'] # Keys and wallets k_sk_alice = keystore["Alice"].addr_sk.k_sk k_sk_bob = keystore["Bob"].addr_sk.k_sk k_sk_charlie = keystore["Charlie"].addr_sk.k_sk alice_wallet = Wallet("alice", coinstore_dir, k_sk_alice) bob_wallet = Wallet("bob", coinstore_dir, k_sk_bob) charlie_wallet = Wallet("charlie", coinstore_dir, k_sk_charlie) print("[INFO] 1. Fetching the verification key from the proving server") vk = prover_client.get_verification_key() print("[INFO] 2. Received VK, writing the key...") zeth.joinsplit.write_verification_key(vk, zksnark) print("[INFO] 3. VK written, deploying the smart contracts...") (proof_verifier_interface, otsig_verifier_interface, mixer_interface) = \ zeth.contracts.compile_contracts(zksnark) hasher_interface, _ = zeth.contracts.compile_util_contracts() (mixer_instance, initial_root) = zeth.contracts.deploy_contracts( mk_tree_depth, proof_verifier_interface, otsig_verifier_interface, mixer_interface, hasher_interface, deployer_eth_address, 4000000, # We mix Ether in this test, so we set the addr of the ERC20 contract # to be 0x0 "0x0000000000000000000000000000000000000000", zksnark ) zeth_client = zeth.joinsplit.ZethClient( prover_client, mixer_instance, zksnark) print("[INFO] 4. Running tests (asset mixed: Ether)...") print("- Initial balances: ") print_balances( bob_eth_address, alice_eth_address, charlie_eth_address, mixer_instance.address) # Bob deposits ETH, split in 2 notes on the mixer result_deposit_bob_to_bob = scenario.bob_deposit( zeth_client, initial_root, bob_eth_address, keystore, mk_tree_depth ) new_merkle_root_bob_to_bob = result_deposit_bob_to_bob.new_merkle_root print("- Balances after Bob's deposit: ") print_balances( bob_eth_address, alice_eth_address, charlie_eth_address, mixer_instance.address ) # Alice sees a deposit and tries to decrypt the ciphertexts to see if she # was the recipient but she wasn't the recipient (Bob was), so she fails to # decrypt recovered_notes_alice = alice_wallet.receive_notes( result_deposit_bob_to_bob.encrypted_notes, result_deposit_bob_to_bob.sender_k_pk) assert(len(recovered_notes_alice) == 0), \ "Alice decrypted a ciphertext that was not encrypted with her key!" # Bob does a transfer to Charlie on the mixer # Bob decrypts one of the note he previously received (useless here but # useful if the payment came from someone else) recovered_notes_bob = bob_wallet.receive_notes( result_deposit_bob_to_bob.encrypted_notes, result_deposit_bob_to_bob.sender_k_pk) assert(len(recovered_notes_bob) == 2), \ f"Bob recovered {len(recovered_notes_bob)} notes from deposit, expected 2" bob_to_charlie = recovered_notes_bob[0] # Execution of the transfer result_transfer_bob_to_charlie = scenario.bob_to_charlie( zeth_client, new_merkle_root_bob_to_bob, bob_to_charlie, bob_eth_address, keystore, mk_tree_depth) new_merkle_root_bob_to_charlie = \ result_transfer_bob_to_charlie.new_merkle_root # Bob tries to spend `input_note_bob_to_charlie` twice result_double_spending = None try: result_double_spending = scenario.bob_to_charlie( zeth_client, new_merkle_root_bob_to_bob, bob_to_charlie, bob_eth_address, keystore, mk_tree_depth) except Exception as e: print(f"Bob's double spending successfully rejected! (msg: {e})") assert(result_double_spending is None), \ "Bob managed to spend the same note twice!" print("- Balances after Bob's transfer to Charlie: ") print_balances( bob_eth_address, alice_eth_address, charlie_eth_address, mixer_instance.address ) # Charlie recovers his notes and attempts to withdraw them. notes_charlie = charlie_wallet.receive_notes( result_transfer_bob_to_charlie.encrypted_notes, result_transfer_bob_to_charlie.sender_k_pk) assert(len(notes_charlie) == 1), \ f"Charlie decrypted {len(notes_charlie)}. Expected 1!" input_charlie_withdraw = notes_charlie[0] assert input_charlie_withdraw[0] == \ result_transfer_bob_to_charlie.encrypted_notes[1][0] result_charlie_withdrawal = scenario.charlie_withdraw( zeth_client, new_merkle_root_bob_to_charlie, input_charlie_withdraw, charlie_eth_address, keystore, mk_tree_depth) new_merkle_root_charlie_withdrawal = result_charlie_withdrawal.new_merkle_root print("Balances after Charlie's withdrawal: ") print_balances( bob_eth_address, alice_eth_address, charlie_eth_address, mixer_instance.address ) # Charlie tries to double-spend by withdrawing twice the same note result_double_spending = None try: # New commitments are added in the tree at each withdraw so we # recompiute the path to have the updated nodes result_double_spending = scenario.charlie_double_withdraw( zeth_client, new_merkle_root_charlie_withdrawal, input_charlie_withdraw, charlie_eth_address, keystore, mk_tree_depth) except Exception as e: print(f"Charlie's double spending successfully rejected! (msg: {e})") print("Balances after Charlie's double withdrawal attempt: ") assert(result_double_spending is None), \ "Charlie managed to withdraw the same note twice!" print_balances( bob_eth_address, alice_eth_address, charlie_eth_address, mixer_instance.address ) # Bob deposits once again ETH, split in 2 notes on the mixer # But Charlie attempts to corrupt the transaction (malleability attack) result_deposit_bob_to_bob = scenario.charlie_corrupt_bob_deposit( zeth_client, new_merkle_root_charlie_withdrawal, bob_eth_address, charlie_eth_address, keystore, mk_tree_depth) # Bob decrypts one of the note he previously received (should fail if # Charlie's attack succeeded) recovered_notes_bob = bob_wallet.receive_notes( result_deposit_bob_to_bob.encrypted_notes, result_deposit_bob_to_bob.sender_k_pk) assert(len(recovered_notes_bob) == 2), \ f"Bob recovered {len(recovered_notes_bob)} notes from deposit, expected 2" print("- Balances after Bob's last deposit: ") print_balances( bob_eth_address, alice_eth_address, charlie_eth_address, mixer_instance.address )