arnaucube
arnaucube/plonky2-u32Public Self-hosted repository
plonky2-u32/src/gates/comparison.rs
src/gates/comparison.rsRaw
  1use alloc::string::{String, ToString};
  2use alloc::vec::Vec;
  3use alloc::{format, vec};
  4use anyhow::Result;
  5use num::integer::div_ceil;
  6use core::marker::PhantomData;
  7use plonky2::plonk::circuit_data::CommonCircuitData;
  8use plonky2::util::serialization::{Buffer, IoResult, Read, Write};
  9
 10use plonky2::field::extension::Extendable;
 11use plonky2::field::packed::PackedField;
 12use plonky2::field::types::{Field, Field64};
 13use plonky2::gates::gate::Gate;
 14use plonky2::gates::packed_util::PackedEvaluableBase;
 15use plonky2::gates::util::StridedConstraintConsumer;
 16use plonky2::hash::hash_types::RichField;
 17use plonky2::iop::ext_target::ExtensionTarget;
 18use plonky2::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGeneratorRef};
 19use plonky2::iop::target::Target;
 20use plonky2::iop::wire::Wire;
 21use plonky2::iop::witness::{PartitionWitness, Witness, WitnessWrite};
 22use plonky2::plonk::circuit_builder::CircuitBuilder;
 23use plonky2::plonk::plonk_common::{reduce_with_powers, reduce_with_powers_ext_circuit};
 24use plonky2::plonk::vars::{
 25    EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch,
 26    EvaluationVarsBasePacked,
 27};
 28use plonky2::util::bits_u64;
 29
 30/// A gate for checking that one value is less than or equal to another.
 31#[derive(Clone, Debug)]
 32pub struct ComparisonGate<F: Field64 + Extendable<D>, const D: usize> {
 33    pub(crate) num_bits: usize,
 34    pub(crate) num_chunks: usize,
 35    _phantom: PhantomData<F>,
 36}
 37
 38impl<F: RichField + Extendable<D>, const D: usize> ComparisonGate<F, D> {
 39    pub fn new(num_bits: usize, num_chunks: usize) -> Self {
 40        debug_assert!(num_bits < bits_u64(F::ORDER));
 41        Self {
 42            num_bits,
 43            num_chunks,
 44            _phantom: PhantomData,
 45        }
 46    }
 47
 48    pub fn chunk_bits(&self) -> usize {
 49        div_ceil(self.num_bits, self.num_chunks)
 50    }
 51
 52    pub fn wire_first_input(&self) -> usize {
 53        0
 54    }
 55
 56    pub fn wire_second_input(&self) -> usize {
 57        1
 58    }
 59
 60    pub fn wire_result_bool(&self) -> usize {
 61        2
 62    }
 63
 64    pub fn wire_most_significant_diff(&self) -> usize {
 65        3
 66    }
 67
 68    pub fn wire_first_chunk_val(&self, chunk: usize) -> usize {
 69        debug_assert!(chunk < self.num_chunks);
 70        4 + chunk
 71    }
 72
 73    pub fn wire_second_chunk_val(&self, chunk: usize) -> usize {
 74        debug_assert!(chunk < self.num_chunks);
 75        4 + self.num_chunks + chunk
 76    }
 77
 78    pub fn wire_equality_dummy(&self, chunk: usize) -> usize {
 79        debug_assert!(chunk < self.num_chunks);
 80        4 + 2 * self.num_chunks + chunk
 81    }
 82
 83    pub fn wire_chunks_equal(&self, chunk: usize) -> usize {
 84        debug_assert!(chunk < self.num_chunks);
 85        4 + 3 * self.num_chunks + chunk
 86    }
 87
 88    pub fn wire_intermediate_value(&self, chunk: usize) -> usize {
 89        debug_assert!(chunk < self.num_chunks);
 90        4 + 4 * self.num_chunks + chunk
 91    }
 92
 93    /// The `bit_index`th bit of 2^n - 1 + most_significant_diff.
 94    pub fn wire_most_significant_diff_bit(&self, bit_index: usize) -> usize {
 95        4 + 5 * self.num_chunks + bit_index
 96    }
 97}
 98
 99impl<F: RichField + Extendable<D>, const D: usize> Gate<F, D> for ComparisonGate<F, D> {
100    fn id(&self) -> String {
101        format!("{self:?}<D={D}>")
102    }
103
104    fn serialize(&self, dst: &mut Vec<u8>, _common_data: &CommonCircuitData<F, D>) -> IoResult<()> {
105        dst.write_usize(self.num_bits)?;
106        dst.write_usize(self.num_chunks)?;
107        Ok(())
108    }
109
110    fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData<F, D>) -> IoResult<Self> {
111        let num_bits = src.read_usize()?;
112        let num_chunks = src.read_usize()?;
113        Ok(Self {
114            num_bits,
115            num_chunks,
116            _phantom: PhantomData,
117        })
118    }
119
120    fn eval_unfiltered(&self, vars: EvaluationVars<F, D>) -> Vec<F::Extension> {
121        let mut constraints = Vec::with_capacity(self.num_constraints());
122
123        let first_input = vars.local_wires[self.wire_first_input()];
124        let second_input = vars.local_wires[self.wire_second_input()];
125
126        // Get chunks and assert that they match
127        let first_chunks: Vec<F::Extension> = (0..self.num_chunks)
128            .map(|i| vars.local_wires[self.wire_first_chunk_val(i)])
129            .collect();
130        let second_chunks: Vec<F::Extension> = (0..self.num_chunks)
131            .map(|i| vars.local_wires[self.wire_second_chunk_val(i)])
132            .collect();
133
134        let first_chunks_combined = reduce_with_powers(
135            &first_chunks,
136            F::Extension::from_canonical_usize(1 << self.chunk_bits()),
137        );
138        let second_chunks_combined = reduce_with_powers(
139            &second_chunks,
140            F::Extension::from_canonical_usize(1 << self.chunk_bits()),
141        );
142
143        constraints.push(first_chunks_combined - first_input);
144        constraints.push(second_chunks_combined - second_input);
145
146        let chunk_size = 1 << self.chunk_bits();
147
148        let mut most_significant_diff_so_far = F::Extension::ZERO;
149
150        for i in 0..self.num_chunks {
151            // Range-check the chunks to be less than `chunk_size`.
152            let first_product: F::Extension = (0..chunk_size)
153                .map(|x| first_chunks[i] - F::Extension::from_canonical_usize(x))
154                .product();
155            let second_product: F::Extension = (0..chunk_size)
156                .map(|x| second_chunks[i] - F::Extension::from_canonical_usize(x))
157                .product();
158            constraints.push(first_product);
159            constraints.push(second_product);
160
161            let difference = second_chunks[i] - first_chunks[i];
162            let equality_dummy = vars.local_wires[self.wire_equality_dummy(i)];
163            let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)];
164
165            // Two constraints to assert that `chunks_equal` is valid.
166            constraints.push(difference * equality_dummy - (F::Extension::ONE - chunks_equal));
167            constraints.push(chunks_equal * difference);
168
169            // Update `most_significant_diff_so_far`.
170            let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)];
171            constraints.push(intermediate_value - chunks_equal * most_significant_diff_so_far);
172            most_significant_diff_so_far =
173                intermediate_value + (F::Extension::ONE - chunks_equal) * difference;
174        }
175
176        let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()];
177        constraints.push(most_significant_diff - most_significant_diff_so_far);
178
179        let most_significant_diff_bits: Vec<F::Extension> = (0..self.chunk_bits() + 1)
180            .map(|i| vars.local_wires[self.wire_most_significant_diff_bit(i)])
181            .collect();
182
183        // Range-check the bits.
184        for &bit in &most_significant_diff_bits {
185            constraints.push(bit * (F::Extension::ONE - bit));
186        }
187
188        let bits_combined = reduce_with_powers(&most_significant_diff_bits, F::Extension::TWO);
189        let two_n = F::Extension::from_canonical_u64(1 << self.chunk_bits());
190        constraints.push((two_n + most_significant_diff) - bits_combined);
191
192        // Iff first <= second, the top (n + 1st) bit of (2^n + most_significant_diff) will be 1.
193        let result_bool = vars.local_wires[self.wire_result_bool()];
194        constraints.push(result_bool - most_significant_diff_bits[self.chunk_bits()]);
195
196        constraints
197    }
198
199    fn eval_unfiltered_base_one(
200        &self,
201        _vars: EvaluationVarsBase<F>,
202        _yield_constr: StridedConstraintConsumer<F>,
203    ) {
204        panic!("use eval_unfiltered_base_packed instead");
205    }
206
207    fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch<F>) -> Vec<F> {
208        self.eval_unfiltered_base_batch_packed(vars_base)
209    }
210
211    fn eval_unfiltered_circuit(
212        &self,
213        builder: &mut CircuitBuilder<F, D>,
214        vars: EvaluationTargets<D>,
215    ) -> Vec<ExtensionTarget<D>> {
216        let mut constraints = Vec::with_capacity(self.num_constraints());
217
218        let first_input = vars.local_wires[self.wire_first_input()];
219        let second_input = vars.local_wires[self.wire_second_input()];
220
221        // Get chunks and assert that they match
222        let first_chunks: Vec<ExtensionTarget<D>> = (0..self.num_chunks)
223            .map(|i| vars.local_wires[self.wire_first_chunk_val(i)])
224            .collect();
225        let second_chunks: Vec<ExtensionTarget<D>> = (0..self.num_chunks)
226            .map(|i| vars.local_wires[self.wire_second_chunk_val(i)])
227            .collect();
228
229        let chunk_base = builder.constant(F::from_canonical_usize(1 << self.chunk_bits()));
230        let first_chunks_combined =
231            reduce_with_powers_ext_circuit(builder, &first_chunks, chunk_base);
232        let second_chunks_combined =
233            reduce_with_powers_ext_circuit(builder, &second_chunks, chunk_base);
234
235        constraints.push(builder.sub_extension(first_chunks_combined, first_input));
236        constraints.push(builder.sub_extension(second_chunks_combined, second_input));
237
238        let chunk_size = 1 << self.chunk_bits();
239
240        let mut most_significant_diff_so_far = builder.zero_extension();
241
242        let one = builder.one_extension();
243        // Find the chosen chunk.
244        for i in 0..self.num_chunks {
245            // Range-check the chunks to be less than `chunk_size`.
246            let mut first_product = one;
247            let mut second_product = one;
248            for x in 0..chunk_size {
249                let x_f = builder.constant_extension(F::Extension::from_canonical_usize(x));
250                let first_diff = builder.sub_extension(first_chunks[i], x_f);
251                let second_diff = builder.sub_extension(second_chunks[i], x_f);
252                first_product = builder.mul_extension(first_product, first_diff);
253                second_product = builder.mul_extension(second_product, second_diff);
254            }
255            constraints.push(first_product);
256            constraints.push(second_product);
257
258            let difference = builder.sub_extension(second_chunks[i], first_chunks[i]);
259            let equality_dummy = vars.local_wires[self.wire_equality_dummy(i)];
260            let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)];
261
262            // Two constraints to assert that `chunks_equal` is valid.
263            let diff_times_equal = builder.mul_extension(difference, equality_dummy);
264            let not_equal = builder.sub_extension(one, chunks_equal);
265            constraints.push(builder.sub_extension(diff_times_equal, not_equal));
266            constraints.push(builder.mul_extension(chunks_equal, difference));
267
268            // Update `most_significant_diff_so_far`.
269            let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)];
270            let old_diff = builder.mul_extension(chunks_equal, most_significant_diff_so_far);
271            constraints.push(builder.sub_extension(intermediate_value, old_diff));
272
273            let not_equal = builder.sub_extension(one, chunks_equal);
274            let new_diff = builder.mul_extension(not_equal, difference);
275            most_significant_diff_so_far = builder.add_extension(intermediate_value, new_diff);
276        }
277
278        let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()];
279        constraints
280            .push(builder.sub_extension(most_significant_diff, most_significant_diff_so_far));
281
282        let most_significant_diff_bits: Vec<ExtensionTarget<D>> = (0..self.chunk_bits() + 1)
283            .map(|i| vars.local_wires[self.wire_most_significant_diff_bit(i)])
284            .collect();
285
286        // Range-check the bits.
287        for &this_bit in &most_significant_diff_bits {
288            let inverse = builder.sub_extension(one, this_bit);
289            constraints.push(builder.mul_extension(this_bit, inverse));
290        }
291
292        let two = builder.two();
293        let bits_combined =
294            reduce_with_powers_ext_circuit(builder, &most_significant_diff_bits, two);
295        let two_n =
296            builder.constant_extension(F::Extension::from_canonical_u64(1 << self.chunk_bits()));
297        let sum = builder.add_extension(two_n, most_significant_diff);
298        constraints.push(builder.sub_extension(sum, bits_combined));
299
300        // Iff first <= second, the top (n + 1st) bit of (2^n + most_significant_diff) will be 1.
301        let result_bool = vars.local_wires[self.wire_result_bool()];
302        constraints.push(
303            builder.sub_extension(result_bool, most_significant_diff_bits[self.chunk_bits()]),
304        );
305
306        constraints
307    }
308
309    fn generators(&self, row: usize, _local_constants: &[F]) -> Vec<WitnessGeneratorRef<F, D>> {
310        let gen = ComparisonGenerator::<F, D> {
311            row,
312            gate: self.clone(),
313        };
314        vec![WitnessGeneratorRef::new(gen.adapter())]
315    }
316
317    fn num_wires(&self) -> usize {
318        4 + 5 * self.num_chunks + (self.chunk_bits() + 1)
319    }
320
321    fn num_constants(&self) -> usize {
322        0
323    }
324
325    fn degree(&self) -> usize {
326        1 << self.chunk_bits()
327    }
328
329    fn num_constraints(&self) -> usize {
330        6 + 5 * self.num_chunks + self.chunk_bits()
331    }
332}
333
334impl<F: RichField + Extendable<D>, const D: usize> PackedEvaluableBase<F, D>
335    for ComparisonGate<F, D>
336{
337    fn eval_unfiltered_base_packed<P: PackedField<Scalar = F>>(
338        &self,
339        vars: EvaluationVarsBasePacked<P>,
340        mut yield_constr: StridedConstraintConsumer<P>,
341    ) {
342        let first_input = vars.local_wires[self.wire_first_input()];
343        let second_input = vars.local_wires[self.wire_second_input()];
344
345        // Get chunks and assert that they match
346        let first_chunks: Vec<_> = (0..self.num_chunks)
347            .map(|i| vars.local_wires[self.wire_first_chunk_val(i)])
348            .collect();
349        let second_chunks: Vec<_> = (0..self.num_chunks)
350            .map(|i| vars.local_wires[self.wire_second_chunk_val(i)])
351            .collect();
352
353        let first_chunks_combined = reduce_with_powers(
354            &first_chunks,
355            F::from_canonical_usize(1 << self.chunk_bits()),
356        );
357        let second_chunks_combined = reduce_with_powers(
358            &second_chunks,
359            F::from_canonical_usize(1 << self.chunk_bits()),
360        );
361
362        yield_constr.one(first_chunks_combined - first_input);
363        yield_constr.one(second_chunks_combined - second_input);
364
365        let chunk_size = 1 << self.chunk_bits();
366
367        let mut most_significant_diff_so_far = P::ZEROS;
368
369        for i in 0..self.num_chunks {
370            // Range-check the chunks to be less than `chunk_size`.
371            let first_product: P = (0..chunk_size)
372                .map(|x| first_chunks[i] - F::from_canonical_usize(x))
373                .product();
374            let second_product: P = (0..chunk_size)
375                .map(|x| second_chunks[i] - F::from_canonical_usize(x))
376                .product();
377            yield_constr.one(first_product);
378            yield_constr.one(second_product);
379
380            let difference = second_chunks[i] - first_chunks[i];
381            let equality_dummy = vars.local_wires[self.wire_equality_dummy(i)];
382            let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)];
383
384            // Two constraints to assert that `chunks_equal` is valid.
385            yield_constr.one(difference * equality_dummy - (P::ONES - chunks_equal));
386            yield_constr.one(chunks_equal * difference);
387
388            // Update `most_significant_diff_so_far`.
389            let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)];
390            yield_constr.one(intermediate_value - chunks_equal * most_significant_diff_so_far);
391            most_significant_diff_so_far =
392                intermediate_value + (P::ONES - chunks_equal) * difference;
393        }
394
395        let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()];
396        yield_constr.one(most_significant_diff - most_significant_diff_so_far);
397
398        let most_significant_diff_bits: Vec<_> = (0..self.chunk_bits() + 1)
399            .map(|i| vars.local_wires[self.wire_most_significant_diff_bit(i)])
400            .collect();
401
402        // Range-check the bits.
403        for &bit in &most_significant_diff_bits {
404            yield_constr.one(bit * (P::ONES - bit));
405        }
406
407        let bits_combined = reduce_with_powers(&most_significant_diff_bits, F::TWO);
408        let two_n = F::from_canonical_u64(1 << self.chunk_bits());
409        yield_constr.one((most_significant_diff + two_n) - bits_combined);
410
411        // Iff first <= second, the top (n + 1st) bit of (2^n - 1 + most_significant_diff) will be 1.
412        let result_bool = vars.local_wires[self.wire_result_bool()];
413        yield_constr.one(result_bool - most_significant_diff_bits[self.chunk_bits()]);
414    }
415}
416
417#[derive(Debug)]
418struct ComparisonGenerator<F: RichField + Extendable<D>, const D: usize> {
419    row: usize,
420    gate: ComparisonGate<F, D>,
421}
422
423impl<F: RichField + Extendable<D>, const D: usize> SimpleGenerator<F, D>
424    for ComparisonGenerator<F, D>
425{
426    fn id(&self) -> String {
427        "ComparisonGenerator".to_string()
428    }
429
430    fn dependencies(&self) -> Vec<Target> {
431        let local_target = |column| Target::wire(self.row, column);
432
433        vec![
434            local_target(self.gate.wire_first_input()),
435            local_target(self.gate.wire_second_input()),
436        ]
437    }
438
439    fn run_once(&self, witness: &PartitionWitness<F>, out_buffer: &mut GeneratedValues<F>) -> Result<()> {
440        let local_wire = |column| Wire {
441            row: self.row,
442            column,
443        };
444
445        let get_local_wire = |column| witness.get_wire(local_wire(column));
446
447        let first_input = get_local_wire(self.gate.wire_first_input());
448        let second_input = get_local_wire(self.gate.wire_second_input());
449
450        let first_input_u64 = first_input.to_canonical_u64();
451        let second_input_u64 = second_input.to_canonical_u64();
452
453        let result = F::from_canonical_usize((first_input_u64 <= second_input_u64) as usize);
454
455        let chunk_size = 1 << self.gate.chunk_bits();
456        let first_input_chunks: Vec<F> = (0..self.gate.num_chunks)
457            .scan(first_input_u64, |acc, _| {
458                let tmp = *acc % chunk_size;
459                *acc /= chunk_size;
460                Some(F::from_canonical_u64(tmp))
461            })
462            .collect();
463        let second_input_chunks: Vec<F> = (0..self.gate.num_chunks)
464            .scan(second_input_u64, |acc, _| {
465                let tmp = *acc % chunk_size;
466                *acc /= chunk_size;
467                Some(F::from_canonical_u64(tmp))
468            })
469            .collect();
470
471        let chunks_equal: Vec<F> = (0..self.gate.num_chunks)
472            .map(|i| F::from_bool(first_input_chunks[i] == second_input_chunks[i]))
473            .collect();
474        let equality_dummies: Vec<F> = first_input_chunks
475            .iter()
476            .zip(second_input_chunks.iter())
477            .map(|(&f, &s)| if f == s { F::ONE } else { F::ONE / (s - f) })
478            .collect();
479
480        let mut most_significant_diff_so_far = F::ZERO;
481        let mut intermediate_values = Vec::new();
482        for i in 0..self.gate.num_chunks {
483            if first_input_chunks[i] != second_input_chunks[i] {
484                most_significant_diff_so_far = second_input_chunks[i] - first_input_chunks[i];
485                intermediate_values.push(F::ZERO);
486            } else {
487                intermediate_values.push(most_significant_diff_so_far);
488            }
489        }
490        let most_significant_diff = most_significant_diff_so_far;
491
492        let two_n = F::from_canonical_usize(1 << self.gate.chunk_bits());
493        let two_n_plus_msd = (two_n + most_significant_diff).to_canonical_u64();
494
495        let msd_bits_u64: Vec<u64> = (0..self.gate.chunk_bits() + 1)
496            .scan(two_n_plus_msd, |acc, _| {
497                let tmp = *acc % 2;
498                *acc /= 2;
499                Some(tmp)
500            })
501            .collect();
502        let msd_bits: Vec<F> = msd_bits_u64
503            .iter()
504            .map(|x| F::from_canonical_u64(*x))
505            .collect();
506
507        out_buffer.set_wire(local_wire(self.gate.wire_result_bool()), result)?;
508        out_buffer.set_wire(
509            local_wire(self.gate.wire_most_significant_diff()),
510            most_significant_diff,
511        )?;
512        for i in 0..self.gate.num_chunks {
513            out_buffer.set_wire(
514                local_wire(self.gate.wire_first_chunk_val(i)),
515                first_input_chunks[i],
516            )?;
517            out_buffer.set_wire(
518                local_wire(self.gate.wire_second_chunk_val(i)),
519                second_input_chunks[i],
520            )?;
521            out_buffer.set_wire(
522                local_wire(self.gate.wire_equality_dummy(i)),
523                equality_dummies[i],
524            )?;
525            out_buffer.set_wire(local_wire(self.gate.wire_chunks_equal(i)), chunks_equal[i])?;
526            out_buffer.set_wire(
527                local_wire(self.gate.wire_intermediate_value(i)),
528                intermediate_values[i],
529            )?;
530        }
531        for i in 0..self.gate.chunk_bits() + 1 {
532            out_buffer.set_wire(
533                local_wire(self.gate.wire_most_significant_diff_bit(i)),
534                msd_bits[i],
535            )?;
536        }
537
538        Ok(())
539    }
540
541    fn serialize(&self, dst: &mut Vec<u8>, common_data: &CommonCircuitData<F, D>) -> IoResult<()> {
542        dst.write_usize(self.row)?;
543        self.gate.serialize(dst, common_data)
544    }
545
546    fn deserialize(src: &mut Buffer, common_data: &CommonCircuitData<F, D>) -> IoResult<Self> {
547        let row = src.read_usize()?;
548        let gate = ComparisonGate::deserialize(src, common_data)?;
549        Ok(Self { row, gate })
550    }
551}
552
553#[cfg(test)]
554mod tests {
555    use anyhow::Result;
556    use plonky2::field::goldilocks_field::GoldilocksField;
557    use plonky2::field::types::{PrimeField64, Sample};
558    use plonky2::gates::gate_testing::{test_eval_fns, test_low_degree};
559    use plonky2::hash::hash_types::HashOut;
560    use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig};
561    use rand::rngs::OsRng;
562    use rand::Rng;
563
564    use super::*;
565
566    #[test]
567    fn wire_indices() {
568        type CG = ComparisonGate<GoldilocksField, 4>;
569        let num_bits = 40;
570        let num_chunks = 5;
571
572        let gate = CG {
573            num_bits,
574            num_chunks,
575            _phantom: PhantomData,
576        };
577
578        assert_eq!(gate.wire_first_input(), 0);
579        assert_eq!(gate.wire_second_input(), 1);
580        assert_eq!(gate.wire_result_bool(), 2);
581        assert_eq!(gate.wire_most_significant_diff(), 3);
582        assert_eq!(gate.wire_first_chunk_val(0), 4);
583        assert_eq!(gate.wire_first_chunk_val(4), 8);
584        assert_eq!(gate.wire_second_chunk_val(0), 9);
585        assert_eq!(gate.wire_second_chunk_val(4), 13);
586        assert_eq!(gate.wire_equality_dummy(0), 14);
587        assert_eq!(gate.wire_equality_dummy(4), 18);
588        assert_eq!(gate.wire_chunks_equal(0), 19);
589        assert_eq!(gate.wire_chunks_equal(4), 23);
590        assert_eq!(gate.wire_intermediate_value(0), 24);
591        assert_eq!(gate.wire_intermediate_value(4), 28);
592        assert_eq!(gate.wire_most_significant_diff_bit(0), 29);
593        assert_eq!(gate.wire_most_significant_diff_bit(8), 37);
594    }
595
596    #[test]
597    fn low_degree() {
598        let num_bits = 40;
599        let num_chunks = 5;
600
601        test_low_degree::<GoldilocksField, _, 4>(ComparisonGate::<_, 4>::new(num_bits, num_chunks))
602    }
603
604    #[test]
605    fn eval_fns() -> Result<()> {
606        let num_bits = 40;
607        let num_chunks = 5;
608        const D: usize = 2;
609        type C = PoseidonGoldilocksConfig;
610        type F = <C as GenericConfig<D>>::F;
611
612        test_eval_fns::<F, C, _, D>(ComparisonGate::<_, 2>::new(num_bits, num_chunks))
613    }
614
615    #[test]
616    fn test_gate_constraint() {
617        const D: usize = 2;
618        type C = PoseidonGoldilocksConfig;
619        type F = <C as GenericConfig<D>>::F;
620        type FF = <C as GenericConfig<D>>::FE;
621
622        let num_bits = 40;
623        let num_chunks = 5;
624        let chunk_bits = num_bits / num_chunks;
625
626        // Returns the local wires for a comparison gate given the two inputs.
627        let get_wires = |first_input: F, second_input: F| -> Vec<FF> {
628            let mut v = Vec::new();
629
630            let first_input_u64 = first_input.to_canonical_u64();
631            let second_input_u64 = second_input.to_canonical_u64();
632
633            let result_bool = F::from_bool(first_input_u64 <= second_input_u64);
634
635            let chunk_size = 1 << chunk_bits;
636            let mut first_input_chunks: Vec<F> = (0..num_chunks)
637                .scan(first_input_u64, |acc, _| {
638                    let tmp = *acc % chunk_size;
639                    *acc /= chunk_size;
640                    Some(F::from_canonical_u64(tmp))
641                })
642                .collect();
643            let mut second_input_chunks: Vec<F> = (0..num_chunks)
644                .scan(second_input_u64, |acc, _| {
645                    let tmp = *acc % chunk_size;
646                    *acc /= chunk_size;
647                    Some(F::from_canonical_u64(tmp))
648                })
649                .collect();
650
651            let mut chunks_equal: Vec<F> = (0..num_chunks)
652                .map(|i| F::from_bool(first_input_chunks[i] == second_input_chunks[i]))
653                .collect();
654            let mut equality_dummies: Vec<F> = first_input_chunks
655                .iter()
656                .zip(second_input_chunks.iter())
657                .map(|(&f, &s)| if f == s { F::ONE } else { F::ONE / (s - f) })
658                .collect();
659
660            let mut most_significant_diff_so_far = F::ZERO;
661            let mut intermediate_values = Vec::new();
662            for i in 0..num_chunks {
663                if first_input_chunks[i] != second_input_chunks[i] {
664                    most_significant_diff_so_far = second_input_chunks[i] - first_input_chunks[i];
665                    intermediate_values.push(F::ZERO);
666                } else {
667                    intermediate_values.push(most_significant_diff_so_far);
668                }
669            }
670            let most_significant_diff = most_significant_diff_so_far;
671
672            let two_n_plus_msd =
673                (1 << chunk_bits) as u64 + most_significant_diff.to_canonical_u64();
674            let mut msd_bits: Vec<F> = (0..chunk_bits + 1)
675                .scan(two_n_plus_msd, |acc, _| {
676                    let tmp = *acc % 2;
677                    *acc /= 2;
678                    Some(F::from_canonical_u64(tmp))
679                })
680                .collect();
681
682            v.push(first_input);
683            v.push(second_input);
684            v.push(result_bool);
685            v.push(most_significant_diff);
686            v.append(&mut first_input_chunks);
687            v.append(&mut second_input_chunks);
688            v.append(&mut equality_dummies);
689            v.append(&mut chunks_equal);
690            v.append(&mut intermediate_values);
691            v.append(&mut msd_bits);
692
693            v.iter().map(|&x| x.into()).collect()
694        };
695
696        let mut rng = OsRng;
697        let max: u64 = 1 << (num_bits - 1);
698        let first_input_u64 = rng.gen_range(0..max);
699        let second_input_u64 = {
700            let mut val = rng.gen_range(0..max);
701            while val < first_input_u64 {
702                val = rng.gen_range(0..max);
703            }
704            val
705        };
706
707        let first_input = F::from_canonical_u64(first_input_u64);
708        let second_input = F::from_canonical_u64(second_input_u64);
709
710        let less_than_gate = ComparisonGate::<F, D> {
711            num_bits,
712            num_chunks,
713            _phantom: PhantomData,
714        };
715        let less_than_vars = EvaluationVars {
716            local_constants: &[],
717            local_wires: &get_wires(first_input, second_input),
718            public_inputs_hash: &HashOut::rand(),
719        };
720        assert!(
721            less_than_gate
722                .eval_unfiltered(less_than_vars)
723                .iter()
724                .all(|x| x.is_zero()),
725            "Gate constraints are not satisfied."
726        );
727
728        let equal_gate = ComparisonGate::<F, D> {
729            num_bits,
730            num_chunks,
731            _phantom: PhantomData,
732        };
733        let equal_vars = EvaluationVars {
734            local_constants: &[],
735            local_wires: &get_wires(first_input, first_input),
736            public_inputs_hash: &HashOut::rand(),
737        };
738        assert!(
739            equal_gate
740                .eval_unfiltered(equal_vars)
741                .iter()
742                .all(|x| x.is_zero()),
743            "Gate constraints are not satisfied."
744        );
745    }
746}