Conversation
| 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)); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Thanks for the comment. Now this problem is addressed in the expected way, right after forking the transcripts and including zeros.
| .circuit_vks | ||
| .get(&name) | ||
| .ok_or(ZKVMError::VKNotFound(name.clone()))?; | ||
| transcript.append_field_element(&E::BaseField::from(num_instances as u64)); |
There was a problem hiding this comment.
In this loop there will be only non-empty circuits. To catch all circuits the self.vk should be involved.
There was a problem hiding this comment.
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.
| } | ||
|
|
||
| for (name, (_, proof)) in vm_proof.opcode_proofs.iter() { | ||
| for (name, (i, proof)) in vm_proof.opcode_proofs.iter() { |
There was a problem hiding this comment.
Why the change here? You don't use the variable i, do you?
There was a problem hiding this comment.
You probably want to put the name of this file into the .gitignore?
| .vk | ||
| .circuit_vks | ||
| .iter() // Sorted by key. | ||
| .zip_eq(transcripts.iter_mut().enumerate()) |
There was a problem hiding this comment.
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);There was a problem hiding this comment.
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.
| // 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)); |
There was a problem hiding this comment.
Please remove this from the final PR.
| 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 { |
There was a problem hiding this comment.
Some of the table circuits may also be skipped. Is there a blocker that requires this if?
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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.
|
Just a memo: #901 addressed this issue in more unified way |
|
Done via #894 |
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.