sigma_proofs/linear_relation/mod.rs
1//! # Linear Maps and Relations Handling.
2//!
3//! This module provides utilities for describing and manipulating **linear group linear maps**,
4//! supporting sigma protocols over group-based statements (e.g., discrete logarithms, DLEQ proofs). See Maurer09.
5//!
6//! It includes:
7//! - [`LinearCombination`]: a sparse representation of scalar multiplication relations.
8//! - [`LinearMap`]: a collection of linear combinations acting on group elements.
9//! - [`LinearRelation`]: a higher-level structure managing linear maps and their associated images.
10
11use alloc::format;
12use alloc::vec::Vec;
13use core::iter;
14use core::marker::PhantomData;
15
16use crate::errors::{Error, InvalidInstance};
17use crate::group::msm::MultiScalarMul;
18use ff::Field;
19use group::prime::PrimeGroup;
20
21/// Implementations of conversion operations such as From and FromIterator for var and term types.
22mod convert;
23/// Implementations of core ops for the linear combination types.
24mod ops;
25
26/// Implementation of canonical linear relation.
27mod canonical;
28pub use canonical::CanonicalLinearRelation;
29
30/// A wrapper representing an index for a scalar variable.
31///
32/// Used to reference scalars in sparse linear combinations.
33#[derive(Copy, Clone, Debug, PartialEq, Eq)]
34pub struct ScalarVar<G>(usize, PhantomData<G>);
35
36impl<G> ScalarVar<G> {
37 pub fn index(&self) -> usize {
38 self.0
39 }
40}
41
42impl<G> core::hash::Hash for ScalarVar<G> {
43 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
44 self.0.hash(state)
45 }
46}
47
48/// A wrapper representing an index for a group element (point).
49///
50/// Used to reference group elements in sparse linear combinations.
51#[derive(Copy, Clone, Debug, PartialEq, Eq)]
52pub struct GroupVar<G>(usize, PhantomData<G>);
53
54impl<G> GroupVar<G> {
55 pub fn index(&self) -> usize {
56 self.0
57 }
58}
59
60impl<G> core::hash::Hash for GroupVar<G> {
61 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
62 self.0.hash(state)
63 }
64}
65
66#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
67pub enum ScalarTerm<G> {
68 Var(ScalarVar<G>),
69 Unit,
70}
71
72impl<G: PrimeGroup> ScalarTerm<G> {
73 // NOTE: This function is private intentionally as it would be replaced if a ScalarMap struct
74 // were to be added.
75 fn value(self, scalars: &[G::Scalar]) -> G::Scalar {
76 match self {
77 Self::Var(var) => scalars[var.0],
78 Self::Unit => G::Scalar::ONE,
79 }
80 }
81}
82
83/// A term in a linear combination, representing `scalar * elem`.
84#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
85pub struct Term<G> {
86 scalar: ScalarTerm<G>,
87 elem: GroupVar<G>,
88}
89
90#[derive(Copy, Clone, Debug)]
91pub struct Weighted<T, F> {
92 pub term: T,
93 pub weight: F,
94}
95
96#[derive(Clone, Debug)]
97pub struct Sum<T>(Vec<T>);
98
99impl<T> Sum<T> {
100 /// Access the terms of the sum as slice reference.
101 pub fn terms(&self) -> &[T] {
102 &self.0
103 }
104}
105
106impl<T> core::iter::Sum<T> for Sum<T> {
107 /// Add a bunch of `T` to yield a `Sum<T>`
108 fn sum<I>(iter: I) -> Self
109 where
110 I: Iterator<Item = T>,
111 {
112 Self(iter.collect())
113 }
114}
115
116/// Represents a sparse linear combination of scalars and group elements.
117///
118/// For example, it can represent an equation like:
119/// `w_1 * (s_1 * P_1) + w_2 * (s_2 * P_2) + ... + w_n * (s_n * P_n)`
120///
121/// where:
122/// - `(s_i * P_i)` are the terms, with `s_i` scalars (referenced by `scalar_vars`) and `P_i` group elements (referenced by `element_vars`).
123/// - `w_i` are the constant weight scalars
124///
125/// The indices refer to external lists managed by the containing LinearMap.
126pub type LinearCombination<G> = Sum<Weighted<Term<G>, <G as group::Group>::Scalar>>;
127
128impl<G: PrimeGroup + MultiScalarMul> LinearMap<G> {
129 fn map(&self, scalars: &[G::Scalar]) -> Result<Vec<G>, InvalidInstance> {
130 self.linear_combinations
131 .iter()
132 .map(|lc| {
133 let weighted_coefficients =
134 lc.0.iter()
135 .map(|weighted| weighted.term.scalar.value(scalars) * weighted.weight)
136 .collect::<Vec<_>>();
137 let elements =
138 lc.0.iter()
139 .map(|weighted| self.group_elements.get(weighted.term.elem))
140 .collect::<Result<Vec<_>, InvalidInstance>>();
141 match elements {
142 Ok(elements) => Ok(G::msm(&weighted_coefficients, &elements)),
143 Err(error) => Err(error),
144 }
145 })
146 .collect::<Result<Vec<_>, InvalidInstance>>()
147 }
148}
149
150/// Ordered mapping of [GroupVar] to group elements assignments.
151#[derive(Clone, Debug)]
152pub struct GroupMap<G>(Vec<Option<G>>);
153
154impl<G: PrimeGroup> GroupMap<G> {
155 /// Assign a group element value to a point variable.
156 ///
157 /// # Parameters
158 ///
159 /// - `var`: The variable to assign.
160 /// - `element`: The value to assign to the variable.
161 ///
162 /// # Panics
163 ///
164 /// Panics if the given assignment conflicts with the existing assignment.
165 pub fn assign_element(&mut self, var: GroupVar<G>, element: G) {
166 if self.0.len() <= var.0 {
167 self.0.resize(var.0 + 1, None);
168 } else if let Some(assignment) = self.0[var.0] {
169 assert_eq!(
170 assignment, element,
171 "conflicting assignments for var {var:?}"
172 )
173 }
174 self.0[var.0] = Some(element);
175 }
176
177 /// Assigns specific group elements to point variables (indices).
178 ///
179 /// # Parameters
180 ///
181 /// - `assignments`: A collection of `(GroupVar, GroupElement)` pairs that can be iterated over.
182 ///
183 /// # Panics
184 ///
185 /// Panics if the collection contains two conflicting assignments for the same variable.
186 pub fn assign_elements(&mut self, assignments: impl IntoIterator<Item = (GroupVar<G>, G)>) {
187 for (var, elem) in assignments.into_iter() {
188 self.assign_element(var, elem);
189 }
190 }
191
192 /// Get the element value assigned to the given point var.
193 ///
194 /// Returns [`InvalidInstance`] if a value is not assigned.
195 pub fn get(&self, var: GroupVar<G>) -> Result<G, InvalidInstance> {
196 match self.0.get(var.0) {
197 Some(Some(elem)) => Ok(*elem),
198 Some(None) | None => Err(InvalidInstance::new(format!(
199 "unassigned group variable {}",
200 var.0
201 ))),
202 }
203 }
204
205 /// Iterate over the assigned variable and group element pairs in this mapping.
206 // NOTE: Not implemented as `IntoIterator` for now because doing so requires explicitly
207 // defining an iterator type, See https://github.com/rust-lang/rust/issues/63063
208 #[allow(clippy::should_implement_trait)]
209 pub fn into_iter(self) -> impl Iterator<Item = (GroupVar<G>, Option<G>)> {
210 self.0
211 .into_iter()
212 .enumerate()
213 .map(|(i, x)| (GroupVar(i, PhantomData), x))
214 }
215
216 pub fn iter(&self) -> impl Iterator<Item = (GroupVar<G>, Option<&G>)> {
217 self.0
218 .iter()
219 .enumerate()
220 .map(|(i, opt)| (GroupVar(i, PhantomData), opt.as_ref()))
221 }
222
223 /// Add a new group element to the map and return its variable index
224 pub fn push(&mut self, element: G) -> GroupVar<G> {
225 let index = self.0.len();
226 self.0.push(Some(element));
227 GroupVar(index, PhantomData)
228 }
229
230 /// Get the number of elements in the map
231 pub fn len(&self) -> usize {
232 self.0.len()
233 }
234
235 /// Check if the map is empty
236 pub fn is_empty(&self) -> bool {
237 self.0.is_empty()
238 }
239}
240
241impl<G> Default for GroupMap<G> {
242 fn default() -> Self {
243 Self(Vec::default())
244 }
245}
246
247impl<G: PrimeGroup> FromIterator<(GroupVar<G>, G)> for GroupMap<G> {
248 fn from_iter<T: IntoIterator<Item = (GroupVar<G>, G)>>(iter: T) -> Self {
249 iter.into_iter()
250 .fold(Self::default(), |mut instance, (var, val)| {
251 instance.assign_element(var, val);
252 instance
253 })
254 }
255}
256
257/// A LinearMap represents a list of linear combinations over group elements.
258///
259/// It supports dynamic allocation of scalars and elements,
260/// and evaluates by performing multi-scalar multiplications.
261#[derive(Clone, Default, Debug)]
262pub struct LinearMap<G: PrimeGroup> {
263 /// The set of linear combination constraints (equations).
264 pub linear_combinations: Vec<LinearCombination<G>>,
265 /// The list of group elements referenced in the linear map.
266 ///
267 /// Uninitialized group elements are presented with `None`.
268 pub group_elements: GroupMap<G>,
269 /// The total number of scalar variables allocated.
270 pub num_scalars: usize,
271 /// The total number of group element variables allocated.
272 pub num_elements: usize,
273}
274
275impl<G: PrimeGroup> LinearMap<G> {
276 /// Creates a new empty [`LinearMap`].
277 ///
278 /// # Returns
279 ///
280 /// A [`LinearMap`] instance with empty linear combinations and group elements,
281 /// and zero allocated scalars and elements.
282 pub fn new() -> Self {
283 Self {
284 linear_combinations: Vec::new(),
285 group_elements: GroupMap::default(),
286 num_scalars: 0,
287 num_elements: 0,
288 }
289 }
290
291 /// Returns the number of constraints (equations) in this linear map.
292 pub fn num_constraints(&self) -> usize {
293 self.linear_combinations.len()
294 }
295
296 /// Adds a new linear combination constraint to the linear map.
297 ///
298 /// # Parameters
299 /// - `lc`: The [`LinearCombination`] to add.
300 pub fn append(&mut self, lc: LinearCombination<G>) {
301 self.linear_combinations.push(lc);
302 }
303
304 /// Evaluates all linear combinations in the linear map with the provided scalars.
305 ///
306 /// # Parameters
307 /// - `scalars`: A slice of scalar values corresponding to the scalar variables.
308 ///
309 /// # Returns
310 ///
311 /// A vector of group elements, each being the result of evaluating one linear combination with the scalars.
312 pub fn evaluate(&self, scalars: &[G::Scalar]) -> Result<Vec<G>, Error>
313 where
314 G: MultiScalarMul,
315 {
316 self.linear_combinations
317 .iter()
318 .map(|lc| {
319 // TODO: The multiplication by the (public) weight is potentially wasteful in the
320 // weight is most commonly 1, but multiplication is constant time.
321 let weighted_coefficients =
322 lc.0.iter()
323 .map(|weighted| weighted.term.scalar.value(scalars) * weighted.weight)
324 .collect::<Vec<_>>();
325 let elements =
326 lc.0.iter()
327 .map(|weighted| self.group_elements.get(weighted.term.elem))
328 .collect::<Result<Vec<_>, _>>()?;
329 Ok(G::msm(&weighted_coefficients, &elements))
330 })
331 .collect()
332 }
333}
334
335/// A wrapper struct coupling a [`LinearMap`] with the corresponding expected output (image) elements.
336///
337/// This structure represents the *preimage problem* for a group linear map: given a set of scalar inputs,
338/// determine whether their image under the linear map matches a target set of group elements.
339///
340/// Internally, the constraint system is defined through:
341/// - A list of group elements and linear equations (held in the [`LinearMap`] field),
342/// - A list of [`GroupVar`] indices (`image`) that specify the expected output for each constraint.
343#[derive(Clone, Default, Debug)]
344pub struct LinearRelation<G: PrimeGroup> {
345 /// The underlying linear map describing the structure of the statement.
346 pub linear_map: LinearMap<G>,
347 /// Indices pointing to elements representing the "target" images for each constraint.
348 pub image: Vec<GroupVar<G>>,
349}
350
351impl<G: PrimeGroup> LinearRelation<G> {
352 /// Create a new empty [`LinearRelation`].
353 pub fn new() -> Self {
354 Self {
355 linear_map: LinearMap::new(),
356 image: Vec::new(),
357 }
358 }
359
360 /// Adds a new equation to the statement of the form:
361 /// `lhs = Σ weight_i * (scalar_i * point_i)`.
362 ///
363 /// # Parameters
364 /// - `lhs`: The image group element variable (left-hand side of the equation).
365 /// - `rhs`: An instance of [`LinearCombination`] representing the linear combination on the right-hand side.
366 pub fn append_equation(&mut self, lhs: GroupVar<G>, rhs: impl Into<LinearCombination<G>>) {
367 self.linear_map.append(rhs.into());
368 self.image.push(lhs);
369 }
370
371 /// Adds a new equation to the statement of the form:
372 /// `lhs = Σ weight_i * (scalar_i * point_i)` without allocating `lhs`.
373 ///
374 /// # Parameters
375 /// - `rhs`: An instance of [`LinearCombination`] representing the linear combination on the right-hand side.
376 pub fn allocate_eq(&mut self, rhs: impl Into<LinearCombination<G>>) -> GroupVar<G> {
377 let var = self.allocate_element();
378 self.append_equation(var, rhs);
379 var
380 }
381
382 /// Allocates a scalar variable for use in the linear map.
383 pub fn allocate_scalar(&mut self) -> ScalarVar<G> {
384 self.linear_map.num_scalars += 1;
385 ScalarVar(self.linear_map.num_scalars - 1, PhantomData)
386 }
387
388 /// Allocates space for `N` new scalar variables.
389 ///
390 /// # Returns
391 /// An array of [`ScalarVar`] representing the newly allocated scalar indices.
392 ///
393 /// # Example
394 /// ```
395 /// # use sigma_proofs::LinearRelation;
396 /// use curve25519_dalek::RistrettoPoint as G;
397 ///
398 /// let mut relation = LinearRelation::<G>::new();
399 /// let [var_x, var_y] = relation.allocate_scalars();
400 /// let vars = relation.allocate_scalars::<10>();
401 /// ```
402 pub fn allocate_scalars<const N: usize>(&mut self) -> [ScalarVar<G>; N] {
403 let mut vars = [ScalarVar(usize::MAX, PhantomData); N];
404 for var in vars.iter_mut() {
405 *var = self.allocate_scalar();
406 }
407 vars
408 }
409
410 /// Allocates a vector of new scalar variables.
411 ///
412 /// # Returns
413 /// A vector of [`ScalarVar`] representing the newly allocated scalar indices.
414 /// /// # Example
415 /// ```
416 /// # use sigma_proofs::LinearRelation;
417 /// use curve25519_dalek::RistrettoPoint as G;
418 ///
419 /// let mut relation = LinearRelation::<G>::new();
420 /// let [var_x, var_y] = relation.allocate_scalars();
421 /// let vars = relation.allocate_scalars_vec(10);
422 /// ```
423 pub fn allocate_scalars_vec(&mut self, n: usize) -> Vec<ScalarVar<G>> {
424 (0..n).map(|_| self.allocate_scalar()).collect()
425 }
426
427 /// Allocates a point variable (group element) for use in the linear map.
428 pub fn allocate_element(&mut self) -> GroupVar<G> {
429 self.linear_map.num_elements += 1;
430 GroupVar(self.linear_map.num_elements - 1, PhantomData)
431 }
432
433 /// Allocates a point variable (group element) and sets it immediately to the given value
434 pub fn allocate_element_with(&mut self, element: G) -> GroupVar<G> {
435 let var = self.allocate_element();
436 self.set_element(var, element);
437 var
438 }
439
440 /// Allocates `N` point variables (group elements) for use in the linear map.
441 ///
442 /// # Returns
443 /// An array of [`GroupVar`] representing the newly allocated group element indices.
444 ///
445 /// # Example
446 /// ```
447 /// # use sigma_proofs::LinearRelation;
448 /// use curve25519_dalek::RistrettoPoint as G;
449 ///
450 /// let mut relation = LinearRelation::<G>::new();
451 /// let [var_g, var_h] = relation.allocate_elements();
452 /// let vars = relation.allocate_elements::<10>();
453 /// ```
454 pub fn allocate_elements<const N: usize>(&mut self) -> [GroupVar<G>; N] {
455 let mut vars = [GroupVar(usize::MAX, PhantomData); N];
456 for var in vars.iter_mut() {
457 *var = self.allocate_element();
458 }
459 vars
460 }
461
462 /// Allocates a vector of new point variables (group elements).
463 ///
464 /// # Returns
465 /// A vector of [`GroupVar`] representing the newly allocated group element indices.
466 ///
467 /// # Example
468 /// ```
469 /// # use sigma_proofs::LinearRelation;
470 /// use curve25519_dalek::RistrettoPoint as G;
471 /// let mut relation = LinearRelation::<G>::new();
472 /// let [var_g, var_h
473 /// ] = relation.allocate_elements();
474 /// let vars = relation.allocate_elements_vec(10);
475 /// ```
476 pub fn allocate_elements_vec(&mut self, n: usize) -> Vec<GroupVar<G>> {
477 (0..n).map(|_| self.allocate_element()).collect()
478 }
479
480 /// Allocates a point variable (group element) and sets it immediately to the given value.
481 pub fn allocate_elements_with(&mut self, elements: &[G]) -> Vec<GroupVar<G>> {
482 elements
483 .iter()
484 .map(|element| self.allocate_element_with(*element))
485 .collect()
486 }
487
488 /// Assign a group element value to a point variable.
489 ///
490 /// # Parameters
491 ///
492 /// - `var`: The variable to assign.
493 /// - `element`: The value to assign to the variable.
494 ///
495 /// # Panics
496 ///
497 /// Panics if the given assignment conflicts with the existing assignment.
498 pub fn set_element(&mut self, var: GroupVar<G>, element: G) {
499 self.linear_map.group_elements.assign_element(var, element)
500 }
501
502 /// Assigns specific group elements to point variables (indices).
503 ///
504 /// # Parameters
505 ///
506 /// - `assignments`: A collection of `(GroupVar, GroupElement)` pairs that can be iterated over.
507 ///
508 /// # Panics
509 ///
510 /// Panics if the collection contains two conflicting assignments for the same variable.
511 pub fn set_elements(&mut self, assignments: impl IntoIterator<Item = (GroupVar<G>, G)>) {
512 self.linear_map.group_elements.assign_elements(assignments)
513 }
514
515 /// Evaluates all linear combinations in the linear map with the provided scalars, computing the
516 /// left-hand side of this constraints (i.e. the image).
517 ///
518 /// After calling this function, all point variables will be assigned.
519 ///
520 /// # Parameters
521 ///
522 /// - `scalars`: A slice of scalar values corresponding to the scalar variables.
523 ///
524 /// # Returns
525 ///
526 /// Return `Ok` on success, and an error if unassigned elements prevent the image from being
527 /// computed. Modifies the group elements assigned in the [LinearRelation].
528 pub fn compute_image(&mut self, scalars: &[G::Scalar]) -> Result<(), Error>
529 where
530 G: MultiScalarMul,
531 {
532 if self.linear_map.num_constraints() != self.image.len() {
533 // NOTE: This is a panic, rather than a returned error, because this can only happen if
534 // this implementation has a bug.
535 panic!("invalid LinearRelation: different number of constraints and image variables");
536 }
537
538 let mapped_scalars = self.linear_map.map(scalars)?;
539
540 for (mapped_scalar, lhs) in iter::zip(mapped_scalars, &self.image) {
541 self.linear_map
542 .group_elements
543 .assign_element(*lhs, mapped_scalar)
544 }
545 Ok(())
546 }
547
548 /// Returns the current group elements corresponding to the image variables.
549 ///
550 /// # Returns
551 ///
552 /// A vector of group elements (`Vec<G>`) representing the linear map's image.
553 // TODO: Should this return GroupMap?
554 pub fn image(&self) -> Result<Vec<G>, InvalidInstance> {
555 self.image
556 .iter()
557 .map(|&var| self.linear_map.group_elements.get(var))
558 .collect()
559 }
560
561 /// Construct a [CanonicalLinearRelation] from this generalized linear relation.
562 ///
563 /// The construction may fail if the linear relation is malformed, unsatisfiable, or trivial.
564 pub fn canonical(&self) -> Result<CanonicalLinearRelation<G>, InvalidInstance>
565 where
566 G: MultiScalarMul,
567 {
568 self.try_into()
569 }
570}