1use std::{cmp::max, collections::HashMap, sync::Arc};
2
3use itertools::Itertools;
4use p3_air::BaseAir;
5use p3_field::{Field, PrimeCharacteristicRing};
6use p3_util::log2_strict_usize;
7use tracing::instrument;
8
9use crate::{
10 air_builders::symbolic::{
11 get_symbolic_builder,
12 symbolic_variable::{Entry, SymbolicVariable},
13 SymbolicConstraintsDag, SymbolicExpressionNode,
14 },
15 hasher::MerkleHasher,
16 keygen::types::{
17 KeygenError, LinearConstraint, MultiStarkProvingKey, MultiStarkVerifyingKey0,
18 StarkProvingKey, StarkVerifyingKey, StarkVerifyingParams, TraceWidth,
19 VerifierSinglePreprocessedData,
20 },
21 proof::CODEC_VERSION,
22 prover::{
23 stacked_pcs::{stacked_commit, StackedPcsData},
24 ColMajorMatrix, MatrixDimensions,
25 },
26 AirRef, AnyAir, StarkProtocolConfig, SystemParams,
27};
28
29pub mod types;
30
31struct AirKeygenBuilder<SC: StarkProtocolConfig> {
32 pub is_required: bool,
33 air: AirRef<SC>,
34 prep_keygen_data: PrepKeygenData<SC>,
35}
36
37pub struct MultiStarkKeygenBuilder<SC: StarkProtocolConfig> {
40 pub config: SC,
41 partitioned_airs: Vec<AirKeygenBuilder<SC>>,
43}
44
45impl<SC: StarkProtocolConfig> MultiStarkKeygenBuilder<SC> {
46 pub fn new(config: SC) -> Self {
47 Self {
48 config,
49 partitioned_airs: vec![],
50 }
51 }
52
53 pub fn params(&self) -> &SystemParams {
54 self.config.params()
55 }
56
57 pub fn add_air(&mut self, air: AirRef<SC>) -> usize {
60 self.add_air_impl(air, false)
61 }
62
63 pub fn add_required_air(&mut self, air: AirRef<SC>) -> usize {
64 self.add_air_impl(air, true)
65 }
66
67 #[instrument(level = "debug", skip_all, fields(name = air.name(), is_required = is_required))]
68 fn add_air_impl(&mut self, air: AirRef<SC>, is_required: bool) -> usize {
69 self.partitioned_airs
70 .push(AirKeygenBuilder::new(&self.config, air, is_required));
71 self.partitioned_airs.len() - 1
72 }
73
74 pub fn generate_pk(self) -> Result<MultiStarkProvingKey<SC>, KeygenError> {
77 let params = self.params().clone();
78 let max_constraint_degree = params.max_constraint_degree;
79 let pk_per_air: Vec<_> = self
80 .partitioned_airs
81 .into_iter()
82 .map(|keygen_builder| {
83 keygen_builder.generate_pk(max_constraint_degree)
85 })
86 .collect::<Result<Vec<_>, KeygenError>>()?;
87
88 let mut air_max_constraint_degree = 0;
89 #[allow(unused)]
90 for (air_id, pk) in pk_per_air.iter().enumerate() {
91 let width = &pk.vk.params.width;
92 tracing::info!("{:<20} | Constraint Deg = {:<2} | Prep Cols = {:<2} | Main Cols = {:<8} | {:4} Constraints | {:3} Interactions",
93 pk.air_name,
94 pk.vk.max_constraint_degree,
95 width.preprocessed.unwrap_or(0),
96 format!("{:?}",width.main_widths()),
97 pk.vk.symbolic_constraints.constraints.constraint_idx.len(),
98 pk.vk.symbolic_constraints.interactions.len(),
99 );
100 air_max_constraint_degree = max(air_max_constraint_degree, pk.vk.max_constraint_degree);
101 tracing::debug!(
102 "On Buses {:?}",
103 pk.vk
104 .symbolic_constraints
105 .interactions
106 .iter()
107 .map(|i| i.bus_index)
108 .collect_vec()
109 );
110 #[cfg(feature = "metrics")]
111 {
112 let labels = [
113 ("air_name", pk.air_name.clone()),
114 ("air_id", air_id.to_string()),
115 ];
116 metrics::counter!("constraint_deg", &labels)
117 .absolute(pk.vk.max_constraint_degree as u64);
118 metrics::counter!("constraints", &labels)
120 .absolute(pk.vk.symbolic_constraints.constraints.constraint_idx.len() as u64);
121 metrics::counter!("interactions", &labels)
122 .absolute(pk.vk.symbolic_constraints.interactions.len() as u64);
123 metrics::counter!("need_rot", &labels).absolute(pk.vk.params.need_rot as u64);
124 }
125 }
126 if max_constraint_degree != air_max_constraint_degree as usize {
127 tracing::info!(
128 "Actual max constraint degree across all AIRs ({air_max_constraint_degree}) does not match configured max constraint degree ({max_constraint_degree})",
129 );
130 }
131
132 let num_airs = pk_per_air.len();
133 let base_order = SC::F::order().to_u32_digits()[0];
134 let mut count_weight_per_air_per_bus_index = HashMap::new();
135
136 let mut num_interactions_per_air: Vec<u32> = Vec::with_capacity(num_airs);
137 for (air_idx, pk) in pk_per_air.iter().enumerate() {
141 let constraints = &pk.vk.symbolic_constraints;
142 num_interactions_per_air.push(constraints.interactions.len().try_into().unwrap());
143 for interaction in &constraints.interactions {
144 let max_msg_len = params.logup.max_message_length();
147 let total_message_length = interaction.message.len() + 1;
149 assert!(
150 total_message_length <= max_msg_len,
151 "interaction message with bus has length {}, which is more than max {max_msg_len}",
152 total_message_length,
153 );
154
155 let b = interaction.bus_index;
156 let constraint = count_weight_per_air_per_bus_index
157 .entry(b)
158 .or_insert_with(|| LinearConstraint {
159 coefficients: vec![0; num_airs],
160 threshold: base_order,
161 });
162 constraint.coefficients[air_idx] += interaction.count_weight;
163 }
164 }
165
166 let log_up_security_params = params.logup;
167
168 let mut all_constraints: Vec<LinearConstraint> = count_weight_per_air_per_bus_index
170 .into_iter()
171 .sorted_by_key(|(bus_index, _)| *bus_index)
172 .map(|(_, constraint)| constraint)
173 .collect_vec();
174
175 all_constraints.push(LinearConstraint {
176 coefficients: num_interactions_per_air,
177 threshold: log_up_security_params.max_interaction_count,
178 });
179
180 let mut trace_height_constraints: Vec<LinearConstraint> = Vec::new();
182 for constraint in all_constraints {
183 if trace_height_constraints
184 .iter()
185 .any(|c| constraint.is_implied_by(c))
186 {
187 continue;
188 }
189 trace_height_constraints.retain(|c| !c.is_implied_by(&constraint));
190 trace_height_constraints.push(constraint);
191 }
192
193 let pre_vk: MultiStarkVerifyingKey0<SC> = MultiStarkVerifyingKey0 {
194 params: params.clone(),
195 per_air: pk_per_air.iter().map(|pk| pk.vk.clone()).collect(),
196 trace_height_constraints: trace_height_constraints.clone(),
197 };
198 let vk_bytes = postcard::to_allocvec(&pre_vk).unwrap();
202 tracing::debug!("pre-vkey: {} bytes", vk_bytes.len());
203 let versioned_vk_bytes = CODEC_VERSION
206 .to_le_bytes()
207 .into_iter()
208 .chain((vk_bytes.len() as u64).to_le_bytes())
209 .chain(vk_bytes);
210 let vk_pre_hash = self.config.hasher().hash_slice(
212 &versioned_vk_bytes
213 .into_iter()
214 .map(SC::F::from_u8)
215 .collect_vec(),
216 );
217
218 Ok(MultiStarkProvingKey {
219 params,
220 per_air: pk_per_air,
221 trace_height_constraints,
222 max_constraint_degree,
223 vk_pre_hash,
224 })
225 }
226}
227
228impl<SC: StarkProtocolConfig> AirKeygenBuilder<SC> {
229 pub fn new(config: &SC, air: AirRef<SC>, is_required: bool) -> Self {
230 let prep_keygen_data = PrepKeygenData::new(config.hasher(), config.params(), air.as_ref());
231 Self {
232 is_required,
233 air,
234 prep_keygen_data,
235 }
236 }
237
238 pub fn generate_pk(
241 self,
242 max_constraint_degree: usize,
243 ) -> Result<StarkProvingKey<SC>, KeygenError> {
244 let air_name = self.air.name();
245
246 let width = self.trace_width();
247 if width.main_width() == 0 {
248 return Err(KeygenError::AirWidthZero { name: air_name });
249 }
250
251 let symbolic_builder = get_symbolic_builder(self.air.as_ref(), &width);
252 let num_public_values = symbolic_builder.num_public_values();
253
254 let symbolic_constraints = symbolic_builder.constraints();
255 if symbolic_constraints.constraints.is_empty()
256 && symbolic_constraints.interactions.is_empty()
257 {
258 return Err(KeygenError::AirNoConstraintsOrInteractions { name: air_name });
259 }
260 if let Some(interaction_index) = symbolic_constraints
261 .interactions
262 .iter()
263 .position(|interaction| interaction.message.is_empty())
264 {
265 return Err(KeygenError::InteractionMessageEmpty {
266 name: air_name,
267 interaction_index,
268 });
269 }
270 let constraint_degree = symbolic_constraints.max_constraint_degree();
271 if constraint_degree > max_constraint_degree {
272 return Err(KeygenError::MaxConstraintDegreeExceeded {
273 name: air_name.clone(),
274 degree: constraint_degree,
275 max_degree: max_constraint_degree,
276 });
277 }
278
279 let Self {
280 prep_keygen_data:
281 PrepKeygenData {
282 verifier_data: preprocessed_vdata,
283 prover_data: prep_prover_data,
284 },
285 ..
286 } = self;
287
288 let dag = SymbolicConstraintsDag::from(symbolic_constraints);
289 let max_rotation = dag.constraints.max_rotation();
290 debug_assert!(max_rotation <= 1);
291 let need_rot = max_rotation == 1;
292 let vparams = StarkVerifyingParams {
293 width,
294 num_public_values,
295 need_rot,
296 };
297
298 let unused_variables = find_unused_vars(&dag, &vparams.width, need_rot);
299 let vk = StarkVerifyingKey {
300 preprocessed_data: preprocessed_vdata,
301 params: vparams,
302 symbolic_constraints: dag,
303 max_constraint_degree: constraint_degree
304 .try_into()
305 .expect("constraint degree should fit in u8"),
306 is_required: self.is_required,
307 unused_variables,
308 };
309 Ok(StarkProvingKey {
310 air_name,
311 vk,
312 preprocessed_data: prep_prover_data,
313 })
314 }
315
316 fn trace_width(&self) -> TraceWidth {
317 TraceWidth {
318 preprocessed: self.prep_keygen_data.width(),
319 cached_mains: self.air.cached_main_widths(),
320 common_main: self.air.common_main_width(),
321 }
322 }
323}
324
325pub(super) struct PrepKeygenData<SC: StarkProtocolConfig> {
326 pub verifier_data: Option<VerifierSinglePreprocessedData<SC::Digest>>,
327 pub prover_data: Option<Arc<StackedPcsData<SC::F, SC::Digest>>>,
328}
329
330impl<SC: StarkProtocolConfig> PrepKeygenData<SC> {
331 fn new(hasher: &SC::Hasher, params: &SystemParams, air: &dyn AnyAir<SC>) -> Self {
332 let preprocessed_trace = BaseAir::<SC::F>::preprocessed_trace(air);
333 let vpdata_opt = preprocessed_trace.map(|trace| {
334 let trace = ColMajorMatrix::from_row_major(&trace);
335 let (commit, data) = stacked_commit(
336 hasher,
337 params.l_skip,
338 params.n_stack,
339 params.log_blowup,
340 params.k_whir(),
341 &[&trace],
342 )
343 .unwrap();
344 debug_assert_eq!(trace.width(), data.mat_view(0).width());
345 let vdata = VerifierSinglePreprocessedData {
346 commit,
347 hypercube_dim: log2_strict_usize(trace.height()) as isize - params.l_skip as isize,
348 stacking_width: data.matrix.width(),
349 };
350 let pdata = Arc::new(data);
351 (vdata, pdata)
352 });
353 if let Some((vdata, pdata)) = vpdata_opt {
354 Self {
355 prover_data: Some(pdata),
356 verifier_data: Some(vdata),
357 }
358 } else {
359 Self {
360 prover_data: None,
361 verifier_data: None,
362 }
363 }
364 }
365
366 fn width(&self) -> Option<usize> {
367 self.prover_data.as_ref().map(|d| d.mat_view(0).width())
368 }
369}
370
371pub(crate) fn find_unused_vars<F: Field>(
372 constraints: &SymbolicConstraintsDag<F>,
373 width: &TraceWidth,
374 need_rot: bool,
375) -> Vec<SymbolicVariable<F>> {
376 let preprocessed_width = width.preprocessed.unwrap_or(0);
377 let mut preprocessed_present = vec![vec![false; 2]; preprocessed_width];
378
379 let mut main_present = vec![];
380 for width in width.main_widths() {
381 main_present.push(vec![vec![false; 2]; width]);
382 }
383
384 for node in &constraints.constraints.nodes {
385 let SymbolicExpressionNode::Variable(var) = node else {
386 continue;
387 };
388
389 match var.entry {
390 Entry::Preprocessed { offset } => {
391 preprocessed_present[var.index][offset] = true;
392 }
393 Entry::Main { part_index, offset } => {
394 main_present[part_index][var.index][offset] = true;
395 }
396 Entry::Public => {}
397 Entry::Challenge => unreachable!(),
398 }
399 }
400
401 let mut missing = vec![];
402 for (index, presents) in preprocessed_present.iter().enumerate() {
403 for (offset, present) in presents.iter().enumerate() {
404 if !present && (offset == 0 || need_rot) {
405 missing.push(SymbolicVariable::new(Entry::Preprocessed { offset }, index));
406 }
407 }
408 }
409 for (part_index, present_per_part) in main_present.iter().enumerate() {
410 for (index, presents) in present_per_part.iter().enumerate() {
411 for (offset, present) in presents.iter().enumerate() {
412 if !present && (offset == 0 || need_rot) {
413 missing.push(SymbolicVariable::new(
414 Entry::Main { part_index, offset },
415 index,
416 ));
417 }
418 }
419 }
420 }
421 missing
422}