1use cid::Cid;
2use ed25519_dalek::{Signature, Verifier, VerifyingKey};
3use ipld_core::ipld::Ipld;
4use serde::{Deserialize, Serialize};
5#[cfg(not(target_arch = "wasm32"))]
6use std::time::{SystemTime, UNIX_EPOCH};
7
8use crate::{
9 did::Did,
10 error::{MaError, MaResult as Result},
11 key::{EncryptionKey, SigningKey, ED25519_PUB_CODEC, EDDSA_SIG_CODEC, X25519_PUB_CODEC},
12 multiformat::{
13 public_key_multibase_decode, signature_multibase_decode, signature_multibase_encode,
14 },
15};
16
17pub const DEFAULT_DID_CONTEXT: &[&str] = &["https://www.w3.org/ns/did/v1.1"];
18pub const DEFAULT_PROOF_TYPE: &str = "MultiformatSignature2023";
19pub const DEFAULT_PROOF_PURPOSE: &str = "assertionMethod";
20
21pub fn now_iso_utc() -> String {
23 #[cfg(target_arch = "wasm32")]
24 {
25 return js_sys::Date::new_0()
26 .to_iso_string()
27 .as_string()
28 .unwrap_or_else(|| "1970-01-01T00:00:00.000Z".to_string());
29 }
30
31 #[cfg(not(target_arch = "wasm32"))]
32 {
33 let duration = SystemTime::now()
34 .duration_since(UNIX_EPOCH)
35 .unwrap_or_default();
36 unix_millis_to_iso(duration.as_secs(), duration.subsec_millis())
37 }
38}
39
40#[cfg(not(target_arch = "wasm32"))]
41fn unix_millis_to_iso(secs: u64, millis: u32) -> String {
42 let z = (secs / 86400) as i64 + 719468;
44 let era = if z >= 0 { z } else { z - 146096 } / 146097;
45 let doe = (z - era * 146097) as u64;
46 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
47 let y = yoe as i64 + era * 400;
48 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
49 let mp = (5 * doy + 2) / 153;
50 let d = doy - (153 * mp + 2) / 5 + 1;
51 let m = if mp < 10 { mp + 3 } else { mp - 9 };
52 let y = if m <= 2 { y + 1 } else { y };
53 let tod = secs % 86400;
54 format!(
55 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
56 y,
57 m,
58 d,
59 tod / 3600,
60 (tod % 3600) / 60,
61 tod % 60,
62 millis,
63 )
64}
65
66#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
67pub struct VerificationMethod {
68 pub id: String,
69 #[serde(rename = "type")]
70 pub key_type: String,
71 pub controller: String,
72 #[serde(rename = "publicKeyMultibase")]
73 pub public_key_multibase: String,
74}
75
76impl VerificationMethod {
77 pub fn new(
78 id: impl AsRef<str>,
79 controller: impl Into<String>,
80 key_type: impl Into<String>,
81 fragment: impl AsRef<str>,
82 public_key_multibase: impl Into<String>,
83 ) -> Result<Self> {
84 let base_id = id
85 .as_ref()
86 .split('#')
87 .next()
88 .ok_or(MaError::MissingIdentifier)?;
89
90 let method = Self {
91 id: format!("{base_id}#{}", fragment.as_ref()),
92 key_type: key_type.into(),
93 controller: controller.into(),
94 public_key_multibase: public_key_multibase.into(),
95 };
96 method.validate()?;
97 Ok(method)
98 }
99
100 pub fn fragment(&self) -> Result<String> {
101 let did = Did::try_from(self.id.as_str())?;
102 did.fragment.ok_or(MaError::MissingFragment)
103 }
104
105 pub fn validate(&self) -> Result<()> {
106 Did::validate_url(&self.id)?;
107
108 if self.key_type.is_empty() {
109 return Err(MaError::VerificationMethodMissingType);
110 }
111
112 if self.controller.is_empty() {
113 return Err(MaError::EmptyController);
114 }
115
116 Did::validate(&self.controller)?;
117
118 if self.public_key_multibase.is_empty() {
119 return Err(MaError::EmptyPublicKeyMultibase);
120 }
121
122 public_key_multibase_decode(&self.public_key_multibase)?;
123 Ok(())
124 }
125}
126
127#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
128pub struct Proof {
129 #[serde(rename = "type")]
130 pub proof_type: String,
131 #[serde(rename = "verificationMethod")]
132 pub verification_method: String,
133 #[serde(rename = "proofPurpose")]
134 pub proof_purpose: String,
135 #[serde(rename = "proofValue")]
136 pub proof_value: String,
137}
138
139impl Proof {
140 pub fn new(proof_value: impl Into<String>, verification_method: impl Into<String>) -> Self {
141 Self {
142 proof_type: DEFAULT_PROOF_TYPE.to_string(),
143 verification_method: verification_method.into(),
144 proof_purpose: DEFAULT_PROOF_PURPOSE.to_string(),
145 proof_value: proof_value.into(),
146 }
147 }
148
149 pub fn is_empty(&self) -> bool {
150 self.proof_value.is_empty()
151 }
152}
153
154fn is_valid_rfc3339_utc(value: &str) -> bool {
155 let trimmed = value.trim();
156 if !trimmed.ends_with('Z') {
158 return false;
159 }
160 let bytes = trimmed.as_bytes();
161 if bytes.len() < 20 {
162 return false;
163 }
164 let expected_punct = [
165 (4usize, b'-'),
166 (7usize, b'-'),
167 (10usize, b'T'),
168 (13usize, b':'),
169 (16usize, b':'),
170 ];
171 if expected_punct
172 .iter()
173 .any(|(idx, punct)| bytes.get(*idx).copied() != Some(*punct))
174 {
175 return false;
176 }
177 let core_digits = [0usize, 1, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18];
178 if core_digits.iter().any(|idx| {
179 !bytes
180 .get(*idx)
181 .copied()
182 .unwrap_or_default()
183 .is_ascii_digit()
184 }) {
185 return false;
186 }
187 let tail = &trimmed[19..trimmed.len() - 1];
188 if tail.is_empty() {
189 return true;
190 }
191 if let Some(frac) = tail.strip_prefix('.') {
192 return !frac.is_empty() && frac.chars().all(|ch| ch.is_ascii_digit());
193 }
194 false
195}
196
197#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
251pub struct Document {
252 #[serde(rename = "@context")]
253 pub context: Vec<String>,
254 pub id: String,
255 pub controller: Vec<String>,
256 #[serde(rename = "verificationMethod")]
257 pub verification_method: Vec<VerificationMethod>,
258 #[serde(rename = "assertionMethod")]
259 pub assertion_method: Vec<String>,
260 #[serde(rename = "keyAgreement")]
261 pub key_agreement: Vec<String>,
262 pub proof: Proof,
263 #[serde(skip_serializing_if = "Option::is_none")]
264 pub identity: Option<String>,
265 #[serde(rename = "createdAt")]
266 pub created_at: String,
267 #[serde(rename = "updatedAt")]
268 pub updated_at: String,
269 #[serde(skip_serializing_if = "Option::is_none")]
270 pub ma: Option<Ipld>,
271}
272
273impl Document {
274 pub fn new(identity: &Did, controller: &Did) -> Self {
275 let now = now_iso_utc();
276 Self {
277 context: DEFAULT_DID_CONTEXT
278 .iter()
279 .map(|value| (*value).to_string())
280 .collect(),
281 id: identity.base_id(),
282 controller: vec![controller.base_id()],
283 verification_method: Vec::new(),
284 assertion_method: Vec::new(),
285 key_agreement: Vec::new(),
286 proof: Proof::default(),
287 identity: None,
288 created_at: now.clone(),
289 updated_at: now,
290 ma: None,
291 }
292 }
293
294 pub fn set_ma(&mut self, ma: Ipld) {
296 match &ma {
297 Ipld::Null => self.ma = None,
298 Ipld::Map(m) if m.is_empty() => self.ma = None,
299 _ => self.ma = Some(ma),
300 }
301 }
302
303 pub fn clear_ma(&mut self) {
305 self.ma = None;
306 }
307
308 pub fn to_cbor(&self) -> Result<Vec<u8>> {
309 serde_ipld_dagcbor::to_vec(self).map_err(|error| MaError::CborEncode(error.to_string()))
310 }
311
312 pub fn from_cbor(bytes: &[u8]) -> Result<Self> {
313 serde_ipld_dagcbor::from_slice(bytes)
314 .map_err(|error| MaError::CborDecode(error.to_string()))
315 }
316
317 pub fn marshal(&self) -> Result<String> {
318 self.to_json()
319 }
320
321 pub fn unmarshal(s: &str) -> Result<Self> {
322 Self::from_json(s)
323 }
324
325 fn to_json(&self) -> Result<String> {
326 let bytes = serde_ipld_dagjson::to_vec(self)
327 .map_err(|error| MaError::JsonEncode(error.to_string()))?;
328 String::from_utf8(bytes).map_err(|error| MaError::JsonEncode(error.to_string()))
329 }
330
331 fn from_json(s: &str) -> Result<Self> {
332 serde_ipld_dagjson::from_slice(s.as_bytes())
333 .map_err(|error| MaError::JsonDecode(error.to_string()))
334 }
335
336 pub fn add_controller(&mut self, controller: impl Into<String>) -> Result<()> {
337 let controller = controller.into();
338 Did::validate(&controller)?;
339 if !self.controller.contains(&controller) {
340 self.controller.push(controller);
341 }
342 Ok(())
343 }
344
345 pub fn add_verification_method(&mut self, method: VerificationMethod) -> Result<()> {
346 method.validate()?;
347 let duplicate = self.verification_method.iter().any(|existing| {
348 existing.id == method.id || existing.public_key_multibase == method.public_key_multibase
349 });
350
351 if !duplicate {
352 self.verification_method.push(method);
353 }
354
355 Ok(())
356 }
357
358 pub fn get_verification_method_by_id(&self, method_id: &str) -> Result<&VerificationMethod> {
359 self.verification_method
360 .iter()
361 .find(|method| method.id == method_id)
362 .ok_or_else(|| MaError::UnknownVerificationMethod(method_id.to_string()))
363 }
364
365 pub fn set_identity(&mut self, identity: impl Into<String>) -> Result<()> {
366 let identity = identity.into();
367 Cid::try_from(identity.as_str()).map_err(|_| MaError::InvalidIdentity)?;
368 self.identity = Some(identity);
369 Ok(())
370 }
371
372 pub fn touch(&mut self) {
374 self.updated_at = now_iso_utc();
375 }
376
377 pub fn assertion_method_public_key(&self) -> Result<VerifyingKey> {
378 let assertion_id = self
379 .assertion_method
380 .first()
381 .ok_or_else(|| MaError::UnknownVerificationMethod("assertionMethod".to_string()))?;
382 let vm = self.get_verification_method_by_id(assertion_id)?;
383 let (codec, public_key_bytes) = public_key_multibase_decode(&vm.public_key_multibase)?;
384 if codec != ED25519_PUB_CODEC {
385 return Err(MaError::InvalidMulticodec {
386 expected: ED25519_PUB_CODEC,
387 actual: codec,
388 });
389 }
390
391 let key_len = public_key_bytes.len();
392 let bytes: [u8; 32] =
393 public_key_bytes
394 .try_into()
395 .map_err(|_| MaError::InvalidKeyLength {
396 expected: 32,
397 actual: key_len,
398 })?;
399
400 VerifyingKey::from_bytes(&bytes).map_err(|_| MaError::Crypto)
401 }
402
403 pub fn key_agreement_public_key_bytes(&self) -> Result<[u8; 32]> {
404 let agreement_id = self
405 .key_agreement
406 .first()
407 .ok_or_else(|| MaError::UnknownVerificationMethod("keyAgreement".to_string()))?;
408 let vm = self.get_verification_method_by_id(agreement_id)?;
409 let (codec, public_key_bytes) = public_key_multibase_decode(&vm.public_key_multibase)?;
410 if codec != X25519_PUB_CODEC {
411 return Err(MaError::InvalidMulticodec {
412 expected: X25519_PUB_CODEC,
413 actual: codec,
414 });
415 }
416
417 let key_len = public_key_bytes.len();
418 public_key_bytes
419 .try_into()
420 .map_err(|_| MaError::InvalidKeyLength {
421 expected: 32,
422 actual: key_len,
423 })
424 }
425
426 pub fn payload_document(&self) -> Self {
427 let mut payload = self.clone();
428 payload.proof = Proof::default();
429 payload
430 }
431
432 pub fn payload_bytes(&self) -> Result<Vec<u8>> {
433 self.payload_document().to_cbor()
434 }
435
436 pub fn payload_hash(&self) -> Result<[u8; 32]> {
437 Ok(blake3::hash(&self.payload_bytes()?).into())
438 }
439
440 pub fn sign(
441 &mut self,
442 signing_key: &SigningKey,
443 verification_method: &VerificationMethod,
444 ) -> Result<()> {
445 if signing_key.public_key_multibase != verification_method.public_key_multibase {
446 return Err(MaError::InvalidPublicKeyMultibase);
447 }
448
449 let signature = signing_key.sign(&self.payload_hash()?);
450 let proof_value = signature_multibase_encode(EDDSA_SIG_CODEC, &signature)?;
451 self.proof = Proof::new(proof_value, verification_method.id.clone());
452 Ok(())
453 }
454
455 pub fn verify(&self) -> Result<()> {
456 if self.proof.is_empty() {
457 return Err(MaError::MissingProof);
458 }
459
460 let (codec, sig_bytes) = signature_multibase_decode(&self.proof.proof_value)?;
461 if codec != EDDSA_SIG_CODEC {
462 return Err(MaError::InvalidDocumentSignature);
463 }
464 let signature =
465 Signature::from_slice(&sig_bytes).map_err(|_| MaError::InvalidDocumentSignature)?;
466 let public_key = self.assertion_method_public_key()?;
467 public_key
468 .verify(&self.payload_hash()?, &signature)
469 .map_err(|_| MaError::InvalidDocumentSignature)
470 }
471
472 pub fn validate(&self) -> Result<()> {
473 if self.context.is_empty() {
474 return Err(MaError::EmptyContext);
475 }
476
477 Did::validate(&self.id)?;
478
479 if self.controller.is_empty() {
480 return Err(MaError::EmptyController);
481 }
482
483 for controller in &self.controller {
484 Did::validate(controller)?;
485 }
486
487 if let Some(identity) = &self.identity {
488 Cid::try_from(identity.as_str()).map_err(|_| MaError::InvalidIdentity)?;
489 }
490
491 if !is_valid_rfc3339_utc(&self.created_at) {
492 return Err(MaError::InvalidCreatedAt(self.created_at.clone()));
493 }
494
495 if !is_valid_rfc3339_utc(&self.updated_at) {
496 return Err(MaError::InvalidUpdatedAt(self.updated_at.clone()));
497 }
498
499 for method in &self.verification_method {
500 method.validate()?;
501 }
502
503 if self.assertion_method.is_empty() {
504 return Err(MaError::UnknownVerificationMethod(
505 "assertionMethod".to_string(),
506 ));
507 }
508
509 if self.key_agreement.is_empty() {
510 return Err(MaError::UnknownVerificationMethod(
511 "keyAgreement".to_string(),
512 ));
513 }
514
515 Ok(())
516 }
517}
518
519impl TryFrom<&EncryptionKey> for VerificationMethod {
520 type Error = MaError;
521
522 fn try_from(value: &EncryptionKey) -> Result<Self> {
523 let fragment = value.did.fragment.clone().ok_or(MaError::MissingFragment)?;
524 VerificationMethod::new(
525 value.did.base_id(),
526 value.did.base_id(),
527 value.key_type.clone(),
528 fragment,
529 value.public_key_multibase.clone(),
530 )
531 }
532}
533
534impl TryFrom<&SigningKey> for VerificationMethod {
535 type Error = MaError;
536
537 fn try_from(value: &SigningKey) -> Result<Self> {
538 let fragment = value.did.fragment.clone().ok_or(MaError::MissingFragment)?;
539 VerificationMethod::new(
540 value.did.base_id(),
541 value.did.base_id(),
542 value.key_type.clone(),
543 fragment,
544 value.public_key_multibase.clone(),
545 )
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552 use std::collections::BTreeMap;
553
554 #[test]
555 fn set_ma_stores_opaque_value() {
556 let root = Did::new_url(
557 "k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr",
558 None::<String>,
559 )
560 .expect("valid test did");
561 let mut document = Document::new(&root, &root);
562
563 let ma = Ipld::Map(BTreeMap::from([(
564 "type".into(),
565 Ipld::String("agent".into()),
566 )]));
567 document.set_ma(ma.clone());
568 assert_eq!(document.ma.as_ref(), Some(&ma));
569 }
570
571 #[test]
572 fn clear_ma_removes_value() {
573 let root = Did::new_url(
574 "k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr",
575 None::<String>,
576 )
577 .expect("valid test did");
578 let mut document = Document::new(&root, &root);
579
580 document.set_ma(Ipld::Map(BTreeMap::from([(
581 "type".into(),
582 Ipld::String("agent".into()),
583 )])));
584 assert!(document.ma.is_some());
585 document.clear_ma();
586 assert!(document.ma.is_none());
587 }
588
589 #[test]
590 fn set_ma_null_clears() {
591 let root = Did::new_url(
592 "k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr",
593 None::<String>,
594 )
595 .expect("valid test did");
596 let mut document = Document::new(&root, &root);
597
598 document.set_ma(Ipld::Map(BTreeMap::from([(
599 "type".into(),
600 Ipld::String("agent".into()),
601 )])));
602 document.set_ma(Ipld::Null);
603 assert!(document.ma.is_none());
604 }
605
606 #[test]
607 fn validate_accepts_opaque_ma() {
608 let identity = crate::identity::generate_identity(
609 "k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr",
610 )
611 .expect("generate identity");
612 let mut document = identity.document;
613 document.set_ma(Ipld::Map(BTreeMap::from([
614 ("type".into(), Ipld::String("bahner".into())),
615 ("custom".into(), Ipld::Integer(42)),
616 ])));
617 document
618 .validate()
619 .expect("validate should accept any ma value");
620 }
621}