Skip to main content

hekate_program/permutation/
rank.rs

1// SPDX-FileCopyrightText: 2026 Andrei Kochergin <andrei@oumuamua.dev>
2// SPDX-FileCopyrightText: 2026 Oumuamua Labs <info@oumuamua.dev>
3// SPDX-License-Identifier: AGPL-3.0-only
4
5//! Clocks of ordered service buses: the `t`-th emit of an endpoint
6//! carries `ω^(base + t)`, `t` counted over its pinned selector's rows.
7
8use alloc::collections::btree_map::Entry;
9use alloc::collections::{BTreeMap, BTreeSet};
10use alloc::string::String;
11use alloc::vec;
12use alloc::vec::Vec;
13use hekate_core::errors;
14use hekate_core::trace::ColumnType;
15use hekate_math::{Block128, Flat, HardwareField, TowerField};
16
17use crate::permutation::{BusKind, PermutationCheckSpec, Side, Source};
18use crate::{FixedColumn, FixedShape, eq_index, residue_sums, validate_shape};
19
20/// `v^((2^128 - 1) / p)` for the tower generator
21/// `v = Block128(1 << 64)`; order exactly `p`, `p` prime.
22pub const OMEGA: Block128 = Block128(0xf90a_c8aa_f4e3_ef21_c3a6_4356_fb12_7046);
23
24/// `p`, the prime cofactor of `274177` in `2^64 + 1`;
25/// labels stay injective while `base + rank < p`.
26pub const OMEGA_ORDER: u64 = 67_280_421_310_721;
27
28const OMEGA_ORDER_BITS: usize = (u64::BITS - OMEGA_ORDER.leading_zeros()) as usize;
29
30/// One table's bus endpoints and
31/// the pins their selectors read.
32pub struct RankTable<'a, F> {
33    pub specs: &'a [(String, PermutationCheckSpec)],
34    pub fixed: &'a [FixedColumn<F>],
35    pub height: TableHeight,
36}
37
38/// Who picks a table's `num_vars`: the instance fixes
39/// the main table's, the prover picks a chiplet's.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum TableHeight {
42    Main(usize),
43    Chiplet(Option<usize>),
44}
45
46#[derive(Clone, Debug, PartialEq, Eq)]
47enum Schedule {
48    Rows(Vec<usize>),
49    Runs(Vec<Run>),
50}
51
52impl Schedule {
53    fn emits(&self) -> u64 {
54        match self {
55            Schedule::Rows(rows) => rows.len() as u64,
56            Schedule::Runs(runs) => runs.iter().map(Run::emits).sum(),
57        }
58    }
59
60    fn fits(&self, height: usize) -> bool {
61        match self {
62            Schedule::Rows(rows) => rows.last().is_none_or(|&row| row < height),
63            Schedule::Runs(runs) => runs
64                .iter()
65                .all(|run| run.origin + run.stride * run.count <= height),
66        }
67    }
68
69    fn for_each_row(&self, mut f: impl FnMut(usize)) {
70        match self {
71            Schedule::Rows(rows) => rows.iter().for_each(|&row| f(row)),
72            Schedule::Runs(runs) => {
73                for run in runs {
74                    for k in 0..run.count {
75                        let start = run.origin + k * run.stride;
76                        for &j in &run.offsets {
77                            f(start + j);
78                        }
79                    }
80                }
81            }
82        }
83    }
84}
85
86#[derive(Clone, Debug, PartialEq, Eq)]
87struct Run {
88    stride: usize,
89    count: usize,
90    origin: usize,
91    offsets: Vec<usize>,
92}
93
94impl Run {
95    fn emits(&self) -> u64 {
96        self.count as u64 * self.offsets.len() as u64
97    }
98
99    fn evaluate<F>(&self, r: &[Flat<F>], base: u64, powers: &OmegaPowers<F>) -> Flat<F>
100    where
101        F: TowerField + HardwareField,
102    {
103        let zero = Flat::from_raw(F::ZERO);
104        let calls = self.offsets.len();
105
106        if calls == 0 || self.count == 0 {
107            return zero;
108        }
109
110        let stride = self.stride;
111        let call_step = powers.pow(calls as u64);
112
113        let mut steps = Vec::with_capacity(r.len());
114        let mut step = call_step;
115
116        for _ in 0..r.len() {
117            steps.push(step);
118
119            step *= step;
120        }
121
122        let end = self.origin + stride * self.count;
123
124        let upper = residue_sums(r, end, stride, Some(steps.as_slice()));
125        let lower = residue_sums(r, self.origin, stride, Some(steps.as_slice()));
126
127        let (q_origin, r_origin) = (self.origin / stride, self.origin % stride);
128
129        let order = OMEGA_ORDER as u128;
130
131        let mut acc = zero;
132        for (i, &j) in self.offsets.iter().enumerate() {
133            let wrap = u128::from(j + r_origin >= stride);
134            let residue = (j + r_origin) % stride;
135
136            let shift = calls as u128 * (q_origin as u128 + wrap) % order;
137            let exponent = (base as u128 + i as u128 + order - shift) % order;
138
139            acc += (upper[residue] + lower[residue]) * powers.pow(exponent as u64);
140        }
141
142        acc
143    }
144}
145
146#[derive(Clone, Debug, PartialEq, Eq)]
147enum Selector {
148    Fixed(Schedule),
149    Tiled { period: usize, offsets: Vec<usize> },
150}
151
152impl Selector {
153    fn parse<F: TowerField>(shape: Option<&FixedShape<F>>) -> errors::Result<Self> {
154        if let Some(shape) = shape {
155            validate_shape(shape, ColumnType::B128, None)?;
156        }
157
158        match shape {
159            None => Ok(Selector::Tiled {
160                period: 1,
161                offsets: vec![0],
162            }),
163            Some(FixedShape::Periodic { period, values }) => Ok(Selector::Tiled {
164                period: *period,
165                offsets: indicator_offsets(values)?,
166            }),
167            Some(FixedShape::Cadence {
168                stride,
169                count,
170                origin,
171                values,
172            }) => Ok(Selector::Fixed(Schedule::Runs(vec![Run {
173                stride: *stride,
174                count: *count,
175                origin: *origin,
176                offsets: indicator_offsets(values)?,
177            }]))),
178            Some(FixedShape::Segments(segments)) => {
179                let mut runs = Vec::with_capacity(segments.len());
180                for seg in segments {
181                    runs.push(Run {
182                        stride: seg.stride,
183                        count: seg.count,
184                        origin: seg.origin,
185                        offsets: indicator_offsets(&seg.values)?,
186                    });
187                }
188
189                Ok(Selector::Fixed(Schedule::Runs(runs)))
190            }
191            Some(FixedShape::Sparse(entries)) => {
192                let mut rows = Vec::with_capacity(entries.len());
193                for &(row, value) in entries {
194                    if indicator(value)? {
195                        rows.push(row);
196                    }
197                }
198
199                rows.sort_unstable();
200
201                Ok(Selector::Fixed(Schedule::Rows(rows)))
202            }
203            Some(
204                FixedShape::Dense(_)
205                | FixedShape::LastRow
206                | FixedShape::FirstRow
207                | FixedShape::Custom(_),
208            ) => Err(errors::Error::Protocol {
209                protocol: "logup_bus",
210                message: "ordered bus selector must be None or pinned to Cadence, \
211                          Segments, Periodic or Sparse",
212            }),
213        }
214    }
215
216    fn emits(&self, num_vars: Option<usize>) -> errors::Result<Option<u64>> {
217        match (self, num_vars) {
218            (_, Some(num_vars)) => self.resolve(num_vars).map(|s| Some(s.emits())),
219            (Selector::Fixed(schedule), None) => Ok(Some(schedule.emits())),
220            (Selector::Tiled { .. }, None) => Ok(None),
221        }
222    }
223
224    fn resolve(&self, num_vars: usize) -> errors::Result<Schedule> {
225        let height = table_height(num_vars)?;
226
227        match self {
228            Selector::Fixed(schedule) if schedule.fits(height) => Ok(schedule.clone()),
229            Selector::Tiled { period, offsets } if *period <= height => {
230                Ok(Schedule::Runs(vec![Run {
231                    stride: *period,
232                    count: height / *period,
233                    origin: 0,
234                    offsets: offsets.clone(),
235                }]))
236            }
237            Selector::Fixed(_) | Selector::Tiled { .. } => Err(errors::Error::Protocol {
238                protocol: "logup_bus",
239                message: "ordered bus selector reaches past the table height",
240            }),
241        }
242    }
243}
244
245struct Ordered<'a> {
246    table: usize,
247    spec: usize,
248    bus_id: &'a str,
249    side: Side,
250    selector: Selector,
251    num_vars: Option<usize>,
252}
253
254struct OmegaPowers<F: HardwareField> {
255    squares: [Flat<F>; OMEGA_ORDER_BITS],
256}
257
258impl<F: TowerField + HardwareField> OmegaPowers<F> {
259    fn new() -> errors::Result<Self> {
260        if F::BITS != 128 {
261            return Err(errors::Error::Protocol {
262                protocol: "emit_rank",
263                message: "ordered buses need GF(2^128); omega has order p nowhere below",
264            });
265        }
266
267        let mut squares = [Flat::from_raw(F::ZERO); OMEGA_ORDER_BITS];
268        let mut power = F::from(OMEGA.0).to_hardware();
269
270        for slot in squares.iter_mut() {
271            *slot = power;
272            power *= power;
273        }
274
275        Ok(Self { squares })
276    }
277
278    fn omega(&self) -> Flat<F> {
279        self.squares[0]
280    }
281
282    fn pow(&self, exponent: u64) -> Flat<F> {
283        let exponent = exponent % OMEGA_ORDER;
284
285        let mut acc = Flat::from_raw(F::ONE);
286        for (bit, &square) in self.squares.iter().enumerate() {
287            if (exponent >> bit) & 1 == 1 {
288                acc *= square;
289            }
290        }
291
292        acc
293    }
294}
295
296/// Public clock column of one ordered endpoint:
297/// `sel(x) · ω^(base + rank(x))`, zero off the selector.
298#[derive(Clone, Debug, PartialEq, Eq)]
299pub struct RankClock {
300    base: u64,
301    num_vars: usize,
302    schedule: Schedule,
303}
304
305impl RankClock {
306    /// The clock of spec `spec_idx` among one table's
307    /// `rank_clocks` entries; errors when it has none.
308    pub fn for_spec(clocks: &[Option<Self>], spec_idx: usize) -> errors::Result<&Self> {
309        clocks
310            .get(spec_idx)
311            .and_then(Option::as_ref)
312            .ok_or(errors::Error::Protocol {
313                protocol: "emit_rank",
314                message: "ordered bus spec has no rank clock",
315            })
316    }
317
318    /// MLE of the clock column at `r`.
319    /// Errors unless `r.len() == num_vars`.
320    pub fn evaluate<F>(&self, r: &[Flat<F>]) -> errors::Result<Flat<F>>
321    where
322        F: TowerField + HardwareField,
323    {
324        if r.len() != self.num_vars {
325            return Err(errors::Error::Protocol {
326                protocol: "emit_rank",
327                message: "evaluation point length differs from the table's num_vars",
328            });
329        }
330
331        let powers = OmegaPowers::<F>::new()?;
332
333        let mut acc = Flat::from_raw(F::ZERO);
334
335        match &self.schedule {
336            Schedule::Rows(rows) => {
337                let omega = powers.omega();
338
339                let mut label = powers.pow(self.base);
340                for &row in rows {
341                    acc += eq_index(r, row) * label;
342                    label *= omega;
343                }
344            }
345            Schedule::Runs(runs) => {
346                let mut base = self.base;
347                for run in runs {
348                    acc += run.evaluate(r, base, &powers);
349                    base += run.emits();
350                }
351            }
352        }
353
354        Ok(acc)
355    }
356
357    /// Adds `coeff · ω^(base + t)` to the row of the
358    /// `t`-th emit. Errors unless `out.len() == 2^num_vars`.
359    pub fn accumulate<F>(&self, out: &mut [Flat<F>], coeff: Flat<F>) -> errors::Result<()>
360    where
361        F: TowerField + HardwareField,
362    {
363        if out.len() != 1usize << self.num_vars {
364            return Err(errors::Error::Protocol {
365                protocol: "emit_rank",
366                message: "clock buffer length differs from the table height",
367            });
368        }
369
370        let powers = OmegaPowers::<F>::new()?;
371        let omega = powers.omega();
372
373        let mut term = coeff * powers.pow(self.base);
374
375        self.schedule.for_each_row(|row| {
376            out[row] += term;
377            term *= omega;
378        });
379
380        Ok(())
381    }
382}
383
384/// The verify-entry predicate over a whole program, main table
385/// first, then chiplets in `chiplet_defs` order; the clock of
386/// every ordered endpoint, indexed by table and spec.
387pub fn rank_clocks<F: TowerField>(
388    tables: &[RankTable<'_, F>],
389) -> errors::Result<Vec<Vec<Option<RankClock>>>> {
390    let mut totals: BTreeMap<(&str, Side), u64> = BTreeMap::new();
391    let mut clocks: Vec<Vec<Option<RankClock>>> =
392        tables.iter().map(|t| vec![None; t.specs.len()]).collect();
393
394    for ep in ordered_endpoints(tables)? {
395        let num_vars = ep.num_vars.ok_or(errors::Error::Protocol {
396            protocol: "emit_rank",
397            message: "rank clocks need the height of every table",
398        })?;
399
400        let schedule = ep.selector.resolve(num_vars)?;
401
402        let total = totals.entry((ep.bus_id, ep.side)).or_insert(0);
403        let base = *total;
404
405        *total = side_total(base, schedule.emits())?;
406
407        clocks[ep.table][ep.spec] = Some(RankClock {
408            base,
409            num_vars,
410            schedule,
411        });
412    }
413
414    Ok(clocks)
415}
416
417/// The height-free half of the predicate;
418/// sound on any subset of a program's tables.
419pub fn validate_ordered_buses<F: TowerField>(tables: &[RankTable<'_, F>]) -> errors::Result<()> {
420    emit_totals(tables).map(|_| ())
421}
422
423/// `validate_ordered_buses`, plus equal request and response
424/// totals on every bus that shows both sides with known counts.
425pub fn validate_ordered_emits<F: TowerField>(tables: &[RankTable<'_, F>]) -> errors::Result<()> {
426    let totals = emit_totals(tables)?;
427
428    for (&(bus_id, side), &total) in &totals {
429        let responses = totals.get(&(bus_id, Side::Response));
430
431        if let (Side::Request, Some(requests), Some(&Some(responses))) = (side, total, responses)
432            && requests != responses
433        {
434            return Err(errors::Error::Protocol {
435                protocol: "logup_bus",
436                message: "ordered bus sides emit different counts; the bus cannot balance",
437            });
438        }
439    }
440
441    Ok(())
442}
443
444fn ordered_endpoints<'a, F: TowerField>(
445    tables: &[RankTable<'a, F>],
446) -> errors::Result<Vec<Ordered<'a>>> {
447    let ordered = ordered_bus_ids(tables);
448
449    if !ordered.is_empty() && F::BITS != 128 {
450        return Err(errors::Error::Protocol {
451            protocol: "logup_bus",
452            message: "ordered buses need GF(2^128); omega has order p nowhere below",
453        });
454    }
455
456    let mut layouts: BTreeMap<&str, (usize, usize)> = BTreeMap::new();
457    let mut tiled_chiplet_sides: BTreeSet<(&str, Side)> = BTreeSet::new();
458
459    let mut out = Vec::new();
460
461    for (table, t) in tables.iter().enumerate() {
462        let num_vars = match t.height {
463            TableHeight::Main(num_vars) => Some(num_vars),
464            TableHeight::Chiplet(num_vars) => num_vars,
465        };
466
467        for (spec_idx, (bus_id, spec)) in t.specs.iter().enumerate() {
468            let bus_id = bus_id.as_str();
469
470            if !ordered.contains(bus_id) {
471                continue;
472            }
473
474            let (side, layout) = rank_slot(spec)?;
475
476            match layouts.entry(bus_id) {
477                Entry::Vacant(slot) => {
478                    slot.insert(layout);
479                }
480                Entry::Occupied(slot) if *slot.get() != layout => {
481                    return Err(errors::Error::Protocol {
482                        protocol: "logup_bus",
483                        message: "ordered bus endpoints disagree on the EmitRank slot \
484                                  position or on the key width",
485                    });
486                }
487                Entry::Occupied(_) => {}
488            }
489
490            let selector = endpoint_selector(spec, t.fixed)?;
491
492            if tiled_chiplet_sides.contains(&(bus_id, side)) {
493                return Err(errors::Error::Protocol {
494                    protocol: "logup_bus",
495                    message: "ordered bus chiplet endpoint with a None or Periodic selector \
496                              must be last on its side; the prover picks the chiplet's height",
497                });
498            }
499
500            if matches!(selector, Selector::Tiled { .. })
501                && matches!(t.height, TableHeight::Chiplet(_))
502            {
503                tiled_chiplet_sides.insert((bus_id, side));
504            }
505
506            out.push(Ordered {
507                table,
508                spec: spec_idx,
509                bus_id,
510                side,
511                selector,
512                num_vars,
513            });
514        }
515    }
516
517    Ok(out)
518}
519
520fn ordered_bus_ids<'a, F>(tables: &[RankTable<'a, F>]) -> BTreeSet<&'a str> {
521    tables
522        .iter()
523        .flat_map(|t| t.specs.iter())
524        .filter(|(_, spec)| {
525            spec.sources
526                .iter()
527                .any(|(source, _)| matches!(source, Source::EmitRank(_)))
528        })
529        .map(|(bus_id, _)| bus_id.as_str())
530        .collect()
531}
532
533fn rank_slot(spec: &PermutationCheckSpec) -> errors::Result<(Side, (usize, usize))> {
534    let mut rank = None;
535    let mut width = 0usize;
536
537    for (source, _) in &spec.sources {
538        if let Source::EmitRank(side) = source
539            && rank.replace((*side, width)).is_some()
540        {
541            return Err(errors::Error::Protocol {
542                protocol: "logup_bus",
543                message: "ordered bus endpoint carries more than one EmitRank source",
544            });
545        }
546
547        width += slot_width(source);
548    }
549
550    match rank {
551        Some((side, position)) => Ok((side, (position, width))),
552        None => Err(errors::Error::Protocol {
553            protocol: "logup_bus",
554            message: "ordered bus endpoint has no EmitRank source; a witness column \
555                      in the clock slot mimics any rank",
556        }),
557    }
558}
559
560fn slot_width(source: &Source) -> usize {
561    match source {
562        Source::Columns(cols) => cols.len(),
563        Source::Column(_)
564        | Source::RowIndexLeBytes(_)
565        | Source::Const(_)
566        | Source::RowIndexByte(_)
567        | Source::PhaseColumn(_)
568        | Source::EmitRank(_) => 1,
569    }
570}
571
572fn endpoint_selector<F: TowerField>(
573    spec: &PermutationCheckSpec,
574    fixed: &[FixedColumn<F>],
575) -> errors::Result<Selector> {
576    if spec.kind != BusKind::Permutation {
577        return Err(errors::Error::Protocol {
578            protocol: "logup_bus",
579            message: "ordered bus endpoint is Lookup kind; \
580                      ranks order Permutation buses only",
581        });
582    }
583
584    if spec.recv_selector.is_some() {
585        return Err(errors::Error::Protocol {
586            protocol: "logup_bus",
587            message: "ordered bus endpoint has a recv_selector; \
588                      a paired row has no single rank",
589        });
590    }
591
592    let shape = match spec.selector {
593        None => None,
594        Some(col) => {
595            let pin = fixed
596                .iter()
597                .find(|fc| fc.col_idx == col)
598                .ok_or(errors::Error::Protocol {
599                    protocol: "logup_bus",
600                    message: "ordered bus selector is a witness column; \
601                              the verifier counts ranks over the pinned shape",
602                })?;
603
604            Some(&pin.shape)
605        }
606    };
607
608    Selector::parse(shape)
609}
610
611fn emit_totals<'a, F: TowerField>(
612    tables: &[RankTable<'a, F>],
613) -> errors::Result<BTreeMap<(&'a str, Side), Option<u64>>> {
614    let mut totals: BTreeMap<(&str, Side), Option<u64>> = BTreeMap::new();
615    for ep in ordered_endpoints(tables)? {
616        let emits = ep.selector.emits(ep.num_vars)?;
617        let total = totals.entry((ep.bus_id, ep.side)).or_insert(Some(0));
618
619        *total = match (*total, emits) {
620            (Some(sum), Some(emits)) => Some(side_total(sum, emits)?),
621            _ => None,
622        };
623    }
624
625    Ok(totals)
626}
627
628fn side_total(sum: u64, emits: u64) -> errors::Result<u64> {
629    sum.checked_add(emits)
630        .filter(|&total| total < OMEGA_ORDER)
631        .ok_or(errors::Error::Protocol {
632            protocol: "logup_bus",
633            message: "an ordered bus side emits p = 67280421310721 times or more; \
634                      rank labels would repeat",
635        })
636}
637
638fn indicator_offsets<F: TowerField>(values: &[F]) -> errors::Result<Vec<usize>> {
639    let mut offsets = Vec::new();
640    for (offset, &value) in values.iter().enumerate() {
641        if indicator(value)? {
642            offsets.push(offset);
643        }
644    }
645
646    Ok(offsets)
647}
648
649fn indicator<F: TowerField>(value: F) -> errors::Result<bool> {
650    match value {
651        v if v == F::ONE => Ok(true),
652        v if v == F::ZERO => Ok(false),
653        _ => Err(errors::Error::Protocol {
654            protocol: "logup_bus",
655            message: "ordered bus selector takes a value outside {0, 1}; \
656                      a rank counts rows where it is one",
657        }),
658    }
659}
660
661fn table_height(num_vars: usize) -> errors::Result<usize> {
662    u32::try_from(num_vars)
663        .ok()
664        .and_then(|n| 1usize.checked_shl(n))
665        .ok_or(errors::Error::Protocol {
666            protocol: "emit_rank",
667            message: "table num_vars exceeds the address width",
668        })
669}
670
671#[cfg(test)]
672mod tests {
673    use super::*;
674    use crate::CadenceSegment;
675    use crate::permutation::{ChallengeLabel, EMIT_RANK_LABEL};
676
677    use alloc::string::ToString;
678    use hekate_math::Block64;
679
680    type F = Block128;
681
682    const BUS: &str = "svc";
683
684    struct Rng(u64);
685
686    impl Rng {
687        fn next(&mut self) -> u64 {
688            self.0 ^= self.0 << 13;
689            self.0 ^= self.0 >> 7;
690            self.0 ^= self.0 << 17;
691
692            self.0
693        }
694
695        fn below(&mut self, bound: usize) -> usize {
696            (self.next() % bound as u64) as usize
697        }
698
699        fn bits(&mut self, len: usize) -> Vec<F> {
700            (0..len)
701                .map(|_| match self.next() & 1 {
702                    1 => F::ONE,
703                    _ => F::ZERO,
704                })
705                .collect()
706        }
707
708        fn point(&mut self, len: usize) -> Vec<Flat<F>> {
709            (0..len)
710                .map(|_| F::from(((self.next() as u128) << 64) | self.next() as u128).to_hardware())
711                .collect()
712        }
713    }
714
715    fn assert_matches_brute_force(shape: Option<&FixedShape<F>>, num_vars: usize, rng: &mut Rng) {
716        let base = rng.next() % (1 << 40);
717
718        let clock = RankClock {
719            base,
720            num_vars,
721            schedule: Selector::parse(shape).unwrap().resolve(num_vars).unwrap(),
722        };
723
724        let expected = brute_column(shape, num_vars, base);
725
726        let coeff = rng.point(1)[0];
727        let mut column = rng.point(1 << num_vars);
728
729        let accumulated: Vec<Flat<F>> = column
730            .iter()
731            .zip(&expected)
732            .map(|(&prior, &label)| prior + coeff * label)
733            .collect();
734
735        clock.accumulate(&mut column, coeff).unwrap();
736
737        assert_eq!(column, accumulated);
738
739        for _ in 0..3 {
740            let r = rng.point(num_vars);
741
742            let mut mle = Flat::from_raw(F::ZERO);
743            for (row, &value) in expected.iter().enumerate() {
744                mle += eq_index(&r, row) * value;
745            }
746
747            assert_eq!(clock.evaluate(&r).unwrap(), mle);
748        }
749    }
750
751    fn brute_column(shape: Option<&FixedShape<F>>, num_vars: usize, base: u64) -> Vec<Flat<F>> {
752        let one = Flat::from_raw(F::ONE);
753        let omega = OMEGA.to_hardware();
754
755        let mut label = pow(omega, base as u128);
756
757        (0..1usize << num_vars)
758            .map(|row| {
759                let live = match shape {
760                    None => true,
761                    Some(shape) => shape.value_at_row(row, num_vars) == one,
762                };
763
764                match live {
765                    true => {
766                        let value = label;
767                        label *= omega;
768
769                        value
770                    }
771                    false => Flat::from_raw(F::ZERO),
772                }
773            })
774            .collect()
775    }
776
777    fn pow(base: Flat<F>, mut exponent: u128) -> Flat<F> {
778        let mut square = base;
779        let mut acc = Flat::from_raw(F::ONE);
780
781        while exponent > 0 {
782            if exponent & 1 == 1 {
783                acc *= square;
784            }
785
786            square *= square;
787            exponent >>= 1;
788        }
789
790        acc
791    }
792
793    fn assert_rejected_against_host(
794        server: &[(String, PermutationCheckSpec)],
795        pins: &[FixedColumn<F>],
796    ) {
797        let host = vec![endpoint(ordered(Side::Request), Some(1))];
798        let host_pins = calls(2);
799
800        assert_rejected(&[table(&host, &host_pins), table(server, pins)]);
801    }
802
803    fn assert_rejected(tables: &[RankTable<'_, F>]) {
804        let unsized_: Vec<RankTable<'_, F>> = tables
805            .iter()
806            .map(|t| unsized_table(t.specs, t.fixed))
807            .collect();
808
809        assert!(rank_clocks(tables).is_err());
810        assert!(validate_ordered_buses(&unsized_).is_err());
811    }
812
813    fn main_table<'a>(
814        specs: &'a [(String, PermutationCheckSpec)],
815        fixed: &'a [FixedColumn<F>],
816    ) -> RankTable<'a, F> {
817        RankTable {
818            specs,
819            fixed,
820            height: TableHeight::Main(4),
821        }
822    }
823
824    fn table<'a>(
825        specs: &'a [(String, PermutationCheckSpec)],
826        fixed: &'a [FixedColumn<F>],
827    ) -> RankTable<'a, F> {
828        RankTable {
829            specs,
830            fixed,
831            height: TableHeight::Chiplet(Some(4)),
832        }
833    }
834
835    fn unsized_table<'a>(
836        specs: &'a [(String, PermutationCheckSpec)],
837        fixed: &'a [FixedColumn<F>],
838    ) -> RankTable<'a, F> {
839        RankTable {
840            specs,
841            fixed,
842            height: TableHeight::Chiplet(None),
843        }
844    }
845
846    fn endpoint(
847        sources: Vec<(Source, ChallengeLabel)>,
848        selector: Option<usize>,
849    ) -> (String, PermutationCheckSpec) {
850        (
851            BUS.to_string(),
852            PermutationCheckSpec::new(sources, selector),
853        )
854    }
855
856    fn ordered(side: Side) -> Vec<(Source, ChallengeLabel)> {
857        vec![
858            (Source::Column(0), b"k_v"),
859            (Source::EmitRank(side), EMIT_RANK_LABEL),
860        ]
861    }
862
863    fn calls(count: usize) -> Vec<FixedColumn<F>> {
864        vec![FixedColumn {
865            col_idx: 1,
866            shape: FixedShape::Cadence {
867                stride: 4,
868                count,
869                origin: 0,
870                values: vec![F::ONE, F::ZERO, F::ZERO, F::ONE],
871            },
872        }]
873    }
874
875    #[test]
876    fn omega_has_prime_order() {
877        let p = OMEGA_ORDER;
878
879        assert_eq!(((1u128 << 64) | 1) % p as u128, 0);
880        assert!(
881            (3u64..)
882                .step_by(2)
883                .take_while(|d| d * d <= p)
884                .all(|d| !p.is_multiple_of(d))
885        );
886
887        let omega = OMEGA.to_hardware();
888        let one = Flat::from_raw(F::ONE);
889        let generator = Block128(1 << 64).to_hardware();
890
891        assert_ne!(omega, one);
892        assert_eq!(pow(omega, p as u128), one);
893        assert_eq!(pow(generator, u128::MAX / p as u128), omega);
894    }
895
896    #[test]
897    fn every_row_clock_matches_brute_force() {
898        let mut rng = Rng(0x9E37_79B9_7F4A_7C15);
899        for num_vars in 0..=10 {
900            assert_matches_brute_force(None, num_vars, &mut rng);
901        }
902    }
903
904    #[test]
905    fn periodic_clock_matches_brute_force() {
906        let mut rng = Rng(0xD1B5_4A32_D192_ED03);
907        for num_vars in 0..=10 {
908            for log_period in 0..=num_vars {
909                let shape = FixedShape::Periodic {
910                    period: 1 << log_period,
911                    values: rng.bits(1 << log_period),
912                };
913
914                assert_matches_brute_force(Some(&shape), num_vars, &mut rng);
915            }
916        }
917    }
918
919    #[test]
920    fn cadence_clock_matches_brute_force() {
921        let mut rng = Rng(0x2545_F491_4F6C_DD1D);
922
923        for num_vars in [6, 8, 10] {
924            let height = 1usize << num_vars;
925
926            for stride in 1..=64 {
927                let count = rng.below(height / stride + 1);
928                let origin = rng.below(height - stride * count + 1);
929
930                let shape = FixedShape::Cadence {
931                    stride,
932                    count,
933                    origin,
934                    values: rng.bits(stride),
935                };
936
937                assert_matches_brute_force(Some(&shape), num_vars, &mut rng);
938            }
939        }
940    }
941
942    #[test]
943    fn segments_clock_matches_brute_force() {
944        let mut rng = Rng(0x6A09_E667_F3BC_C909);
945        let num_vars = 10;
946        let height = 1usize << num_vars;
947
948        for _ in 0..64 {
949            let mut segments = Vec::new();
950            let mut cursor = rng.below(64);
951
952            for _ in 0..1 + rng.below(4) {
953                let stride = 1 + rng.below(16);
954                let count = 1 + rng.below(8);
955
956                if cursor + stride * count > height {
957                    break;
958                }
959
960                segments.push(CadenceSegment {
961                    stride,
962                    count,
963                    origin: cursor,
964                    values: rng.bits(stride),
965                });
966
967                cursor += stride * count + rng.below(32);
968            }
969
970            if segments.is_empty() {
971                continue;
972            }
973
974            let shape = FixedShape::Segments(segments);
975
976            assert_matches_brute_force(Some(&shape), num_vars, &mut rng);
977        }
978    }
979
980    #[test]
981    fn sparse_clock_matches_brute_force() {
982        let mut rng = Rng(0xBB67_AE85_84CA_A73B);
983        let num_vars = 10;
984
985        for _ in 0..32 {
986            let mut entries = Vec::new();
987            for row in 0..1usize << num_vars {
988                if rng.next().is_multiple_of(8) {
989                    entries.push((row, rng.bits(1)[0]));
990                }
991            }
992
993            entries.reverse();
994
995            let shape = FixedShape::Sparse(entries);
996
997            assert_matches_brute_force(Some(&shape), num_vars, &mut rng);
998        }
999    }
1000
1001    #[test]
1002    fn requester_tables_tile_one_responder() {
1003        let host = vec![endpoint(ordered(Side::Request), Some(1))];
1004        let server = vec![endpoint(ordered(Side::Response), Some(1))];
1005
1006        let (pins_a, pins_b, pins_server) = (calls(2), calls(1), calls(3));
1007
1008        let tables = [
1009            table(&host, &pins_a),
1010            table(&host, &pins_b),
1011            table(&server, &pins_server),
1012        ];
1013
1014        let clocks = rank_clocks(&tables).unwrap();
1015        let base = |t: usize| clocks[t][0].as_ref().unwrap().base;
1016
1017        assert_eq!((base(0), base(1), base(2)), (0, 4, 0));
1018
1019        validate_ordered_emits(&tables).unwrap();
1020
1021        let short = calls(2);
1022        let unbalanced = [
1023            table(&host, &pins_a),
1024            table(&host, &pins_b),
1025            table(&server, &short),
1026        ];
1027
1028        rank_clocks(&unbalanced).unwrap();
1029
1030        assert!(validate_ordered_emits(&unbalanced).is_err());
1031    }
1032
1033    #[test]
1034    fn tiled_chiplet_endpoint_must_be_last_on_its_side() {
1035        let every_row = vec![endpoint(ordered(Side::Request), None)];
1036        let host = vec![endpoint(ordered(Side::Request), Some(1))];
1037        let server = vec![endpoint(ordered(Side::Response), Some(1))];
1038
1039        let periodic = vec![FixedColumn {
1040            col_idx: 1,
1041            shape: FixedShape::Periodic {
1042                period: 2,
1043                values: vec![F::ONE, F::ZERO],
1044            },
1045        }];
1046
1047        let (pins, server_pins) = (calls(1), calls(3));
1048
1049        for tiled in [table(&every_row, &[]), table(&host, &periodic)] {
1050            assert_rejected(&[tiled, table(&host, &pins), table(&server, &server_pins)]);
1051        }
1052
1053        let last = [
1054            table(&host, &pins),
1055            table(&host, &periodic),
1056            table(&server, &server_pins),
1057        ];
1058
1059        rank_clocks(&last).unwrap();
1060        validate_ordered_buses(&last).unwrap();
1061
1062        let on_main = [
1063            main_table(&host, &periodic),
1064            table(&host, &pins),
1065            table(&server, &server_pins),
1066        ];
1067
1068        rank_clocks(&on_main).unwrap();
1069    }
1070
1071    #[test]
1072    fn lone_side_leaves_balance_to_its_partner() {
1073        let server = vec![endpoint(ordered(Side::Response), Some(1))];
1074        let pins = calls(3);
1075
1076        validate_ordered_emits(&[table(&server, &pins)]).unwrap();
1077    }
1078
1079    #[test]
1080    fn honest_pair_is_accepted_both_ways() {
1081        let host = vec![endpoint(ordered(Side::Request), Some(1))];
1082        let server = vec![endpoint(ordered(Side::Response), Some(1))];
1083        let pins = calls(2);
1084
1085        rank_clocks(&[table(&host, &pins), table(&server, &pins)]).unwrap();
1086        validate_ordered_buses(&[unsized_table(&host, &pins), unsized_table(&server, &pins)])
1087            .unwrap();
1088    }
1089
1090    #[test]
1091    fn witness_column_in_the_clock_slot_is_rejected() {
1092        let server = vec![endpoint(
1093            vec![
1094                (Source::Column(0), b"k_v"),
1095                (Source::Column(2), EMIT_RANK_LABEL),
1096            ],
1097            Some(1),
1098        )];
1099
1100        assert_rejected_against_host(&server, &calls(2));
1101    }
1102
1103    #[test]
1104    fn misaligned_rank_slot_is_rejected() {
1105        let server = vec![endpoint(
1106            vec![
1107                (Source::EmitRank(Side::Response), EMIT_RANK_LABEL),
1108                (Source::Column(0), b"k_v"),
1109            ],
1110            Some(1),
1111        )];
1112
1113        assert_rejected_against_host(&server, &calls(2));
1114    }
1115
1116    #[test]
1117    fn differing_key_width_is_rejected() {
1118        let mut sources = ordered(Side::Response);
1119        sources.push((Source::Const(7), b"k_dom"));
1120
1121        assert_rejected_against_host(&[endpoint(sources, Some(1))], &calls(2));
1122    }
1123
1124    #[test]
1125    fn rank_slot_is_measured_in_beta_positions() {
1126        let server = vec![endpoint(
1127            vec![
1128                (Source::Columns(vec![0, 2]), b"k_v"),
1129                (Source::EmitRank(Side::Response), EMIT_RANK_LABEL),
1130            ],
1131            Some(1),
1132        )];
1133
1134        assert_rejected_against_host(&server, &calls(2));
1135    }
1136
1137    #[test]
1138    fn second_rank_source_is_rejected() {
1139        let mut sources = ordered(Side::Response);
1140        sources.push((Source::EmitRank(Side::Response), EMIT_RANK_LABEL));
1141
1142        assert_rejected(&[table(&[endpoint(sources, Some(1))], &calls(2))]);
1143    }
1144
1145    #[test]
1146    fn dense_selector_is_rejected() {
1147        let pins = vec![FixedColumn {
1148            col_idx: 1,
1149            shape: FixedShape::Dense(vec![F::ONE; 16]),
1150        }];
1151
1152        assert_rejected_against_host(&[endpoint(ordered(Side::Response), Some(1))], &pins);
1153    }
1154
1155    #[test]
1156    fn witness_selector_is_rejected() {
1157        assert_rejected_against_host(&[endpoint(ordered(Side::Response), Some(1))], &[]);
1158    }
1159
1160    #[test]
1161    fn recv_selector_is_rejected() {
1162        let paired =
1163            PermutationCheckSpec::new_paired(ordered(Side::Response), 1, 2, BusKind::Permutation);
1164
1165        let mut pins = calls(1);
1166        pins.push(FixedColumn {
1167            col_idx: 2,
1168            shape: FixedShape::Sparse(vec![(9, F::ONE)]),
1169        });
1170
1171        assert_rejected_against_host(&[(BUS.to_string(), paired)], &pins);
1172    }
1173
1174    #[test]
1175    fn lookup_kind_is_rejected() {
1176        let lookup = PermutationCheckSpec::new_lookup(ordered(Side::Response), Some(1));
1177
1178        assert_rejected_against_host(&[(BUS.to_string(), lookup)], &calls(2));
1179    }
1180
1181    #[test]
1182    fn non_indicator_selector_is_rejected() {
1183        let pins = vec![FixedColumn {
1184            col_idx: 1,
1185            shape: FixedShape::Cadence {
1186                stride: 4,
1187                count: 2,
1188                origin: 0,
1189                values: vec![F::from(2u128), F::ZERO, F::ZERO, F::ONE],
1190            },
1191        }];
1192
1193        assert_rejected_against_host(&[endpoint(ordered(Side::Response), Some(1))], &pins);
1194    }
1195
1196    #[test]
1197    fn side_total_stays_below_p() {
1198        let host = vec![endpoint(ordered(Side::Request), None)];
1199
1200        let tall = RankTable::<F> {
1201            specs: &host,
1202            fixed: &[],
1203            height: TableHeight::Main(46),
1204        };
1205
1206        assert!(rank_clocks(&[tall]).is_err());
1207
1208        let host = vec![endpoint(ordered(Side::Request), Some(1))];
1209        let pins = vec![FixedColumn {
1210            col_idx: 1,
1211            shape: FixedShape::Cadence {
1212                stride: 1,
1213                count: OMEGA_ORDER as usize,
1214                origin: 0,
1215                values: vec![F::ONE],
1216            },
1217        }];
1218
1219        assert!(validate_ordered_buses(&[unsized_table(&host, &pins)]).is_err());
1220    }
1221
1222    #[test]
1223    fn subfields_carry_no_rank_clock() {
1224        let host = vec![endpoint(ordered(Side::Request), None)];
1225
1226        let narrow = RankTable::<Block64> {
1227            specs: &host,
1228            fixed: &[],
1229            height: TableHeight::Main(4),
1230        };
1231
1232        assert!(rank_clocks(&[narrow]).is_err());
1233    }
1234}