1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//! LogUp adapter — natural last-row σ-closing.
//!
//! Re-uses miden-vm's closure-based lookup framework (`LookupAir`,
//! `LookupBuilder`, `LookupColumn`, `LookupGroup`, `LookupBatch`,
//! `LookupMessage`, `Challenges`, `LookupFractions`, `accumulate`) but
//! swaps the constraint-side column-0 finalization (and the matching
//! prover-side residue) for a **natural last-row σ-closing**.
//!
//! ## Closing the running sum
//!
//! Where miden's stock
//! [`ConstraintLookupBuilder`](miden_air::lookup::ConstraintLookupBuilder)
//! uses a normalized cyclic recurrence, this adapter closes the unnormalized running sum on the
//! **live last row**:
//!
//! ```text
//! when_first: acc[0] = 0
//! when_transition: D₀·(acc_next[0] − Σ_{i<L} acc[i]) − N₀ = 0
//! when_last: D₀·(σ − Σ_{i<L} acc[i]) − N₀ = 0
//! ungated (i>0): D_i · acc[i] − N_i = 0
//! ```
//!
//! where `σ` lives at `permutation_values()[0]` and `L = num_logup_cols`
//! bounds the sum to the LogUp columns (trailing Schwartz–Zippel register
//! columns stay out of σ). The `acc[0] = 0` boundary plus the last-row
//! bind pin `σ = Σ_r delta_r` — the column's full LogUp residue — folding
//! the final row's interactions into the committed σ, so even a packed
//! chiplet whose last row fires (e.g. the 2^16 byte-pair table) closes
//! correctly. No padding row reserved, no `inv_n` public input. The col-0
//! transition/last gate costs +1 degree over the older ungated σ/n-cyclic
//! form; 0.26's per-AIR quotient coset absorbs it.
//!
//! Prover-side: [`build_logup_aux_trace`] runs miden's stock `build_lookup_fractions` +
//! normalized `accumulate`, adds `r * sigma_prime` back to column 0 to recover the plain running
//! sum (`aux[r] = Σ_{i<r} delta_i`), and commits `sigma = n * sigma_prime`. Fraction columns are
//! kept verbatim.
//!
//! ## Encoding
//!
//! Encoding is delegated to [`Challenges`]:
//!
//! ```text
//! encode(bus, elems) = bus_prefix[bus] + Σ β^i · elems[i]
//! bus_prefix[bus] = α + (bus + 1) · β^W
//! ```
//!
//! where `W = `[`MAX_MESSAGE_WIDTH`]. Distinct bus ids live on disjoint
//! `β^W`-spaced offsets, so two `(bus, payload)` pairs collide only on
//! a vanishing-probability subset of `(α, β)`.
//!
//! [`lookup_challenges_from_slice`] builds a `Challenges<QuadFelt>` from
//! the flat `[α, β]` slice that `LiftedAir::build_aux_trace` is given —
//! sized to [`MAX_MESSAGE_WIDTH`] / [`NUM_BUS_IDS`] so prover and
//! verifier see identical prefixes.
/// Emit one **flattened** LogUp column — a single batch of its fractions —
/// inside a chiplet's `LookupAir::eval` (where the builder param is `LB` and
/// the `LookupColumn` / `LookupGroup` / `LookupBatch` traits are in scope).
///
/// Each fraction is `(name, multiplicity, message, deg)`. Keep <= 2 degree-2
/// fractions (or 1 degree-3) per column, and <= 1 in column 0 (the gated
/// running sum), so every closing constraint stays at degree <= 3 -> lqd 1.
/// `$cd` is the (ignored, on the constraint path) column-degree hint.
, $cd);
}, $cd);
},
$cd,
);
};
}
pub use build_logup_aux_trace;
pub use ;
pub use frac_col;
// Re-export miden-vm's framework so chiplets only need one `use`.
pub use ;
use ;
use crate;
// CHALLENGES
// ================================================================================================
/// Number of extension-field challenges drawn by the verifier — one
/// global `(α, β)` pair, shared across every relation.
pub const NUM_RANDOMNESS: usize = 2;
/// Build a `Challenges<QuadFelt>` from the flat `[α, β]` slice handed to
/// `LiftedAir::build_aux_trace`.
///
/// Sizes the precomputed tables to [`MAX_MESSAGE_WIDTH`] /
/// [`NUM_BUS_IDS`] so prover and verifier see identical prefixes.
// PUBLIC-INPUT LAYOUT
// ================================================================================================
/// Number of base-field public inputs the VM exposes: the 4-felt
/// transcript root (a Poseidon2 digest).
///
/// 0.26's `air_inputs` is a single slice every AIR reads, so every chiplet
/// declares the *same* count and they must agree. The root is the VM's one
/// genuine public input; only the transcript-eval chip reads it (pinning
/// its row-0 hash to `public_values()[0..4]`), the others just declare it.
/// The σ/n `inv_n` slot is gone — the natural last-row closing
/// (`constraint.rs`) needs no per-AIR height input, which is what lets a
/// per-AIR value drop out of the now-shared public inputs.
pub const NUM_PUBLIC_VALUES: usize = 4;
// PERMUTATION (σ) CONTRACT
// ================================================================================================
/// Number of permutation (σ) values every chiplet exposes: exactly
/// **one** — the running `σ = Σ_r delta_r` committed at aux column 0
/// and pinned by the last-row σ-closing constraint. (Aux *column* counts vary
/// per chiplet; this exposed-σ count does not.) Backs the
/// `LiftedAir::num_aux_values` method / the layout's
/// `num_permutation_values`. Shared so the single-σ shape reads as a
/// VM-wide convention, not a per-chiplet choice.
pub const NUM_SIGMA_VALUES: usize = 1;
/// Cross-AIR σ closure for
/// [`MultiAir::eval_external`](miden_lifted_air::MultiAir::eval_external):
/// sum every AIR's committed σ residue. Each AIR exposes exactly one —
/// aux column 0's full LogUp residue (see [`NUM_SIGMA_VALUES`]) — so
/// `aux_values[i][0]` is AIR `i`'s contribution. The cross-chiplet bus
/// identity `Σ σ = 0` holds iff the returned value is zero, which
/// `eval_external` surfaces as its single assertion expression.