remainder/
verifiable_circuit.rs

1//! Defines the utilities for taking a list of nodes and turning it into a
2//! layedout circuit
3use std::collections::HashMap;
4use std::fmt::Debug;
5
6use itertools::Itertools;
7use ligero::ligero_commit::ligero_verify;
8use shared_types::circuit_hash::CircuitHashType;
9use shared_types::config::global_config::get_current_global_verifier_config;
10use shared_types::config::ProofConfig;
11use shared_types::transcript::VerifierTranscript;
12use shared_types::Halo2FFTFriendlyField;
13
14use crate::input_layer::ligero_input_layer::{
15    LigeroInputLayerDescriptionWithOptionalVerifierPrecommit, LigeroRoot,
16};
17use crate::prover::helpers::get_circuit_description_hash_as_field_elems;
18use crate::prover::{GKRCircuitDescription, GKRError};
19use crate::{layer::LayerId, mle::evals::MultilinearExtension};
20
21use anyhow::{anyhow, Result};
22
23/// A circuit that contains a [GKRCircuitDescription] alongside a description of
24/// the committed input layers.
25#[derive(Clone, Debug)]
26pub struct VerifiableCircuit<F: Halo2FFTFriendlyField> {
27    circuit_description: GKRCircuitDescription<F>,
28    /// A partial mapping of public input layers to MLEs.
29    /// Some (or all) public input layer IDs may be missing.
30    /// The input layers present in this mapping are public input data that the verifier placed in
31    /// the circuit, and which will be checked for equality with the respective public inputs in the
32    /// proof during verification.
33    predetermined_public_inputs: HashMap<LayerId, MultilinearExtension<F>>,
34    committed_inputs: HashMap<LayerId, LigeroInputLayerDescriptionWithOptionalVerifierPrecommit<F>>,
35    layer_label_to_layer_id: HashMap<String, LayerId>,
36}
37
38impl<F: Halo2FFTFriendlyField> VerifiableCircuit<F> {
39    /// Returns a [VerifiableCircuit] initialized with the given data.
40    pub fn new(
41        circuit_description: GKRCircuitDescription<F>,
42        predetermined_public_inputs: HashMap<LayerId, MultilinearExtension<F>>,
43        committed_inputs: HashMap<
44            LayerId,
45            LigeroInputLayerDescriptionWithOptionalVerifierPrecommit<F>,
46        >,
47        layer_label_to_layer_id: HashMap<String, LayerId>,
48    ) -> Self {
49        Self {
50            circuit_description,
51            predetermined_public_inputs,
52            committed_inputs,
53            layer_label_to_layer_id,
54        }
55    }
56
57    /// Returns a reference to the mapping which maps a [LayerId] of a committed input layer to its
58    /// description.
59    ///
60    /// TODO: This is too transparent. Replace this with methods that answer the queries of the
61    /// prover directly, and do _not_ expose it to the circuit developer.
62    pub fn get_committed_inputs_ref(
63        &self,
64    ) -> &HashMap<LayerId, LigeroInputLayerDescriptionWithOptionalVerifierPrecommit<F>> {
65        &self.committed_inputs
66    }
67
68    /// Returns a reference to the circuit description.
69    ///
70    /// TODO: This is only used by the back end. Do _not_ expose it to the circuit developer.
71    pub fn get_gkr_circuit_description_ref(&self) -> &GKRCircuitDescription<F> {
72        &self.circuit_description
73    }
74
75    /// Returns a vector of the [LayerId]s of all input layers which are public.
76    pub fn get_public_input_layer_ids(&self) -> Vec<LayerId> {
77        self.circuit_description
78            .input_layers
79            .iter()
80            .filter(|input_layer_description| {
81                // All input layers which are not committed are public by default.
82                !self
83                    .committed_inputs
84                    .contains_key(&input_layer_description.layer_id)
85            })
86            .map(|public_input_layer_description| public_input_layer_description.layer_id)
87            .collect()
88    }
89
90    /// Returns a vector of the [LayerId]s of all input layers which are committed
91    /// to using a PCS.
92    pub fn get_committed_input_layer_ids(&self) -> Vec<LayerId> {
93        self.committed_inputs.keys().cloned().collect_vec()
94    }
95
96    /// Returns the data associated with the public input layer with ID `layer_id`, or None if
97    /// no such public input layer exists.
98    pub fn get_predetermined_public_input_mle_ref(
99        &self,
100        layer_id: &LayerId,
101    ) -> Option<&MultilinearExtension<F>> {
102        self.predetermined_public_inputs.get(layer_id)
103    }
104
105    /// Sets the pre-commitment of the input layer with label `layer_label` to `commitment`.
106    /// Pre-commitments allow the verifier to check that an input layer commitment in the
107    /// proof is the same as the expected `commitment` provided here.
108    ///
109    /// # Panics
110    /// If `layer_label` is not a valid committed input layer label.
111    pub fn set_pre_commitment(
112        &mut self,
113        layer_label: &str,
114        commitment: LigeroRoot<F>,
115    ) -> Result<()> {
116        let layer_id = self.layer_label_to_layer_id.get(layer_label).unwrap();
117
118        let (_, optional_commitment) = self
119            .committed_inputs
120            .get_mut(layer_id)
121            .expect("Layer {layer_id} either does not exist, or is not a committed input layer.");
122
123        *optional_commitment = Some(commitment);
124
125        Ok(())
126    }
127
128    /// Verify a GKR proof from a transcript.
129    pub fn verify(
130        &self,
131        circuit_description_hash_type: CircuitHashType,
132        transcript: &mut impl VerifierTranscript<F>,
133        proof_config: &ProofConfig,
134    ) -> Result<()> {
135        // Check whether proof config matches current global verifier config
136        if !get_current_global_verifier_config().matches_proof_config(proof_config) {
137            panic!("Error: Attempted to verify a GKR proof whose config doesn't match that of the verifier.");
138        }
139
140        // Check the shape of the circuit description against the proof -- only needed to be done
141        // for the input layers, because for intermediate and output layers, the proof is in the
142        // transcript, which the verifier checks shape against the circuit description already.
143        assert_eq!(
144            self.get_public_input_layer_ids().len() + self.get_committed_inputs_ref().len(),
145            self.get_gkr_circuit_description_ref().input_layers.len()
146        );
147
148        // Generate circuit description hash and check against prover-provided circuit description hash
149        let hash_value_as_field_elems = get_circuit_description_hash_as_field_elems(
150            self.get_gkr_circuit_description_ref(),
151            circuit_description_hash_type,
152        );
153        let prover_supplied_circuit_description_hash = transcript
154            .consume_elements("Circuit description hash", hash_value_as_field_elems.len())
155            .unwrap();
156        assert_eq!(
157            prover_supplied_circuit_description_hash,
158            hash_value_as_field_elems
159        );
160
161        let mut public_inputs = self.predetermined_public_inputs.clone();
162
163        // Read and check public input values to transcript in order of layer id.
164        self.get_public_input_layer_ids()
165            .into_iter()
166            .sorted_by_key(|layer_id| layer_id.get_raw_input_layer_id())
167            .map(|layer_id| {
168                let layer_desc = self
169                    .get_gkr_circuit_description_ref()
170                    .input_layers
171                    .iter()
172                    .find(|desc| desc.layer_id == layer_id)
173                    .unwrap();
174                let (transcript_evaluations, _expected_input_hash_chain_digest) = transcript
175                    .consume_input_elements("Public input layer", 1 << layer_desc.num_vars)
176                    .unwrap();
177                match self.get_predetermined_public_input_mle_ref(&layer_id) {
178                    Some(predetermined_public_input) => {
179                        // If the verifier already knows what the input should be
180                        // ahead of time, check against the transcript evaluations
181                        // sent by the prover.
182                        if predetermined_public_input.f.iter().collect_vec()
183                            != transcript_evaluations
184                        {
185                            Err(anyhow!(GKRError::PublicInputLayerValuesMismatch(layer_id)))
186                        } else {
187                            Ok(())
188                        }
189                    }
190                    None => {
191                        // Otherwise, we append the proof values read from
192                        // transcript to the public inputs.
193                        public_inputs
194                            .insert(layer_id, MultilinearExtension::new(transcript_evaluations));
195                        Ok(())
196                    }
197                }
198            })
199            .collect::<Result<Vec<_>>>()?;
200
201        // Sanitycheck: ensure that all of the public inputs are populated
202        assert_eq!(public_inputs.len(), self.get_public_input_layer_ids().len());
203
204        // Read the Ligero input layer commitments from transcript in order of layer id.
205        let mut ligero_commitments = HashMap::<LayerId, F>::new();
206        self.get_committed_input_layer_ids()
207            .into_iter()
208            .sorted_by_key(|layer_id| layer_id.get_raw_input_layer_id())
209            .for_each(|layer_id| {
210                let (commitment_as_vec, _expected_input_hash_chain_digest) = transcript
211                    .consume_input_elements("Ligero commit", 1)
212                    .unwrap();
213                assert_eq!(commitment_as_vec.len(), 1);
214                ligero_commitments.insert(layer_id, commitment_as_vec[0]);
215            });
216
217        let input_layer_claims = self
218            .get_gkr_circuit_description_ref()
219            .verify(transcript)
220            .unwrap();
221
222        // Every input layer claim is either for a public- or Ligero- input layer.
223        let mut public_input_layer_claims = vec![];
224        let mut ligero_input_layer_claims = vec![];
225        input_layer_claims.into_iter().for_each(|claim| {
226            let layer_id = claim.get_to_layer_id();
227            if self.get_public_input_layer_ids().contains(&layer_id) {
228                public_input_layer_claims.push(claim);
229            } else if ligero_commitments.contains_key(&layer_id) {
230                ligero_input_layer_claims.push(claim);
231            } else {
232                // This can only be a programming error on our part (since there was sufficient input
233                // data to verify the proof of the circuit).
234                panic!("Input layer {layer_id:?} has a claim but is not a public input layer nor a Ligero input layer.");
235            }
236        });
237
238        // Check the claims on public input layers via explicit evaluation.
239        for claim in public_input_layer_claims.iter() {
240            let input_mle = public_inputs.get(&claim.get_to_layer_id()).unwrap();
241            let evaluation = input_mle.evaluate_at_point(claim.get_point());
242            if evaluation != claim.get_eval() {
243                return Err(anyhow!(GKRError::EvaluationMismatch(
244                    claim.get_to_layer_id(),
245                    claim.get_from_layer_id(),
246                )));
247            }
248        }
249
250        // Check the claims on Ligero input layers via their evaluation proofs.
251        for claim in ligero_input_layer_claims.iter() {
252            let claim_layer_id = claim.get_to_layer_id();
253            let commitment = ligero_commitments.get(&claim_layer_id).unwrap();
254
255            let (_, (desc, opt_pre_commitment)) = self
256                .get_committed_inputs_ref()
257                .iter()
258                .find(|(layer_id, _)| **layer_id == claim_layer_id)
259                .unwrap();
260
261            if let Some(pre_commitment) = opt_pre_commitment {
262                assert_eq!(pre_commitment.root, *commitment);
263            }
264
265            ligero_verify::<F>(
266                commitment,
267                &desc.aux,
268                transcript,
269                claim.get_point(),
270                claim.get_eval(),
271            );
272        }
273
274        Ok(())
275    }
276}