Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(avm): Integrate public inputs in AVM recursive verifier #8846

Merged
merged 11 commits into from
Sep 28, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include "barretenberg/stdlib_circuit_builders/ultra_flavor.hpp"
#include "barretenberg/vm/avm/recursion/avm_recursive_flavor.hpp"
#include "barretenberg/vm/avm/recursion/avm_recursive_verifier.hpp"
#include "barretenberg/vm/avm/trace/common.hpp"
#include "barretenberg/vm/avm/trace/helper.hpp"
#include "barretenberg/vm/aztec_constants.hpp"
#include "proof_surgeon.hpp"
#include <cstddef>
Expand Down Expand Up @@ -168,13 +170,28 @@ AggregationObjectIndices create_avm_recursion_constraints(Builder& builder,
// proof with public inputs reinserted std::vector<uint32_t> proof_indices =
// ProofSurgeon::create_indices_for_reconstructed_proof(input.proof, input.public_inputs);

std::vector<field_ct> proof_fields;
proof_fields.reserve(input.proof.size());

for (const auto& idx : input.proof) {
auto field = field_ct::from_witness_index(&builder, idx);
proof_fields.emplace_back(field);
}
auto fields_from_witnesses = [&](std::vector<uint32_t> const& input) {
std::vector<field_ct> result;
result.reserve(input.size());
for (const auto& idx : input) {
auto field = field_ct::from_witness_index(&builder, idx);
result.emplace_back(field);
}
return result;
};

const auto proof_fields = fields_from_witnesses(input.proof);
const auto public_inputs_flattened = fields_from_witnesses(input.public_inputs);

auto it = public_inputs_flattened.begin();
avm_trace::VmPublicInputs<field_ct> vm_public_inputs =
avm_trace::convert_public_inputs(std::vector(it, it + PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH));
it += PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH;
std::vector<field_ct> calldata(it, it + AVM_PUBLIC_COLUMN_MAX_SIZE);
it += AVM_PUBLIC_COLUMN_MAX_SIZE;
std::vector<field_ct> return_data(it, it + AVM_PUBLIC_COLUMN_MAX_SIZE);

auto public_inputs_vectors = avm_trace::copy_public_inputs_columns(vm_public_inputs, calldata, return_data);

// Populate the key fields and proof fields with dummy values to prevent issues (e.g. points must be on curve).
if (!has_valid_witness_assignments) {
Expand All @@ -186,7 +203,7 @@ AggregationObjectIndices create_avm_recursion_constraints(Builder& builder,
RecursiveVerifier verifier(&builder, vkey);
aggregation_state_ct input_agg_obj = bb::stdlib::recursion::convert_witness_indices_to_agg_obj<Builder, bn254>(
builder, input_aggregation_object_indices);
aggregation_state_ct output_agg_object = verifier.verify_proof(proof_fields, input_agg_obj);
aggregation_state_ct output_agg_object = verifier.verify_proof(proof_fields, public_inputs_vectors, input_agg_obj);
// TODO(https://github.com/AztecProtocol/barretenberg/issues/996): investigate whether assert_equal on public
// inputs is important, like what the plonk recursion constraint does.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
#include "barretenberg/vm/avm/generated/prover.hpp"
#include "barretenberg/vm/avm/generated/verifier.hpp"
#include "barretenberg/vm/avm/tests/helpers.test.hpp"
#include "barretenberg/vm/avm/trace/helper.hpp"
#include "barretenberg/vm/avm/trace/trace.hpp"
#include "barretenberg/vm/aztec_constants.hpp"
#include "barretenberg/vm/constants.hpp"
#include "proof_surgeon.hpp"
#include <gtest/gtest.h>
#include <memory>
Expand All @@ -39,14 +42,9 @@ class AcirAvmRecursionConstraint : public ::testing::Test {

static void SetUpTestSuite() { bb::srs::init_crs_factory("../srs_db/ignition"); }

static InnerBuilder create_inner_circuit()
static InnerBuilder create_inner_circuit(const std::vector<FF>& kernel_public_inputs_vec)
{
VmPublicInputs public_inputs;
std::array<FF, KERNEL_INPUTS_LENGTH> kernel_inputs{};
kernel_inputs.at(DA_GAS_LEFT_CONTEXT_INPUTS_OFFSET) = 1000000;
kernel_inputs.at(L2_GAS_LEFT_CONTEXT_INPUTS_OFFSET) = 1000000;
std::get<0>(public_inputs) = kernel_inputs;

auto public_inputs = convert_public_inputs(kernel_public_inputs_vec);
AvmTraceBuilder trace_builder(public_inputs);
InnerBuilder builder;

Expand All @@ -66,7 +64,8 @@ class AcirAvmRecursionConstraint : public ::testing::Test {
/**
* @brief Create a circuit that recursively verifies one or more inner avm circuits
*/
static OuterBuilder create_outer_circuit(std::vector<InnerBuilder>& inner_avm_circuits)
static OuterBuilder create_outer_circuit(std::vector<InnerBuilder>& inner_avm_circuits,
const std::vector<FF>& kernel_public_inputs_vec)
{
std::vector<RecursionConstraint> avm_recursion_constraints;

Expand All @@ -80,6 +79,9 @@ class AcirAvmRecursionConstraint : public ::testing::Test {
std::vector<fr> key_witnesses = verifier.key->to_field_elements();
std::vector<fr> proof_witnesses = prover.construct_proof();

std::vector<FF> public_inputs_vec(kernel_public_inputs_vec);
public_inputs_vec.resize(AVM_PUBLIC_INPUTS_FLATTENED_SIZE);

// Helper to append some values to the witness vector and return their corresponding indices
auto add_to_witness_and_track_indices =
[&witness](const std::vector<bb::fr>& input) -> std::vector<uint32_t> {
Expand All @@ -96,7 +98,7 @@ class AcirAvmRecursionConstraint : public ::testing::Test {
RecursionConstraint avm_recursion_constraint{
.key = add_to_witness_and_track_indices(key_witnesses),
.proof = add_to_witness_and_track_indices(proof_witnesses),
.public_inputs = {},
.public_inputs = add_to_witness_and_track_indices(public_inputs_vec),
.key_hash = 0, // not used
.proof_type = AVM,
};
Expand All @@ -121,9 +123,14 @@ class AcirAvmRecursionConstraint : public ::testing::Test {

TEST_F(AcirAvmRecursionConstraint, TestBasicSingleAvmRecursionConstraint)
{
std::vector<FF> public_inputs_vec;
public_inputs_vec.resize(PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH);
public_inputs_vec.at(L2_START_GAS_LEFT_PCPI_OFFSET) = FF(1000000);
public_inputs_vec.at(DA_START_GAS_LEFT_PCPI_OFFSET) = FF(1000000);

std::vector<InnerBuilder> layer_1_circuits;
layer_1_circuits.push_back(create_inner_circuit());
auto layer_2_circuit = create_outer_circuit(layer_1_circuits);
layer_1_circuits.push_back(create_inner_circuit(public_inputs_vec));
auto layer_2_circuit = create_outer_circuit(layer_1_circuits, public_inputs_vec);

info("circuit gates = ", layer_2_circuit.get_num_gates());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@

namespace bb {

// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
template <typename Fr> std::shared_ptr<Fr[]> _allocate_aligned_memory(const size_t n_elements)
{
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
return std::static_pointer_cast<Fr[]>(get_mem_slab(sizeof(Fr) * n_elements));
}

template <typename Fr> void LegacyPolynomial<Fr>::allocate_backing_memory(size_t n_elements)
{
size_ = n_elements;
Expand Down
61 changes: 1 addition & 60 deletions barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@

namespace bb {

// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
template <typename Fr> std::shared_ptr<Fr[]> _allocate_aligned_memory(size_t n_elements)
{
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
return std::static_pointer_cast<Fr[]>(get_mem_slab(sizeof(Fr) * n_elements));
}

// Note: This function is pretty gnarly, but we try to make it the only function that deals
// with copying polynomials. It should be scrutinized thusly.
template <typename Fr>
Expand Down Expand Up @@ -187,58 +180,7 @@ template <typename Fr> Fr Polynomial<Fr>::evaluate(const Fr& z) const

template <typename Fr> Fr Polynomial<Fr>::evaluate_mle(std::span<const Fr> evaluation_points, bool shift) const
{
if (size() == 0) {
return Fr(0);
}

const size_t n = evaluation_points.size();
const size_t dim = numeric::get_msb(end_index() - 1) + 1; // Round up to next power of 2

// To simplify handling of edge cases, we assume that the index space is always a power of 2
ASSERT(virtual_size() == static_cast<size_t>(1 << n));

// We first fold over dim rounds l = 0,...,dim-1.
// in round l, n_l is the size of the buffer containing the Polynomial partially evaluated
// at u₀,..., u_l.
// In round 0, this is half the size of dim
size_t n_l = 1 << (dim - 1);

// temporary buffer of half the size of the Polynomial
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1096): Make this a Polynomial with DontZeroMemory::FLAG
auto tmp_ptr = _allocate_aligned_memory<Fr>(sizeof(Fr) * n_l);
auto tmp = tmp_ptr.get();

size_t offset = 0;
if (shift) {
ASSERT((*this)[0] == Fr::zero());
offset++;
}

Fr u_l = evaluation_points[0];
for (size_t i = 0; i < n_l; ++i) {
// curr[i] = (Fr(1) - u_l) * prev[i * 2] + u_l * prev[(i * 2) + 1];
// Note: i * 2 + 1 + offset might equal virtual_size. This used to subtlely be handled by extra capacity padding
// (and there used to be no assert time checks, which this constant helps with).
const size_t ALLOW_ONE_PAST_READ = 1;
tmp[i] = get(i * 2 + offset) + u_l * (get(i * 2 + 1 + offset, ALLOW_ONE_PAST_READ) - get(i * 2 + offset));
}

// partially evaluate the dim-1 remaining points
for (size_t l = 1; l < dim; ++l) {
n_l = 1 << (dim - l - 1);
u_l = evaluation_points[l];
for (size_t i = 0; i < n_l; ++i) {
tmp[i] = tmp[i * 2] + u_l * (tmp[(i * 2) + 1] - tmp[i * 2]);
}
}
auto result = tmp[0];

// We handle the "trivial" dimensions which are full of zeros.
for (size_t i = dim; i < n; i++) {
result *= (Fr(1) - evaluation_points[i]);
}

return result;
return _evaluate_mle(evaluation_points, coefficients_, shift);
}

template <typename Fr> Polynomial<Fr> Polynomial<Fr>::partial_evaluate_mle(std::span<const Fr> evaluation_points) const
Expand Down Expand Up @@ -384,5 +326,4 @@ template <typename Fr> Polynomial<Fr> Polynomial<Fr>::shifted() const

template class Polynomial<bb::fr>;
template class Polynomial<grumpkin::fr>;

} // namespace bb
93 changes: 90 additions & 3 deletions barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
#include "barretenberg/common/zip_view.hpp"
#include "barretenberg/crypto/sha256/sha256.hpp"
#include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp"
#include "barretenberg/plonk_honk_shared/types/circuit_type.hpp"
#include "barretenberg/polynomials/shared_shifted_virtual_zeroes_array.hpp"
#include "evaluation_domain.hpp"
#include "polynomial_arithmetic.hpp"
#include <cstddef>
#include <fstream>
#include <ranges>

namespace bb {

/* Span class with a start index offset.
Expand Down Expand Up @@ -363,7 +363,95 @@ template <typename Fr> class Polynomial {
SharedShiftedVirtualZeroesArray<Fr> coefficients_;
};

template <typename Fr> inline std::ostream& operator<<(std::ostream& os, Polynomial<Fr> const& p)
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
template <typename Fr> std::shared_ptr<Fr[]> _allocate_aligned_memory(size_t n_elements)
{
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
return std::static_pointer_cast<Fr[]>(get_mem_slab(sizeof(Fr) * n_elements));
}

/**
* @brief Internal implementation to support both native and stdlib circuit field types.
* @details Instantiation with circuit field type is always called with shift == false
*/
template <typename Fr_>
Fr_ _evaluate_mle(std::span<const Fr_> evaluation_points,
const SharedShiftedVirtualZeroesArray<Fr_>& coefficients,
bool shift)
{
constexpr bool is_native = IsAnyOf<Fr_, bb::fr, grumpkin::fr>;
// shift ==> native
ASSERT(!shift || is_native);

if (coefficients.size() == 0) {
return Fr_(0);
}

const size_t n = evaluation_points.size();
const size_t dim = numeric::get_msb(coefficients.end_ - 1) + 1; // Round up to next power of 2

// To simplify handling of edge cases, we assume that the index space is always a power of 2
ASSERT(coefficients.virtual_size() == static_cast<size_t>(1 << n));

// We first fold over dim rounds l = 0,...,dim-1.
// in round l, n_l is the size of the buffer containing the Polynomial partially evaluated
// at u₀,..., u_l.
// In round 0, this is half the size of dim
size_t n_l = 1 << (dim - 1);

// temporary buffer of half the size of the Polynomial
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1096): Make this a Polynomial with DontZeroMemory::FLAG
auto tmp_ptr = _allocate_aligned_memory<Fr_>(sizeof(Fr_) * n_l);
auto tmp = tmp_ptr.get();

size_t offset = 0;
if constexpr (is_native) {
if (shift) {
ASSERT(coefficients.get(0) == Fr_::zero());
offset++;
}
}

Fr_ u_l = evaluation_points[0];

// Note below: i * 2 + 1 + offset might equal virtual_size. This used to subtlely be handled by extra capacity
// padding (and there used to be no assert time checks, which this constant helps with).
const size_t ALLOW_ONE_PAST_READ = 1;
for (size_t i = 0; i < n_l; ++i) {
// curr[i] = (Fr(1) - u_l) * prev[i * 2] + u_l * prev[(i * 2) + 1];
tmp[i] = coefficients.get(i * 2 + offset) +
u_l * (coefficients.get(i * 2 + 1 + offset, ALLOW_ONE_PAST_READ) - coefficients.get(i * 2 + offset));
}

// partially evaluate the dim-1 remaining points
for (size_t l = 1; l < dim; ++l) {
n_l = 1 << (dim - l - 1);
u_l = evaluation_points[l];
for (size_t i = 0; i < n_l; ++i) {
tmp[i] = tmp[i * 2] + u_l * (tmp[(i * 2) + 1] - tmp[i * 2]);
}
}
auto result = tmp[0];

// We handle the "trivial" dimensions which are full of zeros.
for (size_t i = dim; i < n; i++) {
result *= (Fr_(1) - evaluation_points[i]);
}

return result;
}

/**
* @brief Static exposed implementation to support both native and stdlib circuit field types.
*/
template <typename Fr_>
Fr_ generic_evaluate_mle(std::span<const Fr_> evaluation_points,
const SharedShiftedVirtualZeroesArray<Fr_>& coefficients)
{
return _evaluate_mle(evaluation_points, coefficients, false);
}

template <typename Fr> inline std::ostream& operator<<(std::ostream& os, const Polynomial<Fr>& p)
{
if (p.size() == 0) {
return os << "[]";
Expand All @@ -387,5 +475,4 @@ template <typename Poly, typename... Polys> auto zip_polys(Poly&& poly, Polys&&.
ASSERT((poly.start_index() == polys.start_index() && poly.end_index() == polys.end_index()) && ...);
return zip_view(poly.indices(), poly.coeffs(), polys.coeffs()...);
}

} // namespace bb
Original file line number Diff line number Diff line change
Expand Up @@ -438,12 +438,6 @@ template <typename Builder> bool_t<Builder> bool_t<Builder>::implies(const bool_
return (!(*this) || other); // P => Q is equiv. to !P || Q (not(P) or Q).
}

/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did this go away because it's incorrect?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seemed to have been duplicated (maybe wrong merge/rebase resolution). The very same comment is on top of the next method and in this case the comment makes sense. Here, I did not see the point and I was misled until I realized that it is a duplicated comment.

* (P => Q) && (P => R)
* <=> (!P || Q) && (!P || R)
* <=> !P || (Q && R) [by distributivity of propositional logic]
* <=> P => (Q && R) [a.k.a. distribution of implication over conjunction]
*/
template <typename Builder> void bool_t<Builder>::must_imply(const bool_t& other, std::string const& msg) const
{
(this->implies(other)).assert_equal(true, msg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ inline FF AvmVerifier::evaluate_public_input_column(const std::vector<FF>& point
* @brief This function verifies an Avm Honk proof for given program settings.
*
*/
bool AvmVerifier::verify_proof(const HonkProof& proof,
[[maybe_unused]] const std::vector<std::vector<FF>>& public_inputs)
jeanmon marked this conversation as resolved.
Show resolved Hide resolved
bool AvmVerifier::verify_proof(const HonkProof& proof, const std::vector<std::vector<FF>>& public_inputs)
{
using Flavor = AvmFlavor;
using FF = Flavor::FF;
Expand Down
Loading
Loading