|
| 1 | +#pragma once |
| 2 | +#include "claim_tx.hpp" |
| 3 | +#include <stdlib/types/turbo.hpp> |
| 4 | +#include "malicious_ratio_check.hpp" |
| 5 | +#include "../notes/circuit/index.hpp" |
| 6 | +#include <stdlib/merkle_tree/membership.hpp> |
| 7 | + |
| 8 | +#ifndef ENABLE_MALICIOUS_RATIO_CHECK_FOR_TESTS |
| 9 | +static_assert(false, "THIS FILE CAN ONLY BE USED INT TESTS"); |
| 10 | +#endif |
| 11 | +namespace rollup { |
| 12 | +namespace proofs { |
| 13 | +namespace claim { |
| 14 | + |
| 15 | +using namespace plonk::stdlib::types::turbo; |
| 16 | +using namespace plonk::stdlib::merkle_tree; |
| 17 | +using namespace notes; |
| 18 | +/** |
| 19 | + * @brief This function creates the same circuit as the regular claim circuit, but feels it with malicious witness |
| 20 | + * |
| 21 | + * @param composer |
| 22 | + * @param tx |
| 23 | + */ |
| 24 | +void malicious_claim_circuit(Composer& composer, claim_tx const& tx) |
| 25 | +{ |
| 26 | + // Create witnesses. |
| 27 | + const auto proof_id = field_ct(witness_ct(&composer, ProofIds::DEFI_CLAIM)); |
| 28 | + proof_id.assert_equal(ProofIds::DEFI_CLAIM); |
| 29 | + const auto data_root = field_ct(witness_ct(&composer, tx.data_root)); |
| 30 | + const auto defi_root = field_ct(witness_ct(&composer, tx.defi_root)); |
| 31 | + const auto claim_note_index = |
| 32 | + suint_ct(witness_ct(&composer, tx.claim_note_index), DATA_TREE_DEPTH, "claim_note_index"); |
| 33 | + const auto claim_note_path = create_witness_hash_path(composer, tx.claim_note_path); |
| 34 | + const auto defi_note_index = |
| 35 | + suint_ct(witness_ct(&composer, tx.defi_note_index), DEFI_TREE_DEPTH, "defi_note_index"); |
| 36 | + |
| 37 | + /** |
| 38 | + * Conversion to `claim_note_witness_data` contains: |
| 39 | + * - range constraints on the claim note's attributes |
| 40 | + * - expansion of bridge_call_data |
| 41 | + * - expansion of the bridge_call_data's bit_config |
| 42 | + * - sense checks on the bit_config's values |
| 43 | + * (some bits can contradict each other) |
| 44 | + */ |
| 45 | + const auto claim_note_data = circuit::claim::claim_note_witness_data(composer, tx.claim_note); |
| 46 | + |
| 47 | + const auto claim_note = circuit::claim::claim_note(claim_note_data); |
| 48 | + const auto defi_interaction_note_path = create_witness_hash_path(composer, tx.defi_interaction_note_path); |
| 49 | + |
| 50 | + /** |
| 51 | + * Implicit conversion to `defi_interaction::witness_data` includes: |
| 52 | + * - range constraints on the defi_interaction_note's attributes |
| 53 | + * - expansion of bridge_call_data |
| 54 | + * - expansion of the bridge_call_data's bit_config |
| 55 | + * - sense checks on the bit_config's values |
| 56 | + * (some bits can contradict each other) |
| 57 | + */ |
| 58 | + const auto defi_interaction_note = circuit::defi_interaction::note({ composer, tx.defi_interaction_note }); |
| 59 | + const auto output_value_a = |
| 60 | + suint_ct(witness_ct(&composer, tx.output_value_a), NOTE_VALUE_BIT_LENGTH, "output_value_a"); |
| 61 | + const auto output_value_b = |
| 62 | + suint_ct(witness_ct(&composer, tx.output_value_b), NOTE_VALUE_BIT_LENGTH, "output_value_b"); |
| 63 | + |
| 64 | + const bool_ct first_output_virtual = |
| 65 | + circuit::get_asset_id_flag(claim_note_data.bridge_call_data_local.output_asset_id_a); |
| 66 | + const bool_ct second_output_virtual = |
| 67 | + circuit::get_asset_id_flag(claim_note_data.bridge_call_data_local.output_asset_id_b); |
| 68 | + const bool_ct& second_input_in_use = claim_note_data.bridge_call_data_local.config.second_input_in_use; |
| 69 | + const bool_ct& second_output_in_use = claim_note_data.bridge_call_data_local.config.second_output_in_use; |
| 70 | + |
| 71 | + { |
| 72 | + // Don't support zero deposits (because they're illogical): |
| 73 | + claim_note.deposit_value.value.assert_is_not_zero("Not supported: zero deposit"); |
| 74 | + // Ensure deposit_value <= total_input_value |
| 75 | + defi_interaction_note.total_input_value.subtract( |
| 76 | + claim_note.deposit_value, NOTE_VALUE_BIT_LENGTH, "deposit_value > total_input_value"); |
| 77 | + // These checks are superfluous, but included just in case: |
| 78 | + // Ensure output_value_a <= total_output_value_a |
| 79 | + defi_interaction_note.total_output_value_a.subtract( |
| 80 | + output_value_a, NOTE_VALUE_BIT_LENGTH, "output_value_a > total_output_value_a"); |
| 81 | + // Ensure output_value_b <= total_output_value_b |
| 82 | + defi_interaction_note.total_output_value_b.subtract( |
| 83 | + output_value_b, NOTE_VALUE_BIT_LENGTH, "output_value_b > total_output_value_b"); |
| 84 | + } |
| 85 | + |
| 86 | + { |
| 87 | + // Ratio checks. |
| 88 | + // Note, these ratio_checks also guarantee: |
| 89 | + // defi_interaction_note.total_input_value != 0 |
| 90 | + // defi_interaction_note.total_output_value_a != 0 (unless output_value_a == 0) |
| 91 | + // defi_interaction_note.total_output_value_b != 0 (unless output_value_b == 0) |
| 92 | + |
| 93 | + // Check that (deposit * total_output_value_a) == (output_value_a * total_input_value) |
| 94 | + // Rearranging, this ensures output_value_a == (deposit / total_input_value) * total_output_value_a |
| 95 | + const bool_ct rc1 = malicious_ratio_check(composer, |
| 96 | + { .a1 = claim_note.deposit_value.value, |
| 97 | + .a2 = defi_interaction_note.total_input_value.value, |
| 98 | + .b1 = output_value_a.value, |
| 99 | + .b2 = defi_interaction_note.total_output_value_a.value }); |
| 100 | + const bool_ct valid1 = (output_value_a == 0 && defi_interaction_note.total_output_value_a == 0) || rc1; |
| 101 | + valid1.assert_equal(true, "ratio check 1 failed"); |
| 102 | + |
| 103 | + // Check that (deposit * total_output_value_b) == (output_value_b * total_input_value) |
| 104 | + // Rearranging, this ensures output_value_b == (deposit / total_input_value) * total_output_value_b |
| 105 | + const bool_ct rc2 = malicious_ratio_check(composer, |
| 106 | + { .a1 = claim_note.deposit_value.value, |
| 107 | + .a2 = defi_interaction_note.total_input_value.value, |
| 108 | + .b1 = output_value_b.value, |
| 109 | + .b2 = defi_interaction_note.total_output_value_b.value }); |
| 110 | + const bool_ct valid2 = (output_value_b == 0 && defi_interaction_note.total_output_value_b == 0) || rc2; |
| 111 | + valid2.assert_equal(true, "ratio check 2 failed"); |
| 112 | + } |
| 113 | + |
| 114 | + // This nullifier1 is unique because the claim_note.commitment is unique (which itself is unique because it contains |
| 115 | + // a unique input_nullifier (from the defi-deposit tx which created it)). |
| 116 | + const auto nullifier1 = circuit::claim::compute_nullifier(claim_note.commitment); |
| 117 | + |
| 118 | + // We 'nullify' this (claim note, defi interaction note) combination. Each owner of a claim note can produce a valid |
| 119 | + // nullifier. |
| 120 | + const auto nullifier2 = |
| 121 | + circuit::defi_interaction::compute_nullifier(defi_interaction_note.commitment, claim_note.commitment); |
| 122 | + |
| 123 | + field_ct output_note_commitment1; |
| 124 | + field_ct output_note_commitment2; |
| 125 | + { |
| 126 | + // Compute output notes. |
| 127 | + const auto virtual_note_flag = suint_ct(uint256_t(1) << (MAX_NUM_ASSETS_BIT_LENGTH - 1)); |
| 128 | + |
| 129 | + // If the defi interaction was unsuccessful, refund the original defi_deposit_value (which was denominated in |
| 130 | + // bridge input_asset_id_a) via output note 1. |
| 131 | + const bool_ct& interaction_success = defi_interaction_note.interaction_result; |
| 132 | + const auto output_value_1 = |
| 133 | + suint_ct::conditional_assign(interaction_success, output_value_a, claim_note_data.deposit_value); |
| 134 | + const auto output_asset_id_1_if_success = |
| 135 | + suint_ct::conditional_assign(first_output_virtual, |
| 136 | + virtual_note_flag + claim_note.defi_interaction_nonce, |
| 137 | + claim_note_data.bridge_call_data_local.output_asset_id_a); |
| 138 | + const auto output_asset_id_1 = suint_ct::conditional_assign( |
| 139 | + interaction_success, output_asset_id_1_if_success, claim_note_data.bridge_call_data_local.input_asset_id_a); |
| 140 | + output_note_commitment1 = circuit::value::complete_partial_commitment( |
| 141 | + claim_note.value_note_partial_commitment, output_value_1, output_asset_id_1, nullifier1); |
| 142 | + |
| 143 | + // If the defi interaction was unsuccessful, refund the original defi_deposit_value (which was denominated in |
| 144 | + // bridge input_asset_id_b) via output note 2, and only if there was a second input asset to the bridge. |
| 145 | + const auto output_value_2 = |
| 146 | + suint_ct::conditional_assign(interaction_success, output_value_b, claim_note_data.deposit_value); |
| 147 | + const auto output_asset_id_2_if_success = |
| 148 | + suint_ct::conditional_assign(second_output_virtual, |
| 149 | + virtual_note_flag + claim_note.defi_interaction_nonce, |
| 150 | + claim_note_data.bridge_call_data_local.output_asset_id_b); |
| 151 | + const auto output_asset_id_2 = suint_ct::conditional_assign( |
| 152 | + interaction_success, output_asset_id_2_if_success, claim_note_data.bridge_call_data_local.input_asset_id_b); |
| 153 | + output_note_commitment2 = circuit::value::complete_partial_commitment( |
| 154 | + claim_note.value_note_partial_commitment, output_value_2, output_asset_id_2, nullifier2); |
| 155 | + |
| 156 | + // We zero the output_note_commitment2 in two cases: |
| 157 | + // - if the bridge interaction succeeded and returned a second output asset; or |
| 158 | + // - if the bridge interaction failed and no second asset was ever sent to the bridge. |
| 159 | + const bool_ct is_bridge_output_b_in_use = interaction_success && second_output_in_use; |
| 160 | + const bool_ct was_bridge_input_b_in_use = !interaction_success && second_input_in_use; |
| 161 | + |
| 162 | + const bool_ct output_note_2_exists = is_bridge_output_b_in_use || was_bridge_input_b_in_use; |
| 163 | + |
| 164 | + output_note_commitment2 = output_note_commitment2 * output_note_2_exists; |
| 165 | + } |
| 166 | + |
| 167 | + { |
| 168 | + // Check claim note and interaction note are related. |
| 169 | + claim_note.bridge_call_data.assert_equal(defi_interaction_note.bridge_call_data, |
| 170 | + "note bridge call datas don't match"); |
| 171 | + claim_note.defi_interaction_nonce.assert_equal(defi_interaction_note.interaction_nonce, |
| 172 | + "note nonces don't match"); |
| 173 | + } |
| 174 | + |
| 175 | + { |
| 176 | + // Existence checks |
| 177 | + |
| 178 | + // Check claim note exists: |
| 179 | + const bool_ct claim_exists = check_membership(data_root, |
| 180 | + claim_note_path, |
| 181 | + claim_note.commitment, |
| 182 | + claim_note_index.value.decompose_into_bits(DATA_TREE_DEPTH)); |
| 183 | + claim_exists.assert_equal(true, "claim note not a member"); |
| 184 | + |
| 185 | + // Check defi interaction note exists: |
| 186 | + const bool_ct din_exists = check_membership(defi_root, |
| 187 | + defi_interaction_note_path, |
| 188 | + defi_interaction_note.commitment, |
| 189 | + defi_note_index.value.decompose_into_bits(DEFI_TREE_DEPTH)); |
| 190 | + din_exists.assert_equal(true, "defi interaction note not a member"); |
| 191 | + } |
| 192 | + |
| 193 | + // Force unused public inputs to 0. |
| 194 | + const field_ct public_value = witness_ct(&composer, 0); |
| 195 | + const field_ct public_owner = witness_ct(&composer, 0); |
| 196 | + const field_ct asset_id = witness_ct(&composer, 0); |
| 197 | + const field_ct defi_deposit_value = witness_ct(&composer, 0); |
| 198 | + const field_ct backward_link = witness_ct(&composer, 0); |
| 199 | + const field_ct allow_chain = witness_ct(&composer, 0); |
| 200 | + public_value.assert_is_zero(); |
| 201 | + public_owner.assert_is_zero(); |
| 202 | + asset_id.assert_is_zero(); |
| 203 | + defi_deposit_value.assert_is_zero(); |
| 204 | + backward_link.assert_is_zero(); |
| 205 | + allow_chain.assert_is_zero(); |
| 206 | + |
| 207 | + // The following make up the public inputs to the circuit. |
| 208 | + proof_id.set_public(); |
| 209 | + output_note_commitment1.set_public(); |
| 210 | + output_note_commitment2.set_public(); |
| 211 | + nullifier1.set_public(); |
| 212 | + nullifier2.set_public(); |
| 213 | + public_value.set_public(); // 0 |
| 214 | + public_owner.set_public(); // 0 |
| 215 | + asset_id.set_public(); // 0 |
| 216 | + data_root.set_public(); |
| 217 | + claim_note.fee.set_public(); |
| 218 | + claim_note_data.bridge_call_data_local.input_asset_id_a.set_public(); |
| 219 | + claim_note.bridge_call_data.set_public(); |
| 220 | + defi_deposit_value.set_public(); // 0 |
| 221 | + defi_root.set_public(); |
| 222 | + backward_link.set_public(); // 0 |
| 223 | + allow_chain.set_public(); // 0 |
| 224 | +} |
| 225 | + |
| 226 | +} // namespace claim |
| 227 | +} // namespace proofs |
| 228 | +} // namespace rollup |
0 commit comments