1use serde::{Deserialize, Serialize};
17
18use crate::{Address, Timestamp};
19
20pub type AddressProofId = [u8; 32];
26
27pub type BankStandingId = [u8; 32];
29
30pub type KycAttestationId = [u8; 32];
32
33pub type PolicyId = [u8; 32];
35
36pub type ProofId = [u8; 32];
38
39pub type SubjectRef = [u8; 32];
41
42pub type IssuerRef = [u8; 32];
44
45pub const ADDRESS_PROOF_DOMAIN_SEP: &[u8] = b"SRC892-ADDRESS-v1";
51
52pub const BANK_STANDING_DOMAIN_SEP: &[u8] = b"SRC893-STANDING-v1";
54
55pub const KYC_ATTESTATION_DOMAIN_SEP: &[u8] = b"SRC894-KYC-v1";
57
58pub const PHYSICAL_ADDRESS_DOMAIN_SEP: &[u8] = b"SRC892-PHYS-v1";
60
61pub const ACCOUNT_COMMITMENT_DOMAIN_SEP: &[u8] = b"SRC893-ACCOUNT-v1";
63
64pub const BALANCE_BRACKET_DOMAIN_SEP: &[u8] = b"SRC893-BALANCE-v1";
66
67pub const FINANCE_PROOF_DOMAIN_SEP: &[u8] = b"SRC895-PROOF-v1";
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
76#[repr(u8)]
77pub enum FinanceIssuerClass {
78 GovernmentRevenue = 0,
81 CentralBank = 1,
83 RegulatedBank = 2,
85 CreditUnion = 3,
87 RegulatedUtility = 4,
89 AddressVerificationService = 5,
91
92 Fintech = 10,
95 Neobank = 11,
97 MoneyServiceBusiness = 12,
99 Utility = 13,
101 Telecom = 14,
103 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 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 pub fn is_lowkey(&self) -> bool {
158 !self.is_official()
159 }
160
161 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 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 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 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#[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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
254pub struct FinanceIssuerProfile {
255 pub issuer_address: Address,
257 pub issuer_class: FinanceIssuerClass,
259 pub issuer_commitment: [u8; 32],
261 pub jurisdiction_code: String,
263 pub policy_id: PolicyId,
265 pub status: FinanceIssuerStatus,
267 pub registered_at_height: u64,
269 pub created_at: Timestamp,
271 pub updated_at: Timestamp,
273}
274
275#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
307#[repr(u8)]
308pub enum AddressProofType {
309 UtilityBill = 0,
311 BankStatement = 1,
313 TaxDocument = 2,
315 GovernmentMail = 3,
317 TelecomBill = 4,
319 InsuranceDocument = 5,
321 RentalAgreement = 6,
323 PropertyOwnership = 7,
325 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
362pub struct AddressProof {
363 pub proof_id: AddressProofId,
365 pub holder_address: Address,
367 pub subject_ref: SubjectRef,
369 pub address_commitment: [u8; 32],
372 pub jurisdiction_code: String,
374 pub postal_commitment: [u8; 32],
377 pub proof_type: AddressProofType,
379 pub document_date: Timestamp,
381 pub issuer_address: Address,
383 pub issuer_class: FinanceIssuerClass,
385 pub valid_from: Timestamp,
387 pub expiry: Timestamp,
389 pub policy_id: PolicyId,
391 pub revocation_ref: Option<[u8; 32]>,
393 pub created_at: Timestamp,
395 pub updated_at: Timestamp,
397}
398
399impl AddressProof {
400 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 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
463#[repr(u8)]
464pub enum AccountStanding {
465 Good = 0,
467 Fair = 1,
469 Poor = 2,
471 Restricted = 3,
473 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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
549#[repr(u8)]
550pub enum BalanceBracket {
551 Bracket0 = 0,
553 Bracket1 = 1,
555 Bracket2 = 2,
557 Bracket3 = 3,
559 Bracket4 = 4,
561 Bracket5 = 5,
563 Bracket6 = 6,
565 Bracket7 = 7,
567 Bracket8 = 8,
569 Bracket9 = 9,
571 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 pub fn is_at_least(&self, other: &BalanceBracket) -> bool {
611 (*self as u8) >= (*other as u8) && *self != BalanceBracket::Custom
612 }
613}
614
615#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
617pub struct BankStandingCredential {
618 pub credential_id: BankStandingId,
620 pub holder_address: Address,
622 pub subject_ref: SubjectRef,
624 pub account_commitment: [u8; 32],
627 pub bank_ref: IssuerRef,
629 pub account_type: AccountType,
631 pub standing: AccountStanding,
633 pub tenure_commitment: [u8; 32],
636 pub balance_bracket: BalanceBracket,
638 pub threshold_commitment: Option<[u8; 32]>,
640 pub issuer_address: Address,
642 pub issuer_class: FinanceIssuerClass,
644 pub valid_from: Timestamp,
646 pub expiry: Timestamp,
648 pub policy_id: PolicyId,
650 pub revocation_ref: Option<[u8; 32]>,
652 pub created_at: Timestamp,
654 pub updated_at: Timestamp,
656}
657
658impl BankStandingCredential {
659 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 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 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
732#[repr(u8)]
733pub enum KycLevel {
734 None = 0,
736 Basic = 1,
738 Enhanced = 2,
740 Full = 3,
742 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 pub fn meets_requirement(&self, required: &KycLevel) -> bool {
770 (*self as u8) >= (*required as u8)
771 }
772}
773
774#[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#[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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
839pub struct KycAttestation {
840 pub attestation_id: KycAttestationId,
842 pub holder_address: Address,
844 pub subject_ref: SubjectRef,
846 pub kyc_level: KycLevel,
848 pub aml_risk: AmlRisk,
850 pub identity_commitment: [u8; 32],
853 pub subject_jurisdiction: String,
855 pub methods_commitment: [u8; 32],
858 pub status: KycStatus,
860 pub issuer_address: Address,
862 pub issuer_class: FinanceIssuerClass,
864 pub valid_from: Timestamp,
866 pub expiry: Timestamp,
868 pub policy_id: PolicyId,
870 pub revocation_ref: Option<[u8; 32]>,
872 pub created_at: Timestamp,
874 pub updated_at: Timestamp,
876}
877
878impl KycAttestation {
879 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 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 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 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
939#[repr(u8)]
940pub enum FinanceProofType {
941 AddressInJurisdiction = 0,
943 AccountInGoodStanding = 1,
945 BalanceAtLeast = 2,
947 KycLevelAchieved = 3,
949 AmlRiskAcceptable = 4,
951 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
971pub struct FinanceProofProfile {
972 pub profile_id: [u8; 32],
974 pub proof_type: FinanceProofType,
976 pub required_jurisdiction: Option<String>,
978 pub min_balance_bracket: Option<BalanceBracket>,
980 pub min_kyc_level: Option<KycLevel>,
982 pub max_aml_risk: Option<AmlRisk>,
984 pub required_issuer_classes: Vec<FinanceIssuerClass>,
986 pub max_credential_age_secs: u64,
988 pub policy_id: PolicyId,
990}
991
992impl FinanceProofProfile {
993 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1014pub struct FinanceProofEnvelope {
1015 pub proof_id: ProofId,
1017 pub profile_id: [u8; 32],
1019 pub proof_type: FinanceProofType,
1021 pub subject_nullifier: [u8; 32],
1023 pub proof_data: Vec<u8>,
1025 pub public_inputs_commitment: [u8; 32],
1027 pub credential_refs: Vec<[u8; 32]>,
1029 pub source_issuer_class: FinanceIssuerClass,
1031 pub policy_id: PolicyId,
1033 pub valid_from: Timestamp,
1035 pub expiry: Timestamp,
1037 pub created_at: Timestamp,
1039}
1040
1041impl FinanceProofEnvelope {
1042 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 pub fn is_valid(&self, current_time: Timestamp) -> bool {
1060 current_time >= self.valid_from && current_time < self.expiry
1061 }
1062}
1063
1064#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1070pub enum FinanceEvent {
1071 IssuerRegistered {
1073 issuer_address: Address,
1074 issuer_class: FinanceIssuerClass,
1075 timestamp: Timestamp,
1076 },
1077 IssuerStatusUpdated {
1079 issuer_address: Address,
1080 old_status: FinanceIssuerStatus,
1081 new_status: FinanceIssuerStatus,
1082 timestamp: Timestamp,
1083 },
1084 AddressProofCreated {
1086 proof_id: AddressProofId,
1087 subject_ref: SubjectRef,
1088 proof_type: AddressProofType,
1089 timestamp: Timestamp,
1090 },
1091 AddressProofRevoked {
1093 proof_id: AddressProofId,
1094 revocation_ref: [u8; 32],
1095 timestamp: Timestamp,
1096 },
1097 BankStandingCreated {
1099 credential_id: BankStandingId,
1100 subject_ref: SubjectRef,
1101 standing: AccountStanding,
1102 timestamp: Timestamp,
1103 },
1104 BankStandingUpdated {
1106 credential_id: BankStandingId,
1107 old_standing: AccountStanding,
1108 new_standing: AccountStanding,
1109 timestamp: Timestamp,
1110 },
1111 BankStandingRevoked {
1113 credential_id: BankStandingId,
1114 revocation_ref: [u8; 32],
1115 timestamp: Timestamp,
1116 },
1117 KycAttestationCreated {
1119 attestation_id: KycAttestationId,
1120 subject_ref: SubjectRef,
1121 kyc_level: KycLevel,
1122 timestamp: Timestamp,
1123 },
1124 KycAttestationUpdated {
1126 attestation_id: KycAttestationId,
1127 old_status: KycStatus,
1128 new_status: KycStatus,
1129 timestamp: Timestamp,
1130 },
1131 KycAttestationRevoked {
1133 attestation_id: KycAttestationId,
1134 revocation_ref: [u8; 32],
1135 timestamp: Timestamp,
1136 },
1137 ProofSubmitted {
1139 proof_id: ProofId,
1140 proof_type: FinanceProofType,
1141 timestamp: Timestamp,
1142 },
1143 ProofVerified {
1145 proof_id: ProofId,
1146 verifier: Address,
1147 result: bool,
1148 timestamp: Timestamp,
1149 },
1150}
1151
1152#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1158#[repr(u8)]
1159pub enum FinanceOperation {
1160 RegisterIssuer = 0,
1162 UpdateIssuer = 1,
1163 SuspendIssuer = 2,
1164 RevokeIssuer = 3,
1165 ReactivateIssuer = 4,
1166
1167 CreateAddressProof = 10,
1169 UpdateAddressProof = 11,
1170 RevokeAddressProof = 12,
1171
1172 CreateBankStanding = 20,
1174 UpdateBankStanding = 21,
1175 RevokeBankStanding = 22,
1176
1177 CreateKycAttestation = 30,
1179 UpdateKycAttestation = 31,
1180 RevokeKycAttestation = 32,
1181
1182 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1213pub struct FinanceTxData {
1214 pub operation: FinanceOperation,
1215 pub data: Vec<u8>,
1216 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 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}