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