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
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ ciborium = "0.2"
digest = { version = "0.10.7", features = ["oid"] }
sha2 = { version = "0.10.9", features = ["oid", "asm"] }
zerocopy = "0.8.33"
zeroize = { version = "1", features = ["zeroize_derive"] }
hex-literal = "0.4.1"
const-oid = "0.9.6"
arrayvec = "0.7.6"
Expand Down
19 changes: 9 additions & 10 deletions src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ where
hash_id,
};

let params = Config::<F>::new(1 << num_variables, &whir_params, 1);
let params = Config::<F>::new(num_variables, &whir_params);

let ds = DomainSeparator::protocol(&params)
.session(&format!("Example at {}:{}", file!(), line!()))
Expand All @@ -270,18 +270,15 @@ where
println!("Whir (PCS + ZK) 🌪️");
println!("Field: {:?} and hash: {:?}", args.field, args.hash);
println!("{params}");
if !params
.blinded_commitment
.check_max_pow_bits(Bits::new(whir_params.pow_bits as f64))
{
if !params.check_max_pow_bits(Bits::new(whir_params.pow_bits as f64)) {
println!("WARN: more PoW bits required than specified.");
}

let embedding = Identity::<F>::new();
let vector = (0..num_coeffs).map(F::from).collect::<Vec<_>>();

// Allocate constraints
let mut linear_forms: Vec<Box<dyn Evaluate<Basefield<F>>>> = Vec::new();
let mut linear_forms: Vec<Box<dyn Evaluate<Identity<F>>>> = Vec::new();
let mut prove_linear_forms: Vec<Box<dyn LinearForm<F>>> = Vec::new();
let mut evaluations = Vec::new();

Expand Down Expand Up @@ -312,9 +309,9 @@ where
let whir_commit_time = whir_commit_time.elapsed();

let whir_prove_time = Instant::now();
let _ = params.prove(
params.prove(
&mut prover_state,
vec![Cow::Borrowed(&vector)],
vec![Cow::Owned(vector)],
witness,
prove_linear_forms,
Cow::Borrowed(&evaluations),
Expand All @@ -340,14 +337,16 @@ where
let whir_verifier_time = Instant::now();
for _ in 0..reps {
let mut verifier_state = VerifierState::new_std(&ds, &proof);
let commitment = params.receive_commitments(&mut verifier_state, 1).unwrap();
let commitments = params.receive_commitments(&mut verifier_state).unwrap();
params
.verify(
&mut verifier_state,
&weight_dyn_refs,
&evaluations,
&commitment,
&commitments,
)
.unwrap()
.verify(weight_dyn_refs.iter().copied())
.unwrap();
}
println!(
Expand Down
149 changes: 110 additions & 39 deletions src/protocols/irs_commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,7 @@ where
let rate = message_length as f64 / codeword_length as f64;

// Pick in- and out-of-domain samples.
// η = slack to Johnson bound. We pick η = √ρ / 20.
// TODO: Optimize picking η.
let johnson_slack = if unique_decoding {
0.0
} else {
rate.sqrt() / 20.
};
let johnson_slack = johnson_slack(unique_decoding, rate);
#[allow(clippy::cast_sign_loss)]
let out_domain_samples = if unique_decoding {
0
Expand Down Expand Up @@ -392,11 +386,98 @@ where
);
}

// Get in-domain openings
let (indices, points) = self.in_domain_challenges(prover_state);
self.open_inner(prover_state, witnesses, &indices, points)
}

/// Verifies one or more openings and returns the in-domain evaluations.
///
/// **Note.** This does not verify the out-of-domain evaluations.
#[cfg_attr(feature = "tracing", instrument(skip_all, fields(self = %self)))]
pub fn verify<H>(
&self,
verifier_state: &mut VerifierState<H>,
commitments: &[&Commitment<M::Target>],
) -> VerificationResult<Evaluations<M::Source>>
where
H: DuplexSpongeInterface,
u8: Decoding<[H::U]>,
Hash: ProverMessage<[H::U]>,
{
for commitment in commitments {
verify!(commitment.out_of_domain.points.len() == self.out_domain_samples);
verify!(
commitment.out_of_domain.matrix.len() == self.num_vectors * self.out_domain_samples
);
}

let (indices, points) = self.in_domain_challenges(verifier_state);
self.verify_inner(verifier_state, commitments, &indices, points)
}

// For each commitment, send the selected rows to the verifier
// and collect them in the evaluation matrix.
/// Opens the commitment at caller-provided codeword indices.
///
/// Like [`open`] but does not sample indices from the transcript.
/// Used for the Γ consistency check in zkWHIR 2.0.
pub fn open_at_indices<H, R>(
&self,
prover_state: &mut ProverState<H, R>,
witnesses: &[&Witness<M::Source, M::Target>],
indices: &[usize],
) -> Evaluations<M::Source>
where
H: DuplexSpongeInterface,
R: RngCore + CryptoRng,
Hash: ProverMessage<[H::U]>,
{
assert!(
indices.iter().all(|&i| i < self.codeword_length),
"index out of bounds: all indices must be < codeword_length ({})",
self.codeword_length
);
let generator = self.generator();
let points: Vec<M::Source> = indices.iter().map(|&i| generator.pow([i as u64])).collect();
self.open_inner(prover_state, witnesses, indices, points)
}

/// Verifies an opening at caller-provided codeword indices.
///
/// Like [`verify`] but does not sample indices from the transcript.
/// Used for the Γ consistency check in zkWHIR 2.0.
pub fn verify_at_indices<H>(
&self,
verifier_state: &mut VerifierState<H>,
commitments: &[&Commitment<M::Target>],
indices: &[usize],
) -> VerificationResult<Evaluations<M::Source>>
where
H: DuplexSpongeInterface,
u8: Decoding<[H::U]>,
Hash: ProverMessage<[H::U]>,
{
assert!(
indices.iter().all(|&i| i < self.codeword_length),
"index out of bounds: all indices must be < codeword_length ({})",
self.codeword_length
);
let generator = self.generator();
let points: Vec<M::Source> = indices.iter().map(|&i| generator.pow([i as u64])).collect();
self.verify_inner(verifier_state, commitments, indices, points)
}

/// Shared open logic for [`open`] and [`open_at_indices`].
fn open_inner<H, R>(
&self,
prover_state: &mut ProverState<H, R>,
witnesses: &[&Witness<M::Source, M::Target>],
indices: &[usize],
points: Vec<M::Source>,
) -> Evaluations<M::Source>
where
H: DuplexSpongeInterface,
R: RngCore + CryptoRng,
Hash: ProverMessage<[H::U]>,
{
let stride = witnesses.len() * self.num_cols();
let mut matrix = vec![M::Source::ZERO; indices.len() * stride];
let mut submatrix = Vec::with_capacity(indices.len() * self.num_cols());
Expand All @@ -414,39 +495,26 @@ where
}
prover_state.prover_hint_ark(&submatrix);
self.matrix_commit
.open(prover_state, &witness.matrix_witness, &indices);
.open(prover_state, &witness.matrix_witness, indices);
matrix_col_offset += self.num_cols();
}

Evaluations { points, matrix }
}

/// Verifies one or more openings and returns the in-domain evaluations.
///
/// **Note.** This does not verify the out-of-domain evaluations.
#[cfg_attr(feature = "tracing", instrument(skip_all, fields(self = %self)))]
pub fn verify<H>(
/// Shared verify logic for [`verify`] and [`verify_at_indices`].
fn verify_inner<H>(
&self,
verifier_state: &mut VerifierState<H>,
commitments: &[&Commitment<M::Target>],
indices: &[usize],
points: Vec<M::Source>,
) -> VerificationResult<Evaluations<M::Source>>
where
H: DuplexSpongeInterface,
u8: Decoding<[H::U]>,
Hash: ProverMessage<[H::U]>,
{
for commitment in commitments {
verify!(commitment.out_of_domain.points.len() == self.out_domain_samples);
verify!(
commitment.out_of_domain.matrix.len() == self.num_vectors * self.out_domain_samples
);
}

// Get in-domain openings
let (indices, points) = self.in_domain_challenges(verifier_state);

// Receive (as a hint) a matrix of all the columns of all the commitments
// corresponding to the in-domain opening rows.
let stride = commitments.len() * self.num_cols();
let mut matrix = vec![M::Source::ZERO; indices.len() * stride];
let mut matrix_col_offset = 0;
Expand All @@ -455,10 +523,9 @@ where
self.matrix_commit.verify(
verifier_state,
&commitment.matrix_commitment,
&indices,
indices,
&submatrix,
)?;
// Horizontally concatenate matrices.
if stride != 0 && self.num_cols() != 0 {
for (dst, src) in zip_strict(
matrix.chunks_exact_mut(stride),
Expand Down Expand Up @@ -576,6 +643,17 @@ where
}
}

/// Compute the Johnson bound slack η = √ρ / 20.
///
/// Returns 0 for unique decoding.
pub(crate) fn johnson_slack(unique_decoding: bool, rate: f64) -> f64 {
if unique_decoding {
0.0
} else {
rate.sqrt() / 20.
}
}

/// Return the number of in-domain queries.
///
/// This is used by [`whir_zk`].
Expand All @@ -586,21 +664,14 @@ pub(crate) fn num_in_domain_queries(
security_target: f64,
rate: f64,
) -> usize {
// Pick in- and out-of-domain samples.
// η = slack to Johnson bound. We pick η = √ρ / 20.
// TODO: Optimize picking η.
let johnson_slack = if unique_decoding {
0.0
} else {
rate.sqrt() / 20.
};
let slack = johnson_slack(unique_decoding, rate);
// Query error is (1 - δ)^q, so we compute 1 - δ
let per_sample = if unique_decoding {
// Unique decoding bound: δ = (1 - ρ) / 2
f64::midpoint(1., rate)
} else {
// Johnson bound: δ = 1 - √ρ - η
rate.sqrt() + johnson_slack
rate.sqrt() + slack
};
(security_target / (-per_sample.log2())).ceil() as usize
}
Expand Down
1 change: 1 addition & 0 deletions src/protocols/whir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

mod config;
mod prover;
pub(crate) mod rounds;
mod verifier;

use std::fmt::Debug;
Expand Down
Loading
Loading