Skip to content
Open
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
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.

6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ Select the workload with `--circuit`:
`--num-blocks` to set the chain or block-aligned message length in 64-byte blocks.
Use `--threads` to set the number of worker threads.

Use `--pcs-security-bits 100` or `128` to select the PCS round budget; the default is `100`.
The implementation derives internal parameters from the padded witness size.
The `100` target uses list decoding and an initial out-of-domain (OOD) check.
The `128` target uses unique decoding and omits that check.
Spartan still uses `Q100`; this option does not set Spartan security.

## Benchmarks

Run all SHA-256 circuit benchmarks with one Rayon worker:
Expand Down
2 changes: 2 additions & 0 deletions crates/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod claim;
pub mod fold;
pub mod opening;
pub mod params;
pub mod security;
pub mod shape;
pub mod table;
pub mod virtual_map;
Expand All @@ -18,6 +19,7 @@ pub use fold::{
};
pub use opening::OpeningQuery;
pub use params::{BitZParams, ParamsError, VirtualParams, VirtualParamsError};
pub use security::SecurityLevel;
pub use shape::{Shape, ShapeError};
pub use table::{BitTable, TableError, TransposeError, TransposedBitTable};
pub use virtual_map::{
Expand Down
47 changes: 47 additions & 0 deletions crates/common/src/security.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! Shared protocol security targets and grinding requirements.

/// The target for each classical PCS challenge block over `F128`.
///
/// This target does not certify the complete protocol or quantum security.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SecurityLevel {
Bits100,
Bits128,
}

impl SecurityLevel {
/// Returns the classical security target in bits.
pub const fn bits(self) -> u32 {
match self {
Self::Bits100 => 100,
Self::Bits128 => 128,
}
}

/// Returns `ceil(log2(coefficient)) + target - 128`, bounded below by zero.
///
/// This covers a challenge block with error at most `coefficient / 2^128`.
/// A zero coefficient needs no grinding.
pub const fn grinding_bits(self, coefficient: usize) -> u32 {
if coefficient == 0 {
return 0;
}
let log_coefficient = usize::BITS - (coefficient - 1).leading_zeros();
(self.bits() + log_coefficient).saturating_sub(128)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn grinding_covers_the_integer_error_coefficient() {
for (coefficient, expected) in [(0, 0), (1, 0), (2, 1), (3, 2), (7, 3), (8, 3), (15, 4)] {
assert_eq!(SecurityLevel::Bits128.grinding_bits(coefficient), expected);
assert_eq!(SecurityLevel::Bits100.grinding_bits(coefficient), 0);
}
assert_eq!(SecurityLevel::Bits100.grinding_bits(1 << 28), 0);
assert_eq!(SecurityLevel::Bits100.grinding_bits((1 << 28) + 1), 1);
}
}
16 changes: 6 additions & 10 deletions crates/common/src/shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,8 @@
/// packed field element carries.
pub const PACK_BITS: u32 = 7;

/// The commitment size window the opening parameters are fixed for.
///
/// `22..=35` is not derived from a security bound here; it is the range
/// `flock-core`'s Ligerito configs are precomputed for (one shipped TOML per
/// `m` in that range, per profile). Sizes outside it have no config to load.
pub const MIN_LOG_BITS: usize = 22;
/// The size window for dynamically derived opening parameters.
pub const MIN_LOG_BITS: usize = 20;
/// The upper end of that window.
pub const MAX_LOG_BITS: usize = 35;

Expand All @@ -19,7 +15,7 @@ pub enum ShapeError {
/// Fewer than seven row-index bits: a packed row would not fill one
/// codeword position. The paper writes this count `t`.
RowIndexTooNarrow,
/// The total bit count falls outside `2^22..=2^35`.
/// The total bit count falls outside `2^20..=2^35`.
CommitmentSizeOutOfRange,
}

Expand Down Expand Up @@ -118,8 +114,8 @@ mod tests {

#[test]
fn rejects_a_commitment_size_outside_the_window() {
// m = 21, then m = 36.
assert_eq!(Shape::new(7, 14), Err(ShapeError::CommitmentSizeOutOfRange));
// m = 19, then m = 36.
assert_eq!(Shape::new(7, 12), Err(ShapeError::CommitmentSizeOutOfRange));
assert_eq!(
Shape::new(13, 23),
Err(ShapeError::CommitmentSizeOutOfRange)
Expand Down Expand Up @@ -157,7 +153,7 @@ mod tests {

#[test]
fn accepts_the_window_boundaries() {
assert_eq!(Shape::new(7, 15).unwrap().log_bits(), MIN_LOG_BITS);
assert_eq!(Shape::new(7, 13).unwrap().log_bits(), MIN_LOG_BITS);
assert_eq!(Shape::new(14, 21).unwrap().log_bits(), MAX_LOG_BITS);
}
}
1 change: 1 addition & 0 deletions crates/gkr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ rust-version.workspace = true
license.workspace = true

[dependencies]
common = { workspace = true }
field = { path = "../field", features = ["spongefish"] }
transcript = {path ="../transcript"}
tracing = { workspace = true }
Expand Down
75 changes: 68 additions & 7 deletions crates/gkr/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::VecDeque;

use common::SecurityLevel;
use field::{F128, Wide256};
use num_traits::{ConstOne, ConstZero};
use rayon::prelude::*;
Expand All @@ -9,6 +10,8 @@ pub type Field = F128;

type Point = VecDeque<Field>;

const CUBIC_GRINDING_LABEL: &[u8] = b"gkr/cubic/v1";

/// Proves the layer-by-layer sumcheck reduction from a claim at `point`
/// (an evaluation point on the output layer) down to a claim on the leaves.
// TODO #[must_use], requires changing the test suite
Expand All @@ -19,6 +22,7 @@ pub fn gpgkr_prove(
point: &[F128],
// All the intermediate witnesses + the input layer. Doesn't contain the output layer
witnesses: LayerWitnesses,
security: SecurityLevel,
) -> (Vec<F128>, Field) {
// Edge cases
// - empty witnesses -> single constant circuit -> one verifier message that permutes the proof state, but a single constant can't have an MLE
Expand All @@ -30,7 +34,7 @@ pub fn gpgkr_prove(
let mut claim = Field::ZERO;
let mut storage = SuffixTable::alloc_storage(log_bits);
for wnext in witnesses.into_iter() {
(point, claim) = prove_layer(ps, &mut storage, point, wnext);
(point, claim) = prove_layer(ps, &mut storage, point, wnext, security);
}

let mut point = Vec::from(point);
Expand All @@ -43,6 +47,7 @@ fn prove_layer(
storage: &mut [Field],
mut point: Point,
mut wnext: Vec<Field>,
security: SecurityLevel,
) -> (Point, Field) {
let suffix_table = SuffixTable::new(storage, &point);
let mut factor = Field::ONE;
Expand Down Expand Up @@ -82,6 +87,7 @@ fn prove_layer(

ps.prover_message(&[factor * sum_endpoint.reduce(), factor * sum_inf.reduce()]);

ps.grind(CUBIC_GRINDING_LABEL, security.grinding_bits(3));
let r = ps.verifier_message();
next_point.push_back(r);

Expand Down Expand Up @@ -127,6 +133,7 @@ fn prove_layer(

ps.prover_message(&[factor * sum_endpoint.reduce(), factor * sum_inf.reduce()]);

ps.grind(CUBIC_GRINDING_LABEL, security.grinding_bits(3));
let r = ps.verifier_message();
next_point.push_back(r);

Expand Down Expand Up @@ -268,6 +275,7 @@ pub fn gpgkr_verify(
mut claim: Field,
point: &[F128],
rounds: u32,
security: SecurityLevel,
) -> Option<(Vec<F128>, Field)> {
// Edge cases around input lenghts, 0 meaning empty
// | circuit | last value |
Expand All @@ -284,7 +292,7 @@ pub fn gpgkr_verify(
let mut point = VecDeque::from(point);

for _i in 0..rounds {
(point, claim) = verify_layer(vs, claim, point)?
(point, claim) = verify_layer(vs, claim, point, security)?
}

let mut point = Vec::from(point);
Expand All @@ -294,7 +302,12 @@ pub fn gpgkr_verify(
}

/// Point's orientation is the reverse of gpgkr_verify
fn verify_layer(vs: &mut VerifierState, mut claim: Field, point: Point) -> Option<(Point, Field)> {
fn verify_layer(
vs: &mut VerifierState,
mut claim: Field,
point: Point,
security: SecurityLevel,
) -> Option<(Point, Field)> {
let mut prefix = Field::ONE;

let mut next_point: Point = VecDeque::new();
Expand All @@ -310,6 +323,8 @@ fn verify_layer(vs: &mut VerifierState, mut claim: Field, point: Point) -> Optio
(sum_endpoint, (claim - eqjsum0) / z)
};

vs.grind(CUBIC_GRINDING_LABEL, security.grinding_bits(3))
.ok()?;
let r = vs.verifier_message();
next_point.push_back(r);
let factor = eq_factor(r, z);
Expand Down Expand Up @@ -393,6 +408,7 @@ impl IntoIterator for LayerWitnesses {
#[cfg(test)]
mod tests {
use super::*;
use common::SecurityLevel::{Bits100, Bits128};
use proptest::prelude::*;

fn field() -> impl Strategy<Value = Field> {
Expand Down Expand Up @@ -500,6 +516,50 @@ mod tests {
}
}

#[test]
fn gpgkr_security_targets_replay_and_reject_changed_nonces() {
let leaves: Vec<Field> = (1u128..=16).map(Field::from).collect();
let point = [Field::from(5u128), Field::from(7u128)];
let instance = (leaves.clone(), point.to_vec());
let mut unground_len = 0;

for security in [Bits100, Bits128] {
let circuit = GrandProductCircuit::new(leaves.clone());
let (output, witnesses) = circuit.batched_eval(4);
let claim = mle(output, &point);
let mut prover = transcript::build_prover("gkr-security", &instance);
let terminal = gpgkr_prove(
&mut prover,
leaves.len().ilog2() as usize,
&point,
witnesses,
security,
);
assert_eq!(terminal.1, mle(leaves.clone(), &terminal.0));
let mut proof = prover.finish();

let mut verifier = transcript::build_verifier("gkr-security", &instance, &proof);
assert_eq!(
gpgkr_verify(&mut verifier, claim, &point, 2, security),
Some(terminal)
);
verifier.check_eof().unwrap();

match security {
Bits100 => unground_len = proof.narg_string.len(),
Bits128 => {
// Two layers have five cubic challenges and five eight-byte nonces.
assert_eq!(proof.narg_string.len(), unground_len + 40);
// The first cubic message occupies two canonical field elements.
proof.narg_string[32] ^= 1;
let mut verifier =
transcript::build_verifier("gkr-security", &instance, &proof);
assert!(gpgkr_verify(&mut verifier, claim, &point, 2, security).is_none());
}
}
}
}

#[test]
fn gpgkr_round_trip_with_zero_coordinates() {
let leaves: Vec<Field> = (1u128..=16).map(Field::from).collect();
Expand All @@ -521,19 +581,20 @@ mod tests {
leaves.len().ilog2() as usize,
&point,
witnesses,
Bits100,
);
assert_eq!(terminal.1, mle(leaves.clone(), &terminal.0));
let proof = prover.finish();

let mut verifier = transcript::build_verifier("gkr-zero", &instance, &proof);
assert_eq!(
gpgkr_verify(&mut verifier, claim, &point, 2),
gpgkr_verify(&mut verifier, claim, &point, 2, Bits100),
Some(terminal)
);
verifier.check_eof().unwrap();

let mut verifier = transcript::build_verifier("gkr-zero", &instance, &proof);
assert!(gpgkr_verify(&mut verifier, claim + Field::ONE, &point, 2).is_none());
assert!(gpgkr_verify(&mut verifier, claim + Field::ONE, &point, 2, Bits100).is_none());
}
}

Expand Down Expand Up @@ -576,7 +637,7 @@ mod tests {
let log_groups = last_value.len().max(1).ilog2();
let point: Vec<Field> = (0..log_groups).map(|_| prover.verifier_message()).collect();

gpgkr_prove(&mut prover, log_bits, &point, witnesses);
gpgkr_prove(&mut prover, log_bits, &point, witnesses, Bits100);
(last_value, prover.finish())
}

Expand All @@ -595,7 +656,7 @@ mod tests {
let log_leafs = circuit.leafs.len().max(1).ilog2();
let rounds = log_leafs.saturating_sub(log_groups);

match gpgkr_verify(&mut verifier, claim, &point, rounds) {
match gpgkr_verify(&mut verifier, claim, &point, rounds, Bits100) {
Some((point, claim)) => {
let leaf_check = mle(circuit.leafs, &point);

Expand Down
Loading
Loading