qualia_client_core/wellfair/api/
encryption.rs1use sha2::{Digest, Sha256};
4
5use super::*;
6
7impl WebizenHostApi {
8 #[cfg(not(target_arch = "wasm32"))]
18 pub fn owner_envelope_public_hex(&self) -> String {
19 use crate::envelope_encryption::{EnvelopeKeypair, OWNER_ENVELOPE_DOMAIN};
20 EnvelopeKeypair::derive(&self.signing_key.to_bytes(), OWNER_ENVELOPE_DOMAIN).public_hex()
21 }
22
23 #[cfg(not(target_arch = "wasm32"))]
30 pub fn seal_and_grant_consent_credential(
31 &self,
32 agent_did: &str,
33 agent_public_hex: &str,
34 scope: &str,
35 purpose: &str,
36 plaintext: &str,
37 expiry_unix: Option<u64>,
38 ) -> Result<crate::consent_credential::ConsentCredential, String> {
39 use crate::envelope_encryption::{EnvelopeKeypair, OWNER_ENVELOPE_DOMAIN};
40 let owner = EnvelopeKeypair::derive(&self.signing_key.to_bytes(), OWNER_ENVELOPE_DOMAIN);
41 let recipient_public: [u8; 32] = if agent_public_hex.trim().is_empty() {
42 owner.public
43 } else {
44 let bytes = hex::decode(agent_public_hex.trim())
45 .map_err(|e| format!("agent public key not hex: {e}"))?;
46 bytes
47 .as_slice()
48 .try_into()
49 .map_err(|_| "agent public key must be 32 bytes".to_string())?
50 };
51 let now = Self::now_unix();
52 let id = {
53 let d = Sha256::digest(format!("{agent_did}:{scope}:{now}").as_bytes());
54 format!("cc-{}", hex::encode(&d[..6]))
55 };
56 self.accountability_store()?
57 .seal_and_grant_credential(
58 id,
59 &self.owner_did,
60 agent_did,
61 scope,
62 purpose,
63 plaintext.as_bytes(),
64 &recipient_public,
65 vec![self.owner_did.clone()],
66 expiry_unix,
67 &self.signing_key,
68 now,
69 )
70 .map_err(|e| e.to_string())
71 }
72
73 #[cfg(not(target_arch = "wasm32"))]
78 pub fn open_owner_payload(&self, credential_id: &str) -> Result<String, String> {
79 use crate::envelope_encryption::{EnvelopeKeypair, OWNER_ENVELOPE_DOMAIN};
80 let owner = EnvelopeKeypair::derive(&self.signing_key.to_bytes(), OWNER_ENVELOPE_DOMAIN);
81 let bytes = self
82 .accountability_store()?
83 .open_payload_via_credential(credential_id, &owner.secret, Self::now_unix())
84 .map_err(|e| e.to_string())?;
85 String::from_utf8(bytes).map_err(|e| format!("payload not valid utf-8: {e}"))
86 }
87
88 pub fn arm_dead_mans_switch(
92 &self,
93 switch: crate::dead_mans_switch::DeadMansSwitch,
94 ) -> Result<(), String> {
95 self.accountability_store()?
96 .arm_dead_mans_switch(switch, &self.signing_key, Self::now_unix())
97 .map_err(|e| e.to_string())
98 }
99
100 pub fn dead_mans_alive(&self, commitment_hex: &str) -> Result<bool, String> {
103 let c = crate::accountability_store::parse_commitment_hex(commitment_hex)?;
104 self.accountability_store()?
105 .dead_mans_alive(&c, &self.signing_key, Self::now_unix())
106 .map_err(|e| e.to_string())
107 }
108
109 pub fn attest_dead_mans(
111 &self,
112 commitment_hex: &str,
113 attestation: crate::dead_mans_switch::PartyAttestation,
114 ) -> Result<bool, String> {
115 let c = crate::accountability_store::parse_commitment_hex(commitment_hex)?;
116 self.accountability_store()?
117 .attest_dead_mans(&c, attestation, &self.signing_key, Self::now_unix())
118 .map_err(|e| e.to_string())
119 }
120
121 pub fn enact_dead_mans(
125 &self,
126 commitment_hex: &str,
127 ) -> Result<Option<crate::dead_mans_switch::Disposition>, String> {
128 let c = crate::accountability_store::parse_commitment_hex(commitment_hex)?;
129 self.accountability_store()?
130 .enact_dead_mans(&c, &self.signing_key, Self::now_unix())
131 .map_err(|e| e.to_string())
132 }
133
134 pub fn list_dead_mans_switches(
136 &self,
137 ) -> Result<Vec<crate::accountability_store::DeadMansSwitchRecord>, String> {
138 self.accountability_store()?
139 .list_dead_mans_switches()
140 .map_err(|e| e.to_string())
141 }
142
143 #[cfg(not(target_arch = "wasm32"))]
149 pub fn enact_dead_mans_release(
150 &self,
151 commitment_hex: &str,
152 party_keys_hex: Vec<(String, String)>,
153 ) -> Result<serde_json::Value, String> {
154 use crate::envelope_encryption::{unwrap_dek, EnvelopeKeypair, OWNER_ENVELOPE_DOMAIN};
155 let c = crate::accountability_store::parse_commitment_hex(commitment_hex)?;
156 let now = Self::now_unix();
157 let store = self.accountability_store()?;
158 let owner = EnvelopeKeypair::derive(&self.signing_key.to_bytes(), OWNER_ENVELOPE_DOMAIN);
159 let wrapped = store
161 .wrapped_key_for(&c, &self.owner_did, now)
162 .map_err(|e| e.to_string())?
163 .ok_or_else(|| {
164 "no owner credential holds the DEK for this payload (seal it to yourself first)"
165 .to_string()
166 })?;
167 let dek = unwrap_dek(&owner.secret, &wrapped)?;
168 let mut party_keys: Vec<(String, [u8; 32])> = Vec::new();
169 for (did, pk_hex) in party_keys_hex {
170 let bytes =
171 hex::decode(pk_hex.trim()).map_err(|e| format!("party key not hex: {e}"))?;
172 let pk: [u8; 32] = bytes
173 .as_slice()
174 .try_into()
175 .map_err(|_| "party key must be 32 bytes".to_string())?;
176 party_keys.push((did, pk));
177 }
178 let disposition = store
179 .enact_dead_mans_release(
180 &c,
181 &dek,
182 &party_keys,
183 &self.owner_did,
184 &self.signing_key,
185 now,
186 )
187 .map_err(|e| e.to_string())?;
188 Ok(serde_json::json!({ "enacted": disposition.is_some(), "disposition": disposition }))
189 }
190
191 #[cfg(not(target_arch = "wasm32"))]
196 pub fn split_dek_recovery(
197 &self,
198 commitment_hex: &str,
199 threshold: usize,
200 parties: Vec<String>,
201 ) -> Result<serde_json::Value, String> {
202 use crate::envelope_encryption::{unwrap_dek, EnvelopeKeypair, OWNER_ENVELOPE_DOMAIN};
203 let c = crate::accountability_store::parse_commitment_hex(commitment_hex)?;
204 let now = Self::now_unix();
205 let store = self.accountability_store()?;
206 let owner = EnvelopeKeypair::derive(&self.signing_key.to_bytes(), OWNER_ENVELOPE_DOMAIN);
207 let wrapped = store
208 .wrapped_key_for(&c, &self.owner_did, now)
209 .map_err(|e| e.to_string())?
210 .ok_or_else(|| "no owner credential holds the DEK for this payload".to_string())?;
211 let dek = unwrap_dek(&owner.secret, &wrapped)?;
212 let shares = crate::shamir_recovery::split(&dek, threshold, parties.len())?;
213 let tagged: Vec<serde_json::Value> = parties
214 .iter()
215 .zip(shares.iter())
216 .map(|(party, share)| serde_json::json!({ "party": party, "share": share }))
217 .collect();
218 Ok(serde_json::json!({ "threshold": threshold, "shares": tagged }))
219 }
220
221 #[cfg(not(target_arch = "wasm32"))]
224 pub fn reconstruct_and_release(
225 &self,
226 commitment_hex: &str,
227 shares: Vec<crate::shamir_recovery::Share>,
228 party_keys_hex: Vec<(String, String)>,
229 ) -> Result<serde_json::Value, String> {
230 let c = crate::accountability_store::parse_commitment_hex(commitment_hex)?;
231 let now = Self::now_unix();
232 let mut party_keys: Vec<(String, [u8; 32])> = Vec::new();
233 for (did, pk_hex) in party_keys_hex {
234 let bytes =
235 hex::decode(pk_hex.trim()).map_err(|e| format!("party key not hex: {e}"))?;
236 let pk: [u8; 32] = bytes
237 .as_slice()
238 .try_into()
239 .map_err(|_| "party key must be 32 bytes".to_string())?;
240 party_keys.push((did, pk));
241 }
242 let disposition = self
243 .accountability_store()?
244 .reconstruct_and_release(
245 &c,
246 &shares,
247 &party_keys,
248 &self.owner_did,
249 &self.signing_key,
250 now,
251 )
252 .map_err(|e| e.to_string())?;
253 Ok(serde_json::json!({ "enacted": disposition.is_some(), "disposition": disposition }))
254 }
255
256 pub fn set_peer_envelope_key(&self, did: &str, pubkey_hex: &str) -> Result<(), String> {
260 crate::social_peers::set_peer_envelope_key(did, pubkey_hex)
261 }
262
263 #[cfg(not(target_arch = "wasm32"))]
268 pub fn enact_dead_mans_release_via_peers(
269 &self,
270 commitment_hex: &str,
271 ) -> Result<serde_json::Value, String> {
272 let c = crate::accountability_store::parse_commitment_hex(commitment_hex)?;
273 let switches = self
274 .accountability_store()?
275 .list_dead_mans_switches()
276 .map_err(|e| e.to_string())?;
277 let rec = switches
278 .iter()
279 .find(|r| r.switch.payload_commitment == c)
280 .ok_or_else(|| "no dead-man switch for that commitment".to_string())?;
281 let parties = match &rec.switch.disposition {
282 crate::dead_mans_switch::Disposition::ReleaseTo { parties } => parties.clone(),
283 _ => Vec::new(),
284 };
285 let peers = crate::social_peers::list_peers();
286 let resolved = crate::social_peers::resolve_envelope_keys(&peers, &parties);
287 let have: std::collections::BTreeSet<&str> =
288 resolved.iter().map(|(d, _)| d.as_str()).collect();
289 let missing: Vec<String> = parties
290 .iter()
291 .filter(|d| !have.contains(d.as_str()))
292 .cloned()
293 .collect();
294 let result = self.enact_dead_mans_release(commitment_hex, resolved)?;
295 Ok(serde_json::json!({ "result": result, "missing_keys_for": missing }))
296 }
297
298 pub fn arm_incapacity_switch(
300 &self,
301 switch: crate::incapacity_switch::IncapacitySwitch,
302 ) -> Result<(), String> {
303 self.accountability_store()?
304 .arm_incapacity_switch(switch, &self.signing_key, Self::now_unix())
305 .map_err(|e| e.to_string())
306 }
307
308 pub fn activate_incapacity(
310 &self,
311 principal_did: &str,
312 attesting_parties: Vec<String>,
313 official_instrument: Option<String>,
314 ) -> Result<bool, String> {
315 self.accountability_store()?
316 .activate_incapacity(
317 principal_did,
318 &attesting_parties,
319 official_instrument.as_deref(),
320 &self.signing_key,
321 Self::now_unix(),
322 )
323 .map_err(|e| e.to_string())
324 }
325
326 pub fn regain_capacity(&self, principal_did: &str) -> Result<bool, String> {
328 self.accountability_store()?
329 .regain_capacity(principal_did, &self.signing_key, Self::now_unix())
330 .map_err(|e| e.to_string())
331 }
332
333 pub fn list_incapacity_switches(
335 &self,
336 ) -> Result<Vec<crate::incapacity_switch::IncapacitySwitch>, String> {
337 self.accountability_store()?
338 .list_incapacity_switches()
339 .map_err(|e| e.to_string())
340 }
341}