예제 #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)
예제 #2
0
    def create_prover_inputs(
        mix_call_desc: MixCallDescription
    ) -> Tuple[api.ProofInputs, signing.SigningKeyPair]:
        """
        Given the basic parameters for a mix call, compute the input to the prover
        server, and the signing key pair.
        """

        # Compute Merkle paths
        mk_tree = mix_call_desc.mk_tree
        sender_ask = mix_call_desc.sender_ownership_keypair.a_sk

        def _create_api_input(input_address: int,
                              input_note: api.ZethNote) -> api.JoinsplitInput:
            mk_path = compute_merkle_path(input_address, mk_tree)
            input_nullifier = compute_nullifier(input_note, sender_ask)
            return create_api_joinsplit_input(mk_path, input_address,
                                              input_note, sender_ask,
                                              input_nullifier)

        inputs = mix_call_desc.inputs
        api_inputs = [_create_api_input(addr, note) for addr, note in inputs]

        mk_root = mk_tree.get_root()

        # Extract (<ownership-address>, <value>) tuples
        outputs_with_a_pk = \
            [(zeth_addr.a_pk, to_zeth_units(value))
             for (zeth_addr, value) in mix_call_desc.outputs]

        # Public input and output values as Zeth units
        public_in_value_zeth_units = to_zeth_units(mix_call_desc.v_in)
        public_out_value_zeth_units = to_zeth_units(mix_call_desc.v_out)

        # Generate the signing key
        signing_keypair = signing.gen_signing_keypair()

        # Use the specified or default h_sig computation
        compute_h_sig_cb = mix_call_desc.compute_h_sig_cb or compute_h_sig
        h_sig = compute_h_sig_cb(
            [bytes.fromhex(input.nullifier) for input in api_inputs],
            signing_keypair.vk)
        phi = _phi_randomness()

        # Create the api.ZethNote objects
        api_outputs = _create_api_zeth_notes(phi, h_sig, outputs_with_a_pk)

        proof_inputs = api.ProofInputs(
            mk_root=mk_root.hex(),
            js_inputs=api_inputs,
            js_outputs=api_outputs,
            pub_in_value=int64_to_hex(public_in_value_zeth_units),
            pub_out_value=int64_to_hex(public_out_value_zeth_units),
            h_sig=h_sig.hex(),
            phi=phi.hex())
        return (proof_inputs, signing_keypair)
예제 #3
0
    def test_sign_verify_random(self) -> None:
        """
        Test the correct signing-verification flow with random message:
        verify(vk, sign(sk,m), m) = 1
        """
        m = urandom(32)
        sigma = signing.sign(self.keypair.sk, m)
        self.assertTrue(signing.verify(self.keypair.vk, m, sigma))

        keypair2 = signing.gen_signing_keypair()
        self.assertFalse(signing.verify(keypair2.vk, m, sigma))
예제 #4
0
    def test_sign_verify(self) -> None:
        """
        Test the correct signing-verification flow:
        verify(vk, sign(sk,m), m) = 1
        """
        m = sha256("clearmatics".encode()).digest()
        sigma = signing.sign(self.keypair.sk, m)
        self.assertTrue(signing.verify(self.keypair.vk, m, sigma))

        keypair2 = signing.gen_signing_keypair()
        self.assertFalse(signing.verify(keypair2.vk, m, sigma))
예제 #5
0
    def get_proof_joinsplit_2_by_2(
        self,
        mk_root: bytes,
        input0: Tuple[int, ZethNote],
        mk_path0: List[str],
        input1: Tuple[int, ZethNote],
        mk_path1: List[str],
        sender_ask: OwnershipSecretKey,
        output0: Tuple[OwnershipPublicKey, int],
        output1: Tuple[OwnershipPublicKey, int],
        public_in_value_zeth_units: int,
        public_out_value_zeth_units: int,
        compute_h_sig_cb: Optional[ComputeHSigCB] = None
    ) -> Tuple[ZethNote, ZethNote, Dict[str, Any], JoinsplitSigKeyPair]:
        """
        Query the prover server to generate a proof for the given joinsplit
        parameters.
        """
        signing_keypair = signing.gen_signing_keypair()
        proof_input = compute_joinsplit2x2_inputs(
            mk_root, input0, mk_path0, input1, mk_path1, sender_ask, output0,
            output1, public_in_value_zeth_units, public_out_value_zeth_units,
            signing_keypair.vk, compute_h_sig_cb)
        proof_proto = self._prover_client.get_proof(proof_input)
        extproof = self._zksnark.proof_from_proto(proof_proto)

        # Sanity check our unpacking code against the prover server output.
        pub_inputs = extproof["inputs"]
        print(f"pub_inputs: {pub_inputs}")
        # pub_inputs_bytes = [bytes.fromhex(x) for x in pub_inputs]
        (v_in, v_out) = public_inputs_extract_public_values(pub_inputs)
        assert public_in_value_zeth_units == v_in
        assert public_out_value_zeth_units == v_out

        # We return the zeth notes to be able to spend them later
        # and the proof used to create them
        return (
            proof_input.js_outputs[0],  # pylint: disable=no-member
            proof_input.js_outputs[1],  # pylint: disable=no-member
            extproof,
            signing_keypair)
예제 #6
0
class TestSigning(TestCase):

    keypair = signing.gen_signing_keypair()

    def test_sign_verify(self) -> None:
        """
        Test the correct signing-verification flow:
        verify(vk, sign(sk,m), m) = 1
        """
        m = sha256("clearmatics".encode()).digest()
        sigma = signing.sign(self.keypair.sk, m)
        self.assertTrue(signing.verify(self.keypair.vk, m, sigma))

        keypair2 = signing.gen_signing_keypair()
        self.assertFalse(signing.verify(keypair2.vk, m, sigma))

    def test_sign_verify_random(self) -> None:
        """
        Test the correct signing-verification flow with random message:
        verify(vk, sign(sk,m), m) = 1
        """
        m = urandom(32)
        sigma = signing.sign(self.keypair.sk, m)
        self.assertTrue(signing.verify(self.keypair.vk, m, sigma))

        keypair2 = signing.gen_signing_keypair()
        self.assertFalse(signing.verify(keypair2.vk, m, sigma))

    def test_signature_encoding(self) -> None:
        """
        Test encoding and decoding of signatures.
        """
        m = sha256("clearmatics".encode()).digest()
        sig = signing.sign(self.keypair.sk, m)
        sig_encoded = signing.encode_signature_to_bytes(sig)
        sig_decoded = signing.decode_signature_from_bytes(sig_encoded)
        self.assertEqual(sig, sig_decoded)
예제 #7
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)
예제 #8
0
    def create_prover_inputs(
        mix_call_desc: MixCallDescription
    ) -> Tuple[ProofInputs, signing.SigningKeyPair]:
        """
        Given the basic parameters for a mix call, compute the input to the prover
        server, and the signing key pair.
        """

        # Compute Merkle paths
        mk_tree = mix_call_desc.mk_tree
        mk_root = mk_tree.get_root()
        inputs = mix_call_desc.inputs
        mk_paths = [compute_merkle_path(addr, mk_tree) for addr, _ in inputs]

        # Extract (<ownership-address>, <value>) tuples
        outputs_with_a_pk = \
            [(zeth_addr.a_pk, to_zeth_units(value))
             for (zeth_addr, value) in mix_call_desc.outputs]
        output0 = outputs_with_a_pk[0]
        output1 = outputs_with_a_pk[1]

        # Public input and output values as Zeth units
        public_in_value_zeth_units = to_zeth_units(mix_call_desc.v_in)
        public_out_value_zeth_units = to_zeth_units(mix_call_desc.v_out)

        # Generate the signing key
        signing_keypair = signing.gen_signing_keypair()
        sender_ask = mix_call_desc.sender_ownership_keypair.a_sk

        # Compute the input note nullifiers
        (input_address0, input_note0) = mix_call_desc.inputs[0]
        (input_address1, input_note1) = mix_call_desc.inputs[1]
        input_nullifier0 = compute_nullifier(input_note0, sender_ask)
        input_nullifier1 = compute_nullifier(input_note1, sender_ask)

        # Convert to JoinsplitInput objects
        js_inputs: List[JoinsplitInput] = [
            create_joinsplit_input(mk_paths[0], input_address0, input_note0,
                                   sender_ask, input_nullifier0),
            create_joinsplit_input(mk_paths[1], input_address1, input_note1,
                                   sender_ask, input_nullifier1)
        ]

        # Use the specified or default h_sig computation
        compute_h_sig_cb = mix_call_desc.compute_h_sig_cb or compute_h_sig
        h_sig = compute_h_sig_cb(input_nullifier0, input_nullifier1,
                                 signing_keypair.vk)
        phi = _phi_randomness()

        # Joinsplit Output Notes
        output_note0, output_note1 = _create_zeth_notes(
            phi, h_sig, output0, output1)
        js_outputs = [output_note0, output_note1]

        proof_inputs = ProofInputs(
            mk_root=mk_root.hex(),
            js_inputs=js_inputs,
            js_outputs=js_outputs,
            pub_in_value=int64_to_hex(public_in_value_zeth_units),
            pub_out_value=int64_to_hex(public_out_value_zeth_units),
            h_sig=h_sig.hex(),
            phi=phi.hex())
        return (proof_inputs, signing_keypair)