1use crate::error::{OptimError, Result};
24use scirs2_core::ndarray::Array1;
25use scirs2_core::numeric::Float;
26use std::collections::HashMap;
27use std::fmt::Debug;
28use std::time::{SystemTime, UNIX_EPOCH};
29
30use super::hashing::{
31 canonical_array_bytes, digests_equal, hmac_sha256, random_key, sha256, Digest32,
32};
33use super::types::{
34 CryptographicKeys, CryptographicProof, CryptographicProofType, ProofAlgorithm,
35 ProofRequirements,
36};
37
38pub const SHA256_INTEGRITY: &str = "sha256-integrity";
40pub const HMAC_SHA256_INTEGRITY: &str = "hmac-sha256-integrity";
42
43pub(super) fn unix_timestamp() -> Result<u64> {
45 SystemTime::now()
46 .duration_since(UNIX_EPOCH)
47 .map(|elapsed| elapsed.as_secs())
48 .map_err(|err| {
49 OptimError::InvalidState(format!(
50 "the system clock is set before the Unix epoch, so audit records cannot be \
51 timestamped: {err}"
52 ))
53 })
54}
55
56impl CryptographicKeys {
57 pub fn new() -> Self {
59 Self {
60 signing_keys: HashMap::new(),
61 verification_keys: HashMap::new(),
62 encryption_keys: HashMap::new(),
63 }
64 }
65
66 pub fn generate() -> Self {
75 let key = random_key();
76 let mut signing_keys = HashMap::new();
77 signing_keys.insert(HMAC_SHA256_INTEGRITY.to_string(), key.to_vec());
78 let mut verification_keys = HashMap::new();
79 verification_keys.insert(HMAC_SHA256_INTEGRITY.to_string(), key.to_vec());
80 Self {
81 signing_keys,
82 verification_keys,
83 encryption_keys: HashMap::new(),
84 }
85 }
86
87 pub fn mac_key(&self, name: &str) -> Option<&[u8]> {
89 self.signing_keys.get(name).map(|key| key.as_slice())
90 }
91}
92
93impl Default for CryptographicKeys {
94 fn default() -> Self {
95 Self::new()
96 }
97}
98
99pub struct CryptographicProofGenerator<T: Float + Debug + Send + Sync + 'static> {
101 proof_types: HashMap<String, CryptographicProofType<T>>,
103 keys: CryptographicKeys,
105}
106
107impl<T: Float + Debug + Send + Sync + 'static> CryptographicProofGenerator<T> {
108 pub fn new() -> Self {
111 let keys = CryptographicKeys::generate();
112 let mut generator = Self {
113 proof_types: HashMap::new(),
114 keys,
115 };
116 generator.register_integrity_proof_types();
117 generator
118 }
119
120 pub fn empty() -> Self {
125 Self {
126 proof_types: HashMap::new(),
127 keys: CryptographicKeys::new(),
128 }
129 }
130
131 fn register_integrity_proof_types(&mut self) {
133 self.proof_types.insert(
134 SHA256_INTEGRITY.to_string(),
135 CryptographicProofType {
136 name: SHA256_INTEGRITY.to_string(),
137 generate_fn: Box::new(|data: &Array1<T>, _keys: &CryptographicKeys| {
138 let bytes = canonical_array_bytes(data)?;
139 let digest = sha256(&[&bytes]);
140 let mut metadata = HashMap::new();
141 metadata.insert("algorithm".to_string(), "SHA-256".to_string());
142 metadata.insert("element_count".to_string(), data.len().to_string());
143 Ok(CryptographicProof {
144 prooftype: SHA256_INTEGRITY.to_string(),
145 proof_data: digest.to_vec(),
146 public_params: (data.len() as u64).to_le_bytes().to_vec(),
147 timestamp: unix_timestamp()?,
148 metadata,
149 })
150 }),
151 verify_fn: Box::new(
152 |proof: &CryptographicProof, data: &Array1<T>, _keys: &CryptographicKeys| {
153 let Ok(bytes) = canonical_array_bytes(data) else {
154 return false;
155 };
156 let digest = sha256(&[&bytes]);
157 proof.proof_data.len() == digest.len()
158 && digest_matches(&proof.proof_data, &digest)
159 },
160 ),
161 },
162 );
163
164 self.proof_types.insert(
165 HMAC_SHA256_INTEGRITY.to_string(),
166 CryptographicProofType {
167 name: HMAC_SHA256_INTEGRITY.to_string(),
168 generate_fn: Box::new(|data: &Array1<T>, keys: &CryptographicKeys| {
169 let key = keys.mac_key(HMAC_SHA256_INTEGRITY).ok_or_else(|| {
170 OptimError::InvalidState(format!(
171 "no MAC key is registered under `{HMAC_SHA256_INTEGRITY}`"
172 ))
173 })?;
174 let bytes = canonical_array_bytes(data)?;
175 let tag = hmac_sha256(key, &bytes);
176 let mut metadata = HashMap::new();
177 metadata.insert("algorithm".to_string(), "HMAC-SHA-256".to_string());
178 metadata.insert("element_count".to_string(), data.len().to_string());
179 metadata.insert("non_repudiation".to_string(), "false".to_string());
180 Ok(CryptographicProof {
181 prooftype: HMAC_SHA256_INTEGRITY.to_string(),
182 proof_data: tag.to_vec(),
183 public_params: (data.len() as u64).to_le_bytes().to_vec(),
184 timestamp: unix_timestamp()?,
185 metadata,
186 })
187 }),
188 verify_fn: Box::new(
189 |proof: &CryptographicProof, data: &Array1<T>, keys: &CryptographicKeys| {
190 let Some(key) = keys.mac_key(HMAC_SHA256_INTEGRITY) else {
191 return false;
192 };
193 let Ok(bytes) = canonical_array_bytes(data) else {
194 return false;
195 };
196 let tag = hmac_sha256(key, &bytes);
197 proof.proof_data.len() == tag.len()
198 && digest_matches(&proof.proof_data, &tag)
199 },
200 ),
201 },
202 );
203 }
204
205 pub fn registered_proof_types(&self) -> Vec<String> {
207 let mut names: Vec<String> = self.proof_types.keys().cloned().collect();
208 names.sort();
209 names
210 }
211
212 pub fn register_proof_type(&mut self, proof_type: CryptographicProofType<T>) {
214 self.proof_types.insert(proof_type.name.clone(), proof_type);
215 }
216
217 pub fn generate_proof(&self, prooftype: &str, data: &Array1<T>) -> Result<CryptographicProof> {
219 if self.proof_types.is_empty() {
220 return Err(OptimError::InvalidState(
221 "no cryptographic proof types are registered, so no proof can be produced"
222 .to_string(),
223 ));
224 }
225 let generator = self.proof_types.get(prooftype).ok_or_else(|| {
226 OptimError::InvalidConfig(format!(
227 "unknown proof type `{prooftype}`; registered types: {:?}",
228 self.registered_proof_types()
229 ))
230 })?;
231 (generator.generate_fn)(data, &self.keys)
232 }
233
234 pub fn verify_proof(&self, proof: &CryptographicProof, data: &Array1<T>) -> Result<bool> {
236 let verifier = self.proof_types.get(&proof.prooftype).ok_or_else(|| {
237 OptimError::InvalidConfig(format!(
238 "unknown proof type `{}`; registered types: {:?}",
239 proof.prooftype,
240 self.registered_proof_types()
241 ))
242 })?;
243 Ok((verifier.verify_fn)(proof, data, &self.keys))
244 }
245
246 pub fn check_requirements(&self, requirements: &ProofRequirements) -> Result<()> {
252 let mut missing = Vec::new();
253 if requirements.zero_knowledge_proofs {
254 missing.push("zero_knowledge_proofs (needs a zero-knowledge proving system)");
255 }
256 if requirements.non_repudiation {
257 missing.push(
258 "non_repudiation (needs an asymmetric signature scheme; HMAC is symmetric and \
259 cannot bind a single signer)",
260 );
261 }
262 if requirements.confidentiality_proofs {
263 missing.push("confidentiality_proofs (needs an authenticated encryption scheme)");
264 }
265 if missing.is_empty() {
266 Ok(())
267 } else {
268 Err(OptimError::UnsupportedOperation(format!(
269 "the audit configuration requests proof guarantees this build cannot provide: {}",
270 missing.join("; ")
271 )))
272 }
273 }
274}
275
276impl<T: Float + Debug + Send + Sync + 'static> Default for CryptographicProofGenerator<T> {
277 fn default() -> Self {
278 Self::new()
279 }
280}
281
282fn digest_matches(proof: &[u8], digest: &Digest32) -> bool {
284 if proof.len() != digest.len() {
285 return false;
286 }
287 let mut fixed = [0u8; 32];
288 fixed.copy_from_slice(proof);
289 digests_equal(&fixed, digest)
290}
291
292pub struct ProofSystem<T: Float + Debug + Send + Sync + 'static> {
294 algorithms: HashMap<String, ProofAlgorithm<T>>,
296 verification_keys: HashMap<String, Vec<u8>>,
298}
299
300impl<T: Float + Debug + Send + Sync + 'static> ProofSystem<T> {
301 pub fn new() -> Self {
303 let key = random_key();
304 let mut verification_keys = HashMap::new();
305 verification_keys.insert(HMAC_SHA256_INTEGRITY.to_string(), key.to_vec());
306
307 let mut algorithms = HashMap::new();
308 algorithms.insert(
309 SHA256_INTEGRITY.to_string(),
310 ProofAlgorithm {
311 name: SHA256_INTEGRITY.to_string(),
312 generate_fn: Box::new(|data: &Array1<T>| {
313 let bytes = canonical_array_bytes(data)?;
314 Ok(sha256(&[&bytes]).to_vec())
315 }),
316 verify_fn: Box::new(|proof: &[u8], data: &Array1<T>| {
317 let Ok(bytes) = canonical_array_bytes(data) else {
318 return false;
319 };
320 digest_matches(proof, &sha256(&[&bytes]))
321 }),
322 },
323 );
324 let mac_key = key;
325 algorithms.insert(
326 HMAC_SHA256_INTEGRITY.to_string(),
327 ProofAlgorithm {
328 name: HMAC_SHA256_INTEGRITY.to_string(),
329 generate_fn: Box::new(move |data: &Array1<T>| {
330 let bytes = canonical_array_bytes(data)?;
331 Ok(hmac_sha256(&mac_key, &bytes).to_vec())
332 }),
333 verify_fn: Box::new(move |proof: &[u8], data: &Array1<T>| {
334 let Ok(bytes) = canonical_array_bytes(data) else {
335 return false;
336 };
337 digest_matches(proof, &hmac_sha256(&mac_key, &bytes))
338 }),
339 },
340 );
341
342 Self {
343 algorithms,
344 verification_keys,
345 }
346 }
347
348 pub fn empty() -> Self {
350 Self {
351 algorithms: HashMap::new(),
352 verification_keys: HashMap::new(),
353 }
354 }
355
356 pub fn registered_algorithms(&self) -> Vec<String> {
358 let mut names: Vec<String> = self.algorithms.keys().cloned().collect();
359 names.sort();
360 names
361 }
362
363 pub fn register_algorithm(&mut self, algorithm: ProofAlgorithm<T>) {
365 self.algorithms.insert(algorithm.name.clone(), algorithm);
366 }
367
368 pub fn verification_key(&self, name: &str) -> Option<&[u8]> {
370 self.verification_keys.get(name).map(|key| key.as_slice())
371 }
372
373 pub fn generate(&self, algorithm: &str, data: &Array1<T>) -> Result<Vec<u8>> {
375 if self.algorithms.is_empty() {
376 return Err(OptimError::InvalidState(
377 "no proof algorithms are registered with the proof system".to_string(),
378 ));
379 }
380 let entry = self.algorithms.get(algorithm).ok_or_else(|| {
381 OptimError::InvalidConfig(format!(
382 "unknown proof algorithm `{algorithm}`; registered: {:?}",
383 self.registered_algorithms()
384 ))
385 })?;
386 (entry.generate_fn)(data)
387 }
388
389 pub fn verify(&self, algorithm: &str, proof: &[u8], data: &Array1<T>) -> Result<bool> {
391 let entry = self.algorithms.get(algorithm).ok_or_else(|| {
392 OptimError::InvalidConfig(format!(
393 "unknown proof algorithm `{algorithm}`; registered: {:?}",
394 self.registered_algorithms()
395 ))
396 })?;
397 Ok((entry.verify_fn)(proof, data))
398 }
399}
400
401impl<T: Float + Debug + Send + Sync + 'static> Default for ProofSystem<T> {
402 fn default() -> Self {
403 Self::new()
404 }
405}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410
411 fn data() -> Array1<f64> {
412 Array1::from(vec![1.0, -2.5, 3.75, 0.0])
413 }
414
415 #[test]
416 fn a_generated_integrity_proof_verifies_and_a_tampered_value_does_not() {
417 let generator: CryptographicProofGenerator<f64> = CryptographicProofGenerator::new();
418 for prooftype in [SHA256_INTEGRITY, HMAC_SHA256_INTEGRITY] {
419 let proof = match generator.generate_proof(prooftype, &data()) {
420 Ok(proof) => proof,
421 Err(err) => panic!("{prooftype} generation failed: {err}"),
422 };
423 assert_eq!(proof.proof_data.len(), 32);
424 match generator.verify_proof(&proof, &data()) {
425 Ok(true) => {}
426 Ok(false) => panic!("{prooftype} must verify against its own input"),
427 Err(err) => panic!("{prooftype} verification failed: {err}"),
428 }
429
430 let mut tampered = data();
432 tampered[2] = f64::from_bits(tampered[2].to_bits() ^ 1);
433 match generator.verify_proof(&proof, &tampered) {
434 Ok(false) => {}
435 Ok(true) => panic!("{prooftype} must not verify against modified data"),
436 Err(err) => panic!("{prooftype} verification failed: {err}"),
437 }
438
439 let shorter = Array1::from(vec![1.0, -2.5, 3.75]);
441 match generator.verify_proof(&proof, &shorter) {
442 Ok(false) => {}
443 Ok(true) => panic!("{prooftype} must commit to the length"),
444 Err(err) => panic!("{prooftype} verification failed: {err}"),
445 }
446 }
447 }
448
449 #[test]
450 fn a_generator_with_no_proof_types_fails_instead_of_succeeding() {
451 let generator: CryptographicProofGenerator<f64> = CryptographicProofGenerator::empty();
452 assert!(generator.generate_proof(SHA256_INTEGRITY, &data()).is_err());
453 assert!(generator.registered_proof_types().is_empty());
454 }
455
456 #[test]
457 fn an_unknown_proof_type_is_an_error() {
458 let generator: CryptographicProofGenerator<f64> = CryptographicProofGenerator::new();
459 assert!(generator.generate_proof("zk-snark", &data()).is_err());
460 }
461
462 #[test]
463 fn two_generators_use_independent_mac_keys() {
464 let left: CryptographicProofGenerator<f64> = CryptographicProofGenerator::new();
465 let right: CryptographicProofGenerator<f64> = CryptographicProofGenerator::new();
466 let left_proof = match left.generate_proof(HMAC_SHA256_INTEGRITY, &data()) {
467 Ok(proof) => proof,
468 Err(err) => panic!("generation failed: {err}"),
469 };
470 match right.verify_proof(&left_proof, &data()) {
472 Ok(false) => {}
473 Ok(true) => panic!("the MAC key must not be a shared constant"),
474 Err(err) => panic!("verification failed: {err}"),
475 }
476 }
477
478 #[test]
479 fn the_unkeyed_digest_is_reproducible_across_generators() {
480 let left: CryptographicProofGenerator<f64> = CryptographicProofGenerator::new();
481 let right: CryptographicProofGenerator<f64> = CryptographicProofGenerator::new();
482 let proof = match left.generate_proof(SHA256_INTEGRITY, &data()) {
483 Ok(proof) => proof,
484 Err(err) => panic!("generation failed: {err}"),
485 };
486 match right.verify_proof(&proof, &data()) {
487 Ok(true) => {}
488 Ok(false) => panic!("an unkeyed digest must be publicly verifiable"),
489 Err(err) => panic!("verification failed: {err}"),
490 }
491 }
492
493 #[test]
494 fn unmeetable_proof_requirements_are_refused() {
495 let generator: CryptographicProofGenerator<f64> = CryptographicProofGenerator::new();
496 let supported = ProofRequirements {
497 zero_knowledge_proofs: false,
498 non_repudiation: false,
499 integrity_proofs: true,
500 confidentiality_proofs: false,
501 completeness_proofs: true,
502 };
503 assert!(generator.check_requirements(&supported).is_ok());
504
505 for requirements in [
506 ProofRequirements {
507 zero_knowledge_proofs: true,
508 non_repudiation: false,
509 integrity_proofs: true,
510 confidentiality_proofs: false,
511 completeness_proofs: false,
512 },
513 ProofRequirements {
514 zero_knowledge_proofs: false,
515 non_repudiation: true,
516 integrity_proofs: true,
517 confidentiality_proofs: false,
518 completeness_proofs: false,
519 },
520 ProofRequirements {
521 zero_knowledge_proofs: false,
522 non_repudiation: false,
523 integrity_proofs: true,
524 confidentiality_proofs: true,
525 completeness_proofs: false,
526 },
527 ] {
528 assert!(
529 generator.check_requirements(&requirements).is_err(),
530 "an unimplementable guarantee must be refused, not silently granted"
531 );
532 }
533 }
534
535 #[test]
536 fn the_proof_system_generates_and_verifies_both_algorithms() {
537 let system: ProofSystem<f64> = ProofSystem::new();
538 for algorithm in [SHA256_INTEGRITY, HMAC_SHA256_INTEGRITY] {
539 let proof = match system.generate(algorithm, &data()) {
540 Ok(proof) => proof,
541 Err(err) => panic!("{algorithm} failed: {err}"),
542 };
543 match system.verify(algorithm, &proof, &data()) {
544 Ok(true) => {}
545 Ok(false) => panic!("{algorithm} must verify its own proof"),
546 Err(err) => panic!("{algorithm} verification failed: {err}"),
547 }
548 let mut tampered = data();
549 tampered[0] = 1.5;
550 match system.verify(algorithm, &proof, &tampered) {
551 Ok(false) => {}
552 Ok(true) => panic!("{algorithm} must reject modified data"),
553 Err(err) => panic!("{algorithm} verification failed: {err}"),
554 }
555 }
556 }
557
558 #[test]
559 fn an_empty_proof_system_reports_that_it_has_nothing_registered() {
560 let system: ProofSystem<f64> = ProofSystem::empty();
561 assert!(system.generate(SHA256_INTEGRITY, &data()).is_err());
562 assert!(system.registered_algorithms().is_empty());
563 }
564
565 #[test]
566 fn non_finite_values_cannot_be_committed_to() {
567 let system: ProofSystem<f64> = ProofSystem::new();
568 let with_nan = Array1::from(vec![f64::NAN, 1.0]);
573 let proof = match system.generate(SHA256_INTEGRITY, &with_nan) {
574 Ok(proof) => proof,
575 Err(err) => panic!("generation failed: {err}"),
576 };
577 match system.verify(SHA256_INTEGRITY, &proof, &with_nan) {
578 Ok(true) => {}
579 Ok(false) => panic!("a NaN commitment must be reproducible"),
580 Err(err) => panic!("verification failed: {err}"),
581 }
582 }
583}