Skip to main content

sumchain_primitives/
tax.rs

1//! SRC-82X Tax & Compliance Domain Standards
2//!
3//! This module implements the tax and compliance domain family:
4//! - SRC-821: Tax Claim Type Registry
5//! - SRC-822: Tax Issuer Classes & Roles
6//! - SRC-823: Tax Policy Templates
7//! - SRC-824: Tax Proof Profiles
8//! - SRC-825: Tax Disclosure Envelope
9//!
10//! Design Principles:
11//! - No PII on-chain - only commitments and hashes
12//! - Policy-driven verification via SRC-803
13//! - ZK-ready structures for SRC-806 proofs
14//! - Jurisdictional flexibility
15
16use serde::{Deserialize, Serialize};
17
18use crate::{Address, Timestamp};
19
20// =============================================================================
21// Type Aliases
22// =============================================================================
23
24/// Tax claim type identifier (e.g., "tax.filed.return")
25pub type TaxClaimType = String;
26
27/// Policy ID (32-byte hash)
28pub type PolicyId = [u8; 32];
29
30/// Claim ID (32-byte hash)
31pub type ClaimId = [u8; 32];
32
33/// Proof ID (32-byte hash)
34pub type ProofId = [u8; 32];
35
36// =============================================================================
37// Domain Separation Constants
38// =============================================================================
39
40/// Domain separator for tax schema hashes
41pub const TAX_SCHEMA_DOMAIN_SEP: &[u8] = b"SRC821-SCHEMA:";
42
43/// Domain separator for tax policy IDs
44pub const TAX_POLICY_DOMAIN_SEP: &[u8] = b"SRC823-POLICY:";
45
46/// Domain separator for tax proof profiles
47pub const TAX_PROOF_DOMAIN_SEP: &[u8] = b"SRC824-PROOF:";
48
49/// Domain separator for disclosure envelopes
50pub const TAX_DISCLOSURE_DOMAIN_SEP: &[u8] = b"SRC825-DISCLOSURE-v1";
51
52/// Domain separator for claim commitments
53pub const TAX_CLAIM_COMMITMENT_DOMAIN_SEP: &[u8] = b"SRC82X-CLAIM-COMMITMENT-v1";
54
55// =============================================================================
56// SRC-821: Tax Claim Type Registry
57// =============================================================================
58
59/// Risk level for tax claims
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
61#[repr(u8)]
62pub enum TaxRiskLevel {
63    Low = 0,
64    Medium = 1,
65    High = 2,
66    Critical = 3,
67}
68
69impl TaxRiskLevel {
70    pub fn from_u8(v: u8) -> Option<Self> {
71        match v {
72            0 => Some(TaxRiskLevel::Low),
73            1 => Some(TaxRiskLevel::Medium),
74            2 => Some(TaxRiskLevel::High),
75            3 => Some(TaxRiskLevel::Critical),
76            _ => None,
77        }
78    }
79
80    pub fn name(&self) -> &'static str {
81        match self {
82            TaxRiskLevel::Low => "Low",
83            TaxRiskLevel::Medium => "Medium",
84            TaxRiskLevel::High => "High",
85            TaxRiskLevel::Critical => "Critical",
86        }
87    }
88}
89
90/// Claim type status in the registry
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
92#[repr(u8)]
93pub enum ClaimTypeStatus {
94    Active = 0,
95    Deprecated = 1,
96    Retired = 2,
97}
98
99impl ClaimTypeStatus {
100    pub fn from_u8(v: u8) -> Option<Self> {
101        match v {
102            0 => Some(ClaimTypeStatus::Active),
103            1 => Some(ClaimTypeStatus::Deprecated),
104            2 => Some(ClaimTypeStatus::Retired),
105            _ => None,
106        }
107    }
108
109    pub fn is_usable(&self) -> bool {
110        matches!(self, ClaimTypeStatus::Active | ClaimTypeStatus::Deprecated)
111    }
112}
113
114/// Tax claim type registry entry
115#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
116pub struct TaxClaimTypeEntry {
117    /// Claim type identifier (e.g., "tax.filed.return")
118    pub claim_type: TaxClaimType,
119    /// Schema hash (BLAKE3)
120    pub schema_hash: [u8; 32],
121    /// Risk level
122    pub risk_level: TaxRiskLevel,
123    /// Recommended validity window in seconds
124    pub recommended_validity_secs: u64,
125    /// Required issuer classes (OR logic between groups, AND within groups)
126    pub required_issuer_classes: Vec<Vec<TaxIssuerClass>>,
127    /// Entry status
128    pub status: ClaimTypeStatus,
129    /// Version number
130    pub version: u32,
131    /// Created at timestamp
132    pub created_at: Timestamp,
133    /// Updated at timestamp
134    pub updated_at: Timestamp,
135}
136
137impl TaxClaimTypeEntry {
138    /// Generate schema hash for a claim type
139    pub fn generate_schema_hash(claim_type: &str, version: u32) -> [u8; 32] {
140        let mut hasher = blake3::Hasher::new();
141        hasher.update(TAX_SCHEMA_DOMAIN_SEP);
142        hasher.update(claim_type.as_bytes());
143        hasher.update(b":v");
144        hasher.update(&version.to_string().as_bytes());
145        *hasher.finalize().as_bytes()
146    }
147
148    /// Check if issuer classes satisfy requirements
149    pub fn check_issuer_classes(&self, issuer_classes: &[TaxIssuerClass]) -> bool {
150        // OR logic between groups
151        for group in &self.required_issuer_classes {
152            // AND logic within group - all classes in group must be present
153            let group_satisfied = group.iter().all(|required| issuer_classes.contains(required));
154            if group_satisfied {
155                return true;
156            }
157        }
158        false
159    }
160}
161
162/// V1 predefined tax claim types
163pub mod v1_claim_types {
164    use super::*;
165
166    pub const TAX_FILED_RETURN: &str = "tax.filed.return";
167    pub const TAX_PAID_STATUS: &str = "tax.paid.status";
168    pub const TAX_BALANCE_STATUS: &str = "tax.balance.status";
169    pub const TAX_INCOME_BRACKET: &str = "tax.income.bracket";
170    pub const TAX_WITHHOLDING_BRACKET: &str = "tax.withholding.bracket";
171    pub const TAX_NOTICE_OPEN: &str = "tax.notice.open";
172    pub const TAX_GOOD_STANDING: &str = "tax.good_standing";
173
174    /// Create the V1 tax.filed.return entry
175    pub fn tax_filed_return_entry(now: Timestamp) -> TaxClaimTypeEntry {
176        TaxClaimTypeEntry {
177            claim_type: TAX_FILED_RETURN.to_string(),
178            schema_hash: TaxClaimTypeEntry::generate_schema_hash(TAX_FILED_RETURN, 1),
179            risk_level: TaxRiskLevel::Medium,
180            recommended_validity_secs: 365 * 24 * 60 * 60, // 365 days
181            required_issuer_classes: vec![
182                vec![TaxIssuerClass::TaxAuthority],
183                vec![TaxIssuerClass::AuditorCpa, TaxIssuerClass::TaxFilingProvider],
184            ],
185            status: ClaimTypeStatus::Active,
186            version: 1,
187            created_at: now,
188            updated_at: now,
189        }
190    }
191
192    /// Create the V1 tax.paid.status entry
193    pub fn tax_paid_status_entry(now: Timestamp) -> TaxClaimTypeEntry {
194        TaxClaimTypeEntry {
195            claim_type: TAX_PAID_STATUS.to_string(),
196            schema_hash: TaxClaimTypeEntry::generate_schema_hash(TAX_PAID_STATUS, 1),
197            risk_level: TaxRiskLevel::Medium,
198            recommended_validity_secs: 90 * 24 * 60 * 60, // 90 days
199            required_issuer_classes: vec![
200                vec![TaxIssuerClass::TaxAuthority],
201                vec![TaxIssuerClass::BankBroker],
202            ],
203            status: ClaimTypeStatus::Active,
204            version: 1,
205            created_at: now,
206            updated_at: now,
207        }
208    }
209
210    /// Create the V1 tax.balance.status entry
211    pub fn tax_balance_status_entry(now: Timestamp) -> TaxClaimTypeEntry {
212        TaxClaimTypeEntry {
213            claim_type: TAX_BALANCE_STATUS.to_string(),
214            schema_hash: TaxClaimTypeEntry::generate_schema_hash(TAX_BALANCE_STATUS, 1),
215            risk_level: TaxRiskLevel::High,
216            recommended_validity_secs: 30 * 24 * 60 * 60, // 30 days
217            required_issuer_classes: vec![vec![TaxIssuerClass::TaxAuthority]],
218            status: ClaimTypeStatus::Active,
219            version: 1,
220            created_at: now,
221            updated_at: now,
222        }
223    }
224
225    /// Create the V1 tax.income.bracket entry
226    pub fn tax_income_bracket_entry(now: Timestamp) -> TaxClaimTypeEntry {
227        TaxClaimTypeEntry {
228            claim_type: TAX_INCOME_BRACKET.to_string(),
229            schema_hash: TaxClaimTypeEntry::generate_schema_hash(TAX_INCOME_BRACKET, 1),
230            risk_level: TaxRiskLevel::High,
231            recommended_validity_secs: 365 * 24 * 60 * 60, // 365 days
232            required_issuer_classes: vec![
233                vec![TaxIssuerClass::TaxAuthority],
234                vec![TaxIssuerClass::AuditorCpa],
235            ],
236            status: ClaimTypeStatus::Active,
237            version: 1,
238            created_at: now,
239            updated_at: now,
240        }
241    }
242
243    /// Create the V1 tax.withholding.bracket entry
244    pub fn tax_withholding_bracket_entry(now: Timestamp) -> TaxClaimTypeEntry {
245        TaxClaimTypeEntry {
246            claim_type: TAX_WITHHOLDING_BRACKET.to_string(),
247            schema_hash: TaxClaimTypeEntry::generate_schema_hash(TAX_WITHHOLDING_BRACKET, 1),
248            risk_level: TaxRiskLevel::Medium,
249            recommended_validity_secs: 365 * 24 * 60 * 60, // 365 days
250            required_issuer_classes: vec![
251                vec![TaxIssuerClass::EmployerPayroll],
252                vec![TaxIssuerClass::TaxAuthority],
253            ],
254            status: ClaimTypeStatus::Active,
255            version: 1,
256            created_at: now,
257            updated_at: now,
258        }
259    }
260
261    /// Create the V1 tax.notice.open entry
262    pub fn tax_notice_open_entry(now: Timestamp) -> TaxClaimTypeEntry {
263        TaxClaimTypeEntry {
264            claim_type: TAX_NOTICE_OPEN.to_string(),
265            schema_hash: TaxClaimTypeEntry::generate_schema_hash(TAX_NOTICE_OPEN, 1),
266            risk_level: TaxRiskLevel::High,
267            recommended_validity_secs: 30 * 24 * 60 * 60, // 30 days
268            required_issuer_classes: vec![vec![TaxIssuerClass::TaxAuthority]],
269            status: ClaimTypeStatus::Active,
270            version: 1,
271            created_at: now,
272            updated_at: now,
273        }
274    }
275
276    /// Create the V1 tax.good_standing entry
277    pub fn tax_good_standing_entry(now: Timestamp) -> TaxClaimTypeEntry {
278        TaxClaimTypeEntry {
279            claim_type: TAX_GOOD_STANDING.to_string(),
280            schema_hash: TaxClaimTypeEntry::generate_schema_hash(TAX_GOOD_STANDING, 1),
281            risk_level: TaxRiskLevel::Medium,
282            recommended_validity_secs: 90 * 24 * 60 * 60, // 90 days
283            required_issuer_classes: vec![vec![TaxIssuerClass::TaxAuthority]],
284            status: ClaimTypeStatus::Active,
285            version: 1,
286            created_at: now,
287            updated_at: now,
288        }
289    }
290
291    /// Get all V1 claim type entries
292    pub fn all_v1_entries(now: Timestamp) -> Vec<TaxClaimTypeEntry> {
293        vec![
294            tax_filed_return_entry(now),
295            tax_paid_status_entry(now),
296            tax_balance_status_entry(now),
297            tax_income_bracket_entry(now),
298            tax_withholding_bracket_entry(now),
299            tax_notice_open_entry(now),
300            tax_good_standing_entry(now),
301        ]
302    }
303}
304
305// =============================================================================
306// SRC-822: Tax Issuer Classes & Roles
307// =============================================================================
308
309/// Tax-specific issuer class
310#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
311#[repr(u8)]
312pub enum TaxIssuerClass {
313    /// Government tax authority (IRS, HMRC, CRA, etc.)
314    TaxAuthority = 0,
315    /// Employer payroll systems
316    EmployerPayroll = 1,
317    /// Financial institutions (banks, brokers)
318    BankBroker = 2,
319    /// Licensed auditors and CPAs
320    AuditorCpa = 3,
321    /// Tax preparation software/services
322    TaxFilingProvider = 4,
323}
324
325impl TaxIssuerClass {
326    pub fn from_u8(v: u8) -> Option<Self> {
327        match v {
328            0 => Some(TaxIssuerClass::TaxAuthority),
329            1 => Some(TaxIssuerClass::EmployerPayroll),
330            2 => Some(TaxIssuerClass::BankBroker),
331            3 => Some(TaxIssuerClass::AuditorCpa),
332            4 => Some(TaxIssuerClass::TaxFilingProvider),
333            _ => None,
334        }
335    }
336
337    pub fn name(&self) -> &'static str {
338        match self {
339            TaxIssuerClass::TaxAuthority => "Tax Authority",
340            TaxIssuerClass::EmployerPayroll => "Employer Payroll",
341            TaxIssuerClass::BankBroker => "Bank/Broker",
342            TaxIssuerClass::AuditorCpa => "Auditor/CPA",
343            TaxIssuerClass::TaxFilingProvider => "Tax Filing Provider",
344        }
345    }
346
347    pub fn trust_level(&self) -> u8 {
348        match self {
349            TaxIssuerClass::TaxAuthority => 5, // Highest
350            TaxIssuerClass::AuditorCpa => 4,
351            TaxIssuerClass::BankBroker => 3,
352            TaxIssuerClass::EmployerPayroll => 2,
353            TaxIssuerClass::TaxFilingProvider => 1, // Lowest
354        }
355    }
356
357    /// Check if this issuer class can issue the given claim type
358    pub fn can_issue(&self, claim_type: &str) -> bool {
359        match self {
360            TaxIssuerClass::TaxAuthority => true, // Can issue all tax claims
361            TaxIssuerClass::EmployerPayroll => {
362                matches!(
363                    claim_type,
364                    "tax.withholding.bracket" | "tax.paid.status"
365                )
366            }
367            TaxIssuerClass::BankBroker => {
368                matches!(claim_type, "tax.paid.status" | "tax.withholding.bracket")
369            }
370            TaxIssuerClass::AuditorCpa => {
371                matches!(claim_type, "tax.filed.return" | "tax.income.bracket")
372            }
373            TaxIssuerClass::TaxFilingProvider => {
374                // Only with co-signature from AuditorCpa
375                matches!(claim_type, "tax.filed.return")
376            }
377        }
378    }
379}
380
381/// Tax issuer status
382#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
383#[repr(u8)]
384pub enum TaxIssuerStatus {
385    Active = 0,
386    Suspended = 1,
387    Revoked = 2,
388    Expired = 3,
389}
390
391impl TaxIssuerStatus {
392    pub fn from_u8(v: u8) -> Option<Self> {
393        match v {
394            0 => Some(TaxIssuerStatus::Active),
395            1 => Some(TaxIssuerStatus::Suspended),
396            2 => Some(TaxIssuerStatus::Revoked),
397            3 => Some(TaxIssuerStatus::Expired),
398            _ => None,
399        }
400    }
401
402    pub fn is_valid(&self) -> bool {
403        matches!(self, TaxIssuerStatus::Active)
404    }
405}
406
407/// Tax issuer registration
408#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
409pub struct TaxIssuer {
410    /// Base issuer address (links to SRC-802)
411    pub address: Address,
412    /// Tax-specific issuer class
413    pub tax_class: TaxIssuerClass,
414    /// Jurisdictions authorized for (ISO 3166-1/2 codes)
415    pub jurisdictions: Vec<String>,
416    /// Class-specific attributes hash
417    pub attributes_hash: [u8; 32],
418    /// Attribute schema hash
419    pub attributes_schema_hash: [u8; 32],
420    /// Registration timestamp
421    pub registered_at: Timestamp,
422    /// Last update timestamp
423    pub updated_at: Timestamp,
424    /// Status
425    pub status: TaxIssuerStatus,
426    /// Optional expiry timestamp
427    pub expires_at: Option<Timestamp>,
428}
429
430impl TaxIssuer {
431    /// Check if issuer is authorized for jurisdiction
432    pub fn is_authorized_for_jurisdiction(&self, jurisdiction: &str) -> bool {
433        // Empty list means all jurisdictions
434        if self.jurisdictions.is_empty() {
435            return true;
436        }
437        // Check exact match or parent jurisdiction
438        self.jurisdictions.iter().any(|j| {
439            j == jurisdiction || jurisdiction.starts_with(&format!("{}-", j))
440        })
441    }
442
443    /// Check if issuer can issue claim type in jurisdiction
444    pub fn can_issue_in_jurisdiction(&self, claim_type: &str, jurisdiction: &str) -> bool {
445        self.status.is_valid()
446            && self.tax_class.can_issue(claim_type)
447            && self.is_authorized_for_jurisdiction(jurisdiction)
448    }
449}
450
451// =============================================================================
452// SRC-823: Tax Policy Templates
453// =============================================================================
454
455/// Quorum rule for policy satisfaction
456#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
457#[repr(u8)]
458pub enum QuorumRule {
459    /// Any single group satisfies
460    Any = 0,
461    /// All groups must satisfy
462    All = 1,
463    /// At least N groups must satisfy
464    AtLeast(u8) = 2,
465}
466
467/// Issuer requirements for a policy
468#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
469pub struct IssuerRequirements {
470    /// Issuer class groups (OR between groups, AND within)
471    pub groups: Vec<Vec<TaxIssuerClass>>,
472    /// Quorum rule
473    pub quorum: QuorumRule,
474}
475
476impl IssuerRequirements {
477    /// Check if issuer classes satisfy requirements
478    pub fn is_satisfied(&self, issuer_classes: &[TaxIssuerClass]) -> bool {
479        let satisfied_groups: Vec<bool> = self
480            .groups
481            .iter()
482            .map(|group| group.iter().all(|c| issuer_classes.contains(c)))
483            .collect();
484
485        match self.quorum {
486            QuorumRule::Any => satisfied_groups.iter().any(|&s| s),
487            QuorumRule::All => satisfied_groups.iter().all(|&s| s),
488            QuorumRule::AtLeast(n) => {
489                satisfied_groups.iter().filter(|&&s| s).count() >= n as usize
490            }
491        }
492    }
493}
494
495/// Tax policy template types
496#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
497#[repr(u8)]
498pub enum TaxPolicyTemplate {
499    /// P-Filed: Tax filing verification
500    Filed = 0,
501    /// P-IncomeBracket: Income bracket verification
502    IncomeBracket = 1,
503    /// P-NoBalance: Zero balance verification
504    NoBalance = 2,
505    /// P-GoodStanding: Tax good standing
506    GoodStanding = 3,
507}
508
509impl TaxPolicyTemplate {
510    pub fn name(&self) -> &'static str {
511        match self {
512            TaxPolicyTemplate::Filed => "P-Filed",
513            TaxPolicyTemplate::IncomeBracket => "P-IncomeBracket",
514            TaxPolicyTemplate::NoBalance => "P-NoBalance",
515            TaxPolicyTemplate::GoodStanding => "P-GoodStanding",
516        }
517    }
518}
519
520/// Tax policy instance
521#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
522pub struct TaxPolicy {
523    /// Policy ID (derived hash)
524    pub policy_id: PolicyId,
525    /// Template type
526    pub template: TaxPolicyTemplate,
527    /// Claim types accepted
528    pub claim_types: Vec<TaxClaimType>,
529    /// Issuer requirements
530    pub issuer_requirements: IssuerRequirements,
531    /// Jurisdiction scope (empty = any)
532    pub jurisdictions: Vec<String>,
533    /// Tax years scope (for applicable templates)
534    pub tax_years: Vec<u32>,
535    /// Maximum age of claim in seconds
536    pub max_age_secs: u64,
537    /// Require revocation check
538    pub revocation_check: bool,
539    /// Policy creator
540    pub creator: Address,
541    /// Created timestamp
542    pub created_at: Timestamp,
543}
544
545impl TaxPolicy {
546    /// Generate policy ID
547    pub fn generate_policy_id(
548        template: TaxPolicyTemplate,
549        jurisdictions: &[String],
550        tax_years: &[u32],
551        params_hash: &[u8; 32],
552    ) -> PolicyId {
553        let mut hasher = blake3::Hasher::new();
554        hasher.update(TAX_POLICY_DOMAIN_SEP);
555        hasher.update(template.name().as_bytes());
556        hasher.update(b":");
557
558        // Hash jurisdictions
559        let mut j_hasher = blake3::Hasher::new();
560        for j in jurisdictions {
561            j_hasher.update(j.as_bytes());
562            j_hasher.update(b",");
563        }
564        hasher.update(j_hasher.finalize().as_bytes());
565        hasher.update(b":");
566
567        // Hash tax years
568        let mut y_hasher = blake3::Hasher::new();
569        for y in tax_years {
570            y_hasher.update(&y.to_be_bytes());
571        }
572        hasher.update(y_hasher.finalize().as_bytes());
573        hasher.update(b":");
574
575        hasher.update(params_hash);
576        *hasher.finalize().as_bytes()
577    }
578
579    /// Create P-Filed policy
580    pub fn create_p_filed(
581        jurisdictions: Vec<String>,
582        tax_years: Vec<u32>,
583        max_age_days: u32,
584        creator: Address,
585        now: Timestamp,
586    ) -> Self {
587        let params_hash = blake3::hash(&max_age_days.to_be_bytes());
588        let policy_id = Self::generate_policy_id(
589            TaxPolicyTemplate::Filed,
590            &jurisdictions,
591            &tax_years,
592            params_hash.as_bytes(),
593        );
594
595        TaxPolicy {
596            policy_id,
597            template: TaxPolicyTemplate::Filed,
598            claim_types: vec![v1_claim_types::TAX_FILED_RETURN.to_string()],
599            issuer_requirements: IssuerRequirements {
600                groups: vec![
601                    vec![TaxIssuerClass::TaxAuthority],
602                    vec![TaxIssuerClass::AuditorCpa, TaxIssuerClass::TaxFilingProvider],
603                ],
604                quorum: QuorumRule::Any,
605            },
606            jurisdictions,
607            tax_years,
608            max_age_secs: max_age_days as u64 * 86400,
609            revocation_check: true,
610            creator,
611            created_at: now,
612        }
613    }
614
615    /// Create P-IncomeBracket policy
616    pub fn create_p_income_bracket(
617        jurisdictions: Vec<String>,
618        tax_year: u32,
619        max_age_days: u32,
620        creator: Address,
621        now: Timestamp,
622    ) -> Self {
623        let params_hash = blake3::hash(&[max_age_days.to_be_bytes(), tax_year.to_be_bytes()].concat());
624        let policy_id = Self::generate_policy_id(
625            TaxPolicyTemplate::IncomeBracket,
626            &jurisdictions,
627            &[tax_year],
628            params_hash.as_bytes(),
629        );
630
631        TaxPolicy {
632            policy_id,
633            template: TaxPolicyTemplate::IncomeBracket,
634            claim_types: vec![v1_claim_types::TAX_INCOME_BRACKET.to_string()],
635            issuer_requirements: IssuerRequirements {
636                groups: vec![
637                    vec![TaxIssuerClass::TaxAuthority],
638                    vec![TaxIssuerClass::AuditorCpa],
639                ],
640                quorum: QuorumRule::Any,
641            },
642            jurisdictions,
643            tax_years: vec![tax_year],
644            max_age_secs: max_age_days as u64 * 86400,
645            revocation_check: true,
646            creator,
647            created_at: now,
648        }
649    }
650
651    /// Create P-NoBalance policy
652    pub fn create_p_no_balance(
653        jurisdictions: Vec<String>,
654        max_age_days: u32,
655        creator: Address,
656        now: Timestamp,
657    ) -> Self {
658        let params_hash = blake3::hash(&max_age_days.to_be_bytes());
659        let policy_id = Self::generate_policy_id(
660            TaxPolicyTemplate::NoBalance,
661            &jurisdictions,
662            &[],
663            params_hash.as_bytes(),
664        );
665
666        TaxPolicy {
667            policy_id,
668            template: TaxPolicyTemplate::NoBalance,
669            claim_types: vec![v1_claim_types::TAX_BALANCE_STATUS.to_string()],
670            issuer_requirements: IssuerRequirements {
671                groups: vec![vec![TaxIssuerClass::TaxAuthority]],
672                quorum: QuorumRule::Any,
673            },
674            jurisdictions,
675            tax_years: vec![],
676            max_age_secs: max_age_days as u64 * 86400,
677            revocation_check: true,
678            creator,
679            created_at: now,
680        }
681    }
682
683    /// Create P-GoodStanding policy
684    pub fn create_p_good_standing(
685        jurisdictions: Vec<String>,
686        max_age_days: u32,
687        creator: Address,
688        now: Timestamp,
689    ) -> Self {
690        let params_hash = blake3::hash(&max_age_days.to_be_bytes());
691        let policy_id = Self::generate_policy_id(
692            TaxPolicyTemplate::GoodStanding,
693            &jurisdictions,
694            &[],
695            params_hash.as_bytes(),
696        );
697
698        TaxPolicy {
699            policy_id,
700            template: TaxPolicyTemplate::GoodStanding,
701            claim_types: vec![v1_claim_types::TAX_GOOD_STANDING.to_string()],
702            issuer_requirements: IssuerRequirements {
703                groups: vec![vec![TaxIssuerClass::TaxAuthority]],
704                quorum: QuorumRule::Any,
705            },
706            jurisdictions,
707            tax_years: vec![],
708            max_age_secs: max_age_days as u64 * 86400,
709            revocation_check: true,
710            creator,
711            created_at: now,
712        }
713    }
714}
715
716// =============================================================================
717// SRC-824: Tax Proof Profiles
718// =============================================================================
719
720/// Tax proof profile identifiers
721pub mod proof_profiles {
722    pub const TAX_PROVE_FILED: &str = "tax.prove_filed.v1";
723    pub const TAX_PROVE_INCOME_BRACKET: &str = "tax.prove_income_bracket.v1";
724    pub const TAX_PROVE_NO_BALANCE: &str = "tax.prove_no_balance.v1";
725    pub const TAX_PROVE_GOOD_STANDING: &str = "tax.prove_good_standing.v1";
726}
727
728/// Public inputs for prove_tax_filed
729#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
730pub struct TaxFiledPublicInputs {
731    /// Tax year
732    pub year: u32,
733    /// Jurisdiction code
734    pub jurisdiction: String,
735    /// Filing status commitment (hides actual status)
736    pub status_commitment: [u8; 32],
737    /// Timestamp of proof generation
738    pub proof_timestamp: Timestamp,
739}
740
741/// Public inputs for prove_income_in_bracket
742#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
743pub struct IncomeBracketPublicInputs {
744    /// Bracket ID being proven
745    pub bracket_id: u32,
746    /// Tax year
747    pub year: u32,
748    /// Jurisdiction code
749    pub jurisdiction: String,
750    /// Proof timestamp
751    pub proof_timestamp: Timestamp,
752}
753
754/// Public inputs for prove_no_outstanding_balance
755#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
756pub struct NoBalancePublicInputs {
757    /// Period end date (YYYYMMDD)
758    pub period_end: u32,
759    /// Jurisdiction code
760    pub jurisdiction: String,
761    /// Zero balance commitment
762    pub balance_commitment: [u8; 32],
763    /// Proof timestamp
764    pub proof_timestamp: Timestamp,
765}
766
767/// Public inputs for prove_tax_good_standing
768#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
769pub struct GoodStandingPublicInputs {
770    /// Period start date (YYYYMMDD)
771    pub period_start: u32,
772    /// Period end date (YYYYMMDD)
773    pub period_end: u32,
774    /// Jurisdiction code
775    pub jurisdiction: String,
776    /// Standing commitment
777    pub standing_commitment: [u8; 32],
778    /// Proof timestamp
779    pub proof_timestamp: Timestamp,
780}
781
782/// Tax proof envelope (SRC-806 compatible)
783#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
784pub struct TaxProofEnvelope {
785    /// Proof ID
786    pub proof_id: ProofId,
787    /// Profile ID
788    pub profile_id: String,
789    /// Policy ID(s) this proof satisfies
790    pub policy_ids: Vec<PolicyId>,
791    /// Claim ID(s) used in proof
792    pub claim_ids: Vec<ClaimId>,
793    /// Public inputs (serialized JSON)
794    pub public_inputs: Vec<u8>,
795    /// Proof data (ZK proof bytes or placeholder)
796    pub proof_data: Vec<u8>,
797    /// Proof type
798    pub proof_type: TaxProofType,
799    /// Subject nullifier (for unlinkability)
800    pub subject_nullifier: [u8; 32],
801    /// Generated timestamp
802    pub generated_at: Timestamp,
803    /// Expires at
804    pub expires_at: Timestamp,
805}
806
807/// Proof type
808#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
809#[repr(u8)]
810pub enum TaxProofType {
811    /// Placeholder/mock proof (for testing)
812    Mock = 0,
813    /// Groth16 ZK proof
814    Groth16 = 1,
815    /// PLONK ZK proof
816    Plonk = 2,
817    /// Signature-based attestation
818    Signature = 3,
819}
820
821impl TaxProofEnvelope {
822    /// Generate proof ID
823    pub fn generate_proof_id(
824        profile_id: &str,
825        public_inputs: &[u8],
826        nonce: &[u8; 32],
827    ) -> ProofId {
828        let mut hasher = blake3::Hasher::new();
829        hasher.update(TAX_PROOF_DOMAIN_SEP);
830        hasher.update(profile_id.as_bytes());
831        hasher.update(b":");
832        hasher.update(public_inputs);
833        hasher.update(nonce);
834        *hasher.finalize().as_bytes()
835    }
836}
837
838/// Verification result
839#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
840pub struct TaxVerificationResult {
841    /// Is proof valid
842    pub valid: bool,
843    /// Profile ID verified
844    pub profile_id: String,
845    /// Is policy compliant
846    pub policy_compliant: bool,
847    /// Revocation check result
848    pub revocation_status: TaxRevocationCheckResult,
849    /// Verified at timestamp
850    pub verified_at: Timestamp,
851    /// Verifier address
852    pub verifier: Option<Address>,
853}
854
855/// Revocation check result
856#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
857pub struct TaxRevocationCheckResult {
858    /// Was check performed
859    pub checked: bool,
860    /// Is any claim revoked
861    pub revoked: bool,
862    /// Revocation reason (if revoked)
863    pub revocation_reason: Option<TaxRevocationReason>,
864}
865
866/// Tax revocation reasons
867#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
868#[repr(u8)]
869pub enum TaxRevocationReason {
870    /// Unspecified
871    Unspecified = 0,
872    /// Information superseded
873    Superseded = 1,
874    /// Fraudulent filing
875    Fraud = 2,
876    /// Amendment filed
877    Amended = 3,
878    /// Audit adjustment
879    AuditAdjustment = 4,
880    /// Issuer revoked
881    IssuerRevoked = 5,
882}
883
884// =============================================================================
885// SRC-825: Tax Disclosure Envelope
886// =============================================================================
887
888/// Encryption algorithm
889#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
890#[repr(u8)]
891pub enum EncryptionAlgorithm {
892    /// ChaCha20-Poly1305
893    ChaCha20Poly1305 = 0,
894    /// AES-256-GCM
895    Aes256Gcm = 1,
896    /// X25519 + ChaCha20-Poly1305
897    X25519ChaCha = 2,
898}
899
900/// Disclosure content type
901#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
902#[repr(u8)]
903pub enum DisclosureContentType {
904    /// Tax return document
905    TaxReturn = 0,
906    /// W-2 or equivalent
907    W2Form = 1,
908    /// 1099 or equivalent
909    Form1099 = 2,
910    /// Tax transcript
911    Transcript = 3,
912    /// Payment receipt
913    PaymentReceipt = 4,
914    /// Assessment notice
915    AssessmentNotice = 5,
916    /// Other document
917    Other = 255,
918}
919
920impl DisclosureContentType {
921    pub fn name(&self) -> &'static str {
922        match self {
923            DisclosureContentType::TaxReturn => "Tax Return",
924            DisclosureContentType::W2Form => "W-2 Form",
925            DisclosureContentType::Form1099 => "Form 1099",
926            DisclosureContentType::Transcript => "Tax Transcript",
927            DisclosureContentType::PaymentReceipt => "Payment Receipt",
928            DisclosureContentType::AssessmentNotice => "Assessment Notice",
929            DisclosureContentType::Other => "Other",
930        }
931    }
932}
933
934/// Encryption metadata
935#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
936pub struct EncryptionMeta {
937    /// Encryption algorithm
938    pub algorithm: EncryptionAlgorithm,
939    /// Key derivation hint (for recipient)
940    pub key_hint: [u8; 32],
941    /// Initialization vector (if applicable)
942    pub iv: Option<[u8; 12]>,
943}
944
945/// Tax disclosure envelope
946#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
947pub struct TaxDisclosureEnvelope {
948    /// Payload hash (BLAKE3 of encrypted content)
949    pub payload_hash: [u8; 32],
950    /// Payload size in bytes
951    pub payload_size: u64,
952    /// Optional storage hint (IPFS CID, URL, etc.)
953    pub hint_uri: Option<String>,
954    /// Encryption metadata
955    pub encryption_meta: Option<EncryptionMeta>,
956    /// Content type
957    pub content_type: DisclosureContentType,
958    /// Associated claim ID
959    pub claim_id: Option<ClaimId>,
960    /// Associated proof ID
961    pub proof_id: Option<ProofId>,
962    /// Created timestamp
963    pub created_at: Timestamp,
964}
965
966impl TaxDisclosureEnvelope {
967    /// Generate commitment for disclosure envelope
968    pub fn generate_commitment(&self) -> [u8; 32] {
969        let mut hasher = blake3::Hasher::new();
970        hasher.update(TAX_DISCLOSURE_DOMAIN_SEP);
971        hasher.update(&self.payload_hash);
972        hasher.update(&[self.content_type as u8]);
973        if let Some(ref claim_id) = self.claim_id {
974            hasher.update(claim_id);
975        }
976        if let Some(ref proof_id) = self.proof_id {
977            hasher.update(proof_id);
978        }
979        *hasher.finalize().as_bytes()
980    }
981}
982
983// =============================================================================
984// Tax Domain Operations
985// =============================================================================
986
987/// Tax domain operations
988#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
989#[repr(u8)]
990pub enum TaxOperation {
991    // Registry operations (0-9)
992    /// Register claim type
993    RegisterClaimType = 0,
994    /// Update claim type
995    UpdateClaimType = 1,
996    /// Deprecate claim type
997    DeprecateClaimType = 2,
998
999    // Issuer operations (10-19)
1000    /// Register tax issuer
1001    RegisterIssuer = 10,
1002    /// Update tax issuer
1003    UpdateIssuer = 11,
1004    /// Suspend tax issuer
1005    SuspendIssuer = 12,
1006    /// Revoke tax issuer
1007    RevokeIssuer = 13,
1008
1009    // Policy operations (20-29)
1010    /// Create policy
1011    CreatePolicy = 20,
1012    /// Update policy
1013    UpdatePolicy = 21,
1014
1015    // Claim operations (30-39)
1016    /// Issue tax claim
1017    IssueClaim = 30,
1018    /// Revoke tax claim
1019    RevokeClaim = 31,
1020
1021    // Proof operations (40-49)
1022    /// Submit proof for verification
1023    VerifyProof = 40,
1024
1025    // Disclosure operations (50-59)
1026    /// Attach disclosure
1027    AttachDisclosure = 50,
1028}
1029
1030impl TaxOperation {
1031    pub fn from_u8(v: u8) -> Option<Self> {
1032        match v {
1033            0 => Some(TaxOperation::RegisterClaimType),
1034            1 => Some(TaxOperation::UpdateClaimType),
1035            2 => Some(TaxOperation::DeprecateClaimType),
1036            10 => Some(TaxOperation::RegisterIssuer),
1037            11 => Some(TaxOperation::UpdateIssuer),
1038            12 => Some(TaxOperation::SuspendIssuer),
1039            13 => Some(TaxOperation::RevokeIssuer),
1040            20 => Some(TaxOperation::CreatePolicy),
1041            21 => Some(TaxOperation::UpdatePolicy),
1042            30 => Some(TaxOperation::IssueClaim),
1043            31 => Some(TaxOperation::RevokeClaim),
1044            40 => Some(TaxOperation::VerifyProof),
1045            50 => Some(TaxOperation::AttachDisclosure),
1046            _ => None,
1047        }
1048    }
1049}
1050
1051/// Tax domain transaction data
1052#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1053pub struct TaxTxData {
1054    /// Operation type
1055    pub operation: TaxOperation,
1056    /// Operation-specific data (serialized)
1057    pub data: Vec<u8>,
1058    /// Token recipient address - the owner of the minted token
1059    pub recipient: crate::Address,
1060}
1061
1062// =============================================================================
1063// Tax Domain Events
1064// =============================================================================
1065
1066/// Tax registry events
1067#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1068pub enum TaxRegistryEvent {
1069    /// Claim type added
1070    ClaimTypeAdded {
1071        claim_type: TaxClaimType,
1072        schema_hash: [u8; 32],
1073        version: u32,
1074    },
1075    /// Claim type updated
1076    ClaimTypeUpdated {
1077        claim_type: TaxClaimType,
1078        schema_hash: [u8; 32],
1079        old_version: u32,
1080        new_version: u32,
1081    },
1082    /// Claim type deprecated
1083    ClaimTypeDeprecated {
1084        claim_type: TaxClaimType,
1085        version: u32,
1086    },
1087}
1088
1089/// Tax issuer events
1090#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1091pub enum TaxIssuerEvent {
1092    /// Issuer registered
1093    IssuerRegistered {
1094        address: Address,
1095        tax_class: TaxIssuerClass,
1096        jurisdictions: Vec<String>,
1097    },
1098    /// Issuer updated
1099    IssuerUpdated {
1100        address: Address,
1101    },
1102    /// Issuer status changed
1103    IssuerStatusChanged {
1104        address: Address,
1105        old_status: TaxIssuerStatus,
1106        new_status: TaxIssuerStatus,
1107    },
1108}
1109
1110/// Tax policy events
1111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1112pub enum TaxPolicyEvent {
1113    /// Policy created
1114    PolicyCreated {
1115        policy_id: PolicyId,
1116        template: TaxPolicyTemplate,
1117        creator: Address,
1118    },
1119    /// Policy updated
1120    PolicyUpdated {
1121        policy_id: PolicyId,
1122        updater: Address,
1123    },
1124}
1125
1126/// Tax proof events
1127#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1128pub enum TaxProofEvent {
1129    /// Proof verified
1130    ProofVerified {
1131        proof_id: ProofId,
1132        profile_id: String,
1133        policy_ids: Vec<PolicyId>,
1134        verifier: Address,
1135        valid: bool,
1136    },
1137}
1138
1139/// Tax disclosure events
1140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1141pub enum TaxDisclosureEvent {
1142    /// Disclosure attached
1143    DisclosureAttached {
1144        disclosure_commitment: [u8; 32],
1145        claim_id: Option<ClaimId>,
1146        proof_id: Option<ProofId>,
1147        content_type: DisclosureContentType,
1148    },
1149}
1150
1151/// Combined tax event
1152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1153pub enum TaxEvent {
1154    Registry(TaxRegistryEvent),
1155    Issuer(TaxIssuerEvent),
1156    Policy(TaxPolicyEvent),
1157    Proof(TaxProofEvent),
1158    Disclosure(TaxDisclosureEvent),
1159}
1160
1161// =============================================================================
1162// Tests
1163// =============================================================================
1164
1165#[cfg(test)]
1166mod tests {
1167    use super::*;
1168
1169    #[test]
1170    fn test_claim_type_schema_hash() {
1171        let hash1 = TaxClaimTypeEntry::generate_schema_hash("tax.filed.return", 1);
1172        let hash2 = TaxClaimTypeEntry::generate_schema_hash("tax.filed.return", 1);
1173        let hash3 = TaxClaimTypeEntry::generate_schema_hash("tax.filed.return", 2);
1174
1175        assert_eq!(hash1, hash2);
1176        assert_ne!(hash1, hash3);
1177    }
1178
1179    #[test]
1180    fn test_issuer_class_authorization() {
1181        let authority = TaxIssuerClass::TaxAuthority;
1182        let cpa = TaxIssuerClass::AuditorCpa;
1183        let filing_provider = TaxIssuerClass::TaxFilingProvider;
1184
1185        // Tax authority can issue all
1186        assert!(authority.can_issue("tax.filed.return"));
1187        assert!(authority.can_issue("tax.income.bracket"));
1188        assert!(authority.can_issue("tax.balance.status"));
1189
1190        // CPA can issue filed.return and income.bracket
1191        assert!(cpa.can_issue("tax.filed.return"));
1192        assert!(cpa.can_issue("tax.income.bracket"));
1193        assert!(!cpa.can_issue("tax.balance.status"));
1194
1195        // Filing provider can only issue filed.return (with co-signature)
1196        assert!(filing_provider.can_issue("tax.filed.return"));
1197        assert!(!filing_provider.can_issue("tax.income.bracket"));
1198    }
1199
1200    #[test]
1201    fn test_issuer_requirements_satisfaction() {
1202        let requirements = IssuerRequirements {
1203            groups: vec![
1204                vec![TaxIssuerClass::TaxAuthority],
1205                vec![TaxIssuerClass::AuditorCpa, TaxIssuerClass::TaxFilingProvider],
1206            ],
1207            quorum: QuorumRule::Any,
1208        };
1209
1210        // Tax authority alone satisfies
1211        assert!(requirements.is_satisfied(&[TaxIssuerClass::TaxAuthority]));
1212
1213        // CPA + Filing provider together satisfy
1214        assert!(requirements.is_satisfied(&[
1215            TaxIssuerClass::AuditorCpa,
1216            TaxIssuerClass::TaxFilingProvider
1217        ]));
1218
1219        // CPA alone does not satisfy
1220        assert!(!requirements.is_satisfied(&[TaxIssuerClass::AuditorCpa]));
1221
1222        // Filing provider alone does not satisfy
1223        assert!(!requirements.is_satisfied(&[TaxIssuerClass::TaxFilingProvider]));
1224    }
1225
1226    #[test]
1227    fn test_policy_id_determinism() {
1228        let creator = Address::ZERO;
1229        let now = 1704067200000u64;
1230
1231        let policy1 = TaxPolicy::create_p_filed(
1232            vec!["US".to_string()],
1233            vec![2023],
1234            365,
1235            creator,
1236            now,
1237        );
1238
1239        let policy2 = TaxPolicy::create_p_filed(
1240            vec!["US".to_string()],
1241            vec![2023],
1242            365,
1243            creator,
1244            now,
1245        );
1246
1247        let policy3 = TaxPolicy::create_p_filed(
1248            vec!["CA".to_string()],
1249            vec![2023],
1250            365,
1251            creator,
1252            now,
1253        );
1254
1255        assert_eq!(policy1.policy_id, policy2.policy_id);
1256        assert_ne!(policy1.policy_id, policy3.policy_id);
1257    }
1258
1259    #[test]
1260    fn test_v1_claim_types() {
1261        let now = 1704067200000u64;
1262        let entries = v1_claim_types::all_v1_entries(now);
1263
1264        assert_eq!(entries.len(), 7);
1265
1266        // Verify all entries have unique claim types
1267        let mut claim_types: Vec<_> = entries.iter().map(|e| &e.claim_type).collect();
1268        claim_types.sort();
1269        claim_types.dedup();
1270        assert_eq!(claim_types.len(), 7);
1271    }
1272
1273    #[test]
1274    fn test_disclosure_commitment() {
1275        let disclosure = TaxDisclosureEnvelope {
1276            payload_hash: [1u8; 32],
1277            payload_size: 1024,
1278            hint_uri: Some("ipfs://Qm...".to_string()),
1279            encryption_meta: None,
1280            content_type: DisclosureContentType::TaxReturn,
1281            claim_id: Some([2u8; 32]),
1282            proof_id: None,
1283            created_at: 1704067200000,
1284        };
1285
1286        let commitment1 = disclosure.generate_commitment();
1287        let commitment2 = disclosure.generate_commitment();
1288
1289        assert_eq!(commitment1, commitment2);
1290
1291        // Different payload should give different commitment
1292        let mut disclosure2 = disclosure.clone();
1293        disclosure2.payload_hash = [3u8; 32];
1294        let commitment3 = disclosure2.generate_commitment();
1295
1296        assert_ne!(commitment1, commitment3);
1297    }
1298
1299    #[test]
1300    fn test_tax_issuer_jurisdiction_check() {
1301        let issuer = TaxIssuer {
1302            address: Address::ZERO,
1303            tax_class: TaxIssuerClass::TaxAuthority,
1304            jurisdictions: vec!["US".to_string()],
1305            attributes_hash: [0u8; 32],
1306            attributes_schema_hash: [0u8; 32],
1307            registered_at: 1704067200000,
1308            updated_at: 1704067200000,
1309            status: TaxIssuerStatus::Active,
1310            expires_at: None,
1311        };
1312
1313        // Exact match
1314        assert!(issuer.is_authorized_for_jurisdiction("US"));
1315
1316        // Sub-jurisdiction
1317        assert!(issuer.is_authorized_for_jurisdiction("US-CA"));
1318        assert!(issuer.is_authorized_for_jurisdiction("US-NY"));
1319
1320        // Different jurisdiction
1321        assert!(!issuer.is_authorized_for_jurisdiction("CA"));
1322        assert!(!issuer.is_authorized_for_jurisdiction("GB"));
1323    }
1324}