Skip to main content

eth_id/privacy/
filter.rs

1use crate::error::{Result, EthIdError};
2use crate::parser::ParsedDocument;
3use crate::claims::{ClaimQuery, ClaimType};
4use super::{DataMinimizer, DataVirtualizer, PrivacyMetadata};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
8pub enum FilterMode {
9    HashPartial,
10    Minimization,
11    Virtualization,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct FilteredData {
16    pub content: String,
17    pub metadata: PrivacyMetadata,
18    pub claim_context: String,
19}
20
21pub struct PrivacyFilter {
22    minimizer: DataMinimizer,
23    virtualizer: DataVirtualizer,
24}
25
26impl PrivacyFilter {
27    pub fn new() -> Self {
28        Self {
29            minimizer: DataMinimizer::new(),
30            virtualizer: DataVirtualizer::new(),
31        }
32    }
33    
34    pub fn filter(
35        &self,
36        document: &ParsedDocument,
37        claim_query: &ClaimQuery,
38    ) -> Result<FilteredData> {
39        let mode = self.determine_filter_mode(claim_query);
40        
41        let mut metadata = PrivacyMetadata::new(mode);
42        metadata.set_original_hash(document.raw_text());
43        
44        let (content, claim_context) = match mode {
45            FilterMode::HashPartial => {
46                tracing::debug!("Using hash partial mode");
47                self.apply_hash_partial(document, claim_query, &mut metadata)?
48            }
49            FilterMode::Minimization => {
50                tracing::debug!("Using minimization mode");
51                self.apply_minimization(document, claim_query, &mut metadata)?
52            }
53            FilterMode::Virtualization => {
54                tracing::debug!("Using virtualization mode");
55                self.apply_virtualization(document, claim_query, &mut metadata)?
56            }
57        };
58        
59        metadata.set_filtered_hash(&content);
60        
61        Ok(FilteredData {
62            content,
63            metadata,
64            claim_context,
65        })
66    }
67    
68    fn determine_filter_mode(&self, claim_query: &ClaimQuery) -> FilterMode {
69        match claim_query.claim_type() {
70            ClaimType::Date => FilterMode::Virtualization,
71            ClaimType::Identity => FilterMode::HashPartial,
72            ClaimType::Amount => FilterMode::Minimization,
73            ClaimType::Signature => FilterMode::Minimization,
74            ClaimType::Presence => FilterMode::Minimization,
75            ClaimType::Comparative => FilterMode::Minimization,
76        }
77    }
78    
79    fn apply_hash_partial(
80        &self,
81        document: &ParsedDocument,
82        claim_query: &ClaimQuery,
83        metadata: &mut PrivacyMetadata,
84    ) -> Result<(String, String)> {
85        if let ClaimQuery::Identity(identity_claim) = claim_query {
86            if let Some(cpf) = document.get_field("cpf") {
87                let masked_cpf = self.mask_cpf(cpf);
88                metadata.fields_included.push("cpf".to_string());
89                metadata.fields_masked.push("cpf".to_string());
90                
91                let content = format!("CPF: {}", masked_cpf);
92                let context = format!("Verify CPF identity claim");
93                
94                return Ok((content, context));
95            }
96        }
97        
98        Ok((String::new(), String::new()))
99    }
100    
101    fn apply_minimization(
102        &self,
103        document: &ParsedDocument,
104        claim_query: &ClaimQuery,
105        metadata: &mut PrivacyMetadata,
106    ) -> Result<(String, String)> {
107        let relevant_fields = self.minimizer.extract_relevant_fields(document, claim_query)?;
108        
109        for field in &relevant_fields {
110            metadata.fields_included.push(field.clone());
111        }
112        
113        let content = self.minimizer.build_minimal_context(document, &relevant_fields)?;
114        let context = format!("Minimal context for {:?} claim", claim_query.claim_type());
115        
116        Ok((content, context))
117    }
118    
119    fn apply_virtualization(
120        &self,
121        document: &ParsedDocument,
122        claim_query: &ClaimQuery,
123        metadata: &mut PrivacyMetadata,
124    ) -> Result<(String, String)> {
125        if let ClaimQuery::Date(date_claim) = claim_query {
126            let virtual_result = self.virtualizer.virtualize_date_check(document, date_claim)?;
127            
128            metadata.fields_included.push("date_calculation".to_string());
129            
130            let content = format!("Age calculation result: {}", virtual_result);
131            let context = "Virtualized age verification".to_string();
132            
133            return Ok((content, context));
134        }
135        
136        Ok((String::new(), String::new()))
137    }
138    
139    pub fn mask_cpf(&self, cpf: &str) -> String {
140        if cpf.len() >= 14 {
141            format!("{}.***.***-{}", &cpf[0..3], &cpf[12..14])
142        } else {
143            "***.***.***-**".to_string()
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn test_mask_cpf() {
154        let filter = PrivacyFilter::new();
155        let masked = filter.mask_cpf("123.456.789-00");
156        assert_eq!(masked, "123.***.***-00");
157    }
158}