use std::ops::{Add, AddAssign, Mul, Neg, Range, Shr, Sub, SubAssign}; use plonky2::field::extension::Extendable; use plonky2::hash::hash_types::RichField; use plonky2::iop::ext_target::ExtensionTarget; use plonky2::plonk::circuit_builder::CircuitBuilder; use crate::arithmetic::columns::{NUM_ARITH_COLUMNS, N_LIMBS}; /// Emit an error message regarding unchecked range assumptions. /// Assumes the values in `cols` are `[cols[0], cols[0] + 1, ..., /// cols[0] + cols.len() - 1]`. /// /// TODO: Hamish to delete this when he has implemented and integrated /// range checks. pub(crate) fn _range_check_error( _file: &str, _line: u32, _cols: Range, _signedness: &str, ) { // error!( // "{}:{}: arithmetic unit skipped {}-bit {} range-checks on columns {}--{}: not yet implemented", // line, // file, // RC_BITS, // signedness, // cols.start, // cols.end - 1, // ); } #[macro_export] macro_rules! range_check_error { ($cols:ident, $rc_bits:expr) => { $crate::arithmetic::utils::_range_check_error::<$rc_bits>( file!(), line!(), $cols, "unsigned", ); }; ($cols:ident, $rc_bits:expr, signed) => { $crate::arithmetic::utils::_range_check_error::<$rc_bits>( file!(), line!(), $cols, "signed", ); }; ([$cols:ident], $rc_bits:expr) => { $crate::arithmetic::utils::_range_check_error::<$rc_bits>( file!(), line!(), &[$cols], "unsigned", ); }; } /// Return an array of `N` zeros of type T. pub(crate) fn pol_zero() -> [T; N] where T: Copy + Default, { // TODO: This should really be T::zero() from num::Zero, because // default() doesn't guarantee to initialise to zero (though in // our case it always does). However I couldn't work out how to do // that without touching half of the entire crate because it // involves replacing Field::is_zero() with num::Zero::is_zero() // which is used everywhere. Hence Default::default() it is. [T::default(); N] } /// a(x) += b(x), but must have deg(a) >= deg(b). pub(crate) fn pol_add_assign(a: &mut [T], b: &[T]) where T: AddAssign + Copy + Default, { debug_assert!(a.len() >= b.len(), "expected {} >= {}", a.len(), b.len()); for (a_item, b_item) in a.iter_mut().zip(b) { *a_item += *b_item; } } pub(crate) fn pol_add_assign_ext_circuit, const D: usize>( builder: &mut CircuitBuilder, a: &mut [ExtensionTarget], b: &[ExtensionTarget], ) { debug_assert!(a.len() >= b.len(), "expected {} >= {}", a.len(), b.len()); for (a_item, b_item) in a.iter_mut().zip(b) { *a_item = builder.add_extension(*a_item, *b_item); } } /// Return a(x) + b(x); returned array is bigger than necessary to /// make the interface consistent with `pol_mul_wide`. pub(crate) fn pol_add(a: [T; N_LIMBS], b: [T; N_LIMBS]) -> [T; 2 * N_LIMBS - 1] where T: Add + Copy + Default, { let mut sum = pol_zero(); for i in 0..N_LIMBS { sum[i] = a[i] + b[i]; } sum } pub(crate) fn pol_add_ext_circuit, const D: usize>( builder: &mut CircuitBuilder, a: [ExtensionTarget; N_LIMBS], b: [ExtensionTarget; N_LIMBS], ) -> [ExtensionTarget; 2 * N_LIMBS - 1] { let zero = builder.zero_extension(); let mut sum = [zero; 2 * N_LIMBS - 1]; for i in 0..N_LIMBS { sum[i] = builder.add_extension(a[i], b[i]); } sum } /// Return a(x) - b(x); returned array is bigger than necessary to /// make the interface consistent with `pol_mul_wide`. pub(crate) fn pol_sub(a: [T; N_LIMBS], b: [T; N_LIMBS]) -> [T; 2 * N_LIMBS - 1] where T: Sub + Copy + Default, { let mut diff = pol_zero(); for i in 0..N_LIMBS { diff[i] = a[i] - b[i]; } diff } pub(crate) fn pol_sub_ext_circuit, const D: usize>( builder: &mut CircuitBuilder, a: [ExtensionTarget; N_LIMBS], b: [ExtensionTarget; N_LIMBS], ) -> [ExtensionTarget; 2 * N_LIMBS - 1] { let zero = builder.zero_extension(); let mut sum = [zero; 2 * N_LIMBS - 1]; for i in 0..N_LIMBS { sum[i] = builder.sub_extension(a[i], b[i]); } sum } /// a(x) -= b(x), but must have deg(a) >= deg(b). pub(crate) fn pol_sub_assign(a: &mut [T], b: &[T]) where T: SubAssign + Copy, { debug_assert!(a.len() >= b.len(), "expected {} >= {}", a.len(), b.len()); for (a_item, b_item) in a.iter_mut().zip(b) { *a_item -= *b_item; } } pub(crate) fn pol_sub_assign_ext_circuit, const D: usize>( builder: &mut CircuitBuilder, a: &mut [ExtensionTarget], b: &[ExtensionTarget], ) { debug_assert!(a.len() >= b.len(), "expected {} >= {}", a.len(), b.len()); for (a_item, b_item) in a.iter_mut().zip(b) { *a_item = builder.sub_extension(*a_item, *b_item); } } /// Given polynomials a(x) and b(x), return a(x)*b(x). /// /// NB: The caller is responsible for ensuring that no undesired /// overflow occurs during the calculation of the coefficients of the /// product. pub(crate) fn pol_mul_wide(a: [T; N_LIMBS], b: [T; N_LIMBS]) -> [T; 2 * N_LIMBS - 1] where T: AddAssign + Copy + Mul + Default, { let mut res = [T::default(); 2 * N_LIMBS - 1]; for (i, &ai) in a.iter().enumerate() { for (j, &bj) in b.iter().enumerate() { res[i + j] += ai * bj; } } res } pub(crate) fn pol_mul_wide_ext_circuit< F: RichField + Extendable, const D: usize, const M: usize, const N: usize, const P: usize, >( builder: &mut CircuitBuilder, a: [ExtensionTarget; M], b: [ExtensionTarget; N], ) -> [ExtensionTarget; P] { let zero = builder.zero_extension(); let mut res = [zero; P]; for (i, &ai) in a.iter().enumerate() { for (j, &bj) in b.iter().enumerate() { res[i + j] = builder.mul_add_extension(ai, bj, res[i + j]); } } res } /// As for `pol_mul_wide` but the first argument has 2N elements and /// hence the result has 3N-1. pub(crate) fn pol_mul_wide2(a: [T; 2 * N_LIMBS], b: [T; N_LIMBS]) -> [T; 3 * N_LIMBS - 1] where T: AddAssign + Copy + Mul + Default, { let mut res = [T::default(); 3 * N_LIMBS - 1]; for (i, &ai) in a.iter().enumerate() { for (j, &bj) in b.iter().enumerate() { res[i + j] += ai * bj; } } res } pub(crate) fn pol_mul_wide2_ext_circuit, const D: usize>( builder: &mut CircuitBuilder, a: [ExtensionTarget; 2 * N_LIMBS], b: [ExtensionTarget; N_LIMBS], ) -> [ExtensionTarget; 3 * N_LIMBS - 1] { let zero = builder.zero_extension(); let mut res = [zero; 3 * N_LIMBS - 1]; for (i, &ai) in a.iter().enumerate() { for (j, &bj) in b.iter().enumerate() { res[i + j] = builder.mul_add_extension(ai, bj, res[i + j]); } } res } /// Given a(x) and b(x), return a(x)*b(x) mod 2^256. pub(crate) fn pol_mul_lo(a: [T; N], b: [T; N]) -> [T; N] where T: AddAssign + Copy + Default + Mul, { let mut res = pol_zero(); for deg in 0..N { // Invariant: i + j = deg for i in 0..=deg { let j = deg - i; res[deg] += a[i] * b[j]; } } res } pub(crate) fn pol_mul_lo_ext_circuit, const D: usize>( builder: &mut CircuitBuilder, a: [ExtensionTarget; N_LIMBS], b: [ExtensionTarget; N_LIMBS], ) -> [ExtensionTarget; N_LIMBS] { let zero = builder.zero_extension(); let mut res = [zero; N_LIMBS]; for deg in 0..N_LIMBS { for i in 0..=deg { let j = deg - i; res[deg] = builder.mul_add_extension(a[i], b[j], res[deg]); } } res } /// Adjoin M - N zeros to a, returning [a[0], a[1], ..., a[N-1], 0, 0, ..., 0]. pub(crate) fn pol_extend(a: [T; N]) -> [T; M] where T: Copy + Default, { assert_eq!(M, 2 * N - 1); let mut zero_extend = pol_zero(); zero_extend[..N].copy_from_slice(&a); zero_extend } pub(crate) fn pol_extend_ext_circuit, const D: usize>( builder: &mut CircuitBuilder, a: [ExtensionTarget; N_LIMBS], ) -> [ExtensionTarget; 2 * N_LIMBS - 1] { let zero = builder.zero_extension(); let mut zero_extend = [zero; 2 * N_LIMBS - 1]; zero_extend[..N_LIMBS].copy_from_slice(&a); zero_extend } /// Given polynomial a(x) = \sum_{i=0}^{N-2} a[i] x^i and an element /// `root`, return b = (x - root) * a(x). pub(crate) fn pol_adjoin_root(a: [T; N], root: U) -> [T; N] where T: Add + Copy + Default + Mul + Sub, U: Copy + Mul + Neg, { // \sum_i res[i] x^i = (x - root) \sum_i a[i] x^i. Comparing // coefficients, res[0] = -root*a[0] and // res[i] = a[i-1] - root * a[i] let mut res = [T::default(); N]; res[0] = -root * a[0]; for deg in 1..N { res[deg] = a[deg - 1] - (root * a[deg]); } res } pub(crate) fn pol_adjoin_root_ext_circuit< F: RichField + Extendable, const D: usize, const N: usize, >( builder: &mut CircuitBuilder, a: [ExtensionTarget; N], root: ExtensionTarget, ) -> [ExtensionTarget; N] { let zero = builder.zero_extension(); let mut res = [zero; N]; // res[deg] = NEG_ONE * root * a[0] + ZERO * zero res[0] = builder.arithmetic_extension(F::NEG_ONE, F::ZERO, root, a[0], zero); for deg in 1..N { // res[deg] = NEG_ONE * root * a[deg] + ONE * a[deg - 1] res[deg] = builder.arithmetic_extension(F::NEG_ONE, F::ONE, root, a[deg], a[deg - 1]); } res } /// Given polynomial a(x) = \sum_{i=0}^{N-1} a[i] x^i and a root of `a` /// of the form 2^EXP, return q(x) satisfying a(x) = (x - root) * q(x). /// /// NB: We do not verify that a(2^EXP) = 0; if this doesn't hold the /// result is basically junk. /// /// NB: The result could be returned in N-1 elements, but we return /// N and set the last element to zero since the calling code /// happens to require a result zero-extended to N elements. pub(crate) fn pol_remove_root_2exp(a: [T; N]) -> [T; N] where T: Copy + Default + Neg + Shr + Sub, { // By assumption β := 2^EXP is a root of `a`, i.e. (x - β) divides // `a`; if we write // // a(x) = \sum_{i=0}^{N-1} a[i] x^i // = (x - β) \sum_{i=0}^{N-2} q[i] x^i // // then by comparing coefficients it is easy to see that // // q[0] = -a[0] / β and q[i] = (q[i-1] - a[i]) / β // // for 0 < i <= N-1 (and the divisions are exact). let mut q = [T::default(); N]; q[0] = -(a[0] >> EXP); // NB: Last element of q is deliberately left equal to zero. for deg in 1..N - 1 { q[deg] = (q[deg - 1] - a[deg]) >> EXP; } q } /// Read the range `value_idxs` of values from `lv` into an array of /// length `N`. Panics if the length of the range is not `N`. pub(crate) fn read_value( lv: &[T; NUM_ARITH_COLUMNS], value_idxs: Range, ) -> [T; N] { lv[value_idxs].try_into().unwrap() } /// Read the range `value_idxs` of values from `lv` into an array of /// length `N`, interpreting the values as `u64`s. Panics if the /// length of the range is not `N`. pub(crate) fn read_value_u64_limbs( lv: &[F; NUM_ARITH_COLUMNS], value_idxs: Range, ) -> [u64; N] { let limbs: [_; N] = lv[value_idxs].try_into().unwrap(); limbs.map(|c| F::to_canonical_u64(&c)) } /// Read the range `value_idxs` of values from `lv` into an array of /// length `N`, interpreting the values as `i64`s. Panics if the /// length of the range is not `N`. pub(crate) fn read_value_i64_limbs( lv: &[F; NUM_ARITH_COLUMNS], value_idxs: Range, ) -> [i64; N] { let limbs: [_; N] = lv[value_idxs].try_into().unwrap(); limbs.map(|c| F::to_canonical_u64(&c) as i64) }