sigma_rs/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 std::collections::HashMap;
12use std::hash::Hash;
13use std::iter;
14use std::marker::PhantomData;
15
16use ff::Field;
17use group::{Group, GroupEncoding};
18
19use crate::codec::ShakeCodec;
20use crate::errors::Error;
21use crate::schnorr_protocol::SchnorrProof;
22use crate::NISigmaProtocol;
23
24/// Implementations of conversion operations such as From and FromIterator for var and term types.
25mod convert;
26/// Implementations of core ops for the linear combination types.
27mod ops;
28
29/// A wrapper representing an index for a scalar variable.
30///
31/// Used to reference scalars in sparse linear combinations.
32#[derive(Copy, Clone, Debug, PartialEq, Eq)]
33pub struct ScalarVar<G>(usize, PhantomData<G>);
34
35impl<G> ScalarVar<G> {
36 pub fn index(&self) -> usize {
37 self.0
38 }
39}
40
41impl<G> Hash for ScalarVar<G> {
42 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
43 self.0.hash(state)
44 }
45}
46
47/// A wrapper representing an index for a group element (point).
48///
49/// Used to reference group elements in sparse linear combinations.
50#[derive(Copy, Clone, Debug, PartialEq, Eq)]
51pub struct GroupVar<G>(usize, PhantomData<G>);
52
53impl<G> GroupVar<G> {
54 pub fn index(&self) -> usize {
55 self.0
56 }
57}
58
59impl<G> Hash for GroupVar<G> {
60 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
61 self.0.hash(state)
62 }
63}
64
65#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
66pub enum ScalarTerm<G> {
67 Var(ScalarVar<G>),
68 Unit,
69}
70
71impl<G: Group> ScalarTerm<G> {
72 // NOTE: This function is private intentionally as it would be replaced if a ScalarMap struct
73 // were to be added.
74 fn value(self, scalars: &[G::Scalar]) -> G::Scalar {
75 match self {
76 Self::Var(var) => scalars[var.0],
77 Self::Unit => G::Scalar::ONE,
78 }
79 }
80}
81
82/// A term in a linear combination, representing `scalar * elem`.
83#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
84pub struct Term<G> {
85 scalar: ScalarTerm<G>,
86 elem: GroupVar<G>,
87}
88
89#[derive(Copy, Clone, Debug)]
90pub struct Weighted<T, F> {
91 pub term: T,
92 pub weight: F,
93}
94
95#[derive(Clone, Debug)]
96pub struct Sum<T>(Vec<T>);
97
98impl<T> Sum<T> {
99 /// Access the terms of the sum as slice reference.
100 pub fn terms(&self) -> &[T] {
101 &self.0
102 }
103}
104
105/// Represents a sparse linear combination of scalars and group elements.
106///
107/// For example, it can represent an equation like:
108/// `w_1 * (s_1 * P_1) + w_2 * (s_2 * P_2) + ... + w_n * (s_n * P_n)`
109///
110/// where:
111/// - `(s_i * P_i)` are the terms, with `s_i` scalars (referenced by `scalar_vars`) and `P_i` group elements (referenced by `element_vars`).
112/// - `w_i` are the constant weight scalars
113///
114/// The indices refer to external lists managed by the containing LinearMap.
115pub type LinearCombination<G> = Sum<Weighted<Term<G>, <G as Group>::Scalar>>;
116
117/// Ordered mapping of [GroupVar] to group elements assignments.
118#[derive(Clone, Debug)]
119pub struct GroupMap<G>(Vec<Option<G>>);
120
121impl<G: Group> GroupMap<G> {
122 /// Assign a group element value to a point variable.
123 ///
124 /// # Parameters
125 ///
126 /// - `var`: The variable to assign.
127 /// - `element`: The value to assign to the variable.
128 ///
129 /// # Panics
130 ///
131 /// Panics if the given assignment conflicts with the existing assignment.
132 pub fn assign_element(&mut self, var: GroupVar<G>, element: G) {
133 if self.0.len() <= var.0 {
134 self.0.resize(var.0 + 1, None);
135 } else if let Some(assignment) = self.0[var.0] {
136 assert_eq!(
137 assignment, element,
138 "conflicting assignments for var {var:?}"
139 )
140 }
141 self.0[var.0] = Some(element);
142 }
143
144 /// Assigns specific group elements to point variables (indices).
145 ///
146 /// # Parameters
147 ///
148 /// - `assignments`: A collection of `(GroupVar, GroupElement)` pairs that can be iterated over.
149 ///
150 /// # Panics
151 ///
152 /// Panics if the collection contains two conflicting assignments for the same variable.
153 pub fn assign_elements(&mut self, assignments: impl IntoIterator<Item = (GroupVar<G>, G)>) {
154 for (var, elem) in assignments.into_iter() {
155 self.assign_element(var, elem);
156 }
157 }
158
159 /// Get the element value assigned to the given point var.
160 ///
161 /// Returns [`Error::UnassignedGroupVar`] if a value is not assigned.
162 pub fn get(&self, var: GroupVar<G>) -> Result<G, Error> {
163 self.0[var.0].ok_or(Error::UnassignedGroupVar {
164 var_debug: format!("{var:?}"),
165 })
166 }
167
168 /// Iterate over the assigned variable and group element pairs in this mapping.
169 // NOTE: Not implemented as `IntoIterator` for now because doing so requires explicitly
170 // defining an iterator type, See https://github.com/rust-lang/rust/issues/63063
171 #[allow(clippy::should_implement_trait)]
172 pub fn into_iter(self) -> impl Iterator<Item = (GroupVar<G>, Option<G>)> {
173 self.0
174 .into_iter()
175 .enumerate()
176 .map(|(i, x)| (GroupVar(i, PhantomData), x))
177 }
178
179 pub fn iter(&self) -> impl Iterator<Item = (GroupVar<G>, Option<&G>)> {
180 self.0
181 .iter()
182 .enumerate()
183 .map(|(i, opt)| (GroupVar(i, PhantomData), opt.as_ref()))
184 }
185}
186
187impl<G> Default for GroupMap<G> {
188 fn default() -> Self {
189 Self(Vec::default())
190 }
191}
192
193impl<G: Group> FromIterator<(GroupVar<G>, G)> for GroupMap<G> {
194 fn from_iter<T: IntoIterator<Item = (GroupVar<G>, G)>>(iter: T) -> Self {
195 iter.into_iter()
196 .fold(Self::default(), |mut instance, (var, val)| {
197 instance.assign_element(var, val);
198 instance
199 })
200 }
201}
202
203/// A LinearMap represents a list of linear combinations over group elements.
204///
205/// It supports dynamic allocation of scalars and elements,
206/// and evaluates by performing multi-scalar multiplications.
207#[derive(Clone, Default, Debug)]
208pub struct LinearMap<G: Group> {
209 /// The set of linear combination constraints (equations).
210 pub constraints: Vec<LinearCombination<G>>,
211 /// The list of group elements referenced in the linear map.
212 ///
213 /// Uninitialized group elements are presented with `None`.
214 pub group_elements: GroupMap<G>,
215 /// The total number of scalar variables allocated.
216 pub num_scalars: usize,
217 /// The total number of group element variables allocated.
218 pub num_elements: usize,
219}
220
221/// Perform a simple multi-scalar multiplication (MSM) over scalars and points.
222///
223/// Given slices of scalars and corresponding group elements (bases),
224/// returns the sum of each base multiplied by its scalar coefficient.
225///
226/// # Parameters
227/// - `scalars`: slice of scalar multipliers.
228/// - `bases`: slice of group elements to be multiplied by the scalars.
229///
230/// # Returns
231/// The group element result of the MSM.
232pub fn msm_pr<G: Group>(scalars: &[G::Scalar], bases: &[G]) -> G {
233 let mut acc = G::identity();
234 for (s, p) in scalars.iter().zip(bases.iter()) {
235 acc += *p * s;
236 }
237 acc
238}
239
240impl<G: Group> LinearMap<G> {
241 /// Creates a new empty [`LinearMap`].
242 ///
243 /// # Returns
244 ///
245 /// A [`LinearMap`] instance with empty linear combinations and group elements,
246 /// and zero allocated scalars and elements.
247 pub fn new() -> Self {
248 Self {
249 constraints: Vec::new(),
250 group_elements: GroupMap::default(),
251 num_scalars: 0,
252 num_elements: 0,
253 }
254 }
255
256 /// Returns the number of constraints (equations) in this linear map.
257 pub fn num_constraints(&self) -> usize {
258 self.constraints.len()
259 }
260
261 /// Adds a new linear combination constraint to the linear map.
262 ///
263 /// # Parameters
264 /// - `lc`: The [`LinearCombination`] to add.
265 pub fn append(&mut self, lc: LinearCombination<G>) {
266 self.constraints.push(lc);
267 }
268
269 /// Evaluates all linear combinations in the linear map with the provided scalars.
270 ///
271 /// # Parameters
272 /// - `scalars`: A slice of scalar values corresponding to the scalar variables.
273 ///
274 /// # Returns
275 ///
276 /// A vector of group elements, each being the result of evaluating one linear combination with the scalars.
277 pub fn evaluate(&self, scalars: &[<G as Group>::Scalar]) -> Result<Vec<G>, Error> {
278 self.constraints
279 .iter()
280 .map(|lc| {
281 // TODO: The multiplication by the (public) weight is potentially wasteful in the
282 // weight is most commonly 1, but multiplication is constant time.
283 let weighted_coefficients =
284 lc.0.iter()
285 .map(|weighted| weighted.term.scalar.value(scalars) * weighted.weight)
286 .collect::<Vec<_>>();
287 let elements =
288 lc.0.iter()
289 .map(|weighted| self.group_elements.get(weighted.term.elem))
290 .collect::<Result<Vec<_>, Error>>()?;
291 Ok(msm_pr(&weighted_coefficients, &elements))
292 })
293 .collect()
294 }
295}
296
297/// A wrapper struct coupling a [`LinearMap`] with the corresponding expected output (image) elements.
298///
299/// This structure represents the *preimage problem* for a group linear map: given a set of scalar inputs,
300/// determine whether their image under the linear map matches a target set of group elements.
301///
302/// Internally, the constraint system is defined through:
303/// - A list of group elements and linear equations (held in the [`LinearMap`] field),
304/// - A list of [`GroupVar`] indices (`image`) that specify the expected output for each constraint.
305#[derive(Clone, Default, Debug)]
306pub struct LinearRelation<G>
307where
308 G: Group + GroupEncoding,
309{
310 /// The underlying linear map describing the structure of the statement.
311 pub linear_map: LinearMap<G>,
312 /// Indices pointing to elements representing the "target" images for each constraint.
313 pub image: Vec<GroupVar<G>>,
314}
315
316/// A normalized form of the [LinearRelation], which is used for serialization into the transcript.
317// NOTE: This is not intended to be exposed beyond this module.
318#[derive(Clone)]
319struct LinearRelationRepr<G: GroupEncoding> {
320 constraints: Vec<(u32, Vec<(u32, u32)>)>,
321 group_elements: Vec<G::Repr>,
322}
323
324impl<G: GroupEncoding> Default for LinearRelationRepr<G> {
325 fn default() -> Self {
326 Self {
327 constraints: Default::default(),
328 group_elements: Default::default(),
329 }
330 }
331}
332
333// A utility struct used to build the LinearRelationRepr.
334#[derive(Clone)]
335struct LinearRelationReprBuilder<G: Group + GroupEncoding> {
336 repr: LinearRelationRepr<G>,
337 /// Mapping from the serialized group representation to its index in the repr.
338 /// Acts as a reverse index into the group_elements.
339 group_repr_mapping: HashMap<Box<[u8]>, u32>,
340 /// A mapping from GroupVar index and weight to repr index, to avoid recomputing the scalar mul
341 /// of the group element multiple times.
342 weighted_group_cache: HashMap<GroupVar<G>, Vec<(G::Scalar, u32)>>,
343}
344
345impl<G: Group + GroupEncoding> Default for LinearRelationReprBuilder<G> {
346 fn default() -> Self {
347 Self {
348 repr: Default::default(),
349 group_repr_mapping: Default::default(),
350 weighted_group_cache: Default::default(),
351 }
352 }
353}
354
355impl<G: Group + GroupEncoding> LinearRelationReprBuilder<G> {
356 fn repr_index(&mut self, elem: &G::Repr) -> u32 {
357 if let Some(index) = self.group_repr_mapping.get(elem.as_ref()) {
358 return *index;
359 }
360
361 let new_index = self.repr.group_elements.len() as u32;
362 self.repr.group_elements.push(*elem);
363 self.group_repr_mapping
364 .insert(elem.as_ref().into(), new_index);
365 new_index
366 }
367
368 fn weighted_group_var_index(&mut self, var: GroupVar<G>, weight: &G::Scalar, elem: &G) -> u32 {
369 let entry = self.weighted_group_cache.entry(var).or_default();
370
371 // If the (weight, group_var) pair is already in the cache, use it.
372 if let Some(index) = entry
373 .iter()
374 .find_map(|(entry_weight, index)| (weight == entry_weight).then_some(index))
375 {
376 return *index;
377 }
378
379 // Compute the scalar mul of the element and the weight, then the representation.
380 let weighted_elem_repr = (*elem * weight).to_bytes();
381 // Lookup or assign the index to the representation.
382 let index = self.repr_index(&weighted_elem_repr);
383
384 // Add the index to the cache.
385 // NOTE: entry is dropped earlier to satisfy borrow-check rules.
386 self.weighted_group_cache
387 .get_mut(&var)
388 .unwrap()
389 .push((*weight, index));
390
391 index
392 }
393
394 fn finalize(self) -> LinearRelationRepr<G> {
395 self.repr
396 }
397}
398
399impl<G> LinearRelation<G>
400where
401 G: Group + GroupEncoding,
402{
403 /// Create a new empty [`LinearRelation`].
404 pub fn new() -> Self {
405 Self {
406 linear_map: LinearMap::new(),
407 image: Vec::new(),
408 }
409 }
410
411 /// Adds a new equation to the statement of the form:
412 /// `lhs = Σ weight_i * (scalar_i * point_i)`.
413 ///
414 /// # Parameters
415 /// - `lhs`: The image group element variable (left-hand side of the equation).
416 /// - `rhs`: An instance of [`LinearCombination`] representing the linear combination on the right-hand side.
417 pub fn append_equation(&mut self, lhs: GroupVar<G>, rhs: impl Into<LinearCombination<G>>) {
418 self.linear_map.append(rhs.into());
419 self.image.push(lhs);
420 }
421
422 /// Adds a new equation to the statement of the form:
423 /// `lhs = Σ weight_i * (scalar_i * point_i)` without allocating `lhs`.
424 ///
425 /// # Parameters
426 /// - `rhs`: An instance of [`LinearCombination`] representing the linear combination on the right-hand side.
427 pub fn allocate_eq(&mut self, rhs: impl Into<LinearCombination<G>>) -> GroupVar<G> {
428 let var = self.allocate_element();
429 self.append_equation(var, rhs);
430 var
431 }
432
433 /// Allocates a scalar variable for use in the linear map.
434 pub fn allocate_scalar(&mut self) -> ScalarVar<G> {
435 self.linear_map.num_scalars += 1;
436 ScalarVar(self.linear_map.num_scalars - 1, PhantomData)
437 }
438
439 /// Allocates space for `N` new scalar variables.
440 ///
441 /// # Returns
442 /// An array of [`ScalarVar`] representing the newly allocated scalar indices.
443 ///
444 /// # Example
445 /// ```
446 /// # use sigma_rs::LinearRelation;
447 /// use curve25519_dalek::RistrettoPoint as G;
448 ///
449 /// let mut relation = LinearRelation::<G>::new();
450 /// let [var_x, var_y] = relation.allocate_scalars();
451 /// let vars = relation.allocate_scalars::<10>();
452 /// ```
453 pub fn allocate_scalars<const N: usize>(&mut self) -> [ScalarVar<G>; N] {
454 let mut vars = [ScalarVar(usize::MAX, PhantomData); N];
455 for var in vars.iter_mut() {
456 *var = self.allocate_scalar();
457 }
458 vars
459 }
460
461 /// Allocates a point variable (group element) for use in the linear map.
462 pub fn allocate_element(&mut self) -> GroupVar<G> {
463 self.linear_map.num_elements += 1;
464 GroupVar(self.linear_map.num_elements - 1, PhantomData)
465 }
466
467 /// Allocates `N` point variables (group elements) for use in the linear map.
468 ///
469 /// # Returns
470 /// An array of [`GroupVar`] representing the newly allocated group element indices.
471 ///
472 /// # Example
473 /// ```
474 /// # use sigma_rs::LinearRelation;
475 /// use curve25519_dalek::RistrettoPoint as G;
476 ///
477 /// let mut relation = LinearRelation::<G>::new();
478 /// let [var_g, var_h] = relation.allocate_elements();
479 /// let vars = relation.allocate_elements::<10>();
480 /// ```
481 pub fn allocate_elements<const N: usize>(&mut self) -> [GroupVar<G>; N] {
482 let mut vars = [GroupVar(usize::MAX, PhantomData); N];
483 for var in vars.iter_mut() {
484 *var = self.allocate_element();
485 }
486 vars
487 }
488
489 /// Assign a group element value to a point variable.
490 ///
491 /// # Parameters
492 ///
493 /// - `var`: The variable to assign.
494 /// - `element`: The value to assign to the variable.
495 ///
496 /// # Panics
497 ///
498 /// Panics if the given assignment conflicts with the existing assignment.
499 pub fn set_element(&mut self, var: GroupVar<G>, element: G) {
500 self.linear_map.group_elements.assign_element(var, element)
501 }
502
503 /// Assigns specific group elements to point variables (indices).
504 ///
505 /// # Parameters
506 ///
507 /// - `assignments`: A collection of `(GroupVar, GroupElement)` pairs that can be iterated over.
508 ///
509 /// # Panics
510 ///
511 /// Panics if the collection contains two conflicting assignments for the same variable.
512 pub fn set_elements(&mut self, assignments: impl IntoIterator<Item = (GroupVar<G>, G)>) {
513 self.linear_map.group_elements.assign_elements(assignments)
514 }
515
516 /// Evaluates all linear combinations in the linear map with the provided scalars, computing the
517 /// left-hand side of this constraints (i.e. the image).
518 ///
519 /// After calling this function, all point variables will be assigned.
520 ///
521 /// # Parameters
522 ///
523 /// - `scalars`: A slice of scalar values corresponding to the scalar variables.
524 ///
525 /// # Returns
526 ///
527 /// Return `Ok` on success, and an error if unassigned elements prevent the image from being
528 /// computed. Modifies the group elements assigned in the [LinearRelation].
529 pub fn compute_image(&mut self, scalars: &[<G as Group>::Scalar]) -> Result<(), Error> {
530 if self.linear_map.num_constraints() != self.image.len() {
531 // NOTE: This is a panic, rather than a returned error, because this can only happen if
532 // this implementation has a bug.
533 panic!("invalid LinearRelation: different number of constraints and image variables");
534 }
535
536 for (lc, lhs) in iter::zip(
537 self.linear_map.constraints.as_slice(),
538 self.image.as_slice(),
539 ) {
540 // TODO: The multiplication by the (public) weight is potentially wasteful in the
541 // weight is most commonly 1, but multiplication is constant time.
542 let weighted_coefficients =
543 lc.0.iter()
544 .map(|weighted| weighted.term.scalar.value(scalars) * weighted.weight)
545 .collect::<Vec<_>>();
546 let elements =
547 lc.0.iter()
548 .map(|weighted| self.linear_map.group_elements.get(weighted.term.elem))
549 .collect::<Result<Vec<_>, Error>>()?;
550 self.linear_map
551 .group_elements
552 .assign_element(*lhs, msm_pr(&weighted_coefficients, &elements))
553 }
554 Ok(())
555 }
556
557 /// Returns the current group elements corresponding to the image variables.
558 ///
559 /// # Returns
560 ///
561 /// A vector of group elements (`Vec<G>`) representing the linear map's image.
562 // TODO: Should this return GroupMap?
563 pub fn image(&self) -> Result<Vec<G>, Error> {
564 self.image
565 .iter()
566 .map(|&var| self.linear_map.group_elements.get(var))
567 .collect()
568 }
569
570 /// Returns a binary label describing the linear map.
571 ///
572 /// The format is:
573 /// - [Ne: u32] number of equations
574 /// - For each equation:
575 /// - [output_point_index: u32]
576 /// - [Nt: u32] number of terms
577 /// - Nt × [scalar_index: u32, point_index: u32] term entries
578 pub fn label(&self) -> Vec<u8> {
579 let mut out = Vec::new();
580 // XXX. We should return an error if the group elements are not assigned, instead of panicking.
581 let repr = self.standard_repr().unwrap();
582
583 // 1. Number of equations
584 let ne = repr.constraints.len();
585 out.extend_from_slice(&(ne as u32).to_le_bytes());
586
587 // 2. Encode each equation
588 for (output_index, constraint) in repr.constraints {
589 // a. Output point index (LHS)
590 out.extend_from_slice(&output_index.to_le_bytes());
591
592 // b. Number of terms in the RHS linear combination
593 out.extend_from_slice(&(constraint.len() as u32).to_le_bytes());
594
595 // c. Each term: scalar index and point index
596 for (scalar_index, group_index) in constraint {
597 out.extend_from_slice(&scalar_index.to_le_bytes());
598 out.extend_from_slice(&group_index.to_le_bytes());
599 }
600 }
601
602 // Dump the group elements.
603 // TODO batch serialization of group elements should not require allocation of a new vector in this case and should be part of a Group trait.
604 for elem in repr.group_elements {
605 out.extend_from_slice(elem.as_ref());
606 }
607
608 out
609 }
610
611 /// Construct an equivalent linear relation in the standardized form, without weights and with
612 /// a single group var on the left-hand side.
613 fn standard_repr(&self) -> Result<LinearRelationRepr<G>, Error> {
614 assert_eq!(
615 self.image.len(),
616 self.linear_map.constraints.len(),
617 "Number of equations and image variables must match"
618 );
619
620 let mut repr_builder = LinearRelationReprBuilder::default();
621
622 // Iterate through the constraints, applying to remapping to weighed group variables and
623 // casting scalar vars to u32.
624 for (image_var, equation) in iter::zip(&self.image, &self.linear_map.constraints) {
625 // Construct the right-hand side, omitting any terms that no not include a scalar, as
626 // they will be moved to the left-hand side.
627 let rhs: Vec<(u32, u32)> = equation
628 .terms()
629 .iter()
630 .filter_map(|weighted_term| match weighted_term.term.scalar {
631 ScalarTerm::Var(var) => {
632 Some((var, weighted_term.term.elem, weighted_term.weight))
633 }
634 ScalarTerm::Unit => None,
635 })
636 .map(|(scalar_var, group_var, weight)| {
637 let group_val = self.linear_map.group_elements.get(group_var)?;
638 let group_index =
639 repr_builder.weighted_group_var_index(group_var, &weight, &group_val);
640 Ok((scalar_var.0 as u32, group_index))
641 })
642 .collect::<Result<_, _>>()?;
643
644 // Construct the left-hand side, subtracting all the terms on the right that don't have
645 // a variable scalar term.
646 let image_val = self.linear_map.group_elements.get(*image_var)?;
647 let lhs_val = equation
648 .terms()
649 .iter()
650 .filter_map(|weighted_term| match weighted_term.term.scalar {
651 ScalarTerm::Unit => Some((weighted_term.term.elem, weighted_term.weight)),
652 ScalarTerm::Var(_) => None,
653 })
654 .try_fold(image_val, |sum, (group_var, weight)| {
655 let group_val = self.linear_map.group_elements.get(group_var)?;
656 Ok(sum - group_val * weight)
657 })?;
658 let lhs_index = repr_builder.repr_index(&lhs_val.to_bytes());
659
660 repr_builder.repr.constraints.push((lhs_index, rhs));
661 }
662
663 Ok(repr_builder.finalize())
664 }
665
666 /// Convert this LinearRelation into a non-interactive zero-knowledge protocol
667 /// using the ShakeCodec and a specified context/domain separator.
668 ///
669 /// # Parameters
670 /// - `context`: Domain separator bytes for the Fiat-Shamir transform
671 ///
672 /// # Returns
673 /// A `NISigmaProtocol` instance ready for proving and verification
674 ///
675 /// # Example
676 /// ```
677 /// # use sigma_rs::{LinearRelation, NISigmaProtocol};
678 /// # use curve25519_dalek::RistrettoPoint as G;
679 /// # use curve25519_dalek::scalar::Scalar;
680 /// # use rand::rngs::OsRng;
681 /// # use group::Group;
682 ///
683 /// let mut relation = LinearRelation::<G>::new();
684 /// let x_var = relation.allocate_scalar();
685 /// let g_var = relation.allocate_element();
686 /// let p_var = relation.allocate_eq(x_var * g_var);
687 ///
688 /// relation.set_element(g_var, G::generator());
689 /// let x = Scalar::random(&mut OsRng);
690 /// relation.compute_image(&[x]).unwrap();
691 ///
692 /// // Convert to NIZK with custom context
693 /// let nizk = relation.into_nizk(b"my-protocol-v1");
694 /// let proof = nizk.prove_batchable(&vec![x], &mut OsRng).unwrap();
695 /// assert!(nizk.verify_batchable(&proof).is_ok());
696 /// ```
697 pub fn into_nizk(
698 self,
699 session_identifier: &[u8],
700 ) -> NISigmaProtocol<SchnorrProof<G>, ShakeCodec<G>>
701 where
702 G: group::GroupEncoding,
703 {
704 let schnorr = SchnorrProof::from(self);
705 NISigmaProtocol::new(session_identifier, schnorr)
706 }
707}