Skip to content

Commit

Permalink
feat!: Optionally supply prover randomness seed
Browse files Browse the repository at this point in the history
Allow producing deterministic proofs by supplying a seed from which all
prover randomness is derived. It is discouraged to actually use this
interface, unless in very specific cases.
  • Loading branch information
jan-ferdinand committed Nov 7, 2024
1 parent 2f5b7a9 commit 923e9e1
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 80 deletions.
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,13 @@ package = "triton-isa"
anyhow = "1.0"
arbitrary = { version = "1", features = ["derive"] }
assert2 = "0.3"
bincode = "1.3.3"
colored = "2.1"
clap = { version = "4", features = ["derive", "cargo", "wrap_help", "unicode", "string"] }
criterion = { version = "0.5", features = ["html_reports"] }
directories = "5"
fs-err = "2.11.0"
get-size = "0.1.4"
indexmap = "2.5.0"
indexmap = { version = "2.5.0", features = ["rayon"] }
itertools = "0.13"
lazy_static = "1.5"
ndarray = { version = "0.16", features = ["rayon"] }
Expand Down
1 change: 0 additions & 1 deletion triton-vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ unicode-width.workspace = true

[dev-dependencies]
assert2.workspace = true
bincode.workspace = true
cargo-husky.workspace = true
constraint-circuit.workspace = true
fs-err.workspace = true
Expand Down
17 changes: 10 additions & 7 deletions triton-vm/src/aet.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
use std::collections::hash_map::Entry::Occupied;
use std::collections::hash_map::Entry::Vacant;
use std::collections::HashMap;
use std::ops::AddAssign;

use air::table::hash::HashTable;
Expand All @@ -13,6 +10,9 @@ use air::table_column::HashMainColumn::CI;
use air::table_column::MasterMainColumn;
use air::AIR;
use arbitrary::Arbitrary;
use indexmap::map::Entry::Occupied;
use indexmap::map::Entry::Vacant;
use indexmap::IndexMap;
use isa::error::InstructionError;
use isa::error::InstructionError::InstructionPointerOverflow;
use isa::instruction::Instruction;
Expand Down Expand Up @@ -73,10 +73,13 @@ pub struct AlgebraicExecutionTrace {
/// The u32 entries hold all pairs of BFieldElements that were written to the U32 Table,
/// alongside the u32 instruction that was executed at the time. Additionally, it records how
/// often the instruction was executed with these arguments.
pub u32_entries: HashMap<U32TableEntry, u64>,
// `IndexMap` over `HashMap` for deterministic iteration order. This is not
// needed for correctness of the STARK.
pub u32_entries: IndexMap<U32TableEntry, u64>,

/// Records how often each entry in the cascade table was looked up.
pub cascade_table_lookup_multiplicities: HashMap<u16, u64>,
// `IndexMap` over `HashMap` for the same reasons as for field `u32_entries`.
pub cascade_table_lookup_multiplicities: IndexMap<u16, u64>,

/// Records how often each entry in the lookup table was looked up.
pub lookup_table_lookup_multiplicities: [u64; AlgebraicExecutionTrace::LOOKUP_TABLE_HEIGHT],
Expand Down Expand Up @@ -108,8 +111,8 @@ impl AlgebraicExecutionTrace {
program_hash_trace: Array2::default([0, HASH_WIDTH]),
hash_trace: Array2::default([0, HASH_WIDTH]),
sponge_trace: Array2::default([0, HASH_WIDTH]),
u32_entries: HashMap::new(),
cascade_table_lookup_multiplicities: HashMap::new(),
u32_entries: IndexMap::new(),
cascade_table_lookup_multiplicities: IndexMap::new(),
lookup_table_lookup_multiplicities: [0; Self::LOOKUP_TABLE_HEIGHT],
};
aet.fill_program_hash_trace();
Expand Down
9 changes: 4 additions & 5 deletions triton-vm/src/ndarray_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use ndarray::Array2;
use ndarray::ArrayViewMut2;
use ndarray::Ix;
use ndarray::ShapeBuilder;
use num_traits::Zero;
use num_traits::ConstZero;

/// Slice a two-dimensional array into many non-overlapping mutable subviews
/// of the same height as the array, based on the contiguous partition induced
Expand Down Expand Up @@ -62,10 +62,10 @@ pub fn contiguous_column_slices(column_indices: &[usize]) -> Vec<usize> {
/// Faster than [`Array2::zeros`] through parallelism.
pub fn par_zeros<FF>(shape: impl ShapeBuilder<Dim = ndarray::Dim<[Ix; 2]>>) -> Array2<FF>
where
FF: Zero + Send + Sync + Copy,
FF: ConstZero + Send + Sync + Copy,
{
let mut array = Array2::uninit(shape);
array.par_mapv_inplace(|_| MaybeUninit::new(FF::zero()));
array.par_mapv_inplace(|_| MaybeUninit::new(FF::ZERO));

unsafe {
// SAFETY:
Expand All @@ -80,7 +80,6 @@ mod test {
use itertools::Itertools;
use ndarray::array;
use ndarray::concatenate;
use ndarray::Array2;
use ndarray::Axis;
use num_traits::Zero;
use proptest::collection::vec;
Expand All @@ -89,7 +88,7 @@ mod test {
use proptest::prop_assert_eq;
use proptest::strategy::Strategy;
use test_strategy::proptest;
use twenty_first::math::x_field_element::XFieldElement;
use twenty_first::prelude::XFieldElement;

use super::*;

Expand Down
49 changes: 22 additions & 27 deletions triton-vm/src/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,11 @@ impl Claim {
#[cfg(test)]
mod tests {
use assert2::assert;
use fs_err as fs;
use proptest::collection::vec;
use proptest::prelude::*;
use proptest_arbitrary_interop::arb;
use rand::prelude::StdRng;
use rand_core::SeedableRng;
use test_strategy::proptest;

use crate::prelude::*;
Expand Down Expand Up @@ -194,37 +195,31 @@ mod tests {

#[test]
fn current_proof_version_is_still_current() {
// todo: Once the prover can be de-randomized (issue #334), change this test.
// In particular:
// Seed prover, generate proof, hash the proof, compare to hardcoded digest.
// Remove the proof stored on disk, and remove dependency `bincode`.

fn generate_proof_file(program: Program, claim: Claim) {
let input = claim.input.clone().into();
let non_determinism = NonDeterminism::default();
let (aet, _) = VM::trace_execution(program, input, non_determinism).unwrap();
let proof = Stark::default().prove(&claim, &aet).unwrap();
let proof = bincode::serialize(&proof).unwrap();
fs::create_dir_all("./test_data/").unwrap();
fs::write("./test_data/current_version_is_current.proof.new", proof).unwrap();
eprintln!("New proof generated. Delete “.new” from its file name & commit to accept.");
}

let program = triton_program! {
pick 11 pick 12 pick 13 pick 14 pick 15
read_io 5 assert_vector halt
};
let claim = Claim::about_program(&program).with_input(program.hash());

let Ok(proof) = fs::read("./test_data/current_version_is_current.proof") else {
generate_proof_file(program, claim);
panic!("Proof file does not exist.");
};
let proof = bincode::deserialize(&proof).unwrap();

if Stark::default().verify(&claim, &proof).is_err() {
generate_proof_file(program, claim);
panic!("Verification of existing proof failed. Need to bump proof version?");
};
let input = claim.input.clone().into();
let non_determinism = NonDeterminism::default();
let (aet, _) = VM::trace_execution(program, input, non_determinism).unwrap();

let mut rng = StdRng::seed_from_u64(4742841043836029231);
let proof = Prover::default()
.set_randomness_seed_which_may_break_zero_knowledge(rng.gen())
.prove(&claim, &aet)
.unwrap();
let proof_digest = Tip5::hash(&proof);
dbg!(proof_digest.to_string());

let expected_digest = Digest::new(bfe_array![
11952904396812311265_u64,
16054901534874249652_u64,
9773073309626813769_u64,
15649970038336331462_u64,
7145239594398485773_u64,
]);
assert_eq!(expected_digest, proof_digest);
}
}
7 changes: 6 additions & 1 deletion triton-vm/src/shared_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use num_traits::Zero;
use proptest::collection::vec;
use proptest::prelude::*;
use proptest_arbitrary_interop::arb;
use rand::rngs::StdRng;
use rand_core::SeedableRng;
use test_strategy::Arbitrary;
use twenty_first::prelude::*;

Expand Down Expand Up @@ -145,11 +147,14 @@ pub(crate) fn construct_master_main_table(
let fri = stark.fri(padded_height).unwrap();
let max_degree = stark.max_degree(padded_height);
let quotient_domain = Prover::quotient_domain(fri.domain, max_degree).unwrap();
let seed = StdRng::seed_from_u64(6718321586953195571).gen();

MasterMainTable::new(
aet,
stark.num_trace_randomizers,
quotient_domain,
fri.domain,
stark.num_trace_randomizers,
seed,
)
}

Expand Down
93 changes: 90 additions & 3 deletions triton-vm/src/stark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ use ndarray::Zip;
use num_traits::ConstOne;
use num_traits::ConstZero;
use num_traits::Zero;
use rand::prelude::StdRng;
use rand::random;
use rand_core::SeedableRng;
use rayon::prelude::*;
use serde::Deserialize;
use serde::Serialize;
Expand Down Expand Up @@ -82,9 +85,23 @@ pub struct Stark {
/// The prover for Triton VM's [zk-STARK](Stark). The core method is
/// [`prove`](Prover::prove). It is probably more convenient to call
/// [`Stark::prove`] directly.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Arbitrary)]
///
/// It is possible to [set the randomness seed][seed] used for deriving all
/// prover randomness. To prevent accidental randomness re-use, the [`Prover`]
/// does not implement [`Clone`].
///
/// [seed]: Prover::set_randomness_seed_which_may_break_zero_knowledge
#[expect(missing_copy_implementations)]
#[derive(Debug, Eq, PartialEq, Arbitrary)]
pub struct Prover {
parameters: Stark,

/// The seed for all randomness used while [proving][Stark::prove].
///
/// For Triton VM's proofs to be zero knowledge, this seed must be sampled
/// uniformly at random, and independently of all other input. No information
/// about it must reach the verifier.
randomness_seed: <StdRng as SeedableRng>::Seed,
}

/// The verifier for Triton VM's [zs-STARK](Stark). The core method is
Expand All @@ -96,8 +113,40 @@ pub struct Verifier {
}

impl Prover {
/// A [`Prover`] with a sane [randomness seed][seed].
///
/// [seed]: Prover::set_randomness_seed_which_may_break_zero_knowledge
pub fn new(parameters: Stark) -> Self {
Self { parameters }
Self {
parameters,
randomness_seed: random(),
}
}

/// Manually set the seed for the randomness used during [proving](Self::prove).
/// This makes the generated [proof](Proof) deterministic.
///
/// # WARNING!
///
/// Careless use of this method can break Zero-Knowledge of Triton VM. In
/// particular, the [verifier](Stark::verify) must learn nothing about the
/// supplied seed, must be unable to influence the supplied seed, and must be
/// unable to guess anything about the supplied seed. The latter implies that
/// whatever source of randomness is chosen must have sufficient entropy.
///
/// ### If in doubt, don't use this method.
// Even though this method can be used to disable or cripple one of the
// core promises of Triton VM, it is not marked `unsafe` because it is always
// _memory_ safe. See also: https://doc.rust-lang.org/std/keyword.unsafe.html
//
// The length of the name is intended to discourage use.
#[must_use]
pub fn set_randomness_seed_which_may_break_zero_knowledge(
mut self,
seed: <StdRng as SeedableRng>::Seed,
) -> Self {
self.randomness_seed = seed;
self
}

/// See also [`Stark::prove`].
Expand All @@ -123,9 +172,10 @@ impl Prover {
profiler!(start "create" ("gen"));
let mut master_main_table = MasterMainTable::new(
aet,
self.parameters.num_trace_randomizers,
quotient_domain,
fri.domain,
self.parameters.num_trace_randomizers,
self.randomness_seed,
);
profiler!(stop "create");

Expand Down Expand Up @@ -1667,6 +1717,12 @@ pub(crate) mod tests {
)
}

#[proptest]
fn two_default_provers_have_different_randomness_seeds() {
let seed = || Prover::default().randomness_seed;
prop_assert_ne!(seed(), seed());
}

#[test]
fn quotient_segments_are_independent_of_fri_table_caching() {
// ensure caching _can_ happen by overwriting environment variables
Expand Down Expand Up @@ -1742,6 +1798,37 @@ pub(crate) mod tests {
assert_no_trace_mutation(CacheDecision::NoCache);
}

#[test]
fn supplying_prover_randomness_seed_fully_derandomizes_produced_proof() {
let ProgramAndInput {
program,
public_input,
non_determinism,
} = program_executing_every_instruction();

let claim = Claim::about_program(&program).with_input(public_input.clone());
let (aet, output) = VM::trace_execution(program, public_input, non_determinism).unwrap();
let claim = claim.with_output(output);

let stark = low_security_stark(DEFAULT_LOG2_FRI_EXPANSION_FACTOR_FOR_TESTS);
let mut rng = StdRng::seed_from_u64(3351975627407608972);
let proof = Prover::new(stark)
.set_randomness_seed_which_may_break_zero_knowledge(rng.gen())
.prove(&claim, &aet)
.unwrap();
let digest = Tip5::hash(&proof);
dbg!(digest.to_string());

let expected_digest = Digest::new(bfe_array![
1208218823199023966_u64,
7248217050651886224_u64,
7139898735589794621_u64,
11774487641367625949_u64,
6650915987150064355_u64,
]);
assert_eq!(expected_digest, digest);
}

#[test]
fn print_ram_table_example_for_specification() {
let program = triton_program!(
Expand Down
14 changes: 9 additions & 5 deletions triton-vm/src/table/cascade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ use air::table_column::MasterMainColumn;
use ndarray::s;
use ndarray::ArrayView2;
use ndarray::ArrayViewMut2;
use ndarray::Axis;
use num_traits::ConstOne;
use num_traits::One;
use rayon::prelude::*;
use strum::EnumCount;
use twenty_first::prelude::*;

Expand Down Expand Up @@ -37,19 +39,21 @@ impl TraceTable for CascadeTable {
type FillReturnInfo = ();

fn fill(mut main_table: ArrayViewMut2<BFieldElement>, aet: &AlgebraicExecutionTrace, _: ()) {
for (row_idx, (&to_look_up, &multiplicity)) in
aet.cascade_table_lookup_multiplicities.iter().enumerate()
{
let rows_and_multiplicities = main_table
.axis_iter_mut(Axis(0))
.into_par_iter()
.zip(&aet.cascade_table_lookup_multiplicities);

rows_and_multiplicities.for_each(|(mut row, (&to_look_up, &multiplicity))| {
let to_look_up_lo = (to_look_up & 0xff) as u8;
let to_look_up_hi = ((to_look_up >> 8) & 0xff) as u8;

let mut row = main_table.row_mut(row_idx);
row[MainColumn::LookInLo.main_index()] = bfe!(to_look_up_lo);
row[MainColumn::LookInHi.main_index()] = bfe!(to_look_up_hi);
row[MainColumn::LookOutLo.main_index()] = lookup_8_bit_limb(to_look_up_lo);
row[MainColumn::LookOutHi.main_index()] = lookup_8_bit_limb(to_look_up_hi);
row[MainColumn::LookupMultiplicity.main_index()] = bfe!(multiplicity);
}
});
}

fn pad(mut main_table: ArrayViewMut2<BFieldElement>, cascade_table_length: usize) {
Expand Down
Loading

0 comments on commit 923e9e1

Please sign in to comment.