diff --git a/src/bits/boolean.rs b/src/bits/boolean.rs index c3c7dc2..1672d88 100644 --- a/src/bits/boolean.rs +++ b/src/bits/boolean.rs @@ -1,4 +1,4 @@ -use ark_ff::{BitIteratorBE, Field, PrimeField}; +use ark_ff::{BitIteratorBE, Field, FpParameters, PrimeField}; use crate::{fields::fp::FpVar, prelude::*, Assignment, ToConstraintFieldGadget, Vec}; use ark_relations::r1cs::{ @@ -608,6 +608,56 @@ impl Boolean { } } + /// Convert a little-endian bitwise representation of a field element to `FpVar` + #[tracing::instrument(target = "r1cs", skip(bits))] + pub fn le_bits_to_fp_var(bits: &[Self]) -> Result, SynthesisError> + where + F: PrimeField, + { + // Compute the value of the `FpVar` variable via double-and-add. + let mut value = None; + let cs = bits.cs(); + // Assign a value only when `cs` is in setup mode, or if we are constructing + // a constant. + let should_construct_value = (!cs.is_in_setup_mode()) || bits.is_constant(); + if should_construct_value { + let bits = bits.iter().map(|b| b.value().unwrap()).collect::>(); + let bytes = bits + .chunks(8) + .map(|c| { + let mut value = 0u8; + for (i, &bit) in c.iter().enumerate() { + value += (bit as u8) << i; + } + value + }) + .collect::>(); + value = Some(F::from_le_bytes_mod_order(&bytes)); + } + + if bits.is_constant() { + Ok(FpVar::constant(value.unwrap())) + } else { + let mut power = F::one(); + // Compute a linear combination for the new field variable, again + // via double and add. + let mut combined_lc = LinearCombination::zero(); + bits.iter().for_each(|b| { + combined_lc = &combined_lc + (power, b.lc()); + power.double_in_place(); + }); + // Allocate the new variable as a SymbolicLc + let variable = cs.new_lc(combined_lc)?; + // If the number of bits is less than the size of the field, + // then we do not need to enforce that the element is less than + // the modulus. + if bits.len() >= F::Params::MODULUS_BITS as usize { + Self::enforce_in_field_le(bits)?; + } + Ok(crate::fields::fp::AllocatedFp::new(value, variable, cs.clone()).into()) + } + } + /// Enforces that `bits`, when interpreted as a integer, is less than /// `F::characteristic()`, That is, interpret bits as a little-endian /// integer, and enforce that this integer is "in the field Z_p", where @@ -1737,4 +1787,37 @@ mod test { } Ok(()) } + + #[test] + fn test_bits_to_fp() -> Result<(), SynthesisError> { + use AllocationMode::*; + let rng = &mut ark_std::test_rng(); + let cs = ConstraintSystem::::new_ref(); + + let modes = [Input, Witness, Constant]; + for &mode in modes.iter() { + for _ in 0..1000 { + let f = Fr::rand(rng); + let bits = BitIteratorLE::new(f.into_repr()).collect::>(); + let bits: Vec<_> = + AllocVar::new_variable(cs.clone(), || Ok(bits.as_slice()), mode)?; + let f = AllocVar::new_variable(cs.clone(), || Ok(f), mode)?; + let claimed_f = Boolean::le_bits_to_fp_var(&bits)?; + claimed_f.enforce_equal(&f)?; + } + + for _ in 0..1000 { + let f = Fr::from(u64::rand(rng)); + let bits = BitIteratorLE::new(f.into_repr()).collect::>(); + let bits: Vec<_> = + AllocVar::new_variable(cs.clone(), || Ok(bits.as_slice()), mode)?; + let f = AllocVar::new_variable(cs.clone(), || Ok(f), mode)?; + let claimed_f = Boolean::le_bits_to_fp_var(&bits)?; + claimed_f.enforce_equal(&f)?; + } + assert!(cs.is_satisfied().unwrap()); + } + + Ok(()) + } }