Skip to content

Commit

Permalink
feat: constant sized PG proofs and const sized PG rec verifier (#8605)
Browse files Browse the repository at this point in the history
Constant sized PG proofs and const sized PG rec verifier (similar to
previous work for Honk using `CONST_PROOF_SIZE_LOG_N = 28`). This is to
facilitate consistent/precomputable VKs for kernel circuits which
contain PG recursive verifiers. I'm using `CONST_PG_LOG_N == 20` since
we don't currently fold anything larger than that but this can easily be
tweaked if necessary.

Closes AztecProtocol/barretenberg#1087 (we now
send the perturbator on the first round even though it is all zeros)
  • Loading branch information
ledwards2225 authored Sep 19, 2024
1 parent 14ff3cf commit 09e2f44
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 37 deletions.
5 changes: 2 additions & 3 deletions barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,7 @@ void ClientIVC::perform_recursive_verification_and_databus_consistency_checks(
// Extract native verifier accumulator from the stdlib accum for use on the next round
verifier_accumulator = std::make_shared<DeciderVerificationKey>(verifier_accum->get_value());
// Initialize the gate challenges to zero for use in first round of folding
auto log_circuit_size = static_cast<size_t>(verifier_accum->verification_key->log_circuit_size);
verifier_accumulator->gate_challenges = std::vector<FF>(log_circuit_size, 0);
verifier_accumulator->gate_challenges = std::vector<FF>(CONST_PG_LOG_N, 0);

// Perform databus commitment consistency checks and propagate return data commitments via public inputs
bus_depot.execute(verifier_accum->witness_commitments,
Expand Down Expand Up @@ -181,7 +180,7 @@ void ClientIVC::accumulate(ClientCircuit& circuit, const std::shared_ptr<Verific
oink_prover.prove();
proving_key->is_accumulator = true; // indicate to PG that it should not run oink on this key
// Initialize the gate challenges to zero for use in first round of folding
proving_key->gate_challenges = std::vector<FF>(proving_key->proving_key.log_circuit_size, 0);
proving_key->gate_challenges = std::vector<FF>(CONST_PG_LOG_N, 0);

fold_output.accumulator = proving_key; // initialize the prover accum with the completed key

Expand Down
5 changes: 5 additions & 0 deletions barretenberg/cpp/src/barretenberg/constants.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ namespace bb {
// The log of the max circuit size assumed in order to achieve constant sized Honk proofs
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1046): Remove the need for const sized proofs
static constexpr uint32_t CONST_PROOF_SIZE_LOG_N = 28;

// The log of the max circuit size of circuits being folded. This size is assumed by the PG prover and verifier in order
// to ensure a constant PG proof size and a PG recursive verifier circuit that is independent of the size of the
// circuits being folded.
static constexpr uint32_t CONST_PG_LOG_N = 20;
} // namespace bb
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ template <typename Flavor> class ProtogalaxyTests : public testing::Test {

auto accumulator = std::make_shared<DeciderProvingKey>();
accumulator->proving_key.polynomials = std::move(full_polynomials);
accumulator->proving_key.log_circuit_size = log_size;
accumulator->gate_challenges = betas;
accumulator->target_sum = target_sum;
accumulator->relation_parameters = relation_parameters;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ void ProtogalaxyProver_<DeciderProvingKeys>::run_oink_prover_on_each_incomplete_
if (!key->is_accumulator) {
run_oink_prover_on_one_incomplete_key(key, domain_separator);
key->target_sum = 0;
key->gate_challenges = std::vector<FF>(key->proving_key.log_circuit_size, 0);
key->gate_challenges = std::vector<FF>(CONST_PG_LOG_N, 0);
}

idx++;
Expand All @@ -51,19 +51,16 @@ ProtogalaxyProver_<DeciderProvingKeys>::perturbator_round(
using Fun = ProtogalaxyProverInternal<DeciderProvingKeys>;

const FF delta = transcript->template get_challenge<FF>("delta");
const std::vector<FF> deltas = compute_round_challenge_pows(accumulator->proving_key.log_circuit_size, delta);
const std::vector<FF> deltas = compute_round_challenge_pows(CONST_PG_LOG_N, delta);
// An honest prover with valid initial key computes that the perturbator is 0 in the first round
const Polynomial<FF> perturbator = accumulator->is_accumulator
? Fun::compute_perturbator(accumulator, deltas)
: Polynomial<FF>(accumulator->proving_key.log_circuit_size + 1);
const Polynomial<FF> perturbator = accumulator->is_accumulator ? Fun::compute_perturbator(accumulator, deltas)
: Polynomial<FF>(CONST_PG_LOG_N + 1);
// Prover doesn't send the constant coefficient of F because this is supposed to be equal to the target sum of
// the accumulator which the folding verifier has from the previous iteration.
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1087): Verifier circuit for first IVC step is
// different
if (accumulator->is_accumulator) {
for (size_t idx = 1; idx <= accumulator->proving_key.log_circuit_size; idx++) {
transcript->send_to_verifier("perturbator_" + std::to_string(idx), perturbator[idx]);
}
for (size_t idx = 1; idx <= CONST_PG_LOG_N; idx++) {
transcript->send_to_verifier("perturbator_" + std::to_string(idx), perturbator[idx]);
}

return std::make_tuple(deltas, perturbator);
Expand All @@ -88,7 +85,7 @@ ProtogalaxyProver_<DeciderProvingKeys>::combiner_quotient_round(const std::vecto
const std::vector<FF> updated_gate_challenges =
update_gate_challenges(perturbator_challenge, gate_challenges, deltas);
const UnivariateRelationSeparator alphas = Fun::compute_and_extend_alphas(keys);
const GateSeparatorPolynomial<FF> gate_separators{ updated_gate_challenges, keys[0]->proving_key.log_circuit_size };
const GateSeparatorPolynomial<FF> gate_separators{ updated_gate_challenges, CONST_PG_LOG_N };
const UnivariateRelationParameters relation_parameters =
Fun::template compute_extended_relation_parameters<UnivariateRelationParameters>(keys);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ template <class DeciderProvingKeys_> class ProtogalaxyProverInternal {
* each level, the resulting parent nodes will be polynomials of degree (level+1) because we multiply by an
* additional factor of X.
*/
static std::vector<FF> construct_coefficients_tree(const std::vector<FF>& betas,
const std::vector<FF>& deltas,
static std::vector<FF> construct_coefficients_tree(std::span<const FF> betas,
std::span<const FF> deltas,
const std::vector<std::vector<FF>>& prev_level_coeffs,
size_t level = 1)
{
Expand Down Expand Up @@ -142,8 +142,8 @@ template <class DeciderProvingKeys_> class ProtogalaxyProverInternal {
* TODO(https://github.com/AztecProtocol/barretenberg/issues/745): make computation of perturbator more memory
* efficient, operate in-place and use std::resize; add multithreading
*/
static std::vector<FF> construct_perturbator_coefficients(const std::vector<FF>& betas,
const std::vector<FF>& deltas,
static std::vector<FF> construct_perturbator_coefficients(std::span<const FF> betas,
std::span<const FF> deltas,
const std::vector<FF>& full_honk_evaluations)
{
auto width = full_honk_evaluations.size();
Expand Down Expand Up @@ -171,7 +171,18 @@ template <class DeciderProvingKeys_> class ProtogalaxyProverInternal {
accumulator->proving_key.polynomials, accumulator->alphas, accumulator->relation_parameters);
const auto betas = accumulator->gate_challenges;
ASSERT(betas.size() == deltas.size());
return Polynomial<FF>(construct_perturbator_coefficients(betas, deltas, full_honk_evaluations));
const size_t log_circuit_size = accumulator->proving_key.log_circuit_size;

// Compute the perturbator using only the first log_circuit_size-many betas/deltas
std::vector<FF> perturbator = construct_perturbator_coefficients(std::span{ betas.data(), log_circuit_size },
std::span{ deltas.data(), log_circuit_size },
full_honk_evaluations);

// Populate the remaining coefficients with zeros to reach the required constant size
for (size_t idx = log_circuit_size; idx < CONST_PG_LOG_N; ++idx) {
perturbator.emplace_back(FF(0));
}
return Polynomial<FF>{ perturbator };
}

/**
Expand Down Expand Up @@ -514,4 +525,4 @@ template <class DeciderProvingKeys_> class ProtogalaxyProverInternal {
return result;
}
};
} // namespace bb
} // namespace bb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ void ProtogalaxyVerifier_<DeciderVerificationKeys>::run_oink_verifier_on_each_in
if (!key->is_accumulator) {
run_oink_verifier_on_one_incomplete_key(key, domain_separator);
key->target_sum = 0;
key->gate_challenges = std::vector<FF>(static_cast<size_t>(key->verification_key->log_circuit_size), 0);
key->gate_challenges = std::vector<FF>(static_cast<size_t>(CONST_PG_LOG_N), 0);
}
index++;

Expand Down Expand Up @@ -74,19 +74,15 @@ std::shared_ptr<typename DeciderVerificationKeys::DeciderVK> ProtogalaxyVerifier
static constexpr size_t NUM_KEYS = DeciderVerificationKeys::NUM;

const std::shared_ptr<const DeciderVK>& accumulator = keys_to_fold[0];
const size_t log_circuit_size = static_cast<size_t>(accumulator->verification_key->log_circuit_size);

run_oink_verifier_on_each_incomplete_key(proof);

// Perturbator round
const FF delta = transcript->template get_challenge<FF>("delta");
const std::vector<FF> deltas = compute_round_challenge_pows(log_circuit_size, delta);
std::vector<FF> perturbator_coeffs(log_circuit_size + 1, 0);
if (accumulator->is_accumulator) {
for (size_t idx = 1; idx <= log_circuit_size; idx++) {
perturbator_coeffs[idx] =
transcript->template receive_from_prover<FF>("perturbator_" + std::to_string(idx));
}
const std::vector<FF> deltas = compute_round_challenge_pows(CONST_PG_LOG_N, delta);
std::vector<FF> perturbator_coeffs(CONST_PG_LOG_N + 1, 0);
for (size_t idx = 1; idx <= CONST_PG_LOG_N; idx++) {
perturbator_coeffs[idx] = transcript->template receive_from_prover<FF>("perturbator_" + std::to_string(idx));
}
const FF perturbator_challenge = transcript->template get_challenge<FF>("perturbator_challenge");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ void ProtogalaxyRecursiveVerifier_<DeciderVerificationKeys>::run_oink_verifier_o
if (!key->is_accumulator) {
run_oink_verifier_on_one_incomplete_key(key, domain_separator);
key->target_sum = 0;
key->gate_challenges = std::vector<FF>(static_cast<size_t>(key->verification_key->log_circuit_size), 0);
key->gate_challenges = std::vector<FF>(static_cast<size_t>(CONST_PG_LOG_N), 0);
}
index++;

Expand All @@ -47,17 +47,13 @@ std::shared_ptr<typename DeciderVerificationKeys::DeciderVK> ProtogalaxyRecursiv
run_oink_verifier_on_each_incomplete_key(proof);

std::shared_ptr<DeciderVK> accumulator = keys_to_fold[0];
const size_t log_circuit_size = static_cast<size_t>(accumulator->verification_key->log_circuit_size);

// Perturbator round
const FF delta = transcript->template get_challenge<FF>("delta");
const std::vector<FF> deltas = compute_round_challenge_pows(log_circuit_size, delta);
std::vector<FF> perturbator_coeffs(log_circuit_size + 1, 0);
if (accumulator->is_accumulator) {
for (size_t idx = 1; idx <= log_circuit_size; idx++) {
perturbator_coeffs[idx] =
transcript->template receive_from_prover<FF>("perturbator_" + std::to_string(idx));
}
const std::vector<FF> deltas = compute_round_challenge_pows(CONST_PG_LOG_N, delta);
std::vector<FF> perturbator_coeffs(CONST_PG_LOG_N + 1, 0);
for (size_t idx = 1; idx <= CONST_PG_LOG_N; idx++) {
perturbator_coeffs[idx] = transcript->template receive_from_prover<FF>("perturbator_" + std::to_string(idx));
}
const FF perturbator_challenge = transcript->template get_challenge<FF>("perturbator_challenge");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,60 @@ template <typename RecursiveFlavor> class ProtogalaxyRecursiveTests : public tes
// Validate that the target sum between prover and verifier is now different
EXPECT_FALSE(folding_proof.accumulator->target_sum == recursive_verifier_acc->target_sum.get_value());
};

// Ensure that the PG recursive verifier circuit is independent of the size of the circuits being folded
static void test_constant_pg_verifier_circuit()
{
struct ProofAndVerifier {
HonkProof fold_proof;
OuterBuilder verifier_circuit;
};

// Fold two circuits of a given size then construct a recursive PG verifier circuit
auto produce_proof_and_verifier_circuit = [](size_t log_num_gates) -> ProofAndVerifier {
InnerBuilder builder1;
create_function_circuit(builder1, log_num_gates);
InnerBuilder builder2;
create_function_circuit(builder2, log_num_gates);

// Generate a folding proof
auto decider_pk_1 = std::make_shared<InnerDeciderProvingKey>(builder1);
auto decider_pk_2 = std::make_shared<InnerDeciderProvingKey>(builder2);
InnerFoldingProver folding_prover({ decider_pk_1, decider_pk_2 });
auto fold_result = folding_prover.prove();

// Create a folding verifier circuit
auto honk_vk_1 = std::make_shared<InnerVerificationKey>(decider_pk_1->proving_key);
auto honk_vk_2 = std::make_shared<InnerVerificationKey>(decider_pk_2->proving_key);
OuterBuilder verifier_circuit;
auto recursive_decider_vk_1 =
std::make_shared<RecursiveDeciderVerificationKey>(&verifier_circuit, honk_vk_1);
auto recursive_decider_vk_2 = std::make_shared<RecursiveVerificationKey>(&verifier_circuit, honk_vk_2);
StdlibProof<OuterBuilder> stdlib_proof = bb::convert_proof_to_witness(&verifier_circuit, fold_result.proof);

auto verifier =
FoldingRecursiveVerifier{ &verifier_circuit, recursive_decider_vk_1, { recursive_decider_vk_2 } };
auto recursive_verifier_accumulator = verifier.verify_folding_proof(stdlib_proof);

return { fold_result.proof, verifier_circuit };
};

// Create fold proofs and verifier circuits from folding circuits of different sizes
auto [proof_1, verifier_circuit_1] = produce_proof_and_verifier_circuit(10);
auto [proof_2, verifier_circuit_2] = produce_proof_and_verifier_circuit(11);

EXPECT_TRUE(CircuitChecker::check(verifier_circuit_1));
EXPECT_TRUE(CircuitChecker::check(verifier_circuit_2));

// Check that the proofs are the same size and that the verifier circuits have the same number of gates
EXPECT_EQ(proof_1.size(), proof_2.size());
EXPECT_EQ(verifier_circuit_1.get_num_gates(), verifier_circuit_2.get_num_gates());

// The circuit blocks (selectors + wires) fully determine the circuit - check that they are identical
if constexpr (!IsSimulator<OuterBuilder>) {
EXPECT_EQ(verifier_circuit_1.blocks, verifier_circuit_2.blocks);
}
}
};

using FlavorTypes =
Expand Down Expand Up @@ -449,4 +503,9 @@ TYPED_TEST(ProtogalaxyRecursiveTests, TamperedAccumulator)
TestFixture::test_tampered_accumulator();
}

TYPED_TEST(ProtogalaxyRecursiveTests, ConstantVerifierCircuit)
{
TestFixture::test_constant_pg_verifier_circuit();
}

} // namespace bb::stdlib::recursion::honk

0 comments on commit 09e2f44

Please sign in to comment.