remainder/
provable_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::collections::HashSet;
5use std::fmt::Debug;
6
7use itertools::Itertools;
8use ligero::ligero_commit::{ligero_commit, ligero_eval_prove};
9use shared_types::circuit_hash::CircuitHashType;
10use shared_types::config::global_config::get_current_global_prover_config;
11use shared_types::config::ProofConfig;
12use shared_types::transcript::{ProverTranscript, TranscriptSponge, TranscriptWriter};
13use shared_types::Halo2FFTFriendlyField;
14
15use crate::input_layer::ligero_input_layer::{
16    LigeroCommitment, LigeroInputLayerDescriptionWithOptionalProverPrecommit,
17    LigeroInputLayerDescriptionWithOptionalVerifierPrecommit,
18};
19use crate::prover::helpers::get_circuit_description_hash_as_field_elems;
20use crate::prover::{prove_circuit, GKRCircuitDescription, GKRError};
21use crate::utils::debug::sanitycheck_input_layers_and_claims;
22use crate::verifiable_circuit::VerifiableCircuit;
23use crate::{layer::LayerId, mle::evals::MultilinearExtension};
24
25use anyhow::{anyhow, Result};
26
27/// A circuit, along with all of its input data, ready to be proven using the vanila GKR proving
28/// system which uses Ligero as a PCS for committed input layers, and provides _no_ zero-knowledge
29/// guarantees.
30#[derive(Clone, Debug)]
31pub struct ProvableCircuit<F: Halo2FFTFriendlyField> {
32    circuit_description: GKRCircuitDescription<F>,
33    inputs: HashMap<LayerId, MultilinearExtension<F>>,
34    committed_inputs: HashMap<LayerId, LigeroInputLayerDescriptionWithOptionalProverPrecommit<F>>,
35    layer_label_to_layer_id: HashMap<String, LayerId>,
36}
37
38impl<F: Halo2FFTFriendlyField> ProvableCircuit<F> {
39    /// Constructor
40    pub fn new(
41        circuit_description: GKRCircuitDescription<F>,
42        inputs: HashMap<LayerId, MultilinearExtension<F>>,
43        committed_inputs: HashMap<
44            LayerId,
45            LigeroInputLayerDescriptionWithOptionalProverPrecommit<F>,
46        >,
47        layer_label_to_layer_id: HashMap<String, LayerId>,
48    ) -> Self {
49        Self {
50            circuit_description,
51            inputs,
52            committed_inputs,
53            layer_label_to_layer_id,
54        }
55    }
56
57    /// # WARNING
58    /// To be used only for testing and debugging.
59    ///
60    /// Constructs a form of this circuit that can be verified when a proof is provided.
61    /// This is done by erasing all input data associated with committed input layers, along with
62    /// any commitments (the latter can be found in the proof).
63    pub fn _gen_verifiable_circuit(&self) -> VerifiableCircuit<F> {
64        let public_ids: HashSet<LayerId> = self.get_public_input_layer_ids().into_iter().collect();
65        let predetermined_public_inputs: HashMap<LayerId, MultilinearExtension<F>> = self
66            .inputs
67            .clone()
68            .into_iter()
69            .filter(|(layer_id, _)| public_ids.contains(layer_id))
70            .collect();
71
72        let committed_inputs: HashMap<
73            LayerId,
74            LigeroInputLayerDescriptionWithOptionalVerifierPrecommit<F>,
75        > = self
76            .committed_inputs
77            .clone()
78            .into_iter()
79            .map(|(layer_id, (desc, opt_commit))| {
80                (layer_id, (desc, opt_commit.map(|commit| commit.get_root())))
81            })
82            .collect();
83
84        VerifiableCircuit::new(
85            self.circuit_description.clone(),
86            predetermined_public_inputs,
87            committed_inputs,
88            self.layer_label_to_layer_id.clone(),
89        )
90    }
91
92    /// Returns a reference to the [GKRCircuitDescription] of this circuit.
93    ///
94    /// TODO: This is only used by the back end. Do _not_ expose it to the circuit developer.
95    pub fn get_gkr_circuit_description_ref(&self) -> &GKRCircuitDescription<F> {
96        &self.circuit_description
97    }
98
99    /// Returns a vector of the [LayerId]s of all input layers which are public.
100    ///
101    /// TODO: Consider returning an iterator instead.
102    pub fn get_public_input_layer_ids(&self) -> Vec<LayerId> {
103        self.inputs
104            .keys()
105            .filter(|layer_id| !self.committed_inputs.contains_key(layer_id))
106            .cloned()
107            .collect()
108    }
109
110    /// Returns a vector of the [LayerId]s of all input layers which are committed
111    /// to using a PCS.
112    ///
113    /// TODO: Consider returning an iterator instead.
114    pub fn get_committed_input_layer_ids(&self) -> Vec<LayerId> {
115        self.committed_inputs.keys().cloned().collect()
116    }
117
118    /// Returns the data associated with the input layer with ID `layer_id`, or an error if there is
119    /// no input layer with this ID.
120    pub fn get_input_mle(&self, layer_id: LayerId) -> Result<MultilinearExtension<F>> {
121        self.inputs
122            .get(&layer_id)
123            .ok_or(anyhow!("Unrecognized Layer ID '{layer_id}'"))
124            .cloned()
125    }
126
127    /// Returns the description of the committed input layer with ID `layer_id`, or an error if no
128    /// such committed input layer exists.
129    pub fn get_committed_input_layer(
130        &self,
131        layer_id: LayerId,
132    ) -> Result<LigeroInputLayerDescriptionWithOptionalProverPrecommit<F>> {
133        self.committed_inputs
134            .get(&layer_id)
135            .ok_or(anyhow!("Unrecognized Layer ID '{layer_id}'"))
136            .cloned()
137    }
138
139    /// Get a reference to the mapping that maps a [LayerId] of layer (either public or committed)
140    /// to the data that are associated with it.
141    ///
142    /// TODO: This is too transparent. Replace this with methods that answer the queries of the
143    /// prover directly, and do _not_ expose it to the circuit developer.
144    pub fn get_inputs_ref(&self) -> &HashMap<LayerId, MultilinearExtension<F>> {
145        &self.inputs
146    }
147
148    /// Write the GKR proof to transcript.
149    /// Appends inputs and Ligero input layer commitments to the transcript: public inputs first, then Ligero input layers, ordering by layer id in both cases.
150    /// Arguments:
151    /// * `circuit_description_hash_type`: The hash function we wish to use to hash the circuit description.
152    /// * `transcript_writer`: The struct used by the prover to write messages to the transcript, which will be used by the verifier to verify the circuit.
153    pub fn prove<Tr: TranscriptSponge<F>>(
154        &self,
155        circuit_description_hash_type: CircuitHashType,
156        transcript_writer: &mut TranscriptWriter<F, Tr>,
157    ) -> Result<ProofConfig> {
158        // Grab proof config from global config
159        let proof_config = ProofConfig::new_from_prover_config(&get_current_global_prover_config());
160
161        // Generate circuit description hash and append to transcript
162        let hash_value_as_field_elems = get_circuit_description_hash_as_field_elems(
163            self.get_gkr_circuit_description_ref(),
164            circuit_description_hash_type,
165        );
166        transcript_writer.append_elements("Circuit description hash", &hash_value_as_field_elems);
167
168        // Add the input values of any public (i.e. non-ligero) input layers to transcript.
169        // Select the public input layers from the input layers, and sort them by layer id, and append
170        // their input values to the transcript.
171        self.get_public_input_layer_ids()
172            .into_iter()
173            .sorted_by_key(|layer_id| layer_id.get_raw_input_layer_id())
174            .for_each(|layer_id| {
175                let mle = self.get_input_mle(layer_id).unwrap();
176                transcript_writer
177                    .append_input_elements("Public input layer", &mle.iter().collect_vec());
178            });
179
180        // For each Ligero input layer, calculate commitments if not already provided, and then add each
181        // commitment to the transcript.
182        let mut ligero_input_commitments = HashMap::<LayerId, LigeroCommitment<F>>::new();
183
184        self.get_committed_input_layer_ids()
185            .into_iter()
186            .sorted_by_key(|layer_id| layer_id.get_raw_input_layer_id())
187            .for_each(|layer_id| {
188                // Commit to the Ligero input layer, if it is not already committed to.
189                let (desc, maybe_precommitment) = self.get_committed_input_layer(layer_id).unwrap();
190                let commitment = if let Some(commitment) = maybe_precommitment {
191                    commitment.clone()
192                } else {
193                    let input_mle = self.get_input_mle(layer_id).unwrap();
194                    let (commitment, _) = ligero_commit(&input_mle.iter().collect_vec(), &desc.aux);
195                    commitment
196                };
197                // Add the root of the commitment to the transcript.
198                let root = commitment.get_root();
199                transcript_writer.append_input_elements("Ligero commit", &[root.into_raw()]);
200                // Store the commitment for later use.
201                ligero_input_commitments.insert(layer_id, commitment);
202            });
203
204        // Mutate the transcript to contain the proof of the intermediate layers of the circuit,
205        // and return the claims on the input layer.
206        let input_layer_claims = prove_circuit(self, transcript_writer).unwrap();
207
208        // If in performance debugging mode, print the number of claims on input
209        // layers and input layer sizes.
210        if cfg!(feature = "performance-debug") {
211            sanitycheck_input_layers_and_claims(
212                &input_layer_claims,
213                self.get_gkr_circuit_description_ref(),
214            );
215        }
216
217        // If in debug mode, then check the claims on all input layers.
218        if cfg!(debug_assertions) {
219            for claim in input_layer_claims.iter() {
220                let input_mle = self.get_input_mle(claim.get_to_layer_id()).unwrap();
221                let evaluation = input_mle.evaluate_at_point(claim.get_point());
222                if evaluation != claim.get_eval() {
223                    return Err(anyhow!(GKRError::EvaluationMismatch(
224                        claim.get_to_layer_id(),
225                        claim.get_to_layer_id(),
226                    )));
227                }
228            }
229        }
230
231        // Create a Ligero evaluation proof for each claim on a Ligero input layer, writing it to transcript.
232        for claim in input_layer_claims.iter() {
233            let layer_id = claim.get_to_layer_id();
234            if let Ok((desc, _)) = self.get_committed_input_layer(layer_id) {
235                let mle = self.get_input_mle(layer_id).unwrap();
236                let commitment = ligero_input_commitments.get(&layer_id).unwrap();
237                ligero_eval_prove(
238                    &mle.f.iter().collect_vec(),
239                    claim.get_point(),
240                    transcript_writer,
241                    &desc.aux,
242                    commitment,
243                )
244                .unwrap();
245            }
246        }
247
248        Ok(proof_config)
249    }
250}