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}