use crate::{DID, Document, DocumentError, verification_method::VerificationMethod};
use std::collections::HashMap;
pub trait DocumentExt {
fn contains_authentication(&self, id: &str) -> bool;
fn contains_key_agreement(&self, id: &str) -> bool;
fn contains_assertion_method(&self, id: &str) -> bool;
fn find_authentication<'a>(&'a self, id: Option<&'a str>) -> Vec<&'a str>;
fn find_key_agreement<'a>(&'a self, id: Option<&'a str>) -> Vec<&'a str>;
fn find_assertion_method<'a>(&'a self, id: Option<&'a str>) -> Vec<&'a str>;
fn get_verification_method(&self, id: &str) -> Option<&VerificationMethod>;
fn expand_peer_keys(&self) -> Result<Document, DocumentError>;
}
impl DocumentExt for Document {
fn contains_authentication(&self, id: &str) -> bool {
self.authentication.iter().any(|vm| vm.get_id() == id)
}
fn contains_key_agreement(&self, id: &str) -> bool {
self.key_agreement.iter().any(|vm| vm.get_id() == id)
}
fn contains_assertion_method(&self, id: &str) -> bool {
self.assertion_method.iter().any(|vm| vm.get_id() == id)
}
fn find_authentication<'a>(&'a self, kid: Option<&'a str>) -> Vec<&'a str> {
if let Some(kid) = kid {
if self.contains_authentication(kid) {
vec![kid]
} else {
vec![]
}
} else {
self.key_agreement.iter().map(|ka| ka.get_id()).collect()
}
}
fn find_key_agreement<'a>(&'a self, kid: Option<&'a str>) -> Vec<&'a str> {
if let Some(kid) = kid {
if self.contains_key_agreement(kid) {
vec![kid]
} else {
vec![]
}
} else {
self.key_agreement.iter().map(|ka| ka.get_id()).collect()
}
}
fn find_assertion_method<'a>(&'a self, kid: Option<&'a str>) -> Vec<&'a str> {
if let Some(kid) = kid {
if self.contains_assertion_method(kid) {
vec![kid]
} else {
vec![]
}
} else {
self.assertion_method.iter().map(|ka| ka.get_id()).collect()
}
}
fn get_verification_method(&self, id: &str) -> Option<&VerificationMethod> {
self.verification_method
.iter()
.find(|vm| vm.id.as_str() == id)
}
fn expand_peer_keys(&self) -> Result<Document, DocumentError> {
let mut new_doc = self.clone();
let mut expanded_vms: Vec<VerificationMethod> = Vec::new();
for vm in &self.verification_method {
expanded_vms.push(expand_verification_method(vm)?);
}
new_doc.verification_method = expanded_vms;
Ok(new_doc)
}
}
fn expand_verification_method(
vm: &VerificationMethod,
) -> Result<VerificationMethod, DocumentError> {
let multibase_key = vm
.property_set
.get("publicKeyMultibase")
.or_else(|| vm.property_set.get("publicKeyBase58"))
.and_then(|v| v.as_str());
let Some(key_multibase) = multibase_key else {
return Ok(vm.clone());
};
let did_key_string = format!("did:key:{key_multibase}");
let did_key: DID = did_key_string.parse().map_err(|e| {
DocumentError::KeyExpansionError(format!("Failed to parse as did:key: {e}"))
})?;
let key_doc = did_key
.resolve()
.map_err(|e| DocumentError::KeyExpansionError(format!("Failed to resolve did:key: {e}")))?;
let resolved_vm = key_doc.verification_method.first().ok_or_else(|| {
DocumentError::KeyExpansionError("Resolved did:key has no verification method".to_string())
})?;
let mut new_properties: HashMap<String, serde_json::Value> = HashMap::new();
for (k, v) in &resolved_vm.property_set {
new_properties.insert(k.clone(), v.clone());
}
Ok(VerificationMethod {
id: vm.id.clone(),
type_: resolved_vm.type_.clone(),
controller: vm.controller.clone(),
expires: vm.expires.clone(),
revoked: vm.revoked.clone(),
property_set: new_properties,
})
}
#[cfg(test)]
mod tests {
use crate::{
Document,
document::DocumentExt,
verification_method::{VerificationMethod, VerificationRelationship},
};
use std::collections::HashMap;
use url::Url;
fn document() -> Document {
Document {
id: Url::parse("did:test:1234").unwrap(),
verification_method: vec![VerificationMethod {
id: Url::parse("did:test:1234#vm").unwrap(),
type_: "Ed25519VerificationKey2018".to_string(),
controller: Url::parse("did:test:1234").unwrap(),
expires: None,
revoked: None,
property_set: HashMap::new(),
}],
assertion_method: vec![
VerificationRelationship::Reference("did:test:1234#assert_ref".to_string()),
VerificationRelationship::VerificationMethod(Box::new(VerificationMethod {
id: Url::parse("did:test:1234#assert_vm").unwrap(),
type_: "Ed25519VerificationKey2018".to_string(),
controller: Url::parse("did:test:1234").unwrap(),
expires: None,
revoked: None,
property_set: HashMap::new(),
})),
],
key_agreement: vec![
VerificationRelationship::Reference("did:test:1234#key_ref".to_string()),
VerificationRelationship::VerificationMethod(Box::new(VerificationMethod {
id: Url::parse("did:test:1234#key_vm").unwrap(),
type_: "Ed25519VerificationKey2018".to_string(),
controller: Url::parse("did:test:1234").unwrap(),
expires: None,
revoked: None,
property_set: HashMap::new(),
})),
],
capability_delegation: vec![],
capability_invocation: vec![],
service: vec![],
authentication: vec![
VerificationRelationship::Reference("did:test:1234#auth_ref".to_string()),
VerificationRelationship::VerificationMethod(Box::new(VerificationMethod {
id: Url::parse("did:test:1234#auth_vm").unwrap(),
type_: "Ed25519VerificationKey2018".to_string(),
controller: Url::parse("did:test:1234").unwrap(),
expires: None,
revoked: None,
property_set: HashMap::new(),
})),
],
parameters_set: HashMap::new(),
}
}
#[test]
fn test_get_verification_method_exists() {
let doc = document();
assert!(doc.get_verification_method("did:test:1234#vm").is_some());
}
#[test]
fn test_get_verification_method_missing() {
let doc = document();
assert!(
doc.get_verification_method("did:test:1234#missing")
.is_none()
);
}
#[test]
fn test_contains_authentication() {
let doc = document();
assert!(doc.contains_authentication("did:test:1234#auth_ref"));
assert!(!doc.contains_authentication("did:test:1234#auth_missing"));
assert!(doc.contains_authentication("did:test:1234#auth_vm"));
assert_eq!(doc.find_authentication(None).len(), 2);
assert_eq!(
doc.find_authentication(Some("did:test:1234#auth_ref"))
.len(),
1
);
assert_eq!(
doc.find_authentication(Some("did:test:1234#missing")).len(),
0
);
}
#[test]
fn test_contains_key_agreement() {
let doc = document();
assert!(doc.contains_key_agreement("did:test:1234#key_ref"));
assert!(!doc.contains_key_agreement("did:test:1234#key_missing"));
assert!(doc.contains_key_agreement("did:test:1234#key_vm"));
assert_eq!(doc.find_key_agreement(None).len(), 2);
assert_eq!(
doc.find_key_agreement(Some("did:test:1234#key_ref")).len(),
1
);
assert_eq!(
doc.find_key_agreement(Some("did:test:1234#missing")).len(),
0
);
}
#[test]
fn test_contains_assertion_method() {
let doc = document();
assert!(doc.contains_assertion_method("did:test:1234#assert_ref"));
assert!(!doc.contains_assertion_method("did:test:1234#assert_missing"));
assert!(doc.contains_assertion_method("did:test:1234#assert_vm"));
assert_eq!(doc.find_assertion_method(None).len(), 2);
assert_eq!(
doc.find_assertion_method(Some("did:test:1234#assert_ref"))
.len(),
1
);
assert_eq!(
doc.find_assertion_method(Some("did:test:1234#missing"))
.len(),
0
);
}
#[test]
fn test_expand_peer_keys() {
use crate::DID;
let did: DID = "did:peer:2.Vz6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc"
.parse()
.unwrap();
let doc = did.resolve().unwrap();
assert_eq!(doc.verification_method.len(), 2);
assert!(
doc.verification_method[0]
.property_set
.contains_key("publicKeyMultibase")
);
let expanded = doc.expand_peer_keys().unwrap();
assert_eq!(expanded.verification_method.len(), 2);
assert!(
expanded.verification_method[0]
.property_set
.contains_key("publicKeyMultibase")
);
assert!(
expanded.verification_method[0]
.id
.as_str()
.contains("did:peer")
);
}
}