diff --git a/src/e2e_tests.rs b/src/e2e_tests.rs new file mode 100644 index 0000000..1e7633c --- /dev/null +++ b/src/e2e_tests.rs @@ -0,0 +1,88 @@ +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use plonky2::{ + field::goldilocks_field::GoldilocksField, + plonk::config::{ + GenericConfig, + PoseidonGoldilocksConfig, + }, + util::timing::TimingTree, + }; + use starky::{ + config::StarkConfig, + proof::StarkProofWithPublicInputs, + prover::prove, + verifier::verify_stark_proof, + }; + + use crate::{ + stark_program_instructions::ProgramInstructionsStark, + vm_specs::{ + Instruction, + MemoryLocation, + Program, + Register, + }, + }; + + #[test] + fn test_add_program() { + let instructions = vec![ + Instruction::Lb(Register::R0, MemoryLocation(0x40)), + Instruction::Lb(Register::R1, MemoryLocation(0x41)), + Instruction::Add(Register::R0, Register::R1), + Instruction::Sb(Register::R0, MemoryLocation(0x42)), + Instruction::Halt, + ]; + + let code = instructions + .into_iter() + .enumerate() + .map(|(idx, inst)| (idx as u8, inst)) + .collect::>(); + + let memory_init: HashMap = + HashMap::from_iter(vec![(0x40, 0x20), (0x41, 0x45)]); + + let program = Program { + entry_point: 0, + code, + memory_init, + }; + + // Generate the static part of the proof + let program_proof = { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S = ProgramInstructionsStark; + type PR = StarkProofWithPublicInputs; + + let stark = S::new(); + let mut config = StarkConfig::standard_fast_config(); + // Need to do this since our table is small. Need atleast 1<<5 + // sized table to not affect this + config + .fri_config + .cap_height = 1; + let program = Program::default(); + let trace = + ProgramInstructionsStark::::generate_trace(&program); + let proof: Result = prove( + stark.clone(), + &config, + trace, + &[], + &mut TimingTree::default(), + ); + assert!(proof.is_ok()); + let proof = proof.unwrap(); + let verification = + verify_stark_proof(stark, proof.clone(), &config); + assert!(verification.is_ok()); + proof + }; + } +} diff --git a/src/lib.rs b/src/lib.rs index 713bce9..e58428e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,5 +17,11 @@ mod preflight_simulator; mod vm_specs; // STARK tables ------------- +//#[allow(dead_code)] +//mod stark_cpu; #[allow(dead_code)] mod stark_program_instructions; + +// END TO END TEST ---------- +#[allow(dead_code)] +mod e2e_tests; diff --git a/src/stark_cpu.rs b/src/stark_cpu.rs new file mode 100644 index 0000000..9fa46ba --- /dev/null +++ b/src/stark_cpu.rs @@ -0,0 +1,182 @@ +//! This file is an encoding of all the execution seen at the "CPU" +//! It is dynamic and changes depending on the memory_init. It has +//! to be linked to the static code "Program" by having a cross-table +//! -lookup with `ProgramInstructionsStark`. + +use core::marker::PhantomData; +use plonky2::{ + field::{ + extension::{ + Extendable, + FieldExtension, + }, + packed::PackedField, + polynomial::PolynomialValues, + }, + hash::hash_types::RichField, + iop::ext_target::ExtensionTarget, + plonk::circuit_builder::CircuitBuilder, +}; +use starky::{ + constraint_consumer::{ + ConstraintConsumer, + RecursiveConstraintConsumer, + }, + evaluation_frame::{ + StarkEvaluationFrame, + StarkFrame, + }, + stark::Stark, + util::trace_rows_to_poly_values, +}; + +use crate::vm_specs::Program; + +// Table description: +// +-----+----+--------+--------+--------------+---------+ +// | Clk | PC | Reg R0 | Reg R1 | MemoryAddr | Opcode* | +// +-----+----+--------+--------+--------------+---------+ +// | .. | .. | ... | ... | .... | ... | +// +-----+----+--------+--------+--------------+---------+ +// +// `Opcode*` means `Opcode` that is one-hot encoded +// 5 Columns for `Clk`, `PC`, `Reg R0`, `Reg R1`, `MemoryAccess` +// 10 Columns for opcodes. See `Instruction::get_opcode`. +const NUMBER_OF_COLS: usize = 5 + 10; +const PUBLIC_INPUTS: usize = 0; + +#[derive(Clone, Copy)] +pub struct CPUStark { + pub _f: PhantomData, +} + +impl CPUStark +where + F: RichField + Extendable, +{ + pub fn new() -> Self { + Self { _f: PhantomData } + } + + pub fn generate_trace(prog: ) -> Vec> + where + F: RichField, + { + let mut trace = prog + .code + .iter() + .map(|(pc, inst)| { + [ + // Program Counter (ID = 0) + F::from_canonical_u8(*pc), + // Instruction Opcode (ID = 1) + F::from_canonical_u8(inst.get_opcode()), + // Filter, true if actual instructions (ID = 2) + F::ONE, + ] + }) + .collect::>(); + + // Need to pad the trace to a len of some power of 2 + let pow2_len = trace + .len() + .next_power_of_two(); + trace.resize(pow2_len, [F::ZERO, F::ZERO, F::ZERO]); + + // Convert into polynomial values + trace_rows_to_poly_values(trace) + } +} + +impl Stark for CPUStark +where + F: RichField + Extendable, +{ + type EvaluationFrame = StarkFrame + where + FE: FieldExtension, + P: PackedField; + type EvaluationFrameTarget = StarkFrame< + ExtensionTarget, + ExtensionTarget, + NUMBER_OF_COLS, + PUBLIC_INPUTS, + >; + + const COLUMNS: usize = NUMBER_OF_COLS; + const PUBLIC_INPUTS: usize = PUBLIC_INPUTS; + + fn eval_packed_generic( + &self, + vars: &Self::EvaluationFrame, + yield_constr: &mut ConstraintConsumer

, + ) where + FE: FieldExtension, + P: PackedField, + { + } + + fn eval_ext_circuit( + &self, + _builder: &mut CircuitBuilder, + _vars: &Self::EvaluationFrameTarget, + _yield_constr: &mut RecursiveConstraintConsumer, + ) { + unimplemented!() + } + + fn constraint_degree(&self) -> usize { + 3 + } +} + +#[cfg(test)] +mod tests { + + use plonky2::{ + field::goldilocks_field::GoldilocksField, + plonk::config::{ + GenericConfig, + PoseidonGoldilocksConfig, + }, + util::timing::TimingTree, + }; + use starky::{ + config::StarkConfig, + proof::StarkProofWithPublicInputs, + prover::prove, + verifier::verify_stark_proof, + }; + + use super::*; + + #[test] + fn test_nil_program() { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S = CPUStark; + type PR = StarkProofWithPublicInputs; + + let stark = S::new(); + let mut config = StarkConfig::standard_fast_config(); + // Need to do this since our table is small. Need atleast 1<<5 + // sized table to not affect this + config + .fri_config + .cap_height = 1; + let program = Program::default(); + let trace = + CPUStark::::generate_program_instructions_trace(&program); + let proof: Result = prove( + stark.clone(), + &config, + trace, + &[], + &mut TimingTree::default(), + ); + assert!(proof.is_ok()); + let verification = verify_stark_proof(stark, proof.unwrap(), &config); + assert!(verification.is_ok()); + } +} diff --git a/src/stark_program_instructions.rs b/src/stark_program_instructions.rs index 5e74c0d..b0e0134 100644 --- a/src/stark_program_instructions.rs +++ b/src/stark_program_instructions.rs @@ -33,16 +33,13 @@ use starky::{ use crate::vm_specs::Program; -/// Represents one row in a STARK table, contains `is_filter` which -/// should be set to `true` in case it represents an actual instruction -//#[repr(C)] -//#[derive(Default, Clone, Copy, PartialEq, Debug)] -//pub struct ProgramInstructions { -// pub program_counter: T, // ID = 0 -// pub opcode: T, // ID = 1 -// pub filter: T, // ID = 2 -//} - +// Table description: +// +-----------------+--------------------+-------------+ +// | Program Counter | Instruction Opcode | Is_Executed | +// +-----------------+--------------------+-------------+ +// | .... | .... | .... | +// | .... | .... | .... | +// +-----------------+--------------------+-------------+ const NUMBER_OF_COLS: usize = 3; const PUBLIC_INPUTS: usize = 0; @@ -59,9 +56,7 @@ where Self { _f: PhantomData } } - pub fn generate_program_instructions_trace( - prog: &Program - ) -> Vec> + pub fn generate_trace(prog: &Program) -> Vec> where F: RichField, { @@ -174,10 +169,7 @@ mod tests { .fri_config .cap_height = 1; let program = Program::default(); - let trace = - ProgramInstructionsStark::::generate_program_instructions_trace( - &program, - ); + let trace = ProgramInstructionsStark::::generate_trace(&program); let proof: Result = prove( stark.clone(), &config,