use crate::error::PrivacyError;
use crate::types::{PrivacyProof, ProofScheme};
use crate::zk::{snark, stark};
use sha2::{Digest, Sha256};
use serde::{Deserialize, Serialize};
extern crate alloc;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ProofContext {
LowBandwidth,
HighTransparency,
Hybrid,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HybridConfig {
pub max_proof_size: usize,
pub max_agg_depth: usize,
pub agg_interval_secs: u64,
}
impl Default for HybridConfig {
fn default() -> Self {
Self {
max_proof_size: 10_240, max_agg_depth: 10,
agg_interval_secs: 60,
}
}
}
pub struct HybridZkLayer {
config: HybridConfig,
buffer: Vec<PrivacyProof>,
snark_buffer: Vec<snark::SnarkProof>,
}
impl HybridZkLayer {
pub fn new() -> Self {
Self::with_config(HybridConfig::default())
}
pub fn with_config(config: HybridConfig) -> Self {
Self { config, buffer: Vec::new(), snark_buffer: Vec::new() }
}
pub fn prove(
&mut self,
statement_hash: [u8; 32],
witness_hash: [u8; 32],
chaos_seed: &[u8; 32],
context: ProofContext,
) -> Result<PrivacyProof, PrivacyError> {
let proof = match context {
ProofContext::LowBandwidth => {
let s = snark::prove(statement_hash, witness_hash, chaos_seed)?;
let p = PrivacyProof::from(s.clone());
self.snark_buffer.push(s);
p
}
ProofContext::HighTransparency => {
let s = stark::prove_statement(statement_hash, witness_hash, chaos_seed)?;
PrivacyProof::from(s)
}
ProofContext::Hybrid => {
let snark_p = snark::prove(statement_hash, witness_hash, chaos_seed)?;
let stark_p = stark::prove_statement(statement_hash, witness_hash, chaos_seed)?;
let mut hasher = Sha256::new();
hasher.update(snark_p.commitment.as_bytes());
hasher.update(stark_p.commitment.as_bytes());
hasher.update(b"hybrid-v1");
let _combined: [u8; 32] = hasher.finalize().into();
let proof_bytes = alloc::format!(
"{}{}{}{}{}",
snark_p.commitment, snark_p.challenge, snark_p.response,
stark_p.commitment, stark_p.eval_value,
);
let proof_size = proof_bytes.len() / 2;
self.snark_buffer.push(snark_p);
PrivacyProof {
proof_bytes,
public_inputs: hex::encode(statement_hash),
scheme: ProofScheme::Hybrid,
security_bits: 128,
proof_size,
chsh_value: 0.0,
lyapunov: stark_p.lyapunov,
}
}
};
if proof.proof_size > self.config.max_proof_size {
}
self.buffer.push(proof.clone());
Ok(proof)
}
pub fn verify(
&self,
proof: &PrivacyProof,
statement_hash: &[u8; 32],
) -> Result<(), PrivacyError> {
if proof.public_inputs != hex::encode(statement_hash) {
return Err(PrivacyError::PublicInputMismatch);
}
if proof.proof_bytes.is_empty() {
return Err(PrivacyError::InvalidProofEncoding);
}
Ok(())
}
pub fn aggregate_buffer(
&mut self,
stake_weights: &[u64],
chaos_seed: &[u8; 32],
) -> Result<PrivacyProof, PrivacyError> {
if self.buffer.is_empty() {
return Err(PrivacyError::ProofGenerationFailed("Empty proof buffer".into()));
}
let depth = (self.buffer.len() as f64).log2().ceil() as usize + 1;
if depth > self.config.max_agg_depth {
self.buffer.clear();
self.snark_buffer.clear();
return Err(PrivacyError::AggregationDepthExceeded(depth));
}
let result = if !self.snark_buffer.is_empty() {
let agg = snark::aggregate(&self.snark_buffer, stake_weights, chaos_seed)?;
PrivacyProof::from(agg)
} else {
let leaves: Vec<Vec<u8>> = self.buffer.iter().enumerate().map(|(i, p)| {
let weight = stake_weights.get(i).copied().unwrap_or(1);
let mut hasher = Sha256::new();
hasher.update(p.proof_bytes.as_bytes());
hasher.update(weight.to_le_bytes());
hasher.update(b"hybrid-fold-leaf-v1");
hasher.finalize().to_vec()
}).collect();
let merkle_root = snark::merkle_root_of(&leaves);
let pub_leaves: Vec<Vec<u8>> = self.buffer.iter().map(|p| {
p.public_inputs.as_bytes().to_vec()
}).collect();
let agg_pub_root = snark::merkle_root_of(&pub_leaves);
let mut final_hasher = Sha256::new();
final_hasher.update(&merkle_root);
final_hasher.update(chaos_seed);
final_hasher.update(b"halo2-hybrid-fold-v1");
let final_commitment: [u8; 32] = final_hasher.finalize().into();
PrivacyProof {
proof_bytes: hex::encode(final_commitment),
public_inputs: hex::encode(&agg_pub_root),
scheme: ProofScheme::Hybrid,
security_bits: 128,
proof_size: 64,
chsh_value: 0.0,
lyapunov: 4.5,
}
};
self.buffer.clear();
self.snark_buffer.clear();
Ok(result)
}
pub fn buffer_len(&self) -> usize {
self.buffer.len()
}
pub fn prove_hybrid(
&mut self,
statement_hash: &[u8],
witness_hash: &[u8],
chaos_seed: &[u8],
) -> Result<PrivacyProof, PrivacyError> {
use sha2::{Digest, Sha256};
let stmt: [u8; 32] = Sha256::digest(statement_hash).into();
let wit: [u8; 32] = Sha256::digest(witness_hash).into();
let seed: [u8; 32] = Sha256::digest(chaos_seed).into();
self.prove(stmt, wit, &seed, ProofContext::Hybrid)
}
pub fn verify_hybrid(proof: &PrivacyProof, statement_hash: &[u8]) -> bool {
use sha2::{Digest, Sha256};
let stmt: [u8; 32] = Sha256::digest(statement_hash).into();
let layer = HybridZkLayer::new();
layer.verify(proof, &stmt).is_ok()
}
}
impl Default for HybridZkLayer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use sha2::{Digest, Sha256};
fn make_hashes(label: &str) -> ([u8; 32], [u8; 32]) {
let stmt: [u8; 32] = Sha256::digest(alloc::format!("stmt-{label}").as_bytes()).into();
let wit: [u8; 32] = Sha256::digest(alloc::format!("wit-{label}").as_bytes()).into();
(stmt, wit)
}
#[test]
fn test_snark_context() {
let mut layer = HybridZkLayer::new();
let (stmt, wit) = make_hashes("snark");
let seed = [0u8; 32];
let proof = layer.prove(stmt, wit, &seed, ProofContext::LowBandwidth).unwrap();
assert_eq!(proof.scheme, ProofScheme::Snark);
assert!(layer.verify(&proof, &stmt).is_ok());
}
#[test]
fn test_stark_context() {
let mut layer = HybridZkLayer::new();
let (stmt, wit) = make_hashes("stark");
let seed = [1u8; 32];
let proof = layer.prove(stmt, wit, &seed, ProofContext::HighTransparency).unwrap();
assert_eq!(proof.scheme, ProofScheme::Stark);
}
#[test]
fn test_hybrid_context() {
let mut layer = HybridZkLayer::new();
let (stmt, wit) = make_hashes("hybrid");
let seed = [2u8; 32];
let proof = layer.prove(stmt, wit, &seed, ProofContext::Hybrid).unwrap();
assert_eq!(proof.scheme, ProofScheme::Hybrid);
}
#[test]
fn test_aggregate_with_snark_proofs() {
let mut layer = HybridZkLayer::new();
let seed = [3u8; 32];
for i in 0..4 {
let (stmt, wit) = make_hashes(&i.to_string());
layer.prove(stmt, wit, &seed, ProofContext::LowBandwidth).unwrap();
}
let weights = alloc::vec![100u64, 200, 150, 50];
let agg = layer.aggregate_buffer(&weights, &seed).unwrap();
assert!(!agg.proof_bytes.is_empty());
assert_eq!(layer.buffer_len(), 0);
}
#[test]
fn test_aggregate_mixed_proofs() {
let mut layer = HybridZkLayer::new();
let seed = [4u8; 32];
let (stmt1, wit1) = make_hashes("a");
let (stmt2, wit2) = make_hashes("b");
layer.prove(stmt1, wit1, &seed, ProofContext::LowBandwidth).unwrap();
layer.prove(stmt2, wit2, &seed, ProofContext::HighTransparency).unwrap();
let weights = alloc::vec![100u64, 200];
let agg = layer.aggregate_buffer(&weights, &seed).unwrap();
assert!(!agg.proof_bytes.is_empty());
assert_eq!(layer.buffer_len(), 0);
}
}