Browse Source

add plonky2 circuit to verify the fibstark proofs

main
arnaucube 2 months ago
parent
commit
c053c137fa
5 changed files with 330 additions and 178 deletions
  1. +2
    -0
      Cargo.toml
  2. +13
    -0
      README.md
  3. +130
    -0
      src/circuit.rs
  4. +179
    -0
      src/fibstark.rs
  5. +6
    -178
      src/lib.rs

+ 2
- 0
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"

+ 13
- 0
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`

+ 130
- 0
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<S: Stark<F, D> + Copy> {
_s: PhantomData<S>,
proof_target: StarkProofWithPublicInputsTarget<D>,
zero_target: Target,
}
impl<S: Stark<F, D> + Copy> FibStarkVerifierTargets<S> {
pub fn add_targets(
builder: &mut CircuitBuilder<F, D>,
stark: S,
degree_bits: usize,
) -> Result<Self> {
let proof_target =
add_virtual_stark_proof_with_pis(builder, &stark, &STARK_CONFIG, degree_bits, 0, 0);
verify_stark_proof_circuit::<F, C, S, D>(
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<F>,
proof: &StarkProofWithPublicInputs<F, C, D>,
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::<F, D>::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::<F, C, FibonacciStark<F, D>, 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::<F, D>::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::<C>();
let proof = data.prove(pw)?;
// 3) verify the plonky2 proof
data.verify(proof)?;
Ok(())
}
}

+ 179
- 0
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<F: RichField + Extendable<D>, const D: usize> {
num_rows: usize,
_phantom: PhantomData<F>,
}
// Define witness generation.
impl<F: RichField + Extendable<D>, const D: usize> FibonacciStark<F, D> {
// 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<PolynomialValues<F>> {
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::<Vec<_>>();
// 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<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for FibonacciStark<F, D> {
type EvaluationFrame<FE, P, const D2: usize>
= StarkFrame<P, P::Scalar, COLUMNS, PUBLIC_INPUTS>
where
FE: FieldExtension<D2, BaseField = F>,
P: PackedField<Scalar = FE>;
type EvaluationFrameTarget =
StarkFrame<ExtensionTarget<D>, ExtensionTarget<D>, COLUMNS, PUBLIC_INPUTS>;
// Define this STARK's constraints.
fn eval_packed_generic<FE, P, const D2: usize>(
&self,
vars: &Self::EvaluationFrame<FE, P, D2>, // evaluation frame of a STARK table
yield_constr: &mut ConstraintConsumer<P>,
) where
FE: FieldExtension<D2, BaseField = F>,
P: PackedField<Scalar = FE>,
{
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<F, D>,
vars: &Self::EvaluationFrameTarget,
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) {
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::<F, D>::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::<F, C, FibonacciStark<F, D>, 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(())
}
}

+ 6
- 178
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 = <C as GenericConfig<D>>::F;
type S = FibonacciStark<F, D>;
#[derive(Copy, Clone)]
pub struct FibonacciStark<F: RichField + Extendable<D>, const D: usize> {
num_rows: usize,
_phantom: PhantomData<F>,
}
// Define witness generation.
impl<F: RichField + Extendable<D>, const D: usize> FibonacciStark<F, D> {
// 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<PolynomialValues<F>> {
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::<Vec<_>>();
// 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<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for FibonacciStark<F, D> {
type EvaluationFrame<FE, P, const D2: usize>
= StarkFrame<P, P::Scalar, COLUMNS, PUBLIC_INPUTS>
where
FE: FieldExtension<D2, BaseField = F>,
P: PackedField<Scalar = FE>;
type EvaluationFrameTarget =
StarkFrame<ExtensionTarget<D>, ExtensionTarget<D>, COLUMNS, PUBLIC_INPUTS>;
// Define this STARK's constraints.
fn eval_packed_generic<FE, P, const D2: usize>(
&self,
vars: &Self::EvaluationFrame<FE, P, D2>,
yield_constr: &mut ConstraintConsumer<P>,
) where
FE: FieldExtension<D2, BaseField = F>,
P: PackedField<Scalar = FE>,
{
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<F, D>,
vars: &Self::EvaluationFrameTarget,
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) {
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<F: Field>(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::<F, D>::new(num_rows);
let trace = stark.generate_trace(public_inputs[0], public_inputs[1]);
let proof = prove::<F, C, S, D>(
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 = <C as GenericConfig<D>>::F;

Loading…
Cancel
Save