From 623bc65dba964117ed711355966a05d0d3c10503 Mon Sep 17 00:00:00 2001 From: M Alghazwi Date: Thu, 10 Jul 2025 12:13:49 +0200 Subject: [PATCH] improve circuit description. --- codex-plonky2-circuits/src/recursion/node.rs | 55 ++++++++++++++++---- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/codex-plonky2-circuits/src/recursion/node.rs b/codex-plonky2-circuits/src/recursion/node.rs index 14c8050..23a8f3e 100755 --- a/codex-plonky2-circuits/src/recursion/node.rs +++ b/codex-plonky2-circuits/src/recursion/node.rs @@ -32,7 +32,7 @@ pub struct NodeCircuit< } /// recursion node targets -/// leaf_proofs: leaf proofs +/// leaf_proofs: leaf proofs - can be real or dummy /// node_verifier_data: node verifier data, note: leaf verifier data is constant /// condition: for switching between leaf and node verifier data /// index: index of the node @@ -75,7 +75,7 @@ impl< pub fn new( leaf_verifier_data: VerifierCircuitData, ) -> Self { - assert!(N.is_power_of_two(), "M is NOT a power of two"); + assert!(N.is_power_of_two(), "N is NOT a power of two"); Self{ leaf_verifier_data, phantom_data:PhantomData::default(), @@ -97,15 +97,30 @@ impl< type Targets = NodeTargets; type Input = NodeInput; + /// Adds the in-circuit targets for the Node recursion circuit. + /// + /// This function builds all necessary virtual targets for verifying `N` leaf proofs + /// and constructing the node-level public inputs. It performs the following steps: + /// 1. Creates virtual proof targets for each leaf proof and extracts their public inputs. + /// 2. Hashes and registers the concatenated public inputs of the inner proofs. + /// 3. Sets up virtual verifier data targets for node and leaf circuits and selects + /// between leaf and node verifier data based on the `condition` flag. + /// 4. Verifies each proof in-circuit, switching to dummy verifier data for flagged dummy proofs. + /// 5. Enforces zero flag buckets for any dummy proof to maintain soundness. + /// 6. Checks that each inner proof's index matches the expected index + /// i.e. it should be in the range of [S, S-1] where S = `index` * `N` + /// 7. Aggregates inner flag buckets across proofs into final flag bucket targets. + /// 8. Registers the final flag buckets as public inputs. + /// fn add_targets(&self, builder: &mut CircuitBuilder, register_pi: bool) -> Result { let inner_common = self.leaf_verifier_data.common.clone(); let zero_target = builder.zero(); - // assert public input is of size 8 + 1 (index) + B (flag buckets) + // assert public input is of size 8 (2 hash digests) + 1 (index) + B (flag buckets) let n_bucket: usize = bucket_count(T); assert_eq!(inner_common.num_public_inputs, 9+n_bucket); - // the proof virtual targets - M proofs + // the proof virtual targets - N proofs let mut vir_proofs = vec![]; let mut pub_input = vec![]; let mut inner_flag_buckets = vec![]; @@ -114,29 +129,42 @@ impl< let vir_proof = builder.add_virtual_proof_with_pis(&inner_common); let inner_pub_input = vir_proof.public_inputs.clone(); vir_proofs.push(vir_proof); + // public input [0...4] contains hash of inner proof public inputs pub_input.extend_from_slice(&inner_pub_input[0..4]); + // public input [4...8] are skipped since they contain the inner verifier data + // public input [8] contains the index inner_indexes.push(inner_pub_input[8]); + // public input [9..(9+n_bucket)] contains the flag buckets inner_flag_buckets.push(inner_pub_input[9..(9+n_bucket)].to_vec()); } - // hash the public input & make it public + // hash the public input of all N inner proofs & make it public let hash_inner_pub_input = builder.hash_n_to_hash_no_pad::(pub_input); if register_pi{ builder.register_public_inputs(&hash_inner_pub_input.elements); } // virtual target for the verifier data + // this is the verifier data for the node which cannot be constant + // since it is unknown at compile time let node_verifier_data = builder.add_virtual_verifier_data(inner_common.config.fri_config.cap_height); - // virtual target for the verifier data + // constant verifier data target + // this is the verifier data for the leaf which is known at this point and is constant let const_leaf_verifier_data = builder.constant_verifier_data(&self.leaf_verifier_data.verifier_only); - // virtual constant target for dummy verifier data + // constant target for dummy verifier data + // this can be the verifier data for either the node or the leaf + // doesn't matter if the given inner proof (leaf or node) is dummy anyway + // it just has to be in the same structure as the real verifier data. let const_dummy_vd = builder.constant_verifier_data( &DummyProofGen::::gen_dummy_verifier_data(&inner_common) ); // register only the node verifier data hash as public input. + // we need to register this as public input since this verifier data is supplied by the prover + // so it must be verified later by the verifier. Otherwise, the prover can use a dummy verifier data + // we don't need to register the leaf verifier data or the dummy verifier data since they are constants let mut vd_pub_input = vec![]; vd_pub_input.extend_from_slice(&node_verifier_data.circuit_digest.elements); for i in 0..builder.config.fri_config.num_cap_elements() { @@ -148,18 +176,23 @@ impl< } // condition for switching between node and leaf + // we need this to switch between node and leaf verifier data since + // the leaf and node circuit contain different logic -> different verifier data. let condition = builder.add_virtual_bool_target_safe(); // flag buckets targets let mut flag_buckets: Vec = (0..n_bucket).map(|_i| zero_target.clone()).collect(); // index: 0 <= index < T where T = total number of proofs let index = builder.add_virtual_public_input(); + // N flags, one for each inner proof let flags: Vec = (0..N).map(|_i| builder.add_virtual_bool_target_safe()).collect(); + // select the verifier data based on the condition - switch between node and leaf // condition: true -> node, false -> leaf let node_or_leaf_vd = builder.select_verifier_data(condition.clone(), &node_verifier_data, &const_leaf_verifier_data); - // verify the proofs in-circuit - M proofs + // verify the proofs in-circuit - N proofs for i in 0..N { + // select the verifier data based on the flag - switch between real and dummy // flag: true -> real, false -> dummy let selected_vd = builder.select_verifier_data(flags[i].clone(), &node_or_leaf_vd, &const_dummy_vd); builder.verify_proof::(&vir_proofs[i], &selected_vd, &inner_common); @@ -179,7 +212,9 @@ impl< } } - // check inner proof indexes are correct + // check inner proof indices are correct + // we expect the inner proof indices to be in the range [`index` * N, `index` * N + N - 1] + // e.g. if index = 0, then we expect inner proof indices to be in the range [0, N - 1] let m_const = builder.constant(F::from_canonical_u64(N as u64)); let mut expected_inner_index = builder.mul(index, m_const); for i in 0..N { @@ -217,7 +252,7 @@ impl< } fn assign_targets(&self, pw: &mut PartialWitness, targets: &Self::Targets, input: &Self::Input) -> Result<()> { - // assert size of proofs vec + // assert size of vec assert_eq!(input.inner_proofs.len(), N); assert_eq!(input.flags.len(), N); assert!(input.index <= T, "given index is not valid");