Multilinear Extensions
A function defined on the -dimensional boolean hypercube has a unique multilinear extension (MLE) : the unique polynomial of degree at most 1 in each variable that agrees with on all boolean inputs. Concretely,
The product term is the equality polynomial , which equals 1 when on the boolean hypercube and interpolates smoothly elsewhere.
MLEs are the central data structure in sumcheck-based proof systems: the sumcheck protocol operates over multilinear polynomials, and essentially all witness data in Jolt is represented as MLEs.
For a more formal introduction, see Section 3.5 of Proofs, Arguments, and Zero Knowledge.
Representation
An MLE over variables is stored as a vector of evaluations over the boolean hypercube . The -th entry of this vector stores where is the binary representation of . Jolt uses big-endian indexing: the most significant bit of corresponds to .
Variable binding
Given represented by evaluations, binding the most significant variable to a challenge produces a new MLE represented by evaluations. For each index :
where and for the same suffix . This is a single pass. Binding the least significant variable instead pairs even/odd entries: .
Which variable gets bound in each round is determined by the BindingOrder: HighToLow (most significant first) or LowToHigh (least significant first).
Implementations in Jolt
The MLE implementations live in jolt-core/src/poly/. All are unified under the MultilinearPolynomial<F> enum (multilinear_polynomial.rs), which dispatches binding and evaluation to the appropriate concrete type. The two most common representations are DensePolynomial and CompactPolynomial.
DensePolynomial
DensePolynomial<F> (dense_mlpoly.rs) is the straightforward representation: a Vec<F> of field elements. This is the baseline MLE type used when coefficients are full-sized field elements.
Binding operates in-place on the evaluation vector. In HighToLow order, the vector is split into left and right halves (corresponding to the most significant variable being 0 or 1) and interpolated:
#![allow(unused)] fn main() { let (left, right) = self.Z.split_at_mut(n); left.iter_mut().zip(right.iter()).for_each(|(a, b)| { *a += r * (*b - *a); }); }
In LowToHigh order, adjacent even/odd pairs are interpolated instead:
#![allow(unused)] fn main() { for i in 0..n { self.Z[i] = self.Z[2 * i] + r * (self.Z[2 * i + 1] - self.Z[2 * i]); } }
Both orders have parallel variants (bind_parallel) that use Rayon. The HighToLow parallel path binds in place, while the LowToHigh parallel path cannot.
CompactPolynomial
CompactPolynomial<T, F> (compact_polynomial.rs) stores coefficients as small scalars of type T (implementing the SmallScalar trait: bool, u8, u16, u32, u64, u128, i64, i128) rather than full field elements. This is the "pay-per-bit" representation that makes Dory commitments efficient.
The polynomial maintains two storage vectors:
coeffs: Vec<T>— the original small-scalar coefficients (immutable after construction).bound_coeffs: Vec<F>— field elements materialized lazily on the first bind.
Binding has two phases. On the first bind, coefficients are promoted from small scalars to field elements using SmallScalar arithmetic that avoids overflow:
#![allow(unused)] fn main() { // For a pair (a, b) of small scalars: match a.cmp(&b) { Ordering::Equal => a.to_field(), Ordering::Less => a.to_field() + r * (b - a).to_field(), Ordering::Greater => a.to_field() - r * (a - b).to_field(), } }
After this first bind populates bound_coeffs, subsequent binds operate on field elements identically to DensePolynomial.
The MultilinearPolynomial enum has a variant for each scalar type (U8Scalars, U16Scalars, I128Scalars, etc.), so dispatch is monomorphized and the small-scalar optimizations apply throughout the sumcheck hot loop.
Other specialized representations
Several other MLE types in jolt-core/src/poly/ exploit structure specific to Jolt's witness polynomials:
OneHotPolynomial(one_hot_polynomial.rs): Stores only the index of the single nonzero entry per cycle rather than a dense vector of 0s and 1s. Used for and polynomials in Twist and Shout.RaPolynomial(ra_poly.rs): A state machine that lazily materializes the (or ) polynomial across sumcheck rounds (Round1 Round2 Round3 RoundN), keeping memory proportional to the address-space size rather than until the final rounds.RLCPolynomial(rlc_polynomial.rs): Represents a random linear combination of multiple polynomials. Dense components are eagerly combined; one-hot components are stored as(coefficient, polynomial)pairs and combined lazily during evaluation. Arises in the PCS opening proof.EqPolynomial(eq_poly.rs): The equality MLE , commonly used as a building block in sumcheck and MLE evaluation.