use serde::{Deserialize, Serialize};
use crate::{Address, BlockHeight, Timestamp};
use crate::agreement::{AttachmentRef, EncryptionMeta};
pub type CaseId = [u8; 32];
pub type ProcessEventId = [u8; 32];
pub type OrderId = [u8; 32];
pub type BenefitId = [u8; 32];
pub type PolicyId = [u8; 32];
pub type ProofId = [u8; 32];
pub type SubjectId = [u8; 32];
pub const CASE_DOMAIN_SEP: &[u8] = b"SRC851-CASE:";
pub const CASE_COMMITMENT_SEP: &[u8] = b"SRC851-COMMITMENT:v1:";
pub const PROCESS_EVENT_DOMAIN_SEP: &[u8] = b"SRC852-EVENT:";
pub const EVENT_COMMITMENT_SEP: &[u8] = b"SRC852-COMMITMENT:v1:";
pub const ORDER_DOMAIN_SEP: &[u8] = b"SRC853-ORDER:";
pub const ORDER_COMMITMENT_SEP: &[u8] = b"SRC853-COMMITMENT:v1:";
pub const BENEFIT_DOMAIN_SEP: &[u8] = b"SRC854-BENEFIT:";
pub const DETERMINATION_COMMITMENT_SEP: &[u8] = b"SRC854-DETERMINATION:v1:";
pub const LEGAL_PROOF_DOMAIN_SEP: &[u8] = b"SRC855-PROOF:";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum CaseType {
Civil = 0,
Criminal = 1,
Administrative = 2,
Family = 3,
Probate = 4,
Bankruptcy = 5,
SmallClaims = 6,
Arbitration = 7,
Mediation = 8,
Regulatory = 9,
TaxCourt = 10,
Immigration = 11,
Other = 255,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum CaseStatus {
Filed = 0,
Active = 1,
Stayed = 2,
Closed = 3,
Dismissed = 4,
Settled = 5,
Consolidated = 6,
Transferred = 7,
OnAppeal = 8,
Sealed = 9,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum LegalIssuerClass {
CourtSystem = 0,
GovernmentAgency = 1,
Tribunal = 2,
ArbitrationBody = 3,
LawFirm = 10,
Notary = 11,
Auditor = 12,
LegalAid = 13,
ProcessServer = 14,
CourtReporter = 15,
GuardianAdLitem = 16,
Mediator = 17,
Other = 255,
}
impl LegalIssuerClass {
pub fn is_official(&self) -> bool {
matches!(self, Self::CourtSystem | Self::GovernmentAgency | Self::Tribunal)
}
pub fn is_lowkey(&self) -> bool {
matches!(
self,
Self::LawFirm
| Self::Notary
| Self::Auditor
| Self::LegalAid
| Self::ProcessServer
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CaseAnchor {
pub case_id: CaseId,
pub case_commitment: [u8; 32],
pub jurisdiction_code: String,
pub case_type: Option<CaseType>,
pub public_reference: Option<String>,
pub policy_id: PolicyId,
pub issuer_class: LegalIssuerClass,
pub issuer_address: Address,
pub status: CaseStatus,
pub created_at: Timestamp,
pub updated_at: Timestamp,
pub anchored_at_height: BlockHeight,
pub related_cases: Vec<CaseId>,
}
impl CaseAnchor {
pub fn generate_id(
issuer: &Address,
case_commitment: &[u8; 32],
jurisdiction: &str,
nonce: &[u8; 32],
) -> CaseId {
let mut hasher = blake3::Hasher::new();
hasher.update(CASE_DOMAIN_SEP);
hasher.update(b":v1:");
hasher.update(issuer.as_ref());
hasher.update(case_commitment);
hasher.update(jurisdiction.as_bytes());
hasher.update(nonce);
*hasher.finalize().as_bytes()
}
pub fn generate_commitment(
case_number: &str,
parties_commitment: &[u8; 32],
filing_date: Timestamp,
additional_data: Option<&[u8]>,
) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(CASE_COMMITMENT_SEP);
hasher.update(case_number.as_bytes());
hasher.update(parties_commitment);
hasher.update(&filing_date.to_le_bytes());
if let Some(data) = additional_data {
hasher.update(data);
}
*hasher.finalize().as_bytes()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum ProcessEventType {
Filed = 0,
AnswerFiled = 1,
MotionFiled = 2,
AmendmentFiled = 3,
DiscoveryFiled = 4,
AppealFiled = 5,
Served = 10,
NoticeDelivered = 11,
SubpoenaServed = 12,
PublicationCompleted = 13,
HearingScheduled = 20,
HearingHeld = 21,
HearingContinued = 22,
HearingCancelled = 23,
TrialStarted = 24,
TrialConcluded = 25,
OrderIssued = 30,
JudgmentEntered = 31,
RulingIssued = 32,
VerdictRendered = 33,
CaseStayed = 40,
StayLifted = 41,
CaseSealed = 42,
CaseUnsealed = 43,
CaseDismissed = 44,
CaseSettled = 45,
CaseClosed = 46,
CaseReopened = 47,
EvidenceAdmitted = 50,
WitnessTestimony = 51,
ExpertOpinion = 52,
MediationCompleted = 53,
SettlementConference = 54,
Other = 255,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum ProcessEventStatus {
Recorded = 0,
Superseded = 1,
Revoked = 2,
Corrected = 3,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProcessEvent {
pub event_id: ProcessEventId,
pub case_id: CaseId,
pub event_type: ProcessEventType,
pub event_commitment: [u8; 32],
pub issuer_address: Address,
pub issuer_class: LegalIssuerClass,
pub event_time_start: Option<Timestamp>,
pub event_time_end: Option<Timestamp>,
pub attachments: Vec<AttachmentRef>,
pub policy_id: PolicyId,
pub revocation_ref: Option<[u8; 32]>,
pub status: ProcessEventStatus,
pub created_at: Timestamp,
pub recorded_at_height: BlockHeight,
pub supersedes: Option<ProcessEventId>,
}
impl ProcessEvent {
pub fn generate_id(
case_id: &CaseId,
event_type: ProcessEventType,
issuer: &Address,
nonce: &[u8; 32],
) -> ProcessEventId {
let mut hasher = blake3::Hasher::new();
hasher.update(PROCESS_EVENT_DOMAIN_SEP);
hasher.update(b":v1:");
hasher.update(case_id);
hasher.update(&[event_type as u8]);
hasher.update(issuer.as_ref());
hasher.update(nonce);
*hasher.finalize().as_bytes()
}
pub fn generate_commitment(
event_details: &[u8],
timestamp: Timestamp,
participants_commitment: Option<&[u8; 32]>,
) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(EVENT_COMMITMENT_SEP);
hasher.update(event_details);
hasher.update(×tamp.to_le_bytes());
if let Some(pc) = participants_commitment {
hasher.update(pc);
}
*hasher.finalize().as_bytes()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum OrderType {
Tro = 0,
PreliminaryInjunction = 1,
ProtectiveOrder = 2,
DiscoveryOrder = 3,
SchedulingOrder = 4,
DefaultJudgment = 10,
SummaryJudgment = 11,
FinalJudgment = 12,
ConsentJudgment = 13,
DeclaratoryJudgment = 14,
PermanentInjunction = 20,
MandatoryInjunction = 21,
ProhibitoryInjunction = 22,
MoneyJudgment = 30,
GarnishmentOrder = 31,
AttachmentOrder = 32,
RestitutionOrder = 33,
CostOrder = 34,
ChildCustodyOrder = 40,
SupportOrder = 41,
DivorceDecree = 42,
ProbateOrder = 43,
GuardianshipOrder = 44,
Sentence = 50,
BailOrder = 51,
ProbationOrder = 52,
ExpungementOrder = 53,
AdministrativeOrder = 60,
ConsentDecree = 61,
ComplianceOrder = 62,
Other = 255,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum OrderStatus {
Active = 0,
Stayed = 1,
Vacated = 2,
Superseded = 3,
Expired = 4,
Satisfied = 5,
Modified = 6,
Reversed = 7,
Affirmed = 8,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CourtOrder {
pub order_id: OrderId,
pub case_id: CaseId,
pub order_type: OrderType,
pub order_commitment: [u8; 32],
pub issuer_address: Address,
pub issuer_class: LegalIssuerClass,
pub status: OrderStatus,
pub effective_from: Timestamp,
pub expiry: Option<Timestamp>,
pub policy_id: PolicyId,
pub revocation_ref: Option<[u8; 32]>,
pub created_at: Timestamp,
pub updated_at: Timestamp,
pub issued_at_height: BlockHeight,
pub supersedes_order_id: Option<OrderId>,
pub attachments: Vec<AttachmentRef>,
}
impl CourtOrder {
pub fn generate_id(
case_id: &CaseId,
order_type: OrderType,
issuer: &Address,
nonce: &[u8; 32],
) -> OrderId {
let mut hasher = blake3::Hasher::new();
hasher.update(ORDER_DOMAIN_SEP);
hasher.update(b":v1:");
hasher.update(case_id);
hasher.update(&[order_type as u8]);
hasher.update(issuer.as_ref());
hasher.update(nonce);
*hasher.finalize().as_bytes()
}
pub fn generate_commitment(
order_text_hash: &[u8; 32],
obligations_hash: Option<&[u8; 32]>,
parties_hash: &[u8; 32],
) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(ORDER_COMMITMENT_SEP);
hasher.update(order_text_hash);
if let Some(oh) = obligations_hash {
hasher.update(oh);
}
hasher.update(parties_hash);
*hasher.finalize().as_bytes()
}
pub fn is_in_effect(&self, current_time: Timestamp) -> bool {
if !matches!(self.status, OrderStatus::Active | OrderStatus::Affirmed) {
return false;
}
if current_time < self.effective_from {
return false;
}
if let Some(exp) = self.expiry {
if current_time >= exp {
return false;
}
}
true
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum BenefitType {
SocialSecurityRetirement = 0,
SocialSecurityDisability = 1,
SupplementalSecurityIncome = 2,
SurvivorsInsurance = 3,
Medicare = 10,
Medicaid = 11,
ChildHealthInsurance = 12,
VeteranHealthcare = 13,
UnemploymentInsurance = 20,
WorkersCompensation = 21,
DisabilityInsurance = 22,
TrainingBenefit = 23,
FoodAssistance = 30,
HousingAssistance = 31,
EnergyAssistance = 32,
ChildNutrition = 33,
ChildTaxCredit = 40,
EarnedIncomeCredit = 41,
ChildcareAssistance = 42,
FamilyLeave = 43,
EducationGrant = 50,
StudentLoanSubsidy = 51,
TuitionAssistance = 52,
VeteranPension = 60,
VeteranDisability = 61,
GiBill = 62,
VeteranHousing = 63,
RefugeeAssistance = 70,
AsylumSupport = 71,
Other = 255,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum BenefitStatus {
Pending = 0,
Eligible = 1,
Approved = 2,
Denied = 3,
Terminated = 4,
Suspended = 5,
UnderReview = 6,
Expired = 7,
Reduced = 8,
Increased = 9,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BenefitDetermination {
pub benefit_id: BenefitId,
pub benefit_type: BenefitType,
pub jurisdiction_code: String,
pub status: BenefitStatus,
pub determination_commitment: [u8; 32],
pub subject_nullifier: [u8; 32],
pub issuer_address: Address,
pub issuer_class: LegalIssuerClass,
pub valid_from: Timestamp,
pub expiry: Option<Timestamp>,
pub policy_id: PolicyId,
pub revocation_ref: Option<[u8; 32]>,
pub created_at: Timestamp,
pub updated_at: Timestamp,
pub recorded_at_height: BlockHeight,
pub supersedes: Option<BenefitId>,
}
impl BenefitDetermination {
pub fn generate_id(
subject_nullifier: &[u8; 32],
benefit_type: BenefitType,
jurisdiction: &str,
nonce: &[u8; 32],
) -> BenefitId {
let mut hasher = blake3::Hasher::new();
hasher.update(BENEFIT_DOMAIN_SEP);
hasher.update(b":v1:");
hasher.update(subject_nullifier);
hasher.update(&[benefit_type as u8]);
hasher.update(jurisdiction.as_bytes());
hasher.update(nonce);
*hasher.finalize().as_bytes()
}
pub fn generate_commitment(
eligibility_criteria_hash: &[u8; 32],
amount_commitment: Option<&[u8; 32]>,
conditions_hash: Option<&[u8; 32]>,
) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(DETERMINATION_COMMITMENT_SEP);
hasher.update(eligibility_criteria_hash);
if let Some(ac) = amount_commitment {
hasher.update(ac);
}
if let Some(ch) = conditions_hash {
hasher.update(ch);
}
*hasher.finalize().as_bytes()
}
pub fn is_valid(&self, current_time: Timestamp) -> bool {
if !matches!(
self.status,
BenefitStatus::Eligible | BenefitStatus::Approved | BenefitStatus::Increased
) {
return false;
}
if current_time < self.valid_from {
return false;
}
if let Some(exp) = self.expiry {
if current_time >= exp {
return false;
}
}
true
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum LegalProofProfile {
ActiveOrderOfType = 0,
BenefitApproved = 1,
CaseEventExists = 2,
CaseStatus = 3,
OrderIssuedByValid = 4,
BenefitEligible = 5,
NoActiveOrderOfType = 6,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum LegalProofType {
Mock = 0,
Groth16 = 1,
Plonk = 2,
ThresholdSignature = 3,
MerkleInclusion = 4,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LegalProofEnvelope {
pub proof_id: ProofId,
pub profile: LegalProofProfile,
pub profile_id: String,
pub policy_ids: Vec<PolicyId>,
pub public_inputs: Vec<u8>,
pub proof_data: Vec<u8>,
pub proof_type: LegalProofType,
pub subject_nullifier: [u8; 32],
pub generated_at: Timestamp,
pub expires_at: Timestamp,
}
impl LegalProofEnvelope {
pub fn generate_id(
profile: LegalProofProfile,
subject_nullifier: &[u8; 32],
policy_ids: &[PolicyId],
nonce: &[u8; 32],
) -> ProofId {
let mut hasher = blake3::Hasher::new();
hasher.update(LEGAL_PROOF_DOMAIN_SEP);
hasher.update(b":v1:");
hasher.update(&[profile as u8]);
hasher.update(subject_nullifier);
for policy_id in policy_ids {
hasher.update(policy_id);
}
hasher.update(nonce);
*hasher.finalize().as_bytes()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum LegalOperation {
AnchorCase = 0,
UpdateCase = 1,
CloseCase = 2,
SealCase = 3,
UnsealCase = 4,
ConsolidateCase = 5,
TransferCase = 6,
RecordEvent = 10,
UpdateEvent = 11,
SupersedeEvent = 12,
RevokeEvent = 13,
IssueOrder = 20,
UpdateOrderStatus = 21,
StayOrder = 22,
VacateOrder = 23,
SupersedeOrder = 24,
ModifyOrder = 25,
DetermineBenefit = 30,
UpdateBenefitStatus = 31,
TerminateBenefit = 32,
SuspendBenefit = 33,
ReinstateBenefit = 34,
SubmitProof = 40,
VerifyProof = 41,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LegalTxData {
pub operation: LegalOperation,
pub data: Vec<u8>,
pub recipient: crate::Address,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum LegalEvent {
CaseAnchored {
case_id: CaseId,
jurisdiction: String,
case_commitment: [u8; 32],
timestamp: Timestamp,
},
CaseUpdated {
case_id: CaseId,
new_status: CaseStatus,
timestamp: Timestamp,
},
CaseSealed {
case_id: CaseId,
timestamp: Timestamp,
},
CaseUnsealed {
case_id: CaseId,
timestamp: Timestamp,
},
CaseEventRecorded {
case_id: CaseId,
event_type: ProcessEventType,
issuer: Address,
event_hash: [u8; 32],
timestamp: Timestamp,
},
CaseEventSuperseded {
old_event_id: ProcessEventId,
new_event_id: ProcessEventId,
timestamp: Timestamp,
},
OrderIssued {
order_id: OrderId,
case_id: CaseId,
order_type: OrderType,
status: OrderStatus,
timestamp: Timestamp,
},
OrderStatusUpdated {
order_id: OrderId,
new_status: OrderStatus,
timestamp: Timestamp,
},
BenefitDetermined {
benefit_id: BenefitId,
benefit_type: BenefitType,
status: BenefitStatus,
jurisdiction: String,
timestamp: Timestamp,
},
BenefitUpdated {
benefit_id: BenefitId,
new_status: BenefitStatus,
timestamp: Timestamp,
},
LegalProofSubmitted {
proof_id: ProofId,
profile: LegalProofProfile,
timestamp: Timestamp,
},
LegalProofVerified {
proof_id: ProofId,
valid: bool,
timestamp: Timestamp,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_case_id_generation() {
let issuer = Address::new([1u8; 20]);
let commitment = [2u8; 32];
let nonce = [3u8; 32];
let id = CaseAnchor::generate_id(&issuer, &commitment, "US-NY", &nonce);
assert_ne!(id, [0u8; 32]);
let id2 = CaseAnchor::generate_id(&issuer, &commitment, "US-NY", &nonce);
assert_eq!(id, id2);
let id3 = CaseAnchor::generate_id(&issuer, &commitment, "US-CA", &nonce);
assert_ne!(id, id3);
}
#[test]
fn test_case_commitment() {
let parties = [1u8; 32];
let commitment = CaseAnchor::generate_commitment("2024-CV-12345", &parties, 1000, None);
assert_ne!(commitment, [0u8; 32]);
}
#[test]
fn test_process_event_id() {
let case_id = [1u8; 32];
let issuer = Address::new([2u8; 20]);
let nonce = [3u8; 32];
let id = ProcessEvent::generate_id(&case_id, ProcessEventType::Filed, &issuer, &nonce);
assert_ne!(id, [0u8; 32]);
}
#[test]
fn test_order_id_generation() {
let case_id = [1u8; 32];
let issuer = Address::new([2u8; 20]);
let nonce = [3u8; 32];
let id = CourtOrder::generate_id(&case_id, OrderType::FinalJudgment, &issuer, &nonce);
assert_ne!(id, [0u8; 32]);
}
#[test]
fn test_order_in_effect() {
let order = CourtOrder {
order_id: [1u8; 32],
case_id: [2u8; 32],
order_type: OrderType::FinalJudgment,
order_commitment: [3u8; 32],
issuer_address: Address::new([4u8; 20]),
issuer_class: LegalIssuerClass::CourtSystem,
status: OrderStatus::Active,
effective_from: 1000,
expiry: Some(2000),
policy_id: [5u8; 32],
revocation_ref: None,
created_at: 1000,
updated_at: 1000,
issued_at_height: 100,
supersedes_order_id: None,
attachments: vec![],
};
assert!(!order.is_in_effect(500)); assert!(order.is_in_effect(1500)); assert!(!order.is_in_effect(2500)); }
#[test]
fn test_benefit_id_generation() {
let nullifier = [1u8; 32];
let nonce = [2u8; 32];
let id = BenefitDetermination::generate_id(
&nullifier,
BenefitType::SocialSecurityRetirement,
"US",
&nonce,
);
assert_ne!(id, [0u8; 32]);
}
#[test]
fn test_benefit_validity() {
let benefit = BenefitDetermination {
benefit_id: [1u8; 32],
benefit_type: BenefitType::Medicare,
jurisdiction_code: "US".to_string(),
status: BenefitStatus::Approved,
determination_commitment: [2u8; 32],
subject_nullifier: [3u8; 32],
issuer_address: Address::new([4u8; 20]),
issuer_class: LegalIssuerClass::GovernmentAgency,
valid_from: 1000,
expiry: None,
policy_id: [5u8; 32],
revocation_ref: None,
created_at: 1000,
updated_at: 1000,
recorded_at_height: 100,
supersedes: None,
};
assert!(!benefit.is_valid(500)); assert!(benefit.is_valid(1500)); }
#[test]
fn test_legal_issuer_class() {
assert!(LegalIssuerClass::CourtSystem.is_official());
assert!(LegalIssuerClass::GovernmentAgency.is_official());
assert!(!LegalIssuerClass::LawFirm.is_official());
assert!(LegalIssuerClass::LawFirm.is_lowkey());
assert!(LegalIssuerClass::Notary.is_lowkey());
assert!(!LegalIssuerClass::CourtSystem.is_lowkey());
}
#[test]
fn test_legal_proof_id() {
let nullifier = [1u8; 32];
let policies = vec![[2u8; 32], [3u8; 32]];
let nonce = [4u8; 32];
let id = LegalProofEnvelope::generate_id(
LegalProofProfile::BenefitApproved,
&nullifier,
&policies,
&nonce,
);
assert_ne!(id, [0u8; 32]);
}
}