Skip to content

Commit

Permalink
Merge pull request #54 from AztecProtocol/master
Browse files Browse the repository at this point in the history
Preparation for release v2.1.96
  • Loading branch information
PhilWindle authored Oct 3, 2023
2 parents f3bff85 + cfccfbc commit f82959a
Show file tree
Hide file tree
Showing 21 changed files with 1,613 additions and 212 deletions.
10 changes: 5 additions & 5 deletions aztec-connect-cpp/src/rollup/constants.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ constexpr bool is_circuit_change_expected = 0;
constraints. They need to be changed when there is a circuit change. */
constexpr uint32_t ACCOUNT = 23967;
constexpr uint32_t JOIN_SPLIT = 64047;
constexpr uint32_t CLAIM = 22684;
constexpr uint32_t CLAIM = 23050;
constexpr uint32_t ROLLUP = 1173221;
constexpr uint32_t ROOT_ROLLUP = 5481327;
constexpr uint32_t ROOT_VERIFIER = 7435892;
Expand All @@ -61,12 +61,12 @@ namespace circuit_vk_hash {
to comply with the from_buffer<>() method. */
constexpr auto ACCOUNT = uint256_t(0xcd6d70c733eaf823, 0x6505d3402817ad3d, 0xbf9e2b6a262589cf, 0xafcc546b55cc45e3);
constexpr auto JOIN_SPLIT = uint256_t(0xb23c7772f47bc823, 0x5493625d4f08603c, 0x21ac50a5929576f9, 0xb7b3113c131460e5);
constexpr auto CLAIM = uint256_t(0x878301ebba40ab60, 0x931466762c62d661, 0x40aad71ec3496905, 0x9f47aaa109759d0a);
constexpr auto ROLLUP = uint256_t(0x8712bcbeb11180c5, 0x598412e4f700c484, 0xfe50ad453c8e4288, 0xa7340fac5feb663f);
constexpr auto ROOT_ROLLUP = uint256_t(0xcf2fee21f089b32f, 0x90c6187354cf70d4, 0x3a5a90b8c86d8c64, 0xd55af088ddc86db7);
constexpr auto CLAIM = uint256_t(0xa753ce523719749e, 0x80216aff7f8bc9ce, 0xa9b0f69bbd24ac33, 0xae17c5fb7d488138);
constexpr auto ROLLUP = uint256_t(0x5f2f6590e5553f19, 0x62c287e01b897621, 0xf32d03437085e2d, 0x567b0be24dc99966);
constexpr auto ROOT_ROLLUP = uint256_t(0x64e5e03cf9534ed6, 0x7fdc871935b9e4fe, 0xd2b81e990cc15f3d, 0x47f00f76d92e5e4d);
;
constexpr auto ROOT_VERIFIER =
uint256_t(0xe91df73df393fb5f, 0x99a9fa13abfbb206, 0x2ffe8c891cbde8c2, 0xdcb051e8ca06df5e);
uint256_t(0xb4349747ae6ea507, 0xfaafa0f2e384c984, 0x9325870bcc594daf, 0x50163a2572c67363);
}; // namespace circuit_vk_hash

namespace ProofIds {
Expand Down
75 changes: 75 additions & 0 deletions aztec-connect-cpp/src/rollup/proofs/claim/claim.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@
#include "index.hpp"
#include "../inner_proof_data/inner_proof_data.hpp"
#include "../notes/native/index.hpp"
#include "../notes/circuit/index.hpp"
#include "./claim_circuit.hpp"
#include <common/test.hpp>
#include <cstddef>
#include <iterator>
#include <stdlib/merkle_tree/index.hpp>
#include <numeric/random/engine.hpp>
#include <string>
#include <utility>

#define ENABLE_MALICIOUS_RATIO_CHECK_FOR_TESTS
#include "./malicious_claim_circuit.hpp"
#undef ENABLE_MALICIOUS_RATIO_CHECK_FOR_TESTS
namespace rollup {
namespace proofs {
namespace claim {
Expand Down Expand Up @@ -1579,6 +1588,72 @@ TEST_F(claim_tests, test_real_virtual)
EXPECT_EQ(tx.get_output_notes()[0], result.public_inputs[InnerProofFields::NOTE_COMMITMENT1]);
EXPECT_EQ(tx.get_output_notes()[1], result.public_inputs[InnerProofFields::NOTE_COMMITMENT2]);
}
/**
* @brief Check that malicious prover can't submit an erroneous claim
*
*/
TEST_F(claim_tests, test_claim_fails_if_prover_is_malicious)
{
// Generate notes with malicious values
const claim_note note1 = { .deposit_value = 1,
.bridge_call_data = 0,
.defi_interaction_nonce = 0,
.fee = 0,
.value_note_partial_commitment =
create_partial_commitment(user.note_secret, user.owner.public_key, 0, 0),
.input_nullifier = fr::random_element(&engine) };

const defi_interaction::note note2 = { .bridge_call_data = 0,
.interaction_nonce = 0,
.total_input_value = 100,
.total_output_value_a = 100,
.total_output_value_b = 100,
.interaction_result = 1 };
append_note(note1, data_tree);
append_note(note2, defi_tree);
claim_tx tx = create_claim_tx(note1, 0, 0, note2);
tx.output_value_a = 100;
tx.output_value_b = 100;

// Create one regular composer
Composer composer = Composer(cd.proving_key, cd.verification_key, cd.num_gates);
// And one donor
Composer donor = Composer(cd.proving_key, cd.verification_key, cd.num_gates);

// Construct the circuit with malicious witness in the donor
malicious_claim_circuit(donor, tx);

// Construct the regular claim circuit in the regular composer
claim_circuit(composer, tx);

// The witness in the regular will not satisfy the contriaints
info("Check circuit before transplant: ", composer.check_circuit());
ASSERT_EQ(composer.variables.size(), donor.variables.size());
// Copy the values of variables into the regular circuit
for (size_t i = 0; i < composer.variables.size(); i++) {
composer.variables[i] = donor.variables[i];
}
// If the circuit is undercontrained, the will both pass now
info("Check donor circuit: ", donor.check_circuit());
info("Check circuit after transplant: ", composer.check_circuit());
Timer proof_timer;
info(": Creating proof...");
verify_result<Composer> result;
auto prover = composer.create_unrolled_prover();
auto proof = prover.construct_proof();
result.proof_data = proof.proof_data;
info(": Proof created in ", proof_timer.toString(), "s");
auto verifier = composer.create_unrolled_verifier();
result.verified = verifier.verify_proof({ result.proof_data });
if (!result.verified) {
info(": Proof validation failed.");
} else {
info(": Verified successfully.");
}
result.verification_key = composer.circuit_verification_key;
EXPECT_FALSE(result.verified);
}

} // namespace claim
} // namespace proofs
} // namespace rollup
228 changes: 228 additions & 0 deletions aztec-connect-cpp/src/rollup/proofs/claim/malicious_claim_circuit.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
#pragma once
#include "claim_tx.hpp"
#include <stdlib/types/turbo.hpp>
#include "malicious_ratio_check.hpp"
#include "../notes/circuit/index.hpp"
#include <stdlib/merkle_tree/membership.hpp>

#ifndef ENABLE_MALICIOUS_RATIO_CHECK_FOR_TESTS
static_assert(false, "THIS FILE CAN ONLY BE USED INT TESTS");
#endif
namespace rollup {
namespace proofs {
namespace claim {

using namespace plonk::stdlib::types::turbo;
using namespace plonk::stdlib::merkle_tree;
using namespace notes;
/**
* @brief This function creates the same circuit as the regular claim circuit, but feels it with malicious witness
*
* @param composer
* @param tx
*/
void malicious_claim_circuit(Composer& composer, claim_tx const& tx)
{
// Create witnesses.
const auto proof_id = field_ct(witness_ct(&composer, ProofIds::DEFI_CLAIM));
proof_id.assert_equal(ProofIds::DEFI_CLAIM);
const auto data_root = field_ct(witness_ct(&composer, tx.data_root));
const auto defi_root = field_ct(witness_ct(&composer, tx.defi_root));
const auto claim_note_index =
suint_ct(witness_ct(&composer, tx.claim_note_index), DATA_TREE_DEPTH, "claim_note_index");
const auto claim_note_path = create_witness_hash_path(composer, tx.claim_note_path);
const auto defi_note_index =
suint_ct(witness_ct(&composer, tx.defi_note_index), DEFI_TREE_DEPTH, "defi_note_index");

/**
* Conversion to `claim_note_witness_data` contains:
* - range constraints on the claim note's attributes
* - expansion of bridge_call_data
* - expansion of the bridge_call_data's bit_config
* - sense checks on the bit_config's values
* (some bits can contradict each other)
*/
const auto claim_note_data = circuit::claim::claim_note_witness_data(composer, tx.claim_note);

const auto claim_note = circuit::claim::claim_note(claim_note_data);
const auto defi_interaction_note_path = create_witness_hash_path(composer, tx.defi_interaction_note_path);

/**
* Implicit conversion to `defi_interaction::witness_data` includes:
* - range constraints on the defi_interaction_note's attributes
* - expansion of bridge_call_data
* - expansion of the bridge_call_data's bit_config
* - sense checks on the bit_config's values
* (some bits can contradict each other)
*/
const auto defi_interaction_note = circuit::defi_interaction::note({ composer, tx.defi_interaction_note });
const auto output_value_a =
suint_ct(witness_ct(&composer, tx.output_value_a), NOTE_VALUE_BIT_LENGTH, "output_value_a");
const auto output_value_b =
suint_ct(witness_ct(&composer, tx.output_value_b), NOTE_VALUE_BIT_LENGTH, "output_value_b");

const bool_ct first_output_virtual =
circuit::get_asset_id_flag(claim_note_data.bridge_call_data_local.output_asset_id_a);
const bool_ct second_output_virtual =
circuit::get_asset_id_flag(claim_note_data.bridge_call_data_local.output_asset_id_b);
const bool_ct& second_input_in_use = claim_note_data.bridge_call_data_local.config.second_input_in_use;
const bool_ct& second_output_in_use = claim_note_data.bridge_call_data_local.config.second_output_in_use;

{
// Don't support zero deposits (because they're illogical):
claim_note.deposit_value.value.assert_is_not_zero("Not supported: zero deposit");
// Ensure deposit_value <= total_input_value
defi_interaction_note.total_input_value.subtract(
claim_note.deposit_value, NOTE_VALUE_BIT_LENGTH, "deposit_value > total_input_value");
// These checks are superfluous, but included just in case:
// Ensure output_value_a <= total_output_value_a
defi_interaction_note.total_output_value_a.subtract(
output_value_a, NOTE_VALUE_BIT_LENGTH, "output_value_a > total_output_value_a");
// Ensure output_value_b <= total_output_value_b
defi_interaction_note.total_output_value_b.subtract(
output_value_b, NOTE_VALUE_BIT_LENGTH, "output_value_b > total_output_value_b");
}

{
// Ratio checks.
// Note, these ratio_checks also guarantee:
// defi_interaction_note.total_input_value != 0
// defi_interaction_note.total_output_value_a != 0 (unless output_value_a == 0)
// defi_interaction_note.total_output_value_b != 0 (unless output_value_b == 0)

// Check that (deposit * total_output_value_a) == (output_value_a * total_input_value)
// Rearranging, this ensures output_value_a == (deposit / total_input_value) * total_output_value_a
const bool_ct rc1 = malicious_ratio_check(composer,
{ .a1 = claim_note.deposit_value.value,
.a2 = defi_interaction_note.total_input_value.value,
.b1 = output_value_a.value,
.b2 = defi_interaction_note.total_output_value_a.value });
const bool_ct valid1 = (output_value_a == 0 && defi_interaction_note.total_output_value_a == 0) || rc1;
valid1.assert_equal(true, "ratio check 1 failed");

// Check that (deposit * total_output_value_b) == (output_value_b * total_input_value)
// Rearranging, this ensures output_value_b == (deposit / total_input_value) * total_output_value_b
const bool_ct rc2 = malicious_ratio_check(composer,
{ .a1 = claim_note.deposit_value.value,
.a2 = defi_interaction_note.total_input_value.value,
.b1 = output_value_b.value,
.b2 = defi_interaction_note.total_output_value_b.value });
const bool_ct valid2 = (output_value_b == 0 && defi_interaction_note.total_output_value_b == 0) || rc2;
valid2.assert_equal(true, "ratio check 2 failed");
}

// This nullifier1 is unique because the claim_note.commitment is unique (which itself is unique because it contains
// a unique input_nullifier (from the defi-deposit tx which created it)).
const auto nullifier1 = circuit::claim::compute_nullifier(claim_note.commitment);

// We 'nullify' this (claim note, defi interaction note) combination. Each owner of a claim note can produce a valid
// nullifier.
const auto nullifier2 =
circuit::defi_interaction::compute_nullifier(defi_interaction_note.commitment, claim_note.commitment);

field_ct output_note_commitment1;
field_ct output_note_commitment2;
{
// Compute output notes.
const auto virtual_note_flag = suint_ct(uint256_t(1) << (MAX_NUM_ASSETS_BIT_LENGTH - 1));

// If the defi interaction was unsuccessful, refund the original defi_deposit_value (which was denominated in
// bridge input_asset_id_a) via output note 1.
const bool_ct& interaction_success = defi_interaction_note.interaction_result;
const auto output_value_1 =
suint_ct::conditional_assign(interaction_success, output_value_a, claim_note_data.deposit_value);
const auto output_asset_id_1_if_success =
suint_ct::conditional_assign(first_output_virtual,
virtual_note_flag + claim_note.defi_interaction_nonce,
claim_note_data.bridge_call_data_local.output_asset_id_a);
const auto output_asset_id_1 = suint_ct::conditional_assign(
interaction_success, output_asset_id_1_if_success, claim_note_data.bridge_call_data_local.input_asset_id_a);
output_note_commitment1 = circuit::value::complete_partial_commitment(
claim_note.value_note_partial_commitment, output_value_1, output_asset_id_1, nullifier1);

// If the defi interaction was unsuccessful, refund the original defi_deposit_value (which was denominated in
// bridge input_asset_id_b) via output note 2, and only if there was a second input asset to the bridge.
const auto output_value_2 =
suint_ct::conditional_assign(interaction_success, output_value_b, claim_note_data.deposit_value);
const auto output_asset_id_2_if_success =
suint_ct::conditional_assign(second_output_virtual,
virtual_note_flag + claim_note.defi_interaction_nonce,
claim_note_data.bridge_call_data_local.output_asset_id_b);
const auto output_asset_id_2 = suint_ct::conditional_assign(
interaction_success, output_asset_id_2_if_success, claim_note_data.bridge_call_data_local.input_asset_id_b);
output_note_commitment2 = circuit::value::complete_partial_commitment(
claim_note.value_note_partial_commitment, output_value_2, output_asset_id_2, nullifier2);

// We zero the output_note_commitment2 in two cases:
// - if the bridge interaction succeeded and returned a second output asset; or
// - if the bridge interaction failed and no second asset was ever sent to the bridge.
const bool_ct is_bridge_output_b_in_use = interaction_success && second_output_in_use;
const bool_ct was_bridge_input_b_in_use = !interaction_success && second_input_in_use;

const bool_ct output_note_2_exists = is_bridge_output_b_in_use || was_bridge_input_b_in_use;

output_note_commitment2 = output_note_commitment2 * output_note_2_exists;
}

{
// Check claim note and interaction note are related.
claim_note.bridge_call_data.assert_equal(defi_interaction_note.bridge_call_data,
"note bridge call datas don't match");
claim_note.defi_interaction_nonce.assert_equal(defi_interaction_note.interaction_nonce,
"note nonces don't match");
}

{
// Existence checks

// Check claim note exists:
const bool_ct claim_exists = check_membership(data_root,
claim_note_path,
claim_note.commitment,
claim_note_index.value.decompose_into_bits(DATA_TREE_DEPTH));
claim_exists.assert_equal(true, "claim note not a member");

// Check defi interaction note exists:
const bool_ct din_exists = check_membership(defi_root,
defi_interaction_note_path,
defi_interaction_note.commitment,
defi_note_index.value.decompose_into_bits(DEFI_TREE_DEPTH));
din_exists.assert_equal(true, "defi interaction note not a member");
}

// Force unused public inputs to 0.
const field_ct public_value = witness_ct(&composer, 0);
const field_ct public_owner = witness_ct(&composer, 0);
const field_ct asset_id = witness_ct(&composer, 0);
const field_ct defi_deposit_value = witness_ct(&composer, 0);
const field_ct backward_link = witness_ct(&composer, 0);
const field_ct allow_chain = witness_ct(&composer, 0);
public_value.assert_is_zero();
public_owner.assert_is_zero();
asset_id.assert_is_zero();
defi_deposit_value.assert_is_zero();
backward_link.assert_is_zero();
allow_chain.assert_is_zero();

// The following make up the public inputs to the circuit.
proof_id.set_public();
output_note_commitment1.set_public();
output_note_commitment2.set_public();
nullifier1.set_public();
nullifier2.set_public();
public_value.set_public(); // 0
public_owner.set_public(); // 0
asset_id.set_public(); // 0
data_root.set_public();
claim_note.fee.set_public();
claim_note_data.bridge_call_data_local.input_asset_id_a.set_public();
claim_note.bridge_call_data.set_public();
defi_deposit_value.set_public(); // 0
defi_root.set_public();
backward_link.set_public(); // 0
allow_chain.set_public(); // 0
}

} // namespace claim
} // namespace proofs
} // namespace rollup
Loading

0 comments on commit f82959a

Please sign in to comment.