Skip to main content

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}