Skip to main content

ark_r1cs_std/fields/fp/
cmp.rs

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