chore: implement Gt and GtEq as syntactic sugar (#216)

* Implement Gt and GtEq as syntactic sugar

* Update src/backends/plonky2/circuits/mainpod.rs

Co-authored-by: Eduard S. <eduardsanou@posteo.net>

* Op verification circuit refactor

* Code review

* Add range check to Eq case of LtEq

* Style

* Factor out ValueOf statement argument type checks

* Formatting

* Clean-up

* Safety

* Take sign into account

* Simplify sign check

---------

Co-authored-by: Eduard S. <eduardsanou@posteo.net>
This commit is contained in:
Ahmad Afuni 2025-05-06 06:59:59 +10:00 committed by GitHub
parent e420aa7b32
commit 53ade6ea26
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 451 additions and 170 deletions

View file

@ -30,6 +30,7 @@ use crate::{
};
pub const CODE_SIZE: usize = HASH_SIZE + 2;
const NUM_BITS: usize = 32;
#[derive(Copy, Clone)]
pub struct ValueTarget {
@ -102,6 +103,13 @@ impl StatementArgTarget {
) -> Self {
Self::new(*pod_id, *key)
}
/// StatementArgTarget to ValueTarget coercion. Make sure to check
/// that the arg is a value using the `statement_arg_is_value` method
/// first!
pub fn as_value(&self) -> ValueTarget {
ValueTarget::from_slice(&self.elements[..VALUE_SIZE])
}
}
#[derive(Clone)]
@ -300,9 +308,17 @@ pub trait CircuitBuilderPod<F: RichField + Extendable<D>, const D: usize> {
// Convenience methods for checking values.
/// Checks whether `xs` is right-padded with 0s so as to represent a `Value`.
fn statement_arg_is_value(&mut self, arg: &StatementArgTarget) -> BoolTarget;
/// Checks whether `x < y` if `b` is true. This involves checking
/// that `x` and `y` each consist of two `u32` limbs.
fn assert_less_if(&mut self, b: BoolTarget, x: ValueTarget, y: ValueTarget);
/// Checks whether `x` is an i64, which involves checking that it
/// consists of two `u32` limbs.
fn assert_i64(&mut self, x: ValueTarget);
/// Checks whether an i64 is negative.
fn i64_is_negative(&mut self, x: ValueTarget) -> BoolTarget;
/// Checks whether `x < y` if `b` is true. This assumes that `x`
/// and `y` each consist of two `u32` limbs.
fn assert_i64_less_if(&mut self, b: BoolTarget, x: ValueTarget, y: ValueTarget);
// Convenience methods for accessing and connecting elements of
// (vectors of) flattenables.
@ -389,14 +405,34 @@ impl CircuitBuilderPod<F, D> for CircuitBuilder<F, D> {
self.is_equal_slice(&arg.elements[VALUE_SIZE..], &zeros)
}
fn assert_less_if(&mut self, b: BoolTarget, x: ValueTarget, y: ValueTarget) {
const NUM_BITS: usize = 32;
fn assert_i64(&mut self, x: ValueTarget) {
// `x` should only have two limbs.
x.elements
.into_iter()
.skip(2)
.for_each(|l| self.assert_zero(l));
// Lt assertion with 32-bit range check.
// 32-bit range check.
self.range_check(x.elements[0], NUM_BITS);
self.range_check(x.elements[1], NUM_BITS);
}
fn i64_is_negative(&mut self, x: ValueTarget) -> BoolTarget {
// x is negative if the most significant bit of its most
// significant limb is 1.
let high_bits = self.split_le(x.elements[1], NUM_BITS);
high_bits[31]
}
fn assert_i64_less_if(&mut self, b: BoolTarget, x: ValueTarget, y: ValueTarget) {
// If b is false, replace `x` and `y` with dummy values.
let zero = ValueTarget::zero(self);
let one = ValueTarget::one(self);
let x = self.select_value(b, x, zero);
let y = self.select_value(b, y, one);
// Lt assertion.
let assert_limb_lt = |builder: &mut Self, x, y| {
// Check that targets fit within `NUM_BITS` bits.
builder.range_check(x, NUM_BITS);
builder.range_check(y, NUM_BITS);
// Check that `y-1-x` fits within `NUM_BITS` bits.
let one = builder.one();
let y_minus_one = builder.sub(y, one);
@ -404,21 +440,14 @@ impl CircuitBuilderPod<F, D> for CircuitBuilder<F, D> {
builder.range_check(expr, NUM_BITS);
};
// If b is false, replace `x` and `y` with dummy values.
let zero = ValueTarget::zero(self);
let one = ValueTarget::one(self);
let x = self.select_value(b, x, zero);
let y = self.select_value(b, y, one);
// `x` and `y` should only have two limbs each.
x.elements
.into_iter()
.skip(2)
.for_each(|l| self.assert_zero(l));
y.elements
.into_iter()
.skip(2)
.for_each(|l| self.assert_zero(l));
// Check if `x` and `y` have the same sign. If not, swap.
let x_is_negative = self.i64_is_negative(x);
let y_is_negative = self.i64_is_negative(y);
let same_sign_ind = self.is_equal(x_is_negative.target, y_is_negative.target);
let (x, y) = (
self.select_value(same_sign_ind, x, y),
self.select_value(same_sign_ind, y, x),
);
let big_limbs_eq = self.is_equal(x.elements[1], y.elements[1]);
let lhs = self.select(big_limbs_eq, x.elements[0], x.elements[1]);