Skip to main content

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}