Ejemplo n.º 1
0
def mix(ctx: Context, vin: str, vout: str, input_notes: List[str],
        output_specs: List[str], eth_addr: Optional[str],
        eth_private_key: Optional[str], wait: bool,
        show_parameters: bool) -> None:
    """
    Generic mix function
    """
    # Some sanity checks
    if len(input_notes) > JS_INPUTS:
        raise ClickException(f"too many inputs (max {JS_INPUTS})")
    if len(output_specs) > JS_OUTPUTS:
        raise ClickException(f"too many outputs (max {JS_OUTPUTS})")

    print(f"vin = {vin}")
    print(f"vout = {vout}")

    vin_pub = EtherValue(vin)
    vout_pub = EtherValue(vout)
    client_ctx = ctx.obj
    zeth_client, mixer_desc = create_zeth_client_and_mixer_desc(client_ctx)
    zeth_address = load_zeth_address(client_ctx)
    wallet = open_wallet(zeth_client.mixer_instance, zeth_address.addr_sk,
                         client_ctx)

    inputs: List[Tuple[int, ZethNote]] = [
        wallet.find_note(note_id).as_input() for note_id in input_notes
    ]
    outputs: List[Tuple[ZethAddressPub, EtherValue]] = [
        parse_output(out_spec) for out_spec in output_specs
    ]

    # Compute input and output value total and check that they match
    input_note_sum = from_zeth_units(
        sum([int(note.value, 16) for _, note in inputs]))
    output_note_sum = sum([value for _, value in outputs], EtherValue(0))
    if vin_pub + input_note_sum != vout_pub + output_note_sum:
        raise ClickException("input and output value mismatch")

    eth_address = load_eth_address(eth_addr)
    eth_private_key_data = load_eth_private_key(eth_private_key)

    # If instance uses an ERC20 token, tx_value can be 0. Otherwise it should
    # match vin_pub.
    tx_value = EtherValue(0) if mixer_desc.token else vin_pub

    mix_params = zeth_client.create_mix_parameters(
        wallet.merkle_tree, zeth_address.ownership_keypair(), eth_address,
        inputs, outputs, vin_pub, vout_pub)

    if show_parameters:
        print(f"mix_params={mix_params.to_json()}")

    tx_hash = zeth_client.mix(mix_params=mix_params,
                              sender_eth_address=eth_address,
                              sender_eth_private_key=eth_private_key_data,
                              tx_value=tx_value)

    print(tx_hash)
    if wait:
        do_sync(zeth_client.web3, wallet, tx_hash, zeth_note_short_print)
Ejemplo n.º 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)

    tx_hash = zeth_client.joinsplit(
        mk_tree,
        charlie_ownership_key,
        charlie_eth_address,
        None,
        [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, tx_hash)
Ejemplo n.º 3
0
    def test_arithmetic(self) -> None:
        aval = EtherValue(1.2)
        bval = EtherValue(0.8)
        cval = EtherValue(0.4)

        self.assertEqual(aval, bval + cval)
        self.assertEqual(bval, aval - cval)
Ejemplo n.º 4
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)),
    ]

    tx_hash = zeth_client.deposit(
        mk_tree,
        bob_js_keypair,
        bob_eth_address,
        None,
        EtherValue(BOB_DEPOSIT_ETH),
        outputs,
        tx_value)
    return wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash)
Ejemplo n.º 5
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
    tx_hash = zeth_client.joinsplit(
        mk_tree,
        OwnershipKeyPair(bob_ask, bob_addr.a_pk),
        bob_eth_address,
        None,
        [input1],
        [output0, output1],
        EtherValue(0),
        EtherValue(0),
        EtherValue(1, 'wei'))
    return wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash)
Ejemplo n.º 6
0
    def test_bool(self) -> None:
        zero = EtherValue(0)
        self.assertFalse(zero)
        self.assertTrue(not zero)

        non_zero = EtherValue(0.1)
        self.assertTrue(non_zero)
        self.assertFalse(not non_zero)
Ejemplo n.º 7
0
    def test_equality(self) -> None:
        aval = EtherValue(1.2)
        aval_same = EtherValue(1.2)
        bval = EtherValue(0.8)

        self.assertEqual(aval, aval)
        self.assertEqual(aval, aval_same)
        self.assertNotEqual(aval, bval)
Ejemplo n.º 8
0
def ls_notes(ctx: Context, balance: bool, spent: bool) -> None:
    """
    List the set of notes owned by this wallet
    """
    client_ctx = ctx.obj
    web3 = open_web3_from_ctx(client_ctx)
    mixer_desc = load_mixer_description_from_ctx(client_ctx)
    mixer_instance = mixer_desc.mixer.instantiate(web3)
    js_secret = load_zeth_address_secret(client_ctx)
    wallet = open_wallet(mixer_instance, js_secret, client_ctx)

    total = EtherValue(0)
    for addr, short_commit, value in wallet.note_summaries():
        print(f"{short_commit}: value={value.ether()}, addr={addr}")
        total = total + value

    if balance:
        print(f"TOTAL BALANCE: {total.ether()}")

    if not spent:
        return

    print("SPENT NOTES:")
    for addr, short_commit, value in wallet.spent_note_summaries():
        print(f"{short_commit}: value={value.ether()}, addr={addr}")
Ejemplo n.º 9
0
    def test_comparison(self) -> None:
        big = EtherValue(1.2)
        small = EtherValue(0.8)
        small_same = EtherValue(0.8)

        self.assertTrue(small < big)
        self.assertTrue(small <= big)
        self.assertTrue(big > small)
        self.assertTrue(big >= small)
        self.assertTrue(small_same >= small)
        self.assertTrue(small_same <= small)

        self.assertFalse(small > big)
        self.assertFalse(small >= big)
        self.assertFalse(big < small)
        self.assertFalse(big <= small)
        self.assertFalse(small_same > small)
        self.assertFalse(small_same < small)
Ejemplo n.º 10
0
def deposit(
        ctx: Context,
        value: str,
        eth_addr: Optional[str],
        eth_private_key: Optional[str],
        wait: bool,
        show_parameters: bool) -> None:
    """
    Deposit function
    """

    value_pub = EtherValue(value)
    client_ctx = ctx.obj
    prover_client = create_prover_client(client_ctx)
    zklay_client, zklay_desc = create_zklay_client_and_zklay_desc(
        client_ctx, prover_client)

    zklay_address = load_zklay_address(client_ctx)
    wallet = open_zklay_wallet(
        zklay_client.mixer_instance, zklay_address.addr_sk, client_ctx)

    eth_address = load_eth_address(eth_addr)
    eth_private_key_data = load_eth_private_key(eth_private_key)

    # If instance uses an ERC20 token, tx_value can be 0. Otherwise it should
    # match vin_pub.
    tx_value = EtherValue(0) if zklay_desc.token else value_pub


    # Create the MixParameters object manually so they can be displayed.
    deposit_params = zklay_client.create_deposit(prover_client, eth_address, eth_private_key_data, zklay_address, value_pub)  

    if show_parameters:
        print(f"deposit_params={deposit_params.to_json()}")

    tx_hash = zklay_client.zklay_deposit(
        deposit_params=deposit_params,
        sender_eth_address=eth_address,
        sender_eth_private_key=eth_private_key_data,
        tx_value=tx_value)

    print(tx_hash)
    if wait:
        pp = prover_client.get_configuration().pairing_parameters
Ejemplo n.º 11
0
 def _from_json_dict(zksnark: IZKSnarkProvider,
                     json_dict: Dict[str, Any]) -> DepositParameters:
     ext_proof = ExtendedProof.from_json_dict(zksnark,
                                              json_dict["extended_proof"])
     zklay_address = decode_encryption_public_key(
         json_dict["zklay_address"])
     eth_amount = EtherValue(json_dict["eth_amount"])
     ciphertexts = [bytes.fromhex(x) for x in json_dict["ciphertexts"]]
     return DepositParameters(ext_proof, zklay_address, eth_amount,
                              ciphertexts)
Ejemplo n.º 12
0
def eth_get_balance(ctx: Any, eth_addr: Optional[str], wei: bool) -> None:
    """
    Command to get the balance of specific addresses. Support multiple queries
    per invocation (outputs one per line), for efficiency.
    """
    eth_addr = load_eth_address(eth_addr)
    web3 = open_web3_from_network(get_eth_network(ctx.obj["eth_network"]))
    balance_wei = web3.eth.getBalance(eth_addr)  # pylint: disable=no-member
    if wei:
        print(balance_wei)
    else:
        print(EtherValue(balance_wei, "wei").ether())
Ejemplo n.º 13
0
def deploy_test_token(eth_network: Optional[str], eth_addr: Optional[str],
                      eth_private_key: Optional[str], mint_amount: int,
                      recipient_address: str) -> None:
    """
    Deploy a simple ERC20 token for testing, and mint some for a specific
    address. Print the token address.
    """
    eth_addr = load_eth_address(eth_addr)
    eth_private_key_data = load_eth_private_key(eth_private_key)
    recipient_address = load_eth_address(recipient_address)
    web3 = open_web3_from_network(get_eth_network(eth_network))
    token_instance = deploy_token(
        web3, eth_addr, eth_private_key_data, 4000000) \
        # pylint: disable=no-member

    mint_tx_hash = mint_token(web3, token_instance, recipient_address,
                              eth_addr, eth_private_key_data,
                              EtherValue(mint_amount, 'ether'))
    web3.eth.waitForTransactionReceipt(mint_tx_hash)
    print(token_instance.address)
Ejemplo n.º 14
0
 def deposit(self,
             mk_tree: MerkleTree,
             zeth_address: ZethAddress,
             sender_eth_address: str,
             sender_eth_private_key: Optional[bytes],
             eth_amount: EtherValue,
             outputs: Optional[List[Tuple[ZethAddressPub,
                                          EtherValue]]] = None,
             tx_value: Optional[EtherValue] = None) -> str:
     if not outputs or len(outputs) == 0:
         outputs = [(zeth_address.addr_pk, eth_amount)]
     return self.joinsplit(
         mk_tree,
         sender_ownership_keypair=zeth_address.ownership_keypair(),
         sender_eth_address=sender_eth_address,
         sender_eth_private_key=sender_eth_private_key,
         inputs=[],
         outputs=outputs,
         v_in=eth_amount,
         v_out=EtherValue(0),
         tx_value=tx_value)
Ejemplo n.º 15
0
def parse_output(output_str: str) -> Tuple[ZethAddressPub, EtherValue]:
    """
    Parse a string of the form "<receiver_pub_address>,<value>" to an output
    specification. <receiver_pub_address> can be a file name containing the
    address. "<value>" is interpretted as the <default-address-file>,<value>.
    """
    parts = output_str.split(",")
    if len(parts) == 1:
        addr = ZETH_PUBLIC_ADDRESS_FILE_DEFAULT
        value = parts[0]
    elif len(parts) == 2:
        addr = parts[0]
        value = parts[1]
    else:
        raise ClickException(f"invalid output spec: {output_str}")

    if exists(addr):
        with open(addr, "r") as addr_f:
            addr = addr_f.read()

    return (ZethAddressPub.parse(addr), EtherValue(value))
Ejemplo n.º 16
0
    def __init__(self,
                 mk_tree: MerkleTree,
                 sender_ownership_keypair: OwnershipKeyPair,
                 inputs: List[Tuple[int, api.ZethNote]],
                 outputs: List[Tuple[ZethAddressPub, EtherValue]],
                 v_in: EtherValue,
                 v_out: EtherValue,
                 compute_h_sig_cb: Optional[ComputeHSigCB] = None):
        assert len(inputs) <= constants.JS_INPUTS
        assert len(outputs) <= constants.JS_OUTPUTS

        self.mk_tree = mk_tree
        self.sender_ownership_keypair = sender_ownership_keypair
        self.v_in = v_in
        self.v_out = v_out
        self.compute_h_sig_cb = compute_h_sig_cb

        # Perform some cleaning and minimal pre-processing of the data. Compute
        # and store data that is not derivable from the ProverInput or Proof
        # structs (such as the encryption keys for receivers), making it
        # available to MixerClient calls.

        # Expand inputs with dummy entries and compute merkle paths.
        sender_a_pk = sender_ownership_keypair.a_pk
        self.inputs = \
            inputs + \
            [get_dummy_input_and_address(sender_a_pk)
             for _ in range(constants.JS_INPUTS - len(inputs))]

        # Pad the list of outputs if necessary
        if len(outputs) < constants.JS_OUTPUTS:
            dummy_k_pk = generate_encryption_keypair().k_pk
            dummy_addr_pk = ZethAddressPub(sender_a_pk, dummy_k_pk)
            self.outputs = \
                outputs + \
                [(dummy_addr_pk, EtherValue(0))
                 for _ in range(constants.JS_OUTPUTS - len(outputs))]
        else:
            self.outputs = outputs
Ejemplo n.º 17
0
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()

    # 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(web3, deployer_eth_address, None, 4000000)

    # 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 = constants.ZETH_MERKLE_TREE_DEPTH
    zeth_client, _contract_desc = MixerClient.deploy(web3, prover_client,
                                                     deployer_eth_address,
                                                     None,
                                                     token_instance.address,
                                                     None)
    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 + "-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, 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: ERC20 token)...")
    # We assign ETHToken to Bob
    mint_token(web3, token_instance, bob_eth_address, deployer_eth_address,
               None, EtherValue(2 * scenario.BOB_DEPOSIT_ETH, 'ether'))
    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, prover_client, mk_tree, bob_eth_address, keystore,
            zeth.core.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,
                                                     prover_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, prover_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, prover_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, prover_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, prover_client, zksnark, 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, 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)
    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")
Ejemplo n.º 18
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 " +
        "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,
            None,
            EtherValue(BOB_DEPOSIT_ETH))
        result_corrupt1 = \
            wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash)
    except Exception as e:
        print(
            "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,
            None,
            EtherValue(BOB_DEPOSIT_ETH))
        result_corrupt2 = \
            wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash)
    except Exception as e:
        print(
            "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,
            None,
            EtherValue(BOB_DEPOSIT_ETH),
            4000000)
        result_corrupt3 = \
            wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash)
    except Exception as e:
        print(
            "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,
        None,
        EtherValue(BOB_DEPOSIT_ETH))
    return wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash)
Ejemplo n.º 19
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
        None,
        EtherValue(1, 'wei'))
    return wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash)
Ejemplo n.º 20
0
    def create_mix_parameters_keep_signing_key(
        self,
        mk_tree: MerkleTree,
        sender_ownership_keypair: OwnershipKeyPair,
        sender_eth_address: str,
        inputs: List[Tuple[int, ZethNote]],
        outputs: List[Tuple[ZethAddressPub, EtherValue]],
        v_in: EtherValue,
        v_out: EtherValue,
        compute_h_sig_cb: Optional[ComputeHSigCB] = None
    ) -> Tuple[contracts.MixParameters, JoinsplitSigKeyPair]:
        assert len(inputs) <= constants.JS_INPUTS
        assert len(outputs) <= constants.JS_OUTPUTS

        sender_a_sk = sender_ownership_keypair.a_sk
        sender_a_pk = sender_ownership_keypair.a_pk
        inputs = \
            inputs + \
            [get_dummy_input_and_address(sender_a_pk)
             for _ in range(constants.JS_INPUTS - len(inputs))]
        mk_root = mk_tree.get_root()
        mk_paths = [compute_merkle_path(addr, mk_tree) for addr, _ in inputs]

        # Generate output notes and proof.  Dummy outputs are constructed with
        # value 0 to an invalid ZethAddressPub, formed from the senders
        # a_pk, and an ephemeral k_pk.
        dummy_k_pk = generate_encryption_keypair().k_pk
        dummy_addr_pk = ZethAddressPub(sender_a_pk, dummy_k_pk)
        outputs = \
            outputs + \
            [(dummy_addr_pk, EtherValue(0))
             for _ in range(constants.JS_OUTPUTS - len(outputs))]
        outputs_with_a_pk = \
            [(zeth_addr.a_pk, to_zeth_units(value))
             for (zeth_addr, value) in outputs]

        # Timer used to time proof-generation round trip time.
        timer = Timer.started()

        (output_note1, output_note2, extproof, signing_keypair) = \
            self.get_proof_joinsplit_2_by_2(
                mk_root,
                inputs[0],
                mk_paths[0],
                inputs[1],
                mk_paths[1],
                sender_a_sk,
                outputs_with_a_pk[0],
                outputs_with_a_pk[1],
                to_zeth_units(v_in),
                to_zeth_units(v_out),
                compute_h_sig_cb)

        proof_gen_time_s = timer.elapsed_seconds()
        print(f"PROOF GEN ROUND TRIP: {proof_gen_time_s} seconds")

        # Encrypt the notes
        outputs_and_notes = zip(outputs, [output_note1, output_note2])
        output_notes_with_k_pk = \
            [(note, zeth_addr.k_pk)
             for ((zeth_addr, _), note) in outputs_and_notes]
        ciphertexts = encrypt_notes(output_notes_with_k_pk)

        # Sign
        signature = joinsplit_sign(signing_keypair, sender_eth_address,
                                   ciphertexts, extproof)

        mix_params = contracts.MixParameters(extproof, signing_keypair.vk,
                                             signature, ciphertexts)
        return mix_params, signing_keypair
Ejemplo n.º 21
0
 def _decode_basename(filename: str) -> Tuple[int, str, EtherValue]:
     components = filename.split("_")
     addr = int(components[2])
     short_commit = components[3]
     value = EtherValue(components[4], 'ether')
     return (addr, short_commit, value)
Ejemplo n.º 22
0
 def test_conversion(self) -> None:
     aval = EtherValue(75641320, 'wei')
     aval_eth = aval.ether()
     bval = EtherValue(aval_eth, 'ether')
     self.assertEqual(aval.wei, bval.wei)
     self.assertEqual(aval.ether(), bval.ether())
Ejemplo n.º 23
0
def charlie_double_withdraw(
        zeth_client: MixerClient,
        prover_client: ProverClient,
        zksnark: IZKSnarkProvider,
        mk_tree: MerkleTree,
        input1: Tuple[int, ZethNote],
        charlie_eth_address: str,
        keystore: mock.KeyStore) -> MixResult:
    """
    Charlie tries to carry out a double spending by modifying the value of the
    nullifier of the previous payment
    """
    pp = zeth_client.prover_config.pairing_parameters
    scalar_field_mod = pp.scalar_field_mod()
    scalar_field_capacity = pp.scalar_field_capacity

    print(
        f" === Charlie attempts to withdraw {CHARLIE_WITHDRAW_ETH}ETH once " +
        "more (double spend) one of his note on the Mixer ===")

    charlie_addr = keystore["Charlie"]
    charlie_apk = charlie_addr.addr_pk.a_pk

    # Create the an additional dummy input for the MixerClient
    input2 = get_dummy_input_and_address(charlie_apk)

    note1_value = 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(
            nfs: List[bytes],
            sign_vk: JoinsplitSigVerificationKey) -> bytes:
        # We disassemble the nfs to get the formatting of the primary inputs
        assert len(nfs) == 2
        nf0 = nfs[0]
        nf1 = nfs[1]
        input_nullifier0 = nf0.hex()
        input_nullifier1 = nf1.hex()
        nf0_rev = "{0:0256b}".format(int(input_nullifier0, 16))
        primary_input3_bits = nf0_rev[:scalar_field_capacity]
        primary_input3_res_bits = nf0_rev[scalar_field_capacity:]
        nf1_rev = "{0:0256b}".format(int(input_nullifier1, 16))
        primary_input4_bits = nf1_rev[:scalar_field_capacity]
        primary_input4_res_bits = nf1_rev[scalar_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) + scalar_field_mod
        attack_primary_input4 = int(primary_input4_bits, 2) + scalar_field_mod

        # 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) - scalar_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) - scalar_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, public_data, signing_keypair = \
        get_mix_parameters_components(
            zeth_client,
            prover_client,
            mk_tree,
            keystore["Charlie"].ownership_keypair(),  # sender
            [input1, input2],
            [(charlie_addr.addr_pk, note1_value),
             (charlie_addr.addr_pk, EtherValue(0))],
            EtherValue(0),
            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 = ", proof)
    print("public_data[3] = ", public_data[3])
    print("public_data[4] = ", public_data[4])
    public_data[3] = attack_primary_input3
    public_data[4] = 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(
        zksnark,
        pp,
        signing_keypair,
        charlie_eth_address,
        ciphertexts,
        proof,
        public_data)

    mix_params = MixParameters(
        proof,
        public_data,
        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
        None,
        EtherValue(1, 'wei'))
    return wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash)
Ejemplo n.º 24
0
def mix(
        ctx: Context,
        vin: str,
        vout: str,
        input_notes: List[str],
        output_specs: List[str],
        eth_addr: Optional[str],
        eth_private_key: Optional[str],
        wait: bool,
        for_dispatch_call: bool,
        dump_parameters: Optional[str],
        dump_signing_keypair: Optional[str],
        dry_run: bool) -> None:
    """
    Generic mix function
    """
    # Some sanity checks
    if len(input_notes) > JS_INPUTS:
        raise ClickException(f"too many inputs (max {JS_INPUTS})")
    if len(output_specs) > JS_OUTPUTS:
        raise ClickException(f"too many outputs (max {JS_OUTPUTS})")

    vin_pub = EtherValue(vin)
    vout_pub = EtherValue(vout)
    client_ctx = ctx.obj
    prover_client = create_prover_client(client_ctx)
    zeth_client, mixer_desc = create_mixer_client_and_mixer_desc(
        client_ctx, prover_client)
    zeth_address = load_zeth_address(client_ctx)
    wallet = open_wallet(
        zeth_client.mixer_instance, zeth_address.addr_sk, client_ctx)

    inputs: List[Tuple[int, ZethNote]] = [
        wallet.find_note(note_id).as_input() for note_id in input_notes]
    outputs: List[Tuple[ZethAddressPub, EtherValue]] = [
        parse_output(out_spec) for out_spec in output_specs]

    # Compute input and output value total and check that they match
    input_note_sum = from_zeth_units(
        sum([int(note.value, 16) for _, note in inputs]))
    output_note_sum = sum([value for _, value in outputs], EtherValue(0))
    if vin_pub + input_note_sum != vout_pub + output_note_sum:
        raise ClickException("input and output value mismatch")

    eth_address = load_eth_address(eth_addr)

    # If instance uses an ERC20 token, tx_value can be 0. Otherwise it should
    # match vin_pub.
    tx_value = EtherValue(0) if mixer_desc.token else vin_pub

    # Create the MixParameters object manually so they can be displayed.
    # TODO: support saving the generated MixParameters to be sent later.
    mix_params, signing_keypair = \
        zeth_client.create_mix_parameters_and_signing_key(
            prover_client,
            wallet.merkle_tree,
            zeth_address.ownership_keypair(),
            eth_address,
            inputs,
            outputs,
            vin_pub,
            vout_pub,
            for_dispatch_call=for_dispatch_call)

    # Dump parameters if requested
    if dump_parameters:
        if dump_parameters == '-':
            print(f"mix_params={mix_params.to_json()}")
        else:
            with open(dump_parameters, "w") as mix_params_f:
                json.dump(mix_params.to_json_dict(), mix_params_f)

    # Dump one-time signature keypair if requested
    if dump_signing_keypair:
        if dump_signing_keypair == '-':
            print(f"signing_key={signing_keypair.to_json_dict()}")
        else:
            with open(dump_signing_keypair, "w") as signing_keypair_f:
                json.dump(signing_keypair.to_json_dict(), signing_keypair_f)

    # Early-out if dry_run flag is set
    if for_dispatch_call or dry_run:
        return

    eth_private_key_data = load_eth_private_key(eth_private_key)
    tx_hash = zeth_client.mix(
        mix_params=mix_params,
        sender_eth_address=eth_address,
        sender_eth_private_key=eth_private_key_data,
        tx_value=tx_value)

    print(tx_hash)
    if wait:
        pp = prover_client.get_configuration().pairing_parameters
        do_sync(zeth_client.web3, wallet, pp, tx_hash, zeth_note_short_print)