1extern crate alloc;
7use alloc::vec;
8use alloc::vec::Vec;
9
10use lib_q_stark_air::{
11 Air,
12 AirBuilder,
13 BaseAir,
14 WindowAccess,
15};
16use lib_q_stark_field::Field;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub struct Wire {
21 pub index: usize,
23}
24
25impl Wire {
26 pub fn new(index: usize) -> Self {
28 Self { index }
29 }
30}
31
32#[derive(Debug, Clone)]
34pub enum Constraint<F: Field> {
35 AssertZero(Wire),
37 AssertEqual(Wire, Wire),
39 AssertConstant(Wire, F),
41 AssertAdd(Wire, Wire, Wire),
43 AssertMul(Wire, Wire, Wire),
45}
46
47#[derive(Debug, Clone)]
49pub struct ArithmeticCircuit<F: Field> {
50 pub constraints: Vec<Constraint<F>>,
52 pub witness_size: usize,
54 pub public_input_size: usize,
56}
57
58impl<F: Field> ArithmeticCircuit<F> {
59 pub fn new(witness_size: usize, public_input_size: usize) -> Self {
61 Self {
62 constraints: Vec::new(),
63 witness_size,
64 public_input_size,
65 }
66 }
67
68 pub fn total_wires(&self) -> usize {
70 self.witness_size + self.public_input_size
71 }
72
73 pub fn add_constraint(&mut self, constraint: Constraint<F>) {
75 self.constraints.push(constraint);
76 }
77
78 fn max_referenced_wire(&self) -> Option<usize> {
80 self.constraints
81 .iter()
82 .flat_map(|c| match c {
83 Constraint::AssertZero(w) => alloc::vec![w.index],
84 Constraint::AssertEqual(l, r) => alloc::vec![l.index, r.index],
85 Constraint::AssertConstant(w, _) => alloc::vec![w.index],
86 Constraint::AssertAdd(o, l, r) => alloc::vec![o.index, l.index, r.index],
87 Constraint::AssertMul(o, l, r) => alloc::vec![o.index, l.index, r.index],
88 })
89 .max()
90 }
91
92 pub fn validate(&self, width: usize) -> Result<(), lib_q_core::Error> {
100 if let Some(max_wire) = self.max_referenced_wire() {
101 if max_wire >= width {
102 return Err(lib_q_core::Error::InvalidState {
103 operation: "ArithmeticCircuit::validate".into(),
104 reason: alloc::format!(
105 "Constraint references wire index {} but trace width is {}; \
106 out-of-range wire indices are rejected",
107 max_wire,
108 width
109 ),
110 });
111 }
112 }
113 Ok(())
114 }
115}
116
117pub struct CircuitBuilder<F: Field> {
119 circuit: ArithmeticCircuit<F>,
120 next_wire: usize,
121}
122
123impl<F: Field> CircuitBuilder<F> {
124 pub fn new(witness_size: usize, public_input_size: usize) -> Self {
148 Self {
149 circuit: ArithmeticCircuit::new(witness_size, public_input_size),
150 next_wire: witness_size + public_input_size,
151 }
152 }
153
154 pub fn alloc_wire(&mut self) -> Wire {
156 let wire = Wire::new(self.next_wire);
157 self.next_wire += 1;
158 wire
159 }
160
161 pub fn wire(&self, index: usize) -> Wire {
163 Wire::new(index)
164 }
165
166 pub fn assert_zero(&mut self, wire: Wire) {
168 self.circuit.add_constraint(Constraint::AssertZero(wire));
169 }
170
171 pub fn assert_eq(&mut self, left: Wire, right: Wire) {
173 self.circuit
174 .add_constraint(Constraint::AssertEqual(left, right));
175 }
176
177 pub fn assert_constant(&mut self, wire: Wire, constant: F) {
179 self.circuit
180 .add_constraint(Constraint::AssertConstant(wire, constant));
181 }
182
183 pub fn add(&mut self, left: Wire, right: Wire) -> Wire {
185 let result = self.alloc_wire();
186 self.circuit
187 .add_constraint(Constraint::AssertAdd(result, left, right));
188 result
189 }
190
191 pub fn mul(&mut self, left: Wire, right: Wire) -> Wire {
193 let result = self.alloc_wire();
194 self.circuit
195 .add_constraint(Constraint::AssertMul(result, left, right));
196 result
197 }
198
199 pub fn build(self) -> ArithmeticCircuit<F> {
201 self.circuit
202 }
203}
204
205pub struct CircuitAir<F: Field> {
210 circuit: ArithmeticCircuit<F>,
211}
212
213impl<F: Field> CircuitAir<F> {
214 pub fn new(circuit: ArithmeticCircuit<F>) -> Self {
216 Self { circuit }
217 }
218
219 pub fn circuit(&self) -> &ArithmeticCircuit<F> {
221 &self.circuit
222 }
223
224 pub fn generate_trace(
240 &self,
241 witness: &[F],
242 public: &[F],
243 ) -> Result<lib_q_stark_matrix::dense::RowMajorMatrix<F>, lib_q_core::Error> {
244 use lib_q_stark_matrix::dense::RowMajorMatrix;
245
246 if witness.len() != self.circuit.witness_size {
248 return Err(lib_q_core::Error::InvalidState {
249 operation: "CircuitAir::generate_trace".into(),
250 reason: alloc::format!(
251 "Witness size mismatch: expected {}, got {}",
252 self.circuit.witness_size,
253 witness.len()
254 ),
255 });
256 }
257
258 if public.len() != self.circuit.public_input_size {
259 return Err(lib_q_core::Error::InvalidState {
260 operation: "CircuitAir::generate_trace".into(),
261 reason: alloc::format!(
262 "Public input size mismatch: expected {}, got {}",
263 self.circuit.public_input_size,
264 public.len()
265 ),
266 });
267 }
268
269 let width = self.width();
270
271 self.circuit.validate(width)?;
274
275 if self.circuit.witness_size + self.circuit.public_input_size > width {
277 return Err(lib_q_core::Error::InvalidState {
278 operation: "CircuitAir::generate_trace".into(),
279 reason: alloc::format!(
280 "Witness ({}) + public inputs ({}) exceed trace width {}",
281 self.circuit.witness_size,
282 self.circuit.public_input_size,
283 width
284 ),
285 });
286 }
287
288 let mut trace_values = F::zero_vec(width);
290
291 for (i, val) in witness.iter().enumerate() {
293 trace_values[i] = *val;
294 }
295
296 for (i, val) in public.iter().enumerate() {
298 let idx = self.circuit.witness_size + i;
299 trace_values[idx] = *val;
300 }
301
302 for constraint in &self.circuit.constraints {
305 match constraint {
306 Constraint::AssertAdd(out, l, r) => {
307 trace_values[out.index] = trace_values[l.index] + trace_values[r.index];
308 }
309 Constraint::AssertMul(out, l, r) => {
310 trace_values[out.index] = trace_values[l.index] * trace_values[r.index];
311 }
312 _ => {}
314 }
315 }
316
317 const MIN_TRACE_ROWS: usize = 64;
319 if MIN_TRACE_ROWS > 1 {
320 let mut padded = trace_values.clone();
321 for _ in 1..MIN_TRACE_ROWS {
322 padded.extend_from_slice(&trace_values);
323 }
324 Ok(RowMajorMatrix::new(padded, width))
325 } else {
326 Ok(RowMajorMatrix::new(trace_values, width))
327 }
328 }
329}
330
331impl<F: Field> BaseAir<F> for CircuitAir<F> {
332 fn width(&self) -> usize {
333 let max_wire = self
336 .circuit
337 .constraints
338 .iter()
339 .flat_map(|c| match c {
340 Constraint::AssertZero(w) => vec![w.index],
341 Constraint::AssertEqual(l, r) => vec![l.index, r.index],
342 Constraint::AssertConstant(w, _) => vec![w.index],
343 Constraint::AssertAdd(r, l, r2) => vec![r.index, l.index, r2.index],
344 Constraint::AssertMul(r, l, r2) => vec![r.index, l.index, r2.index],
345 })
346 .max()
347 .unwrap_or(0);
348 (max_wire + 1).max(self.circuit.total_wires())
349 }
350}
351
352impl<F: Field, AB: AirBuilder<F = F>> Air<AB> for CircuitAir<F> {
353 fn eval(&self, builder: &mut AB) {
354 let pubs = builder.public_values().to_vec();
359
360 let main = builder.main();
361 let row = main.current_slice();
362
363 let base = self.circuit.witness_size;
365 for (i, pv) in pubs.iter().enumerate() {
366 let col = base + i;
367 if col < row.len() {
368 builder.assert_eq(row[col], *pv);
369 }
370 }
371
372 for constraint in &self.circuit.constraints {
374 match constraint {
375 Constraint::AssertZero(w) => {
376 if w.index < row.len() {
378 builder.assert_zero(row[w.index]);
379 }
380 }
381 Constraint::AssertEqual(l, r) => {
382 if l.index < row.len() && r.index < row.len() {
384 builder.assert_eq(row[l.index], row[r.index]);
385 }
386 }
387 Constraint::AssertConstant(w, c) => {
388 if w.index < row.len() {
390 builder.assert_eq(row[w.index], *c);
391 }
392 }
393 Constraint::AssertAdd(out, l, r) => {
394 if out.index < row.len() && l.index < row.len() && r.index < row.len() {
396 let sum = row[l.index] + row[r.index];
397 builder.assert_eq(row[out.index], sum);
398 }
399 }
400 Constraint::AssertMul(out, l, r) => {
401 if out.index < row.len() && l.index < row.len() && r.index < row.len() {
403 let product = row[l.index] * row[r.index];
404 builder.assert_eq(row[out.index], product);
405 }
406 }
407 }
408 }
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use lib_q_stark_air::BaseAir;
415 use lib_q_stark_field::PrimeCharacteristicRing;
416 use lib_q_stark_field::extension::Complex;
417 use lib_q_stark_mersenne31::Mersenne31;
418
419 use super::*;
420
421 type TestField = Complex<Mersenne31>;
422
423 #[test]
424 fn test_circuit_builder_new() {
425 let builder = CircuitBuilder::<TestField>::new(5, 2);
426 let circuit = builder.build();
427 assert_eq!(circuit.witness_size, 5);
428 assert_eq!(circuit.public_input_size, 2);
429 assert_eq!(circuit.total_wires(), 7);
430 }
431
432 #[test]
433 fn test_circuit_builder_alloc_wire() {
434 let mut builder = CircuitBuilder::<TestField>::new(3, 2);
435 let wire1 = builder.alloc_wire();
436 let wire2 = builder.alloc_wire();
437 assert_eq!(wire1.index, 5); assert_eq!(wire2.index, 6);
439 }
440
441 #[test]
442 fn test_circuit_builder_constraints() {
443 let mut builder = CircuitBuilder::<TestField>::new(2, 1);
444 let w0 = builder.wire(0);
445 let w1 = builder.wire(1);
446 let w2 = builder.wire(2);
447
448 builder.assert_zero(w0);
449 builder.assert_eq(w1, w2);
450 builder.assert_constant(w0, <TestField as PrimeCharacteristicRing>::ONE);
451
452 let circuit = builder.build();
453 assert_eq!(circuit.constraints.len(), 3);
454 }
455
456 #[test]
457 fn test_circuit_builder_add_mul() {
458 let mut builder = CircuitBuilder::<TestField>::new(2, 1);
459 let a = builder.wire(0);
460 let b = builder.wire(1);
461 let sum = builder.add(a, b);
462 let product = builder.mul(a, b);
463
464 assert!(sum.index >= 3);
465 assert!(product.index >= 3);
466 assert!(product.index > sum.index);
467
468 let circuit = builder.build();
469 assert_eq!(circuit.constraints.len(), 2);
470 }
471
472 #[test]
473 fn test_arithmetic_circuit() {
474 let mut circuit = ArithmeticCircuit::<TestField>::new(3, 2);
475 circuit.add_constraint(Constraint::AssertZero(Wire::new(0)));
476 circuit.add_constraint(Constraint::AssertEqual(Wire::new(1), Wire::new(2)));
477
478 assert_eq!(circuit.constraints.len(), 2);
479 assert_eq!(circuit.total_wires(), 5);
480 }
481
482 #[test]
483 fn test_circuit_air_width() {
484 let mut circuit = ArithmeticCircuit::<TestField>::new(2, 1);
485 circuit.add_constraint(Constraint::AssertZero(Wire::new(0)));
486 circuit.add_constraint(Constraint::AssertEqual(Wire::new(1), Wire::new(2)));
487
488 let air = CircuitAir::new(circuit);
489 assert!(BaseAir::<TestField>::width(&air) >= 3); }
491}