ark_r1cs_std/fields/fp/
cmp.rs

1use crate::{
2    boolean::Boolean,
3    convert::ToBitsGadget,
4    fields::{fp::FpVar, FieldVar},
5    prelude::*,
6};
7use ark_ff::PrimeField;
8use ark_relations::r1cs::{SynthesisError, Variable};
9use core::cmp::Ordering;
10
11impl<F: PrimeField> FpVar<F> {
12    /// This function enforces the ordering between `self` and `other`. The
13    /// constraint system will not be satisfied otherwise. If `self` should
14    /// also be checked for equality, e.g. `self <= other` instead of `self <
15    /// other`, set `should_also_check_quality` to `true`. This variant
16    /// verifies `self` and `other` are `<= (p-1)/2`.
17    #[tracing::instrument(target = "r1cs")]
18    pub fn enforce_cmp(
19        &self,
20        other: &FpVar<F>,
21        ordering: Ordering,
22        should_also_check_equality: bool,
23    ) -> Result<(), SynthesisError> {
24        let (left, right) = self.process_cmp_inputs(other, ordering, should_also_check_equality)?;
25        left.enforce_smaller_than(&right)
26    }
27
28    /// This function enforces the ordering between `self` and `other`. The
29    /// constraint system will not be satisfied otherwise. If `self` should
30    /// also be checked for equality, e.g. `self <= other` instead of `self <
31    /// other`, set `should_also_check_quality` to `true`. This variant
32    /// assumes `self` and `other` are `<= (p-1)/2` and does not generate
33    /// constraints to verify that.
34    #[tracing::instrument(target = "r1cs")]
35    pub fn enforce_cmp_unchecked(
36        &self,
37        other: &FpVar<F>,
38        ordering: Ordering,
39        should_also_check_equality: bool,
40    ) -> Result<(), SynthesisError> {
41        let (left, right) = self.process_cmp_inputs(other, ordering, should_also_check_equality)?;
42        left.enforce_smaller_than_unchecked(&right)
43    }
44
45    /// This function checks the ordering between `self` and `other`. It outputs
46    /// self `Boolean` that contains the result - `1` if true, `0`
47    /// otherwise. The constraint system will be satisfied in any case. If
48    /// `self` should also be checked for equality, e.g. `self <= other`
49    /// instead of `self < other`, set `should_also_check_quality` to
50    /// `true`. This variant verifies `self` and `other` are `<= (p-1)/2`.
51    #[tracing::instrument(target = "r1cs")]
52    pub fn is_cmp(
53        &self,
54        other: &FpVar<F>,
55        ordering: Ordering,
56        should_also_check_equality: bool,
57    ) -> Result<Boolean<F>, SynthesisError> {
58        let (left, right) = self.process_cmp_inputs(other, ordering, should_also_check_equality)?;
59        left.is_smaller_than(&right)
60    }
61
62    /// This function checks the ordering between `self` and `other`. It outputs
63    /// a `Boolean` that contains the result - `1` if true, `0` otherwise.
64    /// The constraint system will be satisfied in any case. If `self`
65    /// should also be checked for equality, e.g. `self <= other` instead of
66    /// `self < other`, set `should_also_check_quality` to `true`. This
67    /// variant assumes `self` and `other` are `<= (p-1)/2` and does not
68    /// generate constraints to verify that.
69    #[tracing::instrument(target = "r1cs")]
70    pub fn is_cmp_unchecked(
71        &self,
72        other: &FpVar<F>,
73        ordering: Ordering,
74        should_also_check_equality: bool,
75    ) -> Result<Boolean<F>, SynthesisError> {
76        let (left, right) = self.process_cmp_inputs(other, ordering, should_also_check_equality)?;
77        left.is_smaller_than_unchecked(&right)
78    }
79
80    fn process_cmp_inputs(
81        &self,
82        other: &Self,
83        ordering: Ordering,
84        should_also_check_equality: bool,
85    ) -> Result<(Self, Self), SynthesisError> {
86        let (left, right) = match ordering {
87            Ordering::Less => (self, other),
88            Ordering::Greater => (other, self),
89            Ordering::Equal => return Err(SynthesisError::Unsatisfiable),
90        };
91        let right_for_check = if should_also_check_equality {
92            right + F::one()
93        } else {
94            right.clone()
95        };
96
97        Ok((left.clone(), right_for_check))
98    }
99
100    /// Helper function to enforce that `self <= (p-1)/2`.
101    #[tracing::instrument(target = "r1cs")]
102    pub fn enforce_smaller_or_equal_than_mod_minus_one_div_two(
103        &self,
104    ) -> Result<(), SynthesisError> {
105        // It's okay to use `to_non_unique_bits` bits here because we're enforcing
106        // self <= (p-1)/2, which implies self < p.
107        let _ = Boolean::enforce_smaller_or_equal_than_le(
108            &self.to_non_unique_bits_le()?,
109            F::MODULUS_MINUS_ONE_DIV_TWO,
110        )?;
111        Ok(())
112    }
113
114    /// Helper function to check `self < other` and output a result bit. This
115    /// function verifies `self` and `other` are `<= (p-1)/2`.
116    fn is_smaller_than(&self, other: &FpVar<F>) -> Result<Boolean<F>, SynthesisError> {
117        self.enforce_smaller_or_equal_than_mod_minus_one_div_two()?;
118        other.enforce_smaller_or_equal_than_mod_minus_one_div_two()?;
119        self.is_smaller_than_unchecked(other)
120    }
121
122    /// Helper function to check `self < other` and output a result bit. This
123    /// function assumes `self` and `other` are `<= (p-1)/2` and does not
124    /// generate constraints to verify that.
125    fn is_smaller_than_unchecked(&self, other: &FpVar<F>) -> Result<Boolean<F>, SynthesisError> {
126        Ok((self - other)
127            .double()?
128            .to_bits_le()?
129            .first()
130            .unwrap()
131            .clone())
132    }
133
134    /// Helper function to enforce `self < other`. This function verifies `self`
135    /// and `other` are `<= (p-1)/2`.
136    fn enforce_smaller_than(&self, other: &FpVar<F>) -> Result<(), SynthesisError> {
137        self.enforce_smaller_or_equal_than_mod_minus_one_div_two()?;
138        other.enforce_smaller_or_equal_than_mod_minus_one_div_two()?;
139        self.enforce_smaller_than_unchecked(other)
140    }
141
142    /// Helper function to enforce `self < other`. This function assumes `self`
143    /// and `other` are `<= (p-1)/2` and does not generate constraints to
144    /// verify that.
145    fn enforce_smaller_than_unchecked(&self, other: &FpVar<F>) -> Result<(), SynthesisError> {
146        let is_smaller_than = self.is_smaller_than_unchecked(other)?;
147        let lc_one = lc!() + Variable::One;
148        [self, other]
149            .cs()
150            .enforce_constraint(is_smaller_than.lc(), lc_one.clone(), lc_one)
151    }
152}
153
154#[cfg(test)]
155mod test {
156    use ark_std::{cmp::Ordering, rand::Rng};
157
158    use crate::{alloc::AllocVar, fields::fp::FpVar};
159    use ark_ff::{PrimeField, UniformRand};
160    use ark_relations::r1cs::ConstraintSystem;
161    use ark_test_curves::bls12_381::Fr;
162
163    #[test]
164    fn test_cmp() {
165        let mut rng = ark_std::test_rng();
166        fn rand_in_range<R: Rng>(rng: &mut R) -> Fr {
167            let pminusonedivtwo: Fr = Fr::MODULUS_MINUS_ONE_DIV_TWO.into();
168            let mut r;
169            loop {
170                r = Fr::rand(rng);
171                if r <= pminusonedivtwo {
172                    break;
173                }
174            }
175            r
176        }
177        for i in 0..10 {
178            let cs = ConstraintSystem::<Fr>::new_ref();
179            let a = rand_in_range(&mut rng);
180            let a_var = FpVar::<Fr>::new_witness(cs.clone(), || Ok(a)).unwrap();
181            let b = rand_in_range(&mut rng);
182            let b_var = FpVar::<Fr>::new_witness(cs.clone(), || Ok(b)).unwrap();
183
184            match a.cmp(&b) {
185                Ordering::Less => {
186                    a_var.enforce_cmp(&b_var, Ordering::Less, false).unwrap();
187                    a_var.enforce_cmp(&b_var, Ordering::Less, true).unwrap();
188                },
189                Ordering::Greater => {
190                    a_var.enforce_cmp(&b_var, Ordering::Greater, false).unwrap();
191                    a_var.enforce_cmp(&b_var, Ordering::Greater, true).unwrap();
192                },
193                _ => {},
194            }
195
196            if i == 0 {
197                println!("number of constraints: {}", cs.num_constraints());
198            }
199            assert!(cs.is_satisfied().unwrap());
200        }
201        println!("Finished with satisfaction tests");
202
203        for _i in 0..10 {
204            let cs = ConstraintSystem::<Fr>::new_ref();
205            let a = rand_in_range(&mut rng);
206            let a_var = FpVar::<Fr>::new_witness(cs.clone(), || Ok(a)).unwrap();
207            let b = rand_in_range(&mut rng);
208            let b_var = FpVar::<Fr>::new_witness(cs.clone(), || Ok(b)).unwrap();
209
210            match b.cmp(&a) {
211                Ordering::Less => {
212                    a_var.enforce_cmp(&b_var, Ordering::Less, false).unwrap();
213                    a_var.enforce_cmp(&b_var, Ordering::Less, true).unwrap();
214                },
215                Ordering::Greater => {
216                    a_var.enforce_cmp(&b_var, Ordering::Greater, false).unwrap();
217                    a_var.enforce_cmp(&b_var, Ordering::Greater, true).unwrap();
218                },
219                _ => {},
220            }
221
222            assert!(!cs.is_satisfied().unwrap());
223        }
224
225        for _i in 0..10 {
226            let cs = ConstraintSystem::<Fr>::new_ref();
227            let a = rand_in_range(&mut rng);
228            let a_var = FpVar::<Fr>::new_witness(cs.clone(), || Ok(a)).unwrap();
229            a_var.enforce_cmp(&a_var, Ordering::Less, false).unwrap();
230
231            assert!(!cs.is_satisfied().unwrap());
232        }
233
234        for _i in 0..10 {
235            let cs = ConstraintSystem::<Fr>::new_ref();
236            let a = rand_in_range(&mut rng);
237            let a_var = FpVar::<Fr>::new_witness(cs.clone(), || Ok(a)).unwrap();
238            a_var.enforce_cmp(&a_var, Ordering::Less, true).unwrap();
239            assert!(cs.is_satisfied().unwrap());
240        }
241    }
242}