Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ proptest = { version = "1", default-features = false, features = ["std"] }
serde = { version = "1", features = ["derive"] }
bincode = "1"
rand = "0.9"
rand_chacha = "0.9"
rand_core = { version = "0.9", features = ["os_rng"] }
thiserror = { version = "2", default-features = false }
tikv-jemallocator = "0.7"
tracing = "0.1.26"
Expand Down
85 changes: 84 additions & 1 deletion bench-mobile/Cargo.lock

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

12 changes: 11 additions & 1 deletion bins/leanvm/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use aggregate::LeafProgram;
use bench::Plan;
use clap::builder::RangedU64ValueParser;
use clap::{Parser, Subcommand};
use leanvm::{Prover, Rate};
use leanvm::{Prover, Randomness, Rate};
use std::error::Error;
use std::fmt::Arguments;
use std::num::{NonZeroUsize, ParseIntError};
Expand Down Expand Up @@ -37,6 +37,10 @@ struct Cli {
#[arg(long, global = true)]
tracing: bool,

/// Make zero-knowledge proofs, from fresh randomness the OS gives each proof.
#[arg(long, global = true)]
zk: bool,

/// Measured proving passes after warmup.
#[arg(
long,
Expand Down Expand Up @@ -178,6 +182,11 @@ fn refuse(what: Arguments) -> ! {

fn main() {
let cli = Cli::parse();
if cli.zk && matches!(cli.command, Command::Aggregate { .. } | Command::Bench { .. }) {
refuse(format_args!(
"--zk proves single runs: no aggregation tree verifies a zero-knowledge leaf, and the tracked benchmarks are plain proofs"
));
}
let fixed_threads = match &cli.command {
Command::Bench { cycles_only: true, .. } => false,
Command::Bench { only, .. } => only.as_deref().is_some_and(|name| name.ends_with("-16thread")),
Expand All @@ -197,6 +206,7 @@ fn main() {
);
}
let prover = Prover::new(cli.rate);
let prover = if cli.zk { prover.zk(Randomness::Os) } else { prover };
let leaf_prover = Prover::new(cli.leaf_rate);
let plan = Plan::new(cli.repeat, cli.cooldown);
if cli.tracing {
Expand Down
3 changes: 2 additions & 1 deletion bins/leanvm/src/workload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ impl Workload {

let cycles = stats.cycles();
let seconds = prove_time.mean();
println!("{}", self.title);
let zk = if prover.is_zk() { ", zero knowledge" } else { "" };
println!("{}{zk}", self.title);
println!(
" advice : {} words",
pretty_integer(&self.advice.len())
Expand Down
14 changes: 14 additions & 0 deletions crates/fiat_shamir/src/arith.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ pub trait Arith {
/// `a^(2^128)`: two Frobenius maps of `E` over `K`, which take `a` to `a^(2^-64)`.
fn frobenius2(&mut self, a: Self::E) -> Self::E;

/// The same element, which the caller reuses many times.
///
/// A recording arithmetic may give it a variable of its own, so that what reuses it stays short; by default it is returned as it is.
fn bind(&mut self, a: Self::E) -> Self::E {
a
}

/// The multilinear extension of public `K` words at `point`, lowest coordinate first.
fn public_mle(&mut self, values: &[F64], point: &[Self::E]) -> Self::E {
assert_eq!(values.len(), 1 << point.len(), "a column has a word per vertex");
Expand Down Expand Up @@ -231,6 +238,13 @@ pub trait Verifier: Arith {
f(self)
}

/// Whether the scalars read from now on travel under one-time-pad keys.
///
/// A verifier that holds values reads no padded proof, so by default this does nothing.
fn set_hidden(&mut self, hidden: bool) {
let _ = hidden;
}

/// The next `n` scalars.
///
/// # Errors
Expand Down
59 changes: 58 additions & 1 deletion crates/fiat_shamir/src/transcript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ pub trait Transmitter: Challenger {
fn add_scalars(&mut self, xs: &[F192]);
fn grind(&mut self, bits: u32);

/// Whether the scalars sent from now on travel under one-time-pad keys.
///
/// A prover holding no keys hides nothing.
fn set_hidden(&mut self, hidden: bool);

/// Transmit a root as its two scalars, not as a byte string. Sending it is what binds it: no verifier absorbs a root
/// separately (see [`Receiver::next_root`], its mirror).
fn add_root(&mut self, root: &Hash) {
Expand Down Expand Up @@ -157,6 +162,14 @@ pub struct ProverState {
fs: Duplex,
stream: Vec<F192>,
merkle: Vec<PrunedMerklePaths>,
pad: Option<Pad>,
}

/// One-time-pad keys, one per hidden scalar, used in order.
struct Pad {
keys: Vec<F192>,
next: usize,
hidden: bool,
}

impl ProverState {
Expand All @@ -175,6 +188,29 @@ impl ProverState {
fs,
stream: Vec::new(),
merkle: Vec::new(),
pad: None,
}
}

/// Hide each scalar sent while [`Transmitter::set_hidden`] holds under the next of these keys: the stream and the transcript carry `x + key`.
///
/// # Panics
///
/// If keys were already given.
pub fn set_keys(&mut self, keys: Vec<F192>) {
assert!(self.pad.is_none(), "a prover takes its keys once");
self.pad = Some(Pad {
keys,
next: 0,
hidden: false,
});
}

/// The proof so far, which a verifier replays from the same seed.
pub fn snapshot(&self) -> ProofTranscript {
ProofTranscript {
stream: self.stream.clone(),
merkle: self.merkle.clone(),
}
}

Expand Down Expand Up @@ -269,9 +305,18 @@ impl Transmitter for ProverState {
}

/// Transmit a scalar into the proof AND bind it into the state (the two are
/// inseparable: you cannot send without binding).
/// inseparable: you cannot send without binding). A hidden scalar travels
/// under its key.
#[inline]
fn add_scalar(&mut self, x: F192) {
let x = match &mut self.pad {
Some(pad) if pad.hidden => {
let key = *pad.keys.get(pad.next).expect("a key per hidden scalar");
pad.next += 1;
x + key
}
_ => x,
};
self.fs.observe(x);
self.stream.push(x);
}
Expand All @@ -292,6 +337,18 @@ impl Transmitter for ProverState {
let nonce = self.fs.grind_pow(bits);
self.stream.push(F192::new(nonce, 0, 0));
}

fn set_hidden(&mut self, hidden: bool) {
if let Some(pad) = &mut self.pad {
pad.hidden = hidden;
}
}

/// A root is public: it is never sent under a key.
fn add_root(&mut self, root: &Hash) {
assert!(!self.pad.as_ref().is_some_and(|p| p.hidden), "a root is never hidden");
self.add_scalars(&hash_to_scalars(root));
}
}

impl<'a> Receiver for VerifierState<'a> {
Expand Down
11 changes: 11 additions & 0 deletions crates/flock/src/lincheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,17 @@ impl MatrixForm {
.bilinear_form(self.alpha, &eq_inner, &w_col)
.unwrap_or_else(|| inner_product(&circuit.fold_alpha_batched(self.alpha, &eq_inner), &w_col))
}

/// The form's weight on each bit slice: [`Self::evaluate`] is `sum_i s_hat_v[i] weights[i]`, the form being linear in its slices, and the weights depend on its other, public, coordinates alone.
pub fn slice_weights(&self, circuit: &dyn LincheckCircuit) -> Vec<F192> {
let n_slices = self.s_hat_v.len();
let eq_inner = build_quirky_eq_table(self.z_skip, &self.x_inner_rest, n_slices.ilog2() as usize);
let marginal = circuit.fold_alpha_batched(self.alpha, &eq_inner);
let eq_rest = eq_table(&self.r_inner_rest);
(0..n_slices)
.map(|i| (eq_rest.iter().enumerate()).fold(F192::ZERO, |acc, (j, &e)| acc + e * marginal[j * n_slices + i]))
.collect()
}
}

/// What the deferred replay leaves to the circuit: the terminal identity holds exactly when the form takes the value.
Expand Down
2 changes: 2 additions & 0 deletions crates/leanvm_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pcs.workspace = true
flock.workspace = true
tracing.workspace = true
thiserror.workspace = true
rand_chacha.workspace = true
rand_core.workspace = true

[dev-dependencies]
proptest.workspace = true
Expand Down
Loading
Loading