Skip to main content

bellpepper_core/gadgets/
num.rs

1//! Gadgets representing numbers in the scalar field of the underlying curve.
2
3use ff::{PrimeField, PrimeFieldBits};
4use serde::{Deserialize, Serialize};
5
6use crate::{ConstraintSystem, LinearCombination, SynthesisError, Variable};
7
8use crate::gadgets::boolean::{self, AllocatedBit, Boolean};
9
10#[derive(Debug, Serialize, Deserialize)]
11pub struct AllocatedNum<Scalar: PrimeField> {
12    value: Option<Scalar>,
13    variable: Variable,
14}
15
16impl<Scalar: PrimeField> Clone for AllocatedNum<Scalar> {
17    fn clone(&self) -> Self {
18        AllocatedNum {
19            value: self.value,
20            variable: self.variable,
21        }
22    }
23}
24
25impl<Scalar: PrimeField> AllocatedNum<Scalar> {
26    /// Allocate a `Variable(Aux)` in a `ConstraintSystem`.
27    pub fn alloc<CS, F>(mut cs: CS, value: F) -> Result<Self, SynthesisError>
28    where
29        CS: ConstraintSystem<Scalar>,
30        F: FnOnce() -> Result<Scalar, SynthesisError>,
31    {
32        let mut new_value = None;
33        let var = cs.alloc(
34            || "num",
35            || {
36                let tmp = value()?;
37
38                new_value = Some(tmp);
39
40                Ok(tmp)
41            },
42        )?;
43
44        Ok(AllocatedNum {
45            value: new_value,
46            variable: var,
47        })
48    }
49
50    /// Allocate a `Variable(Aux)` in a `ConstraintSystem`. Requires an
51    /// infallible getter for the value.
52    pub fn alloc_infallible<CS, F>(cs: CS, value: F) -> Self
53    where
54        CS: ConstraintSystem<Scalar>,
55        F: FnOnce() -> Scalar,
56    {
57        Self::alloc(cs, || Ok(value())).unwrap()
58    }
59
60    /// Allocate a `Variable(Input)` in a `ConstraintSystem`.
61    pub fn alloc_input<CS, F>(mut cs: CS, value: F) -> Result<Self, SynthesisError>
62    where
63        CS: ConstraintSystem<Scalar>,
64        F: FnOnce() -> Result<Scalar, SynthesisError>,
65    {
66        let mut new_value = None;
67        let var = cs.alloc_input(
68            || "input num",
69            || {
70                let tmp = value()?;
71
72                new_value = Some(tmp);
73
74                Ok(tmp)
75            },
76        )?;
77
78        Ok(AllocatedNum {
79            value: new_value,
80            variable: var,
81        })
82    }
83
84    /// Allocate a `Variable` of either `Aux` or `Input` in a
85    /// `ConstraintSystem`. The `Variable` is a an `Input` if `is_input` is
86    /// true. This allows uniform creation of circuits containing components
87    /// which may or may not be public inputs.
88    pub fn alloc_maybe_input<CS, F>(
89        cs: CS,
90        is_input: bool,
91        value: F,
92    ) -> Result<Self, SynthesisError>
93    where
94        CS: ConstraintSystem<Scalar>,
95        F: FnOnce() -> Result<Scalar, SynthesisError>,
96    {
97        if is_input {
98            Self::alloc_input(cs, value)
99        } else {
100            Self::alloc(cs, value)
101        }
102    }
103
104    pub fn inputize<CS>(&self, mut cs: CS) -> Result<(), SynthesisError>
105    where
106        CS: ConstraintSystem<Scalar>,
107    {
108        let input = cs.alloc_input(
109            || "input variable",
110            || self.value.ok_or(SynthesisError::AssignmentMissing),
111        )?;
112
113        cs.enforce(
114            || "enforce input is correct",
115            |lc| lc + input,
116            |lc| lc + CS::one(),
117            |lc| lc + self.variable,
118        );
119
120        Ok(())
121    }
122
123    /// Deconstructs this allocated number into its
124    /// boolean representation in little-endian bit
125    /// order, requiring that the representation
126    /// strictly exists "in the field" (i.e., a
127    /// congruency is not allowed.)
128    pub fn to_bits_le_strict<CS>(&self, mut cs: CS) -> Result<Vec<Boolean>, SynthesisError>
129    where
130        CS: ConstraintSystem<Scalar>,
131        Scalar: PrimeFieldBits,
132    {
133        pub fn kary_and<Scalar, CS>(
134            mut cs: CS,
135            v: &[AllocatedBit],
136        ) -> Result<AllocatedBit, SynthesisError>
137        where
138            Scalar: PrimeField,
139            CS: ConstraintSystem<Scalar>,
140        {
141            assert!(!v.is_empty());
142
143            // Let's keep this simple for now and just AND them all
144            // manually
145            let mut cur = None;
146
147            for (i, v) in v.iter().enumerate() {
148                if cur.is_none() {
149                    cur = Some(v.clone());
150                } else {
151                    cur = Some(AllocatedBit::and(
152                        cs.namespace(|| format!("and {}", i)),
153                        cur.as_ref().unwrap(),
154                        v,
155                    )?);
156                }
157            }
158
159            Ok(cur.expect("v.len() > 0"))
160        }
161
162        // We want to ensure that the bit representation of a is
163        // less than or equal to r - 1.
164        let a = self.value.map(|e| e.to_le_bits());
165        let b = (-Scalar::ONE).to_le_bits();
166
167        // Get the bits of `a` in big-endian order.
168        let mut a = a.as_ref().map(|e| e.into_iter().rev());
169
170        let mut result = vec![];
171
172        // Runs of ones in r
173        let mut last_run = None;
174        let mut current_run = vec![];
175
176        let mut found_one = false;
177        let mut i = 0;
178        for b in b.into_iter().rev() {
179            let a_bit: Option<bool> = a.as_mut().map(|e| *e.next().unwrap());
180
181            // Skip over unset bits at the beginning
182            found_one |= b;
183            if !found_one {
184                // a_bit should also be false
185                if let Some(a_bit) = a_bit {
186                    assert!(!a_bit);
187                }
188                continue;
189            }
190
191            if b {
192                // This is part of a run of ones. Let's just
193                // allocate the boolean with the expected value.
194                let a_bit = AllocatedBit::alloc(cs.namespace(|| format!("bit {}", i)), a_bit)?;
195                // ... and add it to the current run of ones.
196                current_run.push(a_bit.clone());
197                result.push(a_bit);
198            } else {
199                if !current_run.is_empty() {
200                    // This is the start of a run of zeros, but we need
201                    // to k-ary AND against `last_run` first.
202
203                    if last_run.is_some() {
204                        current_run.push(last_run.clone().unwrap());
205                    }
206                    last_run = Some(kary_and(
207                        cs.namespace(|| format!("run ending at {}", i)),
208                        &current_run,
209                    )?);
210                    current_run.truncate(0);
211                }
212
213                // If `last_run` is true, `a` must be false, or it would
214                // not be in the field.
215                //
216                // If `last_run` is false, `a` can be true or false.
217
218                let a_bit = AllocatedBit::alloc_conditionally(
219                    cs.namespace(|| format!("bit {}", i)),
220                    a_bit,
221                    last_run.as_ref().expect("char always starts with a one"),
222                )?;
223                result.push(a_bit);
224            }
225
226            i += 1;
227        }
228
229        // char is prime, so we'll always end on
230        // a run of zeros.
231        assert_eq!(current_run.len(), 0);
232
233        // Now, we have `result` in big-endian order.
234        // However, now we have to unpack self!
235
236        let mut lc = LinearCombination::zero();
237        let mut coeff = Scalar::ONE;
238
239        for bit in result.iter().rev() {
240            lc = lc + (coeff, bit.get_variable());
241
242            coeff = coeff.double();
243        }
244
245        lc = lc - self.variable;
246
247        cs.enforce(|| "unpacking constraint", |lc| lc, |lc| lc, |_| lc);
248
249        // Convert into booleans, and reverse for little-endian bit order
250        Ok(result.into_iter().map(Boolean::from).rev().collect())
251    }
252
253    /// Convert the allocated number into its little-endian representation.
254    /// Note that this does not strongly enforce that the commitment is
255    /// "in the field."
256    pub fn to_bits_le<CS>(&self, mut cs: CS) -> Result<Vec<Boolean>, SynthesisError>
257    where
258        CS: ConstraintSystem<Scalar>,
259        Scalar: PrimeFieldBits,
260    {
261        let bits = boolean::field_into_allocated_bits_le(&mut cs, self.value)?;
262
263        let mut lc = LinearCombination::zero();
264        let mut coeff = Scalar::ONE;
265
266        for bit in bits.iter() {
267            lc = lc + (coeff, bit.get_variable());
268
269            coeff = coeff.double();
270        }
271
272        lc = lc - self.variable;
273
274        cs.enforce(|| "unpacking constraint", |lc| lc, |lc| lc, |_| lc);
275
276        Ok(bits.into_iter().map(Boolean::from).collect())
277    }
278
279    pub fn add<CS>(&self, mut cs: CS, other: &Self) -> Result<Self, SynthesisError>
280    where
281        CS: ConstraintSystem<Scalar>,
282    {
283        let mut value = None;
284
285        let var = cs.alloc(
286            || "sum num",
287            || {
288                let mut tmp = self.value.ok_or(SynthesisError::AssignmentMissing)?;
289                tmp.add_assign(other.value.ok_or(SynthesisError::AssignmentMissing)?);
290
291                value = Some(tmp);
292
293                Ok(tmp)
294            },
295        )?;
296
297        // Constrain: (a + b) * 1 = a + b
298        cs.enforce(
299            || "addition constraint",
300            |lc| lc + self.variable + other.variable,
301            |lc| lc + CS::one(),
302            |lc| lc + var,
303        );
304
305        Ok(AllocatedNum {
306            value,
307            variable: var,
308        })
309    }
310
311    pub fn mul<CS>(&self, mut cs: CS, other: &Self) -> Result<Self, SynthesisError>
312    where
313        CS: ConstraintSystem<Scalar>,
314    {
315        let mut value = None;
316
317        let var = cs.alloc(
318            || "product num",
319            || {
320                let mut tmp = self.value.ok_or(SynthesisError::AssignmentMissing)?;
321                tmp.mul_assign(other.value.ok_or(SynthesisError::AssignmentMissing)?);
322
323                value = Some(tmp);
324
325                Ok(tmp)
326            },
327        )?;
328
329        // Constrain: a * b = ab
330        cs.enforce(
331            || "multiplication constraint",
332            |lc| lc + self.variable,
333            |lc| lc + other.variable,
334            |lc| lc + var,
335        );
336
337        Ok(AllocatedNum {
338            value,
339            variable: var,
340        })
341    }
342
343    pub fn square<CS>(&self, mut cs: CS) -> Result<Self, SynthesisError>
344    where
345        CS: ConstraintSystem<Scalar>,
346    {
347        let mut value = None;
348
349        let var = cs.alloc(
350            || "squared num",
351            || {
352                let mut tmp = self.value.ok_or(SynthesisError::AssignmentMissing)?;
353                tmp = tmp.square();
354
355                value = Some(tmp);
356
357                Ok(tmp)
358            },
359        )?;
360
361        // Constrain: a * a = aa
362        cs.enforce(
363            || "squaring constraint",
364            |lc| lc + self.variable,
365            |lc| lc + self.variable,
366            |lc| lc + var,
367        );
368
369        Ok(AllocatedNum {
370            value,
371            variable: var,
372        })
373    }
374
375    pub fn assert_nonzero<CS>(&self, mut cs: CS) -> Result<(), SynthesisError>
376    where
377        CS: ConstraintSystem<Scalar>,
378    {
379        let inv = cs.alloc(
380            || "ephemeral inverse",
381            || {
382                let tmp = self.value.ok_or(SynthesisError::AssignmentMissing)?;
383
384                if tmp.is_zero().into() {
385                    Err(SynthesisError::DivisionByZero)
386                } else {
387                    Ok(tmp.invert().unwrap())
388                }
389            },
390        )?;
391
392        // Constrain a * inv = 1, which is only valid
393        // iff a has a multiplicative inverse, untrue
394        // for zero.
395        cs.enforce(
396            || "nonzero assertion constraint",
397            |lc| lc + self.variable,
398            |lc| lc + inv,
399            |lc| lc + CS::one(),
400        );
401
402        Ok(())
403    }
404
405    /// Takes two allocated numbers (a, b) and returns
406    /// (b, a) if the condition is true, and (a, b)
407    /// otherwise.
408    pub fn conditionally_reverse<CS>(
409        mut cs: CS,
410        a: &Self,
411        b: &Self,
412        condition: &Boolean,
413    ) -> Result<(Self, Self), SynthesisError>
414    where
415        CS: ConstraintSystem<Scalar>,
416    {
417        let c = Self::alloc(cs.namespace(|| "conditional reversal result 1"), || {
418            if condition
419                .get_value()
420                .ok_or(SynthesisError::AssignmentMissing)?
421            {
422                Ok(b.value.ok_or(SynthesisError::AssignmentMissing)?)
423            } else {
424                Ok(a.value.ok_or(SynthesisError::AssignmentMissing)?)
425            }
426        })?;
427
428        cs.enforce(
429            || "first conditional reversal",
430            |lc| lc + a.variable - b.variable,
431            |_| condition.lc(CS::one(), Scalar::ONE),
432            |lc| lc + a.variable - c.variable,
433        );
434
435        let d = Self::alloc(cs.namespace(|| "conditional reversal result 2"), || {
436            if condition
437                .get_value()
438                .ok_or(SynthesisError::AssignmentMissing)?
439            {
440                Ok(a.value.ok_or(SynthesisError::AssignmentMissing)?)
441            } else {
442                Ok(b.value.ok_or(SynthesisError::AssignmentMissing)?)
443            }
444        })?;
445
446        cs.enforce(
447            || "second conditional reversal",
448            |lc| lc + b.variable - a.variable,
449            |_| condition.lc(CS::one(), Scalar::ONE),
450            |lc| lc + b.variable - d.variable,
451        );
452
453        Ok((c, d))
454    }
455
456    pub fn get_value(&self) -> Option<Scalar> {
457        self.value
458    }
459
460    pub fn get_variable(&self) -> Variable {
461        self.variable
462    }
463}
464
465#[derive(Debug, Clone)]
466pub struct Num<Scalar: PrimeField> {
467    value: Option<Scalar>,
468    lc: LinearCombination<Scalar>,
469}
470
471impl<Scalar: PrimeField> From<AllocatedNum<Scalar>> for Num<Scalar> {
472    fn from(num: AllocatedNum<Scalar>) -> Num<Scalar> {
473        Num {
474            value: num.value,
475            lc: LinearCombination::<Scalar>::from_variable(num.variable),
476        }
477    }
478}
479
480impl<Scalar: PrimeField> Num<Scalar> {
481    pub fn zero() -> Self {
482        Num {
483            value: Some(Scalar::ZERO),
484            lc: LinearCombination::zero(),
485        }
486    }
487
488    pub fn get_value(&self) -> Option<Scalar> {
489        self.value
490    }
491
492    pub fn lc(&self, coeff: Scalar) -> LinearCombination<Scalar> {
493        LinearCombination::zero() + (coeff, &self.lc)
494    }
495
496    pub fn add_bool_with_coeff(self, one: Variable, bit: &Boolean, coeff: Scalar) -> Self {
497        let newval = match (self.value, bit.get_value()) {
498            (Some(mut curval), Some(bval)) => {
499                if bval {
500                    curval.add_assign(&coeff);
501                }
502
503                Some(curval)
504            }
505            _ => None,
506        };
507
508        Num {
509            value: newval,
510            lc: self.lc + &bit.lc(one, coeff),
511        }
512    }
513
514    #[allow(clippy::should_implement_trait)]
515    pub fn add(self, other: &Self) -> Self {
516        let lc = self.lc + &other.lc;
517        let value = match (self.value, other.value) {
518            (Some(v1), Some(v2)) => {
519                let mut tmp = v1;
520                tmp.add_assign(&v2);
521                Some(tmp)
522            }
523            (Some(v), None) | (None, Some(v)) => Some(v),
524            (None, None) => None,
525        };
526
527        Num { value, lc }
528    }
529
530    pub fn scale(mut self, scalar: Scalar) -> Self {
531        for (_variable, fr) in self.lc.iter_mut() {
532            fr.mul_assign(&scalar);
533        }
534
535        if let Some(ref mut v) = self.value {
536            v.mul_assign(&scalar);
537        }
538
539        self
540    }
541}
542
543#[cfg(test)]
544mod test {
545    use std::ops::{AddAssign, MulAssign, SubAssign};
546
547    use crate::ConstraintSystem;
548    use blstrs::Scalar as Fr;
549    use ff::{Field, PrimeField, PrimeFieldBits};
550    use rand_core::SeedableRng;
551    use rand_xorshift::XorShiftRng;
552
553    use super::{AllocatedNum, Boolean, Num};
554    use crate::util_cs::test_cs::*;
555
556    #[test]
557    fn test_allocated_num() {
558        let mut cs = TestConstraintSystem::<Fr>::new();
559
560        AllocatedNum::alloc(&mut cs, || Ok(Fr::ONE)).unwrap();
561
562        assert!(cs.get("num") == Fr::ONE);
563    }
564
565    #[test]
566    fn test_allocated_infallible_num() {
567        let mut cs = TestConstraintSystem::<Fr>::new();
568
569        AllocatedNum::alloc_infallible(&mut cs, || Fr::ONE);
570
571        assert!(cs.get("num") == Fr::ONE);
572    }
573
574    #[test]
575    fn test_num_addition() {
576        let mut cs = TestConstraintSystem::<Fr>::new();
577
578        let mut char = Fr::char();
579        char[0] -= 1u8;
580        let mod_minus_one = Fr::from_repr(char);
581        assert!(bool::from(mod_minus_one.is_some()));
582        let mod_minus_one = mod_minus_one.unwrap();
583
584        let a = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(mod_minus_one)).unwrap();
585        let b = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(Fr::ONE)).unwrap();
586        let c = a.add(&mut cs, &b).unwrap();
587
588        assert!(cs.is_satisfied());
589        assert!(cs.get("sum num") == Fr::ZERO);
590        assert!(c.value.unwrap() == Fr::ZERO);
591        cs.set("sum num", Fr::ONE);
592        assert!(!cs.is_satisfied());
593    }
594
595    #[test]
596    fn test_num_squaring() {
597        let mut cs = TestConstraintSystem::<Fr>::new();
598
599        let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::from(3u64))).unwrap();
600        let n2 = n.square(&mut cs).unwrap();
601
602        assert!(cs.is_satisfied());
603        assert!(cs.get("squared num") == Fr::from(9u64));
604        assert!(n2.value.unwrap() == Fr::from(9u64));
605        cs.set("squared num", Fr::from(10u64));
606        assert!(!cs.is_satisfied());
607    }
608
609    #[test]
610    fn test_num_multiplication() {
611        let mut cs = TestConstraintSystem::<Fr>::new();
612
613        let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::from(12u64))).unwrap();
614        let n2 = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(Fr::from(10u64))).unwrap();
615        let n3 = n.mul(&mut cs, &n2).unwrap();
616
617        assert!(cs.is_satisfied());
618        assert!(cs.get("product num") == Fr::from(120u64));
619        assert!(n3.value.unwrap() == Fr::from(120u64));
620        cs.set("product num", Fr::from(121u64));
621        assert!(!cs.is_satisfied());
622    }
623
624    #[test]
625    fn test_num_conditional_reversal() {
626        let mut rng = XorShiftRng::from_seed([
627            0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
628            0xbc, 0xe5,
629        ]);
630        {
631            let mut cs = TestConstraintSystem::<Fr>::new();
632
633            let a = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::random(&mut rng))).unwrap();
634            let b = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(Fr::random(&mut rng))).unwrap();
635            let condition = Boolean::constant(false);
636            let (c, d) = AllocatedNum::conditionally_reverse(&mut cs, &a, &b, &condition).unwrap();
637
638            assert!(cs.is_satisfied());
639
640            assert_eq!(a.value.unwrap(), c.value.unwrap());
641            assert_eq!(b.value.unwrap(), d.value.unwrap());
642        }
643
644        {
645            let mut cs = TestConstraintSystem::<Fr>::new();
646
647            let a = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::random(&mut rng))).unwrap();
648            let b = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(Fr::random(&mut rng))).unwrap();
649            let condition = Boolean::constant(true);
650            let (c, d) = AllocatedNum::conditionally_reverse(&mut cs, &a, &b, &condition).unwrap();
651
652            assert!(cs.is_satisfied());
653
654            assert_eq!(a.value.unwrap(), d.value.unwrap());
655            assert_eq!(b.value.unwrap(), c.value.unwrap());
656        }
657    }
658
659    #[test]
660    fn test_num_nonzero() {
661        {
662            let mut cs = TestConstraintSystem::<Fr>::new();
663
664            let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::from(3u64))).unwrap();
665            n.assert_nonzero(&mut cs).unwrap();
666
667            assert!(cs.is_satisfied());
668            cs.set("ephemeral inverse", Fr::from(3u64));
669            assert!(cs.which_is_unsatisfied() == Some("nonzero assertion constraint"));
670        }
671        {
672            let mut cs = TestConstraintSystem::<Fr>::new();
673
674            let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::ZERO)).unwrap();
675            assert!(n.assert_nonzero(&mut cs).is_err());
676        }
677    }
678
679    #[test]
680    fn test_into_bits_strict() {
681        let negone = -Fr::ONE;
682
683        let mut cs = TestConstraintSystem::<Fr>::new();
684
685        let n = AllocatedNum::alloc(&mut cs, || Ok(negone)).unwrap();
686        n.to_bits_le_strict(&mut cs).unwrap();
687
688        assert!(cs.is_satisfied());
689
690        // make the bit representation the characteristic
691        cs.set("bit 254/boolean", Fr::ONE);
692
693        // this makes the conditional boolean constraint fail
694        assert_eq!(
695            cs.which_is_unsatisfied().unwrap(),
696            "bit 254/boolean constraint"
697        );
698    }
699
700    #[test]
701    fn test_into_bits() {
702        let mut rng = XorShiftRng::from_seed([
703            0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
704            0xbc, 0xe5,
705        ]);
706
707        for i in 0..200 {
708            let r = Fr::random(&mut rng);
709            let mut cs = TestConstraintSystem::<Fr>::new();
710
711            let n = AllocatedNum::alloc(&mut cs, || Ok(r)).unwrap();
712
713            let bits = if i % 2 == 0 {
714                n.to_bits_le(&mut cs).unwrap()
715            } else {
716                n.to_bits_le_strict(&mut cs).unwrap()
717            };
718
719            assert!(cs.is_satisfied());
720
721            for (i, b) in r.to_le_bits().iter().enumerate() {
722                // `r.to_le_bits()` contains every bit in a representation (including bits which
723                // exceed the field size), whereas the length of `bits` does not exceed the field
724                // size.
725                match bits.get(i) {
726                    Some(Boolean::Is(a)) => assert_eq!(b, a.get_value().unwrap()),
727                    Some(_) => unreachable!(),
728                    None => assert!(!b),
729                };
730            }
731
732            cs.set("num", Fr::random(&mut rng));
733            assert!(!cs.is_satisfied());
734            cs.set("num", r);
735            assert!(cs.is_satisfied());
736
737            for i in 0..Fr::NUM_BITS {
738                let name = format!("bit {}/boolean", i);
739                let cur = cs.get(&name);
740                let mut tmp = Fr::ONE;
741                tmp.sub_assign(&cur);
742                cs.set(&name, tmp);
743                assert!(!cs.is_satisfied());
744                cs.set(&name, cur);
745                assert!(cs.is_satisfied());
746            }
747        }
748    }
749
750    #[test]
751    fn test_num_scale() {
752        use crate::{Index, LinearCombination, Variable};
753
754        let mut rng = XorShiftRng::from_seed([
755            0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
756            0xbc, 0xe5,
757        ]);
758
759        let n = 5;
760
761        let mut lc = LinearCombination::<Fr>::zero();
762
763        let mut expected_sums = vec![Fr::ZERO; n];
764        let mut value = Fr::ZERO;
765        for (i, expected_sum) in expected_sums.iter_mut().enumerate() {
766            let coeff = Fr::random(&mut rng);
767            lc = lc + (coeff, Variable::new_unchecked(Index::Aux(i)));
768            expected_sum.add_assign(&coeff);
769
770            value.add_assign(&coeff);
771        }
772
773        let scalar = Fr::random(&mut rng);
774        let num = Num {
775            value: Some(value),
776            lc,
777        };
778
779        let scaled_num = num.clone().scale(scalar);
780
781        let mut scaled_value = num.value.unwrap();
782        scaled_value.mul_assign(&scalar);
783
784        assert_eq!(scaled_value, scaled_num.value.unwrap());
785
786        // Each variable has the expected coefficient, the sume of those added by its Index.
787        scaled_num.lc.iter().for_each(|(var, coeff)| match var.0 {
788            Index::Aux(i) => {
789                let mut tmp = expected_sums[i];
790                tmp.mul_assign(&scalar);
791                assert_eq!(tmp, *coeff)
792            }
793            _ => panic!("unexpected variable type"),
794        });
795    }
796}