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

Add num_instances to transcript #648

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

emmorais
Copy link
Collaborator

@emmorais emmorais commented Nov 27, 2024

Each opcode has its own transcript after forking. This PR includes num_instances for each opcode as the first element of each transcript (after forking). This way it is not possible to fold instances arbitrarily.

@emmorais emmorais changed the title Add num_instances to transcript [WIP] Add num_instances to transcript Nov 27, 2024
@emmorais emmorais requested a review from naure November 27, 2024 19:36
@@ -150,6 +150,7 @@ impl<E: ExtensionField, PCS: PolynomialCommitmentScheme<E>> ZKVMProver<E, PCS> {
for lk_s in cs.lk_expressions_namespace_map.iter() {
tracing::debug!("opcode circuit {}: {}", circuit_name, lk_s);
}
transcript.append_field_element(&E::BaseField::from(num_instances as u64));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Assuming it is line 131 above that does the skipping of empty circuits:

            if witness.is_empty() {
                continue;

Then to say "there is an empty circuit" the num_instances (potentially 0) should be written before that if.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for the comment. Now this problem is addressed in the expected way, right after forking the transcripts and including zeros.

@@ -148,6 +149,7 @@ impl<E: ExtensionField, PCS: PolynomialCommitmentScheme<E>> ZKVMVerifier<E, PCS>
.circuit_vks
.get(&name)
.ok_or(ZKVMError::VKNotFound(name.clone()))?;
transcript.append_field_element(&E::BaseField::from(num_instances as u64));
Copy link
Collaborator

Choose a reason for hiding this comment

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

In this loop there will be only non-empty circuits. To catch all circuits the self.vk should be involved.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The verifier now is basically doing the same as the prover, using the information included in the proofs, mimicking the same updates in the corresponding transcripts.

@naure naure linked an issue Nov 28, 2024 that may be closed by this pull request
@emmorais emmorais self-assigned this Dec 4, 2024
@@ -117,7 +117,7 @@ impl<E: ExtensionField, PCS: PolynomialCommitmentScheme<E>> ZKVMVerifier<E, PCS>
}
}

for (name, (_, proof)) in vm_proof.opcode_proofs.iter() {
for (name, (i, proof)) in vm_proof.opcode_proofs.iter() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why the change here? You don't use the variable i, do you?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

tracing.folded Outdated
Copy link
Collaborator

Choose a reason for hiding this comment

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

You probably want to put the name of this file into the .gitignore?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

.vk
.circuit_vks
.iter() // Sorted by key.
.zip_eq(transcripts.iter_mut().enumerate())
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's probably less confusing to do the enumerate once on the outside of the zip:

diff --git a/ceno_zkvm/src/scheme/verifier.rs b/ceno_zkvm/src/scheme/verifier.rs
index 52c11a2c..d6b3e41a 100644
--- a/ceno_zkvm/src/scheme/verifier.rs
+++ b/ceno_zkvm/src/scheme/verifier.rs
@@ -4,7 +4,7 @@ use ark_std::iterable::Iterable;
 use ceno_emul::WORD_SIZE;
 use ff_ext::ExtensionField;
 
-use itertools::{Itertools, interleave, izip};
+use itertools::{Itertools, enumerate, interleave, izip};
 use mpcs::PolynomialCommitmentScheme;
 use multilinear_extensions::{
     mle::{IntoMLE, MultilinearExtension},
@@ -142,12 +142,8 @@ impl<E: ExtensionField, PCS: PolynomialCommitmentScheme<E>> ZKVMVerifier<E, PCS>
 
         // For each opcode, include the num_instances
         // into its corresponding fork of the transcript.
-        for ((name, _), (i, transcript)) in self
-            .vk
-            .circuit_vks
-            .iter() // Sorted by key.
-            .zip_eq(transcripts.iter_mut().enumerate())
-        {
+        // Note: circuits are sorted by key.
+        for (i, ((name, _), transcript)) in enumerate(izip!(self.vk.circuit_vks, transcripts)) {
             // get num_instances from opcode proof
             let opcode_result = vm_proof.opcode_proofs.get(name);
             let num_instances = opcode_result.map(|(_, p)| p.num_instances).unwrap_or(0);

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Enumeration is not needed just for updating the transcripts, so I removed it.

Just a remark: without "iter_mut()" the compiler asks to clone the transcripts, which is not what we want, right? So maybe I am missing something from your suggestion here. Any way, since the enumeration was removed, this discussion may be useless now. But I appreciate your comment, thank you.

@@ -92,6 +92,7 @@ impl<E: ExtensionField, PCS: PolynomialCommitmentScheme<E>> ZKVMProver<E, PCS> {
// commit to opcode circuits first and then commit to table circuits, sorted by name
for (circuit_name, witness) in witnesses.into_iter_sorted() {
let num_instances = witness.num_instances();
//transcript.append_field_element(&E::BaseField::from(num_instances as u64));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please remove this from the final PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

@emmorais emmorais marked this pull request as ready for review December 4, 2024 12:55
@emmorais emmorais changed the title [WIP] Add num_instances to transcript Add num_instances to transcript Dec 4, 2024
// TODO: add an enum for circuit type either in constraint_system or vk
let cs = pk.get_cs();
let is_opcode_circuit = cs.lk_table_expressions.is_empty()
&& cs.r_table_expressions.is_empty()
&& cs.w_table_expressions.is_empty();
if is_opcode_circuit {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Some of the table circuits may also be skipped. Is there a blocker that requires this if?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Although it is a little less obvious how the num_instances of tables is communicated. Look at the verifier variable expected_rounds. Maybe we can do the table case somehow in another PR.

// get num_instances from opcode proof
let opcode_result = vm_proof.opcode_proofs.get(name);
let num_instances = opcode_result.map(|(_, p)| p.num_instances).unwrap_or(0);
if opcode_result.is_some() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is supposed to be the same as if is_opcode_circuit in the prover side, but I’m not sure that’s the case. Looking at vm_proof.opcode_proofs.insert which ultimately becomes opcode_result.is_some(), not the same as is_opcode_circuit?

let opcode_result = vm_proof.opcode_proofs.get(name);
let num_instances = opcode_result.map(|(_, p)| p.num_instances).unwrap_or(0);
if opcode_result.is_some() {
transcript.append_field_element(&E::BaseField::from(num_instances as u64));
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would protect against more potential attacks to do this before the alpha, beta read_challenge above. It’s before the transcript fork, so just a sequence of append to the root transcript.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Remove free choice of circuits
3 participants