use crate::error::PrivacyError;
use crate::hypergraph::CHSH_THRESHOLD;
use crate::types::{PrivacyProof, ProofScheme};
use crate::zk::snark::merkle_root_of;
use sha2::{Digest, Sha256};
use serde::{Deserialize, Serialize};
extern crate alloc;
use alloc::vec::Vec;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntanglementConfig {
pub chsh_threshold: f64,
pub max_depth: usize,
pub min_stake: u64,
}
impl Default for EntanglementConfig {
fn default() -> Self {
Self {
chsh_threshold: CHSH_THRESHOLD,
max_depth: 20,
min_stake: 1,
}
}
}
pub struct EntanglementEngine {
config: EntanglementConfig,
}
impl EntanglementEngine {
pub fn new() -> Self {
Self::with_config(EntanglementConfig::default())
}
pub fn with_config(config: EntanglementConfig) -> Self {
Self { config }
}
pub fn entangle_pair(
&self,
proof_a: &PrivacyProof,
proof_b: &PrivacyProof,
chaos_phase: f64,
) -> Result<PrivacyProof, PrivacyError> {
let a_commitment = extract_commitment_bytes(&proof_a.proof_bytes);
let b_commitment = extract_commitment_bytes(&proof_b.proof_bytes);
let mut hasher = Sha256::new();
hasher.update(&a_commitment);
hasher.update(&b_commitment);
hasher.update(chaos_phase.to_le_bytes());
hasher.update(b"bell-cz-entangle-v1");
let tensor: [u8; 32] = hasher.finalize().into();
let chsh = 2.0_f64 * 2.0_f64.sqrt() * chaos_phase.cos().abs();
let chsh = chsh.min(2.828_427);
if chsh <= self.config.chsh_threshold {
return Err(PrivacyError::EntanglementFailed(chsh));
}
let pub_a = hex::decode(&proof_a.public_inputs).unwrap_or_default();
let pub_b = hex::decode(&proof_b.public_inputs).unwrap_or_default();
let combined_pub = merkle_root_of(&[pub_a, pub_b]);
let mut ch_hasher = Sha256::new();
ch_hasher.update(tensor);
ch_hasher.update(chaos_phase.to_le_bytes());
ch_hasher.update(b"entangle-challenge-v1");
let challenge: [u8; 32] = ch_hasher.finalize().into();
let mut resp_hasher = Sha256::new();
resp_hasher.update(&a_commitment);
resp_hasher.update(&b_commitment);
resp_hasher.update(challenge);
resp_hasher.update(b"entangle-response-v1");
let response: [u8; 32] = resp_hasher.finalize().into();
let proof_bytes = alloc::format!("{}{}{}",
hex::encode(tensor),
hex::encode(challenge),
hex::encode(response),
);
Ok(PrivacyProof {
proof_bytes,
public_inputs: hex::encode(combined_pub),
scheme: ProofScheme::Snark,
security_bits: 128,
proof_size: 96, chsh_value: chsh,
lyapunov: proof_a.lyapunov.max(proof_b.lyapunov),
})
}
pub fn aggregate_recursive(
&self,
proofs: Vec<PrivacyProof>,
stake_weights: &[u64],
chaos_seed: &[u8; 32],
) -> Result<PrivacyProof, PrivacyError> {
if proofs.is_empty() {
return Err(PrivacyError::ProofGenerationFailed("No proofs to aggregate".into()));
}
let depth = (proofs.len() as f64).log2().ceil() as usize;
if depth > self.config.max_depth {
return Err(PrivacyError::AggregationDepthExceeded(depth));
}
let eligible: Vec<(PrivacyProof, u64)> = proofs
.into_iter()
.zip(stake_weights.iter().copied().chain(core::iter::repeat(1)))
.filter(|(_, w)| *w >= self.config.min_stake)
.collect();
if eligible.is_empty() {
return Err(PrivacyError::ProofGenerationFailed("No eligible proofs after stake filter".into()));
}
let all_weights: Vec<u64> = eligible.iter().map(|(_, w)| *w).collect();
let mut current: Vec<PrivacyProof> = eligible.into_iter().map(|(p, _)| p).collect();
let mut fold_round = 0u64;
while current.len() > 1 {
let mut next = Vec::new();
let mut i = 0;
while i < current.len() {
if i + 1 < current.len() {
let mut phase_hasher = Sha256::new();
phase_hasher.update(chaos_seed);
phase_hasher.update(fold_round.to_le_bytes());
phase_hasher.update((i as u64).to_le_bytes());
let phase_bytes: [u8; 32] = phase_hasher.finalize().into();
let phase_raw = u64::from_le_bytes(phase_bytes[..8].try_into().unwrap_or([0u8; 8]));
let phase = (phase_raw as f64 / u64::MAX as f64) * 0.1_f64;
let folded = self.entangle_pair(¤t[i], ¤t[i + 1], phase)?;
next.push(folded);
i += 2;
} else {
next.push(current[i].clone());
i += 1;
}
}
current = next;
fold_round += 1;
}
let mut result = current.remove(0);
let commitment_bytes = extract_commitment_bytes(&result.proof_bytes);
let mut final_hasher = Sha256::new();
final_hasher.update(&commitment_bytes);
final_hasher.update(chaos_seed);
final_hasher.update((all_weights.iter().sum::<u64>()).to_le_bytes());
final_hasher.update(b"halo2-final-v1");
let final_commitment: [u8; 32] = final_hasher.finalize().into();
let mut ch_hasher = Sha256::new();
ch_hasher.update(final_commitment);
ch_hasher.update(chaos_seed);
ch_hasher.update(b"halo2-final-challenge-v1");
let final_challenge: [u8; 32] = ch_hasher.finalize().into();
let mut resp_hasher = Sha256::new();
resp_hasher.update(final_commitment);
resp_hasher.update(final_challenge);
resp_hasher.update(b"halo2-final-response-v1");
let final_response: [u8; 32] = resp_hasher.finalize().into();
result.proof_bytes = alloc::format!("{}{}{}",
hex::encode(final_commitment),
hex::encode(final_challenge),
hex::encode(final_response),
);
result.scheme = ProofScheme::Snark;
result.proof_size = 96;
Ok(result)
}
pub fn verify_non_locality(&self, proof: &PrivacyProof) -> Result<(), PrivacyError> {
if proof.chsh_value <= self.config.chsh_threshold {
return Err(PrivacyError::EntanglementFailed(proof.chsh_value));
}
Ok(())
}
}
impl Default for EntanglementEngine {
fn default() -> Self {
Self::new()
}
}
fn extract_commitment_bytes(proof_bytes_hex: &str) -> Vec<u8> {
let hex_str = if proof_bytes_hex.len() >= 64 {
&proof_bytes_hex[..64]
} else {
proof_bytes_hex
};
hex::decode(hex_str).unwrap_or_else(|_| {
Sha256::digest(proof_bytes_hex.as_bytes()).to_vec()
})
}
#[cfg(test)]
mod tests {
use super::*;
use sha2::{Digest, Sha256};
fn make_proof(label: &str) -> PrivacyProof {
let h: [u8; 32] = Sha256::digest(label.as_bytes()).into();
let commitment = h;
let challenge: [u8; 32] = Sha256::digest(h).into();
let response: [u8; 32] = Sha256::digest(challenge).into();
PrivacyProof {
proof_bytes: alloc::format!("{}{}{}",
hex::encode(commitment),
hex::encode(challenge),
hex::encode(response),
),
public_inputs: hex::encode(h),
scheme: ProofScheme::Snark,
security_bits: 128,
proof_size: 96,
chsh_value: 0.0,
lyapunov: 4.5,
}
}
#[test]
fn test_entangle_pair() {
let engine = EntanglementEngine::new();
let pa = make_proof("proof-a");
let pb = make_proof("proof-b");
let result = engine.entangle_pair(&pa, &pb, 0.01);
assert!(result.is_ok());
let p = result.unwrap();
assert!(p.chsh_value > CHSH_THRESHOLD);
}
#[test]
fn test_entangle_pair_high_phase_fails() {
let engine = EntanglementEngine::new();
let pa = make_proof("proof-a");
let pb = make_proof("proof-b");
let result = engine.entangle_pair(&pa, &pb, core::f64::consts::FRAC_PI_2);
assert!(result.is_err());
}
#[test]
fn test_aggregate_recursive() {
let engine = EntanglementEngine::new();
let proofs: Vec<PrivacyProof> = (0..4).map(|i| make_proof(&alloc::format!("p{i}"))).collect();
let weights = vec![100u64, 200, 150, 50];
let seed = [0u8; 32];
let agg = engine.aggregate_recursive(proofs, &weights, &seed).unwrap();
assert!(!agg.proof_bytes.is_empty());
assert_eq!(agg.proof_size, 96);
}
#[test]
fn test_non_locality_check() {
let engine = EntanglementEngine::new();
let mut proof = make_proof("test");
proof.chsh_value = 2.9;
assert!(engine.verify_non_locality(&proof).is_ok());
proof.chsh_value = 2.5;
assert!(engine.verify_non_locality(&proof).is_err());
}
#[test]
fn test_extract_commitment_bytes() {
let h: [u8; 32] = Sha256::digest(b"test").into();
let proof_bytes = alloc::format!("{}{}{}",
hex::encode(h), hex::encode(h), hex::encode(h));
let extracted = extract_commitment_bytes(&proof_bytes);
assert_eq!(extracted, h.to_vec());
}
}