示例#1
0
def bob_deposit(
        zeth_client: MixerClient,
        mk_tree: MerkleTree,
        bob_eth_address: str,
        keystore: mock.KeyStore,
        tx_value: Optional[EtherValue] = None) -> contracts.MixResult:
    print(
        f"=== Bob deposits {BOB_DEPOSIT_ETH} ETH for himself and splits into " +
        f"note1: {BOB_SPLIT_1_ETH}ETH, note2: {BOB_SPLIT_2_ETH}ETH ===")

    bob_js_keypair = keystore["Bob"]
    bob_addr = keystore["Bob"].addr_pk

    outputs = [
        (bob_addr, EtherValue(BOB_SPLIT_1_ETH)),
        (bob_addr, EtherValue(BOB_SPLIT_2_ETH)),
    ]

    (outputresult, receipt) = zeth_client.deposit(
        mk_tree,
        bob_js_keypair,
        bob_eth_address,
        EtherValue(BOB_DEPOSIT_ETH),
        outputs,
        tx_value)
    # print(outputresult)
    print("receipt status: ", receipt['status'])
    return wait_for_tx_update_mk_tree(zeth_client, mk_tree, receipt)
示例#2
0
def charlie_withdraw(
        zeth_client: MixerClient,
        mk_tree: MerkleTree,
        input1: Tuple[int, ZethNote],
        charlie_eth_address: str,
        keystore: mock.KeyStore) -> contracts.MixResult:
    print(
        f" === Charlie withdraws {CHARLIE_WITHDRAW_ETH}ETH from his funds " +
        "on the Mixer ===")

    charlie_pk = keystore["Charlie"].addr_pk
    charlie_apk = charlie_pk.a_pk
    charlie_ask = keystore["Charlie"].addr_sk.a_sk
    charlie_ownership_key = \
        OwnershipKeyPair(charlie_ask, charlie_apk)

    (outputresult, receipt)  = zeth_client.joinsplit(
        mk_tree,
        charlie_ownership_key,
        charlie_eth_address,
        [input1],
        [(charlie_pk, EtherValue(CHARLIE_WITHDRAW_CHANGE_ETH))],
        EtherValue(0),
        EtherValue(CHARLIE_WITHDRAW_ETH),
        EtherValue(1, 'wei'))
    return wait_for_tx_update_mk_tree(zeth_client, mk_tree, receipt)
示例#3
0
def bob_to_charlie(
        zeth_client: MixerClient,
        mk_tree: MerkleTree,
        input1: Tuple[int, ZethNote],
        bob_eth_address: str,
        keystore: mock.KeyStore) -> contracts.MixResult:
    print(
        f"=== Bob transfers {BOB_TO_CHARLIE_ETH}ETH to Charlie from his funds " +
        "on the mixer ===")

    bob_ask = keystore["Bob"].addr_sk.a_sk
    charlie_addr = keystore["Charlie"].addr_pk
    bob_addr = keystore["Bob"].addr_pk

    # Coin for Bob (change)
    output0 = (bob_addr, EtherValue(BOB_TO_CHARLIE_ETH))
    # Coin for Charlie
    output1 = (charlie_addr, EtherValue(BOB_TO_CHARLIE_CHANGE_ETH))

    # Send the tx
    (outputresult, receipt) = zeth_client.joinsplit(
        mk_tree,
        OwnershipKeyPair(bob_ask, bob_addr.a_pk),
        bob_eth_address,
        [input1],
        [output0, output1],
        EtherValue(0),
        EtherValue(0),
        EtherValue(1, 'wei'))
    return wait_for_tx_update_mk_tree(zeth_client, mk_tree, receipt)
示例#4
0
def create_mixer_client(ctx: ClientConfig) -> MixerClient:
    """
    Create a MixerClient for an existing deployment.
    """
    web3 = open_web3_from_ctx(ctx)
    mixer_desc = load_mixer_description_from_ctx(ctx)
    mixer_instance = mixer_desc.mixer.instantiate(web3)
    return MixerClient.open(web3, ctx.prover_server_endpoint, mixer_instance)
示例#5
0
def create_zeth_client_and_mixer_desc(
        ctx: ClientConfig) -> Tuple[MixerClient, MixerDescription]:
    """
    Create a MixerClient and MixerDescription object, for an existing deployment.
    """
    web3 = open_web3_from_ctx(ctx)
    mixer_desc = load_mixer_description_from_ctx(ctx)
    mixer_instance = mixer_desc.mixer.instantiate(web3)
    zeth_client = MixerClient.open(
        web3, ctx.prover_server_endpoint, mixer_instance)
    return (zeth_client, mixer_desc)
示例#6
0
def deploy(ctx: Context, eth_addr: Optional[str], instance_out: str,
           token_address: str, deploy_gas: str) -> None:
    """
    Deploy the zeth contracts and record the instantiation details.
    """
    eth_address = load_eth_address(eth_addr)
    client_ctx = ctx.obj
    web3 = open_web3_from_ctx(client_ctx)
    deploy_gas_value = EtherValue(deploy_gas, 'wei') if deploy_gas else None

    print(f"deploy: eth_address={eth_address}")
    print(f"deploy: instance_out={instance_out}")
    print(f"deploy: token_address={token_address}")

    token_instance_desc = get_erc20_instance_description(token_address) \
        if token_address else None

    _zeth_client, mixer_instance_desc = MixerClient.deploy(
        web3, client_ctx.prover_server_endpoint, eth_address, token_address,
        deploy_gas_value)

    mixer_desc = MixerDescription(mixer_instance_desc, token_instance_desc)
    write_mixer_description(instance_out, mixer_desc)
示例#7
0
def create_zeth_client_and_mixer_desc(
        prover_server_endpoint: str, mixer_addr: str, username: str,
        password: str) -> Tuple[MixerClient, MixerDescription]:
    """
    Create a MixerClient and MixerDescription object, for an existing deployment.
    """
    #web3 = open_web3_from_ctx(ctx)
    #mixer_desc = load_mixer_description_from_ctx(ctx)
    mixer_instance = Groth16Mixer(mixer_addr)
    keystore_file = "{}/{}/{}".format(USER_DIR, username, FISCO_ADDRESS_FILE)
    if exists(keystore_file) is False:
        raise ClickException(f"invalid output spec: {keystore_file}")
    with open(keystore_file, "r") as dump_f:
        keytext = json.load(dump_f)
        privkey = Account.decrypt(keytext, password)
        mixer_instance.client.ecdsa_account = Account.from_key(privkey)
        keypair = BcosKeyPair()
        keypair.private_key = mixer_instance.client.ecdsa_account.privateKey
        keypair.public_key = mixer_instance.client.ecdsa_account.publickey
        keypair.address = mixer_instance.client.ecdsa_account.address
        mixer_instance.client.keypair = keypair
    zeth_client = MixerClient.open(prover_server_endpoint, mixer_instance)
    return (zeth_client)
def main() -> None:
    print("-------------------- Evaluating BaseMixer.sol --------------------")

    web3, eth = mock.open_test_web3()

    # Ethereum addresses
    deployer_eth_address = eth.accounts[0]

    zeth_client, _ = MixerClient.deploy(
        web3, mock.TEST_PROVER_SERVER_ENDPOINT, deployer_eth_address)

    mixer_instance = zeth_client.mixer_instance

    # We can now call the instance and test its functions.
    print("[INFO] 4. Running tests")
    result = 0
    result += test_assemble_commitments(mixer_instance)
    result += test_assemble_nullifiers(mixer_instance)
    result += test_assemble_vpub(mixer_instance)
    result += test_assemble_hsig(mixer_instance)
    # We do not re-assemble of h_is in the contract

    if result == 0:
        print("base_mixer tests PASS\n")
示例#9
0
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")
示例#10
0
def charlie_corrupt_bob_deposit(
        zeth_client: MixerClient, mk_tree: MerkleTree, bob_eth_address: str,
        charlie_eth_address: str,
        keystore: mock.KeyStore) -> contracts.MixResult:
    """
    Charlie tries to break transaction malleability and corrupt the coins
    bob is sending in a transaction
    She does so by intercepting bob's transaction and either:
    - case 1: replacing the ciphertexts (or sender_eph_pk) by garbage/arbitrary
      data
    - case 2: replacing the ciphertexts by garbage/arbitrary data and using a
      new OT-signature
    - case 3: Charlie replays the mix call of Bob, to try to receive the vout
    Both attacks should fail,
    - case 1: the signature check should fail, else Charlie broke UF-CMA of the
      OT signature
    - case 2: the h_sig/vk verification should fail, as h_sig is not a function
      of vk any longer
    - case 3: the signature check should fail, because `msg.sender` will no match
      the value used in the mix parameters (Bob's Ethereum Address).
    NB. If the adversary were to corrupt the ciphertexts (or the encryption key),
    replace the OT-signature by a new one and modify the h_sig accordingly so that
    the check on the signature verification (key h_sig/vk) passes, the proof would
    not verify, which is why we do not test this case.
    """
    print(
        f"=== Bob deposits {BOB_DEPOSIT_ETH} ETH for himself and split into " +
        f"note1: {BOB_SPLIT_1_ETH}ETH, note2: {BOB_SPLIT_2_ETH}ETH" +
        f"but Charlie attempts to corrupt the transaction ===")
    bob_apk = keystore["Bob"].addr_pk.a_pk
    bob_ask = keystore["Bob"].addr_sk.a_sk
    tree_depth = mk_tree.depth
    mk_root = mk_tree.get_root()
    # mk_tree_depth = zeth_client.mk_tree_depth
    # mk_root = zeth_client.merkle_root

    # Create the JoinSplit dummy inputs for the deposit
    input1 = get_dummy_input_and_address(bob_apk)
    input2 = get_dummy_input_and_address(bob_apk)
    dummy_mk_path = mock.get_dummy_merkle_path(tree_depth)

    note1_value = to_zeth_units(EtherValue(BOB_SPLIT_1_ETH))
    note2_value = to_zeth_units(EtherValue(BOB_SPLIT_2_ETH))

    v_in = to_zeth_units(EtherValue(BOB_DEPOSIT_ETH))

    (output_note1, output_note2, proof_json, joinsplit_keypair) = \
        zeth_client.get_proof_joinsplit_2_by_2(
            mk_root,
            input1,
            dummy_mk_path,
            input2,
            dummy_mk_path,
            bob_ask,  # sender
            (bob_apk, note1_value),  # recipient1
            (bob_apk, note2_value),  # recipient2
            v_in,  # v_in
            to_zeth_units(EtherValue(0))  # v_out
        )

    # Encrypt the coins to bob
    pk_bob = keystore["Bob"].addr_pk.k_pk
    ciphertexts = encrypt_notes([(output_note1, pk_bob),
                                 (output_note2, pk_bob)])

    # ### ATTACK BLOCK
    # Charlie intercepts Bob's deposit, corrupts it and
    # sends her transaction before Bob's transaction is accepted

    # Case 1: replacing the ciphertexts by garbage/arbitrary data
    # Corrupt the ciphertexts
    # (another way would have been to overwrite sender_eph_pk)
    fake_ciphertext0 = urandom(32)
    fake_ciphertext1 = urandom(32)

    result_corrupt1 = None
    try:
        joinsplit_sig_charlie = joinsplit_sign(joinsplit_keypair,
                                               charlie_eth_address,
                                               ciphertexts, proof_json)

        mix_params = contracts.MixParameters(
            proof_json, joinsplit_keypair.vk, joinsplit_sig_charlie,
            [fake_ciphertext0, fake_ciphertext1])
        tx_hash = zeth_client.mix(mix_params, charlie_eth_address,
                                  Web3.toWei(BOB_DEPOSIT_ETH, 'ether'),
                                  DEFAULT_MIX_GAS_WEI)
        result_corrupt1 = \
            wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash)
    except Exception as e:
        print(f"Charlie's first corruption attempt" +
              f" successfully rejected! (msg: {e})")
    assert(result_corrupt1 is None), \
        "Charlie managed to corrupt Bob's deposit the first time!"
    print("")

    # Case 2: replacing the ciphertexts by garbage/arbitrary data and
    # using a new OT-signature
    # Corrupt the ciphertexts
    fake_ciphertext0 = urandom(32)
    fake_ciphertext1 = urandom(32)
    new_joinsplit_keypair = signing.gen_signing_keypair()

    # Sign the primary inputs, sender_eph_pk and the ciphertexts

    result_corrupt2 = None
    try:
        joinsplit_sig_charlie = joinsplit_sign(
            new_joinsplit_keypair, charlie_eth_address,
            [fake_ciphertext0, fake_ciphertext1], proof_json)
        mix_params = contracts.MixParameters(
            proof_json, new_joinsplit_keypair.vk, joinsplit_sig_charlie,
            [fake_ciphertext0, fake_ciphertext1])
        tx_hash = zeth_client.mix(mix_params, charlie_eth_address,
                                  Web3.toWei(BOB_DEPOSIT_ETH, 'ether'),
                                  DEFAULT_MIX_GAS_WEI)
        result_corrupt2 = \
            wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash)
    except Exception as e:
        print(f"Charlie's second corruption attempt" +
              f" successfully rejected! (msg: {e})")
    assert(result_corrupt2 is None), \
        "Charlie managed to corrupt Bob's deposit the second time!"

    # Case3: Charlie uses the correct mix data, but attempts to send the mix
    # call from his own address (thereby receiving the output).
    result_corrupt3 = None
    try:
        joinsplit_sig_bob = joinsplit_sign(joinsplit_keypair, bob_eth_address,
                                           ciphertexts, proof_json)
        mix_params = contracts.MixParameters(proof_json, joinsplit_keypair.vk,
                                             joinsplit_sig_bob, ciphertexts)
        tx_hash = zeth_client.mix(mix_params, charlie_eth_address,
                                  Web3.toWei(BOB_DEPOSIT_ETH, 'ether'),
                                  4000000)
        result_corrupt3 = \
            wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash)
    except Exception as e:
        print(f"Charlie's third corruption attempt" +
              f" successfully rejected! (msg: {e})")
    assert(result_corrupt3 is None), \
        "Charlie managed to corrupt Bob's deposit the third time!"
    # ### ATTACK BLOCK

    # Bob transaction is finally mined
    joinsplit_sig_bob = joinsplit_sign(joinsplit_keypair, bob_eth_address,
                                       ciphertexts, proof_json)
    mix_params = contracts.MixParameters(proof_json, joinsplit_keypair.vk,
                                         joinsplit_sig_bob, ciphertexts)
    tx_hash = zeth_client.mix(mix_params, bob_eth_address,
                              Web3.toWei(BOB_DEPOSIT_ETH, 'ether'),
                              DEFAULT_MIX_GAS_WEI)
    return wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash)
示例#11
0
def charlie_double_withdraw(zeth_client: MixerClient, mk_tree: MerkleTree,
                            input1: Tuple[int,
                                          ZethNote], charlie_eth_address: str,
                            keystore: mock.KeyStore) -> contracts.MixResult:
    """
    Charlie tries to carry out a double spending by modifying the value of the
    nullifier of the previous payment
    """
    print(
        f" === Charlie attempts to withdraw {CHARLIE_WITHDRAW_ETH}ETH once " +
        "more (double spend) one of his note on the Mixer ===")

    charlie_apk = keystore["Charlie"].addr_pk.a_pk
    charlie_ask = keystore["Charlie"].addr_sk.a_sk

    tree_depth = mk_tree.depth
    mk_path1 = compute_merkle_path(input1[0], mk_tree)
    mk_root = mk_tree.get_root()

    # Create the an additional dummy input for the MixerClient
    input2 = get_dummy_input_and_address(charlie_apk)
    dummy_mk_path = mock.get_dummy_merkle_path(tree_depth)

    note1_value = to_zeth_units(EtherValue(CHARLIE_WITHDRAW_CHANGE_ETH))
    v_out = EtherValue(CHARLIE_WITHDRAW_ETH)

    # ### ATTACK BLOCK
    # Add malicious nullifiers: we reuse old nullifiers to double spend by
    # adding $r$ to them so that they have the same value as before in Z_r,
    # and so the zksnark verification passes, but have different values in
    # {0;1}^256 so that they appear different to the contract.
    # See: https://github.com/clearmatics/zeth/issues/38

    attack_primary_input3: int = 0
    attack_primary_input4: int = 0

    def compute_h_sig_attack_nf(nf0: bytes, nf1: bytes,
                                sign_vk: JoinsplitSigVerificationKey) -> bytes:
        # We disassemble the nfs to get the formatting of the primary inputs
        input_nullifier0 = nf0.hex()
        input_nullifier1 = nf1.hex()
        nf0_rev = "{0:0256b}".format(int(input_nullifier0, 16))
        primary_input3_bits = nf0_rev[:FIELD_CAPACITY]
        primary_input3_res_bits = nf0_rev[FIELD_CAPACITY:]
        nf1_rev = "{0:0256b}".format(int(input_nullifier1, 16))
        primary_input4_bits = nf1_rev[:FIELD_CAPACITY]
        primary_input4_res_bits = nf1_rev[FIELD_CAPACITY:]

        # We perform the attack, recoding the modified public input values
        nonlocal attack_primary_input3
        nonlocal attack_primary_input4
        attack_primary_input3 = int(primary_input3_bits, 2) + ZETH_PRIME
        attack_primary_input4 = int(primary_input4_bits, 2) + ZETH_PRIME

        # We reassemble the nfs
        attack_primary_input3_bits = "{0:0256b}".format(attack_primary_input3)
        attack_nf0_bits = attack_primary_input3_bits[
            len(attack_primary_input3_bits) - FIELD_CAPACITY:] +\
            primary_input3_res_bits
        attack_nf0 = "{0:064x}".format(int(attack_nf0_bits, 2))
        attack_primary_input4_bits = "{0:0256b}".format(attack_primary_input4)
        attack_nf1_bits = attack_primary_input4_bits[
            len(attack_primary_input4_bits) - FIELD_CAPACITY:] +\
            primary_input4_res_bits
        attack_nf1 = "{0:064x}".format(int(attack_nf1_bits, 2))
        return compute_h_sig(bytes.fromhex(attack_nf0),
                             bytes.fromhex(attack_nf1), sign_vk)

    (output_note1, output_note2, proof_json, signing_keypair) = \
        zeth_client.get_proof_joinsplit_2_by_2(
            mk_root,
            input1,
            mk_path1,
            input2,
            dummy_mk_path,
            charlie_ask,  # sender
            (charlie_apk, note1_value),  # recipient1
            (charlie_apk, 0),  # recipient2
            to_zeth_units(EtherValue(0)),  # v_in
            to_zeth_units(v_out),  # v_out
            compute_h_sig_attack_nf)

    # Update the primary inputs to the modified nullifiers, since libsnark
    # overwrites them with values in Z_p

    assert attack_primary_input3 != 0
    assert attack_primary_input4 != 0

    print("proof_json => ", proof_json)
    print("proof_json[inputs][3] => ", proof_json["inputs"][3])
    print("proof_json[inputs][4] => ", proof_json["inputs"][4])
    proof_json["inputs"][3] = hex(attack_primary_input3)
    proof_json["inputs"][4] = hex(attack_primary_input4)
    # ### ATTACK BLOCK

    # construct pk object from bytes
    pk_charlie = keystore["Charlie"].addr_pk.k_pk

    # encrypt the coins
    ciphertexts = encrypt_notes([(output_note1, pk_charlie),
                                 (output_note2, pk_charlie)])

    # Compute the joinSplit signature
    joinsplit_sig_charlie = joinsplit_sign(signing_keypair,
                                           charlie_eth_address, ciphertexts,
                                           proof_json)

    mix_params = contracts.MixParameters(proof_json, signing_keypair.vk,
                                         joinsplit_sig_charlie, ciphertexts)

    tx_hash = zeth_client.mix(
        mix_params,
        charlie_eth_address,
        # Pay an arbitrary amount (1 wei here) that will be refunded since the
        # `mix` function is payable
        Web3.toWei(1, 'wei'),
        DEFAULT_MIX_GAS_WEI)
    return wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash)
示例#12
0
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")