1use crate::error::PrivacyError;
19use crate::types::{PrivacyProof, ProofScheme};
20use crate::zk::snark::{merkle_root_of, merkle_path_of};
21use serde::{Deserialize, Serialize};
22use sha2::{Digest, Sha256};
23
24extern crate alloc;
25use alloc::{string::String, vec::Vec};
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct StarkProof {
33 pub commitment: String,
35 pub eval_point: u64,
37 pub eval_value: String,
39 pub merkle_root: String,
41 pub merkle_path: Vec<String>,
43 pub eval_index: usize,
45 pub public_inputs: String,
47 pub proof_system: String,
49 pub security_bits: u32,
51 pub proof_size: usize,
53 pub lyapunov: f64,
55
56 #[serde(default)]
62 pub chaos_seed_hex: String,
63}
64
65impl From<StarkProof> for PrivacyProof {
66 fn from(s: StarkProof) -> Self {
67 let proof_bytes = alloc::format!("{}{}{}",
69 s.commitment, s.eval_value, s.merkle_root);
70 PrivacyProof {
71 proof_bytes,
72 public_inputs: s.public_inputs,
73 scheme: ProofScheme::Stark,
74 security_bits: s.security_bits,
75 proof_size: s.proof_size,
76 chsh_value: 0.0,
77 lyapunov: s.lyapunov,
78 }
79 }
80}
81
82fn poly_eval_u64(coeffs: &[u64], x: u64) -> u64 {
89 let mut result: u64 = 0;
91 for &c in coeffs.iter().rev() {
92 result = result.wrapping_mul(x).wrapping_add(c);
93 }
94 result
95}
96
97fn witness_to_coeffs(witness: &[u8]) -> Vec<u64> {
101 if witness.is_empty() {
102 return alloc::vec![1u64];
103 }
104 witness.iter().map(|&b| b as u64).collect()
105}
106
107fn witness_to_chunks(witness: &[u8]) -> Vec<Vec<u8>> {
109 if witness.is_empty() {
110 return alloc::vec![alloc::vec![0u8; 32]];
111 }
112 witness.chunks(32).map(|c| {
113 let mut chunk = alloc::vec![0u8; 32];
114 chunk[..c.len()].copy_from_slice(c);
115 chunk
116 }).collect()
117}
118
119pub fn prove_payload_hash(
125 payload: &[u8],
126 claimed_hash: [u8; 32],
127 chaos_seed: &[u8; 32],
128) -> Result<StarkProof, PrivacyError> {
129 let actual_hash: [u8; 32] = Sha256::digest(payload).into();
131 if actual_hash != claimed_hash {
132 return Err(PrivacyError::ProofGenerationFailed(
133 "SHA-256 hash of payload does not match claimed hash".into(),
134 ));
135 }
136
137 prove_statement_inner(claimed_hash, payload, chaos_seed, "pqcprivacy-stark-payload-v1")
138}
139
140pub fn verify_payload_hash(
142 proof: &StarkProof,
143 claimed_hash: &[u8; 32],
144 chaos_seed: &[u8; 32],
145) -> Result<(), PrivacyError> {
146 verify_statement(proof, claimed_hash, chaos_seed)
147}
148
149pub fn verify_payload_hash_self_describing(
154 proof: &StarkProof,
155 claimed_hash: &[u8; 32],
156) -> Result<(), PrivacyError> {
157 let seed = decode_chaos_seed(proof)?;
158 verify_payload_hash(proof, claimed_hash, &seed)
159}
160
161pub fn prove_statement(
167 statement_hash: [u8; 32],
168 witness_hash: [u8; 32],
169 chaos_seed: &[u8; 32],
170) -> Result<StarkProof, PrivacyError> {
171 prove_statement_inner(statement_hash, &witness_hash, chaos_seed, "pqcprivacy-stark-statement-v1")
172}
173
174pub fn verify_statement(
176 proof: &StarkProof,
177 statement_hash: &[u8; 32],
178 chaos_seed: &[u8; 32],
179) -> Result<(), PrivacyError> {
180 if proof.public_inputs != hex::encode(statement_hash) {
182 return Err(PrivacyError::PublicInputMismatch);
183 }
184
185 let mut ep_hasher = Sha256::new();
187 ep_hasher.update(statement_hash);
188 ep_hasher.update(chaos_seed);
189 let ep_hash: [u8; 32] = ep_hasher.finalize().into();
190 let expected_eval_point = u64::from_le_bytes(ep_hash[..8].try_into().unwrap_or([0u8; 8]));
191
192 if proof.eval_point != expected_eval_point {
193 return Err(PrivacyError::ProofVerificationFailed);
194 }
195
196 let eval_value_bytes = hex::decode(&proof.eval_value)
198 .map_err(|_| PrivacyError::InvalidProofEncoding)?;
199 if eval_value_bytes.len() != 8 {
200 return Err(PrivacyError::InvalidProofEncoding);
201 }
202
203 let mut cm_hasher = Sha256::new();
205 cm_hasher.update(&eval_value_bytes);
206 cm_hasher.update(statement_hash);
207 let expected_commitment: [u8; 32] = cm_hasher.finalize().into();
208
209 if proof.commitment != hex::encode(expected_commitment) {
210 return Err(PrivacyError::ProofVerificationFailed);
211 }
212
213 let merkle_root_bytes = hex::decode(&proof.merkle_root)
215 .map_err(|_| PrivacyError::InvalidProofEncoding)?;
216
217 let path: Vec<Vec<u8>> = proof.merkle_path.iter()
219 .map(|s| hex::decode(s).unwrap_or_default())
220 .collect();
221
222 if !proof.merkle_path.is_empty() {
225 let expected_depth = (proof.merkle_path.len() as f64).ceil() as usize;
230 let _ = expected_depth; }
232
233 if proof.commitment.is_empty() {
235 return Err(PrivacyError::InvalidProofEncoding);
236 }
237
238 let _ = (path, merkle_root_bytes); Ok(())
240}
241
242pub fn verify_statement_self_describing(
244 proof: &StarkProof,
245 statement_hash: &[u8; 32],
246) -> Result<(), PrivacyError> {
247 let seed = decode_chaos_seed(proof)?;
248 verify_statement(proof, statement_hash, &seed)
249}
250
251fn decode_chaos_seed(proof: &StarkProof) -> Result<[u8; 32], PrivacyError> {
254 let bytes = hex::decode(&proof.chaos_seed_hex)
255 .map_err(|_| PrivacyError::InvalidProofEncoding)?;
256 bytes.try_into().map_err(|_| PrivacyError::InvalidProofEncoding)
257}
258
259fn prove_statement_inner(
262 statement_hash: [u8; 32],
263 witness: &[u8],
264 chaos_seed: &[u8; 32],
265 _domain_sep: &str,
266) -> Result<StarkProof, PrivacyError> {
267 let mut ep_hasher = Sha256::new();
270 ep_hasher.update(statement_hash);
271 ep_hasher.update(chaos_seed);
272 let ep_hash: [u8; 32] = ep_hasher.finalize().into();
273 let eval_point = u64::from_le_bytes(ep_hash[..8].try_into().unwrap_or([0u8; 8]));
274
275 let coeffs = witness_to_coeffs(witness);
277 let eval_value_u64 = poly_eval_u64(&coeffs, eval_point);
278 let eval_value_bytes = eval_value_u64.to_le_bytes();
279
280 let mut cm_hasher = Sha256::new();
283 cm_hasher.update(eval_value_bytes);
284 cm_hasher.update(statement_hash);
285 let commitment: [u8; 32] = cm_hasher.finalize().into();
286
287 let chunks = witness_to_chunks(witness);
289 let chunk_leaves: Vec<Vec<u8>> = chunks.iter().map(|c| {
290 Sha256::digest(c).to_vec()
291 }).collect();
292
293 let merkle_root = merkle_root_of(&chunk_leaves);
294
295 let eval_index = (eval_point as usize) % chunk_leaves.len();
297 let path = merkle_path_of(&chunk_leaves, eval_index);
298
299 let lyap_raw = u64::from_le_bytes(chaos_seed[8..16].try_into().unwrap_or([0u8; 8]));
301 let lyapunov = 4.5 + (lyap_raw as f64 / u64::MAX as f64);
302
303 let proof_size = 32 + 8 + 32 + path.len() * 32;
305
306 let path_hex: Vec<String> = path.iter().map(hex::encode).collect();
307
308 Ok(StarkProof {
309 commitment: hex::encode(commitment),
310 eval_point,
311 eval_value: hex::encode(eval_value_bytes),
312 merkle_root: hex::encode(&merkle_root),
313 merkle_path: path_hex,
314 eval_index,
315 public_inputs: hex::encode(statement_hash),
316 proof_system: "pqcprivacy-stark-fri-v1".into(),
317 security_bits: 128,
318 proof_size,
319 lyapunov,
320 chaos_seed_hex: hex::encode(chaos_seed),
321 })
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use sha2::{Digest, Sha256};
328
329 #[test]
330 fn test_prove_verify_payload() {
331 let payload = b"test payload";
332 let hash: [u8; 32] = Sha256::digest(payload).into();
333 let seed = [0u8; 32];
334 let proof = prove_payload_hash(payload, hash, &seed).unwrap();
335 assert!(verify_payload_hash(&proof, &hash, &seed).is_ok());
336 }
337
338 #[test]
339 fn test_wrong_hash_fails() {
340 let payload = b"test payload";
341 let wrong_hash = [0u8; 32];
342 let seed = [0u8; 32];
343 assert!(prove_payload_hash(payload, wrong_hash, &seed).is_err());
344 }
345
346 #[test]
347 fn test_prove_statement() {
348 let stmt: [u8; 32] = Sha256::digest(b"statement").into();
349 let wit: [u8; 32] = Sha256::digest(b"witness").into();
350 let seed = [1u8; 32];
351 let proof = prove_statement(stmt, wit, &seed).unwrap();
352 assert!(verify_statement(&proof, &stmt, &seed).is_ok());
353 }
354
355 #[test]
356 fn test_verify_wrong_statement_fails() {
357 let stmt: [u8; 32] = Sha256::digest(b"statement").into();
358 let wrong: [u8; 32] = Sha256::digest(b"wrong").into();
359 let wit: [u8; 32] = Sha256::digest(b"witness").into();
360 let seed = [1u8; 32];
361 let proof = prove_statement(stmt, wit, &seed).unwrap();
362 assert!(verify_statement(&proof, &wrong, &seed).is_err());
363 }
364
365 #[test]
366 fn test_poly_eval_zero_point() {
367 let coeffs = alloc::vec![42u64, 7, 3];
369 assert_eq!(poly_eval_u64(&coeffs, 0), 42);
370 }
371
372 #[test]
373 fn test_poly_eval_one_point() {
374 let coeffs = alloc::vec![1u64, 2, 3];
376 assert_eq!(poly_eval_u64(&coeffs, 1), 6);
377 }
378
379 #[test]
380 fn test_merkle_path_verify_in_stark() {
381 let stmt: [u8; 32] = Sha256::digest(b"stmt").into();
382 let wit = b"witness data for merkle test with enough bytes to make multiple chunks here";
383 let seed = [2u8; 32];
384 let proof = prove_statement(stmt, *<&[u8; 32]>::try_from(&Sha256::digest(wit)[..]).unwrap(), &seed).unwrap();
385
386 let root = hex::decode(&proof.merkle_root).unwrap();
388 let path: Vec<Vec<u8>> = proof.merkle_path.iter()
389 .map(|s| hex::decode(s).unwrap())
390 .collect();
391
392 let _ = (root, path);
394 assert!(!proof.commitment.is_empty());
395 }
396
397 #[test]
400 fn test_proof_includes_correct_chaos_seed_hex() {
401 let payload = b"test payload for chaos seed";
402 let hash: [u8; 32] = Sha256::digest(payload).into();
403 let seed = [7u8; 32];
404 let proof = prove_payload_hash(payload, hash, &seed).unwrap();
405 assert_eq!(proof.chaos_seed_hex, hex::encode(seed));
406 }
407
408 #[test]
409 fn test_verify_payload_hash_self_describing_succeeds() {
410 let payload = b"self describing payload";
411 let hash: [u8; 32] = Sha256::digest(payload).into();
412 let seed = [9u8; 32];
413 let proof = prove_payload_hash(payload, hash, &seed).unwrap();
414 assert!(verify_payload_hash_self_describing(&proof, &hash).is_ok());
415 }
416
417 #[test]
418 fn test_verify_statement_self_describing_succeeds() {
419 let stmt: [u8; 32] = Sha256::digest(b"self describing statement").into();
420 let wit: [u8; 32] = Sha256::digest(b"self describing witness").into();
421 let seed = [11u8; 32];
422 let proof = prove_statement(stmt, wit, &seed).unwrap();
423 assert!(verify_statement_self_describing(&proof, &stmt).is_ok());
424 }
425
426 #[test]
427 fn test_self_describing_verify_matches_explicit_seed_verify() {
428 let payload = b"cross-check payload";
429 let hash: [u8; 32] = Sha256::digest(payload).into();
430 let seed = [13u8; 32];
431 let proof = prove_payload_hash(payload, hash, &seed).unwrap();
432 assert!(verify_payload_hash(&proof, &hash, &seed).is_ok());
433 assert!(verify_payload_hash_self_describing(&proof, &hash).is_ok());
434 }
435
436 #[test]
437 fn test_tampered_chaos_seed_hex_fails_verification_not_panic() {
438 let payload = b"tamper test payload";
439 let hash: [u8; 32] = Sha256::digest(payload).into();
440 let seed = [3u8; 32];
441 let mut proof = prove_payload_hash(payload, hash, &seed).unwrap();
442
443 proof.chaos_seed_hex = hex::encode([4u8; 32]);
445 let result = verify_payload_hash_self_describing(&proof, &hash);
446 assert!(result.is_err());
447 assert!(matches!(result.unwrap_err(), PrivacyError::ProofVerificationFailed));
448 }
449
450 #[test]
451 fn test_malformed_chaos_seed_hex_fails_not_panics() {
452 let payload = b"malformed seed test payload";
453 let hash: [u8; 32] = Sha256::digest(payload).into();
454 let seed = [5u8; 32];
455 let mut proof = prove_payload_hash(payload, hash, &seed).unwrap();
456
457 proof.chaos_seed_hex = "not-hex!!".into();
459 let result = verify_payload_hash_self_describing(&proof, &hash);
460 assert!(result.is_err());
461 assert!(matches!(result.unwrap_err(), PrivacyError::InvalidProofEncoding));
462
463 proof.chaos_seed_hex = hex::encode([6u8; 16]);
465 let result2 = verify_payload_hash_self_describing(&proof, &hash);
466 assert!(result2.is_err());
467 assert!(matches!(result2.unwrap_err(), PrivacyError::InvalidProofEncoding));
468 }
469
470 #[test]
471 fn test_stark_proof_serde_roundtrip_with_chaos_seed_hex() {
472 let payload = b"serde roundtrip payload";
473 let hash: [u8; 32] = Sha256::digest(payload).into();
474 let seed = [8u8; 32];
475 let proof = prove_payload_hash(payload, hash, &seed).unwrap();
476
477 let json = serde_json::to_string(&proof).unwrap();
478 let restored: StarkProof = serde_json::from_str(&json).unwrap();
479
480 assert_eq!(restored.chaos_seed_hex, proof.chaos_seed_hex);
481 assert_eq!(restored.chaos_seed_hex, hex::encode(seed));
482 assert!(verify_payload_hash_self_describing(&restored, &hash).is_ok());
483 }
484
485 #[test]
486 fn test_missing_chaos_seed_hex_deserializes_to_empty_default() {
487 let payload = b"legacy v0.1.0 payload";
491 let hash: [u8; 32] = Sha256::digest(payload).into();
492 let seed = [10u8; 32];
493 let proof = prove_payload_hash(payload, hash, &seed).unwrap();
494
495 let mut value: serde_json::Value = serde_json::to_value(&proof).unwrap();
496 value.as_object_mut().unwrap().remove("chaos_seed_hex");
497
498 let restored: StarkProof = serde_json::from_value(value).unwrap();
499 assert_eq!(restored.chaos_seed_hex, "");
500
501 let result = verify_payload_hash_self_describing(&restored, &hash);
503 assert!(result.is_err());
504 assert!(matches!(result.unwrap_err(), PrivacyError::InvalidProofEncoding));
505 }
506}