use halo2_base::{
gates::flex_gate::GateInstructions,
utils::{biguint_to_fe, decompose_biguint, fe_to_biguint, BigPrimeField, ScalarField},
AssignedValue, Context,
QuantumCell::Constant,
};
use num_bigint::{BigInt, BigUint};
use num_traits::Zero;
pub mod add_no_carry;
pub mod big_is_equal;
pub mod big_is_zero;
pub mod big_less_than;
pub mod carry_mod;
pub mod check_carry_mod_to_zero;
pub mod check_carry_to_zero;
pub mod mul_no_carry;
pub mod negative;
pub mod scalar_mul_and_add_no_carry;
pub mod scalar_mul_no_carry;
pub mod select;
pub mod select_by_indicator;
pub mod sub;
pub mod sub_no_carry;
#[derive(Clone, Debug, PartialEq, Default)]
pub enum BigIntStrategy {
#[default]
Simple,
}
#[derive(Clone, Debug)]
pub struct OverflowInteger<F: ScalarField> {
pub limbs: Vec<AssignedValue<F>>,
pub max_limb_bits: usize,
}
impl<F: ScalarField> OverflowInteger<F> {
pub fn new(limbs: Vec<AssignedValue<F>>, max_limb_bits: usize) -> Self {
Self { limbs, max_limb_bits }
}
#[cfg(test)]
pub fn to_bigint(&self, limb_bits: usize) -> BigInt
where
F: BigPrimeField,
{
use halo2_base::utils::fe_to_bigint;
self.limbs
.iter()
.rev()
.fold(BigInt::zero(), |acc, acell| (acc << limb_bits) + fe_to_bigint(acell.value()))
}
pub fn evaluate_native(
ctx: &mut Context<F>,
gate: &impl GateInstructions<F>,
limbs: impl IntoIterator<Item = AssignedValue<F>>,
limb_bases: &[F],
) -> AssignedValue<F> {
gate.inner_product(ctx, limbs, limb_bases.iter().map(|c| Constant(*c)))
}
}
#[repr(transparent)]
#[derive(Clone, Debug)]
pub struct ProperUint<F: ScalarField>(pub(crate) Vec<AssignedValue<F>>);
impl<F: ScalarField> ProperUint<F> {
pub fn limbs(&self) -> &[AssignedValue<F>] {
self.0.as_slice()
}
pub fn into_overflow(self, limb_bits: usize) -> OverflowInteger<F> {
OverflowInteger::new(self.0, limb_bits)
}
pub fn into_crt(
self,
ctx: &mut Context<F>,
gate: &impl GateInstructions<F>,
value: BigUint,
limb_bases: &[F],
limb_bits: usize,
) -> ProperCrtUint<F> {
let native =
OverflowInteger::evaluate_native(ctx, gate, self.0.iter().copied(), limb_bases);
ProperCrtUint(CRTInteger::new(self.into_overflow(limb_bits), native, value.into()))
}
}
#[repr(transparent)]
#[derive(Clone, Debug)]
pub struct FixedOverflowInteger<F: ScalarField> {
pub limbs: Vec<F>,
}
impl<F: BigPrimeField> FixedOverflowInteger<F> {
pub fn construct(limbs: Vec<F>) -> Self {
Self { limbs }
}
pub fn from_native(value: &BigUint, num_limbs: usize, limb_bits: usize) -> Self {
let limbs = decompose_biguint(value, num_limbs, limb_bits);
Self { limbs }
}
pub fn to_bigint(&self, limb_bits: usize) -> BigUint {
self.limbs
.iter()
.rev()
.fold(BigUint::zero(), |acc, x| (acc << limb_bits) + fe_to_biguint(x))
}
pub fn assign(self, ctx: &mut Context<F>) -> ProperUint<F> {
let assigned_limbs = self.limbs.into_iter().map(|limb| ctx.load_constant(limb)).collect();
ProperUint(assigned_limbs)
}
pub fn select_by_indicator(
gate: &impl GateInstructions<F>,
ctx: &mut Context<F>,
a: &[Self],
coeffs: &[AssignedValue<F>],
limb_bits: usize,
) -> OverflowInteger<F> {
let k = a[0].limbs.len();
let out_limbs = (0..k)
.map(|idx| {
let int_limbs = a.iter().map(|a| Constant(a.limbs[idx]));
gate.select_by_indicator(ctx, int_limbs, coeffs.iter().copied())
})
.collect();
OverflowInteger::new(out_limbs, limb_bits)
}
}
#[derive(Clone, Debug)]
pub struct CRTInteger<F: ScalarField> {
pub truncation: OverflowInteger<F>,
pub native: AssignedValue<F>,
pub value: BigInt,
}
impl<F: ScalarField> AsRef<CRTInteger<F>> for CRTInteger<F> {
fn as_ref(&self) -> &CRTInteger<F> {
self
}
}
impl<'a, F: ScalarField> From<&'a CRTInteger<F>> for CRTInteger<F> {
fn from(x: &'a CRTInteger<F>) -> Self {
x.clone()
}
}
impl<F: ScalarField> CRTInteger<F> {
pub fn new(truncation: OverflowInteger<F>, native: AssignedValue<F>, value: BigInt) -> Self {
Self { truncation, native, value }
}
pub fn native(&self) -> &AssignedValue<F> {
&self.native
}
pub fn limbs(&self) -> &[AssignedValue<F>] {
self.truncation.limbs.as_slice()
}
}
#[repr(transparent)]
#[derive(Clone, Debug)]
pub struct ProperCrtUint<F: ScalarField>(pub(crate) CRTInteger<F>);
impl<F: ScalarField> AsRef<CRTInteger<F>> for ProperCrtUint<F> {
fn as_ref(&self) -> &CRTInteger<F> {
&self.0
}
}
impl<'a, F: ScalarField> From<&'a ProperCrtUint<F>> for ProperCrtUint<F> {
fn from(x: &'a ProperCrtUint<F>) -> Self {
x.clone()
}
}
impl<F: ScalarField> From<ProperCrtUint<F>> for CRTInteger<F> {
fn from(x: ProperCrtUint<F>) -> Self {
x.0
}
}
impl<'a, F: ScalarField> From<&'a ProperCrtUint<F>> for CRTInteger<F> {
fn from(x: &'a ProperCrtUint<F>) -> Self {
x.0.clone()
}
}
impl<F: ScalarField> From<ProperCrtUint<F>> for ProperUint<F> {
fn from(x: ProperCrtUint<F>) -> Self {
ProperUint(x.0.truncation.limbs)
}
}
impl<F: ScalarField> ProperCrtUint<F> {
pub fn limbs(&self) -> &[AssignedValue<F>] {
self.0.limbs()
}
pub fn native(&self) -> &AssignedValue<F> {
self.0.native()
}
pub fn value(&self) -> BigUint {
self.0.value.to_biguint().expect("Value of proper uint should not be negative")
}
}
#[derive(Clone, Debug)]
pub struct FixedCRTInteger<F: ScalarField> {
pub truncation: FixedOverflowInteger<F>,
pub value: BigUint,
}
impl<F: BigPrimeField> FixedCRTInteger<F> {
pub fn new(truncation: FixedOverflowInteger<F>, value: BigUint) -> Self {
Self { truncation, value }
}
pub fn from_native(value: BigUint, num_limbs: usize, limb_bits: usize) -> Self {
let truncation = FixedOverflowInteger::from_native(&value, num_limbs, limb_bits);
Self { truncation, value }
}
pub fn assign(
self,
ctx: &mut Context<F>,
limb_bits: usize,
native_modulus: &BigUint,
) -> ProperCrtUint<F> {
let assigned_truncation = self.truncation.assign(ctx).into_overflow(limb_bits);
let assigned_native = ctx.load_constant(biguint_to_fe(&(&self.value % native_modulus)));
ProperCrtUint(CRTInteger::new(assigned_truncation, assigned_native, self.value.into()))
}
}