use std::fmt::Debug;
use crate::air::WordAirBuilder;
use num::{BigUint, Zero};
use slop_air::AirBuilder;
use slop_algebra::PrimeField32;
use sp1_core_executor::events::{ByteRecord, FieldOperation};
use sp1_derive::AlignedBorrow;
use sp1_hypercube::air::SP1AirBuilder;
use sp1_primitives::polynomial::Polynomial;
use super::util_air::eval_field_operation;
use sp1_curves::params::{FieldParameters, Limbs};
#[derive(Debug, Clone, AlignedBorrow)]
#[repr(C)]
pub struct FieldOpCols<T, P: FieldParameters> {
pub result: Limbs<T, P::Limbs>,
pub carry: Limbs<T, P::Limbs>,
pub(crate) witness: Limbs<T, P::Witness>,
}
impl<F: PrimeField32, P: FieldParameters> FieldOpCols<F, P> {
#[allow(clippy::too_many_arguments)]
pub fn populate_mul_and_carry(
&mut self,
record: &mut impl ByteRecord,
a: &BigUint,
b: &BigUint,
c: &BigUint,
modulus: &BigUint,
) -> (BigUint, BigUint) {
let mut p_a: Vec<u8> = a.to_bytes_le();
p_a.resize(P::NB_LIMBS, 0);
let mut p_b: Vec<u8> = b.to_bytes_le();
p_b.resize(P::NB_LIMBS, 0);
let mut p_c: Vec<u8> = c.to_bytes_le();
p_c.resize(P::NB_LIMBS, 0);
let mul_add = a * b + c;
let result = &mul_add % modulus;
let carry = (mul_add - &result) / modulus;
debug_assert!(&result < modulus);
debug_assert!(&carry < modulus);
debug_assert_eq!(&carry * modulus, a * b + c - &result);
let mut p_modulus: Vec<u8> = modulus.to_bytes_le();
p_modulus.resize(P::MODULUS_LIMBS, 0);
let mut p_result: Vec<u8> = result.to_bytes_le();
p_result.resize(P::NB_LIMBS, 0);
let mut p_carry: Vec<u8> = carry.to_bytes_le();
p_carry.resize(P::NB_LIMBS, 0);
let mut p_vanishing_limbs = vec![0i32; P::NB_WITNESS_LIMBS + 1];
for i in 0..P::NB_LIMBS {
for j in 0..P::NB_LIMBS {
p_vanishing_limbs[i + j] += (p_a[i] as u16 * p_b[j] as u16) as i32;
}
}
for i in 0..P::NB_LIMBS {
p_vanishing_limbs[i] += (p_c[i] as u16) as i32;
p_vanishing_limbs[i] -= (p_result[i] as u16) as i32;
}
for i in 0..P::NB_LIMBS {
for j in 0..P::MODULUS_LIMBS {
p_vanishing_limbs[i + j] -= (p_carry[i] as u16 * p_modulus[j] as u16) as i32;
}
}
let len = P::NB_WITNESS_LIMBS + 1;
let mut pol_carry = p_vanishing_limbs[len - 1];
for i in (0..len - 1).rev() {
let ai = p_vanishing_limbs[i];
p_vanishing_limbs[i] = pol_carry;
pol_carry = ai + pol_carry * 256;
}
debug_assert_eq!(pol_carry, 0);
for i in 0..P::NB_LIMBS {
self.result[i] = F::from_canonical_u8(p_result[i]);
self.carry[i] = F::from_canonical_u8(p_carry[i]);
}
for i in 0..P::NB_WITNESS_LIMBS {
self.witness[i] =
F::from_canonical_u16((p_vanishing_limbs[i] + P::WITNESS_OFFSET as i32) as u16);
}
record.add_u8_range_checks_field(&self.result.0);
record.add_u8_range_checks_field(&self.carry.0);
record.add_u16_range_checks_field(&self.witness.0);
(result, carry)
}
#[allow(clippy::too_many_arguments)]
pub fn populate_conditional_op_and_carry(
&mut self,
record: &mut impl ByteRecord,
a: &BigUint,
b: &BigUint,
c: &BigUint,
modulus: &BigUint,
is_add: bool,
_is_mul: bool,
) -> (BigUint, BigUint) {
let mut p_a: Vec<u8> = a.to_bytes_le();
p_a.resize(P::NB_LIMBS, 0);
let mut p_b: Vec<u8> = b.to_bytes_le();
p_b.resize(P::NB_LIMBS, 0);
let mut p_c: Vec<u8> = c.to_bytes_le();
p_c.resize(P::NB_LIMBS, 0);
let intermediate = if is_add { a + b + c } else { a * b + c };
let result = &intermediate % modulus;
let carry = (&intermediate - &result) / modulus;
debug_assert!(&result < modulus);
debug_assert!(&carry < modulus);
debug_assert_eq!(&carry * modulus, &intermediate - &result);
let mut p_modulus: Vec<u8> = modulus.to_bytes_le();
p_modulus.resize(P::MODULUS_LIMBS, 0);
let mut p_result: Vec<u8> = result.to_bytes_le();
p_result.resize(P::NB_LIMBS, 0);
let mut p_carry: Vec<u8> = carry.to_bytes_le();
p_carry.resize(P::NB_LIMBS, 0);
let mut p_vanishing_limbs = vec![0i32; P::NB_WITNESS_LIMBS + 1];
if is_add {
for i in 0..P::NB_LIMBS {
p_vanishing_limbs[i] += (p_a[i] as u16 + p_b[i] as u16 + p_c[i] as u16) as i32;
}
} else {
for i in 0..P::NB_LIMBS {
for j in 0..P::NB_LIMBS {
p_vanishing_limbs[i + j] += (p_a[i] as u16 * p_b[j] as u16) as i32;
}
}
for i in 0..P::NB_LIMBS {
p_vanishing_limbs[i] += (p_c[i] as u16) as i32;
}
}
for i in 0..P::NB_LIMBS {
p_vanishing_limbs[i] -= (p_result[i] as u16) as i32;
}
for i in 0..P::NB_LIMBS {
for j in 0..P::MODULUS_LIMBS {
p_vanishing_limbs[i + j] -= (p_carry[i] as u16 * p_modulus[j] as u16) as i32;
}
}
let len = P::NB_WITNESS_LIMBS + 1;
let mut pol_carry = p_vanishing_limbs[len - 1];
for i in (0..len - 1).rev() {
let ai = p_vanishing_limbs[i];
p_vanishing_limbs[i] = pol_carry;
pol_carry = ai + pol_carry * 256;
}
debug_assert_eq!(pol_carry, 0);
for i in 0..P::NB_LIMBS {
self.result[i] = F::from_canonical_u8(p_result[i]);
self.carry[i] = F::from_canonical_u8(p_carry[i]);
}
for i in 0..P::NB_WITNESS_LIMBS {
self.witness[i] =
F::from_canonical_u16((p_vanishing_limbs[i] + P::WITNESS_OFFSET as i32) as u16);
}
record.add_u8_range_checks_field(&self.result.0);
record.add_u8_range_checks_field(&self.carry.0);
record.add_u16_range_checks_field(&self.witness.0);
(result, carry)
}
pub fn populate_carry_and_witness(
&mut self,
a: &BigUint,
b: &BigUint,
op: FieldOperation,
modulus: &BigUint,
) -> BigUint {
let mut p_a: Vec<u8> = a.to_bytes_le();
p_a.resize(P::NB_LIMBS, 0);
let mut p_b: Vec<u8> = b.to_bytes_le();
p_b.resize(P::NB_LIMBS, 0);
let (result, carry) = match op {
FieldOperation::Add => ((a + b) % modulus, (a + b - (a + b) % modulus) / modulus),
FieldOperation::Mul => ((a * b) % modulus, (a * b - (a * b) % modulus) / modulus),
FieldOperation::Sub | FieldOperation::Div => unreachable!(),
};
debug_assert!(&result < modulus);
debug_assert!(&carry < modulus);
match op {
FieldOperation::Add => debug_assert_eq!(&carry * modulus, a + b - &result),
FieldOperation::Mul => debug_assert_eq!(&carry * modulus, a * b - &result),
FieldOperation::Sub | FieldOperation::Div => unreachable!(),
}
let mut p_modulus: Vec<u8> = modulus.to_bytes_le();
p_modulus.resize(P::MODULUS_LIMBS, 0);
let mut p_result: Vec<u8> = result.to_bytes_le();
p_result.resize(P::NB_LIMBS, 0);
let mut p_carry: Vec<u8> = carry.to_bytes_le();
p_carry.resize(P::NB_LIMBS, 0);
let mut p_vanishing = vec![0i32; P::NB_WITNESS_LIMBS + 1];
match op {
FieldOperation::Add => {
for i in 0..P::NB_LIMBS {
p_vanishing[i] += (p_a[i] as u16 + p_b[i] as u16) as i32;
}
}
FieldOperation::Mul => {
for i in 0..P::NB_LIMBS {
for j in 0..P::NB_LIMBS {
p_vanishing[i + j] += (p_a[i] as u16 * p_b[j] as u16) as i32;
}
}
}
FieldOperation::Sub | FieldOperation::Div => unreachable!(),
}
for i in 0..P::NB_LIMBS {
p_vanishing[i] -= p_result[i] as i32;
for j in 0..P::MODULUS_LIMBS {
p_vanishing[i + j] -= (p_carry[i] as u16 * p_modulus[j] as u16) as i32;
}
}
let len = P::NB_WITNESS_LIMBS + 1;
let mut carry = p_vanishing[len - 1];
for i in (0..len - 1).rev() {
let ai = p_vanishing[i];
p_vanishing[i] = carry;
carry = ai + carry * 256;
}
debug_assert_eq!(carry, 0);
for i in 0..P::NB_LIMBS {
self.result[i] = F::from_canonical_u8(p_result[i]);
self.carry[i] = F::from_canonical_u8(p_carry[i]);
}
for i in 0..P::NB_WITNESS_LIMBS {
self.witness[i] =
F::from_canonical_u16((p_vanishing[i] + P::WITNESS_OFFSET as i32) as u16);
}
result
}
#[allow(clippy::too_many_arguments)]
pub fn populate_with_modulus(
&mut self,
record: &mut impl ByteRecord,
a: &BigUint,
b: &BigUint,
modulus: &BigUint,
op: FieldOperation,
) -> BigUint {
if op == FieldOperation::Div {
assert_ne!(*b, BigUint::zero(), "division by zero is not allowed");
assert_ne!(*b, *modulus, "division by zero is not allowed");
}
let result = match op {
FieldOperation::Sub => {
let result = (modulus.clone() + a - b) % modulus;
self.populate_carry_and_witness(&result, b, FieldOperation::Add, modulus);
self.result = P::to_limbs_field::<F, _>(&result);
result
}
FieldOperation::Div => {
cfg_if::cfg_if! {
if #[cfg(feature = "bigint-rug")] {
use sp1_curves::utils::{biguint_to_rug, rug_to_biguint};
let rug_a = biguint_to_rug(a);
let rug_b = biguint_to_rug(b);
let rug_modulus = biguint_to_rug(modulus);
let rug_result = (rug_a
* rug_b.pow_mod(&(rug_modulus.clone() - 2u32), &rug_modulus.clone()).unwrap())
% rug_modulus.clone();
let result = rug_to_biguint(&rug_result);
} else {
let result =
(a * b.modpow(&(modulus.clone() - 2u32), &modulus.clone())) % modulus.clone();
}
}
self.populate_carry_and_witness(&result, b, FieldOperation::Mul, modulus);
self.result = P::to_limbs_field::<F, _>(&result);
result
}
_ => self.populate_carry_and_witness(a, b, op, modulus),
};
record.add_u8_range_checks_field(&self.result.0);
record.add_u8_range_checks_field(&self.carry.0);
record.add_u16_range_checks_field(&self.witness.0);
result
}
pub fn populate(
&mut self,
record: &mut impl ByteRecord,
a: &BigUint,
b: &BigUint,
op: FieldOperation,
) -> BigUint {
self.populate_with_modulus(record, a, b, &P::modulus(), op)
}
}
impl<V: Copy, P: FieldParameters> FieldOpCols<V, P> {
#[allow(clippy::too_many_arguments)]
pub fn eval_variable<AB: SP1AirBuilder<Var = V>>(
&self,
builder: &mut AB,
a: &(impl Into<Polynomial<AB::Expr>> + Clone),
b: &(impl Into<Polynomial<AB::Expr>> + Clone),
modulus: &(impl Into<Polynomial<AB::Expr>> + Clone),
is_add: impl Into<AB::Expr> + Clone,
is_sub: impl Into<AB::Expr> + Clone,
is_mul: impl Into<AB::Expr> + Clone,
is_div: impl Into<AB::Expr> + Clone,
is_real: impl Into<AB::Expr> + Clone,
) where
V: Into<AB::Expr>,
Limbs<V, P::Limbs>: Copy,
{
let p_a_param: Polynomial<AB::Expr> = (a).clone().into();
let p_b: Polynomial<AB::Expr> = (b).clone().into();
let p_res_param: Polynomial<AB::Expr> = self.result.into();
let is_add: AB::Expr = is_add.into();
let is_sub: AB::Expr = is_sub.into();
let is_mul: AB::Expr = is_mul.into();
let is_div: AB::Expr = is_div.into();
let p_result = p_res_param.clone() * (is_add.clone() + is_mul.clone())
+ p_a_param.clone() * (is_sub.clone() + is_div.clone());
let p_add = p_a_param.clone() + p_b.clone();
let p_sub = p_res_param.clone() + p_b.clone();
let p_mul = p_a_param.clone() * p_b.clone();
let p_div = p_res_param * p_b.clone();
let p_op = p_add * is_add + p_sub * is_sub + p_mul * is_mul + p_div * is_div;
self.eval_with_polynomials(builder, p_op, modulus.clone(), p_result, is_real);
}
#[allow(clippy::too_many_arguments)]
pub fn eval_add_mul_and_carry<AB: SP1AirBuilder<Var = V>>(
&self,
builder: &mut AB,
is_add: impl Into<AB::Expr> + Clone,
is_mul: impl Into<AB::Expr> + Clone,
a: &(impl Into<Polynomial<AB::Expr>> + Clone),
b: &(impl Into<Polynomial<AB::Expr>> + Clone),
c: &(impl Into<Polynomial<AB::Expr>> + Clone),
modulus: &(impl Into<Polynomial<AB::Expr>> + Clone),
is_real: impl Into<AB::Expr> + Clone,
) where
V: Into<AB::Expr>,
Limbs<V, P::Limbs>: Copy,
{
let p_a: Polynomial<AB::Expr> = (a).clone().into();
let p_b: Polynomial<AB::Expr> = (b).clone().into();
let p_c: Polynomial<AB::Expr> = (c).clone().into();
let is_add: AB::Expr = is_add.into();
let is_mul: AB::Expr = is_mul.into();
let p_result: Polynomial<_> = self.result.into();
let p_op = (p_a.clone() + p_b.clone()) * is_add + (p_a * p_b) * is_mul + p_c;
self.eval_with_polynomials(builder, p_op, modulus.clone(), p_result, is_real);
}
#[allow(clippy::too_many_arguments)]
pub fn eval_mul_and_carry<AB: SP1AirBuilder<Var = V>>(
&self,
builder: &mut AB,
a: &(impl Into<Polynomial<AB::Expr>> + Clone),
b: &(impl Into<Polynomial<AB::Expr>> + Clone),
c: &(impl Into<Polynomial<AB::Expr>> + Clone),
modulus: &(impl Into<Polynomial<AB::Expr>> + Clone),
is_real: impl Into<AB::Expr> + Clone,
) where
V: Into<AB::Expr>,
Limbs<V, P::Limbs>: Copy,
{
let p_a: Polynomial<AB::Expr> = (a).clone().into();
let p_b: Polynomial<AB::Expr> = (b).clone().into();
let p_c: Polynomial<AB::Expr> = (c).clone().into();
let p_result: Polynomial<_> = self.result.into();
let p_op = p_a * p_b + p_c;
self.eval_with_polynomials(builder, p_op, modulus.clone(), p_result, is_real);
}
#[allow(clippy::too_many_arguments)]
pub fn eval_with_modulus<AB: SP1AirBuilder<Var = V>>(
&self,
builder: &mut AB,
a: &(impl Into<Polynomial<AB::Expr>> + Clone),
b: &(impl Into<Polynomial<AB::Expr>> + Clone),
modulus: &(impl Into<Polynomial<AB::Expr>> + Clone),
op: FieldOperation,
is_real: impl Into<AB::Expr> + Clone,
) where
V: Into<AB::Expr>,
Limbs<V, P::Limbs>: Copy,
{
let p_a_param: Polynomial<AB::Expr> = (a).clone().into();
let p_b: Polynomial<AB::Expr> = (b).clone().into();
let (p_a, p_result): (Polynomial<_>, Polynomial<_>) = match op {
FieldOperation::Add | FieldOperation::Mul => (p_a_param, self.result.into()),
FieldOperation::Sub | FieldOperation::Div => (self.result.into(), p_a_param),
};
let p_op: Polynomial<<AB as AirBuilder>::Expr> = match op {
FieldOperation::Add | FieldOperation::Sub => p_a + p_b,
FieldOperation::Mul | FieldOperation::Div => p_a * p_b,
};
self.eval_with_polynomials(builder, p_op, modulus.clone(), p_result, is_real);
}
#[allow(clippy::too_many_arguments)]
pub fn eval_with_polynomials<AB: SP1AirBuilder<Var = V>>(
&self,
builder: &mut AB,
op: impl Into<Polynomial<AB::Expr>>,
modulus: impl Into<Polynomial<AB::Expr>>,
result: impl Into<Polynomial<AB::Expr>>,
is_real: impl Into<AB::Expr> + Clone,
) where
V: Into<AB::Expr>,
Limbs<V, P::Limbs>: Copy,
{
let p_op: Polynomial<AB::Expr> = op.into();
let p_result: Polynomial<AB::Expr> = result.into();
let p_modulus: Polynomial<AB::Expr> = modulus.into();
let p_carry: Polynomial<<AB as AirBuilder>::Expr> = self.carry.into();
let p_op_minus_result: Polynomial<AB::Expr> = p_op - &p_result;
let p_vanishing = p_op_minus_result - &(&p_carry * &p_modulus);
let p_witness = self.witness.0.iter().into();
eval_field_operation::<AB, P>(builder, &p_vanishing, &p_witness);
builder.slice_range_check_u8(&self.result.0, is_real.clone());
builder.slice_range_check_u8(&self.carry.0, is_real.clone());
builder.slice_range_check_u16(p_witness.coefficients(), is_real.clone());
}
#[allow(clippy::too_many_arguments)]
pub fn eval<AB: SP1AirBuilder<Var = V>>(
&self,
builder: &mut AB,
a: &(impl Into<Polynomial<AB::Expr>> + Clone),
b: &(impl Into<Polynomial<AB::Expr>> + Clone),
op: FieldOperation,
is_real: impl Into<AB::Expr> + Clone,
) where
V: Into<AB::Expr>,
Limbs<V, P::Limbs>: Copy,
{
let p_limbs = Polynomial::from_iter(P::modulus_field_iter::<AB::F>().map(AB::Expr::from));
self.eval_with_modulus::<AB>(builder, a, b, &p_limbs, op, is_real);
}
}