Skip to main content

sumchain_primitives/
finance.rs

1//! SRC-89X Utility Address Proof, Banking & Finance Domain Standards
2//!
3//! This module implements the finance and banking domain family:
4//! - SRC-891: Financial Institution & Utility Issuer Profile
5//! - SRC-892: Proof-of-Address Credential
6//! - SRC-893: Bank Account Standing Credential
7//! - SRC-894: KYC / AML Attestation
8//! - SRC-895: 89X Proof Profiles
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//! - Supports both Phase 1 (banks, utilities) and Phase 2 (official institutions)
15
16use serde::{Deserialize, Serialize};
17
18use crate::{Address, Timestamp};
19
20// =============================================================================
21// Type Aliases
22// =============================================================================
23
24/// Address proof ID (32-byte hash)
25pub type AddressProofId = [u8; 32];
26
27/// Bank standing ID (32-byte hash)
28pub type BankStandingId = [u8; 32];
29
30/// KYC attestation ID (32-byte hash)
31pub type KycAttestationId = [u8; 32];
32
33/// Policy ID (32-byte hash)
34pub type PolicyId = [u8; 32];
35
36/// Proof ID (32-byte hash)
37pub type ProofId = [u8; 32];
38
39/// Subject reference (commitment to identity)
40pub type SubjectRef = [u8; 32];
41
42/// Issuer reference (commitment or issuer ID)
43pub type IssuerRef = [u8; 32];
44
45// =============================================================================
46// Domain Separation Constants
47// =============================================================================
48
49/// Domain separator for address proof commitments
50pub const ADDRESS_PROOF_DOMAIN_SEP: &[u8] = b"SRC892-ADDRESS-v1";
51
52/// Domain separator for bank standing commitments
53pub const BANK_STANDING_DOMAIN_SEP: &[u8] = b"SRC893-STANDING-v1";
54
55/// Domain separator for KYC attestation commitments
56pub const KYC_ATTESTATION_DOMAIN_SEP: &[u8] = b"SRC894-KYC-v1";
57
58/// Domain separator for physical address commitments
59pub const PHYSICAL_ADDRESS_DOMAIN_SEP: &[u8] = b"SRC892-PHYS-v1";
60
61/// Domain separator for account commitment
62pub const ACCOUNT_COMMITMENT_DOMAIN_SEP: &[u8] = b"SRC893-ACCOUNT-v1";
63
64/// Domain separator for balance bracket commitments
65pub const BALANCE_BRACKET_DOMAIN_SEP: &[u8] = b"SRC893-BALANCE-v1";
66
67/// Domain separator for proof profiles
68pub const FINANCE_PROOF_DOMAIN_SEP: &[u8] = b"SRC895-PROOF-v1";
69
70// =============================================================================
71// SRC-891: Financial Institution & Utility Issuer Profile
72// =============================================================================
73
74/// Issuer class for finance domain (layered over SRC-802)
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
76#[repr(u8)]
77pub enum FinanceIssuerClass {
78    // Phase 2 (Official/Lower Risk)
79    /// Government revenue/tax authority
80    GovernmentRevenue = 0,
81    /// Central bank or treasury
82    CentralBank = 1,
83    /// Regulated commercial bank
84    RegulatedBank = 2,
85    /// Licensed credit union
86    CreditUnion = 3,
87    /// Regulated utility company
88    RegulatedUtility = 4,
89    /// Official address verification service
90    AddressVerificationService = 5,
91
92    // Phase 1 (Higher Risk)
93    /// Fintech company
94    Fintech = 10,
95    /// Neobank / digital-only bank
96    Neobank = 11,
97    /// Money service business
98    MoneyServiceBusiness = 12,
99    /// Utility company (unregulated)
100    Utility = 13,
101    /// Telecom provider
102    Telecom = 14,
103    /// Payment processor
104    PaymentProcessor = 15,
105}
106
107impl FinanceIssuerClass {
108    pub fn from_u8(v: u8) -> Option<Self> {
109        match v {
110            0 => Some(FinanceIssuerClass::GovernmentRevenue),
111            1 => Some(FinanceIssuerClass::CentralBank),
112            2 => Some(FinanceIssuerClass::RegulatedBank),
113            3 => Some(FinanceIssuerClass::CreditUnion),
114            4 => Some(FinanceIssuerClass::RegulatedUtility),
115            5 => Some(FinanceIssuerClass::AddressVerificationService),
116            10 => Some(FinanceIssuerClass::Fintech),
117            11 => Some(FinanceIssuerClass::Neobank),
118            12 => Some(FinanceIssuerClass::MoneyServiceBusiness),
119            13 => Some(FinanceIssuerClass::Utility),
120            14 => Some(FinanceIssuerClass::Telecom),
121            15 => Some(FinanceIssuerClass::PaymentProcessor),
122            _ => None,
123        }
124    }
125
126    pub fn name(&self) -> &'static str {
127        match self {
128            FinanceIssuerClass::GovernmentRevenue => "Government Revenue Authority",
129            FinanceIssuerClass::CentralBank => "Central Bank / Treasury",
130            FinanceIssuerClass::RegulatedBank => "Regulated Commercial Bank",
131            FinanceIssuerClass::CreditUnion => "Credit Union",
132            FinanceIssuerClass::RegulatedUtility => "Regulated Utility",
133            FinanceIssuerClass::AddressVerificationService => "Address Verification Service",
134            FinanceIssuerClass::Fintech => "Fintech Company",
135            FinanceIssuerClass::Neobank => "Neobank / Digital Bank",
136            FinanceIssuerClass::MoneyServiceBusiness => "Money Service Business",
137            FinanceIssuerClass::Utility => "Utility Company",
138            FinanceIssuerClass::Telecom => "Telecom Provider",
139            FinanceIssuerClass::PaymentProcessor => "Payment Processor",
140        }
141    }
142
143    /// Check if this is a Phase 2 (official) issuer class
144    pub fn is_official(&self) -> bool {
145        matches!(
146            self,
147            FinanceIssuerClass::GovernmentRevenue
148                | FinanceIssuerClass::CentralBank
149                | FinanceIssuerClass::RegulatedBank
150                | FinanceIssuerClass::CreditUnion
151                | FinanceIssuerClass::RegulatedUtility
152                | FinanceIssuerClass::AddressVerificationService
153        )
154    }
155
156    /// Check if this is a Phase 1 (lowkey) issuer class
157    pub fn is_lowkey(&self) -> bool {
158        !self.is_official()
159    }
160
161    /// Get default risk level for this issuer class
162    pub fn default_risk_level(&self) -> FinanceRiskLevel {
163        match self {
164            FinanceIssuerClass::GovernmentRevenue => FinanceRiskLevel::Low,
165            FinanceIssuerClass::CentralBank => FinanceRiskLevel::Low,
166            FinanceIssuerClass::RegulatedBank => FinanceRiskLevel::Low,
167            FinanceIssuerClass::CreditUnion => FinanceRiskLevel::Low,
168            FinanceIssuerClass::RegulatedUtility => FinanceRiskLevel::Low,
169            FinanceIssuerClass::AddressVerificationService => FinanceRiskLevel::Medium,
170            FinanceIssuerClass::Fintech => FinanceRiskLevel::Medium,
171            FinanceIssuerClass::Neobank => FinanceRiskLevel::Medium,
172            FinanceIssuerClass::MoneyServiceBusiness => FinanceRiskLevel::High,
173            FinanceIssuerClass::Utility => FinanceRiskLevel::Medium,
174            FinanceIssuerClass::Telecom => FinanceRiskLevel::Medium,
175            FinanceIssuerClass::PaymentProcessor => FinanceRiskLevel::Medium,
176        }
177    }
178
179    /// Check if this issuer can issue address proofs
180    pub fn can_issue_address_proof(&self) -> bool {
181        matches!(
182            self,
183            FinanceIssuerClass::GovernmentRevenue
184                | FinanceIssuerClass::RegulatedBank
185                | FinanceIssuerClass::CreditUnion
186                | FinanceIssuerClass::RegulatedUtility
187                | FinanceIssuerClass::AddressVerificationService
188                | FinanceIssuerClass::Utility
189                | FinanceIssuerClass::Telecom
190        )
191    }
192
193    /// Check if this issuer can issue bank standing credentials
194    pub fn can_issue_bank_standing(&self) -> bool {
195        matches!(
196            self,
197            FinanceIssuerClass::CentralBank
198                | FinanceIssuerClass::RegulatedBank
199                | FinanceIssuerClass::CreditUnion
200                | FinanceIssuerClass::Neobank
201                | FinanceIssuerClass::Fintech
202        )
203    }
204
205    /// Check if this issuer can issue KYC attestations
206    pub fn can_issue_kyc(&self) -> bool {
207        matches!(
208            self,
209            FinanceIssuerClass::GovernmentRevenue
210                | FinanceIssuerClass::CentralBank
211                | FinanceIssuerClass::RegulatedBank
212                | FinanceIssuerClass::CreditUnion
213                | FinanceIssuerClass::Fintech
214                | FinanceIssuerClass::Neobank
215                | FinanceIssuerClass::MoneyServiceBusiness
216                | FinanceIssuerClass::PaymentProcessor
217        )
218    }
219}
220
221/// Risk level for finance credentials
222#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
223#[repr(u8)]
224pub enum FinanceRiskLevel {
225    Low = 0,
226    Medium = 1,
227    High = 2,
228    Critical = 3,
229}
230
231impl FinanceRiskLevel {
232    pub fn from_u8(v: u8) -> Option<Self> {
233        match v {
234            0 => Some(FinanceRiskLevel::Low),
235            1 => Some(FinanceRiskLevel::Medium),
236            2 => Some(FinanceRiskLevel::High),
237            3 => Some(FinanceRiskLevel::Critical),
238            _ => None,
239        }
240    }
241
242    pub fn name(&self) -> &'static str {
243        match self {
244            FinanceRiskLevel::Low => "Low",
245            FinanceRiskLevel::Medium => "Medium",
246            FinanceRiskLevel::High => "High",
247            FinanceRiskLevel::Critical => "Critical",
248        }
249    }
250}
251
252/// Finance issuer profile
253#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
254pub struct FinanceIssuerProfile {
255    /// Issuer address on-chain
256    pub issuer_address: Address,
257    /// Issuer class
258    pub issuer_class: FinanceIssuerClass,
259    /// Issuer commitment (company name, license number, etc. - all committed, not revealed)
260    pub issuer_commitment: [u8; 32],
261    /// Jurisdiction code (ISO 3166-1 alpha-2 + optional subdivision)
262    pub jurisdiction_code: String,
263    /// Policy ID governing this issuer
264    pub policy_id: PolicyId,
265    /// Status
266    pub status: FinanceIssuerStatus,
267    /// Registered at height
268    pub registered_at_height: u64,
269    /// Created at timestamp
270    pub created_at: Timestamp,
271    /// Updated at timestamp
272    pub updated_at: Timestamp,
273}
274
275/// Finance issuer status
276#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
277#[repr(u8)]
278pub enum FinanceIssuerStatus {
279    Pending = 0,
280    Active = 1,
281    Suspended = 2,
282    Revoked = 3,
283}
284
285impl FinanceIssuerStatus {
286    pub fn from_u8(v: u8) -> Option<Self> {
287        match v {
288            0 => Some(FinanceIssuerStatus::Pending),
289            1 => Some(FinanceIssuerStatus::Active),
290            2 => Some(FinanceIssuerStatus::Suspended),
291            3 => Some(FinanceIssuerStatus::Revoked),
292            _ => None,
293        }
294    }
295
296    pub fn is_active(&self) -> bool {
297        matches!(self, FinanceIssuerStatus::Active)
298    }
299}
300
301// =============================================================================
302// SRC-892: Proof-of-Address Credential
303// =============================================================================
304
305/// Address proof type
306#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
307#[repr(u8)]
308pub enum AddressProofType {
309    /// Utility bill (electricity, gas, water)
310    UtilityBill = 0,
311    /// Bank statement
312    BankStatement = 1,
313    /// Tax document
314    TaxDocument = 2,
315    /// Government correspondence
316    GovernmentMail = 3,
317    /// Telecom bill
318    TelecomBill = 4,
319    /// Insurance document
320    InsuranceDocument = 5,
321    /// Rental agreement
322    RentalAgreement = 6,
323    /// Property ownership record
324    PropertyOwnership = 7,
325    /// Voter registration
326    VoterRegistration = 8,
327}
328
329impl AddressProofType {
330    pub fn from_u8(v: u8) -> Option<Self> {
331        match v {
332            0 => Some(AddressProofType::UtilityBill),
333            1 => Some(AddressProofType::BankStatement),
334            2 => Some(AddressProofType::TaxDocument),
335            3 => Some(AddressProofType::GovernmentMail),
336            4 => Some(AddressProofType::TelecomBill),
337            5 => Some(AddressProofType::InsuranceDocument),
338            6 => Some(AddressProofType::RentalAgreement),
339            7 => Some(AddressProofType::PropertyOwnership),
340            8 => Some(AddressProofType::VoterRegistration),
341            _ => None,
342        }
343    }
344
345    pub fn name(&self) -> &'static str {
346        match self {
347            AddressProofType::UtilityBill => "Utility Bill",
348            AddressProofType::BankStatement => "Bank Statement",
349            AddressProofType::TaxDocument => "Tax Document",
350            AddressProofType::GovernmentMail => "Government Correspondence",
351            AddressProofType::TelecomBill => "Telecom Bill",
352            AddressProofType::InsuranceDocument => "Insurance Document",
353            AddressProofType::RentalAgreement => "Rental Agreement",
354            AddressProofType::PropertyOwnership => "Property Ownership Record",
355            AddressProofType::VoterRegistration => "Voter Registration",
356        }
357    }
358}
359
360/// Proof-of-Address credential (SRC-892)
361#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
362pub struct AddressProof {
363    /// Unique address proof ID
364    pub proof_id: AddressProofId,
365    /// Holder wallet address (for token ownership)
366    pub holder_address: Address,
367    /// Subject reference (commitment to identity - NO PII)
368    pub subject_ref: SubjectRef,
369    /// Address commitment (full address committed, not revealed)
370    /// commitment = blake3(ADDRESS_DOMAIN || country || region || city || postal || street || salt)
371    pub address_commitment: [u8; 32],
372    /// Jurisdiction code (country + optional subdivision, revealed for compliance)
373    pub jurisdiction_code: String,
374    /// Postal code commitment (for regional matching)
375    /// commitment = blake3(POSTAL_DOMAIN || postal_code || salt)
376    pub postal_commitment: [u8; 32],
377    /// Proof type
378    pub proof_type: AddressProofType,
379    /// Document date (when the proof document was issued)
380    pub document_date: Timestamp,
381    /// Issuer address
382    pub issuer_address: Address,
383    /// Issuer class
384    pub issuer_class: FinanceIssuerClass,
385    /// Valid from timestamp
386    pub valid_from: Timestamp,
387    /// Expiry timestamp
388    pub expiry: Timestamp,
389    /// Policy ID governing this credential
390    pub policy_id: PolicyId,
391    /// Revocation reference (for SRC-805 compatibility)
392    pub revocation_ref: Option<[u8; 32]>,
393    /// Created at timestamp
394    pub created_at: Timestamp,
395    /// Updated at timestamp
396    pub updated_at: Timestamp,
397}
398
399impl AddressProof {
400    /// Generate address proof ID
401    pub fn generate_id(
402        subject_ref: &SubjectRef,
403        address_commitment: &[u8; 32],
404        proof_type: AddressProofType,
405        nonce: u64,
406    ) -> AddressProofId {
407        let mut hasher = blake3::Hasher::new();
408        hasher.update(ADDRESS_PROOF_DOMAIN_SEP);
409        hasher.update(subject_ref);
410        hasher.update(address_commitment);
411        hasher.update(&[proof_type as u8]);
412        hasher.update(&nonce.to_le_bytes());
413        *hasher.finalize().as_bytes()
414    }
415
416    /// Generate address commitment
417    pub fn generate_address_commitment(
418        country: &str,
419        region: &str,
420        city: &str,
421        postal_code: &str,
422        street_address: &str,
423        salt: &[u8; 32],
424    ) -> [u8; 32] {
425        let mut hasher = blake3::Hasher::new();
426        hasher.update(PHYSICAL_ADDRESS_DOMAIN_SEP);
427        hasher.update(country.as_bytes());
428        hasher.update(b":");
429        hasher.update(region.as_bytes());
430        hasher.update(b":");
431        hasher.update(city.as_bytes());
432        hasher.update(b":");
433        hasher.update(postal_code.as_bytes());
434        hasher.update(b":");
435        hasher.update(street_address.as_bytes());
436        hasher.update(salt);
437        *hasher.finalize().as_bytes()
438    }
439
440    /// Generate postal commitment (for regional matching)
441    pub fn generate_postal_commitment(postal_code: &str, salt: &[u8; 32]) -> [u8; 32] {
442        let mut hasher = blake3::Hasher::new();
443        hasher.update(b"SRC892-POSTAL-v1");
444        hasher.update(postal_code.as_bytes());
445        hasher.update(salt);
446        *hasher.finalize().as_bytes()
447    }
448
449    /// Check if proof is valid at a given time
450    pub fn is_valid(&self, current_time: Timestamp) -> bool {
451        current_time >= self.valid_from
452            && current_time < self.expiry
453            && self.revocation_ref.is_none()
454    }
455}
456
457// =============================================================================
458// SRC-893: Bank Account Standing Credential
459// =============================================================================
460
461/// Account standing type
462#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
463#[repr(u8)]
464pub enum AccountStanding {
465    /// Account in good standing
466    Good = 0,
467    /// Account with minor issues
468    Fair = 1,
469    /// Account with significant issues
470    Poor = 2,
471    /// Account restricted
472    Restricted = 3,
473    /// Account closed
474    Closed = 4,
475}
476
477impl AccountStanding {
478    pub fn from_u8(v: u8) -> Option<Self> {
479        match v {
480            0 => Some(AccountStanding::Good),
481            1 => Some(AccountStanding::Fair),
482            2 => Some(AccountStanding::Poor),
483            3 => Some(AccountStanding::Restricted),
484            4 => Some(AccountStanding::Closed),
485            _ => None,
486        }
487    }
488
489    pub fn name(&self) -> &'static str {
490        match self {
491            AccountStanding::Good => "Good Standing",
492            AccountStanding::Fair => "Fair Standing",
493            AccountStanding::Poor => "Poor Standing",
494            AccountStanding::Restricted => "Restricted",
495            AccountStanding::Closed => "Closed",
496        }
497    }
498
499    pub fn is_good(&self) -> bool {
500        matches!(self, AccountStanding::Good | AccountStanding::Fair)
501    }
502}
503
504/// Account type
505#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
506#[repr(u8)]
507pub enum AccountType {
508    Checking = 0,
509    Savings = 1,
510    MoneyMarket = 2,
511    CertificateOfDeposit = 3,
512    Brokerage = 4,
513    Retirement = 5,
514    Business = 6,
515    Joint = 7,
516}
517
518impl AccountType {
519    pub fn from_u8(v: u8) -> Option<Self> {
520        match v {
521            0 => Some(AccountType::Checking),
522            1 => Some(AccountType::Savings),
523            2 => Some(AccountType::MoneyMarket),
524            3 => Some(AccountType::CertificateOfDeposit),
525            4 => Some(AccountType::Brokerage),
526            5 => Some(AccountType::Retirement),
527            6 => Some(AccountType::Business),
528            7 => Some(AccountType::Joint),
529            _ => None,
530        }
531    }
532
533    pub fn name(&self) -> &'static str {
534        match self {
535            AccountType::Checking => "Checking",
536            AccountType::Savings => "Savings",
537            AccountType::MoneyMarket => "Money Market",
538            AccountType::CertificateOfDeposit => "Certificate of Deposit",
539            AccountType::Brokerage => "Brokerage",
540            AccountType::Retirement => "Retirement",
541            AccountType::Business => "Business",
542            AccountType::Joint => "Joint Account",
543        }
544    }
545}
546
547/// Balance bracket for range-first verification
548#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
549#[repr(u8)]
550pub enum BalanceBracket {
551    /// Below $1,000
552    Bracket0 = 0,
553    /// $1,000 - $5,000
554    Bracket1 = 1,
555    /// $5,000 - $10,000
556    Bracket2 = 2,
557    /// $10,000 - $25,000
558    Bracket3 = 3,
559    /// $25,000 - $50,000
560    Bracket4 = 4,
561    /// $50,000 - $100,000
562    Bracket5 = 5,
563    /// $100,000 - $250,000
564    Bracket6 = 6,
565    /// $250,000 - $500,000
566    Bracket7 = 7,
567    /// $500,000 - $1,000,000
568    Bracket8 = 8,
569    /// Above $1,000,000
570    Bracket9 = 9,
571    /// Custom threshold (use threshold_commitment)
572    Custom = 255,
573}
574
575impl BalanceBracket {
576    pub fn from_u8(v: u8) -> Option<Self> {
577        match v {
578            0 => Some(BalanceBracket::Bracket0),
579            1 => Some(BalanceBracket::Bracket1),
580            2 => Some(BalanceBracket::Bracket2),
581            3 => Some(BalanceBracket::Bracket3),
582            4 => Some(BalanceBracket::Bracket4),
583            5 => Some(BalanceBracket::Bracket5),
584            6 => Some(BalanceBracket::Bracket6),
585            7 => Some(BalanceBracket::Bracket7),
586            8 => Some(BalanceBracket::Bracket8),
587            9 => Some(BalanceBracket::Bracket9),
588            255 => Some(BalanceBracket::Custom),
589            _ => None,
590        }
591    }
592
593    pub fn description(&self) -> &'static str {
594        match self {
595            BalanceBracket::Bracket0 => "Below $1,000",
596            BalanceBracket::Bracket1 => "$1,000 - $5,000",
597            BalanceBracket::Bracket2 => "$5,000 - $10,000",
598            BalanceBracket::Bracket3 => "$10,000 - $25,000",
599            BalanceBracket::Bracket4 => "$25,000 - $50,000",
600            BalanceBracket::Bracket5 => "$50,000 - $100,000",
601            BalanceBracket::Bracket6 => "$100,000 - $250,000",
602            BalanceBracket::Bracket7 => "$250,000 - $500,000",
603            BalanceBracket::Bracket8 => "$500,000 - $1,000,000",
604            BalanceBracket::Bracket9 => "Above $1,000,000",
605            BalanceBracket::Custom => "Custom threshold",
606        }
607    }
608
609    /// Check if balance is at least this bracket
610    pub fn is_at_least(&self, other: &BalanceBracket) -> bool {
611        (*self as u8) >= (*other as u8) && *self != BalanceBracket::Custom
612    }
613}
614
615/// Bank account standing credential (SRC-893)
616#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
617pub struct BankStandingCredential {
618    /// Unique credential ID
619    pub credential_id: BankStandingId,
620    /// Holder wallet address (for token ownership)
621    pub holder_address: Address,
622    /// Subject reference (commitment to account holder identity - NO PII)
623    pub subject_ref: SubjectRef,
624    /// Account commitment (account number committed, not revealed)
625    /// commitment = blake3(ACCOUNT_DOMAIN || routing || account || account_type || salt)
626    pub account_commitment: [u8; 32],
627    /// Bank issuer reference (commitment to bank identity)
628    pub bank_ref: IssuerRef,
629    /// Account type
630    pub account_type: AccountType,
631    /// Account standing
632    pub standing: AccountStanding,
633    /// Account tenure commitment (how long account has been open)
634    /// commitment = blake3(TENURE_DOMAIN || open_date || salt)
635    pub tenure_commitment: [u8; 32],
636    /// Balance bracket (range-first, not exact)
637    pub balance_bracket: BalanceBracket,
638    /// Optional threshold commitment for custom brackets
639    pub threshold_commitment: Option<[u8; 32]>,
640    /// Issuer address
641    pub issuer_address: Address,
642    /// Issuer class
643    pub issuer_class: FinanceIssuerClass,
644    /// Valid from timestamp
645    pub valid_from: Timestamp,
646    /// Expiry timestamp
647    pub expiry: Timestamp,
648    /// Policy ID governing this credential
649    pub policy_id: PolicyId,
650    /// Revocation reference (for SRC-805 compatibility)
651    pub revocation_ref: Option<[u8; 32]>,
652    /// Created at timestamp
653    pub created_at: Timestamp,
654    /// Updated at timestamp
655    pub updated_at: Timestamp,
656}
657
658impl BankStandingCredential {
659    /// Generate credential ID
660    pub fn generate_id(
661        subject_ref: &SubjectRef,
662        account_commitment: &[u8; 32],
663        bank_ref: &IssuerRef,
664        nonce: u64,
665    ) -> BankStandingId {
666        let mut hasher = blake3::Hasher::new();
667        hasher.update(BANK_STANDING_DOMAIN_SEP);
668        hasher.update(subject_ref);
669        hasher.update(account_commitment);
670        hasher.update(bank_ref);
671        hasher.update(&nonce.to_le_bytes());
672        *hasher.finalize().as_bytes()
673    }
674
675    /// Generate account commitment
676    pub fn generate_account_commitment(
677        routing_number: &str,
678        account_number: &str,
679        account_type: AccountType,
680        salt: &[u8; 32],
681    ) -> [u8; 32] {
682        let mut hasher = blake3::Hasher::new();
683        hasher.update(ACCOUNT_COMMITMENT_DOMAIN_SEP);
684        hasher.update(routing_number.as_bytes());
685        hasher.update(b":");
686        hasher.update(account_number.as_bytes());
687        hasher.update(&[account_type as u8]);
688        hasher.update(salt);
689        *hasher.finalize().as_bytes()
690    }
691
692    /// Generate tenure commitment
693    pub fn generate_tenure_commitment(open_date: Timestamp, salt: &[u8; 32]) -> [u8; 32] {
694        let mut hasher = blake3::Hasher::new();
695        hasher.update(b"SRC893-TENURE-v1");
696        hasher.update(&open_date.to_le_bytes());
697        hasher.update(salt);
698        *hasher.finalize().as_bytes()
699    }
700
701    /// Generate threshold commitment for custom balance brackets
702    pub fn generate_threshold_commitment(
703        threshold_min: u64,
704        threshold_max: u64,
705        currency: &str,
706        salt: &[u8; 32],
707    ) -> [u8; 32] {
708        let mut hasher = blake3::Hasher::new();
709        hasher.update(BALANCE_BRACKET_DOMAIN_SEP);
710        hasher.update(&threshold_min.to_le_bytes());
711        hasher.update(&threshold_max.to_le_bytes());
712        hasher.update(currency.as_bytes());
713        hasher.update(salt);
714        *hasher.finalize().as_bytes()
715    }
716
717    /// Check if credential is valid at a given time
718    pub fn is_valid(&self, current_time: Timestamp) -> bool {
719        current_time >= self.valid_from
720            && current_time < self.expiry
721            && self.revocation_ref.is_none()
722            && self.standing.is_good()
723    }
724}
725
726// =============================================================================
727// SRC-894: KYC / AML Attestation
728// =============================================================================
729
730/// KYC level
731#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
732#[repr(u8)]
733pub enum KycLevel {
734    /// No KYC performed
735    None = 0,
736    /// Basic KYC (name, email, phone)
737    Basic = 1,
738    /// Enhanced KYC (ID verification)
739    Enhanced = 2,
740    /// Full KYC (ID + proof of address + source of funds)
741    Full = 3,
742    /// Institutional KYC (for businesses)
743    Institutional = 4,
744}
745
746impl KycLevel {
747    pub fn from_u8(v: u8) -> Option<Self> {
748        match v {
749            0 => Some(KycLevel::None),
750            1 => Some(KycLevel::Basic),
751            2 => Some(KycLevel::Enhanced),
752            3 => Some(KycLevel::Full),
753            4 => Some(KycLevel::Institutional),
754            _ => None,
755        }
756    }
757
758    pub fn name(&self) -> &'static str {
759        match self {
760            KycLevel::None => "No KYC",
761            KycLevel::Basic => "Basic KYC",
762            KycLevel::Enhanced => "Enhanced KYC",
763            KycLevel::Full => "Full KYC",
764            KycLevel::Institutional => "Institutional KYC",
765        }
766    }
767
768    /// Check if level meets minimum requirement
769    pub fn meets_requirement(&self, required: &KycLevel) -> bool {
770        (*self as u8) >= (*required as u8)
771    }
772}
773
774/// AML risk classification
775#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
776#[repr(u8)]
777pub enum AmlRisk {
778    Low = 0,
779    Medium = 1,
780    High = 2,
781    Prohibited = 3,
782}
783
784impl AmlRisk {
785    pub fn from_u8(v: u8) -> Option<Self> {
786        match v {
787            0 => Some(AmlRisk::Low),
788            1 => Some(AmlRisk::Medium),
789            2 => Some(AmlRisk::High),
790            3 => Some(AmlRisk::Prohibited),
791            _ => None,
792        }
793    }
794
795    pub fn name(&self) -> &'static str {
796        match self {
797            AmlRisk::Low => "Low Risk",
798            AmlRisk::Medium => "Medium Risk",
799            AmlRisk::High => "High Risk",
800            AmlRisk::Prohibited => "Prohibited",
801        }
802    }
803
804    pub fn is_acceptable(&self) -> bool {
805        !matches!(self, AmlRisk::Prohibited)
806    }
807}
808
809/// KYC attestation status
810#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
811#[repr(u8)]
812pub enum KycStatus {
813    Pending = 0,
814    Active = 1,
815    Expired = 2,
816    Revoked = 3,
817    UnderReview = 4,
818}
819
820impl KycStatus {
821    pub fn from_u8(v: u8) -> Option<Self> {
822        match v {
823            0 => Some(KycStatus::Pending),
824            1 => Some(KycStatus::Active),
825            2 => Some(KycStatus::Expired),
826            3 => Some(KycStatus::Revoked),
827            4 => Some(KycStatus::UnderReview),
828            _ => None,
829        }
830    }
831
832    pub fn is_active(&self) -> bool {
833        matches!(self, KycStatus::Active)
834    }
835}
836
837/// KYC / AML Attestation (SRC-894)
838#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
839pub struct KycAttestation {
840    /// Unique attestation ID
841    pub attestation_id: KycAttestationId,
842    /// Holder wallet address (for token ownership)
843    pub holder_address: Address,
844    /// Subject reference (commitment to identity - NO PII)
845    pub subject_ref: SubjectRef,
846    /// KYC level achieved
847    pub kyc_level: KycLevel,
848    /// AML risk classification
849    pub aml_risk: AmlRisk,
850    /// Identity verification commitment
851    /// commitment = blake3(ID_DOMAIN || id_type || id_hash || verification_date || salt)
852    pub identity_commitment: [u8; 32],
853    /// Jurisdiction of subject (for compliance)
854    pub subject_jurisdiction: String,
855    /// Verification methods used (committed)
856    /// commitment = blake3(METHODS_DOMAIN || methods_list || salt)
857    pub methods_commitment: [u8; 32],
858    /// Status
859    pub status: KycStatus,
860    /// Issuer address
861    pub issuer_address: Address,
862    /// Issuer class
863    pub issuer_class: FinanceIssuerClass,
864    /// Valid from timestamp
865    pub valid_from: Timestamp,
866    /// Expiry timestamp
867    pub expiry: Timestamp,
868    /// Policy ID governing this attestation
869    pub policy_id: PolicyId,
870    /// Revocation reference (for SRC-805 compatibility)
871    pub revocation_ref: Option<[u8; 32]>,
872    /// Created at timestamp
873    pub created_at: Timestamp,
874    /// Updated at timestamp
875    pub updated_at: Timestamp,
876}
877
878impl KycAttestation {
879    /// Generate attestation ID
880    pub fn generate_id(
881        subject_ref: &SubjectRef,
882        identity_commitment: &[u8; 32],
883        kyc_level: KycLevel,
884        nonce: u64,
885    ) -> KycAttestationId {
886        let mut hasher = blake3::Hasher::new();
887        hasher.update(KYC_ATTESTATION_DOMAIN_SEP);
888        hasher.update(subject_ref);
889        hasher.update(identity_commitment);
890        hasher.update(&[kyc_level as u8]);
891        hasher.update(&nonce.to_le_bytes());
892        *hasher.finalize().as_bytes()
893    }
894
895    /// Generate identity commitment
896    pub fn generate_identity_commitment(
897        id_type: &str,
898        id_hash: &[u8; 32],
899        verification_date: Timestamp,
900        salt: &[u8; 32],
901    ) -> [u8; 32] {
902        let mut hasher = blake3::Hasher::new();
903        hasher.update(b"SRC894-ID-v1");
904        hasher.update(id_type.as_bytes());
905        hasher.update(id_hash);
906        hasher.update(&verification_date.to_le_bytes());
907        hasher.update(salt);
908        *hasher.finalize().as_bytes()
909    }
910
911    /// Generate methods commitment
912    pub fn generate_methods_commitment(methods: &[&str], salt: &[u8; 32]) -> [u8; 32] {
913        let mut hasher = blake3::Hasher::new();
914        hasher.update(b"SRC894-METHODS-v1");
915        for method in methods {
916            hasher.update(method.as_bytes());
917            hasher.update(b",");
918        }
919        hasher.update(salt);
920        *hasher.finalize().as_bytes()
921    }
922
923    /// Check if attestation is valid at a given time
924    pub fn is_valid(&self, current_time: Timestamp) -> bool {
925        current_time >= self.valid_from
926            && current_time < self.expiry
927            && self.revocation_ref.is_none()
928            && self.status.is_active()
929            && self.aml_risk.is_acceptable()
930    }
931}
932
933// =============================================================================
934// SRC-895: 89X Proof Profiles
935// =============================================================================
936
937/// Proof profile types for finance domain
938#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
939#[repr(u8)]
940pub enum FinanceProofType {
941    /// Prove address in a jurisdiction
942    AddressInJurisdiction = 0,
943    /// Prove bank account in good standing
944    AccountInGoodStanding = 1,
945    /// Prove balance at least in a bracket
946    BalanceAtLeast = 2,
947    /// Prove KYC level achieved
948    KycLevelAchieved = 3,
949    /// Prove AML risk acceptable
950    AmlRiskAcceptable = 4,
951    /// Combined proof (multiple conditions)
952    Combined = 255,
953}
954
955impl FinanceProofType {
956    pub fn from_u8(v: u8) -> Option<Self> {
957        match v {
958            0 => Some(FinanceProofType::AddressInJurisdiction),
959            1 => Some(FinanceProofType::AccountInGoodStanding),
960            2 => Some(FinanceProofType::BalanceAtLeast),
961            3 => Some(FinanceProofType::KycLevelAchieved),
962            4 => Some(FinanceProofType::AmlRiskAcceptable),
963            255 => Some(FinanceProofType::Combined),
964            _ => None,
965        }
966    }
967}
968
969/// Finance proof profile
970#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
971pub struct FinanceProofProfile {
972    /// Profile ID
973    pub profile_id: [u8; 32],
974    /// Profile type
975    pub proof_type: FinanceProofType,
976    /// Required jurisdiction (for address proofs)
977    pub required_jurisdiction: Option<String>,
978    /// Minimum balance bracket
979    pub min_balance_bracket: Option<BalanceBracket>,
980    /// Minimum KYC level
981    pub min_kyc_level: Option<KycLevel>,
982    /// Maximum acceptable AML risk
983    pub max_aml_risk: Option<AmlRisk>,
984    /// Required issuer classes
985    pub required_issuer_classes: Vec<FinanceIssuerClass>,
986    /// Maximum age of credential in seconds
987    pub max_credential_age_secs: u64,
988    /// Policy ID
989    pub policy_id: PolicyId,
990}
991
992impl FinanceProofProfile {
993    /// Generate profile ID
994    pub fn generate_id(&self) -> [u8; 32] {
995        let mut hasher = blake3::Hasher::new();
996        hasher.update(FINANCE_PROOF_DOMAIN_SEP);
997        hasher.update(&[self.proof_type.clone() as u8]);
998        if let Some(ref jurisdiction) = self.required_jurisdiction {
999            hasher.update(jurisdiction.as_bytes());
1000        }
1001        if let Some(ref bracket) = self.min_balance_bracket {
1002            hasher.update(&[*bracket as u8]);
1003        }
1004        if let Some(ref level) = self.min_kyc_level {
1005            hasher.update(&[*level as u8]);
1006        }
1007        hasher.update(&self.policy_id);
1008        *hasher.finalize().as_bytes()
1009    }
1010}
1011
1012/// Finance proof envelope (SRC-806 compatible)
1013#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1014pub struct FinanceProofEnvelope {
1015    /// Unique proof ID
1016    pub proof_id: ProofId,
1017    /// Proof profile ID
1018    pub profile_id: [u8; 32],
1019    /// Proof type
1020    pub proof_type: FinanceProofType,
1021    /// Subject nullifier (prevents linkability)
1022    pub subject_nullifier: [u8; 32],
1023    /// Proof data (ZK proof bytes or threshold signature)
1024    pub proof_data: Vec<u8>,
1025    /// Public inputs commitment
1026    pub public_inputs_commitment: [u8; 32],
1027    /// Credential references (commitments to source credentials)
1028    pub credential_refs: Vec<[u8; 32]>,
1029    /// Issuer class of source credential
1030    pub source_issuer_class: FinanceIssuerClass,
1031    /// Policy ID
1032    pub policy_id: PolicyId,
1033    /// Valid from timestamp
1034    pub valid_from: Timestamp,
1035    /// Expiry timestamp
1036    pub expiry: Timestamp,
1037    /// Created at timestamp
1038    pub created_at: Timestamp,
1039}
1040
1041impl FinanceProofEnvelope {
1042    /// Generate proof ID
1043    pub fn generate_id(
1044        profile_id: &[u8; 32],
1045        subject_nullifier: &[u8; 32],
1046        proof_data: &[u8],
1047        nonce: u64,
1048    ) -> ProofId {
1049        let mut hasher = blake3::Hasher::new();
1050        hasher.update(FINANCE_PROOF_DOMAIN_SEP);
1051        hasher.update(profile_id);
1052        hasher.update(subject_nullifier);
1053        hasher.update(&blake3::hash(proof_data).as_bytes()[..]);
1054        hasher.update(&nonce.to_le_bytes());
1055        *hasher.finalize().as_bytes()
1056    }
1057
1058    /// Check if proof is valid at a given time
1059    pub fn is_valid(&self, current_time: Timestamp) -> bool {
1060        current_time >= self.valid_from && current_time < self.expiry
1061    }
1062}
1063
1064// =============================================================================
1065// Events
1066// =============================================================================
1067
1068/// Finance domain events
1069#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1070pub enum FinanceEvent {
1071    /// Issuer registered
1072    IssuerRegistered {
1073        issuer_address: Address,
1074        issuer_class: FinanceIssuerClass,
1075        timestamp: Timestamp,
1076    },
1077    /// Issuer status updated
1078    IssuerStatusUpdated {
1079        issuer_address: Address,
1080        old_status: FinanceIssuerStatus,
1081        new_status: FinanceIssuerStatus,
1082        timestamp: Timestamp,
1083    },
1084    /// Address proof created
1085    AddressProofCreated {
1086        proof_id: AddressProofId,
1087        subject_ref: SubjectRef,
1088        proof_type: AddressProofType,
1089        timestamp: Timestamp,
1090    },
1091    /// Address proof revoked
1092    AddressProofRevoked {
1093        proof_id: AddressProofId,
1094        revocation_ref: [u8; 32],
1095        timestamp: Timestamp,
1096    },
1097    /// Bank standing credential created
1098    BankStandingCreated {
1099        credential_id: BankStandingId,
1100        subject_ref: SubjectRef,
1101        standing: AccountStanding,
1102        timestamp: Timestamp,
1103    },
1104    /// Bank standing credential updated
1105    BankStandingUpdated {
1106        credential_id: BankStandingId,
1107        old_standing: AccountStanding,
1108        new_standing: AccountStanding,
1109        timestamp: Timestamp,
1110    },
1111    /// Bank standing credential revoked
1112    BankStandingRevoked {
1113        credential_id: BankStandingId,
1114        revocation_ref: [u8; 32],
1115        timestamp: Timestamp,
1116    },
1117    /// KYC attestation created
1118    KycAttestationCreated {
1119        attestation_id: KycAttestationId,
1120        subject_ref: SubjectRef,
1121        kyc_level: KycLevel,
1122        timestamp: Timestamp,
1123    },
1124    /// KYC attestation updated
1125    KycAttestationUpdated {
1126        attestation_id: KycAttestationId,
1127        old_status: KycStatus,
1128        new_status: KycStatus,
1129        timestamp: Timestamp,
1130    },
1131    /// KYC attestation revoked
1132    KycAttestationRevoked {
1133        attestation_id: KycAttestationId,
1134        revocation_ref: [u8; 32],
1135        timestamp: Timestamp,
1136    },
1137    /// Proof submitted
1138    ProofSubmitted {
1139        proof_id: ProofId,
1140        proof_type: FinanceProofType,
1141        timestamp: Timestamp,
1142    },
1143    /// Proof verified
1144    ProofVerified {
1145        proof_id: ProofId,
1146        verifier: Address,
1147        result: bool,
1148        timestamp: Timestamp,
1149    },
1150}
1151
1152// =============================================================================
1153// Operations
1154// =============================================================================
1155
1156/// Finance domain operations
1157#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1158#[repr(u8)]
1159pub enum FinanceOperation {
1160    // Issuer operations (SRC-891)
1161    RegisterIssuer = 0,
1162    UpdateIssuer = 1,
1163    SuspendIssuer = 2,
1164    RevokeIssuer = 3,
1165    ReactivateIssuer = 4,
1166
1167    // Address proof operations (SRC-892)
1168    CreateAddressProof = 10,
1169    UpdateAddressProof = 11,
1170    RevokeAddressProof = 12,
1171
1172    // Bank standing operations (SRC-893)
1173    CreateBankStanding = 20,
1174    UpdateBankStanding = 21,
1175    RevokeBankStanding = 22,
1176
1177    // KYC attestation operations (SRC-894)
1178    CreateKycAttestation = 30,
1179    UpdateKycAttestation = 31,
1180    RevokeKycAttestation = 32,
1181
1182    // Proof operations (SRC-895)
1183    SubmitProof = 40,
1184    VerifyProof = 41,
1185}
1186
1187impl FinanceOperation {
1188    pub fn from_u8(v: u8) -> Option<Self> {
1189        match v {
1190            0 => Some(FinanceOperation::RegisterIssuer),
1191            1 => Some(FinanceOperation::UpdateIssuer),
1192            2 => Some(FinanceOperation::SuspendIssuer),
1193            3 => Some(FinanceOperation::RevokeIssuer),
1194            4 => Some(FinanceOperation::ReactivateIssuer),
1195            10 => Some(FinanceOperation::CreateAddressProof),
1196            11 => Some(FinanceOperation::UpdateAddressProof),
1197            12 => Some(FinanceOperation::RevokeAddressProof),
1198            20 => Some(FinanceOperation::CreateBankStanding),
1199            21 => Some(FinanceOperation::UpdateBankStanding),
1200            22 => Some(FinanceOperation::RevokeBankStanding),
1201            30 => Some(FinanceOperation::CreateKycAttestation),
1202            31 => Some(FinanceOperation::UpdateKycAttestation),
1203            32 => Some(FinanceOperation::RevokeKycAttestation),
1204            40 => Some(FinanceOperation::SubmitProof),
1205            41 => Some(FinanceOperation::VerifyProof),
1206            _ => None,
1207        }
1208    }
1209}
1210
1211/// Finance transaction data
1212#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1213pub struct FinanceTxData {
1214    pub operation: FinanceOperation,
1215    pub data: Vec<u8>,
1216    /// Token recipient address - the owner of the minted token
1217    pub recipient: crate::Address,
1218}
1219
1220#[cfg(test)]
1221mod tests {
1222    use super::*;
1223
1224    #[test]
1225    fn test_issuer_class_risk_levels() {
1226        assert_eq!(
1227            FinanceIssuerClass::RegulatedBank.default_risk_level(),
1228            FinanceRiskLevel::Low
1229        );
1230        assert_eq!(
1231            FinanceIssuerClass::MoneyServiceBusiness.default_risk_level(),
1232            FinanceRiskLevel::High
1233        );
1234    }
1235
1236    #[test]
1237    fn test_issuer_class_phases() {
1238        assert!(FinanceIssuerClass::RegulatedBank.is_official());
1239        assert!(!FinanceIssuerClass::RegulatedBank.is_lowkey());
1240        assert!(FinanceIssuerClass::Fintech.is_lowkey());
1241        assert!(!FinanceIssuerClass::Fintech.is_official());
1242    }
1243
1244    #[test]
1245    fn test_issuer_capabilities() {
1246        assert!(FinanceIssuerClass::RegulatedUtility.can_issue_address_proof());
1247        assert!(!FinanceIssuerClass::RegulatedUtility.can_issue_bank_standing());
1248        assert!(FinanceIssuerClass::RegulatedBank.can_issue_bank_standing());
1249        assert!(FinanceIssuerClass::RegulatedBank.can_issue_kyc());
1250    }
1251
1252    #[test]
1253    fn test_address_proof_id_generation() {
1254        let subject_ref = [1u8; 32];
1255        let address_commitment = [2u8; 32];
1256        let id = AddressProof::generate_id(
1257            &subject_ref,
1258            &address_commitment,
1259            AddressProofType::UtilityBill,
1260            1,
1261        );
1262        assert_ne!(id, [0u8; 32]);
1263
1264        // Same inputs, different nonce = different ID
1265        let id2 = AddressProof::generate_id(
1266            &subject_ref,
1267            &address_commitment,
1268            AddressProofType::UtilityBill,
1269            2,
1270        );
1271        assert_ne!(id, id2);
1272    }
1273
1274    #[test]
1275    fn test_address_commitment_generation() {
1276        let salt = [3u8; 32];
1277        let commitment = AddressProof::generate_address_commitment(
1278            "US",
1279            "CA",
1280            "San Francisco",
1281            "94102",
1282            "123 Main St",
1283            &salt,
1284        );
1285        assert_ne!(commitment, [0u8; 32]);
1286    }
1287
1288    #[test]
1289    fn test_balance_bracket_ordering() {
1290        assert!(BalanceBracket::Bracket5.is_at_least(&BalanceBracket::Bracket3));
1291        assert!(!BalanceBracket::Bracket2.is_at_least(&BalanceBracket::Bracket5));
1292        assert!(!BalanceBracket::Custom.is_at_least(&BalanceBracket::Bracket1));
1293    }
1294
1295    #[test]
1296    fn test_bank_standing_id_generation() {
1297        let subject_ref = [4u8; 32];
1298        let account_commitment = [5u8; 32];
1299        let bank_ref = [6u8; 32];
1300        let id = BankStandingCredential::generate_id(
1301            &subject_ref,
1302            &account_commitment,
1303            &bank_ref,
1304            1,
1305        );
1306        assert_ne!(id, [0u8; 32]);
1307    }
1308
1309    #[test]
1310    fn test_kyc_level_requirements() {
1311        assert!(KycLevel::Full.meets_requirement(&KycLevel::Basic));
1312        assert!(KycLevel::Enhanced.meets_requirement(&KycLevel::Enhanced));
1313        assert!(!KycLevel::Basic.meets_requirement(&KycLevel::Full));
1314    }
1315
1316    #[test]
1317    fn test_kyc_attestation_id_generation() {
1318        let subject_ref = [7u8; 32];
1319        let identity_commitment = [8u8; 32];
1320        let id = KycAttestation::generate_id(
1321            &subject_ref,
1322            &identity_commitment,
1323            KycLevel::Enhanced,
1324            1,
1325        );
1326        assert_ne!(id, [0u8; 32]);
1327    }
1328
1329    #[test]
1330    fn test_account_standing_checks() {
1331        assert!(AccountStanding::Good.is_good());
1332        assert!(AccountStanding::Fair.is_good());
1333        assert!(!AccountStanding::Poor.is_good());
1334        assert!(!AccountStanding::Closed.is_good());
1335    }
1336
1337    #[test]
1338    fn test_aml_risk_acceptability() {
1339        assert!(AmlRisk::Low.is_acceptable());
1340        assert!(AmlRisk::Medium.is_acceptable());
1341        assert!(AmlRisk::High.is_acceptable());
1342        assert!(!AmlRisk::Prohibited.is_acceptable());
1343    }
1344}