Skip to content

Commit

Permalink
feat: mock IVC state from arbitrary acir IVC recursion constraints (#…
Browse files Browse the repository at this point in the history
…10314)

Generating a bberg kernel circuit from a noir kernel program represented
as acir requires an IVC instance containing certain state including a
verifier accumulator and verification queue containing proofs/VKs for
input to recursive verifiers. In the context of a write_vk flow, this
data is not known and must be mocked so that the recursive verifiers in
the kernel can be constructed properly. (Similar to how we construct a
dummy proof to generate a Honk recursive verifier).

The main method in this PR is `create_mock_ivc_from_constraints()` which
constructs an IVC instance with mocked state based on the IVC recursion
constraints present in the acir data. For example, if there are two PG
recursive verifications in the constraint system, we must generate two
mocked PG proofs plus some other auxiliary data.

So no actual write_vk flow exists but the logic is tested though the
`IvcRecursionConstraintTest` suite which constructs VKs from programs
containing each of the 3 different possible combinations of IVC
recursion constraints that appear in Aztec kernel circuits. (These are:
(a) 1 Oink recursive verification (init kernel), (b) 1 PG recursive
verification (reset or tail kernel), and (c) 2 PG recursive
verifications (inner kernel)).
  • Loading branch information
ledwards2225 authored Dec 3, 2024
1 parent 90668c3 commit ac7c0da
Show file tree
Hide file tree
Showing 7 changed files with 368 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,11 @@ class AcirHonkRecursionConstraint : public ::testing::Test {

std::vector<bb::fr> key_witnesses = verification_key->to_field_elements();
std::vector<fr> proof_witnesses = inner_proof;
const size_t num_public_inputs = inner_circuit.get_public_inputs().size();
const size_t num_public_inputs_to_extract =
inner_circuit.get_public_inputs().size() - bb::PAIRING_POINT_ACCUMULATOR_SIZE;

auto [key_indices, proof_indices, inner_public_inputs] = ProofSurgeon::populate_recursion_witness_data(
witness, proof_witnesses, key_witnesses, num_public_inputs);
witness, proof_witnesses, key_witnesses, num_public_inputs_to_extract);

RecursionConstraint honk_recursion_constraint{
.key = key_indices,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,91 +13,222 @@
namespace acir_format {

using namespace bb;
using field_ct = stdlib::field_t<Builder>;

ClientIVC create_mock_ivc_from_constraints(const std::vector<RecursionConstraint>& constraints)
/**
* @brief Create an IVC object with mocked state corresponding to a set of IVC recursion constraints
* @details Construction of a kernel circuit requires two inputs: kernel prgram acir constraints and an IVC instance
* containing state needed to complete the kernel logic, e.g. proofs for input to recursive verifiers. To construct
* verification keys for kernel circuits without running a full IVC, we mock the IVC state corresponding to a provided
* set of IVC recurson constraints. For example, if the constraints contain a single PG recursive verification, we
* initialize an IVC with mocked data for the verifier accumulator, the folding proof, the circuit verification key,
* and a merge proof.
* @note There are only three valid combinations of IVC recursion constraints for a kernel program. See below for
* details.
*
* @param constraints IVC recursion constraints from a kernel circuit
* @param trace_settings
* @return ClientIVC
*/
ClientIVC create_mock_ivc_from_constraints(const std::vector<RecursionConstraint>& constraints,
const TraceSettings& trace_settings)
{
ClientIVC ivc{ { SMALL_TEST_STRUCTURE } };
ClientIVC ivc{ trace_settings };

for (const auto& constraint : constraints) {
if (static_cast<uint32_t>(PROOF_TYPE::OINK) == constraint.proof_type) {
mock_ivc_oink_accumulation(ivc, constraint.public_inputs.size());
} else if (static_cast<uint32_t>(PROOF_TYPE::PG) == constraint.proof_type) {
// perform equivalent mocking for PG accumulation
}
uint32_t oink_type = static_cast<uint32_t>(PROOF_TYPE::OINK);
uint32_t pg_type = static_cast<uint32_t>(PROOF_TYPE::PG);

// There are only three valid combinations of IVC recursion constraints for Aztec kernel circuits:

// Case: INIT kernel; single Oink recursive verification of an app
if (constraints.size() == 1 && constraints[0].proof_type == oink_type) {
mock_ivc_accumulation(ivc, ClientIVC::QUEUE_TYPE::OINK, /*is_kernel=*/false);
return ivc;
}

// Case: RESET or TAIL kernel; single PG recursive verification of a kernel
if (constraints.size() == 1 && constraints[0].proof_type == pg_type) {
ivc.verifier_accumulator = create_mock_decider_vk();
mock_ivc_accumulation(ivc, ClientIVC::QUEUE_TYPE::PG, /*is_kernel=*/true);
return ivc;
}

return ivc;
// Case: INNER kernel; two PG recursive verifications, kernel and app in that order
if (constraints.size() == 2) {
ASSERT(constraints[0].proof_type == pg_type && constraints[1].proof_type == pg_type);
ivc.verifier_accumulator = create_mock_decider_vk();
mock_ivc_accumulation(ivc, ClientIVC::QUEUE_TYPE::PG, /*is_kernel=*/true);
mock_ivc_accumulation(ivc, ClientIVC::QUEUE_TYPE::PG, /*is_kernel=*/false);
return ivc;
}

ASSERT(false && "WARNING: Invalid set of IVC recursion constraints!");
return ClientIVC{};
}

/**
* @brief Populate an IVC instance with data that mimics the state after accumulating the first app (which runs the oink
* prover)
*@details Mock state consists a mock verification queue entry of type OINK (proof, VK) and a mocked merge proof
* @brief Populate an IVC instance with data that mimics the state after a single IVC accumulation (Oink or PG)
* @details Mock state consists of a mock verification queue entry of type OINK (proof, VK) and a mocked merge proof
*
* @param ivc
* @param num_public_inputs_app num pub inputs in accumulated app, excluding fixed components, e.g. pairing points
*/
void mock_ivc_oink_accumulation(ClientIVC& ivc, size_t num_public_inputs_app)
void mock_ivc_accumulation(ClientIVC& ivc, ClientIVC::QUEUE_TYPE type, const bool is_kernel)
{
ClientIVC::VerifierInputs oink_entry =
acir_format::create_dummy_vkey_and_proof_oink(ivc.trace_settings, num_public_inputs_app);
ivc.verification_queue.emplace_back(oink_entry);
ClientIVC::VerifierInputs entry =
acir_format::create_mock_verification_queue_entry(type, ivc.trace_settings, is_kernel);
ivc.verification_queue.emplace_back(entry);
ivc.merge_verification_queue.emplace_back(acir_format::create_dummy_merge_proof());
ivc.initialized = true;
}

/**
* @brief Create a mock oink proof and VK that have the correct structure but are not necessarily valid
* @brief Create a mock verification queue entry with proof and VK that have the correct structure but are not
* necessarily valid
*
*/
ClientIVC::VerifierInputs create_dummy_vkey_and_proof_oink(const TraceSettings& trace_settings,
const size_t num_public_inputs = 0)
ClientIVC::VerifierInputs create_mock_verification_queue_entry(const ClientIVC::QUEUE_TYPE verification_type,
const TraceSettings& trace_settings,
const bool is_kernel)
{
using Flavor = MegaFlavor;
using FF = bb::fr;
using FF = ClientIVC::FF;
using MegaVerificationKey = ClientIVC::MegaVerificationKey;

// Use the trace settings to determine the correct dyadic size and the public inputs offset
MegaExecutionTraceBlocks blocks;
blocks.set_fixed_block_sizes(trace_settings);
blocks.compute_offsets(/*is_structured=*/true);
size_t structured_dyadic_size = blocks.get_structured_dyadic_size();
size_t dyadic_size = blocks.get_structured_dyadic_size();
size_t pub_inputs_offset = blocks.pub_inputs.trace_offset;
// All circuits have pairing point public inputs; kernels have additional public inputs for two databus commitments
size_t num_public_inputs = bb::PAIRING_POINT_ACCUMULATOR_SIZE;
if (is_kernel) {
num_public_inputs += bb::PROPAGATED_DATABUS_COMMITMENTS_SIZE;
}

ClientIVC::VerifierInputs verifier_inputs;
verifier_inputs.type = ClientIVC::QUEUE_TYPE::OINK;
// Construct a mock Oink or PG proof
std::vector<FF> proof;
if (verification_type == ClientIVC::QUEUE_TYPE::OINK) {
proof = create_mock_oink_proof(dyadic_size, num_public_inputs, pub_inputs_offset);
} else { // ClientIVC::QUEUE_TYPE::PG)
proof = create_mock_pg_proof(dyadic_size, num_public_inputs, pub_inputs_offset);
}

FF mock_val(5);
// Construct a mock MegaHonk verification key
std::shared_ptr<MegaVerificationKey> verification_key =
create_mock_honk_vk(dyadic_size, num_public_inputs, pub_inputs_offset);

auto mock_commitment = curve::BN254::AffineElement::one() * mock_val;
std::vector<FF> mock_commitment_frs = field_conversion::convert_to_bn254_frs(mock_commitment);
// If the verification queue entry corresponds to a kernel circuit, set the databus data to indicate the presence of
// propagated return data commitments on the public inputs
if (is_kernel) {
verification_key->databus_propagation_data = bb::DatabusPropagationData::kernel_default();
}

return ClientIVC::VerifierInputs{ proof, verification_key, verification_type };
}

/**
* @brief Create a mock oink proof that has the correct structure but is not in general valid
*
*/
std::vector<ClientIVC::FF> create_mock_oink_proof(const size_t dyadic_size,
const size_t num_public_inputs,
const size_t pub_inputs_offset)
{
using Flavor = ClientIVC::Flavor;
using FF = ClientIVC::FF;

// Set proof preamble (metadata plus public inputs)
size_t total_num_public_inputs = num_public_inputs + bb::PAIRING_POINT_ACCUMULATOR_SIZE;
verifier_inputs.proof.emplace_back(structured_dyadic_size);
verifier_inputs.proof.emplace_back(total_num_public_inputs);
verifier_inputs.proof.emplace_back(pub_inputs_offset);
for (size_t i = 0; i < total_num_public_inputs; ++i) {
verifier_inputs.proof.emplace_back(0);
std::vector<FF> proof;

// Populate proof metadata
proof.emplace_back(dyadic_size);
proof.emplace_back(num_public_inputs);
proof.emplace_back(pub_inputs_offset);

// Populate mock public inputs
for (size_t i = 0; i < num_public_inputs; ++i) {
proof.emplace_back(0);
}

// Witness polynomial commitments
// Populate mock witness polynomial commitments
auto mock_commitment = curve::BN254::AffineElement::one();
std::vector<FF> mock_commitment_frs = field_conversion::convert_to_bn254_frs(mock_commitment);
for (size_t i = 0; i < Flavor::NUM_WITNESS_ENTITIES; ++i) {
for (const FF& val : mock_commitment_frs) {
verifier_inputs.proof.emplace_back(val);
proof.emplace_back(val);
}
}

return proof;
}

/**
* @brief Create a mock PG proof that has the correct structure but is not in general valid
*
*/
std::vector<ClientIVC::FF> create_mock_pg_proof(const size_t dyadic_size,
const size_t num_public_inputs,
const size_t pub_inputs_offset)
{
using FF = ClientIVC::FF;
using DeciderProvingKeys = ClientIVC::DeciderProvingKeys;

// The first part of a PG proof is an Oink proof
std::vector<FF> proof = create_mock_oink_proof(dyadic_size, num_public_inputs, pub_inputs_offset);

// Populate mock perturbator coefficients
for (size_t idx = 1; idx <= CONST_PG_LOG_N; idx++) {
proof.emplace_back(0);
}

// Populate mock combiner quotient coefficients
for (size_t idx = DeciderProvingKeys::NUM; idx < DeciderProvingKeys::BATCHED_EXTENDED_LENGTH; idx++) {
proof.emplace_back(0);
}

return proof;
}

/**
* @brief Create a mock MegaHonk VK that has the correct structure
*
*/
std::shared_ptr<ClientIVC::MegaVerificationKey> create_mock_honk_vk(const size_t dyadic_size,
const size_t num_public_inputs,
const size_t pub_inputs_offset)
{
// Set relevant VK metadata and commitments
auto honk_verification_key = std::make_shared<ClientIVC::MegaVerificationKey>();
honk_verification_key->circuit_size = dyadic_size;
honk_verification_key->num_public_inputs = num_public_inputs;
honk_verification_key->pub_inputs_offset = pub_inputs_offset; // must be set correctly
honk_verification_key->contains_pairing_point_accumulator = true;

for (auto& commitment : honk_verification_key->get_all()) {
commitment = curve::BN254::AffineElement::one(); // arbitrary mock commitment
}

return honk_verification_key;
}

/**
* @brief Create a mock Decider verification key for initilization of a mock verifier accumulator
*
*/
std::shared_ptr<ClientIVC::DeciderVerificationKey> create_mock_decider_vk()
{
using FF = ClientIVC::FF;

// Set relevant VK metadata and commitments
verifier_inputs.honk_verification_key = std::make_shared<Flavor::VerificationKey>();
verifier_inputs.honk_verification_key->circuit_size = structured_dyadic_size;
verifier_inputs.honk_verification_key->num_public_inputs = total_num_public_inputs;
verifier_inputs.honk_verification_key->pub_inputs_offset = blocks.pub_inputs.trace_offset; // must be set correctly
verifier_inputs.honk_verification_key->contains_pairing_point_accumulator = true;
for (auto& commitment : verifier_inputs.honk_verification_key->get_all()) {
commitment = mock_commitment;
auto decider_verification_key = std::make_shared<ClientIVC::DeciderVerificationKey>();
decider_verification_key->verification_key = create_mock_honk_vk(0, 0, 0); // metadata does not need to be accurate
decider_verification_key->is_accumulator = true;
decider_verification_key->gate_challenges = std::vector<FF>(static_cast<size_t>(CONST_PG_LOG_N), 0);

for (auto& commitment : decider_verification_key->witness_commitments.get_all()) {
commitment = curve::BN254::AffineElement::one(); // arbitrary mock commitment
}

return verifier_inputs;
return decider_verification_key;
}

/**
Expand All @@ -107,12 +238,12 @@ ClientIVC::VerifierInputs create_dummy_vkey_and_proof_oink(const TraceSettings&
*/
ClientIVC::MergeProof create_dummy_merge_proof()
{
using FF = bb::fr;
using FF = ClientIVC::FF;

std::vector<FF> proof;

FF mock_val(5);
auto mock_commitment = curve::BN254::AffineElement::one() * mock_val;
auto mock_commitment = curve::BN254::AffineElement::one();
std::vector<FF> mock_commitment_frs = field_conversion::convert_to_bn254_frs(mock_commitment);

// There are 12 entities in the merge protocol (4 columns x 3 components; aggregate transcript, previous aggregate
Expand Down Expand Up @@ -148,8 +279,7 @@ void populate_dummy_vk_in_constraint(MegaCircuitBuilder& builder,
const std::shared_ptr<MegaFlavor::VerificationKey>& mock_verification_key,
std::vector<uint32_t>& key_witness_indices)
{
using Flavor = MegaFlavor;
using FF = Flavor::FF;
using FF = ClientIVC::FF;

// Convert the VerificationKey to fields
std::vector<FF> mock_vk_fields = mock_verification_key->to_field_elements();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,28 @@ using namespace bb;
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1148): logic in this file is incomplete. See issue for
// details.

ClientIVC create_mock_ivc_from_constraints(const std::vector<RecursionConstraint>& constraints);
ClientIVC create_mock_ivc_from_constraints(const std::vector<RecursionConstraint>& constraints,
const TraceSettings& trace_settings);

void mock_ivc_oink_accumulation(ClientIVC& ivc, size_t num_public_inputs_app = 0);
void mock_ivc_accumulation(ClientIVC& ivc, ClientIVC::QUEUE_TYPE type, const bool is_kernel);

ClientIVC::VerifierInputs create_dummy_vkey_and_proof_oink(const TraceSettings& trace_settings,
const size_t num_public_inputs);
std::vector<ClientIVC::FF> create_mock_oink_proof(const size_t dyadic_size,
const size_t num_public_inputs,
const size_t pub_inputs_offset);

std::vector<ClientIVC::FF> create_mock_pg_proof(const size_t dyadic_size,
const size_t num_public_inputs,
const size_t pub_inputs_offset);

std::shared_ptr<ClientIVC::MegaVerificationKey> create_mock_honk_vk(const size_t dyadic_size,
const size_t num_public_inputs,
const size_t pub_inputs_offset);

std::shared_ptr<ClientIVC::DeciderVerificationKey> create_mock_decider_vk();

ClientIVC::VerifierInputs create_mock_verification_queue_entry(const ClientIVC::QUEUE_TYPE type,
const TraceSettings& trace_settings,
const bool is_kernel);

ClientIVC::MergeProof create_dummy_merge_proof();

Expand Down
Loading

0 comments on commit ac7c0da

Please sign in to comment.