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