9 MixerClient, OwnershipKeyPair, JoinsplitSigVerificationKey, ComputeHSigCB, \
10 JoinsplitSigKeyPair, joinsplit_sign, encrypt_notes, \
11 event_args_to_mix_result, get_dummy_input_and_address, compute_h_sig
18 from zeth.api.zeth_messages_pb2
import ZethNote
19 from test_commands
import mock
21 from os
import urandom
23 from typing
import List, Tuple, Optional, Any
25 ZERO_UNITS_HEX =
"0000000000000000"
30 BOB_TO_CHARLIE_ETH = 50
31 BOB_TO_CHARLIE_CHANGE_ETH = BOB_SPLIT_1_ETH - BOB_TO_CHARLIE_ETH
33 CHARLIE_WITHDRAW_ETH = 10.5
34 CHARLIE_WITHDRAW_CHANGE_ETH = 39.5
38 print(
"[DEBUG] Displaying the Merkle tree of commitments: ")
40 print(
"Node: " + Web3.toHex(node)[2:])
45 tx_receipt: Any) -> MixResult:
47 Get the logs data associated with this mixing
56 zeth_client: MixerClient,
58 tx_hash: str) -> MixResult:
59 tx_receipt = zeth_client.web3.eth.waitForTransactionReceipt(tx_hash, 10000)
61 for out_ev
in result.output_events:
62 mk_tree.insert(out_ev.commitment)
64 if mk_tree.recompute_root() != result.new_merkle_root:
65 raise Exception(
"Merkle root mismatch between log and local tree")
70 zeth_client: MixerClient,
71 prover_client: ProverClient,
73 sender_ownership_keypair: OwnershipKeyPair,
74 inputs: List[Tuple[int, ZethNote]],
75 outputs: List[Tuple[ZethAddressPub, EtherValue]],
78 compute_h_sig_cb: Optional[ComputeHSigCB] =
None
79 ) -> Tuple[ZethNote, ZethNote, ExtendedProof, List[int], JoinsplitSigKeyPair]:
81 Manually create the components required for MixParameters. The tests below
82 manipulate these to create custom MixParameters as part of attacks.
86 sender_ownership_keypair,
92 prover_inputs, signing_keypair = zeth_client.create_prover_inputs(
94 ext_proof, public_data = prover_client.get_proof(prover_inputs)
96 prover_inputs.js_outputs[0],
97 prover_inputs.js_outputs[1],
104 zeth_client: MixerClient,
105 prover_client: ProverClient,
107 bob_eth_address: str,
108 keystore: mock.KeyStore,
109 tx_value: Optional[EtherValue] =
None) -> MixResult:
111 f
"=== Bob deposits {BOB_DEPOSIT_ETH} ETH for himself and splits into " +
112 f
"note1: {BOB_SPLIT_1_ETH}ETH, note2: {BOB_SPLIT_2_ETH}ETH ===")
114 bob_js_keypair = keystore[
"Bob"]
115 bob_addr = keystore[
"Bob"].addr_pk
122 tx_hash = zeth_client.deposit(
135 zeth_client: MixerClient,
136 prover_client: ProverClient,
138 input1: Tuple[int, ZethNote],
139 bob_eth_address: str,
140 keystore: mock.KeyStore) -> MixResult:
142 f
"=== Bob transfers {BOB_TO_CHARLIE_ETH}ETH to Charlie from his funds " +
145 bob_ask = keystore[
"Bob"].addr_sk.a_sk
146 charlie_addr = keystore[
"Charlie"].addr_pk
147 bob_addr = keystore[
"Bob"].addr_pk
150 output0 = (bob_addr,
EtherValue(BOB_TO_CHARLIE_ETH))
152 output1 = (charlie_addr,
EtherValue(BOB_TO_CHARLIE_CHANGE_ETH))
155 tx_hash = zeth_client.joinsplit(
158 OwnershipKeyPair(bob_ask, bob_addr.a_pk),
170 zeth_client: MixerClient,
171 prover_client: ProverClient,
173 input1: Tuple[int, ZethNote],
174 charlie_eth_address: str,
175 keystore: mock.KeyStore) -> MixResult:
177 f
" === Charlie withdraws {CHARLIE_WITHDRAW_ETH}ETH from his funds " +
180 charlie_pk = keystore[
"Charlie"].addr_pk
181 charlie_apk = charlie_pk.a_pk
182 charlie_ask = keystore[
"Charlie"].addr_sk.a_sk
183 charlie_ownership_key = \
184 OwnershipKeyPair(charlie_ask, charlie_apk)
186 tx_hash = zeth_client.joinsplit(
189 charlie_ownership_key,
193 [(charlie_pk,
EtherValue(CHARLIE_WITHDRAW_CHANGE_ETH))],
201 zeth_client: MixerClient,
202 prover_client: ProverClient,
203 zksnark: IZKSnarkProvider,
205 input1: Tuple[int, ZethNote],
206 charlie_eth_address: str,
207 keystore: mock.KeyStore) -> MixResult:
209 Charlie tries to carry out a double spending by modifying the value of the
210 nullifier of the previous payment
212 pp = zeth_client.prover_config.pairing_parameters
213 scalar_field_mod = pp.scalar_field_mod()
214 scalar_field_capacity = pp.scalar_field_capacity
217 f
" === Charlie attempts to withdraw {CHARLIE_WITHDRAW_ETH}ETH once " +
218 "more (double spend) one of his note on the Mixer ===")
220 charlie_addr = keystore[
"Charlie"]
221 charlie_apk = charlie_addr.addr_pk.a_pk
226 note1_value =
EtherValue(CHARLIE_WITHDRAW_CHANGE_ETH)
236 attack_primary_input3: int = 0
237 attack_primary_input4: int = 0
239 def compute_h_sig_attack_nf(
241 sign_vk: JoinsplitSigVerificationKey) -> bytes:
246 input_nullifier0 = nf0.hex()
247 input_nullifier1 = nf1.hex()
248 nf0_rev =
"{0:0256b}".format(
int(input_nullifier0, 16))
249 primary_input3_bits = nf0_rev[:scalar_field_capacity]
250 primary_input3_res_bits = nf0_rev[scalar_field_capacity:]
251 nf1_rev =
"{0:0256b}".format(
int(input_nullifier1, 16))
252 primary_input4_bits = nf1_rev[:scalar_field_capacity]
253 primary_input4_res_bits = nf1_rev[scalar_field_capacity:]
256 nonlocal attack_primary_input3
257 nonlocal attack_primary_input4
258 attack_primary_input3 =
int(primary_input3_bits, 2) + scalar_field_mod
259 attack_primary_input4 =
int(primary_input4_bits, 2) + scalar_field_mod
262 attack_primary_input3_bits =
"{0:0256b}".format(attack_primary_input3)
263 attack_nf0_bits = attack_primary_input3_bits[
264 len(attack_primary_input3_bits) - scalar_field_capacity:] +\
265 primary_input3_res_bits
266 attack_nf0 =
"{0:064x}".format(
int(attack_nf0_bits, 2))
267 attack_primary_input4_bits =
"{0:0256b}".format(attack_primary_input4)
268 attack_nf1_bits = attack_primary_input4_bits[
269 len(attack_primary_input4_bits) - scalar_field_capacity:] +\
270 primary_input4_res_bits
271 attack_nf1 =
"{0:064x}".format(
int(attack_nf1_bits, 2))
273 [bytes.fromhex(attack_nf0), bytes.fromhex(attack_nf1)], sign_vk)
275 output_note1, output_note2, proof, public_data, signing_keypair = \
280 keystore[
"Charlie"].ownership_keypair(),
282 [(charlie_addr.addr_pk, note1_value),
286 compute_h_sig_attack_nf)
291 assert attack_primary_input3 != 0
292 assert attack_primary_input4 != 0
294 print(
"proof = ", proof)
295 print(
"public_data[3] = ", public_data[3])
296 print(
"public_data[4] = ", public_data[4])
297 public_data[3] = attack_primary_input3
298 public_data[4] = attack_primary_input4
302 pk_charlie = keystore[
"Charlie"].addr_pk.k_pk
306 (output_note1, pk_charlie),
307 (output_note2, pk_charlie)])
323 joinsplit_sig_charlie,
326 tx_hash = zeth_client.mix(
337 zeth_client: MixerClient,
338 prover_client: ProverClient,
339 zksnark: IZKSnarkProvider,
341 bob_eth_address: str,
342 charlie_eth_address: str,
343 keystore: mock.KeyStore) -> MixResult:
345 Charlie tries to break transaction malleability and corrupt the coins
346 bob is sending in a transaction
347 She does so by intercepting bob's transaction and either:
348 - case 1: replacing the ciphertexts (or sender_eph_pk) by garbage/arbitrary
350 - case 2: replacing the ciphertexts by garbage/arbitrary data and using a
352 - case 3: Charlie replays the mix call of Bob, to try to receive the vout
353 Both attacks should fail,
354 - case 1: the signature check should fail, else Charlie broke UF-CMA of the
356 - case 2: the h_sig/vk verification should fail, as h_sig is not a function
358 - case 3: the signature check should fail, because `msg.sender` will no match
359 the value used in the mix parameters (Bob's Ethereum Address).
360 NB. If the adversary were to corrupt the ciphertexts (or the encryption key),
361 replace the OT-signature by a new one and modify the h_sig accordingly so that
362 the check on the signature verification (key h_sig/vk) passes, the proof would
363 not verify, which is why we do not test this case.
366 f
"=== Bob deposits {BOB_DEPOSIT_ETH} ETH for himself and split into " +
367 f
"note1: {BOB_SPLIT_1_ETH}ETH, note2: {BOB_SPLIT_2_ETH}ETH " +
368 "but Charlie attempts to corrupt the transaction ===")
369 bob_addr_pk = keystore[
"Bob"]
370 bob_apk = bob_addr_pk.addr_pk.a_pk
373 pp = prover_client.get_configuration().pairing_parameters
384 output_note1, output_note2, proof, public_data, joinsplit_keypair = \
389 keystore[
"Bob"].ownership_keypair(),
391 [(bob_addr_pk.addr_pk, note1_value),
392 (bob_addr_pk.addr_pk, note2_value)],
397 pk_bob = keystore[
"Bob"].addr_pk.k_pk
399 (output_note1, pk_bob),
400 (output_note2, pk_bob)])
409 fake_ciphertext0 = urandom(32)
410 fake_ciphertext1 = urandom(32)
412 result_corrupt1 =
None
426 joinsplit_keypair.vk,
427 joinsplit_sig_charlie,
428 [fake_ciphertext0, fake_ciphertext1])
429 tx_hash = zeth_client.mix(
436 except Exception
as e:
438 "Charlie's first corruption attempt" +
439 f
" successfully rejected! (msg: {e})"
441 assert(result_corrupt1
is None), \
442 "Charlie managed to corrupt Bob's deposit the first time!"
448 fake_ciphertext0 = urandom(32)
449 fake_ciphertext1 = urandom(32)
450 new_joinsplit_keypair = signing.gen_signing_keypair()
454 result_corrupt2 =
None
459 new_joinsplit_keypair,
461 [fake_ciphertext0, fake_ciphertext1],
467 new_joinsplit_keypair.vk,
468 joinsplit_sig_charlie,
469 [fake_ciphertext0, fake_ciphertext1])
470 tx_hash = zeth_client.mix(
477 except Exception
as e:
479 "Charlie's second corruption attempt" +
480 f
" successfully rejected! (msg: {e})"
482 assert(result_corrupt2
is None), \
483 "Charlie managed to corrupt Bob's deposit the second time!"
487 result_corrupt3 =
None
500 joinsplit_keypair.vk,
503 tx_hash = zeth_client.mix(
511 except Exception
as e:
513 "Charlie's third corruption attempt" +
514 f
" successfully rejected! (msg: {e})"
516 assert(result_corrupt3
is None), \
517 "Charlie managed to corrupt Bob's deposit the third time!"
532 joinsplit_keypair.vk,
535 tx_hash = zeth_client.mix(