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 Oct 30, 2024
1 parent 2f5b7a9 commit adfc20a
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 55 deletions.
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ 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"] }
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
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![
7646759784856898744_u64,
16007626009383034779_u64,
11608121746093262393_u64,
9185992315272057002_u64,
11976510896481695939_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![
140613996981846773_u64,
9443235005377950615_u64,
7631096391962691143_u64,
4837056396945632607_u64,
4643103402788456518_u64,
]);
assert_eq!(expected_digest, digest);
}

#[test]
fn print_ram_table_example_for_specification() {
let program = triton_program!(
Expand Down
8 changes: 5 additions & 3 deletions triton-vm/src/table/cascade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use air::cross_table_argument::LookupArg;
use air::table::cascade::CascadeTable;
use air::table_column::MasterAuxColumn;
use air::table_column::MasterMainColumn;
use itertools::Itertools;
use ndarray::s;
use ndarray::ArrayView2;
use ndarray::ArrayViewMut2;
Expand Down Expand Up @@ -37,9 +38,10 @@ 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()
{
// sort to make this function deterministic; not needed for correctness
let lookup_multiplicities = aet.cascade_table_lookup_multiplicities.iter().sorted();

for (row_idx, (&to_look_up, &multiplicity)) in lookup_multiplicities.enumerate() {
let to_look_up_lo = (to_look_up & 0xff) as u8;
let to_look_up_hi = ((to_look_up >> 8) & 0xff) as u8;

Expand Down
41 changes: 23 additions & 18 deletions triton-vm/src/table/master_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ use num_traits::Zero;
use rand::distributions::Standard;
use rand::prelude::Distribution;
use rand::prelude::StdRng;
use rand::random;
use rand::Rng;
use rand_core::SeedableRng;
use strum::EnumCount;
Expand Down Expand Up @@ -819,9 +818,10 @@ type ExtendFunction = fn(ArrayView2<BFieldElement>, ArrayViewMut2<XFieldElement>
impl MasterMainTable {
pub fn new(
aet: &AlgebraicExecutionTrace,
num_trace_randomizers: usize,
quotient_domain: ArithmeticDomain,
fri_domain: ArithmeticDomain,
num_trace_randomizers: usize,
trace_randomizer_seed: <StdRng as SeedableRng>::Seed,
) -> Self {
let padded_height = aet.padded_height();
let num_rows = randomized_trace_len(padded_height, num_trace_randomizers);
Expand All @@ -848,7 +848,7 @@ impl MasterMainTable {
quotient_domain,
fri_domain,
trace_table,
trace_randomizer_seed: random(),
trace_randomizer_seed,
low_degree_extended_table: None,
};

Expand Down Expand Up @@ -888,7 +888,7 @@ impl MasterMainTable {
pub fn pad(&mut self) {
let table_lengths = self.all_table_lengths();

let main_tables: [_; TableId::COUNT] = horizontal_multi_slice_mut(
let tables: [_; TableId::COUNT] = horizontal_multi_slice_mut(
self.trace_table.view_mut(),
&partial_sums(&[
ProgramMainColumn::COUNT,
Expand Down Expand Up @@ -917,13 +917,12 @@ impl MasterMainTable {
LookupTable::pad,
U32Table::pad,
];

all_pad_functions
.into_par_iter()
.zip_eq(main_tables.into_par_iter())
.zip_eq(table_lengths.into_par_iter())
.for_each(|((pad, main_table), table_length)| {
pad(main_table, table_length);
});
.zip_eq(tables)
.zip_eq(table_lengths)
.for_each(|((pad, table), table_length)| pad(table, table_length));
profiler!(stop "pad original tables");

profiler!(start "fill degree-lowering table");
Expand Down Expand Up @@ -952,26 +951,32 @@ impl MasterMainTable {
/// table. The `.extend()` for each table is specific to that table, but always involves
/// adding some number of columns.
pub fn extend(&self, challenges: &Challenges) -> MasterAuxTable {
// construct a seed that hasn't been used for any column's trace randomizer
let mut seed = self.trace_randomizer_seed;
for (seed_byte, offset) in seed.iter_mut().zip(Self::NUM_COLUMNS.to_le_bytes()) {
*seed_byte = seed_byte.wrapping_add(offset);
}
let mut rng = StdRng::from_seed(seed);

profiler!(start "initialize master table");
// column majority (“`F`”) for contiguous column slices
let mut aux_trace_table = ndarray_helper::par_zeros(
(self.trace_table().nrows(), MasterAuxTable::NUM_COLUMNS).f(),
);
let aux_trace_table_shape = (self.trace_table().nrows(), MasterAuxTable::NUM_COLUMNS).f();
let mut aux_trace_table = ndarray_helper::par_zeros(aux_trace_table_shape);

let randomizers_start = MasterAuxTable::NUM_COLUMNS - NUM_RANDOMIZER_POLYNOMIALS;
aux_trace_table
.slice_mut(s![.., randomizers_start..])
.par_mapv_inplace(|_| random());
.mapv_inplace(|_| rng.gen());
profiler!(stop "initialize master table");

let mut master_aux_table = MasterAuxTable {
num_trace_randomizers: self.num_trace_randomizers,
trace_domain: self.trace_domain(),
randomized_trace_domain: self.randomized_trace_domain(),
quotient_domain: self.quotient_domain(),
fri_domain: self.fri_domain(),
trace_domain: self.trace_domain,
randomized_trace_domain: self.randomized_trace_domain,
quotient_domain: self.quotient_domain,
fri_domain: self.fri_domain,
trace_table: aux_trace_table,
trace_randomizer_seed: random(),
trace_randomizer_seed: rng.gen(),
low_degree_extended_table: None,
};

Expand Down
Loading

0 comments on commit adfc20a

Please sign in to comment.