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 #[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 #[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 #[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 #[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 #[tracing::instrument(target = "gr1cs")]
97 pub fn enforce_smaller_or_equal_than_mod_minus_one_div_two(
98 &self,
99 ) -> Result<(), SynthesisError> {
100 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 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 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 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 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}