arnaucube
arnaucube/plonky2-u32Public Self-hosted repository
plonky2-u32/src/gadgets/multiple_comparison.rs
src/gadgets/multiple_comparison.rsRaw
  1use alloc::vec;
  2use alloc::vec::Vec;
  3
  4use plonky2::field::extension::Extendable;
  5use plonky2::hash::hash_types::RichField;
  6use plonky2::iop::target::{BoolTarget, Target};
  7use plonky2::plonk::circuit_builder::CircuitBuilder;
  8use plonky2::util::ceil_div_usize;
  9
 10use crate::gadgets::arithmetic_u32::U32Target;
 11use crate::gates::comparison::ComparisonGate;
 12
 13/// Returns true if a is less than or equal to b, considered as base-`2^num_bits` limbs of a large value.
 14/// This range-checks its inputs.
 15pub fn list_le_circuit<F: RichField + Extendable<D>, const D: usize>(
 16    builder: &mut CircuitBuilder<F, D>,
 17    a: Vec<Target>,
 18    b: Vec<Target>,
 19    num_bits: usize,
 20) -> BoolTarget {
 21    assert_eq!(
 22        a.len(),
 23        b.len(),
 24        "Comparison must be between same number of inputs and outputs"
 25    );
 26    let n = a.len();
 27
 28    let chunk_bits = 2;
 29    let num_chunks = ceil_div_usize(num_bits, chunk_bits);
 30
 31    let one = builder.one();
 32    let mut result = one;
 33    for i in 0..n {
 34        let a_le_b_gate = ComparisonGate::new(num_bits, num_chunks);
 35        let a_le_b_row = builder.add_gate(a_le_b_gate.clone(), vec![]);
 36        builder.connect(
 37            Target::wire(a_le_b_row, a_le_b_gate.wire_first_input()),
 38            a[i],
 39        );
 40        builder.connect(
 41            Target::wire(a_le_b_row, a_le_b_gate.wire_second_input()),
 42            b[i],
 43        );
 44        let a_le_b_result = Target::wire(a_le_b_row, a_le_b_gate.wire_result_bool());
 45
 46        let b_le_a_gate = ComparisonGate::new(num_bits, num_chunks);
 47        let b_le_a_row = builder.add_gate(b_le_a_gate.clone(), vec![]);
 48        builder.connect(
 49            Target::wire(b_le_a_row, b_le_a_gate.wire_first_input()),
 50            b[i],
 51        );
 52        builder.connect(
 53            Target::wire(b_le_a_row, b_le_a_gate.wire_second_input()),
 54            a[i],
 55        );
 56        let b_le_a_result = Target::wire(b_le_a_row, b_le_a_gate.wire_result_bool());
 57
 58        let these_limbs_equal = builder.mul(a_le_b_result, b_le_a_result);
 59        let these_limbs_less_than = builder.sub(one, b_le_a_result);
 60        result = builder.mul_add(these_limbs_equal, result, these_limbs_less_than);
 61    }
 62
 63    // `result` being boolean is an invariant, maintained because its new value is always
 64    // `x * result + y`, where `x` and `y` are booleans that are not simultaneously true.
 65    BoolTarget::new_unsafe(result)
 66}
 67
 68/// Helper function for comparing, specifically, lists of `U32Target`s.
 69pub fn list_le_u32_circuit<F: RichField + Extendable<D>, const D: usize>(
 70    builder: &mut CircuitBuilder<F, D>,
 71    a: Vec<U32Target>,
 72    b: Vec<U32Target>,
 73) -> BoolTarget {
 74    let a_targets: Vec<Target> = a.iter().map(|&t| t.0).collect();
 75    let b_targets: Vec<Target> = b.iter().map(|&t| t.0).collect();
 76
 77    list_le_circuit(builder, a_targets, b_targets, 32)
 78}
 79
 80#[cfg(test)]
 81mod tests {
 82    use anyhow::Result;
 83    use num::BigUint;
 84    use plonky2::field::types::Field;
 85    use plonky2::iop::witness::PartialWitness;
 86    use plonky2::plonk::circuit_data::CircuitConfig;
 87    use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig};
 88    use rand::rngs::OsRng;
 89    use rand::Rng;
 90
 91    use super::*;
 92
 93    fn test_list_le(size: usize, num_bits: usize) -> Result<()> {
 94        const D: usize = 2;
 95        type C = PoseidonGoldilocksConfig;
 96        type F = <C as GenericConfig<D>>::F;
 97        let config = CircuitConfig::standard_recursion_config();
 98        let pw = PartialWitness::new();
 99        let mut builder = CircuitBuilder::<F, D>::new(config);
100
101        let mut rng = OsRng;
102
103        let lst1: Vec<u64> = (0..size)
104            .map(|_| rng.gen_range(0..(1 << num_bits)))
105            .collect();
106        let lst2: Vec<u64> = (0..size)
107            .map(|_| rng.gen_range(0..(1 << num_bits)))
108            .collect();
109
110        let a_biguint = BigUint::from_slice(
111            &lst1
112                .iter()
113                .flat_map(|&x| [x as u32, (x >> 32) as u32])
114                .collect::<Vec<_>>(),
115        );
116        let b_biguint = BigUint::from_slice(
117            &lst2
118                .iter()
119                .flat_map(|&x| [x as u32, (x >> 32) as u32])
120                .collect::<Vec<_>>(),
121        );
122
123        let a = lst1
124            .iter()
125            .map(|&x| builder.constant(F::from_canonical_u64(x)))
126            .collect();
127        let b = lst2
128            .iter()
129            .map(|&x| builder.constant(F::from_canonical_u64(x)))
130            .collect();
131
132        let result = list_le_circuit(&mut builder, a, b, num_bits);
133
134        let expected_result = builder.constant_bool(a_biguint <= b_biguint);
135        builder.connect(result.target, expected_result.target);
136
137        let data = builder.build::<C>();
138        let proof = data.prove(pw).unwrap();
139        data.verify(proof)
140    }
141
142    #[test]
143    fn test_multiple_comparison() -> Result<()> {
144        for size in [1, 3, 6] {
145            for num_bits in [20, 32, 40, 44] {
146                test_list_le(size, num_bits).unwrap();
147            }
148        }
149
150        Ok(())
151    }
152}