Skip to content

Commit

Permalink
feat: translation evaluations: refactor + soundness fix (#12051)
Browse files Browse the repository at this point in the history
In the ECCVM, we have a special sub-protocol to prove the univariate
evaluations of `Op`, `Px`, `Py`, `z1`, and `z2`. The corresponding logic
used to be un-rolled in `execute_pcs_rounds()` and `verify_proof()`. I
isolated this logic into separate methods for the following reasons:
* It improves the readability of the main methods
* This sub-protocol will be saturated by extra steps needed for ZK
translation evaluations
* There's a cleaner correspondence between the Prover and Verifier steps

The following Goblin soundness issue has been discovered:
* The unvariate evaluation challenge `evaluation_challenge_x` sampled in
ECCVM would be propagated from the `ECCVMProver` to the
`TranslatorProver` and **sent** to the `TranslatorVerifier` meaning that
a malicious `GoblinProver` was free to send any field element and choose
`accumulated_result`.
* Moreover, the extra batching challenge sampled **after** (by
`ECCVMProver`/`TranslatorVerifier`) the translation evaluations proof
seemed redundant and would have blocked the further changes. Namely, in
the fix being implemented, we must enforce that the batching challenge
used to compute the batched claim is the batching challenge used by the
Translator.
* Now `TranslatorVerifier` retrieves `evaluation_challenge_x` and
`batching_challenge_v` as class members of ECCVMVerifier.
  • Loading branch information
iakovenkos authored Feb 27, 2025
1 parent 107c41c commit 5359310
Show file tree
Hide file tree
Showing 19 changed files with 235 additions and 160 deletions.
3 changes: 3 additions & 0 deletions barretenberg/cpp/src/barretenberg/constants.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,7 @@ static constexpr uint32_t NUM_LIBRA_COMMITMENTS = 3;
static constexpr uint32_t NUM_LIBRA_EVALUATIONS = 4;

static constexpr uint32_t MERGE_PROOF_SIZE = 65; // used to ensure mock proofs are generated correctly
// There are 5 distinguished wires in ECCVM that have to be opened as univariates to establish the connection between
// ECCVM and Translator
static constexpr uint32_t NUM_TRANSLATION_EVALUATIONS = 5;
} // namespace bb
90 changes: 44 additions & 46 deletions barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,50 +157,8 @@ void ECCVMProver::execute_pcs_rounds()
sumcheck_output.round_univariates,
sumcheck_output.round_univariate_evaluations);

// Get the challenge at which we evaluate all transcript polynomials as univariates
evaluation_challenge_x = transcript->template get_challenge<FF>("Translation:evaluation_challenge_x");

// Evaluate the transcript polynomials at the challenge
translation_evaluations.op = key->polynomials.transcript_op.evaluate(evaluation_challenge_x);
translation_evaluations.Px = key->polynomials.transcript_Px.evaluate(evaluation_challenge_x);
translation_evaluations.Py = key->polynomials.transcript_Py.evaluate(evaluation_challenge_x);
translation_evaluations.z1 = key->polynomials.transcript_z1.evaluate(evaluation_challenge_x);
translation_evaluations.z2 = key->polynomials.transcript_z2.evaluate(evaluation_challenge_x);

// Add the univariate evaluations to the transcript so the verifier can reconstruct the batched evaluation
transcript->send_to_verifier("Translation:op", translation_evaluations.op);
transcript->send_to_verifier("Translation:Px", translation_evaluations.Px);
transcript->send_to_verifier("Translation:Py", translation_evaluations.Py);
transcript->send_to_verifier("Translation:z1", translation_evaluations.z1);
transcript->send_to_verifier("Translation:z2", translation_evaluations.z2);
const OpeningClaim translation_opening_claim = ECCVMProver::compute_translation_opening_claim();

// Get another challenge for batching the univariates and evaluations
FF ipa_batching_challenge = transcript->template get_challenge<FF>("Translation:ipa_batching_challenge");

// Collect the polynomials and evaluations to be batched
RefArray univariate_polynomials{ key->polynomials.transcript_op,
key->polynomials.transcript_Px,
key->polynomials.transcript_Py,
key->polynomials.transcript_z1,
key->polynomials.transcript_z2 };
std::array<FF, univariate_polynomials.size()> univariate_evaluations{ translation_evaluations.op,
translation_evaluations.Px,
translation_evaluations.Py,
translation_evaluations.z1,
translation_evaluations.z2 };

// Construct the batched polynomial and batched evaluation to produce the batched opening claim
Polynomial batched_univariate{ key->circuit_size };
FF batched_evaluation{ 0 };
FF batching_scalar = FF(1);
for (auto [polynomial, eval] : zip_view(univariate_polynomials, univariate_evaluations)) {
batched_univariate.add_scaled(polynomial, batching_scalar);
batched_evaluation += eval * batching_scalar;
batching_scalar *= ipa_batching_challenge;
}

const OpeningClaim translation_opening_claim = { .polynomial = batched_univariate,
.opening_pair = { evaluation_challenge_x, batched_evaluation } };
const std::array<OpeningClaim, 2> opening_claims = { multivariate_to_univariate_opening_claim,
translation_opening_claim };

Expand All @@ -209,9 +167,6 @@ void ECCVMProver::execute_pcs_rounds()

// Compute the opening proof for the batched opening claim with the univariate PCS
PCS::compute_opening_proof(key->commitment_key, batch_opening_claim, ipa_transcript);

// Produce another challenge passed as input to the translator verifier
translation_batching_challenge_v = transcript->template get_challenge<FF>("Translation:batching_challenge");
}

ECCVMProof ECCVMProver::export_proof()
Expand All @@ -237,4 +192,47 @@ ECCVMProof ECCVMProver::construct_proof()

return export_proof();
}

/**
* @brief The evaluations of the wires `op`, `Px`, `Py`, `z_1`, and `z_2` as univariate polynomials have to be proved as
* they are used in the 'TranslatorVerifier::verify_translation' sub-protocol and its recursive counterpart. To increase
* the efficiency, we produce an OpeningClaim that is fed to Shplonk along with the OpeningClaim produced by Shplemini.
*
* @return ProverOpeningClaim<typename ECCVMFlavor::Curve>
*/
ProverOpeningClaim<typename ECCVMFlavor::Curve> ECCVMProver::compute_translation_opening_claim()
{
// Collect the polynomials and evaluations to be batched
RefArray translation_polynomials{ key->polynomials.transcript_op,
key->polynomials.transcript_Px,
key->polynomials.transcript_Py,
key->polynomials.transcript_z1,
key->polynomials.transcript_z2 };

// Get the challenge at which we evaluate all transcript polynomials as univariates
evaluation_challenge_x = transcript->template get_challenge<FF>("Translation:evaluation_challenge_x");

// Evaluate the transcript polynomials as univariates and add their evaluations at x to the transcript
for (auto [eval, poly, label] :
zip_view(translation_evaluations.get_all(), translation_polynomials, translation_labels)) {
eval = poly.evaluate(evaluation_challenge_x);
transcript->template send_to_verifier(label, eval);
}

// Get another challenge to batch the evaluations of the transcript polynomials
translation_batching_challenge_v = transcript->template get_challenge<FF>("Translation:batching_challenge_v");

// Construct the batched polynomial and batched evaluation to produce the batched opening claim
Polynomial batched_translation_univariate{ key->circuit_size };
FF batched_translation_evaluation{ 0 };
FF batching_scalar = FF(1);
for (auto [polynomial, eval] : zip_view(translation_polynomials, translation_evaluations.get_all())) {
batched_translation_univariate.add_scaled(polynomial, batching_scalar);
batched_translation_evaluation += eval * batching_scalar;
batching_scalar *= translation_batching_challenge_v;
}

return { .polynomial = batched_translation_univariate,
.opening_pair = { evaluation_challenge_x, batched_translation_evaluation } };
}
} // namespace bb
8 changes: 7 additions & 1 deletion barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class ECCVMProver {
using CircuitBuilder = typename Flavor::CircuitBuilder;
using ZKData = ZKSumcheckData<Flavor>;
using SmallSubgroupIPA = SmallSubgroupIPAProver<Flavor>;
using OpeningClaim = ProverOpeningClaim<typename Flavor::Curve>;

explicit ECCVMProver(CircuitBuilder& builder,
const bool fixed_size = false,
Expand All @@ -44,6 +45,7 @@ class ECCVMProver {

ECCVMProof export_proof();
ECCVMProof construct_proof();
OpeningClaim compute_translation_opening_claim();

std::shared_ptr<Transcript> transcript;
std::shared_ptr<Transcript> ipa_transcript;
Expand All @@ -52,6 +54,10 @@ class ECCVMProver {

TranslationEvaluations translation_evaluations;

std::array<std::string, 5> translation_labels = {
"Translation:op", "Translation:Px", "Translation:Py", "Translation:z1", "Translation:z2"
};

std::vector<FF> public_inputs;

bb::RelationParameters<FF> relation_parameters;
Expand All @@ -62,7 +68,7 @@ class ECCVMProver {
ZKData zk_sumcheck_data;

FF evaluation_challenge_x;
FF translation_batching_challenge_v; // to be rederived by the translator verifier
FF translation_batching_challenge_v;

SumcheckOutput<Flavor> sumcheck_output;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ class ECCVMTranscriptTests : public ::testing::Test {
manifest_expected.add_entry(round, "Translation:Py", frs_per_Fr);
manifest_expected.add_entry(round, "Translation:z1", frs_per_Fr);
manifest_expected.add_entry(round, "Translation:z2", frs_per_Fr);
manifest_expected.add_challenge(round, "Translation:ipa_batching_challenge");
manifest_expected.add_challenge(round, "Translation:batching_challenge_v");

round++;
manifest_expected.add_challenge(round, "Shplonk:nu");
Expand All @@ -200,9 +200,6 @@ class ECCVMTranscriptTests : public ::testing::Test {
manifest_expected.add_entry(round, "Shplonk:Q", frs_per_G);
manifest_expected.add_challenge(round, "Shplonk:z");

round++;
manifest_expected.add_challenge(round, "Translation:batching_challenge");

return manifest_expected;
}

Expand Down
75 changes: 44 additions & 31 deletions barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,38 +99,14 @@ bool ECCVMVerifier::verify_proof(const ECCVMProof& proof)
const OpeningClaim multivariate_to_univariate_opening_claim =
PCS::reduce_batch_opening_claim(sumcheck_batch_opening_claims);

const FF evaluation_challenge_x = transcript->template get_challenge<FF>("Translation:evaluation_challenge_x");
// Produce the opening claim for batch opening of 'op', 'Px', 'Py', 'z1', and 'z2' wires as univariate polynomials
translation_commitments = { commitments.transcript_op,
commitments.transcript_Px,
commitments.transcript_Py,
commitments.transcript_z1,
commitments.transcript_z2 };

// Construct arrays of commitments and evaluations to be batched, the evaluations being received from the prover
const size_t NUM_UNIVARIATES = 5;
std::array<Commitment, NUM_UNIVARIATES> transcript_commitments = { commitments.transcript_op,
commitments.transcript_Px,
commitments.transcript_Py,
commitments.transcript_z1,
commitments.transcript_z2 };
std::array<FF, NUM_UNIVARIATES> transcript_evaluations = {
transcript->template receive_from_prover<FF>("Translation:op"),
transcript->template receive_from_prover<FF>("Translation:Px"),
transcript->template receive_from_prover<FF>("Translation:Py"),
transcript->template receive_from_prover<FF>("Translation:z1"),
transcript->template receive_from_prover<FF>("Translation:z2")
};

// Get the batching challenge for commitments and evaluations
const FF ipa_batching_challenge = transcript->template get_challenge<FF>("Translation:ipa_batching_challenge");

// Compute the batched commitment and batched evaluation for the univariate opening claim
Commitment batched_commitment = transcript_commitments[0];
FF batched_transcript_eval = transcript_evaluations[0];
FF batching_scalar = ipa_batching_challenge;
for (size_t idx = 1; idx < NUM_UNIVARIATES; ++idx) {
batched_commitment = batched_commitment + transcript_commitments[idx] * batching_scalar;
batched_transcript_eval += batching_scalar * transcript_evaluations[idx];
batching_scalar *= ipa_batching_challenge;
}

const OpeningClaim translation_opening_claim = { { evaluation_challenge_x, batched_transcript_eval },
batched_commitment };
const OpeningClaim translation_opening_claim = compute_translation_opening_claim(translation_commitments);

const std::array<OpeningClaim, 2> opening_claims = { multivariate_to_univariate_opening_claim,
translation_opening_claim };
Expand All @@ -145,4 +121,41 @@ bool ECCVMVerifier::verify_proof(const ECCVMProof& proof)
vinfo("batch opening verified?: ", batched_opening_verified);
return sumcheck_output.verified && batched_opening_verified && consistency_checked;
}

/**
* @brief To link the ECCVM Transcript wires 'op', 'Px', 'Py', 'z1', and 'z2' to the accumulator computed by the
* translator, we verify their evaluations as univariates. For efficiency reasons, we batch these evaluations.
*
* @param translation_commitments Commitments to 'op', 'Px', 'Py', 'z1', and 'z2'
* @return OpeningClaim<typename ECCVMFlavor::Curve>
*/
OpeningClaim<typename ECCVMFlavor::Curve> ECCVMVerifier::compute_translation_opening_claim(
const std::array<Commitment, NUM_TRANSLATION_EVALUATIONS>& translation_commitments)
{
evaluation_challenge_x = transcript->template get_challenge<FF>("Translation:evaluation_challenge_x");

// Construct arrays of commitments and evaluations to be batched, the evaluations being received from the prover
std::array<FF, NUM_TRANSLATION_EVALUATIONS> translation_evaluations = {
transcript->template receive_from_prover<FF>("Translation:op"),
transcript->template receive_from_prover<FF>("Translation:Px"),
transcript->template receive_from_prover<FF>("Translation:Py"),
transcript->template receive_from_prover<FF>("Translation:z1"),
transcript->template receive_from_prover<FF>("Translation:z2")
};

// Get the batching challenge for commitments and evaluations
batching_challenge_v = transcript->template get_challenge<FF>("Translation:batching_challenge_v");

// Compute the batched commitment and batched evaluation for the univariate opening claim
Commitment batched_commitment = translation_commitments[0];
FF batched_translation_evaluation = translation_evaluations[0];
FF batching_scalar = batching_challenge_v;
for (size_t idx = 1; idx < NUM_TRANSLATION_EVALUATIONS; ++idx) {
batched_commitment = batched_commitment + translation_commitments[idx] * batching_scalar;
batched_translation_evaluation += batching_scalar * translation_evaluations[idx];
batching_scalar *= batching_challenge_v;
}

return { { evaluation_challenge_x, batched_translation_evaluation }, batched_commitment };
};
} // namespace bb
7 changes: 7 additions & 0 deletions barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,17 @@ class ECCVMVerifier {
: ECCVMVerifier(std::make_shared<ECCVMFlavor::VerificationKey>(proving_key)){};

bool verify_proof(const ECCVMProof& proof);
OpeningClaim<typename ECCVMFlavor::Curve> compute_translation_opening_claim(
const std::array<Commitment, NUM_TRANSLATION_EVALUATIONS>& translation_commitments);

std::array<Commitment, NUM_TRANSLATION_EVALUATIONS> translation_commitments;
std::shared_ptr<VerificationKey> key;
std::map<std::string, Commitment> commitments;
std::shared_ptr<Transcript> transcript;
std::shared_ptr<Transcript> ipa_transcript;

// Translation evaluation and batching challenges. They are propagated to the TranslatorVerifier
FF evaluation_challenge_x;
FF batching_challenge_v;
};
} // namespace bb
3 changes: 2 additions & 1 deletion barretenberg/cpp/src/barretenberg/goblin/goblin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,8 @@ class GoblinVerifier {

TranslatorVerifier translator_verifier(translator_verification_key, eccvm_verifier.transcript);

bool accumulator_construction_verified = translator_verifier.verify_proof(proof.translator_proof);
bool accumulator_construction_verified = translator_verifier.verify_proof(
proof.translator_proof, eccvm_verifier.evaluation_challenge_x, eccvm_verifier.batching_challenge_v);
// TODO(https://github.com/AztecProtocol/barretenberg/issues/799): Ensure translation_evaluations are passed
// correctly
bool translation_verified = translator_verifier.verify_translation(proof.translation_evaluations);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#pragma once
#include "barretenberg/common/ref_array.hpp"
#include "barretenberg/constants.hpp"
#include "barretenberg/ecc/curves/bn254/fq.hpp"
#include "barretenberg/ecc/fields/field_conversion.hpp"

Expand All @@ -11,9 +13,10 @@ namespace bb {
*/
template <typename BF, typename FF> struct TranslationEvaluations_ {
BF op, Px, Py, z1, z2;
static constexpr uint32_t NUM_EVALUATIONS = 5;
static size_t size() { return field_conversion::calc_num_bn254_frs<BF>() * NUM_EVALUATIONS; }
static size_t size() { return field_conversion::calc_num_bn254_frs<BF>() * NUM_TRANSLATION_EVALUATIONS; }

RefArray<BF, NUM_TRANSLATION_EVALUATIONS> get_all() { return { op, Px, Py, z1, z2 }; }

MSGPACK_FIELDS(op, Px, Py, z1, z2);
};
} // namespace bb
} // namespace bb
Loading

0 comments on commit 5359310

Please sign in to comment.