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