Skip to content
Draft
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
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ This property enables efficient proof aggregation and batch verification. See `e
## Usage

```rust
use dory_pcs::{setup, prove, verify, Transparent};
use dory_pcs::{setup, prove, verify_transparent, Transparent};
use dory_pcs::backends::arkworks::{
BN254, G1Routines, G2Routines, ArkworksPolynomial, ArkFr, Blake2bTranscript
};
Expand Down Expand Up @@ -139,7 +139,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// 6. Verify: check that the proof is valid
let evaluation = polynomial.evaluate(&point);
let mut verifier_transcript = Blake2bTranscript::new(b"dory-example");
verify::<_, BN254, G1Routines, G2Routines, _>(
verify_transparent::<_, BN254, G1Routines, G2Routines, _>(
tier_2,
evaluation,
&point,
Expand All @@ -153,6 +153,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
```

`verify_transparent` and `verify_zk` reject proofs for the wrong mode. The older
`verify` function remains as a compatibility entry point that autodetects the
mode from the proof shape. In ZK mode, the evaluation hiding commitment is owned
by the proof; protocols that need to bind it should read `proof.y_com()` after
proving and bind that value, rather than carrying a separate commitment.

## Examples

The repository includes six comprehensive examples demonstrating different aspects of Dory:
Expand Down
7 changes: 5 additions & 2 deletions examples/zk_e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use dory_pcs::backends::arkworks::{
};
use dory_pcs::primitives::arithmetic::Field;
use dory_pcs::primitives::poly::Polynomial;
use dory_pcs::{prove, setup, verify, ZK};
use dory_pcs::{prove, setup, verify_zk, ZK};

fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
Expand Down Expand Up @@ -43,9 +43,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
&prover_setup,
&mut prover_transcript,
)?;
let _evaluation_hiding_commitment = proof
.y_com()
.expect("ZK proofs contain an evaluation hiding commitment");

let mut verifier_transcript = Blake2bTranscript::new(b"dory-zk-example");
verify::<_, BN254, G1Routines, G2Routines, _>(
verify_zk::<_, BN254, G1Routines, G2Routines, _>(
tier_2,
evaluation,
&point,
Expand Down
49 changes: 42 additions & 7 deletions src/backends/arkworks/ark_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,22 @@ pub struct PreparedCache {
pub g2_prepared: Vec<<Bn254 as Pairing>::G2Prepared>,
}

static CACHE: RwLock<Option<Arc<PreparedCache>>> = RwLock::new(None);
#[derive(Debug, Clone)]
struct CachedPrepared {
prepared: Arc<PreparedCache>,
g1_vec: Vec<ArkG1>,
g2_vec: Vec<ArkG2>,
}

static CACHE: RwLock<Option<Arc<CachedPrepared>>> = RwLock::new(None);

fn slice_starts_with<T: PartialEq>(cached: &[T], requested: &[T]) -> bool {
cached.len() >= requested.len() && &cached[..requested.len()] == requested
}

fn cache_covers(cached: &CachedPrepared, g1_vec: &[ArkG1], g2_vec: &[ArkG2]) -> bool {
slice_starts_with(&cached.g1_vec, g1_vec) && slice_starts_with(&cached.g2_vec, g2_vec)
}

/// Initialize the global cache with G1 and G2 vectors.
///
Expand Down Expand Up @@ -52,7 +67,7 @@ pub fn init_cache(g1_vec: &[ArkG1], g2_vec: &[ArkG2]) {
{
let read_guard = CACHE.read().unwrap();
if let Some(ref cache) = *read_guard {
if cache.g1_prepared.len() >= g1_vec.len() && cache.g2_prepared.len() >= g2_vec.len() {
if cache_covers(cache, g1_vec, g2_vec) {
return; // Existing cache is large enough
}
}
Expand All @@ -63,7 +78,7 @@ pub fn init_cache(g1_vec: &[ArkG1], g2_vec: &[ArkG2]) {

// Double-check after acquiring write lock (another thread may have initialized)
if let Some(ref cache) = *write_guard {
if cache.g1_prepared.len() >= g1_vec.len() && cache.g2_prepared.len() >= g2_vec.len() {
if cache_covers(cache, g1_vec, g2_vec) {
return; // Another thread initialized a sufficient cache
}
}
Expand All @@ -85,9 +100,13 @@ pub fn init_cache(g1_vec: &[ArkG1], g2_vec: &[ArkG2]) {
})
.collect();

*write_guard = Some(Arc::new(PreparedCache {
g1_prepared,
g2_prepared,
*write_guard = Some(Arc::new(CachedPrepared {
prepared: Arc::new(PreparedCache {
g1_prepared,
g2_prepared,
}),
g1_vec: g1_vec.to_vec(),
g2_vec: g2_vec.to_vec(),
}));
}

Expand All @@ -110,7 +129,23 @@ pub fn invalidate_cache() {
/// # Returns
/// Arc-wrapped cache, or `None` if uninitialized.
pub fn get_prepared_cache() -> Option<Arc<PreparedCache>> {
CACHE.read().unwrap().clone()
CACHE
.read()
.unwrap()
.as_ref()
.map(|cached| cached.prepared.clone())
}

pub(crate) fn get_prepared_cache_for_g1(g1_vec: &[ArkG1]) -> Option<Arc<PreparedCache>> {
CACHE.read().unwrap().as_ref().and_then(|cached| {
slice_starts_with(&cached.g1_vec, g1_vec).then(|| cached.prepared.clone())
})
}

pub(crate) fn get_prepared_cache_for_g2(g2_vec: &[ArkG2]) -> Option<Arc<PreparedCache>> {
CACHE.read().unwrap().as_ref().and_then(|cached| {
slice_starts_with(&cached.g2_vec, g2_vec).then(|| cached.prepared.clone())
})
}

/// Check if cache is initialized.
Expand Down
6 changes: 5 additions & 1 deletion src/backends/arkworks/ark_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ pub struct ArkG1(pub G1Projective);
#[repr(transparent)]
pub struct ArkG2(pub G2Projective);

#[derive(Default, Clone, Copy, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)]
/// BN254 target-group element.
///
/// Use checked deserialization for untrusted inputs. Unchecked deserialization
/// skips zero and r-torsion validation.
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug, CanonicalSerialize)]
#[repr(transparent)]
pub struct ArkGT(pub Fq12);

Expand Down
10 changes: 6 additions & 4 deletions src/backends/arkworks/ark_pairing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ mod pairing_helpers {

#[cfg(feature = "cache")]
{
if let Some(cache) = crate::backends::arkworks::ark_cache::get_prepared_cache() {
if let Some(cache) = crate::backends::arkworks::ark_cache::get_prepared_cache_for_g2(qs)
{
return multi_pair_with_prepared(ps_prep, &cache.g2_prepared[..qs.len()]);
}
}
Expand Down Expand Up @@ -107,7 +108,8 @@ mod pairing_helpers {

#[cfg(feature = "cache")]
{
if let Some(cache) = crate::backends::arkworks::ark_cache::get_prepared_cache() {
if let Some(cache) = crate::backends::arkworks::ark_cache::get_prepared_cache_for_g1(ps)
{
let ps_prep: Vec<_> = ps
.iter()
.enumerate()
Expand Down Expand Up @@ -189,7 +191,7 @@ mod pairing_helpers {
let chunk_size = determine_chunk_size(ps.len());

#[cfg(feature = "cache")]
let cache = crate::backends::arkworks::ark_cache::get_prepared_cache();
let cache = crate::backends::arkworks::ark_cache::get_prepared_cache_for_g2(qs);

let combined = ps
.par_chunks(chunk_size)
Expand Down Expand Up @@ -253,7 +255,7 @@ mod pairing_helpers {
let chunk_size = determine_chunk_size(ps.len());

#[cfg(feature = "cache")]
let cache = crate::backends::arkworks::ark_cache::get_prepared_cache();
let cache = crate::backends::arkworks::ark_cache::get_prepared_cache_for_g1(ps);

let combined = qs
.par_chunks(chunk_size)
Expand Down
7 changes: 7 additions & 0 deletions src/backends/arkworks/ark_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
use super::{ArkG1, ArkG2, ArkGT};
use crate::proof::DoryProof;

/// Maximum number of reduce-and-fold rounds accepted by default proof deserialization.
///
/// This bounds allocation before a verifier setup is available. It is intentionally
/// conservative for current deployments; callers needing larger proofs should add
/// an explicitly bounded deserialization entry point tied to their setup.
pub const MAX_SERIALIZED_PROOF_ROUNDS: usize = 64;

/// Arkworks-specific Dory proof type
///
/// This is a type alias for `DoryProof` specialized to arkworks group types.
Expand Down
90 changes: 83 additions & 7 deletions src/backends/arkworks/ark_serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use crate::backends::arkworks::{ArkFr, ArkG1, ArkG2, ArkGT};
use crate::primitives::serialization::{Compress, SerializationError, Valid, Validate};
use crate::primitives::{DoryDeserialize, DorySerialize};
use ark_ff::{Field as ArkField, PrimeField, Zero};
use ark_serialize::{
CanonicalDeserialize, CanonicalSerialize, Compress as ArkCompress,
SerializationError as ArkSerializationError, Valid as ArkValid, Validate as ArkValidate,
Expand Down Expand Up @@ -183,11 +184,37 @@ impl DoryDeserialize for ArkG2 {
}
}

fn ark_gt_in_target_group(inner: &ark_bn254::Fq12) -> bool {
!inner.is_zero() && inner.pow(ark_bn254::Fr::MODULUS) == ark_bn254::Fq12::ONE
}

fn validate_dory_gt(inner: &ark_bn254::Fq12) -> Result<(), SerializationError> {
inner
.check()
.map_err(|e| SerializationError::InvalidData(format!("{e:?}")))?;

if ark_gt_in_target_group(inner) {
Ok(())
} else {
Err(SerializationError::InvalidData(
"invalid BN254 target-group element".to_string(),
))
}
}

fn validate_ark_gt(inner: &ark_bn254::Fq12) -> Result<(), ArkSerializationError> {
inner.check()?;

if ark_gt_in_target_group(inner) {
Ok(())
} else {
Err(ArkSerializationError::InvalidData)
}
}

impl Valid for ArkGT {
fn check(&self) -> Result<(), SerializationError> {
self.0
.check()
.map_err(|e| SerializationError::InvalidData(format!("{e:?}")))
validate_dory_gt(&self.0)
}
}

Expand Down Expand Up @@ -231,17 +258,61 @@ impl DoryDeserialize for ArkGT {
};

if matches!(validate, Validate::Yes) {
inner
.check()
.map_err(|e| SerializationError::InvalidData(format!("{e:?}")))?;
validate_dory_gt(&inner)?;
}

Ok(ArkGT(inner))
}
}

impl ArkValid for ArkGT {
fn check(&self) -> Result<(), ArkSerializationError> {
validate_ark_gt(&self.0)
}
}

impl CanonicalDeserialize for ArkGT {
fn deserialize_with_mode<R: Read>(
reader: R,
compress: ArkCompress,
validate: ArkValidate,
) -> Result<Self, ArkSerializationError> {
let inner = match compress {
ArkCompress::Yes => ark_bn254::Fq12::deserialize_compressed(reader)?,
ArkCompress::No => ark_bn254::Fq12::deserialize_uncompressed(reader)?,
};

if matches!(validate, ArkValidate::Yes) {
validate_ark_gt(&inner)?;
}

Ok(ArkGT(inner))
}
}

// Arkworks-specific Dory proof type
use super::ArkDoryProof;
use super::{ArkDoryProof, MAX_SERIALIZED_PROOF_ROUNDS};

fn validate_serialized_proof_shape(
num_rounds: usize,
nu: usize,
sigma: usize,
) -> Result<(), ArkSerializationError> {
let total_dimension = nu
.checked_add(sigma)
.ok_or(ArkSerializationError::InvalidData)?;

if num_rounds > MAX_SERIALIZED_PROOF_ROUNDS
|| nu > sigma
|| sigma != num_rounds
|| sigma >= usize::BITS as usize
|| total_dimension >= usize::BITS as usize
{
return Err(ArkSerializationError::InvalidData);
}

Ok(())
}

#[cfg(feature = "zk")]
mod zk_serde {
Expand Down Expand Up @@ -437,6 +508,9 @@ impl CanonicalDeserialize for ArkDoryProof {
let num_rounds =
<u32 as CanonicalDeserialize>::deserialize_with_mode(&mut reader, compress, validate)?
as usize;
if num_rounds > MAX_SERIALIZED_PROOF_ROUNDS {
return Err(ArkSerializationError::InvalidData);
}

// Deserialize first messages
let mut first_messages = Vec::with_capacity(num_rounds);
Expand Down Expand Up @@ -501,6 +575,8 @@ impl CanonicalDeserialize for ArkDoryProof {
<u32 as CanonicalDeserialize>::deserialize_with_mode(&mut reader, compress, validate)?
as usize;

validate_serialized_proof_shape(num_rounds, nu, sigma)?;

Ok(ArkDoryProof {
vmv_message,
first_messages,
Expand Down
2 changes: 1 addition & 1 deletion src/backends/arkworks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub use ark_field::ArkFr;
pub use ark_group::{ArkG1, ArkG2, ArkGT, G1Routines, G2Routines};
pub use ark_pairing::BN254;
pub use ark_poly::ArkworksPolynomial;
pub use ark_proof::ArkDoryProof;
pub use ark_proof::{ArkDoryProof, MAX_SERIALIZED_PROOF_ROUNDS};
pub use ark_setup::{ArkworksProverSetup, ArkworksVerifierSetup};
pub use blake2b_transcript::Blake2bTranscript;

Expand Down
Loading
Loading