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
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 crates/pcs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ post_gkr = { workspace = true }
transcript = { workspace = true }
tracing = { workspace = true }
num-traits = { workspace = true }
poly = { workspace = true }
rayon = { workspace = true }

[dev-dependencies]
divan = { workspace = true }
Expand Down
39 changes: 1 addition & 38 deletions crates/pcs/src/challenger.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Flock challenger adapters over the project transcript.

use crate::bridge::{as_flock_f128, from_flock_f128};
use crate::pow::{find as find_pow, valid as pow_valid};
use field::F128 as LocalF128;
use flock_core::challenger::Challenger;
use flock_core::field::F128 as FlockF128;
Expand Down Expand Up @@ -232,44 +233,6 @@ impl Challenger for VerifierChallenger<'_, '_> {
}
}

/// todo: parallel pow? use potentially spongefish?
fn find_pow(seed: &[u8; 16], bits: u32) -> u64 {
if bits == 0 {
return 0;
}
let mut nonce = 0u64;
loop {
if pow_valid(seed, nonce, bits) {
return nonce;
}
nonce = nonce.checked_add(1).expect("proof-of-work nonce exhausted");
}
}

fn pow_valid(seed: &[u8; 16], nonce: u64, bits: u32) -> bool {
if bits == 0 {
return nonce == 0;
}
let mut hasher = blake3::Hasher::new();
hasher.update(b"bitz-pcs-pow-v1");
hasher.update(seed);
hasher.update(&nonce.to_le_bytes());
let digest = hasher.finalize();
leading_zero_bits(digest.as_bytes()) >= bits
}

fn leading_zero_bits(bytes: &[u8]) -> u32 {
let mut total = 0;
for byte in bytes {
let zeros = byte.leading_zeros();
total += zeros;
if zeros != 8 {
break;
}
}
total
}

#[cfg(test)]
mod tests {
use proptest::prelude::*;
Expand Down
146 changes: 129 additions & 17 deletions crates/pcs/src/commitment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@

use core::mem::size_of;

use crate::VerifyError;
use crate::bridge::as_flock_f128s;
use crate::ligerito::CheckedLigerito;
use crate::ood::{OodClaim, prove, verify};
use crate::profiles::{ood_grinding_bits, security_config};
use common::{Root, Shape};
use field::F128;
pub use flock_core::hash::HashKind;
use flock_core::pcs::Commitment as FlockCommitment;
use flock_core::pcs::ligerito::LigeritoProfile;
use flock_core::pcs::{PcsParams, ProverData as FlockProverData};
use transcript::Encoding;
use flock_core::pcs::{PcsParams, ProverData as FlockProverData, commit};
use transcript::{Encoding, ProverState, VerifierState};

/// Errors from PCS configuration.
#[derive(Clone, Debug, PartialEq, Eq)]
Expand All @@ -38,16 +41,44 @@ pub enum CommitError {
pub struct Pcs {
params: PcsParams,
checked_ligerito: CheckedLigerito,
ood_grinding_bits: Option<u32>,
bit_len: usize,
packed_len: usize,
}

/// Flock state retained between commitment and openings.
/// Commitment and private Flock data retained for proving openings.
pub struct ProverData {
commitment: FlockCommitment,
commitment: Commitment,
flock_prover_data: FlockProverData,
}

/// Root, parameters, and optional out-of-domain claim retained after commitment.
/// Both prover and verifier use this state for openings on the commitment transcript.
/// The verifier authenticates the claim when [`CommitScheme::verify_lin`](crate::CommitScheme::verify_lin) succeeds.
#[derive(Debug)]
pub struct Commitment {
flock: FlockCommitment,
pub(crate) ood: Option<OodClaim>,
}

impl Commitment {
/// Returns the public commitment root.
pub fn root(&self) -> Root {
Root(self.flock.root)
}

pub(crate) fn matches(&self, pcs: &Pcs) -> bool {
let expected = pcs.params();
let actual = &self.flock.params;
expected.m == actual.m
&& expected.log_inv_rate == actual.log_inv_rate
&& expected.log_batch_size == actual.log_batch_size
&& expected.profile == actual.profile
&& expected.merkle_hash == actual.merkle_hash
&& self.ood.is_some() == pcs.ood_grinding_bits().is_some()
}
}

impl Pcs {
pub fn new(
shape: &Shape,
Expand All @@ -61,7 +92,7 @@ impl Pcs {
// The ladder fixes the L0 interleaving: the commit must use the same
// `log_batch_size` as the opening's `initial_k`, or the L0 tree is not
// reusable as Ligerito's first oracle.
let security = crate::profiles::security_config(m, security_profile, merkle_hash)?;
let security = security_config(m, security_profile, merkle_hash)?;
let params = PcsParams {
m,
log_inv_rate: security_profile.log_inv_rate(),
Expand All @@ -70,43 +101,74 @@ impl Pcs {
merkle_hash,
};
let checked_ligerito = CheckedLigerito::new(&params, &security)?;
let ood_grinding_bits = ood_grinding_bits(&security, checked_ligerito.log_n_u32() as usize);
let packed_len = 1usize
.checked_shl(checked_ligerito.log_n_u32())
.ok_or(ConfigError::Invalid("packed length overflow"))?;

Ok(Self {
params,
checked_ligerito,
ood_grinding_bits,
bit_len,
packed_len,
})
}

/// Commits to the exact configured number of packed field elements.
/// Commits and samples the initial OOD claim when the security profile requires it.
/// Call before witness-dependent challenges and continue with the same transcript.
#[tracing::instrument(name = "Commit witness", skip_all)]
pub fn commit(&self, packed_witness: &[F128]) -> Result<(Root, ProverData), CommitError> {
pub fn commit(
&self,
packed_witness: &[F128],
transcript: &mut ProverState,
) -> Result<(Root, ProverData), CommitError> {
// 1. Input Validation
if packed_witness.len() != self.packed_len() {
return Err(CommitError::PackedWitnessLengthMismatch);
}

// 2. Commit Packed Witness
let (flock_commitment, flock_prover_data) =
flock_core::pcs::commit(as_flock_f128s(packed_witness), &self.params);
commit(as_flock_f128s(packed_witness), &self.params);

// 3. Build Public Commitment
let commitment = Root(flock_commitment.root);
let root = Root(flock_commitment.root);
let ood = prove(self, &root.0, packed_witness, transcript);

// 4. Retain Opening Data
Ok((
commitment,
root,
ProverData {
commitment: flock_commitment,
commitment: Commitment {
flock: flock_commitment,
ood,
},
flock_prover_data,
},
))
}

/// Receives the OOD claim for the public root before subsequent protocol challenges.
///
/// Mirrors [`Self::commit`]. Invalid grinding or a truncated evaluation
/// returns [`VerifyError::MalformedProof`]; authentication of the evaluation is
/// deferred to [`CommitScheme::verify_lin`](crate::CommitScheme::verify_lin).
pub fn receive_commitment(
&self,
root: Root,
transcript: &mut VerifierState<'_>,
) -> Result<Commitment, VerifyError> {
let ood = verify(self, &root.0, transcript)?;
Ok(Commitment {
flock: FlockCommitment {
root: root.0,
params: self.params.clone(),
},
ood,
})
}

pub fn bit_len(&self) -> usize {
self.bit_len
}
Expand All @@ -120,6 +182,10 @@ impl Pcs {
&self.params
}

pub(crate) fn ood_grinding_bits(&self) -> Option<u32> {
self.ood_grinding_bits
}

pub(crate) fn prover_config(&self) -> &flock_core::pcs::ligerito::ProverConfig {
self.checked_ligerito.prover_config()
}
Expand Down Expand Up @@ -167,14 +233,15 @@ impl ProverData {

/// The commitment this data opens against.
pub fn root(&self) -> Root {
Root(self.commitment.root)
self.commitment.root()
}

pub(crate) fn flock_data(&self) -> &FlockProverData {
&self.flock_prover_data
}

pub(crate) fn commitment(&self) -> &FlockCommitment {
/// Returns the shared commitment without the private proving data.
pub fn commitment(&self) -> &Commitment {
&self.commitment
}
}
Expand All @@ -184,13 +251,40 @@ mod tests {
use flock_core::pcs::pack_witness;
use num_traits::ConstZero;
use proptest::prelude::*;
use transcript::{build_prover, build_verifier};

use super::*;

fn shape() -> Shape {
Shape::new(7, 15).unwrap()
}

#[test]
fn commitment_profiles_select_ood_and_preserve_transcript_agreement() {
for profile in [
LigeritoProfile::Fast,
LigeritoProfile::Slim,
LigeritoProfile::Secure,
] {
let pcs = Pcs::new(&shape(), profile, HashKind::Blake3).unwrap();
let witness = vec![F128::ZERO; pcs.packed_len()];
let mut prover = build_prover(b"commit-test", b"profile");
let (root, data) = pcs.commit(&witness, &mut prover).unwrap();
let expected_ood = profile != LigeritoProfile::Secure;
assert_eq!(data.commitment().ood.is_some(), expected_ood);
let next_challenge = prover.verifier_message::<F128>();
let proof = prover.finish();
assert_eq!(proof.narg_string.is_empty(), !expected_ood);
let mut verifier = build_verifier(b"commit-test", b"profile", &proof);
let received = pcs.receive_commitment(root, &mut verifier).unwrap();
assert_eq!(received.root(), data.commitment().root());
assert_eq!(received.ood.is_some(), expected_ood);
assert!(received.matches(&pcs));
assert_eq!(verifier.verifier_message::<F128>(), next_challenge);
verifier.check_eof().unwrap();
}
}

#[test]
fn commitment_is_deterministic_for_packed_boundary_bits() {
let scheme = Pcs::new(&shape(), LigeritoProfile::Fast, HashKind::Blake3).unwrap();
Expand All @@ -199,11 +293,26 @@ mod tests {
packed_witness[1] = F128::new(1, 0);
packed_witness.last_mut().unwrap().hi = 1 << 63;

let (commitment, data) = scheme.commit(&packed_witness).unwrap();
let (second_commitment, _) = scheme.commit(&packed_witness).unwrap();
let (commitment, data) = scheme
.commit(
&packed_witness,
&mut build_prover(b"commit-test", b"witness"),
)
.unwrap();
let (second_commitment, _) = scheme
.commit(
&packed_witness,
&mut build_prover(b"commit-test", b"witness"),
)
.unwrap();
let mut changed_witness = packed_witness.clone();
changed_witness[0].lo |= 1 << 2;
let (changed_commitment, _) = scheme.commit(&changed_witness).unwrap();
let (changed_commitment, _) = scheme
.commit(
&changed_witness,
&mut build_prover(b"commit-test", b"witness"),
)
.unwrap();

assert_eq!(commitment, second_commitment);
assert_ne!(commitment, changed_commitment);
Expand Down Expand Up @@ -267,11 +376,14 @@ mod tests {
fn rejects_arbitrary_short_packed_witnesses(len in 0usize..4096) {
let pcs = Pcs::new(&shape(), LigeritoProfile::Fast, HashKind::Blake3).unwrap();
let packed_witness = vec![F128::ZERO; len];
let mut transcript = build_prover(b"commit-test", b"short");

prop_assert!(matches!(
pcs.commit(&packed_witness),
pcs.commit(&packed_witness, &mut transcript),
Err(CommitError::PackedWitnessLengthMismatch)
));
let proof = transcript.finish();
prop_assert!(proof.narg_string.is_empty() && proof.hints.is_empty());
}
}
}
Loading
Loading