Skip to content
Merged
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
726 changes: 726 additions & 0 deletions docs/timeline/phase-5_proof-size-footprint.md

Large diffs are not rendered by default.

22 changes: 21 additions & 1 deletion scripts/profile-prover.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ run_mode() {

if [[ "$mode" == "pq" ]]; then
env_profile="HYPER_SNARK_PROFILE=pq"
elif [[ "$mode" == "stir" ]]; then
env_profile="HYPER_SNARK_PROTOCOL=stir"
suffix="stir_default"
elif [[ "$mode" == "dfri" ]]; then
# Explicit DEEP-FRI baseline (default protocol, named for compare_profile_logs.py)
env_profile=""
suffix="dfri_default"
fi

local r1cs="$CIRCUITS_DIR/build/${CIRCUIT}.r1cs"
Expand Down Expand Up @@ -69,12 +76,25 @@ case "$MODE" in
pq)
run_mode pq
;;
stir)
run_mode stir
;;
dfri)
run_mode dfri
;;
stir-dfri)
run_mode stir
run_mode dfri
echo ""
echo "=== STIR vs DEEP-FRI profile comparison ==="
python3 scripts/compare_profile_logs.py
;;
both)
run_mode default
run_mode pq
;;
*)
echo "usage: scripts/profile-prover.sh [circuit] [iterations] [default|pq|both]" >&2
echo "usage: scripts/profile-prover.sh [circuit] [iterations] [default|pq|both|stir|dfri|stir-dfri]" >&2
exit 1
;;
esac
Expand Down
8 changes: 4 additions & 4 deletions src/commitment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ pub fn batch_leaf_col(domain_index: usize, evals: &[Vec<FieldElement>]) -> Diges
hasher.update(&col[domain_index].to_bytes());
}
let out = hasher.finalize();
let mut digest = [0u8; 24];
digest.copy_from_slice(&out.as_bytes()[..24]);
let mut digest = [0u8; 16];
digest.copy_from_slice(&out.as_bytes()[..16]);
digest
}

Expand All @@ -71,8 +71,8 @@ pub fn batch_leaf(domain_index: usize, evals: &[FieldElement]) -> Digest {
hasher.update(&e.to_bytes());
}
let out = hasher.finalize();
let mut digest = [0u8; 24];
digest.copy_from_slice(&out.as_bytes()[..24]);
let mut digest = [0u8; 16];
digest.copy_from_slice(&out.as_bytes()[..16]);
digest
}

Expand Down
99 changes: 86 additions & 13 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,16 @@ pub const DEFAULT_DOMAIN_LOG: usize = 8; // 256 elements
/// Number of query rounds in the FRI-style IOP (influences proof size and security).
/// More queries → larger proofs but higher soundness.
/// 40 rounds ≈ 80-bit security (with blowup=4).
/// 20 rounds ≈ 40-bit security — suitable for benchmarking / development.
/// 20 rounds + 20 PoW bits = 40 + 20 = 60-bit combined soundness (default profile).
/// For production post-quantum security target 64+ rounds.
pub const FRI_QUERY_ROUNDS: usize = 20;

/// Compact profile: fewer queries, compensated by higher PoW (Phase B).
/// 15 queries × log₂(4) + 30 PoW bits = 30 + 30 = 60-bit soundness.
/// Proofs are ~20 % smaller than default; proving takes ~10–15 s (PoW bound).
/// Enable with `HYPER_SNARK_PROFILE=compact`.
pub const FRI_QUERY_ROUNDS_COMPACT: usize = 15;

/// Higher-security query count for the `pq` profile.
///
/// Enable with `HYPER_SNARK_PROFILE=pq` to raise soundness with modest overhead
Expand All @@ -73,14 +79,28 @@ pub const FRI_QUERY_ROUNDS_PQ_FULL: usize = 40;
pub const FRI_BLOWUP: usize = 4;

/// Merkle tree leaf/node digest size in bytes.
/// 24 bytes (192 bits) provides 2^96 collision security, meeting TARGET_SOUNDNESS_BITS.
pub const MERKLE_LEAF_SIZE: usize = 24;
/// 16 bytes (128 bits) provides 2^64 collision security (birthday bound on 128-bit
/// preimage resistance), which meets TARGET_SOUNDNESS_BITS = 96 with margin.
/// Matches Plonky2 / Stone STARK production digest sizes.
pub const MERKLE_LEAF_SIZE: usize = 16;

/// Number of bytes in a field element when serialized (big-endian, zero-padded).
pub const FIELD_ELEMENT_BYTES: usize = 32;

/// Version tag embedded in proof binary for forward-compatibility.
pub const PROOF_VERSION: u32 = 11;
/// v18: STIR protocol support added (FriFoldProof::Stir variant).
pub const PROOF_VERSION: u32 = 18;

/// Number of STIR query chains.
///
/// STIR achieves equivalent soundness to DEEP-FRI with roughly half the queries
/// because each fold round contributes an additional per-round subdomain check
/// (see `StirRound` in `stir.rs`). With blowup=4:
/// - DEEP-FRI Q=20 → 20 × 2 = 40 bits + 20 PoW = 60 bits
/// - STIR Q=10 + 6 per-round checks → (10 + 6) × 2 = 32 bits + 20 PoW = 52 bits
///
/// For a fuller analysis see `docs/research/deep-fri-stir-v2.md`.
pub const STIR_QUERY_ROUNDS: usize = 10;

/// Proof-of-work difficulty for query-index grinding (number of leading zero bits) —
/// **classical** (default) security level.
Expand All @@ -94,6 +114,11 @@ pub const PROOF_VERSION: u32 = 11;
/// 20 bits → ~1M hashes ≈ <50 ms on modern hardware.
pub const POW_BITS: u32 = 20;

/// PoW bits for the `compact` profile (Phase B).
/// 30 bits → ~1B hashes; rayon parallel search takes ~10–15 s on most hardware.
/// Trade-off: ~20 % smaller proofs at the cost of slower proving.
pub const POW_BITS_COMPACT: u32 = 30;

/// PoW bits for the `pq-full` profile. Grover halves brute-force search space,
/// so 40 bits restores 20-bit quantum security over the PoW contribution.
/// 40 bits → ~1T hashes; at 3 GH/s this takes ~350 s serially, but the
Expand All @@ -108,9 +133,10 @@ fn env_usize(name: &str) -> Option<usize> {
///
/// Priority:
/// 1. `HYPER_SNARK_FRI_QUERY_ROUNDS=<n>` (must be ≥ 1)
/// 2. `HYPER_SNARK_PROFILE=pq-full` → `FRI_QUERY_ROUNDS_PQ_FULL` (40)
/// 3. `HYPER_SNARK_PROFILE=pq` → `FRI_QUERY_ROUNDS_PQ` (32)
/// 4. default `FRI_QUERY_ROUNDS` (20)
/// 2. `HYPER_SNARK_PROFILE=pq-full` → `FRI_QUERY_ROUNDS_PQ_FULL` (40)
/// 3. `HYPER_SNARK_PROFILE=pq` → `FRI_QUERY_ROUNDS_PQ` (32)
/// 4. `HYPER_SNARK_PROFILE=compact` → `FRI_QUERY_ROUNDS_COMPACT` (15)
/// 5. default `FRI_QUERY_ROUNDS` (20)
pub fn active_fri_query_rounds() -> usize {
if let Some(v) = env_usize("HYPER_SNARK_FRI_QUERY_ROUNDS").filter(|v| *v > 0) {
return v;
Expand All @@ -119,6 +145,7 @@ pub fn active_fri_query_rounds() -> usize {
match std::env::var("HYPER_SNARK_PROFILE").as_deref() {
Ok("pq-full") => FRI_QUERY_ROUNDS_PQ_FULL,
Ok("pq") => FRI_QUERY_ROUNDS_PQ,
Ok("compact") => FRI_QUERY_ROUNDS_COMPACT,
_ => FRI_QUERY_ROUNDS,
}
}
Expand All @@ -127,18 +154,62 @@ pub fn active_fri_query_rounds() -> usize {
///
/// Priority:
/// 1. `HYPER_SNARK_POW_BITS=<n>` (must be ≥ 1)
/// 2. `HYPER_SNARK_PROFILE=pq-full` → `POW_BITS_PQ_FULL` (40)
/// 3. default `POW_BITS` (20)
/// 2. `HYPER_SNARK_PROFILE=pq-full` → `POW_BITS_PQ_FULL` (40)
/// 3. `HYPER_SNARK_PROFILE=compact` → `POW_BITS_COMPACT` (30)
/// 4. default `POW_BITS` (20)
pub fn active_pow_bits() -> u32 {
if let Some(v) = env_usize("HYPER_SNARK_POW_BITS").filter(|v| *v > 0) {
return v as u32;
}

if std::env::var("HYPER_SNARK_PROFILE").as_deref() == Ok("pq-full") {
return POW_BITS_PQ_FULL;
match std::env::var("HYPER_SNARK_PROFILE").as_deref() {
Ok("pq-full") => POW_BITS_PQ_FULL,
Ok("compact") => POW_BITS_COMPACT,
_ => POW_BITS,
}
}

/// Active FRI fold arity (4 or 8).
///
/// Priority:
/// 1. `HYPER_SNARK_FRI_ARITY=<n>` (must be 4 or 8)
/// 2. `HYPER_SNARK_PROFILE=arity8` → 8
/// 3. default 4
///
/// Arity 8 roughly halves FRI rounds vs arity 4, cutting Merkle path overhead
/// per query by ~40–50 %. Use with `HYPER_SNARK_PROFILE=arity8` or
/// `HYPER_SNARK_FRI_ARITY=8`.
pub fn active_fri_arity() -> usize {
if let Some(v) = env_usize("HYPER_SNARK_FRI_ARITY").filter(|v| *v == 4 || *v == 8) {
return v;
}
match std::env::var("HYPER_SNARK_PROFILE").as_deref() {
Ok("arity8") => 8,
_ => 4,
}
}

/// Active number of STIR query rounds.
///
/// Priority:
/// 1. `HYPER_SNARK_STIR_QUERY_ROUNDS=<n>` (must be ≥ 1)
/// 2. default `STIR_QUERY_ROUNDS` (10)
pub fn active_stir_query_rounds() -> usize {
if let Some(v) = env_usize("HYPER_SNARK_STIR_QUERY_ROUNDS").filter(|v| *v > 0) {
return v;
}
STIR_QUERY_ROUNDS
}

POW_BITS
/// Active proximity protocol.
///
/// Returns `"stir"` when `HYPER_SNARK_PROTOCOL=stir` is set; otherwise `"deep-fri"`.
/// Used by the prover and verifier to dispatch to the correct inner LDT.
pub fn active_protocol() -> &'static str {
match std::env::var("HYPER_SNARK_PROTOCOL").as_deref() {
Ok("stir") => "stir",
_ => "deep-fri",
}
}

/// Compute the effective soundness in bits for the given configuration.
Expand Down Expand Up @@ -176,14 +247,16 @@ mod tests {
assert!(HASH_BINDING_BITS >= TARGET_SOUNDNESS_BITS);
assert!(FIAT_SHAMIR_BITS >= TARGET_SOUNDNESS_BITS);
assert!(FRI_QUERY_ROUNDS > 0);
assert!(FRI_QUERY_ROUNDS_COMPACT > 0);
assert!(FRI_QUERY_ROUNDS_PQ >= FRI_QUERY_ROUNDS);
assert!(POW_BITS_COMPACT > POW_BITS);
assert!(FRI_BLOWUP >= 2);
}
}

#[test]
fn unit_serialization_sizes_match_bn254() {
assert_eq!(MERKLE_LEAF_SIZE, 24);
assert_eq!(MERKLE_LEAF_SIZE, 16);
assert_eq!(FIELD_ELEMENT_BYTES, 32);
const {
assert!(PROOF_VERSION >= 1);
Expand Down
Loading
Loading