cpop_protocol/war/profiles/
vc.rs1use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7
8type Result<T> = std::result::Result<T, String>;
9use crate::war::ear::{Ar4siStatus, EarToken};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct VerifiableCredential {
14 #[serde(rename = "@context")]
15 pub context: Vec<String>,
16 #[serde(rename = "type")]
17 pub vc_type: Vec<String>,
18 pub issuer: String,
19 #[serde(rename = "validFrom")]
20 pub valid_from: String,
21 #[serde(rename = "credentialSubject")]
22 pub credential_subject: CredentialSubject,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub evidence: Option<Vec<VcEvidence>>,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub proof: Option<VcProof>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct CredentialSubject {
32 pub id: String,
33 #[serde(rename = "type")]
34 pub subject_type: String,
35 #[serde(rename = "processAttestation")]
36 pub process_attestation: ProcessAttestation,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ProcessAttestation {
42 pub status: String,
43 #[serde(rename = "trustVector", skip_serializing_if = "Option::is_none")]
44 pub trust_vector: Option<TrustVectorVc>,
45 #[serde(rename = "documentRef", skip_serializing_if = "Option::is_none")]
46 pub document_ref: Option<String>,
47 #[serde(rename = "chainDuration", skip_serializing_if = "Option::is_none")]
48 pub chain_duration: Option<String>,
49 #[serde(rename = "attestationTier", skip_serializing_if = "Option::is_none")]
50 pub attestation_tier: Option<String>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct TrustVectorVc {
56 pub instance_identity: i8,
57 pub configuration: i8,
58 pub executables: i8,
59 pub file_system: i8,
60 pub hardware: i8,
61 pub runtime_opaque: i8,
62 pub storage_opaque: i8,
63 pub sourced_data: i8,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct VcEvidence {
69 #[serde(rename = "type")]
70 pub evidence_type: String,
71 pub verifier: String,
72 #[serde(rename = "sealHash", skip_serializing_if = "Option::is_none")]
73 pub seal_hash: Option<String>,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct VcProof {
79 #[serde(rename = "type")]
80 pub proof_type: String,
81 pub cryptosuite: String,
82 #[serde(rename = "verificationMethod")]
83 pub verification_method: String,
84 #[serde(rename = "proofPurpose")]
85 pub proof_purpose: String,
86 #[serde(rename = "proofValue")]
87 pub proof_value: String,
88}
89
90pub fn to_verifiable_credential(ear: &EarToken, author_did: &str) -> Result<VerifiableCredential> {
92 let appr = ear
93 .pop_appraisal()
94 .ok_or_else(|| String::from("EAR token missing 'pop' submodule"))?;
95
96 let tv_vc = appr
97 .ear_trustworthiness_vector
98 .as_ref()
99 .map(|tv| TrustVectorVc {
100 instance_identity: tv.instance_identity,
101 configuration: tv.configuration,
102 executables: tv.executables,
103 file_system: tv.file_system,
104 hardware: tv.hardware,
105 runtime_opaque: tv.runtime_opaque,
106 storage_opaque: tv.storage_opaque,
107 sourced_data: tv.sourced_data,
108 });
109
110 let document_ref = appr.pop_evidence_ref.as_ref().map(hex::encode);
111
112 let chain_duration = appr.pop_chain_duration.map(|secs| {
113 let hours = secs / 3600;
114 let minutes = (secs % 3600) / 60;
115 let remaining_secs = secs % 60;
116 if hours > 0 {
117 format!("PT{}H{}M{}S", hours, minutes, remaining_secs)
118 } else if minutes > 0 {
119 format!("PT{}M{}S", minutes, remaining_secs)
120 } else {
121 format!("PT{}S", remaining_secs)
122 }
123 });
124
125 let tier_str = appr
126 .ear_trustworthiness_vector
127 .as_ref()
128 .map(|tv| {
129 if tv.hardware >= Ar4siStatus::Affirming as i8 {
130 "hardware_bound"
131 } else if tv.hardware >= Ar4siStatus::Warning as i8 {
132 "attested_software"
133 } else {
134 "software_only"
135 }
136 })
137 .map(String::from);
138
139 let valid_from: DateTime<Utc> = DateTime::from_timestamp(ear.iat, 0).unwrap_or_else(Utc::now);
140
141 let seal_hash = appr.pop_seal.as_ref().map(|s| hex::encode(s.h3));
142
143 let evidence = vec![VcEvidence {
144 evidence_type: "ProofOfProcessEvidence".to_string(),
145 verifier: ear.ear_verifier_id.build.clone(),
146 seal_hash,
147 }];
148
149 let proof = VcProof {
151 proof_type: "DataIntegrityProof".to_string(),
152 cryptosuite: "eddsa-rdfc-2022".to_string(),
153 verification_method: format!("{}#key-1", author_did),
154 proof_purpose: "assertionMethod".to_string(),
155 proof_value: String::new(),
156 };
157
158 Ok(VerifiableCredential {
159 context: vec![
160 "https://www.w3.org/ns/credentials/v2".to_string(),
161 "https://writerslogic.com/ns/pop/v1".to_string(),
162 ],
163 vc_type: vec![
164 "VerifiableCredential".to_string(),
165 "ProcessAttestationCredential".to_string(),
166 ],
167 issuer: "did:web:writerslogic.com".to_string(),
168 valid_from: valid_from.to_rfc3339(),
169 credential_subject: CredentialSubject {
170 id: author_did.to_string(),
171 subject_type: "Author".to_string(),
172 process_attestation: ProcessAttestation {
173 status: appr.ear_status.as_str().to_string(),
174 trust_vector: tv_vc,
175 document_ref,
176 chain_duration,
177 attestation_tier: tier_str,
178 },
179 },
180 evidence: Some(evidence),
181 proof: Some(proof),
182 })
183}