use crate::{ boolean::Boolean, fields::{fp::FpGadget, FieldGadget}, ToBitsGadget, }; use algebra::PrimeField; use r1cs_core::{ConstraintSystem, SynthesisError}; use std::marker::PhantomData; pub struct SmallerThanGadget { constraint_field_type: PhantomData, } impl SmallerThanGadget { // the function assumes a and b are known to be <= (p-1)/2 pub fn is_smaller_than>( mut cs: CS, a: &FpGadget, b: &FpGadget, ) -> Result { let two = ConstraintF::one() + ConstraintF::one(); let d0 = a.sub(cs.ns(|| "a - b"), b)?; let d = d0.mul_by_constant(cs.ns(|| "mul 2"), &two)?; let d_bits = d.to_bits_strict(cs.ns(|| "d to bits"))?; let d_bits_len = d_bits.len(); Ok(d_bits[d_bits_len - 1]) } // the function assumes a and b are known to be <= (p-1)/2 pub fn is_smaller_than_or_equal_to>( mut cs: CS, a: &FpGadget, b: &FpGadget, ) -> Result { let b_plus_one = b.add_constant(cs.ns(|| "plus one"), &ConstraintF::one())?; Self::is_smaller_than(cs.ns(|| "is smaller than"), a, &b_plus_one) } // the function assumes a and b are known to be <= (p-1)/2 pub fn enforce_smaller_than>( mut cs: CS, a: &FpGadget, b: &FpGadget, ) -> Result<(), SynthesisError> { let is_smaller_than = Self::is_smaller_than(cs.ns(|| "is smaller than"), a, b)?; cs.enforce( || "enforce smaller than", |_| is_smaller_than.lc(CS::one(), ConstraintF::one()), |lc| lc + (ConstraintF::one(), CS::one()), |lc| lc + (ConstraintF::one(), CS::one()), ); Ok(()) } // the function assumes a and b are known to be <= (p-1)/2 pub fn enforce_smaller_than_or_equal_to>( mut cs: CS, a: &FpGadget, b: &FpGadget, ) -> Result<(), SynthesisError> { let b_plus_one = b.add_constant(cs.ns(|| "plus one"), &ConstraintF::one())?; Self::enforce_smaller_than(cs.ns(|| "enforce smaller than"), a, &b_plus_one) } pub fn enforce_smaller_than_strict>( mut cs: CS, a: &FpGadget, b: &FpGadget, ) -> Result<(), SynthesisError> { let a_bits = a.to_bits(cs.ns(|| "a to bits"))?; Boolean::enforce_smaller_or_equal_than::<_, _, ConstraintF, _>( cs.ns(|| "enforce a smaller than modulus minus one div two"), &a_bits, ConstraintF::modulus_minus_one_div_two(), )?; let b_bits = b.to_bits(cs.ns(|| "b to bits"))?; Boolean::enforce_smaller_or_equal_than::<_, _, ConstraintF, _>( cs.ns(|| "enforce b smaller than modulus minus one div two"), &b_bits, ConstraintF::modulus_minus_one_div_two(), )?; let is_smaller_than = Self::is_smaller_than(cs.ns(|| "is smaller than"), a, b)?; cs.enforce( || "enforce smaller than", |_| is_smaller_than.lc(CS::one(), ConstraintF::one()), |lc| lc + (ConstraintF::one(), CS::one()), |lc| lc + (ConstraintF::one(), CS::one()), ); Ok(()) } pub fn enforce_smaller_than_or_equal_to_strict>( mut cs: CS, a: &FpGadget, b: &FpGadget, ) -> Result<(), SynthesisError> { let b_plus_one = b.add_constant(cs.ns(|| "plus one"), &ConstraintF::one())?; Self::enforce_smaller_than_strict(cs.ns(|| "enforce smaller than strict"), a, &b_plus_one) } } #[cfg(test)] mod test { use rand::{Rng, SeedableRng}; use rand_xorshift::XorShiftRng; use std::cmp::Ordering; use super::SmallerThanGadget; use crate::{ alloc::AllocGadget, fields::fp::FpGadget, test_constraint_system::TestConstraintSystem, }; use algebra::{bls12_381::Fr, PrimeField, UniformRand}; use r1cs_core::ConstraintSystem; #[test] fn test_smaller_than() { let mut rng = &mut XorShiftRng::from_seed([ 0x5d, 0xbe, 0x62, 0x59, 0x8d, 0x31, 0x3d, 0x76, 0x32, 0x37, 0xdb, 0x17, 0xe5, 0xbc, 0x06, 0x54, ]); fn rand_in_range(rng: &mut R) -> Fr { let pminusonedivtwo = Fr::from_repr(Fr::modulus_minus_one_div_two()); let mut r; loop { r = Fr::rand(rng); if r <= pminusonedivtwo { break; } } r } for i in 0..10 { let mut cs = TestConstraintSystem::::new(); let a = rand_in_range(&mut rng); let a_var = FpGadget::::alloc(cs.ns(|| "a"), || Ok(a)).unwrap(); let b = rand_in_range(&mut rng); let b_var = FpGadget::::alloc(cs.ns(|| "b"), || Ok(b)).unwrap(); match a.cmp(&b) { Ordering::Less => { SmallerThanGadget::::enforce_smaller_than_strict( cs.ns(|| "smaller than test"), &a_var, &b_var, ) .unwrap(); SmallerThanGadget::::enforce_smaller_than_or_equal_to_strict( cs.ns(|| "smaller than test 2"), &a_var, &b_var, ) .unwrap(); }, Ordering::Greater => { SmallerThanGadget::::enforce_smaller_than_strict( cs.ns(|| "smaller than test"), &b_var, &a_var, ) .unwrap(); SmallerThanGadget::::enforce_smaller_than_or_equal_to_strict( cs.ns(|| "smaller than test 2"), &b_var, &a_var, ) .unwrap(); }, _ => {}, } if i == 0 { println!("number of constraints: {}", cs.num_constraints()); } assert!(cs.is_satisfied()); } for _i in 0..10 { let mut cs = TestConstraintSystem::::new(); let a = rand_in_range(&mut rng); let a_var = FpGadget::::alloc(cs.ns(|| "a"), || Ok(a)).unwrap(); let b = rand_in_range(&mut rng); let b_var = FpGadget::::alloc(cs.ns(|| "b"), || Ok(b)).unwrap(); match b.cmp(&a) { Ordering::Less => { SmallerThanGadget::::enforce_smaller_than_strict( cs.ns(|| "smaller than test"), &a_var, &b_var, ) .unwrap(); SmallerThanGadget::::enforce_smaller_than_or_equal_to_strict( cs.ns(|| "smaller than test 2"), &a_var, &b_var, ) .unwrap(); }, Ordering::Greater => { SmallerThanGadget::::enforce_smaller_than_strict( cs.ns(|| "smaller than test"), &b_var, &a_var, ) .unwrap(); SmallerThanGadget::::enforce_smaller_than_or_equal_to( cs.ns(|| "smaller than test 2"), &b_var, &a_var, ) .unwrap(); }, _ => {}, } assert!(!cs.is_satisfied()); } for _i in 0..10 { let mut cs = TestConstraintSystem::::new(); let a = rand_in_range(&mut rng); let a_var = FpGadget::::alloc(cs.ns(|| "a"), || Ok(a)).unwrap(); SmallerThanGadget::::enforce_smaller_than_strict( cs.ns(|| "smaller than test"), &a_var, &a_var, ) .unwrap(); assert!(!cs.is_satisfied()); } for _i in 0..10 { let mut cs = TestConstraintSystem::::new(); let a = rand_in_range(&mut rng); let a_var = FpGadget::::alloc(cs.ns(|| "a"), || Ok(a)).unwrap(); SmallerThanGadget::::enforce_smaller_than_or_equal_to( cs.ns(|| "smaller than or equal to test"), &a_var, &a_var, ) .unwrap(); assert!(cs.is_satisfied()); } } }