2022-10-21 16:25:38 +11:00
|
|
|
//! Support for the EVM modular instructions ADDMOD, MULMOD and MOD,
|
|
|
|
|
//! as well as DIV.
|
2022-10-07 17:15:50 +11:00
|
|
|
//!
|
|
|
|
|
//! This crate verifies an EVM modular instruction, which takes three
|
|
|
|
|
//! 256-bit inputs A, B and M, and produces a 256-bit output C satisfying
|
|
|
|
|
//!
|
|
|
|
|
//! C = operation(A, B) (mod M).
|
|
|
|
|
//!
|
|
|
|
|
//! where operation can be addition, multiplication, or just return
|
|
|
|
|
//! the first argument (for MOD). Inputs A, B and M, and output C,
|
|
|
|
|
//! are given as arrays of 16-bit limbs. For example, if the limbs of
|
|
|
|
|
//! A are a[0]...a[15], then
|
|
|
|
|
//!
|
|
|
|
|
//! A = \sum_{i=0}^15 a[i] β^i,
|
|
|
|
|
//!
|
|
|
|
|
//! where β = 2^16 = 2^LIMB_BITS. To verify that A, B, M and C satisfy
|
|
|
|
|
//! the equation we proceed as follows. Define
|
|
|
|
|
//!
|
|
|
|
|
//! a(x) = \sum_{i=0}^15 a[i] x^i
|
|
|
|
|
//!
|
|
|
|
|
//! (so A = a(β)) and similarly for b(x), m(x) and c(x). Then
|
2022-10-11 18:59:02 +11:00
|
|
|
//! operation(A,B) = C (mod M) if and only if there exists q such that
|
|
|
|
|
//! the polynomial
|
2022-10-07 17:15:50 +11:00
|
|
|
//!
|
|
|
|
|
//! operation(a(x), b(x)) - c(x) - m(x) * q(x)
|
|
|
|
|
//!
|
2022-10-11 18:59:02 +11:00
|
|
|
//! is zero when evaluated at x = β, i.e. it is divisible by (x - β);
|
|
|
|
|
//! equivalently, there exists a polynomial s such that
|
2022-10-07 17:15:50 +11:00
|
|
|
//!
|
|
|
|
|
//! operation(a(x), b(x)) - c(x) - m(x) * q(x) - (x - β) * s(x) == 0
|
|
|
|
|
//!
|
|
|
|
|
//! if and only if operation(A,B) = C (mod M). In the code below, this
|
|
|
|
|
//! "constraint polynomial" is constructed in the variable
|
|
|
|
|
//! `constr_poly`. It must be identically zero for the modular
|
|
|
|
|
//! operation to be verified, or, equivalently, each of its
|
|
|
|
|
//! coefficients must be zero. The variable names of the constituent
|
|
|
|
|
//! polynomials are (writing N for N_LIMBS=16):
|
|
|
|
|
//!
|
2022-10-11 18:59:02 +11:00
|
|
|
//! a(x) = \sum_{i=0}^{N-1} input0[i] * x^i
|
|
|
|
|
//! b(x) = \sum_{i=0}^{N-1} input1[i] * x^i
|
|
|
|
|
//! c(x) = \sum_{i=0}^{N-1} output[i] * x^i
|
|
|
|
|
//! m(x) = \sum_{i=0}^{N-1} modulus[i] * x^i
|
|
|
|
|
//! q(x) = \sum_{i=0}^{2N-1} quot[i] * x^i
|
|
|
|
|
//! s(x) = \sum_i^{2N-2} aux[i] * x^i
|
2022-10-07 17:15:50 +11:00
|
|
|
//!
|
|
|
|
|
//! Because A, B, M and C are 256-bit numbers, the degrees of a, b, m
|
|
|
|
|
//! and c are (at most) N-1 = 15. If m = 1, then Q would be A*B which
|
|
|
|
|
//! can be up to 2^512 - ε, so deg(q) can be up to 2*N-1 = 31. Note
|
|
|
|
|
//! that, although for arbitrary m and q we might have deg(m*q) = 3*N-2,
|
|
|
|
|
//! because the magnitude of M*Q must match that of operation(A,B), we
|
|
|
|
|
//! always have deg(m*q) <= 2*N-1. Finally, in order for all the degrees
|
|
|
|
|
//! to match, we have deg(s) <= 2*N-2 = 30.
|
|
|
|
|
//!
|
|
|
|
|
//! -*-
|
|
|
|
|
//!
|
|
|
|
|
//! To verify that the output is reduced, that is, output < modulus,
|
|
|
|
|
//! the prover supplies the value `out_aux_red` which must satisfy
|
|
|
|
|
//!
|
|
|
|
|
//! output - modulus = out_aux_red + 2^256
|
|
|
|
|
//!
|
|
|
|
|
//! and these values are passed to the "less than" operation.
|
|
|
|
|
//!
|
|
|
|
|
//! -*-
|
|
|
|
|
//!
|
|
|
|
|
//! The EVM defines division by zero as zero. We handle this as
|
|
|
|
|
//! follows:
|
|
|
|
|
//!
|
|
|
|
|
//! The prover supplies a binary value `mod_is_zero` which is one if
|
|
|
|
|
//! the modulus is zero and zero otherwise. This is verified, then
|
|
|
|
|
//! added to the modulus (this can't overflow, as modulus[0] was
|
|
|
|
|
//! range-checked and mod_is_zero is 0 or 1). The rest of the
|
|
|
|
|
//! calculation proceeds as if modulus was actually 1; this correctly
|
|
|
|
|
//! verifies that the output is zero, as required by the standard.
|
|
|
|
|
//! To summarise:
|
|
|
|
|
//!
|
|
|
|
|
//! - mod_is_zero is 0 or 1
|
|
|
|
|
//! - if mod_is_zero is 1, then
|
|
|
|
|
//! - given modulus is 0
|
|
|
|
|
//! - updated modulus is 1, which forces the correct output of 0
|
|
|
|
|
//! - if mod_is_zero is 0, then
|
|
|
|
|
//! - given modulus can be 0 or non-zero
|
|
|
|
|
//! - updated modulus is same as given
|
|
|
|
|
//! - if modulus is non-zero, correct output is obtained
|
|
|
|
|
//! - if modulus is 0, then the test output < modulus, checking that
|
|
|
|
|
//! the output is reduced, will fail, because output is non-negative.
|
2022-10-21 16:25:38 +11:00
|
|
|
//!
|
|
|
|
|
//! In the case of DIV, we do something similar, except that we "replace"
|
|
|
|
|
//! the modulus with "2^256" to force the quotient to be zero.
|
2022-12-02 17:11:31 +11:00
|
|
|
//!
|
|
|
|
|
//! -*-
|
|
|
|
|
//!
|
|
|
|
|
//! NB: The implementation uses 9 * N_LIMBS = 144 columns because of
|
|
|
|
|
//! the requirements of the general purpose MULMOD; since ADDMOD,
|
|
|
|
|
//! SUBMOD, MOD and DIV are currently implemented in terms of the
|
|
|
|
|
//! general modular code, they also take 144 columns. Possible
|
|
|
|
|
//! improvements:
|
|
|
|
|
//!
|
|
|
|
|
//! - We could reduce the number of columns to 112 for ADDMOD, SUBMOD,
|
|
|
|
|
//! etc. if they were implemented separately, so they don't pay the
|
|
|
|
|
//! full cost of the general MULMOD.
|
|
|
|
|
//!
|
|
|
|
|
//! - All these operations could have alternative forms where the
|
|
|
|
|
//! output was not guaranteed to be reduced, which is often sufficient
|
|
|
|
|
//! in practice, and which would save a further 16 columns.
|
|
|
|
|
//!
|
|
|
|
|
//! - If the modulus is known in advance (such as for elliptic curve
|
|
|
|
|
//! arithmetic), specialised handling of MULMOD in that case would
|
|
|
|
|
//! only require 96 columns, or 80 if the output doesn't need to be
|
|
|
|
|
//! reduced.
|
2022-10-07 17:15:50 +11:00
|
|
|
|
2023-02-10 23:07:57 +11:00
|
|
|
use ethereum_types::U256;
|
2022-11-02 17:50:31 -07:00
|
|
|
use num::bigint::Sign;
|
|
|
|
|
use num::{BigInt, One, Zero};
|
2022-10-07 17:15:50 +11:00
|
|
|
use plonky2::field::extension::Extendable;
|
|
|
|
|
use plonky2::field::packed::PackedField;
|
2023-02-10 23:07:57 +11:00
|
|
|
use plonky2::field::types::{Field, PrimeField64};
|
2022-10-07 17:15:50 +11:00
|
|
|
use plonky2::hash::hash_types::RichField;
|
|
|
|
|
use plonky2::iop::ext_target::ExtensionTarget;
|
|
|
|
|
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
|
|
|
|
|
|
|
|
|
use super::columns;
|
2023-02-10 23:07:57 +11:00
|
|
|
use crate::arithmetic::addcy::{eval_ext_circuit_addcy, eval_packed_generic_addcy};
|
2022-10-07 17:15:50 +11:00
|
|
|
use crate::arithmetic::columns::*;
|
|
|
|
|
use crate::arithmetic::utils::*;
|
|
|
|
|
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
|
|
|
|
|
|
2022-10-20 04:46:01 +11:00
|
|
|
/// Convert the base-2^16 representation of a number into a BigInt.
|
2022-10-07 17:15:50 +11:00
|
|
|
///
|
2022-10-20 04:46:01 +11:00
|
|
|
/// Given `N` signed (16 + ε)-bit values in `limbs`, return the BigInt
|
2022-10-07 17:15:50 +11:00
|
|
|
///
|
|
|
|
|
/// \sum_{i=0}^{N-1} limbs[i] * β^i.
|
|
|
|
|
///
|
2022-10-20 04:46:01 +11:00
|
|
|
/// This is basically "evaluate the given polynomial at β". Although
|
|
|
|
|
/// the input type is i64, the values must always be in (-2^16 - ε,
|
|
|
|
|
/// 2^16 + ε) because of the caller's range check on the inputs (the ε
|
|
|
|
|
/// allows us to convert calculated output, which can be bigger than
|
|
|
|
|
/// 2^16).
|
|
|
|
|
fn columns_to_bigint<const N: usize>(limbs: &[i64; N]) -> BigInt {
|
2022-10-07 17:15:50 +11:00
|
|
|
const BASE: i64 = 1i64 << LIMB_BITS;
|
|
|
|
|
|
2022-10-20 04:46:01 +11:00
|
|
|
let mut pos_limbs_u32 = Vec::with_capacity(N / 2 + 1);
|
|
|
|
|
let mut neg_limbs_u32 = Vec::with_capacity(N / 2 + 1);
|
2022-10-07 17:15:50 +11:00
|
|
|
let mut cy = 0i64; // cy is necessary to handle ε > 0
|
|
|
|
|
for i in 0..(N / 2) {
|
|
|
|
|
let t = cy + limbs[2 * i] + BASE * limbs[2 * i + 1];
|
2022-10-20 04:46:01 +11:00
|
|
|
pos_limbs_u32.push(if t > 0 { t as u32 } else { 0u32 });
|
|
|
|
|
neg_limbs_u32.push(if t < 0 { -t as u32 } else { 0u32 });
|
|
|
|
|
cy = t / (1i64 << 32);
|
2022-10-07 17:15:50 +11:00
|
|
|
}
|
|
|
|
|
if N & 1 != 0 {
|
|
|
|
|
// If N is odd we need to add the last limb on its own
|
|
|
|
|
let t = cy + limbs[N - 1];
|
2022-10-20 04:46:01 +11:00
|
|
|
pos_limbs_u32.push(if t > 0 { t as u32 } else { 0u32 });
|
|
|
|
|
neg_limbs_u32.push(if t < 0 { -t as u32 } else { 0u32 });
|
|
|
|
|
cy = t / (1i64 << 32);
|
2022-10-07 17:15:50 +11:00
|
|
|
}
|
2022-10-20 04:46:01 +11:00
|
|
|
pos_limbs_u32.push(if cy > 0 { cy as u32 } else { 0u32 });
|
|
|
|
|
neg_limbs_u32.push(if cy < 0 { -cy as u32 } else { 0u32 });
|
2022-10-07 17:15:50 +11:00
|
|
|
|
2022-10-20 04:46:01 +11:00
|
|
|
let pos = BigInt::from_slice(Sign::Plus, &pos_limbs_u32);
|
|
|
|
|
let neg = BigInt::from_slice(Sign::Plus, &neg_limbs_u32);
|
|
|
|
|
pos - neg
|
2022-10-07 17:15:50 +11:00
|
|
|
}
|
|
|
|
|
|
2022-10-20 04:46:01 +11:00
|
|
|
/// Convert a BigInt into a base-2^16 representation.
|
2022-10-07 17:15:50 +11:00
|
|
|
///
|
2022-10-20 04:46:01 +11:00
|
|
|
/// Given a BigInt `num`, return an array of `N` signed 16-bit
|
2022-10-07 17:15:50 +11:00
|
|
|
/// values, say `limbs`, such that
|
|
|
|
|
///
|
|
|
|
|
/// num = \sum_{i=0}^{N-1} limbs[i] * β^i.
|
|
|
|
|
///
|
|
|
|
|
/// Note that `N` must be at least ceil(log2(num)/16) in order to be
|
|
|
|
|
/// big enough to hold `num`.
|
2022-10-20 04:46:01 +11:00
|
|
|
fn bigint_to_columns<const N: usize>(num: &BigInt) -> [i64; N] {
|
2022-10-07 17:15:50 +11:00
|
|
|
assert!(num.bits() <= 16 * N as u64);
|
|
|
|
|
let mut output = [0i64; N];
|
|
|
|
|
for (i, limb) in num.iter_u32_digits().enumerate() {
|
|
|
|
|
output[2 * i] = limb as u16 as i64;
|
|
|
|
|
output[2 * i + 1] = (limb >> LIMB_BITS) as i64;
|
|
|
|
|
}
|
2022-10-20 04:46:01 +11:00
|
|
|
if num.sign() == Sign::Minus {
|
|
|
|
|
for c in output.iter_mut() {
|
|
|
|
|
*c = -*c;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-10-07 17:15:50 +11:00
|
|
|
output
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Generate the output and auxiliary values for given `operation`.
|
|
|
|
|
///
|
|
|
|
|
/// NB: `operation` can set the higher order elements in its result to
|
|
|
|
|
/// zero if they are not used.
|
2023-02-10 23:07:57 +11:00
|
|
|
fn generate_modular_op<F: PrimeField64>(
|
2023-01-31 02:23:24 +11:00
|
|
|
lv: &mut [F],
|
|
|
|
|
nv: &mut [F],
|
2022-10-21 16:25:38 +11:00
|
|
|
filter: usize,
|
2022-10-07 17:15:50 +11:00
|
|
|
operation: fn([i64; N_LIMBS], [i64; N_LIMBS]) -> [i64; 2 * N_LIMBS - 1],
|
|
|
|
|
) {
|
|
|
|
|
// Inputs are all range-checked in [0, 2^16), so the "as i64"
|
|
|
|
|
// conversion is safe.
|
2022-12-02 17:11:31 +11:00
|
|
|
|
2022-10-12 02:39:13 +11:00
|
|
|
let input0_limbs = read_value_i64_limbs(lv, MODULAR_INPUT_0);
|
|
|
|
|
let input1_limbs = read_value_i64_limbs(lv, MODULAR_INPUT_1);
|
|
|
|
|
let mut modulus_limbs = read_value_i64_limbs(lv, MODULAR_MODULUS);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
2022-10-20 04:46:01 +11:00
|
|
|
// BigInts are just used to avoid having to implement modular
|
|
|
|
|
// reduction.
|
|
|
|
|
let mut modulus = columns_to_bigint(&modulus_limbs);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
// constr_poly is initialised to the calculated input, and is
|
2022-10-20 04:46:01 +11:00
|
|
|
// used as such for the BigInt reduction; later, other values are
|
2022-10-07 17:15:50 +11:00
|
|
|
// added/subtracted, which is where its meaning as the "constraint
|
|
|
|
|
// polynomial" comes in.
|
|
|
|
|
let mut constr_poly = [0i64; 2 * N_LIMBS];
|
|
|
|
|
constr_poly[..2 * N_LIMBS - 1].copy_from_slice(&operation(input0_limbs, input1_limbs));
|
|
|
|
|
|
2023-02-10 23:07:57 +11:00
|
|
|
// two_exp_256 == 2^256
|
|
|
|
|
let two_exp_256 = {
|
|
|
|
|
let mut t = BigInt::zero();
|
|
|
|
|
t.set_bit(256, true);
|
|
|
|
|
t
|
|
|
|
|
};
|
|
|
|
|
|
2022-10-12 02:39:13 +11:00
|
|
|
let mut mod_is_zero = F::ZERO;
|
2022-10-07 17:15:50 +11:00
|
|
|
if modulus.is_zero() {
|
2022-10-21 16:25:38 +11:00
|
|
|
if filter == columns::IS_DIV {
|
2023-02-07 23:52:58 +11:00
|
|
|
// set modulus = 2^256; the condition above means we know
|
|
|
|
|
// it's zero at this point, so we can just set bit 256.
|
|
|
|
|
modulus.set_bit(256, true);
|
2022-10-21 16:25:38 +11:00
|
|
|
// modulus_limbs don't play a role below
|
|
|
|
|
} else {
|
|
|
|
|
// set modulus = 1
|
|
|
|
|
modulus = BigInt::one();
|
|
|
|
|
modulus_limbs[0] = 1i64;
|
|
|
|
|
}
|
2022-10-12 02:39:13 +11:00
|
|
|
mod_is_zero = F::ONE;
|
2022-10-07 17:15:50 +11:00
|
|
|
}
|
|
|
|
|
|
2022-10-20 04:46:01 +11:00
|
|
|
let input = columns_to_bigint(&constr_poly);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
// modulus != 0 here, because, if the given modulus was zero, then
|
2022-10-21 16:25:38 +11:00
|
|
|
// it was set to 1 or 2^256 above
|
2022-10-20 04:46:01 +11:00
|
|
|
let mut output = &input % &modulus;
|
|
|
|
|
// output will be -ve (but > -modulus) if input was -ve, so we can
|
|
|
|
|
// add modulus to obtain a "canonical" +ve output.
|
|
|
|
|
if output.sign() == Sign::Minus {
|
|
|
|
|
output += &modulus;
|
|
|
|
|
}
|
|
|
|
|
let output_limbs = bigint_to_columns::<N_LIMBS>(&output);
|
|
|
|
|
let quot = (&input - &output) / &modulus; // exact division; can be -ve
|
|
|
|
|
let quot_limbs = bigint_to_columns::<{ 2 * N_LIMBS }>(");
|
2022-10-07 17:15:50 +11:00
|
|
|
|
2023-02-10 23:07:57 +11:00
|
|
|
// output < modulus here; the proof requires (output - modulus) % 2^256:
|
|
|
|
|
let out_aux_red = bigint_to_columns::<N_LIMBS>(&(two_exp_256 - modulus + output));
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
// constr_poly is the array of coefficients of the polynomial
|
|
|
|
|
//
|
|
|
|
|
// operation(a(x), b(x)) - c(x) - s(x)*m(x).
|
|
|
|
|
//
|
|
|
|
|
pol_sub_assign(&mut constr_poly, &output_limbs);
|
|
|
|
|
let prod = pol_mul_wide2(quot_limbs, modulus_limbs);
|
|
|
|
|
pol_sub_assign(&mut constr_poly, &prod[0..2 * N_LIMBS]);
|
|
|
|
|
|
|
|
|
|
// Higher order terms of the product must be zero for valid quot and modulus:
|
|
|
|
|
debug_assert!(&prod[2 * N_LIMBS..].iter().all(|&x| x == 0i64));
|
|
|
|
|
|
2023-01-31 02:23:24 +11:00
|
|
|
lv[MODULAR_OUTPUT].copy_from_slice(&output_limbs.map(F::from_canonical_i64));
|
|
|
|
|
lv[MODULAR_QUO_INPUT].copy_from_slice("_limbs.map(F::from_noncanonical_i64));
|
2022-10-07 17:15:50 +11:00
|
|
|
// constr_poly must be zero when evaluated at x = β :=
|
|
|
|
|
// 2^LIMB_BITS, hence it's divisible by (x - β). `aux_limbs` is
|
|
|
|
|
// the result of removing that root.
|
2023-01-31 02:23:24 +11:00
|
|
|
let mut aux_limbs = pol_remove_root_2exp::<LIMB_BITS, _, { 2 * N_LIMBS }>(constr_poly);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
2023-01-31 02:23:24 +11:00
|
|
|
for c in aux_limbs.iter_mut() {
|
|
|
|
|
// we store the unsigned offset value c + 2^20.
|
|
|
|
|
*c += AUX_COEFF_ABS_MAX;
|
2022-12-02 17:11:31 +11:00
|
|
|
}
|
2023-01-31 02:23:24 +11:00
|
|
|
debug_assert!(aux_limbs.iter().all(|&c| c.abs() <= 2 * AUX_COEFF_ABS_MAX));
|
2022-12-02 17:11:31 +11:00
|
|
|
|
2023-01-31 02:23:24 +11:00
|
|
|
for (i, &c) in MODULAR_AUX_INPUT_LO.zip(&aux_limbs[..2 * N_LIMBS - 1]) {
|
|
|
|
|
nv[i] = F::from_canonical_u16(c as u16);
|
|
|
|
|
}
|
|
|
|
|
for (i, &c) in MODULAR_AUX_INPUT_HI.zip(&aux_limbs[..2 * N_LIMBS - 1]) {
|
|
|
|
|
nv[i] = F::from_canonical_u16((c >> 16) as u16);
|
2022-12-02 17:11:31 +11:00
|
|
|
}
|
|
|
|
|
nv[MODULAR_MOD_IS_ZERO] = mod_is_zero;
|
2023-01-31 02:23:24 +11:00
|
|
|
nv[MODULAR_OUT_AUX_RED].copy_from_slice(&out_aux_red.map(F::from_canonical_i64));
|
|
|
|
|
nv[MODULAR_DIV_DENOM_IS_ZERO] = mod_is_zero * lv[IS_DIV];
|
2022-10-07 17:15:50 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Generate the output and auxiliary values for modular operations.
|
|
|
|
|
///
|
|
|
|
|
/// `filter` must be one of `columns::IS_{ADDMOD,MULMOD,MOD}`.
|
2023-02-10 23:07:57 +11:00
|
|
|
pub(crate) fn generate<F: PrimeField64>(
|
|
|
|
|
lv: &mut [F],
|
|
|
|
|
nv: &mut [F],
|
|
|
|
|
filter: usize,
|
|
|
|
|
input0: U256,
|
|
|
|
|
input1: U256,
|
|
|
|
|
modulus: U256,
|
|
|
|
|
) {
|
2023-01-31 02:23:24 +11:00
|
|
|
debug_assert!(lv.len() == NUM_ARITH_COLUMNS && nv.len() == NUM_ARITH_COLUMNS);
|
2023-02-10 23:07:57 +11:00
|
|
|
|
|
|
|
|
u256_to_array(&mut lv[MODULAR_INPUT_0], input0);
|
|
|
|
|
u256_to_array(&mut lv[MODULAR_INPUT_1], input1);
|
|
|
|
|
u256_to_array(&mut lv[MODULAR_MODULUS], modulus);
|
|
|
|
|
|
2022-10-07 17:15:50 +11:00
|
|
|
match filter {
|
2022-12-02 17:11:31 +11:00
|
|
|
columns::IS_ADDMOD => generate_modular_op(lv, nv, filter, pol_add),
|
|
|
|
|
columns::IS_SUBMOD => generate_modular_op(lv, nv, filter, pol_sub),
|
|
|
|
|
columns::IS_MULMOD => generate_modular_op(lv, nv, filter, pol_mul_wide),
|
|
|
|
|
columns::IS_MOD | columns::IS_DIV => {
|
|
|
|
|
generate_modular_op(lv, nv, filter, |a, _| pol_extend(a))
|
|
|
|
|
}
|
2022-10-07 17:15:50 +11:00
|
|
|
_ => panic!("generate modular operation called with unknown opcode"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Build the part of the constraint polynomial that's common to all
|
|
|
|
|
/// modular operations, and perform the common verifications.
|
|
|
|
|
///
|
|
|
|
|
/// Specifically, with the notation above, build the polynomial
|
|
|
|
|
///
|
|
|
|
|
/// c(x) + q(x) * m(x) + (x - β) * s(x)
|
|
|
|
|
///
|
|
|
|
|
/// and check consistency when m = 0, and that c is reduced.
|
|
|
|
|
fn modular_constr_poly<P: PackedField>(
|
|
|
|
|
lv: &[P; NUM_ARITH_COLUMNS],
|
2022-12-02 17:11:31 +11:00
|
|
|
nv: &[P; NUM_ARITH_COLUMNS],
|
2022-10-07 17:15:50 +11:00
|
|
|
yield_constr: &mut ConstraintConsumer<P>,
|
|
|
|
|
filter: P,
|
|
|
|
|
) -> [P; 2 * N_LIMBS] {
|
2022-10-12 02:39:13 +11:00
|
|
|
let mut modulus = read_value::<N_LIMBS, _>(lv, MODULAR_MODULUS);
|
2022-12-02 17:11:31 +11:00
|
|
|
let mod_is_zero = nv[MODULAR_MOD_IS_ZERO];
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
// Check that mod_is_zero is zero or one
|
2022-12-02 17:11:31 +11:00
|
|
|
yield_constr.constraint_transition(filter * (mod_is_zero * mod_is_zero - mod_is_zero));
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
// Check that mod_is_zero is zero if modulus is not zero (they
|
|
|
|
|
// could both be zero)
|
|
|
|
|
let limb_sum = modulus.into_iter().sum::<P>();
|
2022-12-02 17:11:31 +11:00
|
|
|
yield_constr.constraint_transition(filter * limb_sum * mod_is_zero);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
// See the file documentation for why this suffices to handle
|
|
|
|
|
// modulus = 0.
|
|
|
|
|
modulus[0] += mod_is_zero;
|
|
|
|
|
|
2022-10-21 16:25:38 +11:00
|
|
|
let mut output = read_value::<N_LIMBS, _>(lv, MODULAR_OUTPUT);
|
|
|
|
|
|
2023-01-31 02:23:24 +11:00
|
|
|
// Is 1 iff the operation is DIV and the denominator is zero.
|
|
|
|
|
let div_denom_is_zero = nv[MODULAR_DIV_DENOM_IS_ZERO];
|
|
|
|
|
yield_constr.constraint_transition(filter * (mod_is_zero * lv[IS_DIV] - div_denom_is_zero));
|
|
|
|
|
|
2022-10-21 16:25:38 +11:00
|
|
|
// Needed to compensate for adding mod_is_zero to modulus above,
|
2023-02-10 23:07:57 +11:00
|
|
|
// since the call eval_packed_generic_addcy() below subtracts modulus
|
2023-01-31 02:23:24 +11:00
|
|
|
// to verify in the case of a DIV.
|
|
|
|
|
output[0] += div_denom_is_zero;
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
// Verify that the output is reduced, i.e. output < modulus.
|
2022-12-02 17:11:31 +11:00
|
|
|
let out_aux_red = &nv[MODULAR_OUT_AUX_RED];
|
2023-02-10 23:07:57 +11:00
|
|
|
// This sets is_less_than to 1 unless we get mod_is_zero when
|
|
|
|
|
// doing a DIV; in that case, we need is_less_than=0, since
|
|
|
|
|
// eval_packed_generic_addcy checks
|
2022-10-21 16:25:38 +11:00
|
|
|
//
|
2023-02-10 23:07:57 +11:00
|
|
|
// modulus + out_aux_red == output + is_less_than*2^256
|
2022-10-21 16:25:38 +11:00
|
|
|
//
|
2023-02-10 23:07:57 +11:00
|
|
|
// and we are given output = out_aux_red when modulus is zero.
|
|
|
|
|
let is_less_than = P::ONES - mod_is_zero * lv[IS_DIV];
|
2023-02-07 23:52:58 +11:00
|
|
|
// NB: output and modulus in lv while out_aux_red and
|
2023-02-10 23:07:57 +11:00
|
|
|
// is_less_than (via mod_is_zero) depend on nv, hence the
|
2023-02-07 23:52:58 +11:00
|
|
|
// 'is_two_row_op' argument is set to 'true'.
|
2023-02-10 23:07:57 +11:00
|
|
|
eval_packed_generic_addcy(
|
2022-10-07 17:15:50 +11:00
|
|
|
yield_constr,
|
|
|
|
|
filter,
|
2023-02-07 23:52:58 +11:00
|
|
|
&modulus,
|
2023-02-10 23:07:57 +11:00
|
|
|
out_aux_red,
|
|
|
|
|
&output,
|
|
|
|
|
is_less_than,
|
2022-12-02 17:11:31 +11:00
|
|
|
true,
|
2022-10-07 17:15:50 +11:00
|
|
|
);
|
2022-10-21 16:25:38 +11:00
|
|
|
// restore output[0]
|
2023-01-31 02:23:24 +11:00
|
|
|
output[0] -= div_denom_is_zero;
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
// prod = q(x) * m(x)
|
2022-12-02 17:11:31 +11:00
|
|
|
let quot = {
|
|
|
|
|
let mut quot = [P::default(); 2 * N_LIMBS];
|
2023-01-31 02:23:24 +11:00
|
|
|
quot.copy_from_slice(&lv[MODULAR_QUO_INPUT]);
|
2022-12-02 17:11:31 +11:00
|
|
|
quot
|
|
|
|
|
};
|
|
|
|
|
|
2022-10-07 17:15:50 +11:00
|
|
|
let prod = pol_mul_wide2(quot, modulus);
|
|
|
|
|
// higher order terms must be zero
|
|
|
|
|
for &x in prod[2 * N_LIMBS..].iter() {
|
2022-12-02 17:11:31 +11:00
|
|
|
yield_constr.constraint_transition(filter * x);
|
2022-10-07 17:15:50 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// constr_poly = c(x) + q(x) * m(x)
|
|
|
|
|
let mut constr_poly: [_; 2 * N_LIMBS] = prod[0..2 * N_LIMBS].try_into().unwrap();
|
2022-10-21 16:25:38 +11:00
|
|
|
pol_add_assign(&mut constr_poly, &output);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
2023-01-31 02:23:24 +11:00
|
|
|
let base = P::Scalar::from_canonical_u64(1 << LIMB_BITS);
|
|
|
|
|
let offset = P::Scalar::from_canonical_u64(AUX_COEFF_ABS_MAX as u64);
|
|
|
|
|
|
2022-10-07 17:15:50 +11:00
|
|
|
// constr_poly = c(x) + q(x) * m(x) + (x - β) * s(x)
|
2022-12-02 17:11:31 +11:00
|
|
|
let mut aux = [P::ZEROS; 2 * N_LIMBS];
|
2023-01-31 02:23:24 +11:00
|
|
|
for (c, i) in aux.iter_mut().zip(MODULAR_AUX_INPUT_LO) {
|
|
|
|
|
// MODULAR_AUX_INPUT elements were offset by 2^20 in
|
|
|
|
|
// generation, so we undo that here.
|
|
|
|
|
*c = nv[i] - offset;
|
|
|
|
|
}
|
|
|
|
|
// add high 16-bits of aux input
|
|
|
|
|
for (c, j) in aux.iter_mut().zip(MODULAR_AUX_INPUT_HI) {
|
|
|
|
|
*c += base * nv[j];
|
2022-12-02 17:11:31 +11:00
|
|
|
}
|
|
|
|
|
|
2022-10-07 17:15:50 +11:00
|
|
|
pol_add_assign(&mut constr_poly, &pol_adjoin_root(aux, base));
|
|
|
|
|
|
|
|
|
|
constr_poly
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Add constraints for modular operations.
|
|
|
|
|
pub(crate) fn eval_packed_generic<P: PackedField>(
|
|
|
|
|
lv: &[P; NUM_ARITH_COLUMNS],
|
2022-12-02 17:11:31 +11:00
|
|
|
nv: &[P; NUM_ARITH_COLUMNS],
|
2022-10-07 17:15:50 +11:00
|
|
|
yield_constr: &mut ConstraintConsumer<P>,
|
|
|
|
|
) {
|
|
|
|
|
// NB: The CTL code guarantees that filter is 0 or 1, i.e. that
|
|
|
|
|
// only one of the operations below is "live".
|
2022-10-20 04:46:01 +11:00
|
|
|
let filter = lv[columns::IS_ADDMOD]
|
2023-01-31 02:23:24 +11:00
|
|
|
+ lv[columns::IS_SUBMOD]
|
2022-10-20 04:46:01 +11:00
|
|
|
+ lv[columns::IS_MULMOD]
|
|
|
|
|
+ lv[columns::IS_MOD]
|
2022-10-21 16:25:38 +11:00
|
|
|
+ lv[columns::IS_DIV];
|
2022-10-07 17:15:50 +11:00
|
|
|
|
2022-12-02 17:11:31 +11:00
|
|
|
// Ensure that this operation is not the last row of the table;
|
|
|
|
|
// needed because we access the next row of the table in nv.
|
|
|
|
|
yield_constr.constraint_last_row(filter);
|
|
|
|
|
|
2022-10-07 17:15:50 +11:00
|
|
|
// constr_poly has 2*N_LIMBS limbs
|
2022-12-02 17:11:31 +11:00
|
|
|
let constr_poly = modular_constr_poly(lv, nv, yield_constr, filter);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
2022-10-12 02:39:13 +11:00
|
|
|
let input0 = read_value(lv, MODULAR_INPUT_0);
|
|
|
|
|
let input1 = read_value(lv, MODULAR_INPUT_1);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
let add_input = pol_add(input0, input1);
|
2022-10-20 04:46:01 +11:00
|
|
|
let sub_input = pol_sub(input0, input1);
|
2022-10-07 17:15:50 +11:00
|
|
|
let mul_input = pol_mul_wide(input0, input1);
|
|
|
|
|
let mod_input = pol_extend(input0);
|
|
|
|
|
|
|
|
|
|
for (input, &filter) in [
|
|
|
|
|
(&add_input, &lv[columns::IS_ADDMOD]),
|
2022-10-20 04:46:01 +11:00
|
|
|
(&sub_input, &lv[columns::IS_SUBMOD]),
|
2022-10-07 17:15:50 +11:00
|
|
|
(&mul_input, &lv[columns::IS_MULMOD]),
|
2022-10-21 16:25:38 +11:00
|
|
|
(&mod_input, &(lv[columns::IS_MOD] + lv[columns::IS_DIV])),
|
2022-10-07 17:15:50 +11:00
|
|
|
] {
|
|
|
|
|
// Need constr_poly_copy to be the first argument to
|
|
|
|
|
// pol_sub_assign, since it is the longer of the two
|
|
|
|
|
// arguments.
|
|
|
|
|
let mut constr_poly_copy = constr_poly;
|
|
|
|
|
pol_sub_assign(&mut constr_poly_copy, input);
|
|
|
|
|
|
|
|
|
|
// At this point constr_poly_copy holds the coefficients of
|
|
|
|
|
// the polynomial
|
|
|
|
|
//
|
|
|
|
|
// operation(a(x), b(x)) - c(x) - q(x) * m(x) - (x - β) * s(x)
|
|
|
|
|
//
|
|
|
|
|
// where operation is add, mul or |a,b|->a. The modular
|
|
|
|
|
// operation is valid if and only if all of those coefficients
|
|
|
|
|
// are zero.
|
|
|
|
|
for &c in constr_poly_copy.iter() {
|
2022-12-02 17:11:31 +11:00
|
|
|
yield_constr.constraint_transition(filter * c);
|
2022-10-07 17:15:50 +11:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn modular_constr_poly_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
|
|
|
|
|
lv: &[ExtensionTarget<D>; NUM_ARITH_COLUMNS],
|
2022-12-02 17:11:31 +11:00
|
|
|
nv: &[ExtensionTarget<D>; NUM_ARITH_COLUMNS],
|
2022-10-07 17:15:50 +11:00
|
|
|
builder: &mut CircuitBuilder<F, D>,
|
|
|
|
|
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
|
|
|
|
|
filter: ExtensionTarget<D>,
|
|
|
|
|
) -> [ExtensionTarget<D>; 2 * N_LIMBS] {
|
2022-10-12 02:39:13 +11:00
|
|
|
let mut modulus = read_value::<N_LIMBS, _>(lv, MODULAR_MODULUS);
|
2022-12-02 17:11:31 +11:00
|
|
|
let mod_is_zero = nv[MODULAR_MOD_IS_ZERO];
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
let t = builder.mul_sub_extension(mod_is_zero, mod_is_zero, mod_is_zero);
|
|
|
|
|
let t = builder.mul_extension(filter, t);
|
2022-12-02 17:11:31 +11:00
|
|
|
yield_constr.constraint_transition(builder, t);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
let limb_sum = builder.add_many_extension(modulus);
|
|
|
|
|
let t = builder.mul_extension(limb_sum, mod_is_zero);
|
|
|
|
|
let t = builder.mul_extension(filter, t);
|
2022-12-02 17:11:31 +11:00
|
|
|
yield_constr.constraint_transition(builder, t);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
modulus[0] = builder.add_extension(modulus[0], mod_is_zero);
|
|
|
|
|
|
2022-10-21 16:25:38 +11:00
|
|
|
let mut output = read_value::<N_LIMBS, _>(lv, MODULAR_OUTPUT);
|
2023-01-31 02:23:24 +11:00
|
|
|
|
|
|
|
|
let div_denom_is_zero = nv[MODULAR_DIV_DENOM_IS_ZERO];
|
|
|
|
|
let t = builder.mul_sub_extension(mod_is_zero, lv[IS_DIV], div_denom_is_zero);
|
|
|
|
|
let t = builder.mul_extension(filter, t);
|
|
|
|
|
yield_constr.constraint_transition(builder, t);
|
|
|
|
|
output[0] = builder.add_extension(output[0], div_denom_is_zero);
|
2022-10-21 16:25:38 +11:00
|
|
|
|
2022-12-02 17:11:31 +11:00
|
|
|
let out_aux_red = &nv[MODULAR_OUT_AUX_RED];
|
2023-02-10 23:07:57 +11:00
|
|
|
let one = builder.one_extension();
|
|
|
|
|
let is_less_than =
|
|
|
|
|
builder.arithmetic_extension(F::NEG_ONE, F::ONE, mod_is_zero, lv[IS_DIV], one);
|
2022-10-21 16:25:38 +11:00
|
|
|
|
2023-02-10 23:07:57 +11:00
|
|
|
eval_ext_circuit_addcy(
|
2022-10-07 17:15:50 +11:00
|
|
|
builder,
|
|
|
|
|
yield_constr,
|
|
|
|
|
filter,
|
2023-02-07 23:52:58 +11:00
|
|
|
&modulus,
|
2023-02-10 23:07:57 +11:00
|
|
|
out_aux_red,
|
|
|
|
|
&output,
|
|
|
|
|
is_less_than,
|
2022-12-02 17:11:31 +11:00
|
|
|
true,
|
2022-10-07 17:15:50 +11:00
|
|
|
);
|
2023-01-31 02:23:24 +11:00
|
|
|
output[0] = builder.sub_extension(output[0], div_denom_is_zero);
|
2022-12-02 17:11:31 +11:00
|
|
|
let quot = {
|
|
|
|
|
let zero = builder.zero_extension();
|
|
|
|
|
let mut quot = [zero; 2 * N_LIMBS];
|
2023-01-31 02:23:24 +11:00
|
|
|
quot.copy_from_slice(&lv[MODULAR_QUO_INPUT]);
|
2022-12-02 17:11:31 +11:00
|
|
|
quot
|
|
|
|
|
};
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
let prod = pol_mul_wide2_ext_circuit(builder, quot, modulus);
|
|
|
|
|
for &x in prod[2 * N_LIMBS..].iter() {
|
|
|
|
|
let t = builder.mul_extension(filter, x);
|
2022-12-02 17:11:31 +11:00
|
|
|
yield_constr.constraint_transition(builder, t);
|
2022-10-07 17:15:50 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut constr_poly: [_; 2 * N_LIMBS] = prod[0..2 * N_LIMBS].try_into().unwrap();
|
2022-10-21 16:25:38 +11:00
|
|
|
pol_add_assign_ext_circuit(builder, &mut constr_poly, &output);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
2023-01-31 02:23:24 +11:00
|
|
|
let offset =
|
|
|
|
|
builder.constant_extension(F::Extension::from_canonical_u64(AUX_COEFF_ABS_MAX as u64));
|
2022-12-02 17:11:31 +11:00
|
|
|
let zero = builder.zero_extension();
|
|
|
|
|
let mut aux = [zero; 2 * N_LIMBS];
|
2023-01-31 02:23:24 +11:00
|
|
|
for (c, i) in aux.iter_mut().zip(MODULAR_AUX_INPUT_LO) {
|
|
|
|
|
*c = builder.sub_extension(nv[i], offset);
|
|
|
|
|
}
|
|
|
|
|
let base = F::from_canonical_u64(1u64 << LIMB_BITS);
|
|
|
|
|
for (c, j) in aux.iter_mut().zip(MODULAR_AUX_INPUT_HI) {
|
|
|
|
|
*c = builder.mul_const_add_extension(base, nv[j], *c);
|
2022-12-02 17:11:31 +11:00
|
|
|
}
|
|
|
|
|
|
2023-01-31 02:23:24 +11:00
|
|
|
let base = builder.constant_extension(base.into());
|
2022-10-07 17:15:50 +11:00
|
|
|
let t = pol_adjoin_root_ext_circuit(builder, aux, base);
|
|
|
|
|
pol_add_assign_ext_circuit(builder, &mut constr_poly, &t);
|
|
|
|
|
|
|
|
|
|
constr_poly
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
|
|
|
|
|
builder: &mut CircuitBuilder<F, D>,
|
|
|
|
|
lv: &[ExtensionTarget<D>; NUM_ARITH_COLUMNS],
|
2022-12-02 17:11:31 +11:00
|
|
|
nv: &[ExtensionTarget<D>; NUM_ARITH_COLUMNS],
|
2022-10-07 17:15:50 +11:00
|
|
|
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
|
|
|
|
|
) {
|
|
|
|
|
let filter = builder.add_many_extension([
|
|
|
|
|
lv[columns::IS_ADDMOD],
|
2022-10-20 04:46:01 +11:00
|
|
|
lv[columns::IS_SUBMOD],
|
2022-10-07 17:15:50 +11:00
|
|
|
lv[columns::IS_MULMOD],
|
|
|
|
|
lv[columns::IS_MOD],
|
2022-10-21 16:25:38 +11:00
|
|
|
lv[columns::IS_DIV],
|
2022-10-07 17:15:50 +11:00
|
|
|
]);
|
|
|
|
|
|
2022-12-02 17:11:31 +11:00
|
|
|
yield_constr.constraint_last_row(builder, filter);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
2022-12-02 17:11:31 +11:00
|
|
|
let constr_poly = modular_constr_poly_ext_circuit(lv, nv, builder, yield_constr, filter);
|
2022-10-12 02:39:13 +11:00
|
|
|
let input0 = read_value(lv, MODULAR_INPUT_0);
|
|
|
|
|
let input1 = read_value(lv, MODULAR_INPUT_1);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
let add_input = pol_add_ext_circuit(builder, input0, input1);
|
2022-10-20 04:46:01 +11:00
|
|
|
let sub_input = pol_sub_ext_circuit(builder, input0, input1);
|
2022-10-07 17:15:50 +11:00
|
|
|
let mul_input = pol_mul_wide_ext_circuit(builder, input0, input1);
|
|
|
|
|
let mod_input = pol_extend_ext_circuit(builder, input0);
|
|
|
|
|
|
2022-10-21 16:25:38 +11:00
|
|
|
let mod_div_filter = builder.add_extension(lv[columns::IS_MOD], lv[columns::IS_DIV]);
|
2022-10-07 17:15:50 +11:00
|
|
|
for (input, &filter) in [
|
|
|
|
|
(&add_input, &lv[columns::IS_ADDMOD]),
|
2022-10-20 04:46:01 +11:00
|
|
|
(&sub_input, &lv[columns::IS_SUBMOD]),
|
2022-10-07 17:15:50 +11:00
|
|
|
(&mul_input, &lv[columns::IS_MULMOD]),
|
2022-10-21 16:25:38 +11:00
|
|
|
(&mod_input, &mod_div_filter),
|
2022-10-07 17:15:50 +11:00
|
|
|
] {
|
|
|
|
|
let mut constr_poly_copy = constr_poly;
|
|
|
|
|
pol_sub_assign_ext_circuit(builder, &mut constr_poly_copy, input);
|
|
|
|
|
for &c in constr_poly_copy.iter() {
|
|
|
|
|
let t = builder.mul_extension(filter, c);
|
2022-12-02 17:11:31 +11:00
|
|
|
yield_constr.constraint_transition(builder, t);
|
2022-10-07 17:15:50 +11:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use plonky2::field::goldilocks_field::GoldilocksField;
|
2022-11-03 08:26:03 -07:00
|
|
|
use plonky2::field::types::{Field, Sample};
|
2022-10-07 17:15:50 +11:00
|
|
|
use rand::{Rng, SeedableRng};
|
|
|
|
|
use rand_chacha::ChaCha8Rng;
|
|
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
use crate::arithmetic::columns::NUM_ARITH_COLUMNS;
|
|
|
|
|
use crate::constraint_consumer::ConstraintConsumer;
|
|
|
|
|
|
|
|
|
|
const N_RND_TESTS: usize = 1000;
|
|
|
|
|
|
|
|
|
|
// TODO: Should be able to refactor this test to apply to all operations.
|
|
|
|
|
#[test]
|
|
|
|
|
fn generate_eval_consistency_not_modular() {
|
|
|
|
|
type F = GoldilocksField;
|
|
|
|
|
|
|
|
|
|
let mut rng = ChaCha8Rng::seed_from_u64(0x6feb51b7ec230f25);
|
2022-11-03 08:26:03 -07:00
|
|
|
let mut lv = [F::default(); NUM_ARITH_COLUMNS].map(|_| F::sample(&mut rng));
|
2022-12-02 17:11:31 +11:00
|
|
|
let nv = [F::default(); NUM_ARITH_COLUMNS].map(|_| F::sample(&mut rng));
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
// if `IS_ADDMOD == 0`, then the constraints should be met even
|
|
|
|
|
// if all values are garbage.
|
|
|
|
|
lv[IS_ADDMOD] = F::ZERO;
|
2022-10-20 04:46:01 +11:00
|
|
|
lv[IS_SUBMOD] = F::ZERO;
|
2022-10-07 17:15:50 +11:00
|
|
|
lv[IS_MULMOD] = F::ZERO;
|
|
|
|
|
lv[IS_MOD] = F::ZERO;
|
2022-10-21 16:25:38 +11:00
|
|
|
lv[IS_DIV] = F::ZERO;
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
let mut constraint_consumer = ConstraintConsumer::new(
|
|
|
|
|
vec![GoldilocksField(2), GoldilocksField(3), GoldilocksField(5)],
|
|
|
|
|
GoldilocksField::ONE,
|
|
|
|
|
GoldilocksField::ONE,
|
|
|
|
|
GoldilocksField::ONE,
|
|
|
|
|
);
|
2022-12-02 17:11:31 +11:00
|
|
|
eval_packed_generic(&lv, &nv, &mut constraint_consumer);
|
2022-10-07 17:15:50 +11:00
|
|
|
for &acc in &constraint_consumer.constraint_accs {
|
|
|
|
|
assert_eq!(acc, GoldilocksField::ZERO);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn generate_eval_consistency() {
|
|
|
|
|
type F = GoldilocksField;
|
|
|
|
|
|
|
|
|
|
let mut rng = ChaCha8Rng::seed_from_u64(0x6feb51b7ec230f25);
|
|
|
|
|
|
2022-10-21 16:25:38 +11:00
|
|
|
for op_filter in [IS_ADDMOD, IS_DIV, IS_SUBMOD, IS_MOD, IS_MULMOD] {
|
2022-10-07 17:15:50 +11:00
|
|
|
for i in 0..N_RND_TESTS {
|
|
|
|
|
// set inputs to random values
|
2023-02-10 23:07:57 +11:00
|
|
|
let mut lv = [F::default(); NUM_ARITH_COLUMNS]
|
|
|
|
|
.map(|_| F::from_canonical_u16(rng.gen::<u16>()));
|
|
|
|
|
let mut nv = [F::default(); NUM_ARITH_COLUMNS]
|
|
|
|
|
.map(|_| F::from_canonical_u16(rng.gen::<u16>()));
|
|
|
|
|
|
|
|
|
|
// Reset operation columns, then select one
|
|
|
|
|
lv[IS_ADDMOD] = F::ZERO;
|
|
|
|
|
lv[IS_SUBMOD] = F::ZERO;
|
|
|
|
|
lv[IS_MULMOD] = F::ZERO;
|
|
|
|
|
lv[IS_MOD] = F::ZERO;
|
|
|
|
|
lv[IS_DIV] = F::ZERO;
|
|
|
|
|
lv[op_filter] = F::ONE;
|
|
|
|
|
|
|
|
|
|
let input0 = U256::from(rng.gen::<[u8; 32]>());
|
|
|
|
|
let input1 = U256::from(rng.gen::<[u8; 32]>());
|
|
|
|
|
|
|
|
|
|
let mut modulus_limbs = [0u8; 32];
|
2022-12-02 17:11:31 +11:00
|
|
|
// For the second half of the tests, set the top
|
|
|
|
|
// 16-start digits of the modulus to zero so it is
|
|
|
|
|
// much smaller than the inputs.
|
2022-10-07 17:15:50 +11:00
|
|
|
if i > N_RND_TESTS / 2 {
|
|
|
|
|
// 1 <= start < N_LIMBS
|
2023-02-10 23:07:57 +11:00
|
|
|
let start = (rng.gen::<usize>() % (modulus_limbs.len() - 1)) + 1;
|
|
|
|
|
for mi in modulus_limbs.iter_mut().skip(start) {
|
|
|
|
|
*mi = 0u8;
|
2022-10-07 17:15:50 +11:00
|
|
|
}
|
|
|
|
|
}
|
2023-02-10 23:07:57 +11:00
|
|
|
let modulus = U256::from(modulus_limbs);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
2023-02-10 23:07:57 +11:00
|
|
|
generate(&mut lv, &mut nv, op_filter, input0, input1, modulus);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
let mut constraint_consumer = ConstraintConsumer::new(
|
|
|
|
|
vec![GoldilocksField(2), GoldilocksField(3), GoldilocksField(5)],
|
|
|
|
|
GoldilocksField::ONE,
|
2022-12-02 17:11:31 +11:00
|
|
|
GoldilocksField::ZERO,
|
|
|
|
|
GoldilocksField::ZERO,
|
2022-10-07 17:15:50 +11:00
|
|
|
);
|
2022-12-02 17:11:31 +11:00
|
|
|
eval_packed_generic(&lv, &nv, &mut constraint_consumer);
|
2022-10-07 17:15:50 +11:00
|
|
|
for &acc in &constraint_consumer.constraint_accs {
|
|
|
|
|
assert_eq!(acc, GoldilocksField::ZERO);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn zero_modulus() {
|
|
|
|
|
type F = GoldilocksField;
|
|
|
|
|
|
|
|
|
|
let mut rng = ChaCha8Rng::seed_from_u64(0x6feb51b7ec230f25);
|
|
|
|
|
|
2022-10-21 16:25:38 +11:00
|
|
|
for op_filter in [IS_ADDMOD, IS_SUBMOD, IS_DIV, IS_MOD, IS_MULMOD] {
|
2022-10-07 17:15:50 +11:00
|
|
|
for _i in 0..N_RND_TESTS {
|
|
|
|
|
// set inputs to random values and the modulus to zero;
|
|
|
|
|
// the output is defined to be zero when modulus is zero.
|
2023-02-10 23:07:57 +11:00
|
|
|
let mut lv = [F::default(); NUM_ARITH_COLUMNS]
|
|
|
|
|
.map(|_| F::from_canonical_u16(rng.gen::<u16>()));
|
|
|
|
|
let mut nv = [F::default(); NUM_ARITH_COLUMNS]
|
|
|
|
|
.map(|_| F::from_canonical_u16(rng.gen::<u16>()));
|
|
|
|
|
|
|
|
|
|
// Reset operation columns, then select one
|
|
|
|
|
lv[IS_ADDMOD] = F::ZERO;
|
|
|
|
|
lv[IS_SUBMOD] = F::ZERO;
|
|
|
|
|
lv[IS_MULMOD] = F::ZERO;
|
|
|
|
|
lv[IS_MOD] = F::ZERO;
|
|
|
|
|
lv[IS_DIV] = F::ZERO;
|
|
|
|
|
lv[op_filter] = F::ONE;
|
|
|
|
|
|
|
|
|
|
let input0 = U256::from(rng.gen::<[u8; 32]>());
|
|
|
|
|
let input1 = U256::from(rng.gen::<[u8; 32]>());
|
|
|
|
|
let modulus = U256::zero();
|
|
|
|
|
|
|
|
|
|
generate(&mut lv, &mut nv, op_filter, input0, input1, modulus);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
// check that the correct output was generated
|
2022-10-21 16:25:38 +11:00
|
|
|
if op_filter == IS_DIV {
|
|
|
|
|
assert!(lv[DIV_OUTPUT].iter().all(|&c| c == F::ZERO));
|
|
|
|
|
} else {
|
|
|
|
|
assert!(lv[MODULAR_OUTPUT].iter().all(|&c| c == F::ZERO));
|
|
|
|
|
}
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
let mut constraint_consumer = ConstraintConsumer::new(
|
|
|
|
|
vec![GoldilocksField(2), GoldilocksField(3), GoldilocksField(5)],
|
|
|
|
|
GoldilocksField::ONE,
|
2022-12-02 17:11:31 +11:00
|
|
|
GoldilocksField::ZERO,
|
|
|
|
|
GoldilocksField::ZERO,
|
2022-10-07 17:15:50 +11:00
|
|
|
);
|
2022-12-02 17:11:31 +11:00
|
|
|
eval_packed_generic(&lv, &nv, &mut constraint_consumer);
|
2022-10-07 17:15:50 +11:00
|
|
|
assert!(constraint_consumer
|
|
|
|
|
.constraint_accs
|
|
|
|
|
.iter()
|
|
|
|
|
.all(|&acc| acc == F::ZERO));
|
|
|
|
|
|
|
|
|
|
// Corrupt one output limb by setting it to a non-zero value
|
2022-12-02 17:11:31 +11:00
|
|
|
if op_filter == IS_DIV {
|
|
|
|
|
let random_oi = DIV_OUTPUT.start + rng.gen::<usize>() % N_LIMBS;
|
|
|
|
|
lv[random_oi] = F::from_canonical_u16(rng.gen_range(1..u16::MAX));
|
2022-10-21 16:25:38 +11:00
|
|
|
} else {
|
2022-12-02 17:11:31 +11:00
|
|
|
let random_oi = MODULAR_OUTPUT.start + rng.gen::<usize>() % N_LIMBS;
|
|
|
|
|
lv[random_oi] = F::from_canonical_u16(rng.gen_range(1..u16::MAX));
|
2022-10-21 16:25:38 +11:00
|
|
|
};
|
2022-10-07 17:15:50 +11:00
|
|
|
|
2022-12-02 17:11:31 +11:00
|
|
|
eval_packed_generic(&lv, &nv, &mut constraint_consumer);
|
2022-10-07 17:15:50 +11:00
|
|
|
|
|
|
|
|
// Check that at least one of the constraints was non-zero
|
|
|
|
|
assert!(constraint_consumer
|
|
|
|
|
.constraint_accs
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|&acc| acc != F::ZERO));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|