From 8bc591fd266a887629f97bd2aeb77876f672a3ba Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Wed, 8 Jan 2025 15:56:19 +0100 Subject: [PATCH 1/4] Arrabbiata: use the concept of "program state" --- arrabbiata/src/main.rs | 2 +- arrabbiata/src/witness.rs | 48 ++++++++++++++++++++++++------------- arrabbiata/tests/witness.rs | 8 +++---- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/arrabbiata/src/main.rs b/arrabbiata/src/main.rs index 69b72b0c67..d29e01575b 100644 --- a/arrabbiata/src/main.rs +++ b/arrabbiata/src/main.rs @@ -106,7 +106,7 @@ pub fn main() { // env.next_commitments) // update next instance with current commitments // FIXME: Check twice the updated commitments - env.compute_and_update_previous_commitments(); + env.commit_state(); // FIXME: // Absorb all commitments in the sponge. diff --git a/arrabbiata/src/witness.rs b/arrabbiata/src/witness.rs index 7dd7dd4557..bae2786940 100644 --- a/arrabbiata/src/witness.rs +++ b/arrabbiata/src/witness.rs @@ -61,9 +61,9 @@ pub struct Env< // FIXME: use a blinded comm and also fold the blinder pub ivc_accumulator_e2: Vec>, - /// Commitments to the previous instances - pub previous_commitments_e1: Vec>, - pub previous_commitments_e2: Vec>, + /// Commitments to the previous program states. + pub previous_committed_state_e1: Vec>, + pub previous_committed_state_e2: Vec>, // ---------------- // ---------------- @@ -498,7 +498,7 @@ where if self.current_iteration % 2 == 0 { match side { Side::Left => { - let pt = self.previous_commitments_e2[i_comm].get_first_chunk(); + let pt = self.previous_committed_state_e2[i_comm].get_first_chunk(); // We suppose we never have a commitment equals to the // point at infinity let (pt_x, pt_y) = pt.to_coordinates().unwrap(); @@ -524,7 +524,7 @@ where } else { match side { Side::Left => { - let pt = self.previous_commitments_e1[i_comm].get_first_chunk(); + let pt = self.previous_committed_state_e1[i_comm].get_first_chunk(); // We suppose we never have a commitment equals to the // point at infinity let (pt_x, pt_y) = pt.to_coordinates().unwrap(); @@ -568,11 +568,11 @@ where } Side::Right => { if self.current_iteration % 2 == 0 { - let pt = self.previous_commitments_e2[i_comm].get_first_chunk(); + let pt = self.previous_committed_state_e2[i_comm].get_first_chunk(); let (x, y) = pt.to_coordinates().unwrap(); (x.to_biguint().into(), y.to_biguint().into()) } else { - let pt = self.previous_commitments_e1[i_comm].get_first_chunk(); + let pt = self.previous_committed_state_e1[i_comm].get_first_chunk(); let (x, y) = pt.to_coordinates().unwrap(); (x.to_biguint().into(), y.to_biguint().into()) } @@ -822,10 +822,10 @@ impl< }; // Default set to the blinders. Using double to make the EC scaling happy. - let previous_commitments_e1: Vec> = (0..NUMBER_OF_COLUMNS) + let previous_committed_state_e1: Vec> = (0..NUMBER_OF_COLUMNS) .map(|_| PolyComm::new(vec![(srs_e1.h + srs_e1.h).into()])) .collect(); - let previous_commitments_e2: Vec> = (0..NUMBER_OF_COLUMNS) + let previous_committed_state_e2: Vec> = (0..NUMBER_OF_COLUMNS) .map(|_| PolyComm::new(vec![(srs_e2.h + srs_e2.h).into()])) .collect(); // FIXME: zero will not work. @@ -851,8 +851,8 @@ impl< // IVC only ivc_accumulator_e1, ivc_accumulator_e2, - previous_commitments_e1, - previous_commitments_e2, + previous_committed_state_e1, + previous_committed_state_e2, // ------ // ------ idx_var: 0, @@ -909,11 +909,19 @@ impl< // TODO } - /// Compute the commitments to the current witness, and update the previous - /// instances. - // Might be worth renaming this function - pub fn compute_and_update_previous_commitments(&mut self) { + /// Commit to the program state and updating the environment with the + /// result. + /// + /// This method is supposed to be called after a new iteration of the + /// program has been executed. + pub fn commit_state(&mut self) { if self.current_iteration % 2 == 0 { + assert_eq!( + self.current_row as u64, + self.domain_fp.d1.size, + "The program has not been fully executed. Missing {} rows", + self.domain_fp.d1.size - self.current_row as u64, + ); let comms: Vec> = self .witness .par_iter() @@ -927,8 +935,14 @@ impl< .commit_evaluations_non_hiding(self.domain_fp.d1, &evals) }) .collect(); - self.previous_commitments_e1 = comms + self.previous_committed_state_e1 = comms } else { + assert_eq!( + self.current_row as u64, + self.domain_fq.d1.size, + "The program has not been fully executed. Missing {} rows", + self.domain_fq.d1.size - self.current_row as u64, + ); let comms: Vec> = self .witness .iter() @@ -942,7 +956,7 @@ impl< .commit_evaluations_non_hiding(self.domain_fq.d1, &evals) }) .collect(); - self.previous_commitments_e2 = comms + self.previous_committed_state_e2 = comms } } diff --git a/arrabbiata/tests/witness.rs b/arrabbiata/tests/witness.rs index 400937091b..dd9d9530a2 100644 --- a/arrabbiata/tests/witness.rs +++ b/arrabbiata/tests/witness.rs @@ -79,7 +79,7 @@ fn test_unit_witness_elliptic_curve_addition() { assert_eq!(env.current_iteration, 0); let (exp_x3, exp_y3) = { let res: Pallas = (env.ivc_accumulator_e2[0].get_first_chunk() - + env.previous_commitments_e2[0].get_first_chunk()) + + env.previous_committed_state_e2[0].get_first_chunk()) .into(); let (x3, y3) = res.to_coordinates().unwrap(); ( @@ -99,7 +99,7 @@ fn test_unit_witness_elliptic_curve_addition() { assert_eq!(env.current_iteration, 1); let (exp_x3, exp_y3) = { let res: Vesta = (env.ivc_accumulator_e1[0].get_first_chunk() - + env.previous_commitments_e1[0].get_first_chunk()) + + env.previous_committed_state_e1[0].get_first_chunk()) .into(); let (x3, y3) = res.to_coordinates().unwrap(); ( @@ -119,7 +119,7 @@ fn test_unit_witness_elliptic_curve_addition() { assert_eq!(env.current_iteration, 2); let (exp_x3, exp_y3) = { let res: Pallas = (env.ivc_accumulator_e2[0].get_first_chunk() - + env.previous_commitments_e2[0].get_first_chunk()) + + env.previous_committed_state_e2[0].get_first_chunk()) .into(); let (x3, y3) = res.to_coordinates().unwrap(); ( @@ -188,7 +188,7 @@ where let x = Fq::rand(rng); Pallas::generator().mul_bigint(x.into_bigint()).into() }; - env.previous_commitments_e2[0] = PolyComm::new(vec![p1]); + env.previous_committed_state_e2[0] = PolyComm::new(vec![p1]); // We only go up to the maximum bit field size. (0..MAXIMUM_FIELD_SIZE_IN_BITS).for_each(|bit_idx| { From b0d201f98ff27e02c4ac52604ed15acaa499968d Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Wed, 8 Jan 2025 15:56:52 +0100 Subject: [PATCH 2/4] Arrabbiata/Env: describe the concept of "program state" --- arrabbiata/src/witness.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/arrabbiata/src/witness.rs b/arrabbiata/src/witness.rs index bae2786940..120d1e3d69 100644 --- a/arrabbiata/src/witness.rs +++ b/arrabbiata/src/witness.rs @@ -19,18 +19,31 @@ use crate::{ NUMBER_OF_VALUES_TO_ABSORB_PUBLIC_IO, }; -/// The first instruction in the IVC is the Poseidon permutation. It is used to -/// start hashing the public input. +/// The first instruction in the verifier circuit (often shortened in "IVC" in +/// the crate) is the Poseidon permutation. It is used to start hashing the +/// public input. pub const IVC_STARTING_INSTRUCTION: Instruction = Instruction::Poseidon(0); -/// An environment that can be shared between IVC instances. +/// An environment is used to contain the state of a long "running program". /// -/// It contains all the accumulators that can be picked for a given fold -/// instance k, including the sponges. +/// The running program is composed of two parts: one user application and one +/// verifier application. The verifier application is used to encode the +/// correctness of previous program states computations. +/// +/// The term "app(lication) state" will be used to refer to the state of the +/// user application, and the term "IVC state" will be used to refer to the +/// state of the verifier application. The term state will be used to refer to +/// the state of the whole program. +/// +/// The environment contains all the accumulators that can be picked for a given +/// fold instance k, including the sponges. /// /// The environment is run over big integers to avoid performing /// reduction at all step. Instead the user implementing the interpreter can /// reduce in the corresponding field when they want. +/// +/// The environment is generic over two curves (called E1 and E2) that are +/// supposed to form a cycle. pub struct Env< Fp: PrimeField, Fq: PrimeField, From 70e09e4ea795d52352c2ad54ab62959e7702bef2 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Thu, 9 Jan 2025 18:06:07 +0100 Subject: [PATCH 3/4] Arrabbiata: WIP --- arrabbiata/src/constraints.rs | 2 +- arrabbiata/src/curve.rs | 6 +++++- arrabbiata/src/witness.rs | 27 ++++++++++++++++++++++++--- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/arrabbiata/src/constraints.rs b/arrabbiata/src/constraints.rs index 02e1bc5e0e..af08765c98 100644 --- a/arrabbiata/src/constraints.rs +++ b/arrabbiata/src/constraints.rs @@ -30,7 +30,7 @@ pub struct Env { impl Env where - <::Params as CurveConfig>::BaseField: PrimeField, + C::BaseField: PrimeField, { pub fn new() -> Self { // This check might not be useful diff --git a/arrabbiata/src/curve.rs b/arrabbiata/src/curve.rs index cc1a72b2c7..af526bd234 100644 --- a/arrabbiata/src/curve.rs +++ b/arrabbiata/src/curve.rs @@ -6,6 +6,7 @@ //! different curves. use ark_ec::short_weierstrass::Affine; +use ark_ff::PrimeField; use kimchi::curve::{pallas_endos, vesta_endos}; use mina_curves::pasta::curves::{pallas::PallasParameters, vesta::VestaParameters}; use mina_poseidon::{constants::SpongeConstants, poseidon::ArithmeticSpongeParams}; @@ -28,7 +29,10 @@ impl SpongeConstants for PlonkSpongeConstants { /// Represents additional information that a curve needs in order to be used /// with Arrabbiata. -pub trait ArrabbiataCurve: CommitmentCurve + EndoCurve { +pub trait ArrabbiataCurve: CommitmentCurve + EndoCurve +where + Self::BaseField: PrimeField, +{ /// A human readable name. const NAME: &'static str; diff --git a/arrabbiata/src/witness.rs b/arrabbiata/src/witness.rs index 120d1e3d69..d574108b03 100644 --- a/arrabbiata/src/witness.rs +++ b/arrabbiata/src/witness.rs @@ -3,11 +3,11 @@ use ark_ff::PrimeField; use ark_poly::Evaluations; use kimchi::circuits::{domains::EvaluationDomains, gate::CurrOrNext}; use log::{debug, info}; -use mina_poseidon::constants::SpongeConstants; +use mina_poseidon::{constants::SpongeConstants, sponge::DefaultFqSponge, FqSponge}; use num_bigint::{BigInt, BigUint}; use num_integer::Integer; use o1_utils::field_helpers::FieldHelpers; -use poly_commitment::{ipa::SRS, PolyComm, SRS as _}; +use poly_commitment::{commitment::CommitmentCurve, ipa::SRS, PolyComm, SRS as _}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use std::time::Instant; @@ -49,7 +49,10 @@ pub struct Env< Fq: PrimeField, E1: ArrabbiataCurve, E2: ArrabbiataCurve, -> { +> where + E1::BaseField: PrimeField, + E2::BaseField: PrimeField, +{ // ---------------- // Setup related (domains + SRS) /// Domain for Fp @@ -973,6 +976,24 @@ impl< } } + fn absorb_state(&mut self) { + if self.current_iteration % 2 == 0 { + let sponge: DefaultFqSponge = + DefaultFqSponge::new(E1::other_curve_sponge_params()); + self.sponge_e1.iter().for_each(|v| { + let v = Fq::from_biguint(&v.to_biguint().unwrap()).unwrap(); + sponge.absorb(v); + }); + } else { + let sponge: DefaultFqSponge = + DefaultFqSponge::new(E2::other_curve_sponge_params()); + self.sponge_e2.iter().for_each(|v| { + let v = Fp::from_biguint(&v.to_biguint().unwrap()).unwrap(); + sponge.absorb(v); + }); + } + } + /// Compute the output of the application on the previous output // TODO: we should compute the hash of the previous commitments, only on // CPU? From 90f12b1222bfa328492a498dd5738fb7e46ef151 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Sun, 12 Jan 2025 08:52:50 +0100 Subject: [PATCH 4/4] WIP --- arrabbiata/src/constraints.rs | 20 ++++++++-- arrabbiata/src/witness.rs | 73 ++++++++++++++++++++--------------- 2 files changed, 58 insertions(+), 35 deletions(-) diff --git a/arrabbiata/src/constraints.rs b/arrabbiata/src/constraints.rs index af08765c98..8665ca7265 100644 --- a/arrabbiata/src/constraints.rs +++ b/arrabbiata/src/constraints.rs @@ -17,7 +17,10 @@ use o1_utils::FieldHelpers; use poly_commitment::commitment::CommitmentCurve; #[derive(Clone, Debug)] -pub struct Env { +pub struct Env +where + C::BaseField: PrimeField, +{ /// The parameter a is the coefficients of the elliptic curve in affine /// coordinates. pub a: BigInt, @@ -31,10 +34,12 @@ pub struct Env { impl Env where C::BaseField: PrimeField, + <::Params as CurveConfig>::BaseField: PrimeField, { pub fn new() -> Self { // This check might not be useful - let a: BigInt = ::Params::COEFF_A.to_biguint().into(); + let a = ::Params::COEFF_A; + let a: BigInt = a.to_biguint().into(); assert!( a < C::ScalarField::modulus_biguint().into(), "a is too large" @@ -55,7 +60,10 @@ where /// proof. /// The constraint environment must be instantiated only once, at the last step /// of the computation. -impl InterpreterEnv for Env { +impl InterpreterEnv for Env +where + C::BaseField: PrimeField, +{ type Position = (Column, CurrOrNext); type Variable = E; @@ -311,7 +319,10 @@ impl InterpreterEnv for Env { } } -impl Env { +impl Env +where + C::BaseField: PrimeField, +{ /// Get all the constraints for the IVC circuit, only. /// /// The following gadgets are used in the IVC circuit: @@ -380,6 +391,7 @@ impl Env { impl Default for Env where + C::BaseField: PrimeField, <::Params as CurveConfig>::BaseField: PrimeField, { fn default() -> Self { diff --git a/arrabbiata/src/witness.rs b/arrabbiata/src/witness.rs index d574108b03..0f149e1fbd 100644 --- a/arrabbiata/src/witness.rs +++ b/arrabbiata/src/witness.rs @@ -56,10 +56,10 @@ pub struct Env< // ---------------- // Setup related (domains + SRS) /// Domain for Fp - pub domain_fp: EvaluationDomains, + pub domain_fp: EvaluationDomains, /// Domain for Fq - pub domain_fq: EvaluationDomains, + pub domain_fq: EvaluationDomains, /// SRS for the first curve pub srs_e1: SRS, @@ -209,6 +209,8 @@ impl< where ::BaseField: PrimeField, ::BaseField: PrimeField, + E1::BaseField: PrimeField, + E2::BaseField: PrimeField, { type Position = (Column, CurrOrNext); @@ -258,9 +260,9 @@ where unimplemented!("Only works for private inputs") }; let modulus: BigInt = if self.current_iteration % 2 == 0 { - Fp::modulus_biguint().into() + E1::ScalarField::modulus_biguint().into() } else { - Fq::modulus_biguint().into() + E2::ScalarField::modulus_biguint().into() }; let v = v.mod_floor(&modulus); match row { @@ -280,9 +282,9 @@ where unimplemented!("Only works for public input columns") }; let modulus: BigInt = if self.current_iteration % 2 == 0 { - Fp::modulus_biguint().into() + E1::ScalarField::modulus_biguint().into() } else { - Fq::modulus_biguint().into() + E2::ScalarField::modulus_biguint().into() }; let v = v.mod_floor(&modulus); self.public_state[idx] = v.clone(); @@ -297,9 +299,9 @@ where fn constrain_boolean(&mut self, x: Self::Variable) { let modulus: BigInt = if self.current_iteration % 2 == 0 { - Fp::modulus_biguint().into() + E1::ScalarField::modulus_biguint().into() } else { - Fq::modulus_biguint().into() + E2::ScalarField::modulus_biguint().into() }; let x = x.mod_floor(&modulus); assert!(x == BigInt::from(0_usize) || x == BigInt::from(1_usize)); @@ -431,10 +433,10 @@ where unsafe fn save_poseidon_state(&mut self, x: Self::Variable, i: usize) { if self.current_iteration % 2 == 0 { - let modulus: BigInt = Fp::modulus_biguint().into(); + let modulus: BigInt = E1::ScalarField::modulus_biguint().into(); self.sponge_e1[i] = x.mod_floor(&modulus) } else { - let modulus: BigInt = Fq::modulus_biguint().into(); + let modulus: BigInt = E2::ScalarField::modulus_biguint().into(); self.sponge_e2[i] = x.mod_floor(&modulus) } } @@ -650,14 +652,14 @@ where /// Zero is not allowed as an input. unsafe fn inverse(&mut self, pos: Self::Position, x: Self::Variable) -> Self::Variable { let res = if self.current_iteration % 2 == 0 { - Fp::from_biguint(&x.to_biguint().unwrap()) + E1::ScalarField::from_biguint(&x.to_biguint().unwrap()) .unwrap() .inverse() .unwrap() .to_biguint() .into() } else { - Fq::from_biguint(&x.to_biguint().unwrap()) + E2::ScalarField::from_biguint(&x.to_biguint().unwrap()) .unwrap() .inverse() .unwrap() @@ -677,9 +679,9 @@ where y2: Self::Variable, ) -> Self::Variable { let modulus: BigInt = if self.current_iteration % 2 == 0 { - Fp::modulus_biguint().into() + E1::ScalarField::modulus_biguint().into() } else { - Fq::modulus_biguint().into() + E2::ScalarField::modulus_biguint().into() }; // If it is not the same point, we compute lambda as: // - λ = (Y1 - Y2) / (X1 - X2) @@ -725,9 +727,9 @@ where y1: Self::Variable, ) -> (Self::Variable, Self::Variable) { let modulus: BigInt = if self.current_iteration % 2 == 0 { - Fp::modulus_biguint().into() + E1::ScalarField::modulus_biguint().into() } else { - Fq::modulus_biguint().into() + E2::ScalarField::modulus_biguint().into() }; // - λ = (3X1^2 + a) / (2Y1) // We compute λ and use an additional column as a temporary value @@ -773,6 +775,13 @@ impl< E1: ArrabbiataCurve, E2: ArrabbiataCurve, > Env +where + ::BaseField: PrimeField, + ::BaseField: PrimeField, + ::BigInt: Into<::BigInt>, + ::BigInt: Into<::BigInt>, + E1::BaseField: PrimeField, + E2::BaseField: PrimeField, { pub fn new( srs_log2_size: usize, @@ -781,16 +790,16 @@ impl< sponge_e2: [BigInt; PlonkSpongeConstants::SPONGE_WIDTH], ) -> Self { { - assert!(Fp::MODULUS_BIT_SIZE <= MAXIMUM_FIELD_SIZE_IN_BITS.try_into().unwrap(), "The size of the field Fp is too large, it should be less than {MAXIMUM_FIELD_SIZE_IN_BITS}"); + assert!(E1::ScalarField::MODULUS_BIT_SIZE <= MAXIMUM_FIELD_SIZE_IN_BITS.try_into().unwrap(), "The size of the field Fp is too large, it should be less than {MAXIMUM_FIELD_SIZE_IN_BITS}"); assert!(Fq::MODULUS_BIT_SIZE <= MAXIMUM_FIELD_SIZE_IN_BITS.try_into().unwrap(), "The size of the field Fq is too large, it should be less than {MAXIMUM_FIELD_SIZE_IN_BITS}"); - let modulus_fp = Fp::modulus_biguint(); + let modulus_fp = E1::ScalarField::modulus_biguint(); let alpha = PlonkSpongeConstants::PERM_SBOX; assert!( (modulus_fp - BigUint::from(1_u64)).gcd(&BigUint::from(alpha)) == BigUint::from(1_u64), "The modulus of Fp should be coprime with {alpha}" ); - let modulus_fq = Fq::modulus_biguint(); + let modulus_fq = E2::ScalarField::modulus_biguint(); let alpha = PlonkSpongeConstants::PERM_SBOX; assert!( (modulus_fq - BigUint::from(1_u64)).gcd(&BigUint::from(alpha)) @@ -799,8 +808,8 @@ impl< ); } let srs_size = 1 << srs_log2_size; - let domain_fp = EvaluationDomains::::create(srs_size).unwrap(); - let domain_fq = EvaluationDomains::::create(srs_size).unwrap(); + let domain_fp = EvaluationDomains::::create(srs_size).unwrap(); + let domain_fq = EvaluationDomains::::create(srs_size).unwrap(); info!("Create an SRS of size {srs_log2_size} for the first curve"); let srs_e1: SRS = { @@ -942,9 +951,9 @@ impl< .witness .par_iter() .map(|evals| { - let evals: Vec = evals + let evals: Vec = evals .par_iter() - .map(|x| Fp::from_biguint(&x.to_biguint().unwrap()).unwrap()) + .map(|x| E1::ScalarField::from_biguint(&x.to_biguint().unwrap()).unwrap()) .collect(); let evals = Evaluations::from_vec_and_domain(evals.to_vec(), self.domain_fp.d1); self.srs_e1 @@ -963,9 +972,9 @@ impl< .witness .iter() .map(|evals| { - let evals: Vec = evals + let evals: Vec = evals .par_iter() - .map(|x| Fq::from_biguint(&x.to_biguint().unwrap()).unwrap()) + .map(|x| E2::ScalarField::from_biguint(&x.to_biguint().unwrap()).unwrap()) .collect(); let evals = Evaluations::from_vec_and_domain(evals.to_vec(), self.domain_fq.d1); self.srs_e2 @@ -979,17 +988,19 @@ impl< fn absorb_state(&mut self) { if self.current_iteration % 2 == 0 { let sponge: DefaultFqSponge = - DefaultFqSponge::new(E1::other_curve_sponge_params()); + DefaultFqSponge::new(E2::other_curve_sponge_params()); self.sponge_e1.iter().for_each(|v| { - let v = Fq::from_biguint(&v.to_biguint().unwrap()).unwrap(); - sponge.absorb(v); + // let v = Fq::from_biguint(&v.to_biguint().unwrap()).unwrap(); + let v = E1::BaseField::zero(); + sponge.absorb_fq(&[v]); }); } else { let sponge: DefaultFqSponge = - DefaultFqSponge::new(E2::other_curve_sponge_params()); + DefaultFqSponge::new(E1::other_curve_sponge_params()); self.sponge_e2.iter().for_each(|v| { - let v = Fp::from_biguint(&v.to_biguint().unwrap()).unwrap(); - sponge.absorb(v); + let v = E2::BaseField::zero(); + // let v = Fp::from_biguint(&v.to_biguint().unwrap()).unwrap(); + sponge.absorb_fq(&[v]); }); } }