Skip to content

Commit

Permalink
Leaf verifier program verifies public values root
Browse files Browse the repository at this point in the history
  • Loading branch information
nyunyunyunyu committed Nov 2, 2024
1 parent 5025033 commit 8590509
Show file tree
Hide file tree
Showing 11 changed files with 354 additions and 72 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ bincode = "1.3.3"
lazy_static = "1.5.0"
once_cell = "1.19.0"
derive-new = "0.6.0"
derivative = "2.2.0"
strum_macros = "0.26.4"
strum = { version = "0.26.3", features = ["derive"] }
enum-utils = "0.1.1"
Expand Down
1 change: 1 addition & 0 deletions axiom-vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ax-stark-sdk = { workspace = true }
p3-baby-bear = { workspace = true }
axvm-circuit = { workspace = true }

derivative = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }

Expand Down
6 changes: 5 additions & 1 deletion axiom-vm/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,14 @@ impl AxiomVmProvingKey {
let app_vm_pk = config.app_vm_config.generate_pk(engine.keygen_builder());
assert!(app_vm_pk.max_constraint_degree < 1 << config.fri_params.log_blowup);
assert!(config.poseidon2_max_constraint_degree < 1 << config.fri_params.log_blowup);
assert_eq!(
config.max_num_user_public_values,
config.app_vm_config.num_public_values
);
assert!(config.app_vm_config.continuation_enabled);
let leaf_vm_config = config.leaf_verifier_vm_config();
let leaf_verifier_pk = leaf_vm_config.generate_pk(engine.keygen_builder());
let leaf_program = LeafVmVerifierConfig {
max_num_user_public_values: config.max_num_user_public_values,
fri_params: config.fri_params,
app_vm_config: config.app_vm_config.clone(),
compiler_options: config.compiler_options.clone(),
Expand Down
113 changes: 62 additions & 51 deletions axiom-vm/src/verifier/leaf/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::{array, borrow::BorrowMut};
use std::array;

use ax_stark_sdk::{
ax_stark_backend::{
keygen::types::MultiStarkVerifyingKey, p3_field::AbstractField, prover::types::Proof,
keygen::types::MultiStarkVerifyingKey, p3_field::AbstractField, p3_util::log2_strict_usize,
prover::types::Proof,
},
config::{baby_bear_poseidon2::BabyBearPoseidon2Config, FriParameters},
};
Expand All @@ -11,8 +12,9 @@ use axvm_circuit::{
instructions::program::Program, VmConfig, CONNECTOR_AIR_ID, MERKLE_AIR_ID,
PROGRAM_CACHED_TRACE_INDEX,
},
circuit_derive::AlignedBorrow,
system::{connector::VmConnectorPvs, memory::merkle::MemoryMerklePvs},
system::{
connector::VmConnectorPvs, memory::tree::public_values::PUBLIC_VALUES_ADDRESS_SPACE_OFFSET,
},
};
use axvm_native_compiler::{conversion::CompilerOptions, prelude::*};
use axvm_recursion::{
Expand All @@ -25,60 +27,26 @@ use axvm_recursion::{
utils::const_fri_config,
vars::StarkProofVariable,
};
use types::LeafVmVerifierPvs;

use crate::{config::AxiomVmConfig, verifier::leaf::types::UserPublicValuesRootProof};

use crate::config::AxiomVmConfig;
pub mod types;
mod vars;

type C = InnerConfig;
type F = InnerVal;

#[derive(Debug, AlignedBorrow)]
#[repr(C)]
pub struct LeafVmVerifierPvs<T, const CHUNK: usize> {
pub app_commit: [T; CHUNK],
pub connector: VmConnectorPvs<T>,
pub memory: MemoryMerklePvs<T, CHUNK>,
pub public_values_commit: [T; CHUNK],
}

impl<const CHUNK: usize> LeafVmVerifierPvs<Felt<F>, { CHUNK }> {
fn uninit(builder: &mut Builder<C>) -> Self {
Self {
app_commit: array::from_fn(|_| builder.uninit()),
connector: VmConnectorPvs {
initial_pc: builder.uninit(),
final_pc: builder.uninit(),
exit_code: builder.uninit(),
is_terminate: builder.uninit(),
},
memory: MemoryMerklePvs {
initial_root: array::from_fn(|_| builder.uninit()),
final_root: array::from_fn(|_| builder.uninit()),
},
public_values_commit: array::from_fn(|_| builder.uninit()),
}
}
}

impl<const CHUNK: usize> LeafVmVerifierPvs<Felt<F>, { CHUNK }> {
pub fn flatten(self) -> Vec<Felt<F>> {
let mut v = vec![Felt(0, Default::default()); LeafVmVerifierPvs::<u8, CHUNK>::width()];
*v.as_mut_slice().borrow_mut() = self;
v
}
}

/// Config to generate
pub struct LeafVmVerifierConfig {
#[allow(unused)]
pub max_num_user_public_values: usize,
pub fri_params: FriParameters,
pub app_vm_config: VmConfig,
pub compiler_options: CompilerOptions,
}

impl LeafVmVerifierConfig {
pub fn build_program(
self,
&self,
app_vm_vk: MultiStarkVerifyingKey<BabyBearPoseidon2Config>,
) -> Program<F> {
self.app_vm_config.memory_config.memory_dimensions();
Expand All @@ -94,7 +62,7 @@ impl LeafVmVerifierConfig {
// At least 1 proof should be provided.
builder.assert_ne::<Usize<_>>(proofs.len(), RVar::zero());

let pvs = LeafVmVerifierPvs::<Felt<F>, { DIGEST_SIZE }>::uninit(&mut builder);
let pvs = LeafVmVerifierPvs::<Felt<F>>::uninit(&mut builder);
builder.range(0, proofs.len()).for_each(|i, builder| {
let proof = builder.get(&proofs, i);
StarkVerifier::verify::<DuplexChallengerVariable<C>>(
Expand Down Expand Up @@ -174,10 +142,14 @@ impl LeafVmVerifierConfig {
},
);
}
// TODO: decommit user public value address space.
for j in 0..DIGEST_SIZE {
builder.assign(&pvs.public_values_commit[j], F::zero());
}
});

let is_terminate = builder.cast_felt_to_var(pvs.connector.is_terminate);
builder.if_eq(is_terminate, F::one()).then(|builder| {
let (pv_commit, expected_memory_root) =
self.verify_user_public_values_root(builder);
builder.assert_eq::<[_; DIGEST_SIZE]>(pvs.memory.final_root, expected_memory_root);
builder.assign(&pvs.public_values_commit, pv_commit);
});
for pv in pvs.flatten() {
builder.commit_public_value(pv);
Expand All @@ -186,14 +158,53 @@ impl LeafVmVerifierConfig {
builder.halt();
}

builder.compile_isa_with_options(self.compiler_options)
builder.compile_isa_with_options(self.compiler_options.clone())
}

/// Read the public values root proof from the input stream and verify it.
// This verification must be consistent `axvm-circuit::system::memory::tree::public_values`.
/// Returns the public values commit and the corresponding memory state root.
fn verify_user_public_values_root(
&self,
builder: &mut Builder<C>,
) -> ([Felt<F>; DIGEST_SIZE], [Felt<F>; DIGEST_SIZE]) {
let memory_dimensions = self.app_vm_config.memory_config.memory_dimensions();
let pv_as = F::from_canonical_usize(
PUBLIC_VALUES_ADDRESS_SPACE_OFFSET + memory_dimensions.as_offset,
);
let pv_start_idx = memory_dimensions.label_to_index((pv_as, 0));
let pv_height = log2_strict_usize(self.app_vm_config.num_public_values / DIGEST_SIZE);
let proof_len = memory_dimensions.overall_height() - pv_height;
let idx_prefix = pv_start_idx >> pv_height;

// Read the public values root proof from the input stream.
let root_proof = UserPublicValuesRootProof::<F>::read(builder);
builder.assert_eq::<Usize<_>>(root_proof.sibling_hashes.len(), Usize::from(proof_len));
let mut curr_commit = root_proof.public_values_commit;
// Share the same state array to avoid unnecessary allocations.
let state: Array<C, Felt<_>> = builder.array(PERMUTATION_WIDTH);
for i in 0..proof_len {
let sibling_hash = builder.get(&root_proof.sibling_hashes, i);
let (l_hash, r_hash) = if idx_prefix & (1 << i) != 0 {
(sibling_hash, curr_commit)
} else {
(curr_commit, sibling_hash)
};
for j in 0..DIGEST_SIZE {
builder.set(&state, j, l_hash[j]);
builder.set(&state, DIGEST_SIZE + j, r_hash[j]);
}
builder.poseidon2_permute_mut(&state);
curr_commit = array::from_fn(|j| builder.get(&state, j));
}
(root_proof.public_values_commit, curr_commit)
}
}

impl AxiomVmConfig {
pub fn leaf_verifier_vm_config(&self) -> VmConfig {
VmConfig::aggregation(
LeafVmVerifierPvs::<u8, DIGEST_SIZE>::width(),
LeafVmVerifierPvs::<u8>::width(),
self.poseidon2_max_constraint_degree,
)
}
Expand Down
90 changes: 90 additions & 0 deletions axiom-vm/src/verifier/leaf/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use std::{array, borrow::BorrowMut};

use ax_stark_sdk::ax_stark_backend::{
config::{Com, StarkGenericConfig, Val},
p3_field::PrimeField32,
prover::types::Proof,
};
use axvm_circuit::{
circuit_derive::AlignedBorrow,
system::{
connector::VmConnectorPvs,
memory::{merkle::MemoryMerklePvs, tree::public_values::UserPublicValuesProof},
},
};
use axvm_native_compiler::ir::{Builder, Config, Felt, DIGEST_SIZE};
use derivative::Derivative;
use serde::{Deserialize, Serialize};

#[derive(Debug, AlignedBorrow)]
#[repr(C)]
pub struct LeafVmVerifierPvs<T> {
pub app_commit: [T; DIGEST_SIZE],
pub connector: VmConnectorPvs<T>,
pub memory: MemoryMerklePvs<T, DIGEST_SIZE>,
pub public_values_commit: [T; DIGEST_SIZE],
}

/// Input for the leaf VM verifier.
#[derive(Serialize, Deserialize, Derivative)]
#[serde(bound = "")]
#[derivative(Clone(bound = "Com<SC>: Clone"))]
pub struct LeafVmVerifierInput<SC: StarkGenericConfig> {
/// The proofs of the execution segments in the execution order.
pub proofs: Vec<Proof<SC>>,
/// The public values root proof. Leaf VM verifier only needs this when verifying the last
/// segment.
pub public_values_root_proof: Option<UserPublicValuesRootProof<Val<SC>>>,
}

/// Proof that the merkle root of public values is in the memory state. Can be extracted from
/// `axvm-circuit::system::memory::public_values::UserPublicValuesProof`.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct UserPublicValuesRootProof<F> {
/// Sibling hashes for proving the merkle root of public values. For a specific VM, the path
/// is constant. So we don't need the boolean which indicates if a node is a left child or right
/// child.
pub sibling_hashes: Vec<[F; DIGEST_SIZE]>,
pub public_values_commit: [F; DIGEST_SIZE],
}

impl<F: PrimeField32> LeafVmVerifierPvs<Felt<F>> {
pub(crate) fn uninit<C: Config<F = F>>(builder: &mut Builder<C>) -> Self {
Self {
app_commit: array::from_fn(|_| builder.uninit()),
connector: VmConnectorPvs {
initial_pc: builder.uninit(),
final_pc: builder.uninit(),
exit_code: builder.uninit(),
is_terminate: builder.uninit(),
},
memory: MemoryMerklePvs {
initial_root: array::from_fn(|_| builder.uninit()),
final_root: array::from_fn(|_| builder.uninit()),
},
public_values_commit: array::from_fn(|_| builder.uninit()),
}
}
}

impl<F: Default + Clone> LeafVmVerifierPvs<Felt<F>> {
pub fn flatten(self) -> Vec<Felt<F>> {
let mut v = vec![Felt(0, Default::default()); LeafVmVerifierPvs::<u8>::width()];
*v.as_mut_slice().borrow_mut() = self;
v
}
}

impl<F: Clone> UserPublicValuesRootProof<F> {
pub fn extract(pvs_proof: &UserPublicValuesProof<{ DIGEST_SIZE }, F>) -> Self {
Self {
sibling_hashes: pvs_proof
.proof
.clone()
.into_iter()
.map(|(_, hash)| hash)
.collect(),
public_values_commit: pvs_proof.public_values_commit.clone(),
}
}
}
69 changes: 69 additions & 0 deletions axiom-vm/src/verifier/leaf/vars.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use std::array;

use ax_stark_sdk::ax_stark_backend::{
config::{StarkGenericConfig, Val},
p3_field::AbstractField,
prover::types::Proof,
};
use axvm_native_compiler::prelude::*;
use axvm_recursion::{
hints::{Hintable, InnerVal},
types::InnerConfig,
};

use crate::verifier::leaf::types::{LeafVmVerifierInput, UserPublicValuesRootProof};

#[derive(DslVariable, Clone)]
pub struct UserPublicValuesRootProofVariable<const CHUNK: usize, C: Config> {
/// Sibling hashes for proving the merkle root of public values. For a specific VM, the path
/// is constant. So we don't need the boolean which indicates if a node is a left child or right
/// child.
pub sibling_hashes: Array<C, [Felt<C::F>; CHUNK]>,
pub public_values_commit: [Felt<C::F>; CHUNK],
}

type C = InnerConfig;
type F = InnerVal;

impl<SC: StarkGenericConfig> LeafVmVerifierInput<SC> {
pub fn write_to_stream<C: Config<N = Val<SC>>>(&self) -> Vec<Vec<Val<SC>>>
where
Vec<Proof<SC>>: Hintable<C>,
UserPublicValuesRootProof<Val<SC>>: Hintable<C>,
{
let mut ret = Hintable::<C>::write(&self.proofs);
if let Some(pvs_root_proof) = &self.public_values_root_proof {
ret.extend(Hintable::<C>::write(pvs_root_proof));
}
ret
}
}

impl Hintable<C> for UserPublicValuesRootProof<F> {
type HintVariable = UserPublicValuesRootProofVariable<{ DIGEST_SIZE }, C>;
fn read(builder: &mut Builder<C>) -> Self::HintVariable {
let len = builder.hint_var();
let sibling_hashes = builder.array(len);
builder.range(0, len).for_each(|i, builder| {
// FIXME: add hint support for slices.
let hash = array::from_fn(|_| builder.hint_felt());
builder.set_value(&sibling_hashes, i, hash);
});
let public_values_commit = array::from_fn(|_| builder.hint_felt());
Self::HintVariable {
sibling_hashes,
public_values_commit,
}
}
fn write(&self) -> Vec<Vec<<C as Config>::N>> {
let len = <<C as Config>::N>::from_canonical_usize(self.sibling_hashes.len());
let mut stream = len.write();
stream.extend(self.sibling_hashes.iter().flat_map(write_field_slice));
stream.extend(write_field_slice(&self.public_values_commit));
stream
}
}

fn write_field_slice(arr: &[F; DIGEST_SIZE]) -> Vec<Vec<<C as Config>::N>> {
arr.iter().flat_map(|x| x.write()).collect()
}
Loading

0 comments on commit 8590509

Please sign in to comment.