Skip to content

Commit

Permalink
Merge pull request #2719 from o1-labs/dw/arrabiata-curve-trait
Browse files Browse the repository at this point in the history
Arrabiata: introduce trait ArrabiataCurve to englobe all data a curve must implement to be used with Arrabiata
  • Loading branch information
dannywillems authored Dec 19, 2024
2 parents a87933c + ef5a1dc commit 163ff27
Show file tree
Hide file tree
Showing 11 changed files with 225 additions and 137 deletions.
48 changes: 32 additions & 16 deletions arrabbiata/src/constraints.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,45 @@
use super::{columns::Column, interpreter::InterpreterEnv};
use crate::{
columns::{Gadget, E},
curve::ArrabbiataCurve,
interpreter::{self, Instruction, Side},
MAX_DEGREE, NUMBER_OF_COLUMNS, NUMBER_OF_PUBLIC_INPUTS,
};
use ark_ff::{Field, PrimeField};
use ark_ec::{short_weierstrass::SWCurveConfig, CurveConfig};
use ark_ff::PrimeField;
use kimchi::circuits::{
expr::{ConstantTerm::Literal, Expr, ExprInner, Operations, Variable},
gate::CurrOrNext,
};
use log::debug;
use num_bigint::BigInt;
use o1_utils::FieldHelpers;
use poly_commitment::commitment::CommitmentCurve;

#[derive(Clone, Debug)]
pub struct Env<Fp: Field> {
pub poseidon_mds: Vec<Vec<Fp>>,
pub struct Env<C: ArrabbiataCurve> {
/// The parameter a is the coefficients of the elliptic curve in affine
/// coordinates.
// FIXME: this is ugly. Let use the curve as a parameter. Only lazy for now.
pub a: BigInt,
pub idx_var: usize,
pub idx_var_next_row: usize,
pub idx_var_pi: usize,
pub constraints: Vec<E<Fp>>,
pub constraints: Vec<E<C::ScalarField>>,
pub activated_gadget: Option<Gadget>,
}

impl<Fp: PrimeField> Env<Fp> {
pub fn new(poseidon_mds: Vec<Vec<Fp>>, a: BigInt) -> Self {
impl<C: ArrabbiataCurve> Env<C>
where
<<C as CommitmentCurve>::Params as CurveConfig>::BaseField: PrimeField,
{
pub fn new() -> Self {
// This check might not be useful
assert!(a < Fp::modulus_biguint().into(), "a is too large");
let a: BigInt = <C as CommitmentCurve>::Params::COEFF_A.to_biguint().into();
assert!(
a < C::ScalarField::modulus_biguint().into(),
"a is too large"
);
Self {
poseidon_mds,
a,
idx_var: 0,
idx_var_next_row: 0,
Expand All @@ -48,10 +55,10 @@ impl<Fp: PrimeField> Env<Fp> {
/// proof.
/// The constraint environment must be instantiated only once, at the last step
/// of the computation.
impl<Fp: PrimeField> InterpreterEnv for Env<Fp> {
impl<C: ArrabbiataCurve> InterpreterEnv for Env<C> {
type Position = (Column, CurrOrNext);

type Variable = E<Fp>;
type Variable = E<C::ScalarField>;

fn allocate(&mut self) -> Self::Position {
assert!(self.idx_var < NUMBER_OF_COLUMNS, "Maximum number of columns reached ({NUMBER_OF_COLUMNS}), increase the number of columns");
Expand Down Expand Up @@ -81,7 +88,7 @@ impl<Fp: PrimeField> InterpreterEnv for Env<Fp> {

fn constant(&self, value: BigInt) -> Self::Variable {
let v = value.to_biguint().unwrap();
let v = Fp::from_biguint(&v).unwrap();
let v = C::ScalarField::from_biguint(&v).unwrap();
let v_inner = Operations::from(Literal(v));
Self::Variable::constant(v_inner)
}
Expand Down Expand Up @@ -180,7 +187,7 @@ impl<Fp: PrimeField> InterpreterEnv for Env<Fp> {
}

fn get_poseidon_mds_matrix(&mut self, i: usize, j: usize) -> Self::Variable {
let v = self.poseidon_mds[i][j];
let v = C::sponge_params().mds[i][j];
let v_inner = Operations::from(Literal(v));
Self::Variable::constant(v_inner)
}
Expand Down Expand Up @@ -304,7 +311,7 @@ impl<Fp: PrimeField> InterpreterEnv for Env<Fp> {
}
}

impl<F: PrimeField> Env<F> {
impl<C: ArrabbiataCurve> Env<C> {
/// Get all the constraints for the IVC circuit, only.
///
/// The following gadgets are used in the IVC circuit:
Expand All @@ -317,7 +324,7 @@ impl<F: PrimeField> Env<F> {
// the computation of the challenges.
// FIXME: add a test checking that whatever the value given in parameter of
// the gadget, the constraints are the same
pub fn get_all_constraints_for_ivc(&self) -> Vec<E<F>> {
pub fn get_all_constraints_for_ivc(&self) -> Vec<E<C::ScalarField>> {
// Copying the instance we got in parameter, and making it mutable to
// avoid modifying the original instance.
let mut env = self.clone();
Expand Down Expand Up @@ -354,7 +361,7 @@ impl<F: PrimeField> Env<F> {
// FIXME: the application should be given as an argument to handle Rust
// zkApp. It is only for the PoC.
// FIXME: the selectors are not added for now.
pub fn get_all_constraints(&self) -> Vec<E<F>> {
pub fn get_all_constraints(&self) -> Vec<E<C::ScalarField>> {
let mut constraints = self.get_all_constraints_for_ivc();

// Copying the instance we got in parameter, and making it mutable to
Expand All @@ -370,3 +377,12 @@ impl<F: PrimeField> Env<F> {
constraints
}
}

impl<C: ArrabbiataCurve> Default for Env<C>
where
<<C as CommitmentCurve>::Params as CurveConfig>::BaseField: PrimeField,
{
fn default() -> Self {
Self::new()
}
}
103 changes: 103 additions & 0 deletions arrabbiata/src/curve.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//! This file defines a trait similar to [kimchi::curve::KimchiCurve] for Pallas and
//! Vesta. It aims to define all the parameters that are needed by a curve to be
//! used in Arrabbiata. For instance, the sponge parameters, the endomorphism
//! coefficients, etc.
//! The goal of this trait is to parametrize the whole library with the
//! different curves.
use ark_ec::short_weierstrass::Affine;
use kimchi::curve::{pallas_endos, vesta_endos};
use mina_curves::pasta::curves::{pallas::PallasParameters, vesta::VestaParameters};
use mina_poseidon::{constants::SpongeConstants, poseidon::ArithmeticSpongeParams};
use poly_commitment::commitment::{CommitmentCurve, EndoCurve};

#[derive(Clone)]
pub struct PlonkSpongeConstants {}

impl SpongeConstants for PlonkSpongeConstants {
const SPONGE_CAPACITY: usize = 1;
const SPONGE_WIDTH: usize = 3;
const SPONGE_RATE: usize = 2;
const PERM_ROUNDS_FULL: usize = 60;
const PERM_ROUNDS_PARTIAL: usize = 0;
const PERM_HALF_ROUNDS_FULL: usize = 0;
const PERM_SBOX: u32 = 5;
const PERM_FULL_MDS: bool = true;
const PERM_INITIAL_ARK: bool = false;
}

/// Represents additional information that a curve needs in order to be used
/// with Arrabbiata.
pub trait ArrabbiataCurve: CommitmentCurve + EndoCurve {
/// A human readable name.
const NAME: &'static str;

// FIXME: use this in the codebase.
// We might want to use different sponge constants for different curves.
// For now, it does use the same constants for both curves.
type SpongeConstants: SpongeConstants;

const SPONGE_CONSTANTS: Self::SpongeConstants;

/// Provides the sponge params to be used with this curve.
fn sponge_params() -> &'static ArithmeticSpongeParams<Self::ScalarField>;

/// Provides the sponge params to be used with the other curve.
fn other_curve_sponge_params() -> &'static ArithmeticSpongeParams<Self::BaseField>;

/// Provides the coefficients for the curve endomorphism, called (q,r) in
/// some places.
fn endos() -> &'static (Self::BaseField, Self::ScalarField);

/// Provides the coefficient for the curve endomorphism over the other
/// field, called q in some places.
fn other_curve_endo() -> &'static Self::ScalarField;
}

impl ArrabbiataCurve for Affine<PallasParameters> {
const NAME: &'static str = "pallas";

type SpongeConstants = PlonkSpongeConstants;

const SPONGE_CONSTANTS: Self::SpongeConstants = PlonkSpongeConstants {};

fn sponge_params() -> &'static ArithmeticSpongeParams<Self::ScalarField> {
crate::poseidon_3_60_0_5_5_fq::static_params()
}

fn other_curve_sponge_params() -> &'static ArithmeticSpongeParams<Self::BaseField> {
crate::poseidon_3_60_0_5_5_fp::static_params()
}

fn endos() -> &'static (Self::BaseField, Self::ScalarField) {
pallas_endos()
}

fn other_curve_endo() -> &'static Self::ScalarField {
&vesta_endos().0
}
}

impl ArrabbiataCurve for Affine<VestaParameters> {
const NAME: &'static str = "vesta";

type SpongeConstants = PlonkSpongeConstants;

const SPONGE_CONSTANTS: Self::SpongeConstants = PlonkSpongeConstants {};

fn sponge_params() -> &'static ArithmeticSpongeParams<Self::ScalarField> {
crate::poseidon_3_60_0_5_5_fp::static_params()
}

fn other_curve_sponge_params() -> &'static ArithmeticSpongeParams<Self::BaseField> {
crate::poseidon_3_60_0_5_5_fq::static_params()
}

fn endos() -> &'static (Self::BaseField, Self::ScalarField) {
vesta_endos()
}

fn other_curve_endo() -> &'static Self::ScalarField {
&pallas_endos().0
}
}
35 changes: 22 additions & 13 deletions arrabbiata/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@
//!
//! Hashing is a crucial part of the IVC scheme. The hash function the
//! interpreter does use for the moment is an instance of the Poseidon hash
//! function with a fixed state size of [POSEIDON_STATE_SIZE]. Increasing the
//! function with a fixed state size of
//! [crate::curve::PlonkSpongeConstants::SPONGE_WIDTH]. Increasing the
//! state size can be considered as it would potentially optimize the
//! number of rounds, and allow hashing more data on one row. We leave this for
//! future works.
Expand Down Expand Up @@ -338,11 +339,11 @@
//! there.
use crate::{
columns::Gadget, MAXIMUM_FIELD_SIZE_IN_BITS, NUMBER_OF_COLUMNS, POSEIDON_ROUNDS_FULL,
POSEIDON_STATE_SIZE,
columns::Gadget, curve::PlonkSpongeConstants, MAXIMUM_FIELD_SIZE_IN_BITS, NUMBER_OF_COLUMNS,
};
use ark_ff::{One, Zero};
use log::debug;
use mina_poseidon::constants::SpongeConstants;
use num_bigint::BigInt;

/// A list of instruction/gadget implemented in the interpreter.
Expand Down Expand Up @@ -830,19 +831,23 @@ pub fn run_ivc<E: InterpreterEnv>(env: &mut E, instr: Instruction) {
Instruction::Poseidon(curr_round) => {
env.activate_gadget(Gadget::Poseidon);
debug!("Executing instruction Poseidon({curr_round})");
if curr_round < POSEIDON_ROUNDS_FULL {
if curr_round < PlonkSpongeConstants::PERM_ROUNDS_FULL {
// Values to be absorbed are 0 when when the round is not zero,
// i.e. when we are processing the rounds.
let values_to_absorb: Vec<E::Variable> = (0..POSEIDON_STATE_SIZE - 1)
let values_to_absorb: Vec<E::Variable> = (0..PlonkSpongeConstants::SPONGE_WIDTH
- 1)
.map(|_i| {
let pos = env.allocate_public_input();
// fetch_value_to_absorb is supposed to return 0 if curr_round != 0.
unsafe { env.fetch_value_to_absorb(pos, curr_round) }
})
.collect();
let round_input_positions: Vec<E::Position> =
(0..POSEIDON_STATE_SIZE).map(|_i| env.allocate()).collect();
let round_output_positions: Vec<E::Position> = (0..POSEIDON_STATE_SIZE)
let round_input_positions: Vec<E::Position> = (0
..PlonkSpongeConstants::SPONGE_WIDTH)
.map(|_i| env.allocate())
.collect();
let round_output_positions: Vec<E::Position> = (0
..PlonkSpongeConstants::SPONGE_WIDTH)
.map(|_i| env.allocate_next_row())
.collect();
// If we are at the first round, we load the state from the environment.
Expand All @@ -856,8 +861,9 @@ pub fn run_ivc<E: InterpreterEnv>(env: &mut E, instr: Instruction) {
.enumerate()
.map(|(i, pos)| {
let res = env.load_poseidon_state(*pos, i);
// Absorb value. The capacity is POSEIDON_STATE_SIZE - 1
if i < POSEIDON_STATE_SIZE - 1 {
// Absorb value. The capacity is
// PlonkSpongeConstants::SPONGE_WIDTH - 1
if i < PlonkSpongeConstants::SPONGE_WIDTH - 1 {
res + values_to_absorb[i].clone()
} else {
res
Expand All @@ -882,7 +888,7 @@ pub fn run_ivc<E: InterpreterEnv>(env: &mut E, instr: Instruction) {

let round = curr_round + idx_round;

let rcs: Vec<E::Variable> = (0..POSEIDON_STATE_SIZE)
let rcs: Vec<E::Variable> = (0..PlonkSpongeConstants::SPONGE_WIDTH)
.map(|i| {
let pos = env.allocate_public_input();
env.get_poseidon_round_constant(pos, round, i)
Expand Down Expand Up @@ -915,7 +921,7 @@ pub fn run_ivc<E: InterpreterEnv>(env: &mut E, instr: Instruction) {
// now, we will save the state at the end of the last round
// and reload it at the beginning of the next Poseidon full
// hash.
if round == POSEIDON_ROUNDS_FULL - 1 {
if round == PlonkSpongeConstants::PERM_ROUNDS_FULL - 1 {
state.iter().enumerate().for_each(|(i, x)| {
unsafe { env.save_poseidon_state(x.clone(), i) };
});
Expand All @@ -924,7 +930,10 @@ pub fn run_ivc<E: InterpreterEnv>(env: &mut E, instr: Instruction) {
state
});
} else {
panic!("Invalid index: it is supposed to be less than {POSEIDON_ROUNDS_FULL}");
panic!(
"Invalid index: it is supposed to be less than {}",
PlonkSpongeConstants::PERM_ROUNDS_FULL
);
}
}
Instruction::NoOp => {}
Expand Down
13 changes: 1 addition & 12 deletions arrabbiata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use strum::EnumCount as _;
pub mod column_env;
pub mod columns;
pub mod constraints;
pub mod curve;
pub mod interpreter;
pub mod logup;
pub mod poseidon_3_60_0_5_5_fp;
Expand Down Expand Up @@ -34,18 +35,6 @@ pub const NUMBER_OF_COLUMNS: usize = 15;
/// to absorb.
pub const NUMBER_OF_PUBLIC_INPUTS: usize = 15 + 2;

/// The low-exponentiation value used by the Poseidon hash function for the
/// substitution box.
///
/// The value is used to perform initialisation checks with the fields.
pub const POSEIDON_ALPHA: u64 = 5;

/// The number of full rounds in the Poseidon hash function.
pub const POSEIDON_ROUNDS_FULL: usize = 60;

/// The number of elements in the state of the Poseidon hash function.
pub const POSEIDON_STATE_SIZE: usize = 3;

/// The maximum number of bits the fields can be.
/// It is critical as we have some assumptions for the gadgets describing the
/// IVC.
Expand Down
7 changes: 5 additions & 2 deletions arrabbiata/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use arrabbiata::{
curve::PlonkSpongeConstants,
interpreter::{self, InterpreterEnv},
witness::Env,
IVC_CIRCUIT_SIZE, MIN_SRS_LOG2_SIZE, POSEIDON_STATE_SIZE,
IVC_CIRCUIT_SIZE, MIN_SRS_LOG2_SIZE,
};
use log::{debug, info};
use mina_curves::pasta::{Fp, Fq, Pallas, Vesta};
use mina_poseidon::constants::SpongeConstants;
use num_bigint::BigInt;
use std::time::Instant;

Expand Down Expand Up @@ -47,7 +49,8 @@ pub fn main() {
let domain_size = 1 << srs_log2_size;

// FIXME: setup correctly the initial sponge state
let sponge_e1: [BigInt; POSEIDON_STATE_SIZE] = std::array::from_fn(|_i| BigInt::from(42u64));
let sponge_e1: [BigInt; PlonkSpongeConstants::SPONGE_WIDTH] =
std::array::from_fn(|_i| BigInt::from(42u64));
// FIXME: make a setup phase to build the selectors
let mut env = Env::<Fp, Fq, Vesta, Pallas>::new(
*srs_log2_size,
Expand Down
7 changes: 3 additions & 4 deletions arrabbiata/src/prover.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! A prover for the folding/accumulation scheme
use crate::proof::Proof;
use ark_ec::AffineRepr;
use crate::{curve::ArrabbiataCurve, proof::Proof};
use ark_ff::PrimeField;

use crate::witness::Env;
Expand All @@ -12,8 +11,8 @@ use crate::witness::Env;
pub fn prove<
Fp: PrimeField,
Fq: PrimeField,
E1: AffineRepr<ScalarField = Fp, BaseField = Fq>,
E2: AffineRepr<ScalarField = Fq, BaseField = Fp>,
E1: ArrabbiataCurve<ScalarField = Fp, BaseField = Fq>,
E2: ArrabbiataCurve<ScalarField = Fq, BaseField = Fp>,
>(
_env: &Env<Fp, Fq, E1, E2>,
) -> Result<Proof, String> {
Expand Down
Loading

0 comments on commit 163ff27

Please sign in to comment.