use std::{borrow::Borrow, marker::PhantomData, ops::Neg};
use crate::{
bits::boolean_input::BooleanInputGadget,
fields::FpGadget,
traits::{alloc::AllocGadget, eq::EqGadget, fields::FieldGadget},
Boolean,
FromFieldElementsGadget,
MergeGadget,
ToBitsLEGadget,
};
use snarkvm_fields::PrimeField;
use snarkvm_r1cs::{ConstraintSystem, LinearCombination, SynthesisError};
use crate::{
fields::AllocatedFp,
nonnative::{
params::{get_params, OptimizationType},
AllocatedNonNativeFieldVar,
NonNativeFieldVar,
},
};
pub struct NonNativeFieldInputVar<F, CF>
where
F: PrimeField,
CF: PrimeField,
{
pub val: Vec<NonNativeFieldVar<F, CF>>,
}
impl<F, CF> NonNativeFieldInputVar<F, CF>
where
F: PrimeField,
CF: PrimeField,
{
pub fn new(val: Vec<NonNativeFieldVar<F, CF>>) -> Self {
Self { val }
}
}
impl<F, CF> IntoIterator for NonNativeFieldInputVar<F, CF>
where
F: PrimeField,
CF: PrimeField,
{
type IntoIter = std::vec::IntoIter<NonNativeFieldVar<F, CF>>;
type Item = NonNativeFieldVar<F, CF>;
fn into_iter(self) -> Self::IntoIter {
self.val.into_iter()
}
}
impl<F, CF> Clone for NonNativeFieldInputVar<F, CF>
where
F: PrimeField,
CF: PrimeField,
{
fn clone(&self) -> Self {
Self { val: self.val.clone() }
}
}
impl<F, CF> AllocGadget<Vec<F>, CF> for NonNativeFieldInputVar<F, CF>
where
F: PrimeField,
CF: PrimeField,
{
fn alloc_constant<Fn: FnOnce() -> Result<T, SynthesisError>, T: Borrow<Vec<F>>, CS: ConstraintSystem<CF>>(
mut cs: CS,
value_gen: Fn,
) -> Result<Self, SynthesisError> {
let obj = value_gen()?;
let mut allocated = Vec::<NonNativeFieldVar<F, CF>>::new();
for (i, elem) in obj.borrow().iter().enumerate() {
let elem_allocated =
NonNativeFieldVar::<F, CF>::alloc_constant(cs.ns(|| format!("alloc_constant_element_{}", i)), || {
Ok(elem)
})?;
allocated.push(elem_allocated);
}
Ok(Self { val: allocated })
}
fn alloc<Fn: FnOnce() -> Result<T, SynthesisError>, T: Borrow<Vec<F>>, CS: ConstraintSystem<CF>>(
mut cs: CS,
value_gen: Fn,
) -> Result<Self, SynthesisError> {
let obj = value_gen()?;
let mut allocated = Vec::<NonNativeFieldVar<F, CF>>::new();
for (i, elem) in obj.borrow().iter().enumerate() {
let elem_allocated =
NonNativeFieldVar::<F, CF>::alloc(cs.ns(|| format!("alloc_element_{}", i)), || Ok(elem))?;
allocated.push(elem_allocated);
}
Ok(Self { val: allocated })
}
fn alloc_input<Fn: FnOnce() -> Result<T, SynthesisError>, T: Borrow<Vec<F>>, CS: ConstraintSystem<CF>>(
mut cs: CS,
value_gen: Fn,
) -> Result<Self, SynthesisError> {
let optimization_type = OptimizationType::Weight;
let params = get_params(F::size_in_bits(), CF::size_in_bits(), optimization_type);
let obj = value_gen()?;
let boolean_allocation = BooleanInputGadget::alloc_input(cs.ns(|| "boolean"), || Ok(obj.borrow()))?;
let mut field_allocation = Vec::<AllocatedNonNativeFieldVar<F, CF>>::new();
for (i, elem) in obj.borrow().iter().enumerate() {
let mut elem_allocated =
AllocatedNonNativeFieldVar::<F, CF>::alloc(cs.ns(|| format!("allocating_element_{}", i)), || Ok(elem))?;
elem_allocated.is_in_the_normal_form = true;
elem_allocated.num_of_additions_over_normal_form = CF::zero();
field_allocation.push(elem_allocated);
}
for (i, (field_bits, field_elem)) in boolean_allocation.val.iter().zip(field_allocation.iter()).enumerate() {
let mut field_bits = field_bits.clone();
field_bits.reverse();
let bit_per_top_limb = F::size_in_bits() - (params.num_limbs - 1) * params.bits_per_limb;
let bit_per_non_top_limb = params.bits_per_limb;
for (j, limb) in field_elem.limbs.iter().enumerate() {
let bits_slice = if j == 0 {
field_bits[0..bit_per_top_limb].to_vec()
} else {
field_bits
[bit_per_top_limb + (j - 1) * bit_per_non_top_limb..bit_per_top_limb + j * bit_per_non_top_limb]
.to_vec()
};
let mut bit_sum = FpGadget::<CF>::zero(cs.ns(|| format!("zero_{}_{}", i, j)))?;
let mut cur = CF::one();
for (k, bit) in bits_slice.iter().rev().enumerate() {
let mut temp =
FpGadget::<CF>::from_boolean(cs.ns(|| format!("from_boolean_{}_{}_{}", i, j, k)), *bit)?;
temp = temp.mul_by_constant(cs.ns(|| format!("mul_by_constant_{}_{}_{}", i, j, k)), &cur)?;
bit_sum = bit_sum.add(cs.ns(|| format!("bit_sum_add_{}_{}_{}", i, j, k)), &temp)?;
cur.double_in_place();
}
limb.enforce_equal(cs.ns(|| format!("enforce_equal_{}_{}", i, j)), &bit_sum)?;
}
}
let mut wrapped_field_allocation = Vec::<NonNativeFieldVar<F, CF>>::new();
for field_gadget in field_allocation.iter() {
wrapped_field_allocation.push(NonNativeFieldVar::Var(field_gadget.clone()));
}
Ok(Self {
val: wrapped_field_allocation,
})
}
}
impl<F: PrimeField, CF: PrimeField> FromFieldElementsGadget<F, CF> for NonNativeFieldInputVar<F, CF> {
fn from_field_elements<CS: ConstraintSystem<CF>>(
mut cs: CS,
field_elements: &[FpGadget<CF>],
) -> Result<Self, SynthesisError> {
let optimization_type = OptimizationType::Weight;
let params = get_params(F::size_in_bits(), CF::size_in_bits(), optimization_type);
let boolean_allocation =
BooleanInputGadget::<F, CF>::from_field_elements(cs.ns(|| "from_field_elements"), field_elements)?;
let mut field_allocation = Vec::<NonNativeFieldVar<F, CF>>::new();
for field_bits in boolean_allocation.val.iter() {
let mut field_bits = field_bits.clone();
field_bits.resize(F::size_in_bits(), Boolean::Constant(false));
field_bits.reverse();
let mut limbs = Vec::<FpGadget<CF>>::new();
let bit_per_top_limb = F::size_in_bits() - (params.num_limbs - 1) * params.bits_per_limb;
let bit_per_non_top_limb = params.bits_per_limb;
for j in 0..params.num_limbs {
let bits_slice = if j == 0 {
field_bits[0..bit_per_top_limb].to_vec()
} else {
field_bits
[bit_per_top_limb + (j - 1) * bit_per_non_top_limb..bit_per_top_limb + j * bit_per_non_top_limb]
.to_vec()
};
let mut lc = LinearCombination::<CF>::zero();
let mut cur = CF::one();
let mut limb_value = CF::zero();
for bit in bits_slice.iter().rev() {
lc = &lc + bit.lc(CS::one(), CF::one()) * cur;
if bit.get_value().unwrap_or_default() {
limb_value += &cur;
}
cur.double_in_place();
}
let limb = AllocatedFp::<CF>::alloc(cs.ns(|| format!("limb_{}", j)), || Ok(limb_value))?;
lc = &limb.variable.clone().neg() + lc;
cs.enforce(|| format!("enforce_constraint_{}", j), |lc| lc, |lc| lc, |_| lc);
limbs.push(FpGadget::from(limb));
}
field_allocation.push(NonNativeFieldVar::<F, CF>::Var(AllocatedNonNativeFieldVar::<F, CF> {
limbs,
num_of_additions_over_normal_form: CF::zero(),
is_in_the_normal_form: true,
target_phantom: PhantomData,
}))
}
Ok(Self { val: field_allocation })
}
}
impl<F: PrimeField, CF: PrimeField> MergeGadget<CF> for NonNativeFieldInputVar<F, CF> {
fn merge<CS: ConstraintSystem<CF>>(&self, _cs: CS, other: &Self) -> Result<Self, SynthesisError> {
let mut elems = vec![];
elems.extend_from_slice(&self.val);
elems.extend_from_slice(&other.val);
Ok(Self { val: elems })
}
fn merge_in_place<CS: ConstraintSystem<CF>>(&mut self, _cs: CS, other: &Self) -> Result<(), SynthesisError> {
self.val.extend_from_slice(&other.val);
Ok(())
}
}
impl<F: PrimeField, CF: PrimeField> ToBitsLEGadget<CF> for NonNativeFieldInputVar<F, CF> {
fn to_bits_le<CS: ConstraintSystem<CF>>(&self, mut cs: CS) -> Result<Vec<Boolean>, SynthesisError> {
let mut res = vec![];
for (i, elem) in self.val.iter().enumerate() {
res.extend_from_slice(&elem.to_bits_le(cs.ns(|| format!("to_bits_{}", i)))?);
}
Ok(res)
}
fn to_bits_le_strict<CS: ConstraintSystem<CF>>(&self, cs: CS) -> Result<Vec<Boolean>, SynthesisError> {
self.to_bits_le(cs)
}
}
#[cfg(test)]
mod test {
use snarkvm_r1cs::{Fr, TestConstraintSystem};
use snarkvm_utilities::rand::{test_rng, UniformRand};
use super::*;
use crate::traits::eq::EqGadget;
#[test]
fn test_nonnative_field_inputs_from_field_elements() {
let rng = &mut test_rng();
let mut cs = TestConstraintSystem::<Fr>::new();
let mut field_elements = vec![];
let mut field_element_gadgets = vec![];
for i in 0..1 {
let field_element = Fr::rand(rng);
let field_element_gadget =
FpGadget::alloc(cs.ns(|| format!("field element_{}", i)), || Ok(field_element)).unwrap();
field_elements.push(field_element);
field_element_gadgets.push(field_element_gadget);
}
let expected_nonnative_field_element_gadgets =
NonNativeFieldInputVar::<Fr, Fr>::alloc(cs.ns(|| "alloc_nonnative_field_elements"), || Ok(field_elements))
.unwrap();
let nonnative_field_element_gadgets = NonNativeFieldInputVar::<Fr, Fr>::from_field_elements(
cs.ns(|| "from_field_elements"),
&field_element_gadgets,
)
.unwrap();
for (i, (expected_nonnative_fe, nonnative_fe)) in expected_nonnative_field_element_gadgets
.val
.iter()
.zip(nonnative_field_element_gadgets.val.iter())
.enumerate()
{
expected_nonnative_fe
.enforce_equal(cs.ns(|| format!("enforce_equal_nonnative_fe_{}", i)), nonnative_fe)
.unwrap();
}
assert!(cs.is_satisfied());
}
}