1#![cfg_attr(not(feature = "std"), no_std)]
25
26extern crate alloc;
27extern crate core;
28
29use alloc::collections::BTreeMap;
30use alloc::string::{String, ToString};
31use alloc::vec;
32use alloc::vec::Vec;
33use constraint::{BoundaryConstraint, Constraint, ConstraintAst};
34use core::marker::PhantomData;
35use expander::VirtualExpander;
36use hekate_core::errors;
37use hekate_core::trace::{ColumnTrace, ColumnType, Trace, TraceCompatibleField};
38use hekate_math::{Flat, HardwareField, TowerField};
39use permutation::PermutationCheckSpec;
40
41pub mod chiplet;
42pub mod circuit;
43pub mod constraint;
44pub mod digest;
45pub mod expander;
46pub mod linearized;
47pub mod outer;
48pub mod permutation;
49pub mod predicate;
50pub mod schema;
51
52pub trait Air<F: TowerField>: Sized + Clone + Sync {
62 fn name(&self) -> String {
63 "HekateAir".to_string()
64 }
65
66 fn num_columns(&self) -> usize {
67 self.virtual_column_layout().len()
68 }
69
70 fn constraints(&self) -> Vec<Constraint<F>> {
72 self.constraint_ast().to_constraints()
73 }
74
75 fn boundary_constraints(&self) -> Vec<BoundaryConstraint<F>> {
79 Vec::new()
80 }
81
82 fn column_layout(&self) -> &[ColumnType];
87
88 fn virtual_column_layout(&self) -> &[ColumnType] {
92 match self.virtual_expander() {
93 Some(e) => e.virtual_layout(),
94 None => self.column_layout(),
95 }
96 }
97
98 fn permutation_checks(&self) -> Vec<(String, PermutationCheckSpec)> {
110 Vec::new()
111 }
112
113 fn fixed_columns(&self) -> Vec<FixedColumn<F>> {
117 Vec::new()
118 }
119
120 fn virtual_expander(&self) -> Option<&VirtualExpander> {
125 None
126 }
127
128 fn parse_virtual_row(&self, bytes: &[u8], res: &mut Vec<Flat<F>>)
135 where
136 F: TraceCompatibleField,
137 {
138 res.clear();
139
140 if let Some(e) = self.virtual_expander() {
141 e.parse_row(bytes, res)
142 .expect("committed row byte length must match physical_row_bytes");
143 return;
144 }
145
146 let mut offset = 0;
147 for col_type in self.column_layout() {
148 let size = col_type.byte_size();
149 if offset + size <= bytes.len() {
150 res.push(col_type.parse_from_bytes(&bytes[offset..offset + size]));
151 offset += size;
152 }
153 }
154 }
155
156 fn constraint_ast(&self) -> ConstraintAst<F>;
158
159 fn inline_chiplets(&self) -> errors::Result<Vec<chiplet::ChipletDef<F>>> {
161 Ok(Vec::new())
162 }
163
164 fn inline_chiplet_kernels(&self) -> Vec<InlineKernelHint> {
166 Vec::new()
167 }
168}
169
170pub trait Program<F: TowerField>: Air<F> {
181 fn num_public_inputs(&self) -> usize {
183 0
184 }
185
186 fn chiplet_defs(&self) -> errors::Result<Vec<chiplet::ChipletDef<F>>> {
191 Ok(Vec::new())
192 }
193}
194
195#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
199pub struct ProgramCell {
200 pub col_idx: usize,
201
202 pub next_row: bool,
205}
206
207impl ProgramCell {
208 pub fn current(col_idx: usize) -> Self {
210 Self {
211 col_idx,
212 next_row: false,
213 }
214 }
215
216 pub fn next(col_idx: usize) -> Self {
218 Self {
219 col_idx,
220 next_row: true,
221 }
222 }
223}
224
225#[derive(Clone, Debug)]
232pub struct ProgramInstance<F: TowerField> {
233 num_rows: usize,
234 public_inputs: Vec<F>,
235}
236
237impl<F: TowerField> ProgramInstance<F> {
238 pub fn new(num_rows: usize, public_inputs: Vec<F>) -> Self {
239 assert!(
240 num_rows.is_power_of_two(),
241 "Program trace height must be power of 2"
242 );
243
244 Self {
245 num_rows,
246 public_inputs,
247 }
248 }
249
250 #[inline(always)]
251 pub fn num_rows(&self) -> usize {
252 self.num_rows
253 }
254
255 #[inline(always)]
257 pub fn public_inputs(&self) -> &[F] {
258 &self.public_inputs
259 }
260
261 #[inline(always)]
262 pub fn public_input(&self, idx: usize) -> Option<F> {
263 self.public_inputs.get(idx).copied()
264 }
265}
266
267pub struct ProgramWitness<F: TowerField, T: Trace = ColumnTrace> {
271 pub trace: T,
272 pub chiplet_traces: Vec<ColumnTrace>,
273 _marker: PhantomData<F>,
274}
275
276impl<F: TowerField, T: Trace> ProgramWitness<F, T> {
277 pub fn new(trace: T) -> Self {
278 Self {
279 trace,
280 chiplet_traces: Vec::new(),
281 _marker: PhantomData,
282 }
283 }
284
285 pub fn with_chiplets(mut self, chiplet_traces: Vec<ColumnTrace>) -> Self {
288 self.chiplet_traces = chiplet_traces;
289 self
290 }
291}
292
293#[derive(Clone, Copy, Debug, PartialEq, Eq)]
297pub struct InlineKernelHint {
298 pub chiplet_idx: usize,
300
301 pub root_offset: usize,
304
305 pub column_offset: usize,
308}
309
310const EQ_TABLE_MAX_VARS: usize = 32;
318
319#[derive(Clone, Debug, PartialEq, Eq)]
320pub struct CadenceSegment<F> {
321 pub stride: usize,
322 pub count: usize,
323 pub origin: usize,
324 pub values: Vec<F>,
325}
326
327#[derive(Clone, Debug, PartialEq, Eq)]
330pub enum FixedShape<F> {
331 LastRow,
332 FirstRow,
333 Custom(Vec<bool>),
334 Periodic {
335 period: usize,
336 values: Vec<F>,
337 },
338 Sparse(Vec<(usize, F)>),
339 Dense(Vec<F>),
340
341 Cadence {
344 stride: usize,
345 count: usize,
346 origin: usize,
347 values: Vec<F>,
348 },
349
350 Segments(Vec<CadenceSegment<F>>),
353}
354
355impl<F> FixedShape<F> {
356 pub fn is_overlay(&self) -> bool {
359 match self {
360 Self::Periodic { .. }
361 | Self::Sparse(..)
362 | Self::Dense(..)
363 | Self::Cadence { .. }
364 | Self::Segments(..) => true,
365 Self::LastRow | Self::FirstRow | Self::Custom(..) => false,
366 }
367 }
368}
369
370impl<F: HardwareField> FixedShape<F> {
371 pub fn evaluate(&self, r: &[Flat<F>]) -> Flat<F> {
374 let one = Flat::from_raw(F::ONE);
375 match self {
376 FixedShape::LastRow => {
377 let mut prod = one;
378 for &r_k in r {
379 prod *= r_k;
380 }
381
382 one - prod
383 }
384 FixedShape::FirstRow => {
385 let mut prod = one;
386 for &r_k in r {
387 prod *= one - r_k;
388 }
389
390 prod
391 }
392 FixedShape::Custom(bits) => {
393 debug_assert_eq!(bits.len(), r.len(), "Custom point bit width != r.len()");
394
395 let mut prod = one;
396 for (k, &b) in bits.iter().enumerate() {
397 let factor = if b { r[k] } else { one - r[k] };
398 prod *= factor;
399 }
400
401 prod
402 }
403 FixedShape::Periodic { period, values } => {
404 let p = period.trailing_zeros() as usize;
407
408 let mut acc = Flat::from_raw(F::ZERO);
409 for (j, &v) in values.iter().enumerate() {
410 acc += v.to_hardware() * eq_index(&r[..p], j);
411 }
412
413 acc
414 }
415 FixedShape::Sparse(entries) => {
416 let mut acc = Flat::from_raw(F::ZERO);
417 for &(row, v) in entries {
418 acc += v.to_hardware() * eq_index(r, row);
419 }
420
421 acc
422 }
423 FixedShape::Dense(values) => {
424 let mut acc = Flat::from_raw(F::ZERO);
425 for (i, &v) in values.iter().enumerate() {
426 acc += v.to_hardware() * eq_index(r, i);
427 }
428
429 acc
430 }
431 FixedShape::Cadence {
432 stride,
433 count,
434 origin,
435 values,
436 } => cadence_mle(r, *stride, *count, *origin, values),
437 FixedShape::Segments(segments) => {
438 let mut acc = Flat::from_raw(F::ZERO);
439 for seg in segments {
440 acc += cadence_mle(r, seg.stride, seg.count, seg.origin, &seg.values);
441 }
442
443 acc
444 }
445 }
446 }
447
448 pub fn value_at_row(&self, row: usize, num_vars: usize) -> Flat<F> {
452 let one = Flat::from_raw(F::ONE);
453 let zero = Flat::from_raw(F::ZERO);
454
455 match self {
456 FixedShape::FirstRow => {
457 if row == 0 {
458 one
459 } else {
460 zero
461 }
462 }
463 FixedShape::LastRow => {
464 if row == (1usize << num_vars) - 1 {
465 zero
466 } else {
467 one
468 }
469 }
470 FixedShape::Custom(bits) => {
471 let target = bits
472 .iter()
473 .enumerate()
474 .fold(0usize, |acc, (k, &b)| acc | ((b as usize) << k));
475
476 if row == target { one } else { zero }
477 }
478 FixedShape::Periodic { period, values } => values[row % period].to_hardware(),
479 FixedShape::Sparse(entries) => {
480 let mut acc = zero;
481 for &(r, v) in entries {
482 if r == row {
483 acc += v.to_hardware();
484 }
485 }
486
487 acc
488 }
489 FixedShape::Dense(values) => values[row].to_hardware(),
490 FixedShape::Cadence {
491 stride,
492 count,
493 origin,
494 values,
495 } => cadence_value_at(row, *stride, *count, *origin, values),
496 FixedShape::Segments(segments) => {
497 let at = segments.partition_point(|seg| seg.origin <= row);
500
501 match at.checked_sub(1) {
502 Some(i) => {
503 let seg = &segments[i];
504
505 cadence_value_at(row, seg.stride, seg.count, seg.origin, &seg.values)
506 }
507 None => zero,
508 }
509 }
510 }
511 }
512}
513
514#[derive(Clone, Debug, PartialEq, Eq)]
516pub struct FixedColumn<F> {
517 pub col_idx: usize,
518 pub shape: FixedShape<F>,
519}
520
521impl<F> FixedColumn<F> {
522 pub fn last_row(col_idx: usize) -> Self {
523 Self {
524 col_idx,
525 shape: FixedShape::LastRow,
526 }
527 }
528
529 pub fn first_row(col_idx: usize) -> Self {
530 Self {
531 col_idx,
532 shape: FixedShape::FirstRow,
533 }
534 }
535
536 pub fn prefix(col_idx: usize, count: usize) -> Self
537 where
538 F: TowerField,
539 {
540 Self {
541 col_idx,
542 shape: FixedShape::Cadence {
543 stride: 1,
544 count,
545 origin: 0,
546 values: vec![F::ONE],
547 },
548 }
549 }
550
551 pub fn custom(col_idx: usize, bits: Vec<bool>) -> Self {
552 Self {
553 col_idx,
554 shape: FixedShape::Custom(bits),
555 }
556 }
557
558 pub fn periodic(col_idx: usize, period: usize, values: Vec<F>) -> Self {
559 Self {
560 col_idx,
561 shape: FixedShape::Periodic { period, values },
562 }
563 }
564
565 pub fn sparse(col_idx: usize, entries: Vec<(usize, F)>) -> Self {
566 Self {
567 col_idx,
568 shape: FixedShape::Sparse(entries),
569 }
570 }
571
572 pub fn dense(col_idx: usize, values: Vec<F>) -> Self {
573 Self {
574 col_idx,
575 shape: FixedShape::Dense(values),
576 }
577 }
578
579 pub fn segments(col_idx: usize, segments: Vec<CadenceSegment<F>>) -> Self {
580 Self {
581 col_idx,
582 shape: FixedShape::Segments(segments),
583 }
584 }
585}
586
587pub struct ShapeEvaluator<'a, F> {
590 r: &'a [Flat<F>],
591 eq: Option<EqTables<F>>,
592 sums: BTreeMap<(usize, usize), Vec<Flat<F>>>,
593}
594
595impl<'a, F: HardwareField> ShapeEvaluator<'a, F> {
596 pub fn new(r: &'a [Flat<F>]) -> Self {
597 Self {
598 r,
599 eq: (r.len() <= EQ_TABLE_MAX_VARS).then(|| EqTables::new(r)),
600 sums: BTreeMap::new(),
601 }
602 }
603
604 pub fn evaluate(&mut self, shape: &FixedShape<F>) -> Flat<F> {
607 let zero = Flat::from_raw(F::ZERO);
608
609 match shape {
610 FixedShape::Sparse(entries) => {
611 let mut acc = zero;
612 for &(row, value) in entries {
613 acc += scaled(value, self.eq(row));
614 }
615
616 acc
617 }
618 FixedShape::Dense(values) => {
619 let mut acc = zero;
620 for (row, &value) in values.iter().enumerate() {
621 acc += scaled(value, self.eq(row));
622 }
623
624 acc
625 }
626 FixedShape::Cadence {
627 stride,
628 count,
629 origin,
630 values,
631 } => self.cadence(*stride, *count, *origin, values),
632 FixedShape::Segments(segments) => {
633 let mut acc = zero;
634 for seg in segments {
635 acc += self.cadence(seg.stride, seg.count, seg.origin, &seg.values);
636 }
637
638 acc
639 }
640 FixedShape::LastRow
641 | FixedShape::FirstRow
642 | FixedShape::Custom(_)
643 | FixedShape::Periodic { .. } => shape.evaluate(self.r),
644 }
645 }
646
647 fn eq(&self, row: usize) -> Flat<F> {
650 match &self.eq {
651 Some(tables) => tables.eq(row),
652 None => eq_index(self.r, row),
653 }
654 }
655
656 fn cadence(&mut self, stride: usize, count: usize, origin: usize, values: &[F]) -> Flat<F> {
659 let zero = Flat::from_raw(F::ZERO);
660
661 if stride == 0 || values.len() != stride {
662 return zero;
663 }
664
665 let end = origin.saturating_add(stride.saturating_mul(count));
666
667 let Self { r, eq, sums } = self;
668
669 match (stride, eq) {
670 (1, Some(tables)) => scaled(values[0], tables.below(end) + tables.below(origin)),
671 _ => {
672 for limit in [end, origin] {
673 sums.entry((stride, limit))
674 .or_insert_with(|| residue_sums(r, limit, stride, None));
675 }
676
677 let upper = &sums[&(stride, end)];
678 let lower = &sums[&(stride, origin)];
679 let shift = stride - origin % stride;
680
681 let mut acc = zero;
682 for (c, (&u, &l)) in upper.iter().zip(lower).enumerate() {
683 acc += scaled(values[(c + shift) % stride], u + l);
684 }
685
686 acc
687 }
688 }
689 }
690}
691
692struct EqTables<F> {
695 low_bits: usize,
696 eq_low: Vec<Flat<F>>,
697 eq_high: Vec<Flat<F>>,
698 below_low: Vec<Flat<F>>,
699 below_high: Vec<Flat<F>>,
700}
701
702impl<F: HardwareField> EqTables<F> {
703 fn new(r: &[Flat<F>]) -> Self {
704 let low_bits = r.len() / 2;
705 let eq_low = eq_table(&r[..low_bits]);
706 let eq_high = eq_table(&r[low_bits..]);
707
708 Self {
709 low_bits,
710 below_low: running_sums(&eq_low),
711 below_high: running_sums(&eq_high),
712 eq_low,
713 eq_high,
714 }
715 }
716
717 fn eq(&self, row: usize) -> Flat<F> {
719 let low = row & (self.eq_low.len() - 1);
720 let high = (row >> self.low_bits) & (self.eq_high.len() - 1);
721
722 self.eq_low[low] * self.eq_high[high]
723 }
724
725 fn below(&self, limit: usize) -> Flat<F> {
727 let high = limit >> self.low_bits;
728
729 if high >= self.eq_high.len() {
730 return Flat::from_raw(F::ONE);
731 }
732
733 let low = limit & (self.eq_low.len() - 1);
734
735 self.below_high[high] + self.eq_high[high] * self.below_low[low]
736 }
737}
738
739pub fn fix<F>(col_idx: usize, shape: FixedShape<F>) -> FixedColumn<F> {
741 FixedColumn { col_idx, shape }
742}
743
744pub fn validate_fixed_columns<F: TowerField>(
748 fixed: &[FixedColumn<F>],
749 layout: &[ColumnType],
750 num_vars: Option<usize>,
751) -> errors::Result<()> {
752 for (i, fc) in fixed.iter().enumerate() {
753 if fc.col_idx >= layout.len() {
754 return Err(errors::Error::Protocol {
755 protocol: "fixed_column",
756 message: "col_idx out of range",
757 });
758 }
759
760 validate_shape(&fc.shape, layout[fc.col_idx], num_vars)?;
761
762 for prior in &fixed[..i] {
763 if prior.col_idx == fc.col_idx {
764 return Err(errors::Error::Protocol {
765 protocol: "fixed_column",
766 message: "duplicate pin on same column",
767 });
768 }
769 }
770 }
771
772 Ok(())
773}
774
775fn residue_sums<F: HardwareField>(
778 r: &[Flat<F>],
779 limit: usize,
780 stride: usize,
781 steps: Option<&[Flat<F>]>,
782) -> Vec<Flat<F>> {
783 let one = Flat::from_raw(F::ONE);
784 let zero = Flat::from_raw(F::ZERO);
785
786 let full = match 1usize.checked_shl(r.len() as u32) {
787 Some(cap) => limit >= cap,
788 None => false,
789 };
790
791 let mut free = vec![zero; stride];
792 let mut next = vec![zero; stride];
793
794 let mut tight = if full { zero } else { one };
795 let mut tight_residue = 0usize;
796
797 if full {
798 free[0] = one;
799 }
800
801 for t in (0..r.len()).rev() {
802 let r_t = r[t];
803 let step = steps.map(|steps| steps[t]);
804
805 next.fill(zero);
806
807 for (residue, &weight) in free.iter().enumerate() {
808 let low = 2 * residue;
809 let hi = weight * r_t;
810
811 let (at, w) = division_step(low, weight + hi, stride, step);
812 next[at] += w;
813
814 let (at, w) = division_step(low + 1, hi, stride, step);
815 next[at] += w;
816 }
817
818 let low = 2 * tight_residue;
819 let hi = tight * r_t;
820
821 (tight_residue, tight) = match (limit >> t) & 1 {
822 1 => {
823 let (at, w) = division_step(low, tight + hi, stride, step);
824 next[at] += w;
825
826 division_step(low + 1, hi, stride, step)
827 }
828 _ => division_step(low, tight + hi, stride, step),
829 };
830
831 core::mem::swap(&mut free, &mut next);
832 }
833
834 free
835}
836
837fn division_step<F: HardwareField>(
838 value: usize,
839 weight: Flat<F>,
840 stride: usize,
841 step: Option<Flat<F>>,
842) -> (usize, Flat<F>) {
843 match (value >= stride, step) {
844 (true, Some(step)) => (value - stride, weight * step),
845 (true, None) => (value - stride, weight),
846 (false, _) => (value, weight),
847 }
848}
849
850fn cadence_mle<F: HardwareField>(
851 r: &[Flat<F>],
852 stride: usize,
853 count: usize,
854 origin: usize,
855 values: &[F],
856) -> Flat<F> {
857 if stride == 0 || values.len() != stride {
858 return Flat::from_raw(F::ZERO);
859 }
860
861 let end = origin.saturating_add(stride.saturating_mul(count));
862
863 let mut class_sums = residue_sums(r, end, stride, None);
864 let start_sums = residue_sums(r, origin, stride, None);
865
866 for (c, s) in start_sums.iter().enumerate() {
867 class_sums[c] += *s;
868 }
869
870 let mut acc = Flat::from_raw(F::ZERO);
871 for (c, sum) in class_sums.iter().enumerate() {
872 let offset = (c + stride - origin % stride) % stride;
873 acc += values[offset].to_hardware() * *sum;
874 }
875
876 acc
877}
878
879fn cadence_value_at<F: HardwareField>(
880 row: usize,
881 stride: usize,
882 count: usize,
883 origin: usize,
884 values: &[F],
885) -> Flat<F> {
886 if stride == 0 || values.len() != stride {
887 return Flat::from_raw(F::ZERO);
888 }
889
890 let end = origin.saturating_add(stride.saturating_mul(count));
891
892 if row < origin || row >= end {
893 Flat::from_raw(F::ZERO)
894 } else {
895 values[(row - origin) % stride].to_hardware()
896 }
897}
898
899fn eq_index<F: HardwareField>(r: &[Flat<F>], index: usize) -> Flat<F> {
900 let one = Flat::from_raw(F::ONE);
901 let mut prod = one;
902
903 for (k, &r_k) in r.iter().enumerate() {
904 let factor = if (index >> k) & 1 == 1 {
905 r_k
906 } else {
907 one - r_k
908 };
909
910 prod *= factor;
911 }
912
913 prod
914}
915
916fn eq_table<F: HardwareField>(r: &[Flat<F>]) -> Vec<Flat<F>> {
919 let mut table = Vec::with_capacity(1 << r.len());
920 table.push(Flat::from_raw(F::ONE));
921
922 for &r_k in r {
923 for i in 0..table.len() {
924 let hi = table[i] * r_k;
925
926 table[i] += hi;
927 table.push(hi);
928 }
929 }
930
931 table
932}
933
934fn running_sums<F: HardwareField>(values: &[Flat<F>]) -> Vec<Flat<F>> {
936 let mut sums = Vec::with_capacity(values.len() + 1);
937 let mut acc = Flat::from_raw(F::ZERO);
938
939 sums.push(acc);
940
941 for &v in values {
942 acc += v;
943
944 sums.push(acc);
945 }
946
947 sums
948}
949
950fn scaled<F: HardwareField>(value: F, x: Flat<F>) -> Flat<F> {
952 if value == F::ZERO {
953 Flat::from_raw(F::ZERO)
954 } else if value == F::ONE {
955 x
956 } else {
957 value.to_hardware() * x
958 }
959}
960
961fn validate_shape<F: TowerField>(
962 shape: &FixedShape<F>,
963 col_type: ColumnType,
964 num_vars: Option<usize>,
965) -> errors::Result<()> {
966 match shape {
967 FixedShape::LastRow | FixedShape::FirstRow => Ok(()),
968 FixedShape::Custom(bits) => match num_vars {
969 Some(nv) if bits.len() != nv => Err(errors::Error::Protocol {
970 protocol: "fixed_column",
971 message: "Custom point bit width != num_vars",
972 }),
973 _ => Ok(()),
974 },
975 FixedShape::Periodic { period, values } => {
976 if !period.is_power_of_two() {
977 return Err(errors::Error::Protocol {
978 protocol: "fixed_column",
979 message: "Periodic period must be a power of two",
980 });
981 }
982
983 if values.len() != *period {
984 return Err(errors::Error::Protocol {
985 protocol: "fixed_column",
986 message: "Periodic values length != period",
987 });
988 }
989
990 if let Some(nv) = num_vars
991 && *period > (1usize << nv)
992 {
993 return Err(errors::Error::Protocol {
994 protocol: "fixed_column",
995 message: "Periodic period exceeds trace height",
996 });
997 }
998
999 check_bit_domain(values.iter().copied(), col_type)
1000 }
1001 FixedShape::Sparse(entries) => {
1002 if let Some(nv) = num_vars {
1003 let n = 1usize << nv;
1004 for &(row, _) in entries {
1005 if row >= n {
1006 return Err(errors::Error::Protocol {
1007 protocol: "fixed_column",
1008 message: "Sparse row index exceeds trace height",
1009 });
1010 }
1011 }
1012 }
1013
1014 let mut rows: Vec<usize> = entries.iter().map(|&(row, _)| row).collect();
1015 rows.sort_unstable();
1016
1017 if rows.windows(2).any(|pair| pair[0] == pair[1]) {
1018 return Err(errors::Error::Protocol {
1019 protocol: "fixed_column",
1020 message: "duplicate Sparse row",
1021 });
1022 }
1023
1024 check_bit_domain(entries.iter().map(|&(_, v)| v), col_type)
1025 }
1026 FixedShape::Dense(values) => {
1027 if let Some(nv) = num_vars
1028 && values.len() != (1usize << nv)
1029 {
1030 return Err(errors::Error::Protocol {
1031 protocol: "fixed_column",
1032 message: "Dense values length != trace height",
1033 });
1034 }
1035
1036 check_bit_domain(values.iter().copied(), col_type)
1037 }
1038 FixedShape::Cadence {
1039 stride,
1040 count,
1041 origin,
1042 values,
1043 } => {
1044 if *stride == 0 {
1045 return Err(errors::Error::Protocol {
1046 protocol: "fixed_column",
1047 message: "Cadence stride must be non-zero",
1048 });
1049 }
1050
1051 if values.len() != *stride {
1052 return Err(errors::Error::Protocol {
1053 protocol: "fixed_column",
1054 message: "Cadence values length != stride",
1055 });
1056 }
1057
1058 let end = stride
1059 .checked_mul(*count)
1060 .and_then(|span| span.checked_add(*origin))
1061 .ok_or(errors::Error::Protocol {
1062 protocol: "fixed_column",
1063 message: "Cadence active range overflows",
1064 })?;
1065
1066 if let Some(nv) = num_vars
1067 && end > (1usize << nv)
1068 {
1069 return Err(errors::Error::Protocol {
1070 protocol: "fixed_column",
1071 message: "Cadence active range exceeds trace height",
1072 });
1073 }
1074
1075 check_bit_domain(values.iter().copied(), col_type)
1076 }
1077 FixedShape::Segments(segments) => {
1078 if segments.is_empty() {
1079 return Err(errors::Error::Protocol {
1080 protocol: "fixed_column",
1081 message: "Segments list is empty",
1082 });
1083 }
1084
1085 let mut prev_end = 0usize;
1086 for seg in segments {
1087 if seg.stride == 0 {
1088 return Err(errors::Error::Protocol {
1089 protocol: "fixed_column",
1090 message: "Segments stride must be non-zero",
1091 });
1092 }
1093
1094 if seg.count == 0 {
1095 return Err(errors::Error::Protocol {
1096 protocol: "fixed_column",
1097 message: "Segments count must be non-zero",
1098 });
1099 }
1100
1101 if seg.values.len() != seg.stride {
1102 return Err(errors::Error::Protocol {
1103 protocol: "fixed_column",
1104 message: "Segments values length != stride",
1105 });
1106 }
1107
1108 let end = seg
1109 .stride
1110 .checked_mul(seg.count)
1111 .and_then(|span| span.checked_add(seg.origin))
1112 .ok_or(errors::Error::Protocol {
1113 protocol: "fixed_column",
1114 message: "Segments active range overflows",
1115 })?;
1116
1117 if seg.origin < prev_end {
1118 return Err(errors::Error::Protocol {
1119 protocol: "fixed_column",
1120 message: "Segments must be sorted by origin and disjoint",
1121 });
1122 }
1123
1124 prev_end = end;
1125
1126 check_bit_domain(seg.values.iter().copied(), col_type)?;
1127 }
1128
1129 if let Some(nv) = num_vars
1130 && prev_end > (1usize << nv)
1131 {
1132 return Err(errors::Error::Protocol {
1133 protocol: "fixed_column",
1134 message: "Segments active range exceeds trace height",
1135 });
1136 }
1137
1138 Ok(())
1139 }
1140 }
1141}
1142
1143fn check_bit_domain<F: TowerField>(
1144 values: impl Iterator<Item = F>,
1145 col_type: ColumnType,
1146) -> errors::Result<()> {
1147 if col_type != ColumnType::Bit {
1148 return Ok(());
1149 }
1150
1151 for v in values {
1152 if v != F::ZERO && v != F::ONE {
1153 return Err(errors::Error::Protocol {
1154 protocol: "fixed_column",
1155 message: "Bit fixed column value not in {0,1}",
1156 });
1157 }
1158 }
1159
1160 Ok(())
1161}