1use serde::{Deserialize, Serialize};
17
18use crate::{Address, Timestamp};
19
20pub type TaxClaimType = String;
26
27pub type PolicyId = [u8; 32];
29
30pub type ClaimId = [u8; 32];
32
33pub type ProofId = [u8; 32];
35
36pub const TAX_SCHEMA_DOMAIN_SEP: &[u8] = b"SRC821-SCHEMA:";
42
43pub const TAX_POLICY_DOMAIN_SEP: &[u8] = b"SRC823-POLICY:";
45
46pub const TAX_PROOF_DOMAIN_SEP: &[u8] = b"SRC824-PROOF:";
48
49pub const TAX_DISCLOSURE_DOMAIN_SEP: &[u8] = b"SRC825-DISCLOSURE-v1";
51
52pub const TAX_CLAIM_COMMITMENT_DOMAIN_SEP: &[u8] = b"SRC82X-CLAIM-COMMITMENT-v1";
54
55#[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#[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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
116pub struct TaxClaimTypeEntry {
117 pub claim_type: TaxClaimType,
119 pub schema_hash: [u8; 32],
121 pub risk_level: TaxRiskLevel,
123 pub recommended_validity_secs: u64,
125 pub required_issuer_classes: Vec<Vec<TaxIssuerClass>>,
127 pub status: ClaimTypeStatus,
129 pub version: u32,
131 pub created_at: Timestamp,
133 pub updated_at: Timestamp,
135}
136
137impl TaxClaimTypeEntry {
138 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 pub fn check_issuer_classes(&self, issuer_classes: &[TaxIssuerClass]) -> bool {
150 for group in &self.required_issuer_classes {
152 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
162pub 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 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, 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 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, 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 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, 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 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, 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 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, 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 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, 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 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, 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
311#[repr(u8)]
312pub enum TaxIssuerClass {
313 TaxAuthority = 0,
315 EmployerPayroll = 1,
317 BankBroker = 2,
319 AuditorCpa = 3,
321 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, TaxIssuerClass::AuditorCpa => 4,
351 TaxIssuerClass::BankBroker => 3,
352 TaxIssuerClass::EmployerPayroll => 2,
353 TaxIssuerClass::TaxFilingProvider => 1, }
355 }
356
357 pub fn can_issue(&self, claim_type: &str) -> bool {
359 match self {
360 TaxIssuerClass::TaxAuthority => true, 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 matches!(claim_type, "tax.filed.return")
376 }
377 }
378 }
379}
380
381#[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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
409pub struct TaxIssuer {
410 pub address: Address,
412 pub tax_class: TaxIssuerClass,
414 pub jurisdictions: Vec<String>,
416 pub attributes_hash: [u8; 32],
418 pub attributes_schema_hash: [u8; 32],
420 pub registered_at: Timestamp,
422 pub updated_at: Timestamp,
424 pub status: TaxIssuerStatus,
426 pub expires_at: Option<Timestamp>,
428}
429
430impl TaxIssuer {
431 pub fn is_authorized_for_jurisdiction(&self, jurisdiction: &str) -> bool {
433 if self.jurisdictions.is_empty() {
435 return true;
436 }
437 self.jurisdictions.iter().any(|j| {
439 j == jurisdiction || jurisdiction.starts_with(&format!("{}-", j))
440 })
441 }
442
443 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
457#[repr(u8)]
458pub enum QuorumRule {
459 Any = 0,
461 All = 1,
463 AtLeast(u8) = 2,
465}
466
467#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
469pub struct IssuerRequirements {
470 pub groups: Vec<Vec<TaxIssuerClass>>,
472 pub quorum: QuorumRule,
474}
475
476impl IssuerRequirements {
477 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
497#[repr(u8)]
498pub enum TaxPolicyTemplate {
499 Filed = 0,
501 IncomeBracket = 1,
503 NoBalance = 2,
505 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
522pub struct TaxPolicy {
523 pub policy_id: PolicyId,
525 pub template: TaxPolicyTemplate,
527 pub claim_types: Vec<TaxClaimType>,
529 pub issuer_requirements: IssuerRequirements,
531 pub jurisdictions: Vec<String>,
533 pub tax_years: Vec<u32>,
535 pub max_age_secs: u64,
537 pub revocation_check: bool,
539 pub creator: Address,
541 pub created_at: Timestamp,
543}
544
545impl TaxPolicy {
546 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 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 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 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 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 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 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
716pub 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
730pub struct TaxFiledPublicInputs {
731 pub year: u32,
733 pub jurisdiction: String,
735 pub status_commitment: [u8; 32],
737 pub proof_timestamp: Timestamp,
739}
740
741#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
743pub struct IncomeBracketPublicInputs {
744 pub bracket_id: u32,
746 pub year: u32,
748 pub jurisdiction: String,
750 pub proof_timestamp: Timestamp,
752}
753
754#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
756pub struct NoBalancePublicInputs {
757 pub period_end: u32,
759 pub jurisdiction: String,
761 pub balance_commitment: [u8; 32],
763 pub proof_timestamp: Timestamp,
765}
766
767#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
769pub struct GoodStandingPublicInputs {
770 pub period_start: u32,
772 pub period_end: u32,
774 pub jurisdiction: String,
776 pub standing_commitment: [u8; 32],
778 pub proof_timestamp: Timestamp,
780}
781
782#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
784pub struct TaxProofEnvelope {
785 pub proof_id: ProofId,
787 pub profile_id: String,
789 pub policy_ids: Vec<PolicyId>,
791 pub claim_ids: Vec<ClaimId>,
793 pub public_inputs: Vec<u8>,
795 pub proof_data: Vec<u8>,
797 pub proof_type: TaxProofType,
799 pub subject_nullifier: [u8; 32],
801 pub generated_at: Timestamp,
803 pub expires_at: Timestamp,
805}
806
807#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
809#[repr(u8)]
810pub enum TaxProofType {
811 Mock = 0,
813 Groth16 = 1,
815 Plonk = 2,
817 Signature = 3,
819}
820
821impl TaxProofEnvelope {
822 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
840pub struct TaxVerificationResult {
841 pub valid: bool,
843 pub profile_id: String,
845 pub policy_compliant: bool,
847 pub revocation_status: TaxRevocationCheckResult,
849 pub verified_at: Timestamp,
851 pub verifier: Option<Address>,
853}
854
855#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
857pub struct TaxRevocationCheckResult {
858 pub checked: bool,
860 pub revoked: bool,
862 pub revocation_reason: Option<TaxRevocationReason>,
864}
865
866#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
868#[repr(u8)]
869pub enum TaxRevocationReason {
870 Unspecified = 0,
872 Superseded = 1,
874 Fraud = 2,
876 Amended = 3,
878 AuditAdjustment = 4,
880 IssuerRevoked = 5,
882}
883
884#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
890#[repr(u8)]
891pub enum EncryptionAlgorithm {
892 ChaCha20Poly1305 = 0,
894 Aes256Gcm = 1,
896 X25519ChaCha = 2,
898}
899
900#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
902#[repr(u8)]
903pub enum DisclosureContentType {
904 TaxReturn = 0,
906 W2Form = 1,
908 Form1099 = 2,
910 Transcript = 3,
912 PaymentReceipt = 4,
914 AssessmentNotice = 5,
916 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
936pub struct EncryptionMeta {
937 pub algorithm: EncryptionAlgorithm,
939 pub key_hint: [u8; 32],
941 pub iv: Option<[u8; 12]>,
943}
944
945#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
947pub struct TaxDisclosureEnvelope {
948 pub payload_hash: [u8; 32],
950 pub payload_size: u64,
952 pub hint_uri: Option<String>,
954 pub encryption_meta: Option<EncryptionMeta>,
956 pub content_type: DisclosureContentType,
958 pub claim_id: Option<ClaimId>,
960 pub proof_id: Option<ProofId>,
962 pub created_at: Timestamp,
964}
965
966impl TaxDisclosureEnvelope {
967 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
989#[repr(u8)]
990pub enum TaxOperation {
991 RegisterClaimType = 0,
994 UpdateClaimType = 1,
996 DeprecateClaimType = 2,
998
999 RegisterIssuer = 10,
1002 UpdateIssuer = 11,
1004 SuspendIssuer = 12,
1006 RevokeIssuer = 13,
1008
1009 CreatePolicy = 20,
1012 UpdatePolicy = 21,
1014
1015 IssueClaim = 30,
1018 RevokeClaim = 31,
1020
1021 VerifyProof = 40,
1024
1025 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1053pub struct TaxTxData {
1054 pub operation: TaxOperation,
1056 pub data: Vec<u8>,
1058 pub recipient: crate::Address,
1060}
1061
1062#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1068pub enum TaxRegistryEvent {
1069 ClaimTypeAdded {
1071 claim_type: TaxClaimType,
1072 schema_hash: [u8; 32],
1073 version: u32,
1074 },
1075 ClaimTypeUpdated {
1077 claim_type: TaxClaimType,
1078 schema_hash: [u8; 32],
1079 old_version: u32,
1080 new_version: u32,
1081 },
1082 ClaimTypeDeprecated {
1084 claim_type: TaxClaimType,
1085 version: u32,
1086 },
1087}
1088
1089#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1091pub enum TaxIssuerEvent {
1092 IssuerRegistered {
1094 address: Address,
1095 tax_class: TaxIssuerClass,
1096 jurisdictions: Vec<String>,
1097 },
1098 IssuerUpdated {
1100 address: Address,
1101 },
1102 IssuerStatusChanged {
1104 address: Address,
1105 old_status: TaxIssuerStatus,
1106 new_status: TaxIssuerStatus,
1107 },
1108}
1109
1110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1112pub enum TaxPolicyEvent {
1113 PolicyCreated {
1115 policy_id: PolicyId,
1116 template: TaxPolicyTemplate,
1117 creator: Address,
1118 },
1119 PolicyUpdated {
1121 policy_id: PolicyId,
1122 updater: Address,
1123 },
1124}
1125
1126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1128pub enum TaxProofEvent {
1129 ProofVerified {
1131 proof_id: ProofId,
1132 profile_id: String,
1133 policy_ids: Vec<PolicyId>,
1134 verifier: Address,
1135 valid: bool,
1136 },
1137}
1138
1139#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1141pub enum TaxDisclosureEvent {
1142 DisclosureAttached {
1144 disclosure_commitment: [u8; 32],
1145 claim_id: Option<ClaimId>,
1146 proof_id: Option<ProofId>,
1147 content_type: DisclosureContentType,
1148 },
1149}
1150
1151#[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#[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 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 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 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 assert!(requirements.is_satisfied(&[TaxIssuerClass::TaxAuthority]));
1212
1213 assert!(requirements.is_satisfied(&[
1215 TaxIssuerClass::AuditorCpa,
1216 TaxIssuerClass::TaxFilingProvider
1217 ]));
1218
1219 assert!(!requirements.is_satisfied(&[TaxIssuerClass::AuditorCpa]));
1221
1222 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 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 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 assert!(issuer.is_authorized_for_jurisdiction("US"));
1315
1316 assert!(issuer.is_authorized_for_jurisdiction("US-CA"));
1318 assert!(issuer.is_authorized_for_jurisdiction("US-NY"));
1319
1320 assert!(!issuer.is_authorized_for_jurisdiction("CA"));
1322 assert!(!issuer.is_authorized_for_jurisdiction("GB"));
1323 }
1324}