36constexpr size_t LOG_N_BASE = 5;
43constexpr size_t SEED_FRS = 1;
46constexpr size_t EVALS_OFFSET = SEED_FRS + (VIRTUAL_LOG_N * UNIVARIATE_LENGTH);
56 FALSE_NONSHIFTED_EVAL_FIRST,
57 FALSE_NONSHIFTED_EVAL_SECOND,
58 FALSE_SHIFTED_EVAL_FIRST,
59 FALSE_SHIFTED_EVAL_SECOND,
65 TAMPER_NONSHIFTED_EVAL_FIRST,
66 TAMPER_NONSHIFTED_EVAL_SECOND,
67 TAMPER_SHIFTED_EVAL_FIRST,
68 TAMPER_SHIFTED_EVAL_SECOND,
70 TAMPER_EQ_EVAL_SECOND,
71 WRONG_NONSHIFTED_COMMITMENT,
72 WRONG_SHIFTED_COMMITMENT,
81FF mle_padded(
const Polynomial<FF>& poly,
const std::vector<FF>& r,
size_t log_n,
bool shift =
false)
83 std::vector<FF> head(r.begin(), r.begin() +
static_cast<std::ptrdiff_t>(log_n));
85 for (
size_t j = log_n; j < r.size(); ++j) {
100 std::vector<size_t> log_ns;
106ClaimSet build_honest_claims(
size_t num_claims)
110 const size_t max_dyadic_size = 1UL << (LOG_N_BASE + num_claims - 1);
114 for (
size_t i = 0; i < num_claims; ++i) {
115 const size_t log_n = LOG_N_BASE + i;
116 const size_t dyadic_size = 1UL << log_n;
119 std::vector<FF> challenge(VIRTUAL_LOG_N);
120 for (
auto& c : challenge) {
127 const FF non_shifted_eval = mle_padded(non_shifted, challenge, log_n);
128 const FF shifted_eval = mle_padded(shifted, challenge, log_n,
true);
129 const Commitment non_shifted_commitment = commitment_key.commit(non_shifted);
130 const Commitment shifted_commitment = commitment_key.commit(shifted);
132 set.non_shifted_polynomials.push_back(non_shifted);
133 set.shifted_polynomials.push_back(shifted);
134 set.log_ns.push_back(log_n);
136 set.prover_claims.push_back(ProverClaim{ .challenge = challenge,
137 .non_shifted_evaluation = non_shifted_eval,
138 .shifted_evaluation = shifted_eval,
139 .non_shifted_polynomial =
std::move(non_shifted),
140 .shifted_polynomial =
std::move(shifted),
141 .non_shifted_commitment = non_shifted_commitment,
142 .shifted_commitment = shifted_commitment,
143 .dyadic_size = dyadic_size });
144 set.verifier_claims.push_back(VerifierClaim{ .challenge = challenge,
145 .non_shifted_evaluation = non_shifted_eval,
146 .shifted_evaluation = shifted_eval,
147 .non_shifted_commitment = non_shifted_commitment,
148 .shifted_commitment = shifted_commitment });
159struct DerivedChallenges {
165DerivedChallenges replay_challenges(
const HonkProof& proof,
size_t num_claims)
168 transcript->load_proof(proof);
169 [[maybe_unused]]
FF seed = transcript->template receive_from_prover<FF>(
"init");
171 DerivedChallenges
out;
172 out.gamma = transcript->template get_challenge<FF>(
"claim_batching_challenge");
173 [[maybe_unused]]
FF alpha = transcript->template get_challenge<FF>(
"Sumcheck:alpha");
174 for (
size_t round = 0; round < VIRTUAL_LOG_N; ++round) {
175 for (
size_t e = 0; e < UNIVARIATE_LENGTH; ++e) {
176 [[maybe_unused]]
FF _ = transcript->template receive_from_prover<FF>(
"u");
178 out.r.push_back(transcript->template get_challenge<FF>(
"Sumcheck:u_" +
std::to_string(round)));
180 for (
size_t e = 0; e < 3 * num_claims; ++e) {
181 [[maybe_unused]]
FF _ = transcript->template receive_from_prover<FF>(
"e");
183 out.rho = transcript->template get_challenge<FF>(
"claim_merge_challenge");
192bool output_claim_is_bound(
const ClaimSet& set,
const HonkProof& proof,
const VerifierClaim& new_claim)
194 auto powers = [](
const FF& base,
size_t count) {
195 std::vector<FF>
result(count);
197 for (
size_t i = 1; i < count; ++i) {
203 const size_t num_claims = set.verifier_claims.size();
204 const DerivedChallenges challenges = replay_challenges(proof, num_claims);
205 const std::vector<FF> rho_powers = powers(challenges.rho, num_claims);
207 FF expected_non_shifted_eval(0);
208 FF expected_shifted_eval(0);
209 std::vector<Commitment> non_shifted_commitments;
210 std::vector<Commitment> shifted_commitments;
211 for (
size_t i = 0; i < num_claims; ++i) {
212 expected_non_shifted_eval +=
213 rho_powers[i] * mle_padded(set.non_shifted_polynomials[i], challenges.r, set.log_ns[i]);
214 expected_shifted_eval +=
215 rho_powers[i] * mle_padded(set.shifted_polynomials[i], challenges.r, set.log_ns[i],
true);
216 non_shifted_commitments.push_back(set.verifier_claims[i].non_shifted_commitment);
217 shifted_commitments.push_back(set.verifier_claims[i].shifted_commitment);
219 std::vector<FF> scalars = rho_powers;
220 const Commitment expected_non_shifted_commitment = Curve::Element::batch_mul(non_shifted_commitments, scalars);
221 const Commitment expected_shifted_commitment = Curve::Element::batch_mul(shifted_commitments, scalars);
223 return new_claim.challenge == challenges.r && new_claim.non_shifted_evaluation == expected_non_shifted_eval &&
224 new_claim.shifted_evaluation == expected_shifted_eval &&
225 new_claim.non_shifted_commitment == expected_non_shifted_commitment &&
226 new_claim.shifted_commitment == expected_shifted_commitment;
239FaultyProof build_faulty_proof(
size_t num_claims,
FaultMode fault)
241 ClaimSet set = build_honest_claims(num_claims);
245 if (fault == FaultMode::FALSE_NONSHIFTED_EVAL_FIRST) {
246 verifier_claims[0].non_shifted_evaluation +=
FF(1);
247 }
else if (fault == FaultMode::FALSE_NONSHIFTED_EVAL_SECOND) {
248 verifier_claims[1].non_shifted_evaluation +=
FF(1);
249 }
else if (fault == FaultMode::FALSE_SHIFTED_EVAL_FIRST) {
250 verifier_claims[0].shifted_evaluation +=
FF(1);
251 }
else if (fault == FaultMode::FALSE_SHIFTED_EVAL_SECOND) {
252 verifier_claims[1].shifted_evaluation +=
FF(1);
253 }
else if (fault == FaultMode::FALSE_EQ_FIRST) {
254 verifier_claims[0].challenge[0] +=
FF(1);
255 }
else if (fault == FaultMode::FALSE_EQ_SECOND) {
256 verifier_claims[1].challenge[0] +=
FF(1);
257 }
else if (fault == FaultMode::WRONG_NONSHIFTED_COMMITMENT) {
258 verifier_claims[0].non_shifted_commitment = verifier_claims[0].non_shifted_commitment + Commitment::one();
259 }
else if (fault == FaultMode::WRONG_SHIFTED_COMMITMENT) {
260 verifier_claims[0].shifted_commitment = verifier_claims[0].shifted_commitment + Commitment::one();
267 HonkProof proof = prover.construct_proof();
272 if (fault == FaultMode::TAMPER_NONSHIFTED_EVAL_FIRST) {
273 proof[EVALS_OFFSET + 0] +=
FF(1);
274 }
else if (fault == FaultMode::TAMPER_NONSHIFTED_EVAL_SECOND) {
275 proof[EVALS_OFFSET + 1] +=
FF(1);
276 }
else if (fault == FaultMode::TAMPER_SHIFTED_EVAL_FIRST) {
277 proof[EVALS_OFFSET + num_claims + 0] +=
FF(1);
278 }
else if (fault == FaultMode::TAMPER_SHIFTED_EVAL_SECOND) {
279 proof[EVALS_OFFSET + num_claims + 1] +=
FF(1);
280 }
else if (fault == FaultMode::TAMPER_EQ_EVAL_FIRST) {
281 proof[EVALS_OFFSET + (2 * num_claims) + 0] +=
FF(1);
282 }
else if (fault == FaultMode::TAMPER_EQ_EVAL_SECOND) {
283 proof[EVALS_OFFSET + (2 * num_claims) + 1] +=
FF(1);
284 }
else if (fault == FaultMode::BREAK_EVAL_BINDING) {
292 const DerivedChallenges challenges = replay_challenges(proof, num_claims);
293 std::array<FF, 3> eq;
296 for (
size_t i = 0; i < 3; ++i) {
299 b[i] =
b[i - 1] * challenges.gamma;
302 const std::array<FF, 3>
a{
b[0] * eq[0],
b[1] * eq[1],
b[2] * eq[2] };
303 const std::array<FF, 3> delta{
a[1] *
b[2] -
a[2] *
b[1],
304 a[2] *
b[0] -
a[0] *
b[2],
305 a[0] *
b[1] -
a[1] *
b[0] };
306 for (
size_t i = 0; i < 3; ++i) {
307 proof[EVALS_OFFSET + i] += delta[i];
314template <
bool Recursive,
size_t Claims>
struct Config {
315 static constexpr bool IsRecursive = Recursive;
316 static constexpr size_t NumClaims = Claims;
319template <
typename Params>
class MultilinearBatchingTests :
public ::testing::Test {
321 static constexpr bool IsRecursive = Params::IsRecursive;
322 static constexpr size_t NumClaims = Params::NumClaims;
330 bool accepted()
const {
return verified && circuit_ok && claim_bound; }
335 FaultyProof faulty = build_faulty_proof(NumClaims, fault);
337 bool verified =
false;
338 bool circuit_ok =
true;
339 VerifierClaim new_claim;
341 if constexpr (!IsRecursive) {
343 transcript->load_proof(faulty.proof);
344 [[maybe_unused]]
FF seed = transcript->template receive_from_prover<FF>(
"init");
346 std::tie(verified, new_claim) = verifier.verify_proof(faulty.verifier_claims);
349 using RecursiveCurve =
typename RecursiveVerifier::Curve;
351 using RecursiveFF =
typename RecursiveCurve::ScalarField;
355 typename RecursiveVerifier::Proof stdlib_proof(
builder, faulty.proof);
356 transcript->load_proof(stdlib_proof);
357 [[maybe_unused]] RecursiveFF seed = transcript->template receive_from_prover<RecursiveFF>(
"init");
360 recursive_claims.reserve(faulty.verifier_claims.size());
361 for (
const auto& claim : faulty.verifier_claims) {
362 RecursiveClaim recursive_claim =
363 RecursiveClaim::template stdlib_from_native<RecursiveCurve>(&
builder, claim);
366 for (
auto& challenge_element : recursive_claim.challenge) {
367 challenge_element.unset_free_witness_tag();
369 recursive_claim.non_shifted_evaluation.unset_free_witness_tag();
370 recursive_claim.shifted_evaluation.unset_free_witness_tag();
371 recursive_claim.non_shifted_commitment.unset_free_witness_tag();
372 recursive_claim.shifted_commitment.unset_free_witness_tag();
373 recursive_claims.push_back(
std::move(recursive_claim));
376 RecursiveVerifier verifier(transcript);
377 auto [verified_in_circuit, recursive_new_claim] = verifier.verify_proof(recursive_claims);
378 verified = verified_in_circuit;
380 new_claim = recursive_new_claim.template get_value<VerifierClaim>();
383 const bool claim_bound = output_claim_is_bound(faulty.set, faulty.proof, new_claim);
384 return { verified, circuit_ok, claim_bound };
390static_assert(CHONK_MAX_CLAIMS_PER_KERNEL == 7,
391 "Update TestConfigs to cover every width in 2 .. CHONK_MAX_CLAIMS_PER_KERNEL.");
392using TestConfigs = ::testing::Types<Config<false, 2>,
408TYPED_TEST(MultilinearBatchingTests, ValidProofPasses)
410 auto result = TestFixture::run(FaultMode::NONE);
411 EXPECT_TRUE(
result.verified);
412 EXPECT_TRUE(
result.circuit_ok);
413 EXPECT_TRUE(
result.claim_bound);
418TYPED_TEST(MultilinearBatchingTests, FalseNonShiftedClaimFirstFails)
421 EXPECT_FALSE(TestFixture::run(FaultMode::FALSE_NONSHIFTED_EVAL_FIRST).accepted());
424TYPED_TEST(MultilinearBatchingTests, FalseNonShiftedClaimSecondFails)
427 EXPECT_FALSE(TestFixture::run(FaultMode::FALSE_NONSHIFTED_EVAL_SECOND).accepted());
432TYPED_TEST(MultilinearBatchingTests, FalseShiftedClaimFirstFails)
435 EXPECT_FALSE(TestFixture::run(FaultMode::FALSE_SHIFTED_EVAL_FIRST).accepted());
438TYPED_TEST(MultilinearBatchingTests, FalseShiftedClaimSecondFails)
441 EXPECT_FALSE(TestFixture::run(FaultMode::FALSE_SHIFTED_EVAL_SECOND).accepted());
446TYPED_TEST(MultilinearBatchingTests, FalseEqFirstFails)
449 EXPECT_FALSE(TestFixture::run(FaultMode::FALSE_EQ_FIRST).accepted());
452TYPED_TEST(MultilinearBatchingTests, FalseEqSecondFails)
455 EXPECT_FALSE(TestFixture::run(FaultMode::FALSE_EQ_SECOND).accepted());
460TYPED_TEST(MultilinearBatchingTests, TamperedNonShiftedEvalFirstFails)
463 EXPECT_FALSE(TestFixture::run(FaultMode::TAMPER_NONSHIFTED_EVAL_FIRST).accepted());
466TYPED_TEST(MultilinearBatchingTests, TamperedNonShiftedEvalSecondFails)
469 EXPECT_FALSE(TestFixture::run(FaultMode::TAMPER_NONSHIFTED_EVAL_SECOND).accepted());
474TYPED_TEST(MultilinearBatchingTests, TamperedShiftedEvalFirstFails)
477 EXPECT_FALSE(TestFixture::run(FaultMode::TAMPER_SHIFTED_EVAL_FIRST).accepted());
480TYPED_TEST(MultilinearBatchingTests, TamperedShiftedEvalSecondFails)
483 EXPECT_FALSE(TestFixture::run(FaultMode::TAMPER_SHIFTED_EVAL_SECOND).accepted());
488TYPED_TEST(MultilinearBatchingTests, TamperedEqEvalFirstFails)
491 EXPECT_FALSE(TestFixture::run(FaultMode::TAMPER_EQ_EVAL_FIRST).accepted());
494TYPED_TEST(MultilinearBatchingTests, TamperedEqEvalSecondFails)
497 EXPECT_FALSE(TestFixture::run(FaultMode::TAMPER_EQ_EVAL_SECOND).accepted());
502TYPED_TEST(MultilinearBatchingTests, WrongNonShiftedCommitment)
504 auto result = TestFixture::run(FaultMode::WRONG_NONSHIFTED_COMMITMENT);
505 EXPECT_TRUE(
result.verified);
506 EXPECT_TRUE(
result.circuit_ok);
507 EXPECT_FALSE(
result.claim_bound);
512TYPED_TEST(MultilinearBatchingTests, WrongShiftedCommitment)
514 auto result = TestFixture::run(FaultMode::WRONG_SHIFTED_COMMITMENT);
515 EXPECT_TRUE(
result.verified);
516 EXPECT_TRUE(
result.circuit_ok);
517 EXPECT_FALSE(
result.claim_bound);
522TYPED_TEST(MultilinearBatchingTests, BrokenEvalBindingIsCaughtByMerge)
524 if (TestFixture::NumClaims < 3) {
525 GTEST_SKIP() <<
"The eval-binding attack needs >= 3 claims (2 constraints, 3 unknowns) to also satisfy the "
526 "pre-fix γ-weighted merge; with 2 claims no such non-trivial perturbation exists.";
528 auto result = TestFixture::run(FaultMode::BREAK_EVAL_BINDING);
529 EXPECT_TRUE(
result.verified);
530 EXPECT_TRUE(
result.circuit_ok);
531 EXPECT_FALSE(
result.claim_bound);
538bool verify_with_mismatched_claim_count(
size_t prover_num_claims,
size_t verifier_num_claims)
540 const HonkProof proof = build_faulty_proof(prover_num_claims, FaultMode::NONE).proof;
544 transcript->load_proof(proof);
545 [[maybe_unused]]
FF seed = transcript->template receive_from_prover<FF>(
"init");
550class MultilinearBatchingClaimCountTests :
public ::testing::Test {
557TEST_F(MultilinearBatchingClaimCountTests, MoreClaimsThanProvedThrows)
560 EXPECT_ANY_THROW(verify_with_mismatched_claim_count(2, 3));
565TEST_F(MultilinearBatchingClaimCountTests, FewerClaimsThanProvedThrows)
567 EXPECT_FALSE(verify_with_mismatched_claim_count(3, 2));
#define BB_DISABLE_ASSERTS()
CommitmentKey object over a pairing group 𝔾₁.
static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH
static constexpr size_t VIRTUAL_LOG_N
Public entrypoint for multilinear batching.
Public entrypoint for multilinear batching verification.
static Polynomial random(size_t size, size_t start_index=0)
Fr evaluate_mle(std::span< const Fr > evaluation_points, bool shift=false) const
evaluate multi-linear extension p(X_0,…,X_{n-1}) = \sum_i a_i*L_i(X_0,…,X_{n-1}) at u = (u_0,...
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
typename Group::affine_element AffineElement
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
testing::Types< RecursiveVerifierTestParams< MegaRecursiveFlavor_< MegaCircuitBuilder >, DefaultIO< MegaCircuitBuilder > >, RecursiveVerifierTestParams< MegaRecursiveFlavor_< UltraCircuitBuilder >, DefaultIO< UltraCircuitBuilder > >, RecursiveVerifierTestParams< UltraRecursiveFlavor_< UltraCircuitBuilder >, DefaultIO< UltraCircuitBuilder > >, RecursiveVerifierTestParams< UltraRecursiveFlavor_< UltraCircuitBuilder >, RollupIO >, RecursiveVerifierTestParams< UltraRecursiveFlavor_< MegaCircuitBuilder >, DefaultIO< MegaCircuitBuilder > >, RecursiveVerifierTestParams< UltraZKRecursiveFlavor_< UltraCircuitBuilder >, DefaultIO< UltraCircuitBuilder > >, RecursiveVerifierTestParams< UltraZKRecursiveFlavor_< MegaCircuitBuilder >, DefaultIO< MegaCircuitBuilder > >, RecursiveVerifierTestParams< MegaZKRecursiveFlavor_< MegaCircuitBuilder >, DefaultIO< MegaCircuitBuilder > >, RecursiveVerifierTestParams< MegaZKRecursiveFlavor_< UltraCircuitBuilder >, DefaultIO< UltraCircuitBuilder > > > TestConfigs
Entry point for Barretenberg command-line interface.
std::vector< fr > HonkProof
TEST_F(IPATest, ChallengesAreZero)
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
MultilinearBatchingVerifier< true > MultilinearBatchingRecursiveVerifier
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
BaseTranscript< FrCodec, bb::crypto::Poseidon2< bb::crypto::Poseidon2Bn254ScalarFieldParams > > NativeTranscript
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
std::string to_string(bb::avm2::ValueTag tag)
bb::VectorAffineElementPushSpan< BaseParams > out
Prover's claim for multilinear batching - contains polynomials and their evaluation claims.
Verifier's claim for multilinear batching - contains commitments and evaluation claims.
static FF eval(std::span< const FF > r_in, std::span< const FF > u)
static field random_element(numeric::RNG *engine=nullptr) noexcept