use crate::{
air::SP1CoreAirBuilder,
memory::{MemoryAccessCols, MemoryAccessColsU8},
operations::{AddrAddOperation, SyscallAddrOperation},
utils::{limbs_to_words, next_multiple_of_32},
};
use core::{
borrow::{Borrow, BorrowMut},
mem::{size_of, MaybeUninit},
};
use generic_array::GenericArray;
use itertools::Itertools;
use num::{BigUint, One, Zero};
use slop_air::{Air, AirBuilder, BaseAir};
use slop_algebra::{AbstractField, PrimeField32};
use slop_matrix::Matrix;
use sp1_core_executor::{
events::{
ByteLookupEvent, ByteRecord, EdDecompressEvent, FieldOperation, MemoryRecordEnum,
PrecompileEvent,
},
ExecutionRecord, Program, SyscallCode,
};
use sp1_curves::{
edwards::{
ed25519::{ed25519_sqrt, Ed25519BaseField},
EdwardsParameters, WordsFieldElement, WORDS_FIELD_ELEMENT,
},
params::{FieldParameters, Limbs},
};
use sp1_derive::AlignedBorrow;
use sp1_hypercube::{
air::{BaseAirBuilder, InteractionScope, MachineAir},
Word,
};
use std::marker::PhantomData;
use typenum::U32;
use crate::operations::field::{
field_op::FieldOpCols, field_sqrt::FieldSqrtCols, range::FieldLtCols,
};
pub const NUM_ED_DECOMPRESS_COLS: usize = size_of::<EdDecompressCols<u8>>();
#[derive(Debug, Clone, AlignedBorrow)]
#[repr(C)]
pub struct EdDecompressCols<T> {
pub is_real: T,
pub clk_high: T,
pub clk_low: T,
pub ptr: SyscallAddrOperation<T>,
pub read_ptrs: [AddrAddOperation<T>; WORDS_FIELD_ELEMENT],
pub addrs: [AddrAddOperation<T>; WORDS_FIELD_ELEMENT],
pub sign: T,
pub x_access: GenericArray<MemoryAccessCols<T>, WordsFieldElement>,
pub x_value: GenericArray<Word<T>, WordsFieldElement>,
pub y_access: GenericArray<MemoryAccessColsU8<T>, WordsFieldElement>,
pub(crate) neg_x_range: FieldLtCols<T, Ed25519BaseField>,
pub(crate) y_range: FieldLtCols<T, Ed25519BaseField>,
pub(crate) yy: FieldOpCols<T, Ed25519BaseField>,
pub(crate) u: FieldOpCols<T, Ed25519BaseField>,
pub(crate) dyy: FieldOpCols<T, Ed25519BaseField>,
pub(crate) v: FieldOpCols<T, Ed25519BaseField>,
pub(crate) u_div_v: FieldOpCols<T, Ed25519BaseField>,
pub(crate) x: FieldSqrtCols<T, Ed25519BaseField>,
pub(crate) neg_x: FieldOpCols<T, Ed25519BaseField>,
}
impl<F: PrimeField32> EdDecompressCols<F> {
pub fn populate<P: FieldParameters, E: EdwardsParameters>(
&mut self,
event: EdDecompressEvent,
record: &mut ExecutionRecord,
) {
let mut new_byte_lookup_events = Vec::new();
self.is_real = F::from_bool(true);
self.clk_high = F::from_canonical_u32((event.clk >> 24) as u32);
self.clk_low = F::from_canonical_u32((event.clk & 0xFFFFFF) as u32);
self.ptr.populate(record, event.ptr, 64);
let read_ptr = event.ptr + 32;
self.sign = F::from_bool(event.sign);
for i in 0..WORDS_FIELD_ELEMENT {
let x_record = MemoryRecordEnum::Write(event.x_memory_records[i]);
self.x_access[i].populate(x_record, &mut new_byte_lookup_events);
let current_x_record = x_record.current_record();
self.x_value[i] = Word::from(current_x_record.value);
let y_record = MemoryRecordEnum::Read(event.y_memory_records[i]);
self.y_access[i].populate(y_record, &mut new_byte_lookup_events);
self.addrs[i].populate(record, event.ptr, i as u64 * 8);
self.read_ptrs[i].populate(record, read_ptr, i as u64 * 8);
}
let y = &BigUint::from_bytes_le(&event.y_bytes);
self.populate_field_ops::<E>(&mut new_byte_lookup_events, y);
record.add_byte_lookup_events(new_byte_lookup_events);
}
fn populate_field_ops<E: EdwardsParameters>(
&mut self,
blu_events: &mut Vec<ByteLookupEvent>,
y: &BigUint,
) {
let one = BigUint::one();
self.y_range.populate(blu_events, y, &Ed25519BaseField::modulus());
let yy = self.yy.populate(blu_events, y, y, FieldOperation::Mul);
let u = self.u.populate(blu_events, &yy, &one, FieldOperation::Sub);
let dyy = self.dyy.populate(blu_events, &E::d_biguint(), &yy, FieldOperation::Mul);
let v = self.v.populate(blu_events, &one, &dyy, FieldOperation::Add);
let u_div_v = self.u_div_v.populate(blu_events, &u, &v, FieldOperation::Div);
let x = self.x.populate(blu_events, &u_div_v, |v| {
ed25519_sqrt(v).expect("curve25519 expected field element to be a square")
});
let neg_x = self.neg_x.populate(blu_events, &BigUint::zero(), &x, FieldOperation::Sub);
self.neg_x_range.populate(blu_events, &neg_x, &Ed25519BaseField::modulus());
}
}
impl<V: Copy> EdDecompressCols<V> {
pub fn eval<AB: SP1CoreAirBuilder<Var = V>, P: FieldParameters, E: EdwardsParameters>(
&self,
builder: &mut AB,
) where
V: Into<AB::Expr>,
{
builder.assert_bool(self.sign);
builder.assert_bool(self.is_real);
let y_limbs = builder.generate_limbs(&self.y_access, self.is_real.into());
let y: Limbs<AB::Expr, U32> = Limbs(y_limbs.try_into().expect("failed to convert limbs"));
let max_num_limbs =
Ed25519BaseField::to_limbs_field::<AB::Expr, AB::F>(&Ed25519BaseField::modulus());
self.y_range.eval(builder, &y, &max_num_limbs, self.is_real);
self.yy.eval(builder, &y, &y, FieldOperation::Mul, self.is_real);
self.u.eval(
builder,
&self.yy.result,
&[AB::Expr::one()].iter(),
FieldOperation::Sub,
self.is_real,
);
let d_biguint = E::d_biguint();
let d_const = E::BaseField::to_limbs_field::<AB::F, _>(&d_biguint);
self.dyy.eval(builder, &d_const, &self.yy.result, FieldOperation::Mul, self.is_real);
self.v.eval(
builder,
&[AB::Expr::one()].iter(),
&self.dyy.result,
FieldOperation::Add,
self.is_real,
);
self.u_div_v.eval(
builder,
&self.u.result,
&self.v.result,
FieldOperation::Div,
self.is_real,
);
self.x.eval(builder, &self.u_div_v.result, AB::F::zero(), self.is_real);
self.neg_x.eval(
builder,
&[AB::Expr::zero()].iter(),
&self.x.multiplication.result,
FieldOperation::Sub,
self.is_real,
);
self.neg_x_range.eval(builder, &self.neg_x.result, &max_num_limbs, self.is_real);
let ptr = SyscallAddrOperation::<AB::F>::eval(builder, 64, self.ptr, self.is_real.into());
for i in 0..WORDS_FIELD_ELEMENT {
AddrAddOperation::<AB::F>::eval(
builder,
Word([ptr[0].into(), ptr[1].into(), ptr[2].into(), AB::Expr::zero()]),
Word::from(8 * i as u64),
self.addrs[i],
self.is_real.into(),
);
}
for i in 0..WORDS_FIELD_ELEMENT {
AddrAddOperation::<AB::F>::eval(
builder,
Word([ptr[0].into(), ptr[1].into(), ptr[2].into(), AB::Expr::zero()]),
Word::from(8 * i as u64 + 32),
self.read_ptrs[i],
self.is_real.into(),
);
}
builder.eval_memory_access_slice_read(
self.clk_high,
self.clk_low,
&self.read_ptrs.map(|ptr| ptr.value.map(Into::into)),
&self.y_access.iter().map(|access| access.memory_access).collect_vec(),
self.is_real,
);
builder.eval_memory_access_slice_write(
self.clk_high,
self.clk_low.into() + AB::Expr::one(),
&self.addrs.map(|addr| addr.value.map(Into::into)),
&self.x_access,
self.x_value.to_vec(),
self.is_real,
);
let neg_x_words = limbs_to_words::<AB>(self.neg_x.result.0.to_vec());
let mul_x_words = limbs_to_words::<AB>(self.x.multiplication.result.0.to_vec());
let x_value_words = self.x_value.to_vec().iter().map(|w| w.map(|x| x.into())).collect_vec();
for (neg_x_word, x_value_word) in neg_x_words.iter().zip(x_value_words.iter()) {
builder
.when(self.is_real)
.when(self.sign)
.assert_all_eq(neg_x_word.clone(), x_value_word.clone());
}
for (mul_x_word, x_value_word) in mul_x_words.iter().zip(x_value_words.iter()) {
builder
.when(self.is_real)
.when_not(self.sign)
.assert_all_eq(mul_x_word.clone(), x_value_word.clone());
}
builder.receive_syscall(
self.clk_high,
self.clk_low,
AB::F::from_canonical_u32(SyscallCode::ED_DECOMPRESS.syscall_id()),
ptr.map(Into::into),
[self.sign.into(), AB::Expr::zero(), AB::Expr::zero()],
self.is_real,
InteractionScope::Local,
);
}
}
#[derive(Default)]
pub struct EdDecompressChip<E> {
_phantom: PhantomData<E>,
}
impl<E: EdwardsParameters> EdDecompressChip<E> {
pub const fn new() -> Self {
Self { _phantom: PhantomData }
}
}
impl<F: PrimeField32, E: EdwardsParameters> MachineAir<F> for EdDecompressChip<E> {
type Record = ExecutionRecord;
type Program = Program;
fn name(&self) -> &'static str {
"EdDecompress"
}
fn num_rows(&self, input: &Self::Record) -> Option<usize> {
let nb_rows = input.get_precompile_events(SyscallCode::ED_DECOMPRESS).len();
let size_log2 = input.fixed_log2_rows::<F, _>(self);
let padded_nb_rows = next_multiple_of_32(nb_rows, size_log2);
Some(padded_nb_rows)
}
fn generate_trace_into(
&self,
input: &ExecutionRecord,
output: &mut ExecutionRecord,
buffer: &mut [MaybeUninit<F>],
) {
let padded_nb_rows = <EdDecompressChip<E> as MachineAir<F>>::num_rows(self, input).unwrap();
let events = input.get_precompile_events(SyscallCode::ED_DECOMPRESS);
let num_event_rows = events.len();
unsafe {
let padding_start = num_event_rows * NUM_ED_DECOMPRESS_COLS;
let padding_size = (padded_nb_rows - num_event_rows) * NUM_ED_DECOMPRESS_COLS;
if padding_size > 0 {
core::ptr::write_bytes(buffer[padding_start..].as_mut_ptr(), 0, padding_size);
}
}
let buffer_ptr = buffer.as_mut_ptr() as *mut F;
let values = unsafe {
core::slice::from_raw_parts_mut(buffer_ptr, num_event_rows * NUM_ED_DECOMPRESS_COLS)
};
values.chunks_mut(NUM_ED_DECOMPRESS_COLS).enumerate().for_each(|(idx, row)| {
let (_, event) = &events[idx];
let event = if let PrecompileEvent::EdDecompress(event) = event {
event
} else {
unreachable!();
};
let cols: &mut EdDecompressCols<F> = row.borrow_mut();
cols.populate::<E::BaseField, E>(event.clone(), output);
});
for idx in num_event_rows..padded_nb_rows {
let row_start = idx * NUM_ED_DECOMPRESS_COLS;
let row = unsafe {
core::slice::from_raw_parts_mut(
buffer[row_start..].as_mut_ptr() as *mut F,
NUM_ED_DECOMPRESS_COLS,
)
};
let cols: &mut EdDecompressCols<F> = row.borrow_mut();
let zero = BigUint::zero();
cols.populate_field_ops::<E>(&mut vec![], &zero);
}
}
fn included(&self, shard: &Self::Record) -> bool {
if let Some(shape) = shard.shape.as_ref() {
shape.included::<F, _>(self)
} else {
!shard.get_precompile_events(SyscallCode::ED_DECOMPRESS).is_empty()
}
}
}
impl<F, E: EdwardsParameters> BaseAir<F> for EdDecompressChip<E> {
fn width(&self) -> usize {
NUM_ED_DECOMPRESS_COLS
}
}
impl<AB, E: EdwardsParameters> Air<AB> for EdDecompressChip<E>
where
AB: SP1CoreAirBuilder,
{
fn eval(&self, builder: &mut AB) {
let main = builder.main();
let local = main.row_slice(0);
let local: &EdDecompressCols<AB::Var> = (*local).borrow();
local.eval::<AB, E::BaseField, E>(builder);
}
}
#[cfg(test)]
pub mod tests {
use std::sync::Arc;
use sp1_core_executor::Program;
use test_artifacts::ED_DECOMPRESS_ELF;
use crate::{io::SP1Stdin, utils};
#[tokio::test(flavor = "multi_thread")]
async fn test_ed_decompress() {
utils::setup_logger();
let program = Program::from(&ED_DECOMPRESS_ELF).unwrap();
let stdin = SP1Stdin::new();
utils::run_test(Arc::new(program), stdin).await.unwrap();
}
}