binius_core/ring_switch/
common.rs

1// Copyright 2024-2025 Irreducible Inc.
2
3use std::sync::Arc;
4
5use binius_field::{Field, TowerField};
6use binius_utils::sparse_index::SparseIndex;
7
8use super::error::Error;
9use crate::{
10	oracle::{MultilinearOracleSet, MultilinearPolyOracle, MultilinearPolyVariant},
11	piop::CommitMeta,
12	protocols::evalcheck::EvalcheckMultilinearClaim,
13};
14
15/// A prefix of an evaluation claim query.
16///
17/// For an evaluation point $(z_0, ..., z_\ell)$, the prefix is $(z_0, ..., z_{\kappa-1})$, where
18/// $\kappa$ is the binary logarithm of the embedding degree.
19#[derive(Debug)]
20pub struct EvalClaimPrefixDesc<F: Field> {
21	pub prefix: Vec<F>,
22}
23
24impl<F: Field> EvalClaimPrefixDesc<F> {
25	pub fn kappa(&self) -> usize {
26		self.prefix.len()
27	}
28}
29
30/// A suffix of an evaluation claim query.
31///
32/// For an evaluation point $(z_0, ..., z_\ell)$, the prefix is $(z_\kappa, ..., z_{\ell-1})$,
33/// where $\kappa$ is the binary logarithm of the embedding degree.
34#[derive(Debug)]
35pub struct EvalClaimSuffixDesc<F: Field> {
36	pub suffix: Arc<[F]>,
37	pub kappa: usize,
38}
39
40/// An incomplete [`crate::piop::PIOPSumcheckClaim`] for which the sum has not been determined yet.
41#[derive(Debug)]
42pub struct PIOPSumcheckClaimDesc<'a, F: Field> {
43	/// Index of the committed multilinear, referencing the commit metadata.
44	pub committed_idx: usize,
45	/// Index of the suffix descriptor, referencing the slice in an [`EvalClaimSystem`].
46	pub suffix_desc_idx: usize,
47	pub eval_claim: &'a EvalcheckMultilinearClaim<F>,
48}
49
50/// A system of relations required to verify multilinear evaluation claims using the batched
51/// FRI-Binius protocol.
52///
53/// Fields are public because this is internal to the module.
54#[derive(Debug)]
55pub struct EvalClaimSystem<'a, F: Field> {
56	pub commit_meta: &'a CommitMeta,
57	pub prefix_descs: Vec<EvalClaimPrefixDesc<F>>,
58	pub suffix_descs: Vec<EvalClaimSuffixDesc<F>>,
59	pub sumcheck_claim_descs: Vec<PIOPSumcheckClaimDesc<'a, F>>,
60	pub eval_claim_to_prefix_desc_index: Vec<usize>,
61}
62
63impl<'a, F: TowerField> EvalClaimSystem<'a, F> {
64	/// Constructs a new system of claim relations.
65	///
66	/// ## Arguments
67	///
68	/// * `commit_meta` - metadata about the polynomial commitment.
69	/// * `oracle_to_commit_index` - a sparse index mapping oracle IDs to IDs in the commit
70	///   metadata.
71	/// * `eval_claims` - the evaluation claims on committed multilinear polynomials.
72	pub fn new(
73		oracles: &MultilinearOracleSet<F>,
74		commit_meta: &'a CommitMeta,
75		oracle_to_commit_index: &SparseIndex<usize>,
76		eval_claims: &'a [EvalcheckMultilinearClaim<F>],
77	) -> Result<Self, Error> {
78		// Sort evaluation claims in ascending order by number of packed variables. This must
79		// happen before we do any further index mapping.
80		let mut eval_claims = eval_claims.iter().collect::<Vec<_>>();
81		eval_claims.sort_by_key(|claim| match oracles.oracle(claim.id) {
82			// The number of packed variables is n_vars + tower_level - F::TOWER_LEVEL. Just use
83			// n_vars + tower_level as the sort key because we haven't checked that the subtraction
84			// wouldn't underflow yet.
85			MultilinearPolyOracle {
86				n_vars,
87				tower_level,
88				variant: MultilinearPolyVariant::Committed,
89				..
90			} => n_vars + tower_level,
91			// Ignore any non-committed oracles for now, they'll be caught later in a context where
92			// we can return an error.
93			_ => 0,
94		});
95
96		let (
97			prefix_descs,
98			eval_claim_to_prefix_desc_index,
99			suffix_descs,
100			eval_claim_to_suffix_desc_index,
101		) = group_claims_by_eval_point(oracles, &eval_claims)?;
102
103		let sumcheck_claim_descs = eval_claims
104			.into_iter()
105			.enumerate()
106			.map(|(i, eval_claim)| {
107				let oracle = oracles.oracle(eval_claim.id);
108				if !matches!(oracle.variant, MultilinearPolyVariant::Committed) {
109					return Err(Error::EvalcheckClaimForDerivedPoly { id: eval_claim.id });
110				}
111				let committed_idx = oracle_to_commit_index
112					.get(oracle.id())
113					.copied()
114					.ok_or_else(|| Error::OracleToCommitIndexMissingEntry { id: eval_claim.id })?;
115				let suffix_desc_idx = eval_claim_to_suffix_desc_index[i];
116				Ok(PIOPSumcheckClaimDesc {
117					committed_idx,
118					suffix_desc_idx,
119					eval_claim,
120				})
121			})
122			.collect::<Result<Vec<_>, _>>()?;
123
124		Ok(Self {
125			commit_meta,
126			prefix_descs,
127			suffix_descs,
128			sumcheck_claim_descs,
129			eval_claim_to_prefix_desc_index,
130		})
131	}
132
133	pub fn max_claim_kappa(&self) -> usize {
134		self.prefix_descs
135			.iter()
136			.map(|desc| desc.kappa())
137			.max()
138			.unwrap_or(0)
139	}
140}
141
142#[allow(clippy::type_complexity)]
143fn group_claims_by_eval_point<F: TowerField>(
144	oracles: &MultilinearOracleSet<F>,
145	claims: &[&EvalcheckMultilinearClaim<F>],
146) -> Result<(Vec<EvalClaimPrefixDesc<F>>, Vec<usize>, Vec<EvalClaimSuffixDesc<F>>, Vec<usize>), Error>
147{
148	let mut prefix_descs = Vec::<EvalClaimPrefixDesc<F>>::new();
149	let mut suffix_descs = Vec::<EvalClaimSuffixDesc<F>>::new();
150	let mut claim_to_prefix_index = Vec::with_capacity(claims.len());
151	let mut claim_to_suffix_index = Vec::with_capacity(claims.len());
152	for claim in claims {
153		let MultilinearPolyOracle {
154			id,
155			tower_level,
156			variant: MultilinearPolyVariant::Committed,
157			..
158		} = oracles.oracle(claim.id)
159		else {
160			return Err(Error::EvalcheckClaimForDerivedPoly { id: claim.id });
161		};
162		let kappa = F::TOWER_LEVEL.checked_sub(tower_level).ok_or_else(|| {
163			Error::OracleTowerLevelTooHigh {
164				id,
165				max: F::TOWER_LEVEL,
166			}
167		})?;
168
169		let (prefix, suffix) = claim.eval_point.split_at(kappa);
170
171		let prefix_id = prefix_descs
172			.iter()
173			.position(|desc| desc.prefix == prefix)
174			.unwrap_or_else(|| {
175				let index = prefix_descs.len();
176				prefix_descs.push(EvalClaimPrefixDesc {
177					prefix: prefix.to_vec(),
178				});
179				index
180			});
181		claim_to_prefix_index.push(prefix_id);
182
183		let suffix_id = suffix_descs
184			.iter()
185			.position(|desc| &*desc.suffix == suffix && desc.kappa == kappa)
186			.unwrap_or_else(|| {
187				let index = suffix_descs.len();
188				suffix_descs.push(EvalClaimSuffixDesc {
189					suffix: suffix.to_vec().into(),
190					kappa,
191				});
192				index
193			});
194		claim_to_suffix_index.push(suffix_id);
195	}
196	Ok((prefix_descs, claim_to_prefix_index, suffix_descs, claim_to_suffix_index))
197}