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)
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 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 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)
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