Beispiel #1
0
    def test_mix_parameters(self) -> None:
        zksnark = Groth16()

        ext_proof = ExtendedProof(proof=Groth16.proof_from_json_dict({
            "a": ["1234", "2345"],
            "b": [["3456", "4567"], ["5678", "6789"]],
            "c": ["789a", "89ab"],
        }),
                                  inputs=[
                                      "9abc",
                                      "abcd",
                                      "bcde",
                                      "cdef",
                                  ])
        sig_keypair = gen_signing_keypair()
        sig_vk = sig_keypair.vk
        sig = sign(sig_keypair.sk, bytes.fromhex("00112233"))
        receiver_enc_keypair = generate_encryption_keypair()
        ciphertexts = [
            encrypt(token_bytes(NOTE_LENGTH_BYTES), receiver_enc_keypair.k_pk),
            encrypt(token_bytes(NOTE_LENGTH_BYTES), receiver_enc_keypair.k_pk),
        ]

        mix_params = MixParameters(ext_proof, sig_vk, sig, ciphertexts)

        mix_params_json = mix_params.to_json()
        mix_params_2 = MixParameters.from_json(zksnark, mix_params_json)

        self.assertEqual(mix_params.extended_proof.to_json_dict(),
                         mix_params_2.extended_proof.to_json_dict())
        self.assertEqual(encode_vk_to_bytes(mix_params.signature_vk),
                         encode_vk_to_bytes(mix_params_2.signature_vk))
        self.assertEqual(mix_params.signature, mix_params_2.signature)
        self.assertEqual(mix_params.ciphertexts, mix_params_2.ciphertexts)
Beispiel #2
0
def create_nested_tx(zeth_tx_file: str, prover_config_file: str,
                     output_file: str) -> None:
    """
    Create a Zecale nested transaction from a zeth MixParameters object
    """

    # Load prover config (which is assumed to already exist)
    with open(prover_config_file, "r") as prover_config_f:
        prover_config = \
            ProverConfiguration.from_json_dict(json.load(prover_config_f))
    zksnark = zksnark = get_zksnark_provider(prover_config.zksnark_name)

    # Read the MixParameters
    with open(zeth_tx_file, "r") as zeth_tx_f:
        zeth_mix_params = \
            MixParameters.from_json_dict(zksnark, json.load(zeth_tx_f))

    # Convert to a nested transaction, and write to output file
    nested_tx = _create_zeth_nested_tx(zeth_mix_params, 0)
    with open(output_file, "w") as output_f:
        json.dump(nested_tx.to_json_dict(), output_f)
Beispiel #3
0
def charlie_corrupt_bob_deposit(
        zeth_client: MixerClient,
        prover_client: ProverClient,
        zksnark: IZKSnarkProvider,
        mk_tree: MerkleTree,
        bob_eth_address: str,
        charlie_eth_address: str,
        keystore: mock.KeyStore) -> 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_addr_pk = keystore["Bob"]
    bob_apk = bob_addr_pk.addr_pk.a_pk

    # Get pairing parameters
    pp = prover_client.get_configuration().pairing_parameters

    # Create the JoinSplit dummy inputs for the deposit
    input1 = get_dummy_input_and_address(bob_apk)
    input2 = get_dummy_input_and_address(bob_apk)

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

    v_in = EtherValue(BOB_DEPOSIT_ETH)

    output_note1, output_note2, proof, public_data, joinsplit_keypair = \
        get_mix_parameters_components(
            zeth_client,
            prover_client,
            mk_tree,
            keystore["Bob"].ownership_keypair(),
            [input1, input2],
            [(bob_addr_pk.addr_pk, note1_value),
             (bob_addr_pk.addr_pk, note2_value)],
            v_in,
            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(
            zksnark,
            pp,
            joinsplit_keypair,
            charlie_eth_address,
            ciphertexts,
            proof,
            public_data)

        mix_params = MixParameters(
            proof,
            public_data,
            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(
            zksnark,
            pp,
            new_joinsplit_keypair,
            charlie_eth_address,
            [fake_ciphertext0, fake_ciphertext1],
            proof,
            public_data)
        mix_params = MixParameters(
            proof,
            public_data,
            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(
            zksnark,
            pp,
            joinsplit_keypair,
            bob_eth_address,
            ciphertexts,
            proof,
            public_data)
        mix_params = MixParameters(
            proof,
            public_data,
            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(
        zksnark,
        pp,
        joinsplit_keypair,
        bob_eth_address,
        ciphertexts,
        proof,
        public_data)
    mix_params = MixParameters(
        proof,
        public_data,
        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)
Beispiel #4
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)