Exemple #1
0
def wait_for_tx_update_mk_tree(zeth_client: MixerClient, mk_tree: MerkleTree,
                               tx_hash: str) -> MixResult:
    tx_receipt = zeth_client.web3.eth.waitForTransactionReceipt(tx_hash, 10000)
    result = parse_mix_call(zeth_client.mixer_instance, tx_receipt)
    for out_ev in result.output_events:
        mk_tree.insert(out_ev.commitment)

    if mk_tree.recompute_root() != result.new_merkle_root:
        raise Exception("Merkle root mismatch between log and local tree")
    return result
    def _check_merkle_path(self, address: int, mkpath: List[str],
                           mktree: MerkleTree) -> None:
        self.assertEqual(len(mkpath), mktree.depth)
        current = mktree.get_node(0, address)
        for i in range(mktree.depth):
            if address & 1:
                current = MerkleTree.combine(bytes.fromhex(mkpath[i]), current)
            else:
                current = MerkleTree.combine(current, bytes.fromhex(mkpath[i]))
            address = address >> 1

        self.assertEqual(mktree.get_root(), current)
    def test_empty(self) -> None:
        mktree = MerkleTree.empty_with_size(MERKLE_TREE_TEST_NUM_LEAVES)
        root = mktree.recompute_root()
        num_entries = mktree.get_num_entries()

        self.assertEqual(0, num_entries)
        self.assertEqual(self._expected_empty(), root)
    def _check_tree_nodes(self, leaves: List[bytes],
                          mktree: MerkleTree) -> None:
        def layer_size(layer: int) -> int:
            return int(MERKLE_TREE_TEST_NUM_LEAVES / pow(2, layer))

        # Check layer 0
        _, layer_0 = next(mktree.get_layers())
        self.assertEqual(leaves, layer_0)

        # Check layer `layer`
        for layer in range(1, MERKLE_TREE_TEST_DEPTH):
            for i in range(layer_size(layer)):
                self.assertEqual(
                    MerkleTree.combine(mktree.get_node(layer - 1, 2 * i),
                                       mktree.get_node(layer - 1, 2 * i + 1)),
                    mktree.get_node(layer, i), f"Layer {layer}, node {i}")
Exemple #5
0
 def _check_path_for_num_entries(num_entries: int,
                                 address: int) -> None:
     mktree = MerkleTree.empty_with_size(tree_size, MERKLE_TREE_HASH)
     for val in TEST_VALUES[0:num_entries]:
         mktree.insert(val)
     _ = mktree.recompute_root()
     mkpath = compute_merkle_path(address, mktree)
     self._check_merkle_path(address, mkpath, mktree)
    def test_single_entry_all_nodes(self) -> None:
        mktree = MerkleTree.empty_with_size(MERKLE_TREE_TEST_NUM_LEAVES)
        mktree.insert(TEST_VALUES[0])
        _ = mktree.recompute_root()
        self._check_tree_nodes([TEST_VALUES[0]], mktree)

        self.assertEqual(mktree.recompute_root(),
                         mktree.get_node(MERKLE_TREE_TEST_DEPTH, 0))
 def test_multiple_entries_all_nodes(self) -> None:
     mktree = MerkleTree.empty_with_size(MERKLE_TREE_TEST_NUM_LEAVES)
     mktree.insert(TEST_VALUES[0])
     mktree.insert(TEST_VALUES[1])
     mktree.insert(TEST_VALUES[2])
     _ = mktree.recompute_root()
     self._check_tree_nodes(
         [TEST_VALUES[0], TEST_VALUES[1], TEST_VALUES[2]], mktree)
    def test_combine(self) -> None:
        # Use test vectors used to test the MiMC contract (generated in
        # test_mimc.py)

        left = self._test_vector_to_bytes32(
            3703141493535563179657531719960160174296085208671919316200479060314459804651
        )  # noqa
        right = self._test_vector_to_bytes32(
            15683951496311901749339509118960676303290224812129752890706581988986633412003
        )  # noqa
        expect = self._test_vector_to_bytes32(
            16797922449555994684063104214233396200599693715764605878168345782964540311877
        )  # noqa

        result = MerkleTree.combine(left, right)
        self.assertEqual(expect, result)
    def _test_partial(num_entries: int, step: int = 1) -> None:
        """
        Take the first 'num_entries' from TEST_VALUES. Cut them at each possible
        place and submit them as two halves to the contract, receiving back the
        root for the updated tree.
        """
        leaves = TEST_VALUES[:num_entries]

        mktree = MerkleTree.empty_with_depth(ZETH_MERKLE_TREE_DEPTH)
        for leaf in leaves:
            mktree.insert(leaf)
        expected_root = mktree.recompute_root()

        for cut in range(0, num_entries + 1, step):
            print(f"_test_partial: num_entries={num_entries}, cut={cut}")
            first = leaves[:cut]
            second = leaves[cut:]
            root = contract.functions.testAddLeaves(first, second).call()
            assert_root(expected_root, root,
                        f"num_entries: {num_entries}, cut: {cut}: ")
    def test_single_entry(self) -> None:
        mktree_file = join(MERKLE_TREE_TEST_DIR, "single")
        data = TEST_VALUES[0]

        mktree = PersistentMerkleTree.open(mktree_file,
                                           MERKLE_TREE_TEST_NUM_LEAVES)
        mktree.insert(data)
        self.assertEqual(1, mktree.get_num_entries())
        self.assertEqual(data, mktree.get_leaf(0))
        self.assertEqual(ZERO_ENTRY, mktree.get_leaf(1))
        root_1 = mktree.recompute_root()
        self.assertEqual(MerkleTree.combine(data, ZERO_ENTRY),
                         mktree.get_node(1, 0))
        self.assertNotEqual(self._expected_empty(), root_1)
        mktree.save()

        mktree = PersistentMerkleTree.open(mktree_file,
                                           MERKLE_TREE_TEST_NUM_LEAVES)
        self.assertEqual(1, mktree.get_num_entries())
        self.assertEqual(data, mktree.get_leaf(0))
        self.assertEqual(ZERO_ENTRY, mktree.get_leaf(1))
        root_2 = mktree.recompute_root()
        self.assertEqual(root_1, root_2)
Exemple #11
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)
Exemple #12
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)
def test_tree_empty(contract: Any) -> None:
    mktree = MerkleTree.empty_with_depth(ZETH_MERKLE_TREE_DEPTH)
    expected_root = mktree.recompute_root()
    root = contract.functions.testAddLeaves([], []).call()
    assert_root(expected_root, root, "test_tree_empty")
Exemple #14
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