Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ivc): protogalaxy::AssignedUnivariatePoly::eval #379

Merged
merged 2 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 238 additions & 5 deletions src/ivc/protogalaxy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ mod verify_chip {
use std::iter;

use halo2_proofs::{
circuit::AssignedCell,
circuit::{AssignedCell, Value as Halo2Value},
halo2curves::{
ff::{FromUniformBytes, PrimeField, PrimeFieldBits},
CurveAffine,
Expand Down Expand Up @@ -183,8 +183,59 @@ mod verify_chip {
}
}

/// Powers of one assigned value counted on-circuit
///
/// Since powers are required many times during synthesis, let's keep these values separate
/// ```math
/// x^0, x^1, x^2, x^3, ... x^i, ...
/// ```
pub struct ValuePowers<F: PrimeField> {
powers: Vec<AssignedValue<F>>,
}

impl<F: PrimeField> ValuePowers<F> {
pub fn new(one: AssignedValue<F>, value: AssignedValue<F>) -> Self {
Self {
powers: vec![one, value],
}
}

pub fn iter(&self) -> impl Iterator<Item = &AssignedValue<F>> {
self.powers.iter()
}

pub fn value(&self) -> &AssignedValue<F> {
self.powers
.get(1)
.expect("Cannot be created without at least one element inside")
}

/// Get from cache or calculate the `exp` degree of original value
///
/// `self.value^exp`
pub fn get_or_eval<const T: usize>(
&mut self,
region: &mut RegionCtx<F>,
main_gate: &MainGate<F, T>,
exp: usize,
) -> Result<AssignedValue<F>, Halo2PlonkError> {
if let Some(value) = self.powers.get(exp) {
return Ok(value.clone());
}

while self.powers.len() <= exp {
let value = self.value();
let last = self.powers.last().unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should each time double the last value instead of multiply by first element? i.e.

{delta, delta^2, delta^4,...}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is not related to delta, this is just a cache for x^0, x^1, x^2,....

Doc updated for this method\struct

let new = main_gate.mul(region, value, last)?;
self.powers.push(new);
}

Ok(self.powers.get(exp).cloned().unwrap())
}
}

/// Assigned version of [`crate::polynomial::univariate::UnivariatePoly`]
pub struct AssignedUnivariatePoly<F: PrimeField>(Box<[AssignedValue<F>]>);
pub struct AssignedUnivariatePoly<F: PrimeField>(UnivariatePoly<AssignedValue<F>>);

impl<F: PrimeField> AssignedUnivariatePoly<F> {
pub fn assign<const T: usize>(
Expand All @@ -193,13 +244,13 @@ mod verify_chip {
annotation: &'static str,
poly: &UnivariatePoly<F>,
) -> Result<Self, Error> {
let up = AssignedUnivariatePoly(
let up = AssignedUnivariatePoly(UnivariatePoly(
main_gate_config
.advice_cycle_assigner()
.assign_all_advice(region, || annotation, poly.iter().copied())
.map_err(|err| Error::Assign { annotation, err })?
.into_boxed_slice(),
);
));

region.next();

Expand All @@ -214,6 +265,103 @@ mod verify_chip {
.inspect(|coeff| debug!("coeff {coeff:?}"))
.map(|coeff| WrapValue::Assigned(coeff.clone()))
}

fn degree(&self) -> usize {
self.0.len()
}

fn len(&self) -> usize {
self.0.len()
}

pub fn eval<const T: usize>(
&self,
region: &mut RegionCtx<F>,
main_gate_config: MainGateConfig<T>,
mut challenge_powers: ValuePowers<F>,
) -> Result<AssignedValue<F>, Halo2PlonkError> {
let main_gate = MainGate::<F, T>::new(main_gate_config.clone());

let enable_selectors = |region: &mut RegionCtx<F>| {
[
main_gate_config.q_m[0],
main_gate_config.q_m[1],
main_gate_config.q_i,
main_gate_config.q_o,
]
.iter()
.try_for_each(|col| region.assign_fixed(|| "one", *col, F::ZERO).map(|_| ()))
};
let coeffs_col = [main_gate_config.state[0], main_gate_config.state[2]];
let cha_col = [main_gate_config.state[1], main_gate_config.state[3]];
let prev_col = &main_gate_config.input;
let result_col = &main_gate_config.out;

challenge_powers.get_or_eval(region, &main_gate, self.len().saturating_sub(1))?;

self.0
.iter()
.zip_eq(challenge_powers.iter())
.chunks(2)
.into_iter()
.try_fold(Option::<AssignedValue<F>>::None, |prev, chunks| {
let (coeffs, cha_in_power): (Vec<_>, Vec<_>) = chunks.unzip();
enable_selectors(region)?;

let assigned_prev = match prev {
None => {
region.assign_advice(|| "zero", *prev_col, Halo2Value::known(F::ZERO))
}
Some(prev_cell) => region.assign_advice_from(
|| "previous chunk values",
*prev_col,
prev_cell,
),
}?;

let assigned_coeffs = coeffs
.iter()
.zip_eq(coeffs_col)
.map(|(coeff, col)| region.assign_advice_from(|| "coeff", col, *coeff))
.collect::<Result<Box<[_]>, _>>()?;

let assigned_cha = cha_in_power
.iter()
.zip_eq(cha_col)
.map(|(cha_in_power, col)| {
region.assign_advice_from(|| "cha", col, *cha_in_power)
})
.collect::<Result<Box<[_]>, _>>()?;

let output = assigned_coeffs
.iter()
.zip_eq(assigned_cha.iter())
.fold(assigned_prev.value().copied(), |res, (coeff, cha)| {
res + (coeff.value().copied() * cha.value())
});

let assigned_output = region.assign_advice(|| "result", *result_col, output);

debug!(
"coeffs: {:?}; cha_in_power: {:?}, prev: {:?}, output: {:?}",
coeffs.iter().map(|cell| cell.value()).collect::<Box<[_]>>(),
cha_in_power
.iter()
.map(|cell| cell.value())
.collect::<Box<[_]>>(),
assigned_prev.value(),
assigned_output
.as_ref()
.ok()
.and_then(|cell| cell.value().unwrap()),
);

region.next();

assigned_output.map(Some)
})?
.ok_or_else(|| Halo2PlonkError::Synthesis)
}
}

/// Assigned version of [`crate::nifs::protogalaxy::Proof]
Expand Down Expand Up @@ -427,7 +575,11 @@ mod verify_chip {
mod tests {
use halo2_proofs::{
arithmetic::Field,
circuit::{floor_planner::single_pass::SingleChipLayouter, Layouter},
circuit::{
floor_planner::single_pass::SingleChipLayouter, Layouter, SimpleFloorPlanner,
},
dev::MockProver,
plonk::Circuit,
};
use tracing_test::traced_test;

Expand Down Expand Up @@ -647,5 +799,86 @@ mod verify_chip {

assert_eq!(off_circuit_beta_strokes, on_circuit_beta_strokes);
}

#[traced_test]
#[test]
fn poly_eval() {
struct TestCircuit;

impl Circuit<Base> for TestCircuit {
type Config = MainGateConfig<T>;
type FloorPlanner = SimpleFloorPlanner;

fn without_witnesses(&self) -> Self {
todo!()
}

fn configure(meta: &mut ConstraintSystem<Base>) -> Self::Config {
MainGate::configure(meta)
}

fn synthesize(
&self,
main_gate_config: Self::Config,
mut layouter: impl Layouter<Base>,
) -> Result<(), Halo2PlonkError> {
let cha = Base::from_u128(123);
let poly = UnivariatePoly::from_iter((0..).map(Into::into).take(10));

let off_circuit_res = poly.eval(cha);

let on_circuit_res = layouter.assign_region(
|| "assigned_poly_eval",
move |region| {
let mut region = RegionCtx::new(region, 0);

let cha = region
.assign_advice(
|| "",
main_gate_config.state[0],
Halo2Value::known(cha),
)
.unwrap();

let one = region
.assign_advice(
|| "",
main_gate_config.state[1],
Halo2Value::known(Base::ONE),
)
.unwrap();

region.next();

let cha = ValuePowers::new(one, cha);

let poly = AssignedUnivariatePoly::assign(
&mut region,
main_gate_config.clone(),
"test poly",
&poly,
)
.unwrap();

Ok(poly
.eval(&mut region, main_gate_config.clone(), cha)
.unwrap())
},
)?;

assert_eq!(
off_circuit_res,
on_circuit_res.value().unwrap().copied().unwrap()
);

Ok(())
}
}

MockProver::run(12, &TestCircuit {}, vec![])
.unwrap()
.verify()
.unwrap();
}
}
}
33 changes: 18 additions & 15 deletions src/polynomial/univariate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,20 @@ use std::{
};

use halo2_proofs::halo2curves::ff::{PrimeField, WithSmallOrderMulGroup};
use tracing::*;

use crate::{ff::Field, fft, util};

/// Represents a univariate polynomial
///
/// Coefficients of the polynomial are presented from smaller degree to larger degree
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct UnivariatePoly<F: Field>(pub(crate) Box<[F]>);
pub struct UnivariatePoly<F>(pub(crate) Box<[F]>);

impl<F: Field> UnivariatePoly<F> {
pub fn new_zeroed(size: usize) -> Self {
Self::from_iter(iter::repeat(F::ZERO).take(size))
}
pub fn iter(&self) -> impl Iterator<Item = &F> {
self.0.iter()
}
pub fn degree(&self) -> usize {
self.0
.iter()
Expand All @@ -31,7 +29,19 @@ impl<F: Field> UnivariatePoly<F> {
}
}

impl<F: Field> IntoIterator for UnivariatePoly<F> {
impl<F> UnivariatePoly<F> {
pub fn iter(&self) -> impl Iterator<Item = &F> {
self.0.iter()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}

impl<F> IntoIterator for UnivariatePoly<F> {
type Item = F;
type IntoIter = <Box<[F]> as IntoIterator>::IntoIter;

Expand All @@ -40,13 +50,13 @@ impl<F: Field> IntoIterator for UnivariatePoly<F> {
}
}

impl<F: Field> AsMut<[F]> for UnivariatePoly<F> {
impl<F> AsMut<[F]> for UnivariatePoly<F> {
fn as_mut(&mut self) -> &mut [F] {
self.0.as_mut()
}
}

impl<F: Field> FromIterator<F> for UnivariatePoly<F> {
impl<F> FromIterator<F> for UnivariatePoly<F> {
fn from_iter<T: IntoIterator<Item = F>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
Expand All @@ -58,6 +68,7 @@ impl<F: Field> UnivariatePoly<F> {
self.0
.iter()
.zip(iter::successors(Some(F::ONE), |val| Some(*val * challenge)))
.inspect(|(coeff, cha)| debug!("coeff: {coeff:?}, challenge_in_degree: {cha:?}"))
.fold(F::ZERO, |res, (coeff, challenge_in_degree)| {
res + (challenge_in_degree * *coeff)
})
Expand All @@ -75,14 +86,6 @@ impl<F: Field> UnivariatePoly<F> {
}
}

pub fn len(&self) -> usize {
self.0.len()
}

pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

/// Multiplies all coefficients of the polynomial by a field element.
pub fn scale(&self, factor: F) -> UnivariatePoly<F> {
let scaled_coeffs: Vec<F> = self.iter().map(|&coeff| coeff * factor).collect();
Expand Down
Loading