p3_miden_lmcs/lib.rs
1//! Lifted Matrix Commitment Scheme (LMCS) for matrices with power-of-two heights.
2//!
3//! This crate provides a Merkle tree commitment scheme optimized for matrices that store
4//! polynomial evaluations in **bit-reversed order** over multiplicative cosets.
5//!
6//! # Main Types
7//!
8//! - [`LmcsConfig`]: Configuration holding cryptographic primitives (sponge + compression)
9//! with packed types for SIMD parallelization.
10//! - [`Lmcs`]: Trait for LMCS configurations, providing type-erased access to commitment operations.
11//! - [`LmcsTree`]: Trait for built LMCS trees, providing opening operations.
12//! - [`LiftedMerkleTree`]: The underlying Merkle tree data structure.
13//! - [`Proof`]: Single-opening proof with rows, optional salt, and authentication path.
14//! - [`BatchProof`]: Parsed batch opening from transcript hints.
15//!
16//! # API Overview
17//!
18//! ## Direct Usage (Simple)
19//!
20//! ```ignore
21//! use p3_miden_lmcs::{HidingLmcsConfig, Lmcs, LmcsConfig, LmcsTree};
22//! use p3_miden_transcript::{ProverTranscript, VerifierTranscript};
23//!
24//! // Config captures PF, PD (packed types), H, C, WIDTH, DIGEST
25//! // F = PF::Value and D = PD::Value are derived
26//! let config = LmcsConfig::<PF, PD, _, _, WIDTH, DIGEST>::new(sponge, compress);
27//! let challenger = /* ... */;
28//!
29//! // Build tree - no turbofish needed, packed types are known from config
30//! let tree = config.build_aligned_tree(matrices);
31//! let root = tree.root();
32//! let mut prover_channel = ProverTranscript::new(challenger);
33//! tree.prove_batch(&indices, &mut prover_channel);
34//! let (_, transcript) = prover_channel.finalize();
35//!
36//! let mut verifier_channel = VerifierTranscript::from_data(challenger, &transcript);
37//! let rows = config.open_batch(&root, &widths, log_max_height, &indices, &mut verifier_channel)?;
38//!
39//! // For hiding commitment with salt, use HidingLmcsConfig with RNG
40//! let hiding_config =
41//! HidingLmcsConfig::<PF, PD, _, _, _, WIDTH, DIGEST, 4>::new(sponge, compress, rng);
42//! let tree = hiding_config.build_aligned_tree(matrices);
43//! ```
44//!
45//! ## Trait-Based Usage (for generic code like FRI)
46//!
47//! ```ignore
48//! use p3_miden_lmcs::{Lmcs, LmcsTree};
49//! use p3_miden_transcript::ProverTranscript;
50//!
51//! fn commit_and_open<L: Lmcs>(lmcs: &L, matrices: Vec<impl Matrix<L::F>>) {
52//! let tree = lmcs.build_aligned_tree(matrices);
53//! let commitment = tree.root();
54//! let challenger = /* ... */;
55//! let mut channel = ProverTranscript::new(challenger);
56//! tree.prove_batch(&[0, 1, 2], &mut channel);
57//! // ...
58//! }
59//! ```
60//!
61//! ## Transcript Hints
62//!
63//! For the `LmcsConfig`/`LiftedMerkleTree` implementation in this crate, batch openings
64//! are streamed as transcript hints: for each unique query index **in sorted tree index order**
65//! (ascending, deduplicated), `prove_batch` writes one row per matrix (in tree
66//! leaf order) followed by optional salt. After all indices, missing sibling hashes are
67//! emitted level-by-level in canonical left-to-right, bottom-to-top order. Hints are not
68//! observed into the Fiat-Shamir challenger.
69//!
70//! `LmcsConfig::open_batch` consumes only the hints it needs to reconstruct the root;
71//! extra hint data is left unread. It expects `widths` and `log_max_height` to match the
72//! committed tree and treats empty `indices` as invalid. Use
73//! [`BatchProof::read_from_channel`](crate::BatchProof::read_from_channel) if you need to
74//! parse hints without hashing; then call [`BatchProof::single_proofs`](crate::BatchProof::single_proofs)
75//! with an LMCS config to reconstruct per-index proofs (keyed by index) without verifying against a
76//! commitment.
77//!
78//! # Mathematical Foundation
79//!
80//! Consider a polynomial `f(X)` of degree less than `d`, and let `g` be the coset generator and
81//! `K` a subgroup of order `n ≥ d` with primitive root `ω`. The coset evaluations
82//! `{f(g·ω^j) : j ∈ [0, n)}` can be stored in two orderings:
83//!
84//! - **Canonical order**: `[f(g·ω^0), f(g·ω^1), ..., f(g·ω^{n-1})]`
85//! - **Bit-reversed order**: `[f(g·ω^{bitrev(0)}), f(g·ω^{bitrev(1)}), ..., f(g·ω^{bitrev(n-1)})]`
86//!
87//! where `bitrev(i)` is the bit-reversal of index `i` within `log2(n)` bits.
88//!
89//! # Lifting by Upsampling
90//!
91//! When we have matrices with different heights n₀ ≤ n₁ ≤ … ≤ nₜ₋₁ (each a power of two),
92//! we "lift" smaller matrices to the maximum height N = nₜ₋₁ using **nearest-neighbor
93//! upsampling**: each row is repeated contiguously `r = N/n` times.
94//!
95//! For a matrix of height `n` lifted to `N`, the index map is: `i ↦ floor(i / r) = i >> log2(r)`
96//!
97//! **Example** (`n=4`, `N=8`):
98//! - Original rows: `[row0, row1, row2, row3]`
99//! - Upsampled: `[row0, row0, row1, row1, row2, row2, row3, row3]` (blocks of 2)
100//!
101//! # Why Upsampling for Bit-Reversed Data
102//!
103//! Given bit-reversed evaluations of `f(X)` over a coset `gK` where `|K| = n`, upsampling to
104//! height `N = n · r` (where `r = 2^k`) produces the bit-reversed evaluations of `f'(X) = f(Xʳ)`
105//! over the coset `gK'` where `|K'| = N`.
106//!
107//! Mathematically, if the input contains `f(g·(ω_n)^{bitrev_n(j)})` at index `j`, then after
108//! upsampling, each index `i` in `[0, N)` maps to the original index `j = i >> k`, giving:
109//!
110//! ```text
111//! upsampled[i] = f(g·(ω_n)^{bitrev_n(i >> k)}) = f'(g·(ω_N)^{bitrev_N(i)})
112//! ```
113//!
114//! where `f'(X) = f(Xʳ)`. This is exactly the bit-reversed evaluation of `f'` over `gK'`.
115//!
116//! # Opening Semantics
117//!
118//! When opening at index `i`, we retrieve the value at position `i` in the bit-reversed list.
119//! For the lifted polynomial `f'(X) = f(Xʳ)`, this gives `f'(g·(ω_N)^{bitrev_N(i)})`.
120//!
121//! Equivalently, this is `f'(g·ξ^i)` where `ξ = (ω_N)^{bitrev_N(i)}` is the `i`-th element
122//! when iterating over `K'` in the order induced by bit-reversed indices.
123//!
124//! # Equivalence to Cyclic Lifting
125//!
126//! Upsampling bit-reversed data is equivalent to cyclically repeating canonically-ordered data:
127//!
128//! ```text
129//! Upsample(BitReverse(data)) = BitReverse(Cyclic(data))
130//! ```
131//!
132//! where cyclic repetition tiles the original `n` rows periodically: `[row0, row1, ..., row_{n-1}, row0, ...]`.
133//!
134//! This equivalence follows from the bit-reversal identity: for `r = N/n = 2^k`,
135//! `bitrev_N(i) mod n = bitrev_n(i >> k)`.
136
137#![no_std]
138
139extern crate alloc;
140
141mod hiding_lmcs;
142mod lifted_tree;
143mod lmcs;
144pub mod mmcs;
145pub mod proof;
146#[cfg(test)]
147mod tests;
148pub mod utils;
149
150use alloc::{collections::BTreeMap, vec::Vec};
151
152// ============================================================================
153// Public Re-exports
154// ============================================================================
155pub use hiding_lmcs::HidingLmcsConfig;
156pub use lifted_tree::LiftedMerkleTree;
157pub use lmcs::LmcsConfig;
158use p3_matrix::Matrix;
159use p3_miden_transcript::{ProverChannel, TranscriptError, VerifierChannel};
160pub use proof::{BatchProof, LeafOpening, Proof};
161use thiserror::Error;
162pub use utils::{RowList, log2_strict_u8};
163
164// ============================================================================
165// Type Aliases
166// ============================================================================
167
168/// Opened rows keyed by leaf index, returned by [`Lmcs::open_batch`].
169pub type OpenedRows<F> = BTreeMap<usize, RowList<F>>;
170
171// ============================================================================
172// Traits
173// ============================================================================
174
175/// Trait for LMCS configurations.
176pub trait Lmcs: Clone {
177 /// Scalar field element type for matrix data.
178 ///
179 /// `Send + Sync` bounds required by [`Matrix<F>`].
180 type F: Clone + Send + Sync;
181 /// Commitment type (root hash).
182 type Commitment: Clone;
183 /// Parsed batch opening type.
184 type BatchProof;
185 /// Tree type (prover data), parameterized by matrix type.
186 type Tree<M: Matrix<Self::F>>: LmcsTree<Self::F, Self::Commitment, M>;
187
188 /// Build a tree from matrices with no transcript padding (alignment = 1).
189 ///
190 /// This affects only transcript hint formatting; the commitment root is unchanged.
191 fn build_tree<M: Matrix<Self::F>>(&self, leaves: Vec<M>) -> Self::Tree<M>;
192
193 /// Build a tree from matrices using the hasher alignment for transcript padding.
194 ///
195 /// Rows are padded to the hasher's alignment when streaming hints.
196 /// When the alignment is 1, this is identical to [`Self::build_tree`].
197 fn build_aligned_tree<M: Matrix<Self::F>>(&self, leaves: Vec<M>) -> Self::Tree<M>;
198
199 /// Hash a sequence of field slices into a leaf hash.
200 ///
201 /// Inputs are absorbed in order. For salted leaves, append the salt slice to the
202 /// iterator (or call this with a chained iterator).
203 fn hash<'a, I>(&self, rows: I) -> Self::Commitment
204 where
205 I: IntoIterator<Item = &'a [Self::F]>,
206 Self::F: 'a;
207
208 /// Compress two hashes into their parent (2-to-1 compression).
209 fn compress(&self, left: Self::Commitment, right: Self::Commitment) -> Self::Commitment;
210
211 /// Open a batch proof by reading hint data from a transcript channel.
212 ///
213 /// The hint format is implementation-defined; callers must use the matching
214 /// `LmcsTree::prove_batch` implementation to produce compatible hints.
215 /// `widths` and `log_max_height` must match the committed tree (including any
216 /// alignment padding if `build_aligned_tree` was used).
217 ///
218 /// # Preconditions
219 /// - `indices` must be non-empty and in `0..2^log_max_height`.
220 ///
221 /// # Postconditions
222 /// On success, the returned map contains exactly one entry per unique index from
223 /// the input. Each entry's `RowList<F>` has one row per width in `widths`,
224 /// with that row's length matching the corresponding width. Duplicate indices
225 /// are coalesced into a single entry.
226 fn open_batch<Ch>(
227 &self,
228 commitment: &Self::Commitment,
229 widths: &[usize],
230 log_max_height: u8,
231 indices: impl IntoIterator<Item = usize>,
232 channel: &mut Ch,
233 ) -> Result<OpenedRows<Self::F>, LmcsError>
234 where
235 Ch: VerifierChannel<F = Self::F, Commitment = Self::Commitment>;
236
237 /// Parse a batch opening from a transcript channel without validation.
238 ///
239 /// This is a parse-only function: it reads hints according to the implementation's
240 /// transcript layout but does not hash leaves or verify against a commitment.
241 /// The returned proof may be invalid if the inputs are themselves invalid;
242 /// validation happens in [`open_batch`](Lmcs::open_batch).
243 fn read_batch_proof_from_channel<Ch>(
244 &self,
245 widths: &[usize],
246 log_max_height: u8,
247 indices: &[usize],
248 channel: &mut Ch,
249 ) -> Result<Self::BatchProof, LmcsError>
250 where
251 Ch: VerifierChannel<F = Self::F, Commitment = Self::Commitment>;
252
253 /// Get the alignment used by `build_aligned_tree`.
254 ///
255 /// This is the hasher's rate, used to pad rows when streaming hints.
256 fn alignment(&self) -> usize;
257}
258
259/// Trait for built LMCS trees.
260///
261/// Provides methods for accessing tree data and generating proofs.
262pub trait LmcsTree<F, Commitment, M> {
263 /// Get the tree root (commitment).
264 fn root(&self) -> Commitment;
265
266 /// Get the tree height (number of leaves).
267 fn height(&self) -> usize;
268
269 /// Get references to the committed matrices.
270 ///
271 /// Matrix widths are not padded; use [`Self::widths`] for aligned widths.
272 fn leaves(&self) -> &[M];
273
274 /// Get the opened rows for a given leaf index.
275 ///
276 /// Rows are padded to the tree's alignment (1 for unaligned trees).
277 fn rows(&self, index: usize) -> RowList<F>;
278
279 /// Column alignment used when streaming openings.
280 fn alignment(&self) -> usize;
281
282 /// Get aligned widths for each committed matrix.
283 fn widths(&self) -> Vec<usize>;
284
285 /// Prove a batch opening and stream it into a transcript channel.
286 ///
287 /// The hint format is implementation-defined and must be consumed by the
288 /// corresponding `Lmcs::open_batch` implementation. Rows are padded to the
289 /// tree's alignment before being written to the channel.
290 ///
291 /// Leaf openings are written in **sorted tree index order** (ascending, deduplicated).
292 fn prove_batch<Ch>(&self, indices: impl IntoIterator<Item = usize>, channel: &mut Ch)
293 where
294 Ch: ProverChannel<F = F, Commitment = Commitment>;
295}
296
297// ============================================================================
298// Error Types
299// ============================================================================
300
301/// Errors that can occur during LMCS operations.
302#[derive(Debug, Clone, PartialEq, Eq, Error)]
303pub enum LmcsError {
304 #[error("invalid proof")]
305 InvalidProof,
306 #[error("root mismatch")]
307 RootMismatch,
308 #[error("transcript error: {0}")]
309 TranscriptError(#[from] TranscriptError),
310}