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 web_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()
27 .to_iso_string()
28 .as_string()
29 .unwrap_or_else(|| "1970-01-01T00:00:00.000Z".to_string());
30 }
31
32 #[cfg(not(target_arch = "wasm32"))]
33 {
34 let duration = SystemTime::now()
35 .duration_since(UNIX_EPOCH)
36 .unwrap_or_default();
37 unix_millis_to_iso(duration.as_secs(), duration.subsec_millis())
38 }
39}
40
41#[cfg(not(target_arch = "wasm32"))]
42fn unix_millis_to_iso(secs: u64, millis: u32) -> String {
43 let days = i64::try_from(secs / 86_400).unwrap_or(i64::MAX);
45 let z = days + 719_468;
46 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
47 let doe = u64::try_from(z - era * 146_097).unwrap_or_default();
48 let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
49 let y = i64::try_from(yoe).unwrap_or(i64::MAX) + era * 400;
50 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
51 let mp = (5 * doy + 2) / 153;
52 let d = doy - (153 * mp + 2) / 5 + 1;
53 let m = if mp < 10 { mp + 3 } else { mp - 9 };
54 let y = if m <= 2 { y + 1 } else { y };
55 let tod = secs % 86400;
56 format!(
57 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
58 y,
59 m,
60 d,
61 tod / 3600,
62 (tod % 3600) / 60,
63 tod % 60,
64 millis,
65 )
66}
67
68#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
69pub struct VerificationMethod {
70 pub id: String,
71 #[serde(rename = "type")]
72 pub key_type: String,
73 pub controller: String,
74 #[serde(rename = "publicKeyMultibase")]
75 pub public_key_multibase: String,
76}
77
78impl VerificationMethod {
79 pub fn new(
80 id: impl AsRef<str>,
81 controller: impl Into<String>,
82 key_type: impl Into<String>,
83 fragment: impl AsRef<str>,
84 public_key_multibase: impl Into<String>,
85 ) -> Result<Self> {
86 let base_id = id
87 .as_ref()
88 .split('#')
89 .next()
90 .ok_or(MaError::MissingIdentifier)?;
91
92 let method = Self {
93 id: format!("{base_id}#{}", fragment.as_ref()),
94 key_type: key_type.into(),
95 controller: controller.into(),
96 public_key_multibase: public_key_multibase.into(),
97 };
98 method.validate()?;
99 Ok(method)
100 }
101
102 pub fn fragment(&self) -> Result<String> {
103 let did = Did::try_from(self.id.as_str())?;
104 did.fragment.ok_or(MaError::MissingFragment)
105 }
106
107 pub fn validate(&self) -> Result<()> {
108 Did::validate_url(&self.id)?;
109
110 if self.key_type.is_empty() {
111 return Err(MaError::VerificationMethodMissingType);
112 }
113
114 if self.controller.is_empty() {
115 return Err(MaError::EmptyController);
116 }
117
118 Did::validate(&self.controller)?;
119
120 if self.public_key_multibase.is_empty() {
121 return Err(MaError::EmptyPublicKeyMultibase);
122 }
123
124 public_key_multibase_decode(&self.public_key_multibase)?;
125 Ok(())
126 }
127}
128
129#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
130pub struct Proof {
131 #[serde(rename = "type")]
132 pub proof_type: String,
133 #[serde(rename = "verificationMethod")]
134 pub verification_method: String,
135 #[serde(rename = "proofPurpose")]
136 pub proof_purpose: String,
137 #[serde(rename = "proofValue")]
138 pub proof_value: String,
139}
140
141impl Proof {
142 pub fn new(proof_value: impl Into<String>, verification_method: impl Into<String>) -> Self {
143 Self {
144 proof_type: DEFAULT_PROOF_TYPE.to_string(),
145 verification_method: verification_method.into(),
146 proof_purpose: DEFAULT_PROOF_PURPOSE.to_string(),
147 proof_value: proof_value.into(),
148 }
149 }
150
151 pub fn is_empty(&self) -> bool {
152 self.proof_value.is_empty()
153 }
154}
155
156#[derive(Debug, Default, Clone)]
179pub struct MaExtension {
180 map: std::collections::BTreeMap<String, Ipld>,
181}
182
183impl MaExtension {
184 pub fn new() -> Self {
186 Self::default()
187 }
188
189 #[must_use]
193 pub fn kind(mut self, kind: &str) -> Self {
194 self.map
195 .insert("type".to_string(), Ipld::String(kind.to_string()));
196 self
197 }
198
199 #[must_use]
203 pub fn add_service(mut self, service: &str) -> Self {
204 let entry = self
205 .map
206 .entry("services".to_string())
207 .or_insert_with(|| Ipld::List(Vec::new()));
208 if let Ipld::List(list) = entry {
209 list.push(Ipld::String(service.to_string()));
210 }
211 self
212 }
213
214 #[must_use]
219 pub fn services(mut self, services: Vec<String>) -> Self {
220 self.map.insert(
221 "services".to_string(),
222 Ipld::List(services.into_iter().map(Ipld::String).collect()),
223 );
224 self
225 }
226
227 #[must_use]
229 pub fn extra(mut self, key: &str, val: Ipld) -> Self {
230 self.map.insert(key.to_string(), val);
231 self
232 }
233
234 pub fn build(self) -> Ipld {
239 if self.map.is_empty() {
240 Ipld::Null
241 } else {
242 Ipld::Map(self.map)
243 }
244 }
245}
246
247fn is_valid_rfc3339_utc(value: &str) -> bool {
248 let trimmed = value.trim();
249 if !trimmed.ends_with('Z') {
251 return false;
252 }
253 let bytes = trimmed.as_bytes();
254 if bytes.len() < 20 {
255 return false;
256 }
257 let expected_punct = [
258 (4usize, b'-'),
259 (7usize, b'-'),
260 (10usize, b'T'),
261 (13usize, b':'),
262 (16usize, b':'),
263 ];
264 if expected_punct
265 .iter()
266 .any(|(idx, punct)| bytes.get(*idx).copied() != Some(*punct))
267 {
268 return false;
269 }
270 let core_digits = [0usize, 1, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18];
271 if core_digits.iter().any(|idx| {
272 !bytes
273 .get(*idx)
274 .copied()
275 .unwrap_or_default()
276 .is_ascii_digit()
277 }) {
278 return false;
279 }
280 let tail = &trimmed[19..trimmed.len() - 1];
281 if tail.is_empty() {
282 return true;
283 }
284 if let Some(frac) = tail.strip_prefix('.') {
285 return !frac.is_empty() && frac.chars().all(|ch| ch.is_ascii_digit());
286 }
287 false
288}
289
290#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
339pub struct Document {
340 #[serde(rename = "@context")]
341 pub context: Vec<String>,
342 pub id: String,
343 pub controller: Vec<String>,
344 #[serde(rename = "verificationMethod")]
345 pub verification_method: Vec<VerificationMethod>,
346 #[serde(rename = "assertionMethod")]
347 pub assertion_method: Vec<String>,
348 #[serde(rename = "keyAgreement")]
349 pub key_agreement: Vec<String>,
350 pub proof: Proof,
351 #[serde(skip_serializing_if = "Option::is_none")]
352 pub identity: Option<String>,
353 #[serde(rename = "createdAt")]
354 pub created_at: String,
355 #[serde(rename = "updatedAt")]
356 pub updated_at: String,
357 #[serde(skip_serializing_if = "Option::is_none")]
358 pub ma: Option<Ipld>,
359}
360
361impl Document {
362 pub fn new(identity: &Did, controller: &Did) -> Self {
363 let now = now_iso_utc();
364 Self {
365 context: DEFAULT_DID_CONTEXT
366 .iter()
367 .map(|value| (*value).to_string())
368 .collect(),
369 id: identity.base_id(),
370 controller: vec![controller.base_id()],
371 verification_method: Vec::new(),
372 assertion_method: Vec::new(),
373 key_agreement: Vec::new(),
374 proof: Proof::default(),
375 identity: None,
376 created_at: now.clone(),
377 updated_at: now,
378 ma: None,
379 }
380 }
381
382 pub fn set_ma(&mut self, ma: Ipld) {
387 match &ma {
388 Ipld::Null => self.ma = None,
389 Ipld::Map(m) if m.is_empty() => self.ma = None,
390 _ => self.ma = Some(ma),
391 }
392 }
393
394 pub fn set_ma_extension(&mut self, ext: MaExtension) {
409 self.set_ma(ext.build());
410 }
411
412 pub fn clear_ma(&mut self) {
414 self.ma = None;
415 }
416
417 pub fn encode(&self) -> Result<Vec<u8>> {
422 serde_ipld_dagcbor::to_vec(self).map_err(|error| MaError::CborEncode(error.to_string()))
423 }
424
425 pub fn decode(bytes: &[u8]) -> Result<Self> {
429 serde_ipld_dagcbor::from_slice(bytes)
430 .map_err(|error| MaError::CborDecode(error.to_string()))
431 }
432
433 pub fn add_controller(&mut self, controller: impl Into<String>) -> Result<()> {
434 let controller = controller.into();
435 Did::validate(&controller)?;
436 if !self.controller.contains(&controller) {
437 self.controller.push(controller);
438 }
439 Ok(())
440 }
441
442 pub fn add_verification_method(&mut self, method: VerificationMethod) -> Result<()> {
443 method.validate()?;
444 let duplicate = self.verification_method.iter().any(|existing| {
445 existing.id == method.id || existing.public_key_multibase == method.public_key_multibase
446 });
447
448 if !duplicate {
449 self.verification_method.push(method);
450 }
451
452 Ok(())
453 }
454
455 pub fn get_verification_method_by_id(&self, method_id: &str) -> Result<&VerificationMethod> {
456 self.verification_method
457 .iter()
458 .find(|method| method.id == method_id)
459 .ok_or_else(|| MaError::UnknownVerificationMethod(method_id.to_string()))
460 }
461
462 pub fn set_identity(&mut self, identity: impl Into<String>) -> Result<()> {
463 let identity = identity.into();
464 Cid::try_from(identity.as_str()).map_err(|_| MaError::InvalidIdentity)?;
465 self.identity = Some(identity);
466 Ok(())
467 }
468
469 pub fn touch(&mut self) {
471 self.updated_at = now_iso_utc();
472 }
473
474 pub fn assertion_method_public_key(&self) -> Result<VerifyingKey> {
475 let assertion_id = self
476 .assertion_method
477 .first()
478 .ok_or_else(|| MaError::UnknownVerificationMethod("assertionMethod".to_string()))?;
479 let vm = self.get_verification_method_by_id(assertion_id)?;
480 let (codec, public_key_bytes) = public_key_multibase_decode(&vm.public_key_multibase)?;
481 if codec != ED25519_PUB_CODEC {
482 return Err(MaError::InvalidMulticodec {
483 expected: ED25519_PUB_CODEC,
484 actual: codec,
485 });
486 }
487
488 let key_len = public_key_bytes.len();
489 let bytes: [u8; 32] =
490 public_key_bytes
491 .try_into()
492 .map_err(|_| MaError::InvalidKeyLength {
493 expected: 32,
494 actual: key_len,
495 })?;
496
497 VerifyingKey::from_bytes(&bytes).map_err(|_| MaError::Crypto)
498 }
499
500 pub fn key_agreement_public_key_bytes(&self) -> Result<[u8; 32]> {
501 let agreement_id = self
502 .key_agreement
503 .first()
504 .ok_or_else(|| MaError::UnknownVerificationMethod("keyAgreement".to_string()))?;
505 let vm = self.get_verification_method_by_id(agreement_id)?;
506 let (codec, public_key_bytes) = public_key_multibase_decode(&vm.public_key_multibase)?;
507 if codec != X25519_PUB_CODEC {
508 return Err(MaError::InvalidMulticodec {
509 expected: X25519_PUB_CODEC,
510 actual: codec,
511 });
512 }
513
514 let key_len = public_key_bytes.len();
515 public_key_bytes
516 .try_into()
517 .map_err(|_| MaError::InvalidKeyLength {
518 expected: 32,
519 actual: key_len,
520 })
521 }
522
523 #[must_use]
524 pub fn payload_document(&self) -> Self {
525 let mut payload = self.clone();
526 payload.proof = Proof::default();
527 payload
528 }
529
530 pub fn payload_bytes(&self) -> Result<Vec<u8>> {
531 self.payload_document().encode()
532 }
533
534 pub fn payload_hash(&self) -> Result<[u8; 32]> {
535 Ok(blake3::hash(&self.payload_bytes()?).into())
536 }
537
538 pub fn sign(
539 &mut self,
540 signing_key: &SigningKey,
541 verification_method: &VerificationMethod,
542 ) -> Result<()> {
543 if signing_key.public_key_multibase != verification_method.public_key_multibase {
544 return Err(MaError::InvalidPublicKeyMultibase);
545 }
546
547 let signature = signing_key.sign(&self.payload_hash()?);
548 let proof_value = signature_multibase_encode(EDDSA_SIG_CODEC, &signature);
549 self.proof = Proof::new(proof_value, verification_method.id.clone());
550 Ok(())
551 }
552
553 pub fn verify(&self) -> Result<()> {
554 if self.proof.is_empty() {
555 return Err(MaError::MissingProof);
556 }
557
558 let (codec, sig_bytes) = signature_multibase_decode(&self.proof.proof_value)?;
559 if codec != EDDSA_SIG_CODEC {
560 return Err(MaError::InvalidDocumentSignature);
561 }
562 let signature =
563 Signature::from_slice(&sig_bytes).map_err(|_| MaError::InvalidDocumentSignature)?;
564 let public_key = self.assertion_method_public_key()?;
565 public_key
566 .verify(&self.payload_hash()?, &signature)
567 .map_err(|_| MaError::InvalidDocumentSignature)
568 }
569
570 pub fn validate(&self) -> Result<()> {
571 if self.context.is_empty() {
572 return Err(MaError::EmptyContext);
573 }
574
575 Did::validate(&self.id)?;
576
577 if self.controller.is_empty() {
578 return Err(MaError::EmptyController);
579 }
580
581 for controller in &self.controller {
582 Did::validate(controller)?;
583 }
584
585 if let Some(identity) = &self.identity {
586 Cid::try_from(identity.as_str()).map_err(|_| MaError::InvalidIdentity)?;
587 }
588
589 if !is_valid_rfc3339_utc(&self.created_at) {
590 return Err(MaError::InvalidCreatedAt(self.created_at.clone()));
591 }
592
593 if !is_valid_rfc3339_utc(&self.updated_at) {
594 return Err(MaError::InvalidUpdatedAt(self.updated_at.clone()));
595 }
596
597 for method in &self.verification_method {
598 method.validate()?;
599 }
600
601 if self.assertion_method.is_empty() {
602 return Err(MaError::UnknownVerificationMethod(
603 "assertionMethod".to_string(),
604 ));
605 }
606
607 if self.key_agreement.is_empty() {
608 return Err(MaError::UnknownVerificationMethod(
609 "keyAgreement".to_string(),
610 ));
611 }
612
613 Ok(())
614 }
615}
616
617impl TryFrom<&[u8]> for Document {
618 type Error = MaError;
619
620 fn try_from(bytes: &[u8]) -> Result<Self> {
621 Self::decode(bytes)
622 }
623}
624
625impl TryFrom<&EncryptionKey> for VerificationMethod {
626 type Error = MaError;
627
628 fn try_from(value: &EncryptionKey) -> Result<Self> {
629 let fragment = value.did.fragment.clone().ok_or(MaError::MissingFragment)?;
630 VerificationMethod::new(
631 value.did.base_id(),
632 value.did.base_id(),
633 value.key_type.clone(),
634 fragment,
635 value.public_key_multibase.clone(),
636 )
637 }
638}
639
640impl TryFrom<&SigningKey> for VerificationMethod {
641 type Error = MaError;
642
643 fn try_from(value: &SigningKey) -> Result<Self> {
644 let fragment = value.did.fragment.clone().ok_or(MaError::MissingFragment)?;
645 VerificationMethod::new(
646 value.did.base_id(),
647 value.did.base_id(),
648 value.key_type.clone(),
649 fragment,
650 value.public_key_multibase.clone(),
651 )
652 }
653}
654
655#[cfg(test)]
656mod tests {
657 use super::*;
658 use std::collections::BTreeMap;
659
660 #[test]
661 fn encode_decode_round_trip() {
662 let identity = crate::generate_identity_from_secret([11u8; 32]).expect("identity");
663 let bytes = identity.document.encode().expect("encode");
664 let decoded = Document::decode(&bytes).expect("decode");
665 assert_eq!(decoded, identity.document);
666 }
667
668 #[test]
669 fn try_from_bytes_round_trip() {
670 let identity = crate::generate_identity_from_secret([12u8; 32]).expect("identity");
671 let bytes = identity.document.encode().expect("encode");
672 let decoded = Document::try_from(bytes.as_slice()).expect("try_from bytes");
673 assert_eq!(decoded, identity.document);
674 }
675
676 #[test]
677 fn decode_rejects_invalid_bytes() {
678 let err = Document::decode(b"not dag-cbor").expect_err("invalid bytes");
679 assert!(matches!(err, MaError::CborDecode(_)));
680 }
681
682 #[test]
683 fn payload_document_clears_proof_only() {
684 let identity = crate::generate_identity_from_secret([13u8; 32]).expect("identity");
685 let payload = identity.document.payload_document();
686
687 assert!(payload.proof.is_empty());
688 assert_eq!(payload.id, identity.document.id);
689 assert_eq!(payload.controller, identity.document.controller);
690 assert_eq!(
691 payload.verification_method,
692 identity.document.verification_method
693 );
694 assert_eq!(payload.assertion_method, identity.document.assertion_method);
695 assert_eq!(payload.key_agreement, identity.document.key_agreement);
696 assert_eq!(payload.identity, identity.document.identity);
697 assert_eq!(payload.created_at, identity.document.created_at);
698 assert_eq!(payload.updated_at, identity.document.updated_at);
699 assert_eq!(payload.ma, identity.document.ma);
700 }
701
702 #[test]
703 fn set_ma_stores_opaque_value() {
704 let root = Did::new_url(
705 "k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr",
706 None::<String>,
707 )
708 .expect("valid test did");
709 let mut document = Document::new(&root, &root);
710
711 let ma = Ipld::Map(BTreeMap::from([(
712 "type".into(),
713 Ipld::String("agent".into()),
714 )]));
715 document.set_ma(ma.clone());
716 assert_eq!(document.ma.as_ref(), Some(&ma));
717 }
718
719 #[test]
720 fn clear_ma_removes_value() {
721 let root = Did::new_url(
722 "k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr",
723 None::<String>,
724 )
725 .expect("valid test did");
726 let mut document = Document::new(&root, &root);
727
728 document.set_ma(Ipld::Map(BTreeMap::from([(
729 "type".into(),
730 Ipld::String("agent".into()),
731 )])));
732 assert!(document.ma.is_some());
733 document.clear_ma();
734 assert!(document.ma.is_none());
735 }
736
737 #[test]
738 fn set_ma_null_clears() {
739 let root = Did::new_url(
740 "k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr",
741 None::<String>,
742 )
743 .expect("valid test did");
744 let mut document = Document::new(&root, &root);
745
746 document.set_ma(Ipld::Map(BTreeMap::from([(
747 "type".into(),
748 Ipld::String("agent".into()),
749 )])));
750 document.set_ma(Ipld::Null);
751 assert!(document.ma.is_none());
752 }
753
754 #[test]
755 fn validate_accepts_opaque_ma() {
756 let identity = crate::identity::generate_identity(
757 "k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr",
758 )
759 .expect("generate identity");
760 let mut document = identity.document;
761 document.set_ma(Ipld::Map(BTreeMap::from([
762 ("type".into(), Ipld::String("bahner".into())),
763 ("custom".into(), Ipld::Integer(42)),
764 ])));
765 document
766 .validate()
767 .expect("validate should accept any ma value");
768 }
769}