Skip to main content

eth_id/attestation/
mod.rs

1use crate::error::{Result, EthIdError};
2use crate::parser::ParsedDocument;
3use crate::verifier::VerificationResult;
4use serde::{Deserialize, Serialize};
5use sha2::{Sha256, Digest};
6use std::path::PathBuf;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct AttestationBundle {
10    pub version: String,
11    pub session_id: String,
12    pub timestamp: chrono::DateTime<chrono::Utc>,
13    pub document_hash: String,
14    pub claim: String,
15    pub result: AttestationResult,
16    pub proof_type: ProofType,
17    pub bundle_hash: String,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct AttestationResult {
22    pub answer: bool,
23    pub confidence: f32,
24    pub reasoning: Option<String>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub enum ProofType {
29    ZeroKnowledge { circuit: String, proof: String },
30    LLM { provider: String, model: String },
31}
32
33impl AttestationBundle {
34    pub fn create(
35        session_id: &str,
36        document: &ParsedDocument,
37        claim: &str,
38        result: &VerificationResult,
39        use_zk: bool,
40    ) -> Result<Self> {
41        let mut hasher = Sha256::new();
42        hasher.update(document.raw_text().as_bytes());
43        let document_hash = format!("{:x}", hasher.finalize());
44        
45        let proof_type = if use_zk {
46            ProofType::ZeroKnowledge {
47                circuit: "age_check".to_string(),
48                proof: result.proof.clone().unwrap_or_default(),
49            }
50        } else {
51            ProofType::LLM {
52                provider: "openai".to_string(),
53                model: "gpt-4o".to_string(),
54            }
55        };
56        
57        let mut bundle = Self {
58            version: "1.0.0".to_string(),
59            session_id: session_id.to_string(),
60            timestamp: chrono::Utc::now(),
61            document_hash,
62            claim: claim.to_string(),
63            result: AttestationResult {
64                answer: result.answer,
65                confidence: result.confidence,
66                reasoning: result.reasoning.clone(),
67            },
68            proof_type,
69            bundle_hash: String::new(),
70        };
71        
72        bundle.bundle_hash = bundle.compute_hash();
73        
74        Ok(bundle)
75    }
76    
77    fn compute_hash(&self) -> String {
78        let mut hasher = Sha256::new();
79        hasher.update(self.version.as_bytes());
80        hasher.update(self.session_id.as_bytes());
81        hasher.update(self.timestamp.to_rfc3339().as_bytes());
82        hasher.update(self.document_hash.as_bytes());
83        hasher.update(self.claim.as_bytes());
84        hasher.update(self.result.answer.to_string().as_bytes());
85        format!("{:x}", hasher.finalize())
86    }
87    
88    pub fn save(&self) -> Result<PathBuf> {
89        let home_dir = dirs::home_dir()
90            .ok_or_else(|| EthIdError::Attestation("Cannot find home directory".to_string()))?;
91        
92        let attestations_dir = home_dir.join(".eth-id").join("attestations");
93        std::fs::create_dir_all(&attestations_dir)?;
94        
95        let filename = format!("attestation_{}.json", self.session_id);
96        let path = attestations_dir.join(filename);
97        
98        let json = serde_json::to_string_pretty(self)?;
99        std::fs::write(&path, json)?;
100        
101        Ok(path)
102    }
103    
104    pub fn load(session_id: &str) -> Result<Self> {
105        let home_dir = dirs::home_dir()
106            .ok_or_else(|| EthIdError::Attestation("Cannot find home directory".to_string()))?;
107        
108        let path = home_dir
109            .join(".eth-id")
110            .join("attestations")
111            .join(format!("attestation_{}.json", session_id));
112        
113        if !path.exists() {
114            return Err(EthIdError::Attestation(
115                format!("Attestation not found: {}", session_id)
116            ));
117        }
118        
119        let json = std::fs::read_to_string(path)?;
120        let bundle: Self = serde_json::from_str(&json)?;
121        
122        Ok(bundle)
123    }
124    
125    pub fn verify_integrity(&self) -> bool {
126        let computed_hash = self.compute_hash();
127        computed_hash == self.bundle_hash
128    }
129}