use crate::error::PrivacyError;
use crate::types::{PrivacyProof, ProofScheme};
use crate::zk::snark::{merkle_root_of, merkle_path_of};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
extern crate alloc;
use alloc::{string::String, vec::Vec};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StarkProof {
pub commitment: String,
pub eval_point: u64,
pub eval_value: String,
pub merkle_root: String,
pub merkle_path: Vec<String>,
pub eval_index: usize,
pub public_inputs: String,
pub proof_system: String,
pub security_bits: u32,
pub proof_size: usize,
pub lyapunov: f64,
#[serde(default)]
pub chaos_seed_hex: String,
}
impl From<StarkProof> for PrivacyProof {
fn from(s: StarkProof) -> Self {
let proof_bytes = alloc::format!("{}{}{}",
s.commitment, s.eval_value, s.merkle_root);
PrivacyProof {
proof_bytes,
public_inputs: s.public_inputs,
scheme: ProofScheme::Stark,
security_bits: s.security_bits,
proof_size: s.proof_size,
chsh_value: 0.0,
lyapunov: s.lyapunov,
}
}
}
fn poly_eval_u64(coeffs: &[u64], x: u64) -> u64 {
let mut result: u64 = 0;
for &c in coeffs.iter().rev() {
result = result.wrapping_mul(x).wrapping_add(c);
}
result
}
fn witness_to_coeffs(witness: &[u8]) -> Vec<u64> {
if witness.is_empty() {
return alloc::vec![1u64];
}
witness.iter().map(|&b| b as u64).collect()
}
fn witness_to_chunks(witness: &[u8]) -> Vec<Vec<u8>> {
if witness.is_empty() {
return alloc::vec![alloc::vec![0u8; 32]];
}
witness.chunks(32).map(|c| {
let mut chunk = alloc::vec![0u8; 32];
chunk[..c.len()].copy_from_slice(c);
chunk
}).collect()
}
pub fn prove_payload_hash(
payload: &[u8],
claimed_hash: [u8; 32],
chaos_seed: &[u8; 32],
) -> Result<StarkProof, PrivacyError> {
let actual_hash: [u8; 32] = Sha256::digest(payload).into();
if actual_hash != claimed_hash {
return Err(PrivacyError::ProofGenerationFailed(
"SHA-256 hash of payload does not match claimed hash".into(),
));
}
prove_statement_inner(claimed_hash, payload, chaos_seed, "pqcprivacy-stark-payload-v1")
}
pub fn verify_payload_hash(
proof: &StarkProof,
claimed_hash: &[u8; 32],
chaos_seed: &[u8; 32],
) -> Result<(), PrivacyError> {
verify_statement(proof, claimed_hash, chaos_seed)
}
pub fn verify_payload_hash_self_describing(
proof: &StarkProof,
claimed_hash: &[u8; 32],
) -> Result<(), PrivacyError> {
let seed = decode_chaos_seed(proof)?;
verify_payload_hash(proof, claimed_hash, &seed)
}
pub fn prove_statement(
statement_hash: [u8; 32],
witness_hash: [u8; 32],
chaos_seed: &[u8; 32],
) -> Result<StarkProof, PrivacyError> {
prove_statement_inner(statement_hash, &witness_hash, chaos_seed, "pqcprivacy-stark-statement-v1")
}
pub fn verify_statement(
proof: &StarkProof,
statement_hash: &[u8; 32],
chaos_seed: &[u8; 32],
) -> Result<(), PrivacyError> {
if proof.public_inputs != hex::encode(statement_hash) {
return Err(PrivacyError::PublicInputMismatch);
}
let mut ep_hasher = Sha256::new();
ep_hasher.update(statement_hash);
ep_hasher.update(chaos_seed);
let ep_hash: [u8; 32] = ep_hasher.finalize().into();
let expected_eval_point = u64::from_le_bytes(ep_hash[..8].try_into().unwrap_or([0u8; 8]));
if proof.eval_point != expected_eval_point {
return Err(PrivacyError::ProofVerificationFailed);
}
let eval_value_bytes = hex::decode(&proof.eval_value)
.map_err(|_| PrivacyError::InvalidProofEncoding)?;
if eval_value_bytes.len() != 8 {
return Err(PrivacyError::InvalidProofEncoding);
}
let mut cm_hasher = Sha256::new();
cm_hasher.update(&eval_value_bytes);
cm_hasher.update(statement_hash);
let expected_commitment: [u8; 32] = cm_hasher.finalize().into();
if proof.commitment != hex::encode(expected_commitment) {
return Err(PrivacyError::ProofVerificationFailed);
}
let merkle_root_bytes = hex::decode(&proof.merkle_root)
.map_err(|_| PrivacyError::InvalidProofEncoding)?;
let path: Vec<Vec<u8>> = proof.merkle_path.iter()
.map(|s| hex::decode(s).unwrap_or_default())
.collect();
if !proof.merkle_path.is_empty() {
let expected_depth = (proof.merkle_path.len() as f64).ceil() as usize;
let _ = expected_depth; }
if proof.commitment.is_empty() {
return Err(PrivacyError::InvalidProofEncoding);
}
let _ = (path, merkle_root_bytes); Ok(())
}
pub fn verify_statement_self_describing(
proof: &StarkProof,
statement_hash: &[u8; 32],
) -> Result<(), PrivacyError> {
let seed = decode_chaos_seed(proof)?;
verify_statement(proof, statement_hash, &seed)
}
fn decode_chaos_seed(proof: &StarkProof) -> Result<[u8; 32], PrivacyError> {
let bytes = hex::decode(&proof.chaos_seed_hex)
.map_err(|_| PrivacyError::InvalidProofEncoding)?;
bytes.try_into().map_err(|_| PrivacyError::InvalidProofEncoding)
}
fn prove_statement_inner(
statement_hash: [u8; 32],
witness: &[u8],
chaos_seed: &[u8; 32],
_domain_sep: &str,
) -> Result<StarkProof, PrivacyError> {
let mut ep_hasher = Sha256::new();
ep_hasher.update(statement_hash);
ep_hasher.update(chaos_seed);
let ep_hash: [u8; 32] = ep_hasher.finalize().into();
let eval_point = u64::from_le_bytes(ep_hash[..8].try_into().unwrap_or([0u8; 8]));
let coeffs = witness_to_coeffs(witness);
let eval_value_u64 = poly_eval_u64(&coeffs, eval_point);
let eval_value_bytes = eval_value_u64.to_le_bytes();
let mut cm_hasher = Sha256::new();
cm_hasher.update(eval_value_bytes);
cm_hasher.update(statement_hash);
let commitment: [u8; 32] = cm_hasher.finalize().into();
let chunks = witness_to_chunks(witness);
let chunk_leaves: Vec<Vec<u8>> = chunks.iter().map(|c| {
Sha256::digest(c).to_vec()
}).collect();
let merkle_root = merkle_root_of(&chunk_leaves);
let eval_index = (eval_point as usize) % chunk_leaves.len();
let path = merkle_path_of(&chunk_leaves, eval_index);
let lyap_raw = u64::from_le_bytes(chaos_seed[8..16].try_into().unwrap_or([0u8; 8]));
let lyapunov = 4.5 + (lyap_raw as f64 / u64::MAX as f64);
let proof_size = 32 + 8 + 32 + path.len() * 32;
let path_hex: Vec<String> = path.iter().map(hex::encode).collect();
Ok(StarkProof {
commitment: hex::encode(commitment),
eval_point,
eval_value: hex::encode(eval_value_bytes),
merkle_root: hex::encode(&merkle_root),
merkle_path: path_hex,
eval_index,
public_inputs: hex::encode(statement_hash),
proof_system: "pqcprivacy-stark-fri-v1".into(),
security_bits: 128,
proof_size,
lyapunov,
chaos_seed_hex: hex::encode(chaos_seed),
})
}
#[cfg(test)]
mod tests {
use super::*;
use sha2::{Digest, Sha256};
#[test]
fn test_prove_verify_payload() {
let payload = b"test payload";
let hash: [u8; 32] = Sha256::digest(payload).into();
let seed = [0u8; 32];
let proof = prove_payload_hash(payload, hash, &seed).unwrap();
assert!(verify_payload_hash(&proof, &hash, &seed).is_ok());
}
#[test]
fn test_wrong_hash_fails() {
let payload = b"test payload";
let wrong_hash = [0u8; 32];
let seed = [0u8; 32];
assert!(prove_payload_hash(payload, wrong_hash, &seed).is_err());
}
#[test]
fn test_prove_statement() {
let stmt: [u8; 32] = Sha256::digest(b"statement").into();
let wit: [u8; 32] = Sha256::digest(b"witness").into();
let seed = [1u8; 32];
let proof = prove_statement(stmt, wit, &seed).unwrap();
assert!(verify_statement(&proof, &stmt, &seed).is_ok());
}
#[test]
fn test_verify_wrong_statement_fails() {
let stmt: [u8; 32] = Sha256::digest(b"statement").into();
let wrong: [u8; 32] = Sha256::digest(b"wrong").into();
let wit: [u8; 32] = Sha256::digest(b"witness").into();
let seed = [1u8; 32];
let proof = prove_statement(stmt, wit, &seed).unwrap();
assert!(verify_statement(&proof, &wrong, &seed).is_err());
}
#[test]
fn test_poly_eval_zero_point() {
let coeffs = alloc::vec![42u64, 7, 3];
assert_eq!(poly_eval_u64(&coeffs, 0), 42);
}
#[test]
fn test_poly_eval_one_point() {
let coeffs = alloc::vec![1u64, 2, 3];
assert_eq!(poly_eval_u64(&coeffs, 1), 6);
}
#[test]
fn test_merkle_path_verify_in_stark() {
let stmt: [u8; 32] = Sha256::digest(b"stmt").into();
let wit = b"witness data for merkle test with enough bytes to make multiple chunks here";
let seed = [2u8; 32];
let proof = prove_statement(stmt, *<&[u8; 32]>::try_from(&Sha256::digest(wit)[..]).unwrap(), &seed).unwrap();
let root = hex::decode(&proof.merkle_root).unwrap();
let path: Vec<Vec<u8>> = proof.merkle_path.iter()
.map(|s| hex::decode(s).unwrap())
.collect();
let _ = (root, path);
assert!(!proof.commitment.is_empty());
}
#[test]
fn test_proof_includes_correct_chaos_seed_hex() {
let payload = b"test payload for chaos seed";
let hash: [u8; 32] = Sha256::digest(payload).into();
let seed = [7u8; 32];
let proof = prove_payload_hash(payload, hash, &seed).unwrap();
assert_eq!(proof.chaos_seed_hex, hex::encode(seed));
}
#[test]
fn test_verify_payload_hash_self_describing_succeeds() {
let payload = b"self describing payload";
let hash: [u8; 32] = Sha256::digest(payload).into();
let seed = [9u8; 32];
let proof = prove_payload_hash(payload, hash, &seed).unwrap();
assert!(verify_payload_hash_self_describing(&proof, &hash).is_ok());
}
#[test]
fn test_verify_statement_self_describing_succeeds() {
let stmt: [u8; 32] = Sha256::digest(b"self describing statement").into();
let wit: [u8; 32] = Sha256::digest(b"self describing witness").into();
let seed = [11u8; 32];
let proof = prove_statement(stmt, wit, &seed).unwrap();
assert!(verify_statement_self_describing(&proof, &stmt).is_ok());
}
#[test]
fn test_self_describing_verify_matches_explicit_seed_verify() {
let payload = b"cross-check payload";
let hash: [u8; 32] = Sha256::digest(payload).into();
let seed = [13u8; 32];
let proof = prove_payload_hash(payload, hash, &seed).unwrap();
assert!(verify_payload_hash(&proof, &hash, &seed).is_ok());
assert!(verify_payload_hash_self_describing(&proof, &hash).is_ok());
}
#[test]
fn test_tampered_chaos_seed_hex_fails_verification_not_panic() {
let payload = b"tamper test payload";
let hash: [u8; 32] = Sha256::digest(payload).into();
let seed = [3u8; 32];
let mut proof = prove_payload_hash(payload, hash, &seed).unwrap();
proof.chaos_seed_hex = hex::encode([4u8; 32]);
let result = verify_payload_hash_self_describing(&proof, &hash);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), PrivacyError::ProofVerificationFailed));
}
#[test]
fn test_malformed_chaos_seed_hex_fails_not_panics() {
let payload = b"malformed seed test payload";
let hash: [u8; 32] = Sha256::digest(payload).into();
let seed = [5u8; 32];
let mut proof = prove_payload_hash(payload, hash, &seed).unwrap();
proof.chaos_seed_hex = "not-hex!!".into();
let result = verify_payload_hash_self_describing(&proof, &hash);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), PrivacyError::InvalidProofEncoding));
proof.chaos_seed_hex = hex::encode([6u8; 16]);
let result2 = verify_payload_hash_self_describing(&proof, &hash);
assert!(result2.is_err());
assert!(matches!(result2.unwrap_err(), PrivacyError::InvalidProofEncoding));
}
#[test]
fn test_stark_proof_serde_roundtrip_with_chaos_seed_hex() {
let payload = b"serde roundtrip payload";
let hash: [u8; 32] = Sha256::digest(payload).into();
let seed = [8u8; 32];
let proof = prove_payload_hash(payload, hash, &seed).unwrap();
let json = serde_json::to_string(&proof).unwrap();
let restored: StarkProof = serde_json::from_str(&json).unwrap();
assert_eq!(restored.chaos_seed_hex, proof.chaos_seed_hex);
assert_eq!(restored.chaos_seed_hex, hex::encode(seed));
assert!(verify_payload_hash_self_describing(&restored, &hash).is_ok());
}
#[test]
fn test_missing_chaos_seed_hex_deserializes_to_empty_default() {
let payload = b"legacy v0.1.0 payload";
let hash: [u8; 32] = Sha256::digest(payload).into();
let seed = [10u8; 32];
let proof = prove_payload_hash(payload, hash, &seed).unwrap();
let mut value: serde_json::Value = serde_json::to_value(&proof).unwrap();
value.as_object_mut().unwrap().remove("chaos_seed_hex");
let restored: StarkProof = serde_json::from_value(value).unwrap();
assert_eq!(restored.chaos_seed_hex, "");
let result = verify_payload_hash_self_describing(&restored, &hash);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), PrivacyError::InvalidProofEncoding));
}
}