Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 150 additions & 2 deletions crypto/stark/src/proof/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use crate::config::Commitment;
use crate::frame::Frame;
use crate::fri::fri_decommit::{ArchivedFriDecommitment, FriDecommitment};
use crate::proof::stark::{
ArchivedDeepPolynomialOpening, ArchivedPolynomialOpenings, ArchivedStarkProof,
DeepPolynomialOpening, PolynomialOpenings, StarkProof,
ArchivedDeepPolynomialOpening, ArchivedMultiProof, ArchivedPolynomialOpenings,
ArchivedStarkProof, DeepPolynomialOpening, MultiProof, PolynomialOpenings, StarkProof,
};
use crate::table::{ArchivedTable, Table, TableView};
use math::field::element::{ArchivedFieldElement, FieldElement};
Expand Down Expand Up @@ -481,6 +481,154 @@ where
}
}

/// Borrowed view over a [`MultiProof`] (owned or archived-in-place),
/// producing per-proof [`StarkProofView`]s without ever materializing an
/// owned `MultiProof` from an archive. Replaces the
/// `proofs.iter().map(StarkProofView::Owned/Archived).collect()` boilerplate
/// that used to appear at every `MultiProof` verify call site.
pub enum MultiProofView<'a, F: IsSubFieldOf<E>, E: IsField, PI>
where
F::BaseType: math::field::element::NativeArchived,
E::BaseType: math::field::element::NativeArchived,
PI: rkyv::Archive,
<PI as rkyv::Archive>::Archived: rkyv::Deserialize<PI, PiDeserializer>,
{
Owned(&'a MultiProof<F, E, PI>),
Archived(&'a ArchivedMultiProof<F, E, PI>),
}

impl<'a, F: IsSubFieldOf<E>, E: IsField, PI> Clone for MultiProofView<'a, F, E, PI>
where
F::BaseType: math::field::element::NativeArchived,
E::BaseType: math::field::element::NativeArchived,
PI: rkyv::Archive,
<PI as rkyv::Archive>::Archived: rkyv::Deserialize<PI, PiDeserializer>,
{
fn clone(&self) -> Self {
*self
}
}
impl<'a, F: IsSubFieldOf<E>, E: IsField, PI> Copy for MultiProofView<'a, F, E, PI>
where
F::BaseType: math::field::element::NativeArchived,
E::BaseType: math::field::element::NativeArchived,
PI: rkyv::Archive,
<PI as rkyv::Archive>::Archived: rkyv::Deserialize<PI, PiDeserializer>,
{
}

impl<'a, F: IsSubFieldOf<E>, E: IsField, PI> MultiProofView<'a, F, E, PI>
where
F::BaseType: math::field::element::NativeArchived,
E::BaseType: math::field::element::NativeArchived,
PI: rkyv::Archive,
<PI as rkyv::Archive>::Archived: rkyv::Deserialize<PI, PiDeserializer>,
{
#[inline(always)]
pub fn len(&self) -> usize {
match self {
Self::Owned(p) => p.proofs.len(),
Self::Archived(p) => p.proofs.len(),
}
}

#[inline(always)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}

#[inline(always)]
pub fn get(&self, i: usize) -> StarkProofView<'a, F, E, PI> {
match self {
Self::Owned(p) => StarkProofView::Owned(&p.proofs[i]),
Self::Archived(p) => StarkProofView::Archived(&p.proofs.as_slice()[i]),
}
}

#[inline(always)]
pub fn last(&self) -> Option<StarkProofView<'a, F, E, PI>> {
let len = self.len();
(len > 0).then(|| self.get(len - 1))
}

#[inline(always)]
pub fn iter(&self) -> impl Iterator<Item = StarkProofView<'a, F, E, PI>> + 'a {
let this = *self;
(0..this.len()).map(move |i| this.get(i))
}
}

/// A source of [`StarkProofView`]s the verifier can iterate over more than
/// once without ever materializing a `Vec` — implemented for a plain slice
/// (or `Vec`) of views and for [`MultiProofView`] alike, so
/// [`crate::verifier::IsStarkVerifier::multi_verify_views`] runs identically
/// whether its caller already had a slice or is reading straight out of a
/// (owned or archived) `MultiProof`.
pub trait ProofViewSource<'a, F: IsSubFieldOf<E> + 'a, E: IsField + 'a, PI: 'a>: Copy
where
F::BaseType: math::field::element::NativeArchived,
E::BaseType: math::field::element::NativeArchived,
PI: rkyv::Archive,
<PI as rkyv::Archive>::Archived: rkyv::Deserialize<PI, PiDeserializer>,
{
fn view_len(&self) -> usize;
fn view_iter(&self) -> impl Iterator<Item = StarkProofView<'a, F, E, PI>>;
}

impl<'a, F: IsSubFieldOf<E> + 'a, E: IsField + 'a, PI: 'a> ProofViewSource<'a, F, E, PI>
for &'a [StarkProofView<'a, F, E, PI>]
where
F::BaseType: math::field::element::NativeArchived,
E::BaseType: math::field::element::NativeArchived,
PI: rkyv::Archive,
<PI as rkyv::Archive>::Archived: rkyv::Deserialize<PI, PiDeserializer>,
{
#[inline(always)]
fn view_len(&self) -> usize {
self.len()
}
#[inline(always)]
fn view_iter(&self) -> impl Iterator<Item = StarkProofView<'a, F, E, PI>> {
self.iter().copied()
}
}

impl<'a, F: IsSubFieldOf<E> + 'a, E: IsField + 'a, PI: 'a> ProofViewSource<'a, F, E, PI>
for &'a Vec<StarkProofView<'a, F, E, PI>>
where
F::BaseType: math::field::element::NativeArchived,
E::BaseType: math::field::element::NativeArchived,
PI: rkyv::Archive,
<PI as rkyv::Archive>::Archived: rkyv::Deserialize<PI, PiDeserializer>,
{
#[inline(always)]
fn view_len(&self) -> usize {
self.len()
}
#[inline(always)]
fn view_iter(&self) -> impl Iterator<Item = StarkProofView<'a, F, E, PI>> {
self.iter().copied()
}
}

impl<'a, F: IsSubFieldOf<E> + 'a, E: IsField + 'a, PI: 'a> ProofViewSource<'a, F, E, PI>
for MultiProofView<'a, F, E, PI>
where
F::BaseType: math::field::element::NativeArchived,
E::BaseType: math::field::element::NativeArchived,
PI: rkyv::Archive,
<PI as rkyv::Archive>::Archived: rkyv::Deserialize<PI, PiDeserializer>,
{
#[inline(always)]
fn view_len(&self) -> usize {
MultiProofView::len(self)
}
#[inline(always)]
fn view_iter(&self) -> impl Iterator<Item = StarkProofView<'a, F, E, PI>> {
MultiProofView::iter(self)
}
}

// ---------------------------------------------------------------------------
// Field-coverage guards.
//
Expand Down
4 changes: 2 additions & 2 deletions crypto/stark/src/tests/bus_tests/soundness_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1049,7 +1049,7 @@ fn test_malformed_ood_next_block_shape_rejected_archived() {
assert!(
!Verifier::multi_verify_archived(
&airs,
&archived.proofs,
archived,
&mut DefaultTranscript::<E>::new(&[]),
&FieldElement::zero(),
),
Expand Down Expand Up @@ -1279,7 +1279,7 @@ fn test_gz_pruning_reduces_next_row_openings() {
.unwrap();
assert!(Verifier::multi_verify_archived(
&airs,
&archived.proofs,
archived,
&mut DefaultTranscript::<E>::new(&[]),
&FieldElement::zero(),
));
Expand Down
53 changes: 28 additions & 25 deletions crypto/stark/src/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ use crate::{
config::Commitment,
domain::new_verifier_domain,
lookup::{BusPublicInputs, LOGUP_CHALLENGE_ALPHA, LOGUP_NUM_CHALLENGES, compute_alpha_powers},
proof::stark::{ArchivedStarkProof, MultiProof},
proof::stark::{ArchivedMultiProof, MultiProof},
proof::view::{
DeepPolynomialOpeningView, FriDecommitmentView, PolynomialOpeningsView, StarkProofView,
StarkTableView,
DeepPolynomialOpeningView, FriDecommitmentView, MultiProofView, PolynomialOpeningsView,
ProofViewSource, StarkProofView, StarkTableView,
},
table::Table,
};
Expand Down Expand Up @@ -1095,49 +1095,55 @@ pub trait IsStarkVerifier<
FieldElement<Field>: AsBytes + Sync + Send,
FieldElement<FieldExtension>: AsBytes + Sync + Send,
{
let views: Vec<StarkProofView<Field, FieldExtension, PI>> = multi_proof
.proofs
.iter()
.map(StarkProofView::Owned)
.collect();
Self::multi_verify_views(airs, &views, transcript, expected_bus_balance)
Self::multi_verify_views(
airs,
MultiProofView::Owned(multi_proof),
transcript,
expected_bus_balance,
)
}

/// Verifies one or more rkyv-archived STARK proofs read **in place** from
/// their archive buffer — no proof deserialization, no per-field allocation.
fn multi_verify_archived(
airs: &[&dyn AIR<Field = Field, FieldExtension = FieldExtension, PublicInputs = PI>],
proofs: &[ArchivedStarkProof<Field, FieldExtension, PI>],
multi_proof: &ArchivedMultiProof<Field, FieldExtension, PI>,
transcript: &mut (impl IsStarkTranscript<FieldExtension, Field> + Clone),
expected_bus_balance: &FieldElement<FieldExtension>,
) -> bool
where
FieldElement<Field>: AsBytes + Sync + Send,
FieldElement<FieldExtension>: AsBytes + Sync + Send,
{
let views: Vec<StarkProofView<Field, FieldExtension, PI>> =
proofs.iter().map(StarkProofView::Archived).collect();
Self::multi_verify_views(airs, &views, transcript, expected_bus_balance)
Self::multi_verify_views(
airs,
MultiProofView::Archived(multi_proof),
transcript,
expected_bus_balance,
)
}

/// The single verification implementation, shared by [`Self::multi_verify`]
/// (owned) and [`Self::multi_verify_archived`] (archived), operating on
/// proof views rather than either's concrete type.
fn multi_verify_views(
fn multi_verify_views<'p>(
airs: &[&dyn AIR<Field = Field, FieldExtension = FieldExtension, PublicInputs = PI>],
proofs: &[StarkProofView<Field, FieldExtension, PI>],
proofs: impl ProofViewSource<'p, Field, FieldExtension, PI>,
transcript: &mut (impl IsStarkTranscript<FieldExtension, Field> + Clone),
expected_bus_balance: &FieldElement<FieldExtension>,
) -> bool
where
Field: 'p,
FieldExtension: 'p,
PI: 'p,
FieldElement<Field>: AsBytes + Sync + Send,
FieldElement<FieldExtension>: AsBytes + Sync + Send,
{
if airs.len() != proofs.len() {
if airs.len() != proofs.view_len() {
error!(
"AIR count ({}) does not match proof count ({})",
airs.len(),
proofs.len()
proofs.view_len()
);
return false;
}
Expand All @@ -1151,8 +1157,7 @@ pub trait IsStarkVerifier<
// For preprocessed tables, use the hardcoded commitment (verifier cannot
// trust the prover). For normal tables, use the commitment from the proof.

for (idx, (air, proof)) in airs.iter().zip(proofs).enumerate() {
let proof = *proof;
for (idx, (air, proof)) in airs.iter().zip(proofs.view_iter()).enumerate() {
// Soundness: the number of composition-poly parts is fixed by the AIR's
// degree bound, NOT chosen by the prover. Deriving it from the proof would
// let a malicious prover inflate the part count, widening the composition
Expand Down Expand Up @@ -1229,8 +1234,7 @@ pub trait IsStarkVerifier<
// boundary constraints on LogUp columns, so the bus balance check is
// the only cross-table validation.

for (idx, (air, proof)) in airs.iter().zip(proofs).enumerate() {
let proof = *proof;
for (idx, (air, proof)) in airs.iter().zip(proofs.view_iter()).enumerate() {
if air.has_trace_interaction() && !proof.has_bus_public_inputs() {
error!(
"Table {idx}: AIR has LogUp interactions but proof is missing bus_public_inputs"
Expand All @@ -1252,8 +1256,7 @@ pub trait IsStarkVerifier<
// state after Phase B, domain-separated by table index). This matches
// the prover's forking and makes per-table verification independent.

for (idx, (air, proof)) in airs.iter().zip(proofs).enumerate() {
let proof = *proof;
for (idx, (air, proof)) in airs.iter().zip(proofs.view_iter()).enumerate() {
// Must match prover: fork with domain separator for multi-table,
// use original transcript directly for single-table.
let num_tables = airs.len();
Expand Down Expand Up @@ -1309,7 +1312,7 @@ pub trait IsStarkVerifier<

if needs_lookup_challenges {
let mut total = FieldElement::<FieldExtension>::zero();
for (air, proof) in airs.iter().zip(proofs) {
for (air, proof) in airs.iter().zip(proofs.view_iter()) {
if air.has_trace_interaction()
&& let Some(contribution) = proof.bus_table_contribution()
{
Expand Down Expand Up @@ -1345,7 +1348,7 @@ pub trait IsStarkVerifier<
{
Self::multi_verify_views(
&[air],
&[StarkProofView::Owned(proof)],
&[StarkProofView::Owned(proof)][..],
transcript,
&FieldElement::zero(),
)
Expand Down
19 changes: 19 additions & 0 deletions executor/programs/asm/data_page_touch.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.data
.align 3
counter:
.dword 0x123456789ABCDEF0

.text
.attribute 5, "rv64i2p1"
.globl main
main:
# Touch an ELF .data page: load, mutate, store back a static global so the
# page is genuinely ELF-backed (init_values non-empty), not stack/zero-init.
la t0, counter # 1: t0 = &counter
ld t1, 0(t0) # 2: t1 = counter (0x123456789ABCDEF0)
addi t1, t1, 1 # 3: t1 += 1
sd t1, 0(t0) # 4: counter = t1

li a0, 0
li a7, 93
ecall # 5: Halt
12 changes: 6 additions & 6 deletions executor/src/tests/memory_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::vm::memory::{Memory, MemoryError};
#[test]
fn test_commit_public_output_single() {
let mut memory = Memory::default();
memory.store_byte(0x100, b'a');
memory.store_byte(0x101, b'b');
memory.store_byte(0x100, b'a').unwrap();
memory.store_byte(0x101, b'b').unwrap();

memory
.commit_public_output(0x100, 2)
Expand All @@ -23,10 +23,10 @@ fn test_commit_public_output_single() {
#[test]
fn test_commit_public_output_appends() {
let mut memory = Memory::default();
memory.store_byte(0x100, b'a');
memory.store_byte(0x101, b'b');
memory.store_byte(0x104, b'c');
memory.store_byte(0x105, b'd');
memory.store_byte(0x100, b'a').unwrap();
memory.store_byte(0x101, b'b').unwrap();
memory.store_byte(0x104, b'c').unwrap();
memory.store_byte(0x105, b'd').unwrap();

memory
.commit_public_output(0x100, 2)
Expand Down
2 changes: 1 addition & 1 deletion executor/src/vm/instruction/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ impl Instruction {
match width {
LoadStoreWidth::Byte => {
let value = read_value & 0xFF;
memory.store_byte(addr, value as u8);
memory.store_byte(addr, value as u8)?;
}
LoadStoreWidth::Half => {
let value = read_value & 0xFFFF;
Expand Down
Loading
Loading