def _decode_basename(filename: str) -> Tuple[int, str, EtherValue]: components = filename.split("_") addr = int(components[3]) short_commit = components[4] value = EtherValue(components[5], 'ether') return (addr, short_commit, value)
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, proof_json, 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, proof_json) mix_params = contracts.MixParameters(proof_json, signing_keypair.vk, signature, ciphertexts) return mix_params, signing_keypair
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())
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" + f"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, Web3.toWei(BOB_DEPOSIT_ETH, 'ether'), DEFAULT_MIX_GAS_WEI) result_corrupt1 = \ wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash) except Exception as e: print( f"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, Web3.toWei(BOB_DEPOSIT_ETH, 'ether'), DEFAULT_MIX_GAS_WEI) result_corrupt2 = \ wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash) except Exception as e: print( f"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, Web3.toWei(BOB_DEPOSIT_ETH, 'ether'), 4000000) result_corrupt3 = \ wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash) except Exception as e: print( f"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, Web3.toWei(BOB_DEPOSIT_ETH, 'ether'), DEFAULT_MIX_GAS_WEI) return wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash)
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 Web3.toWei(1, 'wei'), DEFAULT_MIX_GAS_WEI) return wait_for_tx_update_mk_tree(zeth_client, mk_tree, tx_hash)
def mixBac(request) -> None: result = {} req = json.loads(request.body) keystore_file = "{}/{}/{}".format(USER_DIR, req['username'], FISCO_ADDRESS_FILE) addr_file = "{}/{}/{}".format(USER_DIR, req['username'], ADDRESS_FILE_DEFAULT) if exists(keystore_file) and exists(addr_file): js_secret = load_zeth_address_secret(req['username']) wallet = open_wallet(None, js_secret, req['username'], None, None) inputs: List[Tuple[int, ZethNote]] = [ wallet.find_note(note_id).as_input() for note_id in req['input_notes'] ] outputs: List[Tuple[ZethAddressPub, EtherValue]] = [ parse_output(out_spec) for out_spec in req['output_specs'] ] mids = [] for note_id in req['input_notes']: mids.append(wallet.find_note(note_id).mid) ''' todo: check the reciever zeth_address whether record in server, if not, return / by searching the address in mysql, so for every zeth account, we need to save their zeth_address in mysql when generating / or specify the reciever by name instead zeth_address, so we can search the user dir ''' 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)) vin_pub = EtherValue(req['vin']) vout_pub = EtherValue(req['vout']) if vin_pub + input_note_sum != vout_pub + output_note_sum: result['status'] = 1 result['text'] = 'input and output value mismatch' return JsonResponse(result) ''' while (merkletree.objects.all().count() and not merkletree.objects.all().last().is_new): time.sleep(1) sqlResult = merkletree.objects.all().last() if sqlResult: sqlResult.is_new = False sqlResult.save() ''' if req['vin']: outputapprove = token_approve(req['vin'], req['mixer_address'], req['token_address'], req['username'], req['password']) if not outputapprove: result['status'] = 1 result['text'] = 'token approve failed' return JsonResponse(result) outputmix = mix(req['mixer_address'], req['username'], req['password'], vin_pub, vout_pub, inputs, outputs, mids) if outputmix: ''' event_sync(req['mixer_address'],sqlResult.blockNumber) total = EtherValue(0) commits = [] for addr, short_commit, value in wallet.note_summaries(): total = total + value commits.append(short_commit) result['status'] = 0 result['commits'] = commits result['total_value'] = total.ether() ''' traType = "mix" inputstr = "" for note_id in req['input_notes']: inputstr = inputstr + note_id + ';' outputstr = "" for out_spec in req['output_specs']: outputstr = outputstr + out_spec + ';' sqlInsert = "insert into transactions (traType, username, vin, vout, input_notes, output_specs) values (%s, %s, %s, %s, %s, %s);" db.ping(reconnect=True) cursor.execute(sqlInsert, [ traType, req['username'], req['vin'], req['vout'], inputstr, outputstr ]) db.commit() result['status'] = 0 result['text'] = 'mix success' return JsonResponse(result) else: ''' if merkletree.objects.all().count(): sqlResult.is_new = True sqlResult.save() ''' result['status'] = 1 result['text'] = 'mix failed' return JsonResponse(result) result['status'] = 1 result[ 'text'] = 'your account is not recorded in server, please import it firstly or create a new one' return JsonResponse(result)