From c053c137fab7dfe5781ddb4d4247376e63a17219 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Wed, 29 Jan 2025 16:11:01 +0100 Subject: [PATCH] add plonky2 circuit to verify the fibstark proofs --- Cargo.toml | 2 + README.md | 13 ++++ src/circuit.rs | 130 ++++++++++++++++++++++++++++++++++ src/fibstark.rs | 179 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 184 ++---------------------------------------------- 5 files changed, 330 insertions(+), 178 deletions(-) create mode 100644 README.md create mode 100644 src/circuit.rs create mode 100644 src/fibstark.rs diff --git a/Cargo.toml b/Cargo.toml index 79a0e63..311b8d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,5 @@ starky = { git = "https://github.com/0xPolygonZero/plonky2" } anyhow = "1.0.86" itertools = "0.13" rand = "0.8.5" +log = "0.4.22" +env_logger = "0.10.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..c14f1c2 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# starky-tmp +Testing Starky. + +This repo builds from the Starky example from the plonky2 repo. + +- `fibstark.rs`, `FibonacciStark`: + - sets the logic to generate the trace + - implements the `Stark` trait (from Starky) +- `circuit.rs`: + - Plonky2 circuit that verifies the FibonacciStark proof +- the test `circuit.rs`>`test_stark_verif_in_plonky2` generates the Fibonacci Starky proof, and then verifies it in a Plonky2 proof. + +Requires nigthly: `rustup override set nightly` diff --git a/src/circuit.rs b/src/circuit.rs new file mode 100644 index 0000000..a2c0674 --- /dev/null +++ b/src/circuit.rs @@ -0,0 +1,130 @@ +/// Plonky2 circuit to verify the fibonacci-starky proofs +use anyhow::Result; +use plonky2::iop::{target::Target, witness::PartialWitness}; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use starky::proof::{StarkProofWithPublicInputs, StarkProofWithPublicInputsTarget}; +use starky::recursive_verifier::{ + add_virtual_stark_proof_with_pis, set_stark_proof_with_pis_target, verify_stark_proof_circuit, +}; +use starky::stark::Stark; +use std::marker::PhantomData; + +use crate::{C, D, F, STARK_CONFIG}; + +/// Plonky2 circuit to verify a Starky proof of fibstarky +pub struct FibStarkVerifierTargets + Copy> { + _s: PhantomData, + + proof_target: StarkProofWithPublicInputsTarget, + zero_target: Target, +} +impl + Copy> FibStarkVerifierTargets { + pub fn add_targets( + builder: &mut CircuitBuilder, + stark: S, + degree_bits: usize, + ) -> Result { + let proof_target = + add_virtual_stark_proof_with_pis(builder, &stark, &STARK_CONFIG, degree_bits, 0, 0); + + verify_stark_proof_circuit::( + builder, + stark, + proof_target.clone(), + &STARK_CONFIG, + None, + ); + let zero_target = builder.zero(); + + Ok(Self { + _s: PhantomData, + proof_target, + zero_target, + }) + } + pub fn set_targets( + &self, + pw: &mut PartialWitness, + proof: &StarkProofWithPublicInputs, + degree_bits: usize, + ) -> Result<()> { + set_stark_proof_with_pis_target( + pw, + &self.proof_target, + &proof, + degree_bits, + self.zero_target, + )?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use plonky2::field::types::Field; + use plonky2::plonk::circuit_data::CircuitConfig; + use plonky2::util::timing::TimingTree; + use starky::prover::prove; + use starky::verifier::verify_stark_proof; + + use super::*; + use crate::fibstark::FibonacciStark; + + fn set_log() { + let _ = env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .is_test(true) + .try_init(); + } + + #[test] + fn test_stark_verif_in_plonky2() -> Result<()> { + set_log(); + + // 1) first generate the fibstark starky proof + let num_rows = 1 << 10; + let x0 = F::from_canonical_u32(2); + let x1 = F::from_canonical_u32(7); + + let stark = FibonacciStark::::new(num_rows); + let public_inputs = [x0, x1, stark.compute_native(x0, x1)]; + let trace = stark.generate_trace(public_inputs[0], public_inputs[1]); + + let stark_proof = prove::, D>( + stark, + &STARK_CONFIG, + trace, + &public_inputs, + None, + &mut TimingTree::default(), + ) + .expect("We should have a valid proof!"); + + verify_stark_proof(stark, stark_proof.clone(), &STARK_CONFIG, None) + .expect("We should be able to verify this proof!"); + + // 2) now build a plonky2 circuit that verifies the starky proof + let degree_bits = stark_proof.proof.recover_degree_bits(&STARK_CONFIG); + + // construct the circuit + let circuit_config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(circuit_config); + let plonky_targets = + FibStarkVerifierTargets::add_targets(&mut builder, stark, degree_bits)?; + + builder.print_gate_counts(0); + + // set concrete values to targets + let mut pw = PartialWitness::new(); + plonky_targets.set_targets(&mut pw, &stark_proof, degree_bits)?; + + // generate the plonky2 proof + let data = builder.build::(); + let proof = data.prove(pw)?; + + // 3) verify the plonky2 proof + data.verify(proof)?; + + Ok(()) + } +} diff --git a/src/fibstark.rs b/src/fibstark.rs new file mode 100644 index 0000000..0324a67 --- /dev/null +++ b/src/fibstark.rs @@ -0,0 +1,179 @@ +// Imports all basic types. +use plonky2::field::extension::{Extendable, FieldExtension}; +use plonky2::field::packed::PackedField; +use plonky2::field::polynomial::PolynomialValues; +use plonky2::hash::hash_types::RichField; +use std::marker::PhantomData; + +// Imports to define the constraints of our STARK. +use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use starky::evaluation_frame::{StarkEvaluationFrame, StarkFrame}; +use starky::stark::Stark; + +// Imports to define the recursive constraints of our STARK. +use plonky2::iop::ext_target::ExtensionTarget; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use starky::util::trace_rows_to_poly_values; + +#[derive(Copy, Clone)] +pub struct FibonacciStark, const D: usize> { + num_rows: usize, + _phantom: PhantomData, +} +// Define witness generation. +impl, const D: usize> FibonacciStark { + // The first public input is `x0`. + const PI_INDEX_X0: usize = 0; + // The second public input is `x1`. + const PI_INDEX_X1: usize = 1; + // The third public input is the second element of the last row, + // which should be equal to the `num_rows`-th Fibonacci number. + const PI_INDEX_RES: usize = 2; + + pub fn new(num_rows: usize) -> Self { + Self { + num_rows, + _phantom: PhantomData, + } + } + + /// Generate the trace using `x0, x1, 0` as initial state values. + pub fn generate_trace(&self, x0: F, x1: F) -> Vec> { + let trace_rows = (0..self.num_rows) + .scan([x0, x1, F::ZERO], |acc, _| { + let tmp = *acc; + acc[0] = tmp[1]; + acc[1] = tmp[0] + tmp[1]; + acc[2] = tmp[2] + F::ONE; + Some(tmp) + }) + .collect::>(); + // Transpose the row-wise trace for the prover. + trace_rows_to_poly_values(trace_rows) + } + + /// performs the same logic as in `generate_trace`, but natively in rust + pub fn compute_native(&self, x0: F, x1: F) -> F { + (0..self.num_rows - 1) + .fold((x0, x1), |acc, _| (acc.1, acc.0 + acc.1)) + .1 + } +} + +// Define constraints. +const COLUMNS: usize = 3; +const PUBLIC_INPUTS: usize = 3; + +impl, const D: usize> Stark for FibonacciStark { + type EvaluationFrame + = StarkFrame + where + FE: FieldExtension, + P: PackedField; + + type EvaluationFrameTarget = + StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; + + // Define this STARK's constraints. + fn eval_packed_generic( + &self, + vars: &Self::EvaluationFrame, // evaluation frame of a STARK table + yield_constr: &mut ConstraintConsumer

, + ) where + FE: FieldExtension, + P: PackedField, + { + let local_values = vars.get_local_values(); // ie. current row + let next_values = vars.get_next_values(); // ie. next row + let public_inputs = vars.get_public_inputs(); // ie. public inputs + + // Check public inputs. + yield_constr.constraint_first_row(local_values[0] - public_inputs[Self::PI_INDEX_X0]); + yield_constr.constraint_first_row(local_values[1] - public_inputs[Self::PI_INDEX_X1]); + yield_constr.constraint_last_row(local_values[1] - public_inputs[Self::PI_INDEX_RES]); + + // Enforce the Fibonacci transition constraints. + // x0' <- x1 + yield_constr.constraint_transition(next_values[0] - local_values[1]); + // x1' <- x0 + x1 + yield_constr.constraint_transition(next_values[1] - local_values[0] - local_values[1]); + } + + // Define the constraints to recursively verify this STARK. + fn eval_ext_circuit( + &self, + builder: &mut CircuitBuilder, + vars: &Self::EvaluationFrameTarget, + yield_constr: &mut RecursiveConstraintConsumer, + ) { + let local_values = vars.get_local_values(); + let next_values = vars.get_next_values(); + let public_inputs = vars.get_public_inputs(); + + // Check public inputs. + let pis_constraints = [ + builder.sub_extension(local_values[0], public_inputs[Self::PI_INDEX_X0]), + builder.sub_extension(local_values[1], public_inputs[Self::PI_INDEX_X1]), + builder.sub_extension(local_values[1], public_inputs[Self::PI_INDEX_RES]), + ]; + + yield_constr.constraint_first_row(builder, pis_constraints[0]); + yield_constr.constraint_first_row(builder, pis_constraints[1]); + yield_constr.constraint_last_row(builder, pis_constraints[2]); + + // Enforce the Fibonacci transition constraints. + // x0' <- x1 + let first_col_constraint = builder.sub_extension(next_values[0], local_values[1]); + yield_constr.constraint_transition(builder, first_col_constraint); + // x1' <- x0 + x1 + let second_col_constraint = { + let tmp = builder.sub_extension(next_values[1], local_values[0]); + builder.sub_extension(tmp, local_values[1]) + }; + yield_constr.constraint_transition(builder, second_col_constraint); + } + + fn constraint_degree(&self) -> usize { + 2 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use anyhow::Result; + use plonky2::field::types::Field; + use plonky2::util::timing::TimingTree; + use starky::prover::prove; + use starky::verifier::verify_stark_proof; + + use crate::{C, D, F, STARK_CONFIG}; + + /// test that instantiates a new `FibonacciStark` instance, generates an associated STARK + /// trace, and generates a proof for it. + #[test] + fn test_fibonacci_stark() -> Result<()> { + let num_rows = 1 << 10; + let x0 = F::from_canonical_u32(2); + let x1 = F::from_canonical_u32(7); + + let stark = FibonacciStark::::new(num_rows); + let public_inputs = [x0, x1, stark.compute_native(x0, x1)]; + let trace = stark.generate_trace(public_inputs[0], public_inputs[1]); + + let proof = prove::, D>( + stark, + &STARK_CONFIG, + trace, + &public_inputs, + None, + &mut TimingTree::default(), + ) + .expect("We should have a valid proof!"); + + verify_stark_proof(stark, proof, &STARK_CONFIG, None) + .expect("We should be able to verify this proof!"); + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 8af7564..b2a0078 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,183 +1,11 @@ -/// The code of this file is an adaptation from the Starky example from the plonky2 repo. -/// -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::field::polynomial::PolynomialValues; -use plonky2::hash::hash_types::RichField; -use std::marker::PhantomData; - -// Imports to define the constraints of our STARK. -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::{StarkEvaluationFrame, StarkFrame}; -use starky::stark::Stark; - -// Imports to define the recursive constraints of our STARK. -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::util::trace_rows_to_poly_values; - -// Imports to generate a STARK instance, compute the trace and prove it -use plonky2::field::types::Field; use plonky2::plonk::config::GenericConfig; use plonky2::plonk::config::PoseidonGoldilocksConfig; -use plonky2::util::timing::TimingTree; use starky::config::StarkConfig; -use starky::prover::prove; -use starky::verifier::verify_stark_proof; - -const D: usize = 2; -const CONFIG: StarkConfig = StarkConfig::standard_fast_config(); -type C = PoseidonGoldilocksConfig; -type F = >::F; -type S = FibonacciStark; - -#[derive(Copy, Clone)] -pub struct FibonacciStark, const D: usize> { - num_rows: usize, - _phantom: PhantomData, -} -// Define witness generation. -impl, const D: usize> FibonacciStark { - // The first public input is `x0`. - const PI_INDEX_X0: usize = 0; - // The second public input is `x1`. - const PI_INDEX_X1: usize = 1; - // The third public input is the second element of the last row, - // which should be equal to the `num_rows`-th Fibonacci number. - const PI_INDEX_RES: usize = 2; - - pub(crate) fn new(num_rows: usize) -> Self { - Self { - num_rows, - _phantom: PhantomData, - } - } - - /// Generate the trace using `x0, x1, 0` as initial state values. - fn generate_trace(&self, x0: F, x1: F) -> Vec> { - let mut trace_rows = (0..self.num_rows) - .scan([x0, x1, F::ZERO], |acc, _| { - let tmp = *acc; - acc[0] = tmp[1]; - acc[1] = tmp[0] + tmp[1]; - acc[2] = tmp[2] + F::ONE; - Some(tmp) - }) - .collect::>(); - // Transpose the row-wise trace for the prover. - trace_rows_to_poly_values(trace_rows) - } -} - -// Define constraints. -const COLUMNS: usize = 3; -const PUBLIC_INPUTS: usize = 3; - -impl, const D: usize> Stark for FibonacciStark { - type EvaluationFrame - = StarkFrame - where - FE: FieldExtension, - P: PackedField; - - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; - - // Define this STARK's constraints. - fn eval_packed_generic( - &self, - vars: &Self::EvaluationFrame, - yield_constr: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, - { - let local_values = vars.get_local_values(); - let next_values = vars.get_next_values(); - let public_inputs = vars.get_public_inputs(); - - // Check public inputs. - yield_constr.constraint_first_row(local_values[0] - public_inputs[Self::PI_INDEX_X0]); - yield_constr.constraint_first_row(local_values[1] - public_inputs[Self::PI_INDEX_X1]); - yield_constr.constraint_last_row(local_values[1] - public_inputs[Self::PI_INDEX_RES]); - - // Enforce the Fibonacci transition constraints. - // x0' <- x1 - yield_constr.constraint_transition(next_values[0] - local_values[1]); - // x1' <- x0 + x1 - yield_constr.constraint_transition(next_values[1] - local_values[0] - local_values[1]); - } - - // Define the constraints to recursively verify this STARK. - fn eval_ext_circuit( - &self, - builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - yield_constr: &mut RecursiveConstraintConsumer, - ) { - let local_values = vars.get_local_values(); - let next_values = vars.get_next_values(); - let public_inputs = vars.get_public_inputs(); - - // Check public inputs. - let pis_constraints = [ - builder.sub_extension(local_values[0], public_inputs[Self::PI_INDEX_X0]), - builder.sub_extension(local_values[1], public_inputs[Self::PI_INDEX_X1]), - builder.sub_extension(local_values[1], public_inputs[Self::PI_INDEX_RES]), - ]; - - yield_constr.constraint_first_row(builder, pis_constraints[0]); - yield_constr.constraint_first_row(builder, pis_constraints[1]); - yield_constr.constraint_last_row(builder, pis_constraints[2]); - - // Enforce the Fibonacci transition constraints. - // x0' <- x1 - let first_col_constraint = builder.sub_extension(next_values[0], local_values[1]); - yield_constr.constraint_transition(builder, first_col_constraint); - // x1' <- x0 + x1 - let second_col_constraint = { - let tmp = builder.sub_extension(next_values[1], local_values[0]); - builder.sub_extension(tmp, local_values[1]) - }; - yield_constr.constraint_transition(builder, second_col_constraint); - } - - fn constraint_degree(&self) -> usize { - 2 - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn fibonacci_native(n: usize, x0: F, x1: F) -> F { - (0..n).fold((x0, x1), |acc, _| (acc.1, acc.0 + acc.1)).1 - } - - // test that instantiates a new `FibonacciStark` instance, generates an associated STARK trace, - // and generates a proof for it. - #[test] - fn test_fibonacci_stark() { - let num_rows = 1 << 10; - let x0 = F::from_canonical_u32(2); - let x1 = F::from_canonical_u32(7); - - let public_inputs = [x0, x1, fibonacci_native(num_rows - 1, x0, x1)]; - let stark = FibonacciStark::::new(num_rows); - let trace = stark.generate_trace(public_inputs[0], public_inputs[1]); - let proof = prove::( - stark, - &CONFIG, - trace, - &public_inputs, - None, - &mut TimingTree::default(), - ) - .expect("We should have a valid proof!"); +pub mod circuit; +pub mod fibstark; - verify_stark_proof(stark, proof, &CONFIG, None) - .expect("We should be able to verify this proof!") - } -} +pub const D: usize = 2; +pub const STARK_CONFIG: StarkConfig = StarkConfig::standard_fast_config(); +pub type C = PoseidonGoldilocksConfig; +pub type F = >::F;