Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
verifier.test.cpp
Go to the documentation of this file.
10
11#include <gtest/gtest.h>
12
13namespace bb::avm2::constraining {
14
15class AvmVerifierTests : public ::testing::Test {
16 public:
19
21
22 // Helper function to create and verify native proof
27
28 // Helper function to create proof.
30 {
31 auto [trace, public_inputs] = testing::get_minimal_trace_with_pi();
32
33 Prover prover;
34 auto public_inputs_cols = public_inputs.to_columns();
35 const auto proof = prover.prove(std::move(trace));
36
37 return { proof, public_inputs_cols };
38 }
39};
40
41TEST_F(AvmVerifierTests, GoodPublicInputs)
42{
44 GTEST_SKIP() << "Skipping slow test";
45 }
46
47 auto [proof, public_inputs_cols] = create_proof();
48
49 Verifier verifier;
50 const bool verified = verifier.verify_proof(proof, public_inputs_cols);
51
52 ASSERT_TRUE(verified) << "native proof verification failed";
53}
54
55TEST_F(AvmVerifierTests, NegativeBadPublicInputs)
56{
58 GTEST_SKIP() << "Skipping slow test";
59 }
60
61 auto [proof, public_inputs_cols] = create_proof();
62 auto verify_with_corrupt_pi_col = [&](size_t col_idx) {
63 public_inputs_cols[col_idx][5] += FF::one();
64 Verifier verifier;
65 const bool verified = verifier.verify_proof(proof, public_inputs_cols);
66 ASSERT_FALSE(verified)
67 << "native proof verification succeeded, but should have failed due to corruption of public inputs col "
68 << col_idx;
69 public_inputs_cols[col_idx][5] -= FF::one(); // reset
70 };
71 for (size_t col_idx = 0; col_idx < 4; col_idx++) {
72 verify_with_corrupt_pi_col(col_idx);
73 }
74 Verifier verifier;
75 const bool verified = verifier.verify_proof(proof, public_inputs_cols);
76 ASSERT_TRUE(verified) << "native proof verification failed, but should have succeeded";
77}
78
79// Attacker simulation: commit honestly to keccak_memory_addr, but in sumcheck use a DIFFERENT
80// (independent) polynomial for keccak_memory_addr_shift whose last-row value is non-zero.
81// The honest prover's `key_poly.shifted()` shares memory with the unshifted polynomial and its
82// end_index is (unshifted.end_index - 1) <= N - 1, so the last-row shifted value is always past
83// the shifted polynomial's end and thus virtually zero.
84// A malicious prover can replace this shifted view after AvmProver construction to try and
85// smuggle a non-zero value at the last row. This test verifies that the PCS (Shplemini) catches
86// the mismatch between the malicious shifted evaluation used in sumcheck and the real shift of
87// the commitment, causing the verifier to reject.
88TEST_F(AvmVerifierTests, ProvingSystemSecurityShiftedLastRowMustBeZero)
89{
91 GTEST_SKIP() << "Skipping slow test";
92 }
93
94 auto [trace, public_inputs] = testing::get_minimal_trace_with_pi();
95 // Capture the number of witness rows before compute_polynomials consumes the trace.
96 const size_t num_witness_rows = trace.get_num_witness_rows() + 1;
97
98 auto polynomials = constraining::compute_polynomials(trace);
99 auto proving_key = constraining::proving_key_from_polynomials(polynomials);
100 auto verification_key = std::make_shared<AvmVerifier::VerificationKey>();
101
102 AvmProver prover(proving_key, verification_key, proving_key->commitment_key);
103
104 // Attacker: overwrite the shifted view with an independent polynomial carrying a non-zero
105 // value at the last row of the circuit. The shared-memory link to the unshifted polynomial
106 // is severed, so the unshifted commitment no longer "agrees" with what sumcheck uses.
108 auto make_malicious_shift = [] {
109 Polynomial p(/*size=*/1, /*virtual_size=*/MAX_AVM_TRACE_SIZE, /*start_index=*/MAX_AVM_TRACE_SIZE - 1);
110 p.at(MAX_AVM_TRACE_SIZE - 1) = FF(FF::modulus - 1);
111 return p;
112 };
113 prover.prover_polynomials.get(ColumnAndShifts::keccak_memory_addr_shift) = make_malicious_shift();
114
115 // Sanity: all relations (main + lookup/permutation) still hold with the attacker's
116 // polynomials. This demonstrates that any subsequent verification failure is NOT due to a
117 // relation violation but to the proving system's cryptographic shift-consistency check
118 // catching the forged shifted value.
119 AvmFlavor::ProverPolynomials check_polys(*proving_key);
120 check_polys.get(ColumnAndShifts::keccak_memory_addr_shift) = make_malicious_shift();
121 ASSERT_NO_THROW(constraining::run_check_circuit(check_polys, num_witness_rows, /*skippable_enabled=*/true));
122
123 const auto proof = prover.construct_proof();
124
125 Verifier verifier;
126 const bool verified = verifier.verify_proof(proof, public_inputs.to_columns());
127
128 ASSERT_FALSE(verified)
129 << "verifier accepted a proof where keccak_memory_addr_shift at the last row was forged to be non-zero";
130}
131
132// Symmetric attacker simulation for the UNSHIFTED polynomial at its first row (index 0).
133// Unlike the shifted-last-row case, this test is DISABLED by default because it cannot run
134// against an unmodified barretenberg tree. The attack path uses a polynomial with
135// start_index = 0, which triggers invariants enforced by the honest prover's polynomial
136// library. To run this test the following safeguards in
137// `barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp` must be relaxed:
138//
139// 1. `Polynomial::add_scaled` (and `Polynomial::operator+=`): the two asserts
140// `BB_ASSERT_LTE(start_index(), other.start_index)` and
141// `BB_ASSERT_GTE(end_index(), other.end_index())` fire during the PCS batching step in
142// `execute_pcs_rounds` because the accumulator has start_index = 1 while the malicious
143// polynomial has start_index = 0. Replace the asserts with a left/right expansion of
144// self's backing memory (using `_clone(..., left_expansion)` /
145// `_clone(..., right_expansion)`) so that the malicious value at index 0 contributes
146// to the batched polynomial consistently.
147//
148// 2. `Polynomial::shifted`: asserts `start_ >= 1` because the Gemini shift
149// (`A_0 = F + G/X`) is only well-defined when the polynomial has zero constant term.
150// To keep the attacker-prover running past this point, special-case start_ == 0 by
151// cloning the backing memory and dropping the first element. This is the same
152// algebraic step that, on the verifier side, makes the proof unverifiable: the
153// committed polynomial and the PCS-derived shifted opening cannot both be consistent
154// when f[0] != 0.
155//
156// Once those patches are applied, this test passes (the verifier rejects the forged proof),
157// confirming that the proving system structurally enforces `f[0] = 0` for any polynomial
158// that is referenced in shifted form — independent of any PIL relation — thanks to the
159// non-cyclic multilinear shift in the PCS.
160TEST_F(AvmVerifierTests, DISABLED_ProvingSystemSecurityUnshiftedFirstRowMustBeZero)
161{
163 GTEST_SKIP() << "Skipping slow test";
164 }
165
166 auto [trace, public_inputs] = testing::get_minimal_trace_with_pi();
167 const size_t num_witness_rows = trace.get_num_witness_rows() + 1;
168
169 auto polynomials = constraining::compute_polynomials(trace);
170 auto proving_key = constraining::proving_key_from_polynomials(polynomials);
171 auto verification_key = std::make_shared<AvmVerifier::VerificationKey>();
172
173 AvmProver prover(proving_key, verification_key, proving_key->commitment_key);
174
175 // Attacker: overwrite the unshifted polynomial with an independent polynomial carrying a
176 // non-zero value at index 0. The honest shifted view is left untouched in
177 // prover_polynomials so we can observe what the verifier does with an inconsistent pair.
179 auto make_malicious_addr = [] {
180 Polynomial p(/*size=*/1024, /*virtual_size=*/MAX_AVM_TRACE_SIZE, /*start_index=*/0);
181 p.at(0) = FF(FF::modulus - 1);
182 return p;
183 };
184 prover.prover_polynomials.get(ColumnAndShifts::keccak_memory_addr) = make_malicious_addr();
185
186 // Sanity: all relations (main + lookup/permutation) still hold with the attacker's
187 // polynomials. This demonstrates that any subsequent verification failure is NOT due to a
188 // relation violation but to the proving system's cryptographic shift-consistency check
189 // catching the forged first-row value.
190 AvmFlavor::ProverPolynomials check_polys(*proving_key);
191 check_polys.get(ColumnAndShifts::keccak_memory_addr) = make_malicious_addr();
192 ASSERT_NO_THROW(constraining::run_check_circuit(check_polys, num_witness_rows, /*skippable_enabled=*/true));
193
194 const auto proof = prover.construct_proof();
195
196 Verifier verifier;
197 const bool verified = verifier.verify_proof(proof, public_inputs.to_columns());
198
199 ASSERT_FALSE(verified)
200 << "verifier accepted a proof where keccak_memory_addr at the first row was forged to be non-zero";
201}
202
203// Verify that the actual proof size matches COMPUTED_AVM_PROOF_LENGTH_IN_FIELDS
204TEST_F(AvmVerifierTests, ProofSizeMatchesComputedConstant)
205{
206 auto [proof, public_inputs_cols] = create_proof();
207
208 const size_t actual_proof_size = proof.size();
209 const size_t computed_proof_size = AvmFlavor::COMPUTED_AVM_PROOF_LENGTH_IN_FIELDS;
210
211 EXPECT_EQ(actual_proof_size, computed_proof_size)
212 << "Actual proof size (" << actual_proof_size << ") does not match COMPUTED_AVM_PROOF_LENGTH_IN_FIELDS ("
213 << computed_proof_size << "). The formula in flavor.hpp needs to be updated.";
214}
215
216} // namespace bb::avm2::constraining
Structured polynomial class that represents the coefficients 'a' of a_0 + a_1 x .....
Fr & at(size_t index)
Our mutable accessor, unlike operator[]. We abuse precedent a bit to differentiate at() and operator[...
DataType & get(ColumnAndShifts c)
Definition flavor.hpp:146
A container for the prover polynomials handles.
Definition flavor.hpp:250
static constexpr size_t COMPUTED_AVM_PROOF_LENGTH_IN_FIELDS
Definition flavor.hpp:100
AvmFlavorSettings::Polynomial Polynomial
Definition flavor.hpp:44
ProverPolynomials prover_polynomials
Definition prover.hpp:56
HonkProof construct_proof()
Definition prover.cpp:297
Proof prove(tracegen::TraceContainer &&trace)
static NativeProofResult create_proof()
TestTraceContainer trace
AvmProver::ProverPolynomials compute_polynomials(tracegen::TraceContainer &trace)
std::shared_ptr< AvmProver::ProvingKey > proving_key_from_polynomials(AvmProver::ProverPolynomials &polynomials)
TEST_F(AvmRecursiveTests, TwoLayerAvmRecursionFailsWithWrongPIs)
void run_check_circuit(AvmFlavor::ProverPolynomials &polys, size_t num_rows, bool skippable_enabled)
bool skip_slow_tests()
Check if slow tests should be skipped.
Definition fixtures.cpp:199
std::pair< tracegen::TraceContainer, PublicInputs > get_minimal_trace_with_pi()
Definition fixtures.cpp:183
constexpr std::size_t MAX_AVM_TRACE_SIZE
Definition constants.hpp:13
AvmFlavorSettings::FF FF
Definition field.hpp:10
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
static constexpr uint256_t modulus