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

Arrabiata: start the permutation argument interface #2593

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
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
4 changes: 0 additions & 4 deletions arrabiata/src/columns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ use strum_macros::{EnumCount as EnumCountMacro, EnumIter};
#[derive(Debug, Clone, Copy, PartialEq, EnumCountMacro, EnumIter)]
pub enum Gadget {
App,
// Permutation argument
PermutationArgument,
// Two old gadgets
/// Decompose a 255 bits scalar into 16 chunks of 16 bits.
SixteenBitsDecomposition,
Expand Down Expand Up @@ -57,7 +55,6 @@ impl FormattedOutput for Column {
match self {
Column::Selector(sel) => match sel {
Gadget::App => "q_app".to_string(),
Gadget::PermutationArgument => "q_perm".to_string(),
Gadget::SixteenBitsDecomposition => "q_16bits".to_string(),
Gadget::BitDecompositionFrom16Bits => "q_bit_from_16bits".to_string(),
Gadget::BitDecomposition => "q_bits".to_string(),
Expand All @@ -75,7 +72,6 @@ impl FormattedOutput for Column {
match self {
Column::Selector(sel) => match sel {
Gadget::App => "q_app".to_string(),
Gadget::PermutationArgument => "q_perm".to_string(),
Gadget::SixteenBitsDecomposition => "q_16bits".to_string(),
Gadget::BitDecompositionFrom16Bits => "q_bit_from_16bits".to_string(),
Gadget::BitDecomposition => "q_bits".to_string(),
Expand Down
8 changes: 8 additions & 0 deletions arrabiata/src/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,4 +317,12 @@ impl<Fp: PrimeField> InterpreterEnv for Env<Fp> {
self.assert_zero(res);
lambda
}

fn save_value(&mut self, _pos: Self::Position) -> Self::Variable {
unimplemented!("TODO")
}

fn load_value(&mut self, _row: usize, _pos: Self::Position) -> Self::Variable {
unimplemented!("TODO")
}
}
31 changes: 21 additions & 10 deletions arrabiata/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,27 @@ pub trait InterpreterEnv {
_v2: Self::Variable,
_side: Side,
);

/// Save the value located in the position `pos` in the interpreter "cache"
/// to be loaded later.
///
/// The interpreter "cache" is handled via a permutation argument.
///
/// The returned value is the value saved in the cache.
fn save_value(&mut self, pos: Self::Position) -> Self::Variable;

/// Load the value saved in the CPU cache at the previous row `row`
/// at the column given by the position `pos`.
///
/// If the value has been already loaded at a previous row, an exception is
/// raised. The value must be loaded only once as, under the hood, we use a
/// permutation argument.
/// The value is loaded in the current row at position `pos` and the value
/// is returned.
///
/// If no value has been saved at the previous row `row`, an exception is
/// also raised. Values must have been saved before using [Self::save_value].
fn load_value(&mut self, row: usize, pos: Self::Position) -> Self::Variable;
}

/// Run the application
Expand Down Expand Up @@ -832,9 +853,6 @@ pub fn run_ivc<E: InterpreterEnv>(env: &mut E, instr: Instruction) {
env.activate_gadget(Gadget::EllipticCurveScaling);
// When processing the first bit, we must load the scalar, and it
// comes from previous computation.
if processing_bit == 0 {
env.activate_gadget(Gadget::PermutationArgument);
}
// The two first columns are supposed to be used for the output.
// It will be used to write the result of the scalar multiplication
// in the next row.
Expand Down Expand Up @@ -976,7 +994,6 @@ pub fn run_ivc<E: InterpreterEnv>(env: &mut E, instr: Instruction) {
};
}
Instruction::EllipticCurveAddition(i_comm) => {
env.activate_gadget(Gadget::PermutationArgument);
env.activate_gadget(Gadget::EllipticCurveAddition);
assert!(i_comm < NUMBER_OF_COLUMNS, "Invalid index. We do only support the addition of the commitments to the columns, for now. We must additionally support the scaling of cross-terms and error terms");
let (x1, y1) = {
Expand Down Expand Up @@ -1020,7 +1037,6 @@ pub fn run_ivc<E: InterpreterEnv>(env: &mut E, instr: Instruction) {
};
}
Instruction::Poseidon(curr_round) => {
env.activate_gadget(Gadget::PermutationArgument);
env.activate_gadget(Gadget::Poseidon);
debug!("Executing instruction Poseidon({curr_round})");
if curr_round < POSEIDON_ROUNDS_FULL {
Expand Down Expand Up @@ -1102,7 +1118,6 @@ pub fn run_ivc<E: InterpreterEnv>(env: &mut E, instr: Instruction) {
// calls, like when we need to hash the public inputs, and the
// state might be from a previous place in the execution trace.
let state: Vec<E::Variable> = if curr_round == 0 {
env.activate_gadget(Gadget::PermutationArgument);
round_input_positions
.iter()
.enumerate()
Expand Down Expand Up @@ -1162,16 +1177,12 @@ pub fn run_ivc<E: InterpreterEnv>(env: &mut E, instr: Instruction) {
.collect();
// If we are at the last round, we save the state in the
// environment.
// It does require the permutation argument to be activated.
// FIXME: activating the permutation argument has no effect
// for now.
// FIXME/IMPROVEME: we might want to execute more Poseidon
// full hash in sequentially, and then save one row. For
// 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 {
env.activate_gadget(Gadget::PermutationArgument);
state.iter().enumerate().for_each(|(i, x)| {
unsafe { env.save_poseidon_state(x.clone(), i) };
});
Expand Down
6 changes: 6 additions & 0 deletions arrabiata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ pub mod columns;
pub mod constraints;
pub mod interpreter;
pub mod logup;
/// Module to represent the values that will be loaded in the permutation
/// argument
pub mod permutation;
pub mod poseidon_3_60_0_5_5_fp;
pub mod poseidon_3_60_0_5_5_fq;
pub mod proof;
Expand All @@ -27,6 +30,9 @@ pub const IVC_CIRCUIT_SIZE: usize = 1 << 13;
/// The maximum number of columns that can be used in the circuit.
pub const NUMBER_OF_COLUMNS: usize = 17;

/// The number of columns used in the permutation argument
pub const NUMBER_OF_PERMUTATION_COLUMNS: usize = 4;

/// The maximum number of public inputs the circuit can use per row
/// We do have 15 for now as we want to compute 5 rounds of poseidon per row
/// using the gadget [crate::columns::Gadget::PoseidonNextRow]. In addition to
Expand Down
14 changes: 14 additions & 0 deletions arrabiata/src/permutation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use ark_ff::PrimeField;

pub enum PermutationSign {
Num,
Den,
}

/// Represent a value that will be accumulated in the permutation acumulator
#[allow(dead_code)]
pub struct Permutation<F: PrimeField> {
sign: PermutationSign,
idx: u64,
v: F,
}
87 changes: 67 additions & 20 deletions arrabiata/src/witness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ use num_integer::Integer;
use o1_utils::field_helpers::FieldHelpers;
use poly_commitment::{commitment::CommitmentCurve, ipa::SRS, PolyComm, SRS as _};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use std::time::Instant;
use std::{
collections::{hash_map::Entry, HashMap},
time::Instant,
};

use crate::{
columns::{Column, Gadget},
interpreter::{Instruction, InterpreterEnv, Side},
poseidon_3_60_0_5_5_fp, poseidon_3_60_0_5_5_fq, BIT_DECOMPOSITION_NUMBER_OF_CHUNKS,
MAXIMUM_FIELD_SIZE_IN_BITS, NUMBER_OF_COLUMNS, NUMBER_OF_PUBLIC_INPUTS, NUMBER_OF_SELECTORS,
NUMBER_OF_VALUES_TO_ABSORB_PUBLIC_IO, POSEIDON_ALPHA, POSEIDON_ROUNDS_FULL,
POSEIDON_STATE_SIZE,
MAXIMUM_FIELD_SIZE_IN_BITS, NUMBER_OF_COLUMNS, NUMBER_OF_PERMUTATION_COLUMNS,
NUMBER_OF_PUBLIC_INPUTS, NUMBER_OF_SELECTORS, NUMBER_OF_VALUES_TO_ABSORB_PUBLIC_IO,
POSEIDON_ALPHA, POSEIDON_ROUNDS_FULL, POSEIDON_STATE_SIZE,
};

pub const IVC_STARTING_INSTRUCTION: Instruction = Instruction::Poseidon(0);
Expand Down Expand Up @@ -93,6 +96,20 @@ pub struct Env<
// FIXME: I don't like this design. Feel free to suggest a better solution
pub public_state: [BigInt; NUMBER_OF_PUBLIC_INPUTS],

/// Keep track of the permutations
/// Each value of the array represents a mapping i -> x_i, where i in `n`
/// and x_i is the value at the previous step i.
///
/// When a value is "saved" at step `i`, the mapping from `i` is created,
/// but set to `None`, meaning it has to be "loaded" later.
///
/// TODO: The permutation itself should be part of a setup phase, where we
/// keep track of the the time we accessed the value.
///
/// The permutations are supposed to be on the first
/// [NUMBER_OF_PERMUTATION_COLUMNS], and are only valid on the same column.
pub permutations: [HashMap<usize, (BigInt, Option<usize>)>; NUMBER_OF_PERMUTATION_COLUMNS],

/// Selectors to activate the gadgets.
/// The size of the outer vector must be equal to the number of gadgets in
/// the circuit.
Expand Down Expand Up @@ -411,10 +428,6 @@ where
}

fn load_poseidon_state(&mut self, pos: Self::Position, i: usize) -> Self::Variable {
assert!(
self.selectors[Gadget::PermutationArgument as usize][self.current_row],
"The permutation argument should be activated"
);
let state = if self.current_iteration % 2 == 0 {
self.sponge_e1[i].clone()
} else {
Expand Down Expand Up @@ -454,10 +467,6 @@ where
}

unsafe fn save_poseidon_state(&mut self, x: Self::Variable, i: usize) {
assert!(
self.selectors[Gadget::PermutationArgument as usize][self.current_row],
"The permutation argument should be activated"
);
if self.current_iteration % 2 == 0 {
let modulus: BigInt = Fp::modulus_biguint().into();
self.sponge_e1[i] = x.mod_floor(&modulus)
Expand Down Expand Up @@ -532,10 +541,6 @@ where
pos_y: Self::Position,
side: Side,
) -> (Self::Variable, Self::Variable) {
assert!(
self.selectors[Gadget::PermutationArgument as usize][self.current_row],
"The permutation argument should be activated"
);
match self.current_instruction {
Instruction::EllipticCurveScaling(i_comm, bit) => {
// If we're processing the leftmost bit (i.e. bit == 0), we must load
Expand Down Expand Up @@ -640,10 +645,6 @@ where
y: Self::Variable,
side: Side,
) {
assert!(
self.selectors[Gadget::PermutationArgument as usize][self.current_row],
"The permutation argument should be activated"
);
match side {
Side::Left => {
self.temporary_accumulators.0 = (x, y);
Expand Down Expand Up @@ -801,6 +802,51 @@ where
};
(x3, y3)
}

// FIXME: Not that it means "write_column" should be called before!!!!
// Do we want to change this interface????
// FIXME: Handle "next row"!!!!
fn save_value(&mut self, pos: Self::Position) -> Self::Variable {
let (Column::X(idx), curr_or_next) = pos else {
panic!("The 'cache' can only be used on private columns. To load public values, use a lookup argument")
};
assert_eq!(curr_or_next, CurrOrNext::Curr);
assert!(idx < NUMBER_OF_PERMUTATION_COLUMNS, "The permutation argument only works on the first {NUMBER_OF_PERMUTATION_COLUMNS} columns");
let permutation = &mut self.permutations[idx];
// This should never happen as we build from top to bottom, but we never
// know...
if let Entry::Vacant(e) = permutation.entry(idx) {
let v = self.state[idx].clone();
e.insert((v, None));
} else {
panic!("It seems that the index {idx} is already in the permutation, which should never happen has the execution trace is built from top to bottom")
};
self.state[idx].clone()
}

fn load_value(&mut self, row: usize, pos: Self::Position) -> Self::Variable {
let (Column::X(idx), _) = pos else {
panic!("The 'cache' can only be used on private columns. To load public values, use a lookup argument")
};
assert!(idx < NUMBER_OF_PERMUTATION_COLUMNS, "The permutation argument only works on the first {NUMBER_OF_PERMUTATION_COLUMNS} columns");
if let Some(v) = self.permutations[idx].get_mut(&row) {
match v {
(_, Some(row_prime)) => {
panic!("It seems that the value saved at row {row} has been already read at step {row_prime}")
}
(res, None) => {
let output = (*res).clone();
*v = (output, Some(row))
}
}
} else {
panic!(
"No value has been saved at row {row} for the position {:?}",
pos
)
};
self.permutations[idx].get(&row).unwrap().0.clone()
}
}

impl<
Expand Down Expand Up @@ -912,6 +958,7 @@ impl<
state: std::array::from_fn(|_| BigInt::from(0_usize)),
next_state: std::array::from_fn(|_| BigInt::from(0_usize)),
public_state: std::array::from_fn(|_| BigInt::from(0_usize)),
permutations: std::array::from_fn(|_| HashMap::new()),
selectors,
challenges,
current_instruction: IVC_STARTING_INSTRUCTION,
Expand Down