Skip to content

fix: CIVC components share a transcript #14144

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

Merged
merged 19 commits into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ using namespace bb;

using Flavor = ECCVMFlavor;
using Builder = ECCVMCircuitBuilder;
using Transcript = ECCVMFlavor::Transcript;

namespace {

Expand Down Expand Up @@ -48,7 +49,8 @@ void eccvm_generate_prover(State& state) noexcept
size_t target_num_gates = 1 << static_cast<size_t>(state.range(0));
for (auto _ : state) {
Builder builder = generate_trace(target_num_gates);
ECCVMProver prover(builder);
std::shared_ptr<Transcript> prover_transcript = std::make_shared<Transcript>();
ECCVMProver prover(builder, prover_transcript);
};
}

Expand All @@ -57,7 +59,8 @@ void eccvm_prove(State& state) noexcept

size_t target_num_gates = 1 << static_cast<size_t>(state.range(0));
Builder builder = generate_trace(target_num_gates);
ECCVMProver prover(builder);
std::shared_ptr<Transcript> prover_transcript = std::make_shared<Transcript>();
ECCVMProver prover(builder, prover_transcript);
for (auto _ : state) {
ECCVMProof proof = prover.construct_proof();
};
Expand Down
34 changes: 21 additions & 13 deletions barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,7 @@ void ClientIVC::hide_op_queue_accumulation_result(ClientCircuit& circuit)
* @brief Construct the proving key of the hiding circuit, which recursively verifies the last folding proof and the
* decider proof.
*/
std::pair<std::shared_ptr<ClientIVC::DeciderZKProvingKey>, ClientIVC::MergeProof> ClientIVC::
construct_hiding_circuit_key()
std::shared_ptr<ClientIVC::DeciderZKProvingKey> ClientIVC::construct_hiding_circuit_key()
{
trace_usage_tracker.print(); // print minimum structured sizes for each block
BB_ASSERT_EQ(verification_queue.size(), static_cast<size_t>(1));
Expand Down Expand Up @@ -330,25 +329,26 @@ std::pair<std::shared_ptr<ClientIVC::DeciderZKProvingKey>, ClientIVC::MergeProof

auto decider_pk = std::make_shared<DeciderZKProvingKey>(builder, TraceSettings(), bn254_commitment_key);
honk_vk = std::make_shared<MegaZKVerificationKey>(decider_pk->proving_key);
// Construct the last merge proof for the present circuit
MergeProof merge_proof = goblin.prove_merge();

return { decider_pk, merge_proof };
return decider_pk;
}

/**
* @brief Construct the hiding circuit then produce a proof of the circuit's correctness with MegaHonk.
*
* @return HonkProof - a Mega proof
*/
std::pair<HonkProof, ClientIVC::MergeProof> ClientIVC::construct_and_prove_hiding_circuit()
HonkProof ClientIVC::construct_and_prove_hiding_circuit()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. cleaner functionality
  2. merge proof has to happen after proving the hiding circuit, as the latter contains the last subtable

{
auto [decider_pk, merge_proof] = construct_hiding_circuit_key();
// FoldingRecursiveVerifier circuit is proven by a MegaZKProver
MegaZKProver prover(decider_pk);
// Create a transcript to be shared by final merge prover, ECCVM, Translator, and Hiding Circuit provers.
goblin.transcript = std::make_shared<Goblin::Transcript>();
goblin.transcript->enable_manifest();
auto decider_pk = construct_hiding_circuit_key();
// Hiding circuit is proven by a MegaZKProver
MegaZKProver prover(decider_pk, goblin.transcript);
HonkProof proof = prover.construct_proof();

return { proof, merge_proof };
return proof;
}

/**
Expand All @@ -358,19 +358,27 @@ std::pair<HonkProof, ClientIVC::MergeProof> ClientIVC::construct_and_prove_hidin
*/
ClientIVC::Proof ClientIVC::prove()
{
auto [mega_proof, merge_proof] = construct_and_prove_hiding_circuit();
auto mega_proof = construct_and_prove_hiding_circuit();

// Construct the last merge proof for the present circuit
MergeProof merge_proof = goblin.prove_final_merge();

// Prove ECCVM and Translator
return { mega_proof, goblin.prove(merge_proof) };
};

bool ClientIVC::verify(const Proof& proof, const VerificationKey& vk)
{
// Create a transcript to be shared by MegaZK-, Merge-, ECCVM-, and Translator- Verifiers.
std::shared_ptr<Goblin::Transcript> civc_verifier_transcript = std::make_shared<Goblin::Transcript>();
// Verify the hiding circuit proof
MegaZKVerifier verifer{ vk.mega };
MegaZKVerifier verifer{ vk.mega, /*ipa_verification_key=*/{}, civc_verifier_transcript };
bool mega_verified = verifer.verify_proof(proof.mega_proof);
vinfo("Mega verified: ", mega_verified);
// Goblin verification (final merge, eccvm, translator)
bool goblin_verified = Goblin::verify(proof.goblin_proof);
bool goblin_verified = Goblin::verify(proof.goblin_proof, civc_verifier_transcript);
vinfo("Goblin verified: ", goblin_verified);
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1396): State tracking in CIVC verifiers.
return goblin_verified && mega_verified;
}

Expand Down
4 changes: 2 additions & 2 deletions barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ class ClientIVC {

Proof prove();

std::pair<std::shared_ptr<ClientIVC::DeciderZKProvingKey>, MergeProof> construct_hiding_circuit_key();
std::shared_ptr<ClientIVC::DeciderZKProvingKey> construct_hiding_circuit_key();
static void hide_op_queue_accumulation_result(ClientCircuit& circuit);
std::pair<HonkProof, MergeProof> construct_and_prove_hiding_circuit();
HonkProof construct_and_prove_hiding_circuit();

static bool verify(const Proof& proof, const VerificationKey& vk);

Expand Down
66 changes: 66 additions & 0 deletions barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ class ClientIVCTests : public ::testing::Test {
}
}
}

static std::pair<ClientIVC::Proof, ClientIVC::VerificationKey> generate_ivc_proof(size_t num_circuits)
{
ClientIVC ivc{ { SMALL_TEST_STRUCTURE } };
ClientIVCMockCircuitProducer circuit_producer;
for (size_t j = 0; j < num_circuits; ++j) {
auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5);
ivc.accumulate(circuit);
}
return { ivc.prove(), ivc.get_vk() };
};
};

/**
Expand Down Expand Up @@ -290,6 +301,61 @@ TEST_F(ClientIVCTests, StructuredPrecomputedVKs)
EXPECT_TRUE(ivc.prove_and_verify());
};

/**
* @brief Produce 2 valid CIVC proofs. Ensure that replacing a proof component with a component from a different proof
* leads to a verification failure.
*
*/
TEST_F(ClientIVCTests, WrongProofComponentFailure)
{
// Produce two valid proofs
auto [civc_proof_1, civc_vk_1] = generate_ivc_proof(/*num_circuits=*/2);
{
EXPECT_TRUE(ClientIVC::verify(civc_proof_1, civc_vk_1));
}

auto [civc_proof_2, civc_vk_2] = generate_ivc_proof(/*num_circuits=*/2);
{
EXPECT_TRUE(ClientIVC::verify(civc_proof_2, civc_vk_2));
}

{
// Replace Merge proof
ClientIVC::Proof tampered_proof = civc_proof_1;

tampered_proof.goblin_proof.merge_proof = civc_proof_2.goblin_proof.merge_proof;

EXPECT_FALSE(ClientIVC::verify(tampered_proof, civc_vk_1));
}

{
// Replace hiding circuit proof
ClientIVC::Proof tampered_proof = civc_proof_1;

tampered_proof.mega_proof = civc_proof_2.mega_proof;

EXPECT_FALSE(ClientIVC::verify(tampered_proof, civc_vk_1));
}

{
// Replace ECCVM proof
ClientIVC::Proof tampered_proof = civc_proof_1;

tampered_proof.goblin_proof.eccvm_proof = civc_proof_2.goblin_proof.eccvm_proof;

EXPECT_FALSE(ClientIVC::verify(tampered_proof, civc_vk_1));
}

{
// Replace Translator proof
ClientIVC::Proof tampered_proof = civc_proof_1;

tampered_proof.goblin_proof.translator_proof = civc_proof_2.goblin_proof.translator_proof;

EXPECT_FALSE(ClientIVC::verify(tampered_proof, civc_vk_1));
}
};

/**
* @brief Ensure that the CIVC VK is independent of the number of circuits accumulated
*
Expand Down
42 changes: 28 additions & 14 deletions barretenberg/cpp/src/barretenberg/eccvm/eccvm.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using namespace bb;
using FF = ECCVMFlavor::FF;
using PK = ECCVMFlavor::ProvingKey;
using Transcript = ECCVMFlavor::Transcript;
class ECCVMTests : public ::testing::Test {
protected:
void SetUp() override { srs::init_file_crs_factory(bb::srs::bb_crs_path()); };
Expand Down Expand Up @@ -93,9 +94,13 @@ void complete_proving_key_for_test(bb::RelationParameters<FF>& relation_paramete
TEST_F(ECCVMTests, BaseCaseFixedSize)
{
ECCVMCircuitBuilder builder = generate_circuit(&engine);
ECCVMProver prover(builder);

std::shared_ptr<Transcript> prover_transcript = std::make_shared<Transcript>();
ECCVMProver prover(builder, prover_transcript);
ECCVMProof proof = prover.construct_proof();
ECCVMVerifier verifier(prover.key);

std::shared_ptr<Transcript> verifier_transcript = std::make_shared<Transcript>();
ECCVMVerifier verifier(verifier_transcript);
bool verified = verifier.verify_proof(proof);

ASSERT_TRUE(verified);
Expand All @@ -107,10 +112,13 @@ TEST_F(ECCVMTests, EqFailsFixedSize)
// Tamper with the eq op such that the expected value is incorect
builder.op_queue->add_erroneous_equality_op_for_testing();

ECCVMProver prover(builder);
std::shared_ptr<Transcript> prover_transcript = std::make_shared<Transcript>();
ECCVMProver prover(builder, prover_transcript);

ECCVMProof proof = prover.construct_proof();
ECCVMVerifier verifier(prover.key);

std::shared_ptr<Transcript> verifier_transcript = std::make_shared<Transcript>();
ECCVMVerifier verifier(verifier_transcript);
bool verified = verifier.verify_proof(proof);
ASSERT_FALSE(verified);
}
Expand All @@ -127,17 +135,19 @@ TEST_F(ECCVMTests, CommittedSumcheck)
std::vector<FF> gate_challenges(CONST_ECCVM_LOG_N);

ECCVMCircuitBuilder builder = generate_circuit(&engine);

ECCVMProver prover(builder);
auto pk = std::make_shared<ProvingKey>(builder);

std::shared_ptr<Transcript> prover_transcript = std::make_shared<Transcript>();
ECCVMProver prover(builder, prover_transcript);
auto pk = std::make_shared<ProvingKey>(builder);

// Prepare the inputs for the sumcheck prover:
// Compute and add beta to relation parameters
const FF alpha = FF::random_element();
complete_proving_key_for_test(relation_parameters, pk, gate_challenges);

// Clear the transcript
prover_transcript = std::make_shared<Transcript>();

// Run Sumcheck on the ECCVM Prover polynomials
using SumcheckProver = SumcheckProver<ECCVMFlavor, CONST_ECCVM_LOG_N>;
SumcheckProver sumcheck_prover(pk->circuit_size, prover_transcript);

Expand All @@ -146,7 +156,6 @@ TEST_F(ECCVMTests, CommittedSumcheck)
auto prover_output =
sumcheck_prover.prove(pk->polynomials, relation_parameters, alpha, gate_challenges, zk_sumcheck_data);

ECCVMVerifier verifier(prover.key);
std::shared_ptr<Transcript> verifier_transcript = std::make_shared<Transcript>(prover_transcript->proof_data);

// Execute Sumcheck Verifier
Expand Down Expand Up @@ -180,24 +189,29 @@ TEST_F(ECCVMTests, FixedVK)
{
// Generate a circuit and its verification key (computed at runtime from the proving key)
ECCVMCircuitBuilder builder = generate_circuit(&engine);
ECCVMProver prover(builder);
ECCVMVerifier verifier(prover.key);
std::shared_ptr<Transcript> prover_transcript = std::make_shared<Transcript>();
ECCVMProver prover(builder, prover_transcript);

std::shared_ptr<Transcript> verifier_transcript = std::make_shared<Transcript>();
ECCVMVerifier verifier(verifier_transcript);

// Generate the default fixed VK
ECCVMFlavor::VerificationKey fixed_vk{};
// Generate a VK from PK
ECCVMFlavor::VerificationKey vk_computed_by_prover(prover.key);

// Set verifier PCS key to null in both the fixed VK and the generated VK
fixed_vk.pcs_verification_key = nullptr;
verifier.key->pcs_verification_key = nullptr;
vk_computed_by_prover.pcs_verification_key = nullptr;

auto labels = verifier.key->get_labels();
size_t index = 0;
for (auto [vk_commitment, fixed_commitment] : zip_view(verifier.key->get_all(), fixed_vk.get_all())) {
for (auto [vk_commitment, fixed_commitment] : zip_view(vk_computed_by_prover.get_all(), fixed_vk.get_all())) {
EXPECT_EQ(vk_commitment, fixed_commitment)
<< "Mismatch between vk_commitment and fixed_commitment at label: " << labels[index];
++index;
}

// Check that the fixed VK is equal to the generated VK
EXPECT_EQ(fixed_vk, *verifier.key.get());
EXPECT_EQ(fixed_vk, vk_computed_by_prover);
}
Loading