Skip to main content

miden_air/constraints/lookup/
messages.rs

1//! Message structs for LogUp bus interactions.
2//!
3//! Each struct represents a reduced denominator encoding: `α + Σ βⁱ · field_i`.
4//! Fields are named for readability; the [`super::lookup::LookupMessage`] trait
5//! (implemented further down in this file) provides the `encode` method that
6//! produces the extension-field value.
7//!
8//! Chiplet messages are addressed by interaction-specific bus domains (one [`BusId`]
9//! variant per semantic message kind). Constructors pick the interaction domain; payloads
10//! start directly with the semantic fields (addr, ctx, etc.).
11//!
12//! All structs are generic over `E` (base-field expression type, typically `AB::Expr`).
13
14use miden_core::field::{Algebra, PrimeCharacteristicRing};
15
16use crate::lookup::Challenges;
17
18// BUS IDENTIFIERS
19// ================================================================================================
20
21/// Width of the `beta_powers` table `Challenges` precomputes for Miden's bus
22/// messages, i.e. the exponent of `gamma = beta^MIDEN_MAX_MESSAGE_WIDTH` used in
23/// `bus_prefix[i] = alpha + (i + 1) * gamma`.
24///
25/// Must match the Poseidon2 absorption loop in `crates/lib/core/asm/stark/` which
26/// reads the same β-power table during recursive verification.
27pub const MIDEN_MAX_MESSAGE_WIDTH: usize = 16;
28
29/// Domain-separated bus interaction identifier.
30///
31/// Each variant identifies a distinct bus interaction type. When encoding a message,
32/// the bus is cast to `usize` and indexes into
33/// [`Challenges::bus_prefix`](crate::lookup::Challenges) to obtain the additive base
34/// `bus_prefix[bus] = alpha + (bus + 1) * gamma`.
35#[repr(usize)]
36#[derive(Copy, Clone, Debug, PartialEq, Eq)]
37pub enum BusId {
38    // --- Out-of-circuit (boundary correction / reduced_aux_values) ---
39    /// Kernel ROM init: kernel procedure digests from variable-length public inputs.
40    KernelRomInit = 0,
41    /// Block hash table (decoder p2): root program hash boundary correction.
42    BlockHashTable = 1,
43    /// Log-precompile transcript: initial/final capacity state boundary correction.
44    LogPrecompileTranscript = 2,
45
46    // --- In-circuit buses ---
47    KernelRomCall = 3,
48    HasherLinearHashInit = 4,
49    HasherReturnState = 5,
50    HasherAbsorption = 6,
51    HasherReturnHash = 7,
52    HasherMerkleVerifyInit = 8,
53    HasherMerkleOldInit = 9,
54    HasherMerkleNewInit = 10,
55    MemoryReadElement = 11,
56    MemoryWriteElement = 12,
57    MemoryReadWord = 13,
58    MemoryWriteWord = 14,
59    Bitwise = 15,
60    AceInit = 16,
61    /// Block stack table (decoder p1): tracks control flow block nesting.
62    BlockStackTable = 17,
63    /// Op group table (decoder p3): tracks operation batch consumption.
64    OpGroupTable = 18,
65    /// Stack overflow table.
66    StackOverflowTable = 19,
67    /// Sibling table: shares Merkle tree sibling nodes between old/new root computations.
68    SiblingTable = 20,
69    /// Range checker bus (LogUp).
70    RangeCheck = 21,
71    /// ACE wiring bus (LogUp).
72    AceWiring = 22,
73    /// Hasher perm-link input bus: pairs controller-input rows with perm-cycle row 0.
74    HasherPermLinkInput = 23,
75    /// Hasher perm-link output bus: pairs controller-output rows with perm-cycle row 15.
76    HasherPermLinkOutput = 24,
77}
78
79impl BusId {
80    /// Last variant discriminant. Paired with the static assertion below, `COUNT` stays
81    /// in lockstep with the enum: adding a new variant with a higher discriminant bumps
82    /// `COUNT` automatically (and the assertion flags a missed update if the new variant's
83    /// discriminant isn't contiguous).
84    pub const COUNT: usize = Self::HasherPermLinkOutput as usize + 1;
85}
86
87// Guard against an enum-update that skips a discriminant: any gap would inflate `COUNT`
88// relative to the real variant count and silently resize the bus-prefix table. If this
89// fires, either fill the gap or extend the check.
90const _: () = assert!(BusId::HasherPermLinkOutput as usize == 24);
91
92// HASHER MESSAGES
93// ================================================================================================
94
95/// Hasher chiplet message: a [`BusId`] tag plus a variable-width payload.
96///
97/// All hasher messages encode as `bus_prefix[kind] + [addr, node_index, ...payload]`; only
98/// the payload width differs between variants.
99#[derive(Clone, Debug)]
100pub struct HasherMsg<E> {
101    pub kind: BusId,
102    pub addr: E,
103    pub node_index: E,
104    pub payload: HasherPayload<E>,
105}
106
107/// Payload for a [`HasherMsg`]; width varies per interaction kind.
108#[derive(Clone, Debug)]
109pub enum HasherPayload<E> {
110    /// 12-lane sponge state.
111    State([E; 12]),
112    /// 8-lane rate.
113    Rate([E; 8]),
114    /// 4-element word/digest.
115    Word([E; 4]),
116}
117
118impl<E: PrimeCharacteristicRing + Clone> HasherMsg<E> {
119    // --- State messages (14 payload elements: [addr, node_index, state[12]]) ---
120
121    /// Linear hash / control block init: full 12-lane sponge state.
122    ///
123    /// Used by: HPERM input, LOGPRECOMPILE input.
124    pub fn linear_hash_init(addr: E, state: [E; 12]) -> Self {
125        Self {
126            kind: BusId::HasherLinearHashInit,
127            addr,
128            node_index: E::ZERO,
129            payload: HasherPayload::State(state),
130        }
131    }
132
133    /// Control block init: 8 rate lanes + opcode at `capacity[1]`, zeros elsewhere.
134    ///
135    /// Used by: JOIN, SPLIT, LOOP, SPAN, CALL, SYSCALL, DYN, DYNCALL.
136    pub fn control_block(addr: E, rate: &[E; 8], opcode: u8) -> Self {
137        let state = [
138            rate[0].clone(),
139            rate[1].clone(),
140            rate[2].clone(),
141            rate[3].clone(),
142            rate[4].clone(),
143            rate[5].clone(),
144            rate[6].clone(),
145            rate[7].clone(),
146            E::ZERO,
147            E::from_u16(opcode as u16),
148            E::ZERO,
149            E::ZERO,
150        ];
151        Self {
152            kind: BusId::HasherLinearHashInit,
153            addr,
154            node_index: E::ZERO,
155            payload: HasherPayload::State(state),
156        }
157    }
158
159    /// Return full sponge state after permutation.
160    ///
161    /// Used by: HPERM output, LOGPRECOMPILE output.
162    pub fn return_state(addr: E, state: [E; 12]) -> Self {
163        Self {
164            kind: BusId::HasherReturnState,
165            addr,
166            node_index: E::ZERO,
167            payload: HasherPayload::State(state),
168        }
169    }
170
171    // --- Rate messages (10 payload elements: [addr, node_index, rate[8]]) ---
172
173    /// Absorb new rate into running hash.
174    ///
175    /// Used by: RESPAN.
176    pub fn absorption(addr: E, rate: [E; 8]) -> Self {
177        Self {
178            kind: BusId::HasherAbsorption,
179            addr,
180            node_index: E::ZERO,
181            payload: HasherPayload::Rate(rate),
182        }
183    }
184
185    // --- Word messages (6 payload elements: [addr, node_index, word[4]]) ---
186
187    /// Return digest only (node_index = 0).
188    ///
189    /// Used by: END, MPVERIFY output, MRUPDATE output.
190    pub fn return_hash(addr: E, word: [E; 4]) -> Self {
191        Self {
192            kind: BusId::HasherReturnHash,
193            addr,
194            node_index: E::ZERO,
195            payload: HasherPayload::Word(word),
196        }
197    }
198
199    /// Start Merkle path verification (with explicit node_index).
200    ///
201    /// Used by: MPVERIFY input.
202    pub fn merkle_verify_init(addr: E, node_index: E, word: [E; 4]) -> Self {
203        Self {
204            kind: BusId::HasherMerkleVerifyInit,
205            addr,
206            node_index,
207            payload: HasherPayload::Word(word),
208        }
209    }
210
211    /// Start Merkle update, old path (with explicit node_index).
212    ///
213    /// Used by: MRUPDATE old input.
214    pub fn merkle_old_init(addr: E, node_index: E, word: [E; 4]) -> Self {
215        Self {
216            kind: BusId::HasherMerkleOldInit,
217            addr,
218            node_index,
219            payload: HasherPayload::Word(word),
220        }
221    }
222
223    /// Start Merkle update, new path (with explicit node_index).
224    ///
225    /// Used by: MRUPDATE new input.
226    pub fn merkle_new_init(addr: E, node_index: E, word: [E; 4]) -> Self {
227        Self {
228            kind: BusId::HasherMerkleNewInit,
229            addr,
230            node_index,
231            payload: HasherPayload::Word(word),
232        }
233    }
234}
235
236// MEMORY MESSAGES
237// ================================================================================================
238
239/// Memory chiplet message. Variants differ by payload size.
240///
241/// Encodes as `bus_prefix[bus] + [ctx, addr, clk, ...payload]`. Use the [`MemoryMsg`]
242/// associated functions (`read_element`, `write_element`, `read_word`, `write_word`) to
243/// build messages with the correct interaction kind.
244#[derive(Clone, Debug)]
245pub enum MemoryMsg<E> {
246    /// 5-element message: `[ctx, addr, clk, element]`.
247    Element {
248        bus: BusId,
249        ctx: E,
250        addr: E,
251        clk: E,
252        element: E,
253    },
254    /// 8-element message: `[ctx, addr, clk, word[0..4]]`.
255    Word {
256        bus: BusId,
257        ctx: E,
258        addr: E,
259        clk: E,
260        word: [E; 4],
261    },
262}
263
264impl<E> MemoryMsg<E> {
265    /// Read a single element from memory.
266    pub fn read_element(ctx: E, addr: E, clk: E, element: E) -> Self {
267        Self::Element {
268            bus: BusId::MemoryReadElement,
269            ctx,
270            addr,
271            clk,
272            element,
273        }
274    }
275
276    /// Write a single element to memory.
277    pub fn write_element(ctx: E, addr: E, clk: E, element: E) -> Self {
278        Self::Element {
279            bus: BusId::MemoryWriteElement,
280            ctx,
281            addr,
282            clk,
283            element,
284        }
285    }
286
287    /// Read a 4-element word from memory.
288    pub fn read_word(ctx: E, addr: E, clk: E, word: [E; 4]) -> Self {
289        Self::Word {
290            bus: BusId::MemoryReadWord,
291            ctx,
292            addr,
293            clk,
294            word,
295        }
296    }
297
298    /// Write a 4-element word to memory.
299    pub fn write_word(ctx: E, addr: E, clk: E, word: [E; 4]) -> Self {
300        Self::Word {
301            bus: BusId::MemoryWriteWord,
302            ctx,
303            addr,
304            clk,
305            word,
306        }
307    }
308}
309
310// BITWISE MESSAGE
311// ================================================================================================
312
313/// Bitwise chiplet message (4 elements): `[op, a, b, result]`.
314#[derive(Clone, Debug)]
315pub struct BitwiseMsg<E> {
316    pub op: E,
317    pub a: E,
318    pub b: E,
319    pub result: E,
320}
321
322impl<E: PrimeCharacteristicRing> BitwiseMsg<E> {
323    const AND_SELECTOR: u32 = 0;
324    const XOR_SELECTOR: u32 = 1;
325
326    /// Bitwise AND message (op selector = 0).
327    pub fn and(a: E, b: E, result: E) -> Self {
328        Self {
329            op: E::from_u32(Self::AND_SELECTOR),
330            a,
331            b,
332            result,
333        }
334    }
335
336    /// Bitwise XOR message (op selector = 1).
337    pub fn xor(a: E, b: E, result: E) -> Self {
338        Self {
339            op: E::from_u32(Self::XOR_SELECTOR),
340            a,
341            b,
342            result,
343        }
344    }
345}
346
347// DECODER MESSAGES
348// ================================================================================================
349
350/// Block stack message: `[block_id, parent_id, is_loop, ctx, fmp, depth, fn_hash[4]]`.
351///
352/// `Simple` — for blocks that don't save context (JOIN/SPLIT/SPAN/DYN/LOOP/RESPAN/END-simple).
353/// Context fields are encoded as zeros.
354///
355/// `Full` — for blocks that save/restore the caller's execution context
356/// (CALL/SYSCALL/DYNCALL/END-call).
357#[derive(Clone, Debug)]
358pub enum BlockStackMsg<E> {
359    Simple {
360        block_id: E,
361        parent_id: E,
362        is_loop: E,
363    },
364    Full {
365        block_id: E,
366        parent_id: E,
367        is_loop: E,
368        ctx: E,
369        fmp: E,
370        depth: E,
371        fn_hash: [E; 4],
372    },
373}
374
375/// Block hash queue message (7 elements):
376/// `[child_hash[4], parent, is_first_child, is_loop_body]`.
377///
378/// `FirstChild` — first child of a JOIN (is_first_child = 1, is_loop_body = 0).
379/// `Child` — non-first, non-loop child (is_first_child = 0, is_loop_body = 0).
380/// `LoopBody` — loop body entry (is_first_child = 0, is_loop_body = 1).
381/// `End` — removal at END; both flags are computed expressions.
382#[derive(Clone, Debug)]
383pub enum BlockHashMsg<E> {
384    FirstChild {
385        parent: E,
386        child_hash: [E; 4],
387    },
388    Child {
389        parent: E,
390        child_hash: [E; 4],
391    },
392    LoopBody {
393        parent: E,
394        child_hash: [E; 4],
395    },
396    End {
397        parent: E,
398        child_hash: [E; 4],
399        is_first_child: E,
400        is_loop_body: E,
401    },
402}
403
404/// Op group table message (3 elements): `[batch_id, group_pos, group_value]`.
405#[derive(Clone, Debug)]
406pub struct OpGroupMsg<E> {
407    pub batch_id: E,
408    pub group_pos: E,
409    pub group_value: E,
410}
411
412impl<E: PrimeCharacteristicRing + Clone> OpGroupMsg<E> {
413    /// Create an op group message. Computes `group_pos = group_count - offset`.
414    pub fn new<V>(batch_id: &E, group_count: V, offset: u16, group_value: E) -> Self
415    where
416        V: core::ops::Sub<E, Output = E> + Clone,
417    {
418        Self {
419            batch_id: batch_id.clone(),
420            group_pos: group_count - E::from_u16(offset),
421            group_value,
422        }
423    }
424}
425
426// STACK MESSAGE
427// ================================================================================================
428
429/// Stack overflow table message (3 elements): `[clk, val, prev]`.
430///
431/// `clk` is the cycle at which the value spilled past `stack[15]`, `val` is the spilled element,
432/// and `prev` links to the previous overflow entry (the prior `b1`).
433#[derive(Clone, Debug)]
434pub struct StackOverflowMsg<E> {
435    pub clk: E,
436    pub val: E,
437    pub prev: E,
438}
439
440// HASHER PERM-LINK MESSAGE
441// ================================================================================================
442
443/// Hasher perm-link message (12 elements): `state[0..12]`.
444///
445/// Binds hasher controller rows to permutation sub-chiplet rows. The `Input` variant pairs a
446/// controller-input row with perm-cycle row 0 on `BusId::HasherPermLinkInput`; the `Output`
447/// variant pairs a controller-output row with perm-cycle row 15 on
448/// `BusId::HasherPermLinkOutput`. `state` carries all 12 sponge lanes (rate_0, rate_1, capacity).
449#[derive(Clone, Debug)]
450pub enum HasherPermLinkMsg<E> {
451    Input { state: [E; 12] },
452    Output { state: [E; 12] },
453}
454
455// KERNEL ROM MESSAGE
456// ================================================================================================
457
458/// Kernel ROM message (4 elements): `bus_prefix[bus] + [digest[4]]`.
459///
460/// Two bus domains: INIT (one remove per declared procedure, balanced by the boundary
461/// correction from public inputs) and CALL (one insert per SYSCALL, carrying the
462/// multiplicity from kernel ROM column 0; balanced by decoder-emitted SYSCALL removes).
463#[derive(Clone, Debug)]
464pub struct KernelRomMsg<E> {
465    bus: BusId,
466    pub digest: [E; 4],
467}
468
469impl<E: PrimeCharacteristicRing + Clone> KernelRomMsg<E> {
470    /// Kernel procedure call message (SYSCALL request side + chiplet CALL response).
471    pub fn call(digest: [E; 4]) -> Self {
472        Self { bus: BusId::KernelRomCall, digest }
473    }
474
475    /// Kernel procedure init message (public-input boundary + chiplet INIT response).
476    pub fn init(digest: [E; 4]) -> Self {
477        Self { bus: BusId::KernelRomInit, digest }
478    }
479}
480
481// ACE MESSAGE
482// ================================================================================================
483
484/// ACE circuit evaluation init message (5 elements): `[clk, ctx, ptr, num_read, num_eval]`.
485#[derive(Clone, Debug)]
486pub struct AceInitMsg<E> {
487    pub clk: E,
488    pub ctx: E,
489    pub ptr: E,
490    pub num_read: E,
491    pub num_eval: E,
492}
493
494// RANGE CHECK MESSAGE
495// ================================================================================================
496
497/// Range check message (1 element): `[value]`.
498///
499/// The denominator is `α + β⁰ · value`.
500#[derive(Clone, Debug)]
501pub struct RangeMsg<E> {
502    pub value: E,
503}
504
505// LOG-PRECOMPILE CAPACITY MESSAGE
506// ================================================================================================
507
508/// Log-precompile capacity state message (4 elements): `cap[4]`.
509#[derive(Clone, Debug)]
510pub struct LogCapacityMsg<E> {
511    pub capacity: [E; 4],
512}
513
514// SIBLING TABLE MESSAGE
515// ================================================================================================
516
517// ACE WIRING MESSAGE
518// ================================================================================================
519
520/// ACE wiring bus message (5 elements): `[clk, ctx, id, v0, v1]`.
521///
522/// Encodes a single wire entry for the ACE wiring bus. Each wire carries
523/// an identifier and a two-coefficient extension-field value.
524#[derive(Clone, Debug)]
525pub struct AceWireMsg<E> {
526    pub clk: E,
527    pub ctx: E,
528    pub id: E,
529    pub v0: E,
530    pub v1: E,
531}
532
533// CHIPLET RESPONSE MESSAGES
534// ================================================================================================
535
536/// Memory chiplet response message with conditional element/word encoding.
537///
538/// The chiplet-side memory response must select between element access (4 payload
539/// elements: `[ctx, addr, clk, element]`) and word access (7 payload elements:
540/// `[ctx, addr, clk, word[4]]`) based on `is_word`. The label, address, and element are
541/// all pre-computed from the chiplet columns (including the idx0/idx1 element mux).
542#[derive(Clone, Debug)]
543pub struct MemoryResponseMsg<E> {
544    pub is_read: E,
545    pub ctx: E,
546    pub addr: E,
547    pub clk: E,
548    pub is_word: E,
549    pub element: E,
550    pub word: [E; 4],
551}
552
553// LOOKUP MESSAGE IMPLEMENTATIONS
554// ================================================================================================
555
556use crate::lookup::message::LookupMessage;
557
558// --- HasherMsg (interaction-specific bus ids) ----------------------------------------------------
559
560impl<E, EF> LookupMessage<E, EF> for HasherMsg<E>
561where
562    E: PrimeCharacteristicRing + Clone,
563    EF: PrimeCharacteristicRing + Clone + Algebra<E>,
564{
565    fn encode(&self, challenges: &Challenges<EF>) -> EF {
566        let mut acc = challenges.bus_prefix[self.kind as usize].clone();
567        acc += challenges.inner_product_at(0, &[self.addr.clone(), self.node_index.clone()]);
568        let payload = match &self.payload {
569            HasherPayload::State(state) => state.as_slice(),
570            HasherPayload::Rate(rate) => rate.as_slice(),
571            HasherPayload::Word(word) => word.as_slice(),
572        };
573        acc += challenges.inner_product_at(2, payload);
574        acc
575    }
576}
577
578// --- MemoryMsg (interaction-specific bus ids) ----------------------------------------------------
579
580impl<E, EF> LookupMessage<E, EF> for MemoryMsg<E>
581where
582    E: PrimeCharacteristicRing + Clone,
583    EF: PrimeCharacteristicRing + Clone + Algebra<E>,
584{
585    fn encode(&self, challenges: &Challenges<EF>) -> EF {
586        let bus = match self {
587            Self::Element { bus, .. } | Self::Word { bus, .. } => *bus as usize,
588        };
589        let mut acc = challenges.bus_prefix[bus].clone();
590        match self {
591            Self::Element { ctx, addr, clk, element, .. } => {
592                acc += challenges.inner_product_at(
593                    0,
594                    &[ctx.clone(), addr.clone(), clk.clone(), element.clone()],
595                );
596            },
597            Self::Word { ctx, addr, clk, word, .. } => {
598                acc += challenges.inner_product_at(0, &[ctx.clone(), addr.clone(), clk.clone()]);
599                acc += challenges.inner_product_at(3, word.as_slice());
600            },
601        }
602        acc
603    }
604}
605
606// --- BitwiseMsg ----------------------------------------------------------------------------------
607
608impl<E, EF> LookupMessage<E, EF> for BitwiseMsg<E>
609where
610    E: PrimeCharacteristicRing + Clone,
611    EF: PrimeCharacteristicRing + Clone + Algebra<E>,
612{
613    fn encode(&self, challenges: &Challenges<EF>) -> EF {
614        challenges.encode(
615            BusId::Bitwise as usize,
616            [self.op.clone(), self.a.clone(), self.b.clone(), self.result.clone()],
617        )
618    }
619}
620
621// --- BlockStackMsg -------------------------------------------------------------------------------
622
623impl<E, EF> LookupMessage<E, EF> for BlockStackMsg<E>
624where
625    E: PrimeCharacteristicRing + Clone,
626    EF: PrimeCharacteristicRing + Clone + Algebra<E>,
627{
628    fn encode(&self, challenges: &Challenges<EF>) -> EF {
629        let mut acc = challenges.bus_prefix[BusId::BlockStackTable as usize].clone();
630        match self {
631            // `Simple` zero-pads to 10 slots; slots `3..10` contribute `β^k · 0 = 0` so
632            // they are elided from the loop.
633            Self::Simple { block_id, parent_id, is_loop } => {
634                acc += challenges
635                    .inner_product_at(0, &[block_id.clone(), parent_id.clone(), is_loop.clone()]);
636            },
637            Self::Full {
638                block_id,
639                parent_id,
640                is_loop,
641                ctx,
642                fmp,
643                depth,
644                fn_hash,
645            } => {
646                acc += challenges.inner_product_at(
647                    0,
648                    &[
649                        block_id.clone(),
650                        parent_id.clone(),
651                        is_loop.clone(),
652                        ctx.clone(),
653                        fmp.clone(),
654                        depth.clone(),
655                    ],
656                );
657                acc += challenges.inner_product_at(6, fn_hash.as_slice());
658            },
659        }
660        acc
661    }
662}
663
664// --- BlockHashMsg --------------------------------------------------------------------------------
665
666impl<E, EF> LookupMessage<E, EF> for BlockHashMsg<E>
667where
668    E: PrimeCharacteristicRing + Clone,
669    EF: PrimeCharacteristicRing + Clone + Algebra<E>,
670{
671    fn encode(&self, challenges: &Challenges<EF>) -> EF {
672        // Per-variant fan-in: produce the (parent, child_hash, is_first_child, is_loop_body)
673        // tuple, then emit a flat 7-slot payload laid out as
674        // `[child_hash[4], parent, is_first_child, is_loop_body]`.
675        let (parent, child_hash, is_first_child, is_loop_body) = match self {
676            Self::FirstChild { parent, child_hash } => (parent, child_hash, E::ONE, E::ZERO),
677            Self::Child { parent, child_hash } => (parent, child_hash, E::ZERO, E::ZERO),
678            Self::LoopBody { parent, child_hash } => (parent, child_hash, E::ZERO, E::ONE),
679            Self::End {
680                parent,
681                child_hash,
682                is_first_child,
683                is_loop_body,
684            } => (parent, child_hash, is_first_child.clone(), is_loop_body.clone()),
685        };
686        challenges.encode(
687            BusId::BlockHashTable as usize,
688            [
689                child_hash[0].clone(),
690                child_hash[1].clone(),
691                child_hash[2].clone(),
692                child_hash[3].clone(),
693                parent.clone(),
694                is_first_child,
695                is_loop_body,
696            ],
697        )
698    }
699}
700
701// --- OpGroupMsg ----------------------------------------------------------------------------------
702
703impl<E, EF> LookupMessage<E, EF> for OpGroupMsg<E>
704where
705    E: PrimeCharacteristicRing + Clone,
706    EF: PrimeCharacteristicRing + Clone + Algebra<E>,
707{
708    fn encode(&self, challenges: &Challenges<EF>) -> EF {
709        challenges.encode(
710            BusId::OpGroupTable as usize,
711            [self.batch_id.clone(), self.group_pos.clone(), self.group_value.clone()],
712        )
713    }
714}
715
716// --- StackOverflowMsg ----------------------------------------------------------------------------
717
718impl<E, EF> LookupMessage<E, EF> for StackOverflowMsg<E>
719where
720    E: PrimeCharacteristicRing + Clone,
721    EF: PrimeCharacteristicRing + Clone + Algebra<E>,
722{
723    fn encode(&self, challenges: &Challenges<EF>) -> EF {
724        challenges.encode(
725            BusId::StackOverflowTable as usize,
726            [self.clk.clone(), self.val.clone(), self.prev.clone()],
727        )
728    }
729}
730
731// --- KernelRomMsg --------------------------------------------------------------------------------
732
733impl<E, EF> LookupMessage<E, EF> for KernelRomMsg<E>
734where
735    E: PrimeCharacteristicRing + Clone,
736    EF: PrimeCharacteristicRing + Clone + Algebra<E>,
737{
738    fn encode(&self, challenges: &Challenges<EF>) -> EF {
739        challenges.encode(self.bus as usize, self.digest.clone())
740    }
741}
742
743// --- AceInitMsg ----------------------------------------------------------------------------------
744
745impl<E, EF> LookupMessage<E, EF> for AceInitMsg<E>
746where
747    E: PrimeCharacteristicRing + Clone,
748    EF: PrimeCharacteristicRing + Clone + Algebra<E>,
749{
750    fn encode(&self, challenges: &Challenges<EF>) -> EF {
751        challenges.encode(
752            BusId::AceInit as usize,
753            [
754                self.clk.clone(),
755                self.ctx.clone(),
756                self.ptr.clone(),
757                self.num_read.clone(),
758                self.num_eval.clone(),
759            ],
760        )
761    }
762}
763
764// --- RangeMsg ------------------------------------------------------------------------------------
765
766impl<E, EF> LookupMessage<E, EF> for RangeMsg<E>
767where
768    E: PrimeCharacteristicRing + Clone,
769    EF: PrimeCharacteristicRing + Clone + Algebra<E>,
770{
771    fn encode(&self, challenges: &Challenges<EF>) -> EF {
772        challenges.encode(BusId::RangeCheck as usize, [self.value.clone()])
773    }
774}
775
776// --- LogCapacityMsg ------------------------------------------------------------------------------
777
778impl<E, EF> LookupMessage<E, EF> for LogCapacityMsg<E>
779where
780    E: PrimeCharacteristicRing + Clone,
781    EF: PrimeCharacteristicRing + Clone + Algebra<E>,
782{
783    fn encode(&self, challenges: &Challenges<EF>) -> EF {
784        challenges.encode(BusId::LogPrecompileTranscript as usize, self.capacity.clone())
785    }
786}
787
788// --- HasherPermLinkMsg ---------------------------------------------------------------------------
789
790impl<E, EF> LookupMessage<E, EF> for HasherPermLinkMsg<E>
791where
792    E: PrimeCharacteristicRing + Clone,
793    EF: PrimeCharacteristicRing + Clone + Algebra<E>,
794{
795    fn encode(&self, challenges: &Challenges<EF>) -> EF {
796        let (bus, state) = match self {
797            Self::Input { state } => (BusId::HasherPermLinkInput, state),
798            Self::Output { state } => (BusId::HasherPermLinkOutput, state),
799        };
800        challenges.encode(bus as usize, state.clone())
801    }
802}
803
804// --- AceWireMsg ----------------------------------------------------------------------------------
805
806impl<E, EF> LookupMessage<E, EF> for AceWireMsg<E>
807where
808    E: PrimeCharacteristicRing + Clone,
809    EF: PrimeCharacteristicRing + Clone + Algebra<E>,
810{
811    fn encode(&self, challenges: &Challenges<EF>) -> EF {
812        challenges.encode(
813            BusId::AceWiring as usize,
814            [
815                self.clk.clone(),
816                self.ctx.clone(),
817                self.id.clone(),
818                self.v0.clone(),
819                self.v1.clone(),
820            ],
821        )
822    }
823}
824
825// LookupMessage impls for the response + sibling structs
826// ================================================================================================
827//
828// The `*ResponseMsg` structs below carry `LookupMessage<E, EF>` impls consumed by
829// `lookup/buses/chiplet_responses.rs`. The runtime-muxed encoding (bus prefix muxed
830// by `is_read`/`is_word` flags) keeps the response-column transition at degree 8.
831
832impl<E, EF> LookupMessage<E, EF> for MemoryResponseMsg<E>
833where
834    E: PrimeCharacteristicRing + Clone,
835    EF: PrimeCharacteristicRing + Clone + Algebra<E>,
836{
837    fn encode(&self, challenges: &Challenges<EF>) -> EF {
838        let bp = &challenges.beta_powers;
839        let is_read = self.is_read.clone();
840        let is_write: E = E::ONE - is_read.clone();
841        let is_word = self.is_word.clone();
842        let is_element: E = E::ONE - is_word.clone();
843
844        // Mux only the bus prefix; the payload (ctx, addr, clk, ...) is shared. Factored
845        // as a read/write select per access width so the four (read/write × element/word)
846        // cases stay audit-visible without blowing the polynomial degree.
847        let prefix_element = challenges.bus_prefix[BusId::MemoryReadElement as usize].clone()
848            * is_read.clone()
849            + challenges.bus_prefix[BusId::MemoryWriteElement as usize].clone() * is_write.clone();
850        let prefix_word = challenges.bus_prefix[BusId::MemoryReadWord as usize].clone() * is_read
851            + challenges.bus_prefix[BusId::MemoryWriteWord as usize].clone() * is_write;
852        let prefix = prefix_element * is_element.clone() + prefix_word * is_word.clone();
853
854        let mut acc = prefix;
855        acc += bp[0].clone() * self.ctx.clone();
856        acc += bp[1].clone() * self.addr.clone();
857        acc += bp[2].clone() * self.clk.clone();
858
859        // Element payload (gated by is_element) vs word payload (gated by is_word).
860        acc += bp[3].clone() * self.element.clone() * is_element;
861        acc += challenges.inner_product_at(3, self.word.as_slice()) * is_word;
862        acc
863    }
864}
865
866// SIBLING MESSAGES
867// ================================================================================================
868//
869// [`SiblingMsg<E>`] carries the relevant hasher half alongside a [`SiblingBit`] tag and
870// encodes against sparse β layouts (`[2, 7, 8, 9, 10]` and `[2, 3, 4, 5, 6]`) dictated by
871// the responder-side hasher chiplet algebra. The trait is permissive about which β
872// positions an `encode` body touches; contiguity is a convention, not a requirement.
873
874/// Sibling-table message for the Merkle sibling bus.
875///
876/// The Merkle direction bit picks which half of the hasher rate block holds the sibling:
877/// `bit = 0` → sibling at `h[4..8]`, payload lands in β positions `[1, 2, 7, 8, 9, 10]`
878/// (mrupdate_id at β¹, node_index at β², rate1 at β⁷..β¹⁰); `bit = 1` → sibling at
879/// `h[0..4]`, payload lands in β positions `[1, 2, 3, 4, 5, 6]`.
880#[derive(Clone, Debug)]
881pub struct SiblingMsg<E> {
882    pub bit: SiblingBit,
883    pub mrupdate_id: E,
884    pub node_index: E,
885    pub h: [E; 4],
886}
887
888/// Which half of the hasher rate block holds the sibling word for this row.
889#[derive(Copy, Clone, Debug, PartialEq, Eq)]
890pub enum SiblingBit {
891    /// `bit = 0` — sibling lives in the high rate half (`h[4..8]`).
892    Zero,
893    /// `bit = 1` — sibling lives in the low rate half (`h[0..4]`).
894    One,
895}
896
897impl<E, EF> LookupMessage<E, EF> for SiblingMsg<E>
898where
899    E: PrimeCharacteristicRing + Clone,
900    EF: PrimeCharacteristicRing + Clone + Algebra<E>,
901{
902    fn encode(&self, challenges: &Challenges<EF>) -> EF {
903        let mut acc = challenges.bus_prefix[BusId::SiblingTable as usize].clone();
904        acc += challenges.inner_product_at(1, &[self.mrupdate_id.clone(), self.node_index.clone()]);
905        let base = match self.bit {
906            SiblingBit::Zero => 7,
907            SiblingBit::One => 3,
908        };
909        acc += challenges.inner_product_at(base, self.h.as_slice());
910        acc
911    }
912}