halo2_base/utils/
mod.rs

1use core::hash::Hash;
2
3use crate::ff::{FromUniformBytes, PrimeField};
4#[cfg(not(feature = "halo2-axiom"))]
5use crate::halo2_proofs::arithmetic::CurveAffine;
6use crate::halo2_proofs::circuit::Value;
7#[cfg(feature = "halo2-axiom")]
8pub use crate::halo2_proofs::halo2curves::CurveAffineExt;
9
10use num_bigint::BigInt;
11use num_bigint::BigUint;
12use num_bigint::Sign;
13use num_traits::Signed;
14use num_traits::{One, Zero};
15
16/// Helper functions for raw halo2 operations to unify slight differences in API for halo2-axiom and halo2-pse
17pub mod halo2;
18#[cfg(any(test, feature = "test-utils"))]
19pub mod testing;
20
21/// Helper trait to convert to and from a [BigPrimeField] by converting a list of [u64] digits
22#[cfg(feature = "halo2-axiom")]
23pub trait BigPrimeField: ScalarField {
24    /// Converts a slice of [u64] to [BigPrimeField]
25    /// * `val`: the slice of u64
26    ///
27    /// # Assumptions
28    /// * `val` has the correct length for the implementation
29    /// * The integer value of `val` is already less than the modulus of `Self`
30    fn from_u64_digits(val: &[u64]) -> Self;
31}
32#[cfg(feature = "halo2-axiom")]
33impl<F> BigPrimeField for F
34where
35    F: ScalarField + From<[u64; 4]>, // Assume [u64; 4] is little-endian. We only implement ScalarField when this is true.
36{
37    #[inline(always)]
38    fn from_u64_digits(val: &[u64]) -> Self {
39        debug_assert!(val.len() <= 4);
40        let mut raw = [0u64; 4];
41        raw[..val.len()].copy_from_slice(val);
42        Self::from(raw)
43    }
44}
45
46/// Helper trait to represent a field element that can be converted into [u64] limbs.
47///
48/// Note: Since the number of bits necessary to represent a field element is larger than the number of bits in a u64, we decompose the integer representation of the field element into multiple [u64] values e.g. `limbs`.
49pub trait ScalarField: PrimeField + FromUniformBytes<64> + From<bool> + Hash + Ord {
50    /// Returns the base `2<sup>bit_len</sup>` little endian representation of the [ScalarField] element up to `num_limbs` number of limbs (truncates any extra limbs).
51    ///
52    /// Assumes `bit_len < 64`.
53    /// * `num_limbs`: number of limbs to return
54    /// * `bit_len`: number of bits in each limb
55    fn to_u64_limbs(self, num_limbs: usize, bit_len: usize) -> Vec<u64>;
56
57    /// Returns the little endian byte representation of the element.
58    fn to_bytes_le(&self) -> Vec<u8>;
59
60    /// Creates a field element from a little endian byte representation.
61    ///
62    /// The default implementation assumes that `PrimeField::from_repr` is implemented for little-endian.
63    /// It should be overriden if this is not the case.
64    fn from_bytes_le(bytes: &[u8]) -> Self {
65        let mut repr = Self::Repr::default();
66        repr.as_mut()[..bytes.len()].copy_from_slice(bytes);
67        Self::from_repr(repr).unwrap()
68    }
69
70    /// Gets the least significant 32 bits of the field element.
71    fn get_lower_32(&self) -> u32 {
72        let bytes = self.to_bytes_le();
73        let mut lower_32 = 0u32;
74        for (i, byte) in bytes.into_iter().enumerate().take(4) {
75            lower_32 |= (byte as u32) << (i * 8);
76        }
77        lower_32
78    }
79
80    /// Gets the least significant 64 bits of the field element.
81    fn get_lower_64(&self) -> u64 {
82        let bytes = self.to_bytes_le();
83        let mut lower_64 = 0u64;
84        for (i, byte) in bytes.into_iter().enumerate().take(8) {
85            lower_64 |= (byte as u64) << (i * 8);
86        }
87        lower_64
88    }
89}
90// See below for implementations
91
92// Later: will need to separate BigPrimeField from ScalarField when Goldilocks is introduced
93
94/// [ScalarField] that is ~256 bits long
95#[cfg(feature = "halo2-pse")]
96pub trait BigPrimeField = PrimeField<Repr = [u8; 32]> + ScalarField;
97
98/// Converts an [Iterator] of u64 digits into `number_of_limbs` limbs of `bit_len` bits returned as a [Vec].
99///
100/// Assumes: `bit_len < 64`.
101/// * `e`: Iterator of [u64] digits
102/// * `number_of_limbs`: number of limbs to return
103/// * `bit_len`: number of bits in each limb
104#[inline(always)]
105pub(crate) fn decompose_u64_digits_to_limbs(
106    e: impl IntoIterator<Item = u64>,
107    number_of_limbs: usize,
108    bit_len: usize,
109) -> Vec<u64> {
110    debug_assert!(bit_len < 64);
111
112    let mut e = e.into_iter();
113    // Mask to extract the bits from each digit
114    let mask: u64 = (1u64 << bit_len) - 1u64;
115    let mut u64_digit = e.next().unwrap_or(0);
116    let mut rem = 64;
117
118    // For each digit, we extract its individual limbs by repeatedly masking and shifting the digit based on how many bits we have left to extract.
119    (0..number_of_limbs)
120        .map(|_| match rem.cmp(&bit_len) {
121            // If `rem` > `bit_len`, we mask the bits from the `u64_digit` to return the first limb.
122            // We shift the digit to the right by `bit_len` bits and subtract `bit_len` from `rem`
123            core::cmp::Ordering::Greater => {
124                let limb = u64_digit & mask;
125                u64_digit >>= bit_len;
126                rem -= bit_len;
127                limb
128            }
129            // If `rem` == `bit_len`, then we mask the bits from the `u64_digit` to return the first limb
130            // We retrieve the next digit and reset `rem` to 64
131            core::cmp::Ordering::Equal => {
132                let limb = u64_digit & mask;
133                u64_digit = e.next().unwrap_or(0);
134                rem = 64;
135                limb
136            }
137            // If `rem` < `bit_len`, we retrieve the next digit, mask it, and shift left `rem` bits from the `u64_digit` to return the first limb.
138            // we shift the digit to the right by `bit_len` - `rem` bits to retrieve the start of the next limb and add 64 - bit_len to `rem` to get the remainder.
139            core::cmp::Ordering::Less => {
140                let mut limb = u64_digit;
141                u64_digit = e.next().unwrap_or(0);
142                limb |= (u64_digit & ((1u64 << (bit_len - rem)) - 1u64)) << rem;
143                u64_digit >>= bit_len - rem;
144                rem += 64 - bit_len;
145                limb
146            }
147        })
148        .collect()
149}
150
151/// Returns the number of bits needed to represent the value of `x`.
152pub const fn bit_length(x: u64) -> usize {
153    (u64::BITS - x.leading_zeros()) as usize
154}
155
156/// Returns the ceiling of the base 2 logarithm of `x`.
157///
158/// `log2_ceil(0)` returns 0.
159pub fn log2_ceil(x: u64) -> usize {
160    (u64::BITS - x.leading_zeros()) as usize - usize::from(x.is_power_of_two())
161}
162
163/// Returns the modulus of [BigPrimeField].
164pub fn modulus<F: BigPrimeField>() -> BigUint {
165    fe_to_biguint(&-F::ONE) + 1u64
166}
167
168/// Returns the [BigPrimeField] element of 2<sup>n</sup>.
169/// * `n`: the desired power of 2.
170pub fn power_of_two<F: BigPrimeField>(n: usize) -> F {
171    biguint_to_fe(&(BigUint::one() << n))
172}
173
174/// Converts an immutable reference to [BigUint] to a [BigPrimeField].
175/// * `e`: immutable reference to [BigUint]
176///
177/// # Assumptions:
178/// * `e` is less than the modulus of `F`
179pub fn biguint_to_fe<F: BigPrimeField>(e: &BigUint) -> F {
180    #[cfg(feature = "halo2-axiom")]
181    {
182        F::from_u64_digits(&e.to_u64_digits())
183    }
184
185    #[cfg(feature = "halo2-pse")]
186    {
187        let bytes = e.to_bytes_le();
188        F::from_bytes_le(&bytes)
189    }
190}
191
192/// Converts an immutable reference to [BigInt] to a [BigPrimeField].
193/// * `e`: immutable reference to [BigInt]
194///
195/// # Assumptions:
196/// * The absolute value of `e` is less than the modulus of `F`
197pub fn bigint_to_fe<F: BigPrimeField>(e: &BigInt) -> F {
198    #[cfg(feature = "halo2-axiom")]
199    {
200        let (sign, digits) = e.to_u64_digits();
201        if sign == Sign::Minus {
202            -F::from_u64_digits(&digits)
203        } else {
204            F::from_u64_digits(&digits)
205        }
206    }
207    #[cfg(feature = "halo2-pse")]
208    {
209        let (sign, bytes) = e.to_bytes_le();
210        let f_abs = F::from_bytes_le(&bytes);
211        if sign == Sign::Minus {
212            -f_abs
213        } else {
214            f_abs
215        }
216    }
217}
218
219/// Converts an immutable reference to an PrimeField element into a [BigUint] element.
220/// * `fe`: immutable reference to PrimeField element to convert
221pub fn fe_to_biguint<F: ScalarField>(fe: &F) -> BigUint {
222    BigUint::from_bytes_le(fe.to_bytes_le().as_ref())
223}
224
225/// Converts a [BigPrimeField] element into a [BigInt] element by sending `fe` in `[0, F::modulus())` to
226/// ```ignore
227/// fe,                 if fe < F::modulus() / 2
228/// fe - F::modulus(),  otherwise
229/// ```
230pub fn fe_to_bigint<F: BigPrimeField>(fe: &F) -> BigInt {
231    // TODO: `F` should just have modulus as lazy_static or something
232    let modulus = modulus::<F>();
233    let e = fe_to_biguint(fe);
234    if e <= &modulus / 2u32 {
235        BigInt::from_biguint(Sign::Plus, e)
236    } else {
237        BigInt::from_biguint(Sign::Minus, modulus - e)
238    }
239}
240
241/// Decomposes an immutable reference to a [BigPrimeField] element into `number_of_limbs` limbs of `bit_len` bits each and returns a [Vec] of [BigPrimeField] represented by those limbs.
242///
243/// Assumes `bit_len < 128`.
244/// * `e`: immutable reference to [BigPrimeField] element to decompose
245/// * `number_of_limbs`: number of limbs to decompose `e` into
246/// * `bit_len`: number of bits in each limb
247pub fn decompose<F: BigPrimeField>(e: &F, number_of_limbs: usize, bit_len: usize) -> Vec<F> {
248    if bit_len > 64 {
249        decompose_biguint(&fe_to_biguint(e), number_of_limbs, bit_len)
250    } else {
251        decompose_fe_to_u64_limbs(e, number_of_limbs, bit_len).into_iter().map(F::from).collect()
252    }
253}
254
255/// Decomposes an immutable reference to a [ScalarField] element into `number_of_limbs` limbs of `bit_len` bits each and returns a [Vec] of [u64] represented by those limbs.
256///
257/// Assumes `bit_len` < 64
258/// * `e`: immutable reference to [ScalarField] element to decompose
259/// * `number_of_limbs`: number of limbs to decompose `e` into
260/// * `bit_len`: number of bits in each limb
261pub fn decompose_fe_to_u64_limbs<F: ScalarField>(
262    e: &F,
263    number_of_limbs: usize,
264    bit_len: usize,
265) -> Vec<u64> {
266    #[cfg(feature = "halo2-axiom")]
267    {
268        e.to_u64_limbs(number_of_limbs, bit_len)
269    }
270
271    #[cfg(feature = "halo2-pse")]
272    {
273        decompose_u64_digits_to_limbs(fe_to_biguint(e).iter_u64_digits(), number_of_limbs, bit_len)
274    }
275}
276
277/// Decomposes an immutable reference to a [BigUint] into `num_limbs` limbs of `bit_len` bits each and returns a [Vec] of [BigPrimeField] represented by those limbs.
278///
279/// Assumes 64 <= `bit_len` < 128.
280/// * `e`: immutable reference to [BigInt] to decompose
281/// * `num_limbs`: number of limbs to decompose `e` into
282/// * `bit_len`: number of bits in each limb
283///
284/// Truncates to `num_limbs` limbs if `e` is too large.
285pub fn decompose_biguint<F: BigPrimeField>(
286    e: &BigUint,
287    num_limbs: usize,
288    bit_len: usize,
289) -> Vec<F> {
290    // bit_len must be between 64` and 128
291    debug_assert!((64..128).contains(&bit_len));
292    let mut e = e.iter_u64_digits();
293
294    // Grab first 128-bit limb from iterator
295    let mut limb0 = e.next().unwrap_or(0) as u128;
296    let mut rem = bit_len - 64;
297    let mut u64_digit = e.next().unwrap_or(0);
298    // Extract second limb (bit length 64) from e
299    limb0 |= ((u64_digit & ((1u64 << rem) - 1u64)) as u128) << 64u32;
300    u64_digit >>= rem;
301    rem = 64 - rem;
302
303    // Convert `limb0` into field element `F` and create an iterator by chaining `limb0` with the computing the remaining limbs
304    core::iter::once(F::from_u128(limb0))
305        .chain((1..num_limbs).map(|_| {
306            let mut limb = u64_digit as u128;
307            let mut bits = rem;
308            u64_digit = e.next().unwrap_or(0);
309            if bit_len >= 64 + bits {
310                limb |= (u64_digit as u128) << bits;
311                u64_digit = e.next().unwrap_or(0);
312                bits += 64;
313            }
314            rem = bit_len - bits;
315            limb |= ((u64_digit & ((1u64 << rem) - 1u64)) as u128) << bits;
316            u64_digit >>= rem;
317            rem = 64 - rem;
318            F::from_u128(limb)
319        }))
320        .collect()
321}
322
323/// Decomposes an immutable reference to a [BigInt] into `num_limbs` limbs of `bit_len` bits each and returns a [Vec] of [BigPrimeField] represented by those limbs.
324///
325/// Assumes `bit_len < 128`.
326/// * `e`: immutable reference to `BigInt` to decompose
327/// * `num_limbs`: number of limbs to decompose `e` into
328/// * `bit_len`: number of bits in each limb
329pub fn decompose_bigint<F: BigPrimeField>(e: &BigInt, num_limbs: usize, bit_len: usize) -> Vec<F> {
330    if e.is_negative() {
331        decompose_biguint::<F>(e.magnitude(), num_limbs, bit_len).into_iter().map(|x| -x).collect()
332    } else {
333        decompose_biguint(e.magnitude(), num_limbs, bit_len)
334    }
335}
336
337/// Decomposes an immutable reference to a [BigInt] into `num_limbs` limbs of `bit_len` bits each and returns a [Vec] of [BigPrimeField] represented by those limbs wrapped in [Value].
338///
339/// Assumes `bit_len` < 128.
340/// * `e`: immutable reference to `BigInt` to decompose
341/// * `num_limbs`: number of limbs to decompose `e` into
342/// * `bit_len`: number of bits in each limb
343pub fn decompose_bigint_option<F: BigPrimeField>(
344    value: Value<&BigInt>,
345    number_of_limbs: usize,
346    bit_len: usize,
347) -> Vec<Value<F>> {
348    value.map(|e| decompose_bigint(e, number_of_limbs, bit_len)).transpose_vec(number_of_limbs)
349}
350
351/// Wraps the internal value of `value` in an [Option].
352/// If the value is [None], then the function returns [None].
353/// * `value`: Value to convert.
354pub fn value_to_option<V>(value: Value<V>) -> Option<V> {
355    let mut v = None;
356    value.map(|val| {
357        v = Some(val);
358    });
359    v
360}
361
362/// Computes the value of an integer by passing as `input` a [Vec] of its limb values and the `bit_len` (bit length) used.
363///
364/// Returns the sum of all limbs scaled by 2<sup>(bit_len * i)</sup> where i is the index of the limb.
365/// * `input`: Limb values of the integer.
366/// * `bit_len`: Length of limb in bits
367pub fn compose(input: Vec<BigUint>, bit_len: usize) -> BigUint {
368    input.iter().rev().fold(BigUint::zero(), |acc, val| (acc << bit_len) + val)
369}
370
371/// Helper trait
372#[cfg(feature = "halo2-pse")]
373pub trait CurveAffineExt: CurveAffine {
374    /// Returns the raw affine (X, Y) coordinantes
375    fn into_coordinates(self) -> (Self::Base, Self::Base) {
376        let coordinates = self.coordinates().unwrap();
377        (*coordinates.x(), *coordinates.y())
378    }
379}
380#[cfg(feature = "halo2-pse")]
381impl<C: CurveAffine> CurveAffineExt for C {}
382
383mod scalar_field_impls {
384    use super::{decompose_u64_digits_to_limbs, ScalarField};
385    #[cfg(feature = "halo2-pse")]
386    use crate::ff::PrimeField;
387    use crate::halo2_proofs::halo2curves::{
388        bn256::{Fq as bn254Fq, Fr as bn254Fr},
389        secp256k1::{Fp as secpFp, Fq as secpFq},
390    };
391
392    /// To ensure `ScalarField` is only implemented for `ff:Field` where `Repr` is little endian, we use the following macro
393    /// to implement the trait for each field.
394    #[cfg(feature = "halo2-axiom")]
395    #[macro_export]
396    macro_rules! impl_scalar_field {
397        ($field:ident) => {
398            impl ScalarField for $field {
399                #[inline(always)]
400                fn to_u64_limbs(self, num_limbs: usize, bit_len: usize) -> Vec<u64> {
401                    // Basically same as `to_repr` but does not go further into bytes
402                    let tmp: [u64; 4] = self.into();
403                    decompose_u64_digits_to_limbs(tmp, num_limbs, bit_len)
404                }
405
406                #[inline(always)]
407                fn to_bytes_le(&self) -> Vec<u8> {
408                    let tmp: [u64; 4] = (*self).into();
409                    tmp.iter().flat_map(|x| x.to_le_bytes()).collect()
410                }
411
412                #[inline(always)]
413                fn get_lower_32(&self) -> u32 {
414                    let tmp: [u64; 4] = (*self).into();
415                    tmp[0] as u32
416                }
417
418                #[inline(always)]
419                fn get_lower_64(&self) -> u64 {
420                    let tmp: [u64; 4] = (*self).into();
421                    tmp[0]
422                }
423            }
424        };
425    }
426
427    /// To ensure `ScalarField` is only implemented for `ff:Field` where `Repr` is little endian, we use the following macro
428    /// to implement the trait for each field.
429    #[cfg(feature = "halo2-pse")]
430    #[macro_export]
431    macro_rules! impl_scalar_field {
432        ($field:ident) => {
433            impl ScalarField for $field {
434                #[inline(always)]
435                fn to_u64_limbs(self, num_limbs: usize, bit_len: usize) -> Vec<u64> {
436                    let bytes = self.to_repr();
437                    let digits = (0..4)
438                        .map(|i| u64::from_le_bytes(bytes[i * 8..(i + 1) * 8].try_into().unwrap()));
439                    decompose_u64_digits_to_limbs(digits, num_limbs, bit_len)
440                }
441
442                #[inline(always)]
443                fn to_bytes_le(&self) -> Vec<u8> {
444                    self.to_repr().to_vec()
445                }
446            }
447        };
448    }
449
450    impl_scalar_field!(bn254Fr);
451    impl_scalar_field!(bn254Fq);
452    impl_scalar_field!(secpFp);
453    impl_scalar_field!(secpFq);
454}
455
456/// Module for reading parameters for Halo2 proving system from the file system.
457pub mod fs {
458    use std::{
459        env::var,
460        fs::{self, File},
461        io::{BufReader, BufWriter},
462    };
463
464    use crate::halo2_proofs::{
465        halo2curves::{
466            bn256::{Bn256, G1Affine},
467            CurveAffine,
468        },
469        poly::{
470            commitment::{Params, ParamsProver},
471            kzg::commitment::ParamsKZG,
472        },
473    };
474    use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng};
475
476    /// Reads the srs from a file found in `./params/kzg_bn254_{k}.srs` or `{dir}/kzg_bn254_{k}.srs` if `PARAMS_DIR` env var is specified.
477    /// * `k`: degree that expresses the size of circuit (i.e., 2^<sup>k</sup> is the number of rows in the circuit)
478    pub fn read_params(k: u32) -> ParamsKZG<Bn256> {
479        let dir = var("PARAMS_DIR").unwrap_or_else(|_| "./params".to_string());
480        ParamsKZG::<Bn256>::read(&mut BufReader::new(
481            File::open(format!("{dir}/kzg_bn254_{k}.srs").as_str())
482                .expect("Params file does not exist"),
483        ))
484        .unwrap()
485    }
486
487    /// Attempts to read the srs from a file found in `./params/kzg_bn254_{k}.srs` or `{dir}/kzg_bn254_{k}.srs` if `PARAMS_DIR` env var is specified, creates a file it if it does not exist.
488    /// * `k`: degree that expresses the size of circuit (i.e., 2^<sup>k</sup> is the number of rows in the circuit)
489    /// * `setup`: a function that creates the srs
490    pub fn read_or_create_srs<'a, C: CurveAffine, P: ParamsProver<'a, C>>(
491        k: u32,
492        setup: impl Fn(u32) -> P,
493    ) -> P {
494        let dir = var("PARAMS_DIR").unwrap_or_else(|_| "./params".to_string());
495        let path = format!("{dir}/kzg_bn254_{k}.srs");
496        match File::open(path.as_str()) {
497            Ok(f) => {
498                #[cfg(feature = "display")]
499                println!("read params from {path}");
500                let mut reader = BufReader::new(f);
501                P::read(&mut reader).unwrap()
502            }
503            Err(_) => {
504                #[cfg(feature = "display")]
505                println!("creating params for {k}");
506                fs::create_dir_all(dir).unwrap();
507                let params = setup(k);
508                params.write(&mut BufWriter::new(File::create(path).unwrap())).unwrap();
509                params
510            }
511        }
512    }
513
514    /// Generates the SRS for the KZG scheme and writes it to a file found in "./params/kzg_bn2_{k}.srs` or `{dir}/kzg_bn254_{k}.srs` if `PARAMS_DIR` env var is specified, creates a file it if it does not exist"
515    /// * `k`: degree that expresses the size of circuit (i.e., 2^<sup>k</sup> is the number of rows in the circuit)
516    pub fn gen_srs(k: u32) -> ParamsKZG<Bn256> {
517        read_or_create_srs::<G1Affine, _>(k, |k| {
518            ParamsKZG::<Bn256>::setup(k, ChaCha20Rng::from_seed(Default::default()))
519        })
520    }
521}
522
523#[cfg(test)]
524mod tests {
525    use crate::halo2_proofs::halo2curves::bn256::Fr;
526    use num_bigint::RandomBits;
527    use rand::{
528        rngs::{OsRng, StdRng},
529        Rng, SeedableRng,
530    };
531    use std::ops::Shl;
532
533    use super::*;
534
535    #[test]
536    fn test_signed_roundtrip() {
537        use crate::halo2_proofs::halo2curves::bn256::Fr;
538        assert_eq!(fe_to_bigint(&bigint_to_fe::<Fr>(&-BigInt::one())), -BigInt::one());
539    }
540
541    #[test]
542    fn test_decompose_biguint() {
543        let mut rng = OsRng;
544        const MAX_LIMBS: u64 = 5;
545        for bit_len in 64..128usize {
546            for num_limbs in 1..=MAX_LIMBS {
547                for _ in 0..10_000usize {
548                    let mut e: BigUint = rng.sample(RandomBits::new(num_limbs * bit_len as u64));
549                    let limbs = decompose_biguint::<Fr>(&e, num_limbs as usize, bit_len);
550
551                    let limbs2 = {
552                        let mut limbs = vec![];
553                        let mask = BigUint::one().shl(bit_len) - 1usize;
554                        for _ in 0..num_limbs {
555                            let limb = &e & &mask;
556                            let mut bytes_le = limb.to_bytes_le();
557                            bytes_le.resize(32, 0u8);
558                            limbs.push(Fr::from_bytes(&bytes_le.try_into().unwrap()).unwrap());
559                            e >>= bit_len;
560                        }
561                        limbs
562                    };
563                    assert_eq!(limbs, limbs2);
564                }
565            }
566        }
567    }
568
569    #[test]
570    fn test_decompose_u64_digits_to_limbs() {
571        let mut rng = OsRng;
572        const MAX_LIMBS: u64 = 5;
573        for bit_len in 0..64usize {
574            for num_limbs in 1..=MAX_LIMBS {
575                for _ in 0..10_000usize {
576                    let mut e: BigUint = rng.sample(RandomBits::new(num_limbs * bit_len as u64));
577                    let limbs = decompose_u64_digits_to_limbs(
578                        e.to_u64_digits(),
579                        num_limbs as usize,
580                        bit_len,
581                    );
582                    let limbs2 = {
583                        let mut limbs = vec![];
584                        let mask = BigUint::one().shl(bit_len) - 1usize;
585                        for _ in 0..num_limbs {
586                            let limb = &e & &mask;
587                            limbs.push(u64::try_from(limb).unwrap());
588                            e >>= bit_len;
589                        }
590                        limbs
591                    };
592                    assert_eq!(limbs, limbs2);
593                }
594            }
595        }
596    }
597
598    #[test]
599    fn test_log2_ceil_zero() {
600        assert_eq!(log2_ceil(0), 0);
601    }
602
603    #[test]
604    fn test_get_lower_32() {
605        let mut rng = StdRng::seed_from_u64(0);
606        for _ in 0..10_000usize {
607            let e: u32 = rng.gen_range(0..u32::MAX);
608            assert_eq!(Fr::from(e as u64).get_lower_32(), e);
609        }
610        assert_eq!(Fr::from((1u64 << 32_i32) + 1).get_lower_32(), 1);
611    }
612
613    #[test]
614    fn test_get_lower_64() {
615        let mut rng = StdRng::seed_from_u64(0);
616        for _ in 0..10_000usize {
617            let e: u64 = rng.gen_range(0..u64::MAX);
618            assert_eq!(Fr::from(e).get_lower_64(), e);
619        }
620    }
621}