1use crate::ProgramCell;
6use crate::constraint::{ConstraintAst, ConstraintExpr, ExprId};
7use alloc::vec;
8use alloc::vec::Vec;
9use hekate_math::{Flat, HardwareField, TowerField};
10
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub enum WireRole {
13 Lhs = 0,
14 Rhs = 1,
15 Product = 2,
16}
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub enum Unknown {
20 Pad(u32),
21 Wire { mul: u32, role: WireRole },
22}
23
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub struct ClaimLayout {
27 pub pad_first: u32,
28 pub half: u32,
29}
30
31impl ClaimLayout {
32 pub fn index(&self, cell: ProgramCell) -> u32 {
33 let base = cell.col_idx as u32;
34
35 match cell.next_row {
36 true => base + self.half,
37 false => base,
38 }
39 }
40}
41
42#[derive(Clone, Debug)]
44pub struct AffineRow<F> {
45 pub unknowns: Vec<(Unknown, Flat<F>)>,
46 pub claims: Vec<(u32, Flat<F>)>,
47 pub constant: Flat<F>,
48}
49
50#[derive(Clone, Debug)]
53pub struct PredicateRows<F> {
54 pub affine: Vec<AffineRow<F>>,
55 pub mul_nodes: u32,
56 pub roots: Vec<Form<F>>,
57}
58
59#[derive(Clone, Debug)]
60pub struct Form<F> {
61 pub unknowns: Vec<(Unknown, Flat<F>)>,
62 pub claims: Vec<(u32, Flat<F>)>,
63 pub constant: Flat<F>,
64}
65
66impl<F: TowerField> Default for Form<F> {
67 fn default() -> Self {
68 Self {
69 unknowns: Vec::new(),
70 claims: Vec::new(),
71 constant: Flat::from_raw(F::ZERO),
72 }
73 }
74}
75
76impl<F: HardwareField> Form<F> {
77 fn extend(&mut self, other: &Self) {
78 self.unknowns.extend_from_slice(&other.unknowns);
79 self.claims.extend_from_slice(&other.claims);
80
81 self.constant += other.constant;
82 }
83
84 fn scaled(&self, coeff: Flat<F>) -> Self {
85 Self {
86 unknowns: self.unknowns.iter().map(|&(v, c)| (v, c * coeff)).collect(),
87 claims: self.claims.iter().map(|&(v, c)| (v, c * coeff)).collect(),
88 constant: self.constant * coeff,
89 }
90 }
91
92 pub fn evaluate(&self, claims: &[Flat<F>], pad: &[Flat<F>], wires: &[[Flat<F>; 3]]) -> Flat<F> {
93 let mut acc = self.constant;
94 for &(idx, coeff) in &self.claims {
95 acc += coeff * claims[idx as usize];
96 }
97
98 for &(unknown, coeff) in &self.unknowns {
99 let v = match unknown {
100 Unknown::Pad(i) => pad[i as usize],
101 Unknown::Wire { mul, role } => wires[mul as usize][role as usize],
102 };
103
104 acc += coeff * v;
105 }
106
107 acc
108 }
109}
110
111pub fn wire_values<F: TowerField + HardwareField>(
114 ast: &ConstraintAst<F>,
115 node_values: &[Flat<F>],
116) -> Vec<[Flat<F>; 3]> {
117 let live = crate::outer::reachable(ast);
118 let mut wires = Vec::new();
119
120 for (i, &alive) in live.iter().enumerate() {
121 if !alive {
122 continue;
123 }
124
125 if let ConstraintExpr::Mul(a, b) = ast.arena.get(ExprId(i as u32)) {
126 wires.push([
127 node_values[a.0 as usize],
128 node_values[b.0 as usize],
129 node_values[i],
130 ]);
131 }
132 }
133
134 wires
135}
136
137pub fn compile<F: HardwareField>(ast: &ConstraintAst<F>, layout: ClaimLayout) -> PredicateRows<F> {
138 let n = ast.arena.len();
139 let live = crate::outer::reachable(ast);
140 let one = Flat::from_raw(F::ONE);
141
142 let mut forms: Vec<Form<F>> = Vec::with_capacity(n);
143 let mut affine: Vec<AffineRow<F>> = Vec::new();
144 let mut mul_nodes: u32 = 0;
145
146 for (i, &alive) in live.iter().enumerate() {
147 if !alive {
148 forms.push(Form::default());
149 continue;
150 }
151
152 let form = match ast.arena.get(ExprId(i as u32)) {
153 ConstraintExpr::Cell(cell) => {
154 let idx = layout.index(*cell);
155
156 Form {
157 unknowns: vec![(Unknown::Pad(layout.pad_first + idx), one)],
158 claims: vec![(idx, one)],
159 constant: Flat::from_raw(F::ZERO),
160 }
161 }
162 ConstraintExpr::Const(v) => Form {
163 unknowns: Vec::new(),
164 claims: Vec::new(),
165 constant: v.to_hardware(),
166 },
167 ConstraintExpr::Add(a, b) => {
168 let mut acc = forms[a.0 as usize].clone();
169 acc.extend(&forms[b.0 as usize]);
170
171 acc
172 }
173 ConstraintExpr::Scale(coeff, a) => forms[a.0 as usize].scaled(coeff.to_hardware()),
174 ConstraintExpr::Sum(children) => {
175 let mut acc = Form::default();
176 for c in children {
177 acc.extend(&forms[c.0 as usize]);
178 }
179
180 acc
181 }
182 ConstraintExpr::Mul(a, b) => {
183 let mul = mul_nodes;
184 mul_nodes += 1;
185
186 for (role, side) in [(WireRole::Lhs, a), (WireRole::Rhs, b)] {
187 let operand = &forms[side.0 as usize];
188
189 let mut unknowns = operand.unknowns.clone();
190 unknowns.push((Unknown::Wire { mul, role }, one));
191
192 affine.push(AffineRow {
193 unknowns,
194 claims: operand.claims.clone(),
195 constant: operand.constant,
196 });
197 }
198
199 Form {
200 unknowns: vec![(
201 Unknown::Wire {
202 mul,
203 role: WireRole::Product,
204 },
205 one,
206 )],
207 claims: Vec::new(),
208 constant: Flat::from_raw(F::ZERO),
209 }
210 }
211 };
212
213 forms.push(form);
214 }
215
216 let roots = ast
217 .roots
218 .iter()
219 .map(|r| forms[r.0 as usize].clone())
220 .collect();
221
222 PredicateRows {
223 affine,
224 mul_nodes,
225 roots,
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232 use crate::constraint::ConstraintArena;
233 use hekate_math::{Block128, Flat, HardwareField};
234
235 type F = Block128;
236
237 const LAYOUT: ClaimLayout = ClaimLayout {
238 pad_first: 100,
239 half: 8,
240 };
241
242 const COLS: usize = 6;
243
244 const HONEST: ClaimLayout = ClaimLayout {
245 pad_first: 0,
246 half: COLS as u32,
247 };
248
249 fn mix(seed: u128) -> F {
250 F::from(
251 seed.wrapping_mul(0x9e37_79b9_7f4a_7c15)
252 .wrapping_add(0x51ed_2701),
253 )
254 }
255
256 fn one() -> Flat<F> {
257 Flat::from_raw(F::ONE)
258 }
259
260 fn value_of(
261 unknowns: &[(Unknown, Flat<F>)],
262 claims: &[(u32, Flat<F>)],
263 constant: Flat<F>,
264 masked: &[F],
265 pad: &[F],
266 wires: &[[Flat<F>; 3]],
267 ) -> Flat<F> {
268 let mut acc = constant;
269 for &(idx, coeff) in claims {
270 acc += coeff * masked[idx as usize].to_hardware();
271 }
272
273 for &(unknown, coeff) in unknowns {
274 let v = match unknown {
275 Unknown::Pad(i) => pad[i as usize].to_hardware(),
276 Unknown::Wire { mul, role } => {
277 let slot = match role {
278 WireRole::Lhs => 0,
279 WireRole::Rhs => 1,
280 WireRole::Product => 2,
281 };
282
283 wires[mul as usize][slot]
284 }
285 };
286
287 acc += coeff * v;
288 }
289
290 acc
291 }
292
293 fn ast_of(build: impl FnOnce(&mut ConstraintArena<F>) -> Vec<ExprId>) -> ConstraintAst<F> {
294 let mut arena = ConstraintArena::<F>::new();
295 let roots = build(&mut arena);
296 let labels = roots.iter().map(|_| None).collect();
297
298 ConstraintAst {
299 arena,
300 roots,
301 labels,
302 }
303 }
304
305 fn rich_ast() -> ConstraintAst<F> {
306 ast_of(|a| {
307 let cols: Vec<ExprId> = (0..COLS).map(|i| a.cell(ProgramCell::current(i))).collect();
308 let nexts: Vec<ExprId> = (0..COLS).map(|i| a.cell(ProgramCell::next(i))).collect();
309
310 let shared = a.mul(cols[0], cols[1]);
311 let k = a.constant(mix(7));
312 let scaled = a.scale(mix(11), shared);
313 let summed = a.sum(vec![cols[2], nexts[3], k, scaled]);
314 let deep = a.mul(summed, nexts[4]);
315 let nested = a.mul(deep, cols[5]);
316 let tail = a.add(nested, shared);
317
318 vec![tail, a.add(shared, nexts[0]), deep]
319 })
320 }
321
322 #[test]
323 fn honest_values_satisfy_every_affine_row() {
324 let ast = rich_ast();
325 let rows = compile(&ast, HONEST);
326
327 let plain: Vec<F> = (0..2 * COLS).map(|i| mix(i as u128 + 1)).collect();
328 let pad: Vec<F> = (0..2 * COLS).map(|i| mix(i as u128 + 977)).collect();
329 let masked: Vec<F> = plain.iter().zip(&pad).map(|(c, h)| *c + *h).collect();
330
331 let current: Vec<Flat<F>> = plain[..COLS].iter().map(|v| v.to_hardware()).collect();
332 let next: Vec<Flat<F>> = plain[COLS..].iter().map(|v| v.to_hardware()).collect();
333
334 let consts = ast.precompute_hardware_consts();
335
336 let mut node_values = Vec::new();
337 ast.evaluate_into(&consts, ¤t, &next, &mut node_values);
338
339 let wires = wire_values(&ast, &node_values);
340
341 assert_eq!(wires.len(), rows.mul_nodes as usize);
342
343 for row in &rows.affine {
344 let zero = Flat::from_raw(F::ZERO);
345 let lhs = value_of(&row.unknowns, &[], zero, &masked, &pad, &wires);
346 let rhs = value_of(&[], &row.claims, row.constant, &masked, &pad, &wires);
347
348 assert_eq!(lhs, rhs);
349 }
350
351 for wire in &wires {
352 assert_eq!(wire[0] * wire[1], wire[2]);
353 }
354 }
355
356 #[test]
357 fn compiled_roots_reproduce_ast_evaluation() {
358 let ast = rich_ast();
359 let rows = compile(&ast, HONEST);
360
361 let plain: Vec<F> = (0..2 * COLS).map(|i| mix(i as u128 + 1)).collect();
362 let pad: Vec<F> = (0..2 * COLS).map(|i| mix(i as u128 + 977)).collect();
363 let masked: Vec<F> = plain.iter().zip(&pad).map(|(c, h)| *c + *h).collect();
364
365 let current: Vec<Flat<F>> = plain[..COLS].iter().map(|v| v.to_hardware()).collect();
366 let next: Vec<Flat<F>> = plain[COLS..].iter().map(|v| v.to_hardware()).collect();
367
368 let consts = ast.precompute_hardware_consts();
369
370 let mut node_values = Vec::new();
371 ast.evaluate_into(&consts, ¤t, &next, &mut node_values);
372
373 let wires = wire_values(&ast, &node_values);
374 let expected = ast.evaluate(¤t, &next);
375
376 for (k, form) in rows.roots.iter().enumerate() {
377 let got = value_of(
378 &form.unknowns,
379 &form.claims,
380 form.constant,
381 &masked,
382 &pad,
383 &wires,
384 );
385
386 assert_eq!(got, expected[k]);
387 }
388 }
389
390 #[test]
391 fn flipped_claim_breaks_roots_that_read_it() {
392 let ast = rich_ast();
393 let rows = compile(&ast, HONEST);
394
395 let plain: Vec<F> = (0..2 * COLS).map(|i| mix(i as u128 + 1)).collect();
396 let pad: Vec<F> = (0..2 * COLS).map(|i| mix(i as u128 + 977)).collect();
397
398 let mut masked: Vec<F> = plain.iter().zip(&pad).map(|(c, h)| *c + *h).collect();
399
400 let current: Vec<Flat<F>> = plain[..COLS].iter().map(|v| v.to_hardware()).collect();
401 let next: Vec<Flat<F>> = plain[COLS..].iter().map(|v| v.to_hardware()).collect();
402
403 let consts = ast.precompute_hardware_consts();
404
405 let mut node_values = Vec::new();
406 ast.evaluate_into(&consts, ¤t, &next, &mut node_values);
407
408 let wires = wire_values(&ast, &node_values);
409
410 let honest: Vec<Flat<F>> = rows
411 .roots
412 .iter()
413 .map(|f| value_of(&f.unknowns, &f.claims, f.constant, &masked, &pad, &wires))
414 .collect();
415
416 const FLIPPED: u32 = 2;
417 masked[FLIPPED as usize] += F::ONE;
418
419 for (k, form) in rows.roots.iter().enumerate() {
420 let got = value_of(
421 &form.unknowns,
422 &form.claims,
423 form.constant,
424 &masked,
425 &pad,
426 &wires,
427 );
428
429 let reads_it = form.claims.iter().any(|&(idx, _)| idx == FLIPPED);
430
431 assert_eq!(got != honest[k], reads_it);
432 }
433 }
434
435 #[test]
436 fn cell_binds_one_pad_entry_and_one_claim() {
437 let ast = ast_of(|a| vec![a.cell(ProgramCell::current(3))]);
438 let rows = compile(&ast, LAYOUT);
439
440 assert_eq!(rows.mul_nodes, 0);
441 assert!(rows.affine.is_empty());
442 assert_eq!(rows.roots[0].unknowns, vec![(Unknown::Pad(103), one())]);
443 assert_eq!(rows.roots[0].claims, vec![(3, one())]);
444 }
445
446 #[test]
447 fn next_row_cell_lands_in_second_half() {
448 let ast = ast_of(|a| vec![a.cell(ProgramCell::next(3))]);
449 let rows = compile(&ast, LAYOUT);
450
451 assert_eq!(rows.roots[0].claims, vec![(11, one())]);
452 assert_eq!(rows.roots[0].unknowns, vec![(Unknown::Pad(111), one())]);
453 }
454
455 #[test]
456 fn mul_emits_two_affine_rows_and_yields_its_product_wire() {
457 let ast = ast_of(|a| {
458 let x = a.cell(ProgramCell::current(0));
459 let y = a.cell(ProgramCell::current(1));
460
461 vec![a.mul(x, y)]
462 });
463
464 let rows = compile(&ast, LAYOUT);
465
466 assert_eq!(rows.mul_nodes, 1);
467 assert_eq!(rows.affine.len(), 2);
468 assert_eq!(
469 rows.roots[0].unknowns,
470 vec![(
471 Unknown::Wire {
472 mul: 0,
473 role: WireRole::Product
474 },
475 one()
476 )]
477 );
478 }
479
480 #[test]
481 fn wire_row_pins_its_operand() {
482 let ast = ast_of(|a| {
483 let x = a.cell(ProgramCell::current(0));
484 let y = a.cell(ProgramCell::current(1));
485
486 vec![a.mul(x, y)]
487 });
488
489 let rows = compile(&ast, LAYOUT);
490 let lhs = &rows.affine[0];
491
492 assert_eq!(lhs.claims, vec![(0, one())]);
493 assert!(lhs.unknowns.contains(&(Unknown::Pad(100), one())));
494 assert!(lhs.unknowns.contains(&(
495 Unknown::Wire {
496 mul: 0,
497 role: WireRole::Lhs
498 },
499 one()
500 )));
501 }
502
503 #[test]
504 fn nested_muls_number_their_wires_in_arena_order() {
505 let ast = ast_of(|a| {
506 let x = a.cell(ProgramCell::current(0));
507 let y = a.cell(ProgramCell::current(1));
508 let z = a.cell(ProgramCell::current(2));
509 let inner = a.mul(x, y);
510
511 vec![a.mul(inner, z)]
512 });
513
514 let rows = compile(&ast, LAYOUT);
515
516 assert_eq!(rows.mul_nodes, 2);
517 assert_eq!(rows.affine.len(), 4);
518 assert_eq!(
519 rows.affine[2].unknowns,
520 vec![
521 (
522 Unknown::Wire {
523 mul: 0,
524 role: WireRole::Product
525 },
526 one()
527 ),
528 (
529 Unknown::Wire {
530 mul: 1,
531 role: WireRole::Lhs
532 },
533 one()
534 )
535 ]
536 );
537 }
538
539 #[test]
540 fn shared_mul_is_compiled_once() {
541 let ast = ast_of(|a| {
542 let x = a.cell(ProgramCell::current(0));
543 let y = a.cell(ProgramCell::current(1));
544 let shared = a.mul(x, y);
545
546 vec![a.add(shared, x), a.add(shared, y)]
547 });
548
549 let rows = compile(&ast, LAYOUT);
550
551 assert_eq!(rows.mul_nodes, 1);
552 assert_eq!(rows.affine.len(), 2);
553 }
554
555 #[test]
556 fn constant_lands_on_public_side() {
557 let ast = ast_of(|a| {
558 let k = a.constant(F::ONE);
559 let x = a.cell(ProgramCell::current(0));
560
561 vec![a.add(k, x)]
562 });
563
564 let rows = compile(&ast, LAYOUT);
565
566 assert_eq!(rows.roots[0].constant, one());
567 assert_eq!(rows.roots[0].claims, vec![(0, one())]);
568 }
569
570 #[test]
571 fn scale_multiplies_every_coefficient() {
572 let two = F::from(2u8);
573
574 let ast = ast_of(|a| {
575 let x = a.cell(ProgramCell::current(0));
576 let k = a.constant(F::ONE);
577 let s = a.add(x, k);
578
579 vec![a.scale(two, s)]
580 });
581
582 let rows = compile(&ast, LAYOUT);
583
584 assert_eq!(rows.roots[0].claims, vec![(0, two.to_hardware())]);
585 assert_eq!(rows.roots[0].constant, two.to_hardware());
586 }
587
588 #[test]
589 fn sum_collects_every_child() {
590 let ast = ast_of(|a| {
591 let cells: Vec<ExprId> = (0..4).map(|i| a.cell(ProgramCell::current(i))).collect();
592
593 vec![a.sum(cells)]
594 });
595
596 let rows = compile(&ast, LAYOUT);
597
598 assert_eq!(rows.roots[0].claims.len(), 4);
599 assert_eq!(rows.roots[0].unknowns.len(), 4);
600 }
601
602 #[test]
603 fn mul_count_agrees_with_shape_pass() {
604 let ast = ast_of(|a| {
605 let x = a.cell(ProgramCell::current(0));
606 let y = a.cell(ProgramCell::current(1));
607 let z = a.cell(ProgramCell::current(2));
608 let p = a.mul(x, y);
609 let q = a.mul(p, z);
610
611 vec![a.add(p, q)]
612 });
613
614 let rows = compile(&ast, LAYOUT);
615
616 assert_eq!(rows.mul_nodes as usize, crate::outer::mul_node_count(&ast));
617 }
618}