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
156fn is_valid_rfc3339_utc(value: &str) -> bool {
157 let trimmed = value.trim();
158 if !trimmed.ends_with('Z') {
160 return false;
161 }
162 let bytes = trimmed.as_bytes();
163 if bytes.len() < 20 {
164 return false;
165 }
166 let expected_punct = [
167 (4usize, b'-'),
168 (7usize, b'-'),
169 (10usize, b'T'),
170 (13usize, b':'),
171 (16usize, b':'),
172 ];
173 if expected_punct
174 .iter()
175 .any(|(idx, punct)| bytes.get(*idx).copied() != Some(*punct))
176 {
177 return false;
178 }
179 let core_digits = [0usize, 1, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18];
180 if core_digits.iter().any(|idx| {
181 !bytes
182 .get(*idx)
183 .copied()
184 .unwrap_or_default()
185 .is_ascii_digit()
186 }) {
187 return false;
188 }
189 let tail = &trimmed[19..trimmed.len() - 1];
190 if tail.is_empty() {
191 return true;
192 }
193 if let Some(frac) = tail.strip_prefix('.') {
194 return !frac.is_empty() && frac.chars().all(|ch| ch.is_ascii_digit());
195 }
196 false
197}
198
199#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
248pub struct Document {
249 #[serde(rename = "@context")]
250 pub context: Vec<String>,
251 pub id: String,
252 pub controller: Vec<String>,
253 #[serde(rename = "verificationMethod")]
254 pub verification_method: Vec<VerificationMethod>,
255 #[serde(rename = "assertionMethod")]
256 pub assertion_method: Vec<String>,
257 #[serde(rename = "keyAgreement")]
258 pub key_agreement: Vec<String>,
259 pub proof: Proof,
260 #[serde(skip_serializing_if = "Option::is_none")]
261 pub identity: Option<String>,
262 #[serde(rename = "createdAt")]
263 pub created_at: String,
264 #[serde(rename = "updatedAt")]
265 pub updated_at: String,
266 #[serde(skip_serializing_if = "Option::is_none")]
267 pub ma: Option<Ipld>,
268}
269
270impl Document {
271 pub fn new(identity: &Did, controller: &Did) -> Self {
272 let now = now_iso_utc();
273 Self {
274 context: DEFAULT_DID_CONTEXT
275 .iter()
276 .map(|value| (*value).to_string())
277 .collect(),
278 id: identity.base_id(),
279 controller: vec![controller.base_id()],
280 verification_method: Vec::new(),
281 assertion_method: Vec::new(),
282 key_agreement: Vec::new(),
283 proof: Proof::default(),
284 identity: None,
285 created_at: now.clone(),
286 updated_at: now,
287 ma: None,
288 }
289 }
290
291 pub fn set_ma(&mut self, ma: Ipld) {
293 match &ma {
294 Ipld::Null => self.ma = None,
295 Ipld::Map(m) if m.is_empty() => self.ma = None,
296 _ => self.ma = Some(ma),
297 }
298 }
299
300 pub fn clear_ma(&mut self) {
302 self.ma = None;
303 }
304
305 pub fn encode(&self) -> Result<Vec<u8>> {
310 serde_ipld_dagcbor::to_vec(self).map_err(|error| MaError::CborEncode(error.to_string()))
311 }
312
313 pub fn decode(bytes: &[u8]) -> Result<Self> {
317 serde_ipld_dagcbor::from_slice(bytes)
318 .map_err(|error| MaError::CborDecode(error.to_string()))
319 }
320
321 pub fn add_controller(&mut self, controller: impl Into<String>) -> Result<()> {
322 let controller = controller.into();
323 Did::validate(&controller)?;
324 if !self.controller.contains(&controller) {
325 self.controller.push(controller);
326 }
327 Ok(())
328 }
329
330 pub fn add_verification_method(&mut self, method: VerificationMethod) -> Result<()> {
331 method.validate()?;
332 let duplicate = self.verification_method.iter().any(|existing| {
333 existing.id == method.id || existing.public_key_multibase == method.public_key_multibase
334 });
335
336 if !duplicate {
337 self.verification_method.push(method);
338 }
339
340 Ok(())
341 }
342
343 pub fn get_verification_method_by_id(&self, method_id: &str) -> Result<&VerificationMethod> {
344 self.verification_method
345 .iter()
346 .find(|method| method.id == method_id)
347 .ok_or_else(|| MaError::UnknownVerificationMethod(method_id.to_string()))
348 }
349
350 pub fn set_identity(&mut self, identity: impl Into<String>) -> Result<()> {
351 let identity = identity.into();
352 Cid::try_from(identity.as_str()).map_err(|_| MaError::InvalidIdentity)?;
353 self.identity = Some(identity);
354 Ok(())
355 }
356
357 pub fn touch(&mut self) {
359 self.updated_at = now_iso_utc();
360 }
361
362 pub fn assertion_method_public_key(&self) -> Result<VerifyingKey> {
363 let assertion_id = self
364 .assertion_method
365 .first()
366 .ok_or_else(|| MaError::UnknownVerificationMethod("assertionMethod".to_string()))?;
367 let vm = self.get_verification_method_by_id(assertion_id)?;
368 let (codec, public_key_bytes) = public_key_multibase_decode(&vm.public_key_multibase)?;
369 if codec != ED25519_PUB_CODEC {
370 return Err(MaError::InvalidMulticodec {
371 expected: ED25519_PUB_CODEC,
372 actual: codec,
373 });
374 }
375
376 let key_len = public_key_bytes.len();
377 let bytes: [u8; 32] =
378 public_key_bytes
379 .try_into()
380 .map_err(|_| MaError::InvalidKeyLength {
381 expected: 32,
382 actual: key_len,
383 })?;
384
385 VerifyingKey::from_bytes(&bytes).map_err(|_| MaError::Crypto)
386 }
387
388 pub fn key_agreement_public_key_bytes(&self) -> Result<[u8; 32]> {
389 let agreement_id = self
390 .key_agreement
391 .first()
392 .ok_or_else(|| MaError::UnknownVerificationMethod("keyAgreement".to_string()))?;
393 let vm = self.get_verification_method_by_id(agreement_id)?;
394 let (codec, public_key_bytes) = public_key_multibase_decode(&vm.public_key_multibase)?;
395 if codec != X25519_PUB_CODEC {
396 return Err(MaError::InvalidMulticodec {
397 expected: X25519_PUB_CODEC,
398 actual: codec,
399 });
400 }
401
402 let key_len = public_key_bytes.len();
403 public_key_bytes
404 .try_into()
405 .map_err(|_| MaError::InvalidKeyLength {
406 expected: 32,
407 actual: key_len,
408 })
409 }
410
411 #[must_use]
412 pub fn payload_document(&self) -> Self {
413 let mut payload = self.clone();
414 payload.proof = Proof::default();
415 payload
416 }
417
418 pub fn payload_bytes(&self) -> Result<Vec<u8>> {
419 self.payload_document().encode()
420 }
421
422 pub fn payload_hash(&self) -> Result<[u8; 32]> {
423 Ok(blake3::hash(&self.payload_bytes()?).into())
424 }
425
426 pub fn sign(
427 &mut self,
428 signing_key: &SigningKey,
429 verification_method: &VerificationMethod,
430 ) -> Result<()> {
431 if signing_key.public_key_multibase != verification_method.public_key_multibase {
432 return Err(MaError::InvalidPublicKeyMultibase);
433 }
434
435 let signature = signing_key.sign(&self.payload_hash()?);
436 let proof_value = signature_multibase_encode(EDDSA_SIG_CODEC, &signature);
437 self.proof = Proof::new(proof_value, verification_method.id.clone());
438 Ok(())
439 }
440
441 pub fn verify(&self) -> Result<()> {
442 if self.proof.is_empty() {
443 return Err(MaError::MissingProof);
444 }
445
446 let (codec, sig_bytes) = signature_multibase_decode(&self.proof.proof_value)?;
447 if codec != EDDSA_SIG_CODEC {
448 return Err(MaError::InvalidDocumentSignature);
449 }
450 let signature =
451 Signature::from_slice(&sig_bytes).map_err(|_| MaError::InvalidDocumentSignature)?;
452 let public_key = self.assertion_method_public_key()?;
453 public_key
454 .verify(&self.payload_hash()?, &signature)
455 .map_err(|_| MaError::InvalidDocumentSignature)
456 }
457
458 pub fn validate(&self) -> Result<()> {
459 if self.context.is_empty() {
460 return Err(MaError::EmptyContext);
461 }
462
463 Did::validate(&self.id)?;
464
465 if self.controller.is_empty() {
466 return Err(MaError::EmptyController);
467 }
468
469 for controller in &self.controller {
470 Did::validate(controller)?;
471 }
472
473 if let Some(identity) = &self.identity {
474 Cid::try_from(identity.as_str()).map_err(|_| MaError::InvalidIdentity)?;
475 }
476
477 if !is_valid_rfc3339_utc(&self.created_at) {
478 return Err(MaError::InvalidCreatedAt(self.created_at.clone()));
479 }
480
481 if !is_valid_rfc3339_utc(&self.updated_at) {
482 return Err(MaError::InvalidUpdatedAt(self.updated_at.clone()));
483 }
484
485 for method in &self.verification_method {
486 method.validate()?;
487 }
488
489 if self.assertion_method.is_empty() {
490 return Err(MaError::UnknownVerificationMethod(
491 "assertionMethod".to_string(),
492 ));
493 }
494
495 if self.key_agreement.is_empty() {
496 return Err(MaError::UnknownVerificationMethod(
497 "keyAgreement".to_string(),
498 ));
499 }
500
501 Ok(())
502 }
503}
504
505impl TryFrom<&[u8]> for Document {
506 type Error = MaError;
507
508 fn try_from(bytes: &[u8]) -> Result<Self> {
509 Self::decode(bytes)
510 }
511}
512
513impl TryFrom<&EncryptionKey> for VerificationMethod {
514 type Error = MaError;
515
516 fn try_from(value: &EncryptionKey) -> Result<Self> {
517 let fragment = value.did.fragment.clone().ok_or(MaError::MissingFragment)?;
518 VerificationMethod::new(
519 value.did.base_id(),
520 value.did.base_id(),
521 value.key_type.clone(),
522 fragment,
523 value.public_key_multibase.clone(),
524 )
525 }
526}
527
528impl TryFrom<&SigningKey> for VerificationMethod {
529 type Error = MaError;
530
531 fn try_from(value: &SigningKey) -> Result<Self> {
532 let fragment = value.did.fragment.clone().ok_or(MaError::MissingFragment)?;
533 VerificationMethod::new(
534 value.did.base_id(),
535 value.did.base_id(),
536 value.key_type.clone(),
537 fragment,
538 value.public_key_multibase.clone(),
539 )
540 }
541}
542
543#[cfg(test)]
544mod tests {
545 use super::*;
546 use std::collections::BTreeMap;
547
548 #[test]
549 fn encode_decode_round_trip() {
550 let identity = crate::generate_identity_from_secret([11u8; 32]).expect("identity");
551 let bytes = identity.document.encode().expect("encode");
552 let decoded = Document::decode(&bytes).expect("decode");
553 assert_eq!(decoded, identity.document);
554 }
555
556 #[test]
557 fn try_from_bytes_round_trip() {
558 let identity = crate::generate_identity_from_secret([12u8; 32]).expect("identity");
559 let bytes = identity.document.encode().expect("encode");
560 let decoded = Document::try_from(bytes.as_slice()).expect("try_from bytes");
561 assert_eq!(decoded, identity.document);
562 }
563
564 #[test]
565 fn decode_rejects_invalid_bytes() {
566 let err = Document::decode(b"not dag-cbor").expect_err("invalid bytes");
567 assert!(matches!(err, MaError::CborDecode(_)));
568 }
569
570 #[test]
571 fn payload_document_clears_proof_only() {
572 let identity = crate::generate_identity_from_secret([13u8; 32]).expect("identity");
573 let payload = identity.document.payload_document();
574
575 assert!(payload.proof.is_empty());
576 assert_eq!(payload.id, identity.document.id);
577 assert_eq!(payload.controller, identity.document.controller);
578 assert_eq!(
579 payload.verification_method,
580 identity.document.verification_method
581 );
582 assert_eq!(payload.assertion_method, identity.document.assertion_method);
583 assert_eq!(payload.key_agreement, identity.document.key_agreement);
584 assert_eq!(payload.identity, identity.document.identity);
585 assert_eq!(payload.created_at, identity.document.created_at);
586 assert_eq!(payload.updated_at, identity.document.updated_at);
587 assert_eq!(payload.ma, identity.document.ma);
588 }
589
590 #[test]
591 fn set_ma_stores_opaque_value() {
592 let root = Did::new_url(
593 "k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr",
594 None::<String>,
595 )
596 .expect("valid test did");
597 let mut document = Document::new(&root, &root);
598
599 let ma = Ipld::Map(BTreeMap::from([(
600 "type".into(),
601 Ipld::String("agent".into()),
602 )]));
603 document.set_ma(ma.clone());
604 assert_eq!(document.ma.as_ref(), Some(&ma));
605 }
606
607 #[test]
608 fn clear_ma_removes_value() {
609 let root = Did::new_url(
610 "k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr",
611 None::<String>,
612 )
613 .expect("valid test did");
614 let mut document = Document::new(&root, &root);
615
616 document.set_ma(Ipld::Map(BTreeMap::from([(
617 "type".into(),
618 Ipld::String("agent".into()),
619 )])));
620 assert!(document.ma.is_some());
621 document.clear_ma();
622 assert!(document.ma.is_none());
623 }
624
625 #[test]
626 fn set_ma_null_clears() {
627 let root = Did::new_url(
628 "k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr",
629 None::<String>,
630 )
631 .expect("valid test did");
632 let mut document = Document::new(&root, &root);
633
634 document.set_ma(Ipld::Map(BTreeMap::from([(
635 "type".into(),
636 Ipld::String("agent".into()),
637 )])));
638 document.set_ma(Ipld::Null);
639 assert!(document.ma.is_none());
640 }
641
642 #[test]
643 fn validate_accepts_opaque_ma() {
644 let identity = crate::identity::generate_identity(
645 "k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr",
646 )
647 .expect("generate identity");
648 let mut document = identity.document;
649 document.set_ma(Ipld::Map(BTreeMap::from([
650 ("type".into(), Ipld::String("bahner".into())),
651 ("custom".into(), Ipld::Integer(42)),
652 ])));
653 document
654 .validate()
655 .expect("validate should accept any ma value");
656 }
657}