use std::collections::HashMap;
use serde_json::{Value, json};
use affinidi_crypto::ed25519::ed25519_public_to_x25519;
use affinidi_encoding::{ED25519_PUB, P256_PUB, P384_PUB, SECP256K1_PUB, X25519_PUB};
use super::DIDMethod;
use super::peer::{PeerNumAlgo, PeerPurpose, PeerService};
use crate::{
DID, DIDError, Document,
service::Service,
verification_method::{VerificationMethod, VerificationRelationship},
};
const PUBLIC_KEY_MULTIBASE: &str = "publicKeyMultibase";
const MULTIKEY_TYPE: &str = "Multikey";
impl DIDMethod {
pub fn resolve(&self, did: &DID) -> Result<Document, DIDError> {
match self {
DIDMethod::Key { identifier, .. } => resolve_key(did, identifier),
DIDMethod::Peer {
numalgo,
identifier,
} => resolve_peer(did, numalgo, identifier),
_ => Err(DIDError::ResolutionError(format!(
"DID method '{}' requires network resolution",
self.name()
))),
}
}
}
fn resolve_key(did: &DID, identifier: &str) -> Result<Document, DIDError> {
let (codec, _) = affinidi_encoding::decode_multikey_with_codec(identifier)
.map_err(|e| DIDError::ResolutionError(format!("Invalid multikey: {e}")))?;
let mut vm_id = did.url();
vm_id.set_fragment(Some(identifier));
let mut vms = Vec::new();
let mut key_agreement = Vec::new();
match codec {
ED25519_PUB => {
let (x25519_encoded, _) = ed25519_public_to_x25519(identifier).map_err(|e| {
DIDError::ResolutionError(format!("Failed to derive X25519 from Ed25519: {e}"))
})?;
let mut x25519_vm_id = did.url();
x25519_vm_id.set_fragment(Some(&x25519_encoded));
vms.push(VerificationMethod {
id: x25519_vm_id.clone(),
type_: MULTIKEY_TYPE.to_string(),
controller: did.url(),
expires: None,
revoked: None,
property_set: HashMap::from([(
PUBLIC_KEY_MULTIBASE.to_string(),
Value::String(x25519_encoded.to_string()),
)]),
});
key_agreement.push(VerificationRelationship::Reference(
x25519_vm_id.to_string(),
));
}
P256_PUB | P384_PUB | SECP256K1_PUB | X25519_PUB => {
key_agreement.push(VerificationRelationship::Reference(vm_id.to_string()));
}
_ => {
return Err(DIDError::ResolutionError(format!(
"Unsupported key codec: 0x{codec:x}"
)));
}
}
vms.insert(
0,
VerificationMethod {
id: vm_id.clone(),
type_: MULTIKEY_TYPE.to_string(),
controller: did.url(),
expires: None,
revoked: None,
property_set: HashMap::from([(
PUBLIC_KEY_MULTIBASE.to_string(),
Value::String(identifier.to_string()),
)]),
},
);
let vm_relationship = VerificationRelationship::Reference(vm_id.to_string());
Ok(Document {
id: did.url(),
verification_method: vms,
authentication: vec![vm_relationship.clone()],
assertion_method: vec![vm_relationship.clone()],
key_agreement,
capability_invocation: vec![vm_relationship.clone()],
capability_delegation: vec![vm_relationship],
service: vec![],
parameters_set: HashMap::from([(
"@context".to_string(),
json!([
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/multikey/v1",
]),
)]),
})
}
fn resolve_peer(did: &DID, numalgo: &PeerNumAlgo, identifier: &str) -> Result<Document, DIDError> {
match numalgo {
PeerNumAlgo::InceptionKey => {
let key_multibase = identifier.strip_prefix('0').unwrap_or(identifier);
let key_did: DID = format!("did:key:{key_multibase}")
.parse()
.map_err(|e| DIDError::ResolutionError(format!("Invalid did:peer:0 key: {e}")))?;
key_did.resolve()
}
PeerNumAlgo::MultipleKeys => resolve_peer_2(did, identifier),
PeerNumAlgo::GenesisDoc => Err(DIDError::ResolutionError(
"did:peer numalgo 1 (genesis doc) is not supported".to_string(),
)),
}
}
fn resolve_peer_2(did: &DID, identifier: &str) -> Result<Document, DIDError> {
use std::str::FromStr;
use url::Url;
let did_string = did.to_string();
let content = identifier.strip_prefix('2').unwrap_or(identifier);
let parts: Vec<&str> = content.split('.').filter(|s| !s.is_empty()).collect();
let mut verification_methods: Vec<VerificationMethod> = Vec::new();
let mut authentication: Vec<VerificationRelationship> = Vec::new();
let mut assertion_method: Vec<VerificationRelationship> = Vec::new();
let mut key_agreement: Vec<VerificationRelationship> = Vec::new();
let mut capability_delegation: Vec<VerificationRelationship> = Vec::new();
let mut capability_invocation: Vec<VerificationRelationship> = Vec::new();
let mut services: Vec<Service> = Vec::new();
let mut key_count: u32 = 0;
let mut service_idx: u32 = 0;
for part in parts {
if part.is_empty() {
continue;
}
let purpose_char = part
.chars()
.next()
.ok_or_else(|| DIDError::ResolutionError("Empty part in did:peer".to_string()))?;
let purpose = PeerPurpose::from_char(purpose_char).ok_or_else(|| {
DIDError::ResolutionError(format!("Invalid purpose code: {purpose_char}"))
})?;
if purpose == PeerPurpose::Service {
let service = PeerService::decode(part)
.map_err(|e| DIDError::ResolutionError(format!("Service decode error: {e}")))?;
let did_service = service
.to_did_service(&did_string, service_idx)
.map_err(|e| DIDError::ResolutionError(format!("Service conversion error: {e}")))?;
services.push(did_service);
service_idx += 1;
} else {
key_count += 1;
let kid = format!("{did_string}#key-{key_count}");
let public_key_multibase = &part[1..];
let vm = VerificationMethod {
id: Url::from_str(&kid)
.map_err(|e| DIDError::ResolutionError(format!("Invalid key ID: {e}")))?,
type_: MULTIKEY_TYPE.to_string(),
controller: did.url(),
expires: None,
revoked: None,
property_set: HashMap::from([(
PUBLIC_KEY_MULTIBASE.to_string(),
Value::String(public_key_multibase.to_string()),
)]),
};
verification_methods.push(vm);
let relationship = VerificationRelationship::Reference(kid);
match purpose {
PeerPurpose::Verification => {
authentication.push(relationship.clone());
assertion_method.push(relationship);
}
PeerPurpose::Encryption => {
key_agreement.push(relationship);
}
PeerPurpose::Assertion => {
assertion_method.push(relationship);
}
PeerPurpose::Delegation => {
capability_delegation.push(relationship);
}
PeerPurpose::Invocation => {
capability_invocation.push(relationship);
}
PeerPurpose::Service => unreachable!(),
}
}
}
Ok(Document {
id: did.url(),
verification_method: verification_methods,
authentication,
assertion_method,
key_agreement,
capability_delegation,
capability_invocation,
service: services,
parameters_set: HashMap::from([(
"@context".to_string(),
json!(["https://www.w3.org/ns/did/v1.1"]),
)]),
})
}
#[cfg(test)]
mod tests {
use crate::DID;
#[test]
fn test_resolve_ed25519() {
let did: DID = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
.parse()
.unwrap();
let doc = did.method().resolve(&did).unwrap();
assert_eq!(
doc.id.as_str(),
"did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
);
assert_eq!(doc.verification_method.len(), 2);
assert_eq!(doc.key_agreement.len(), 1);
}
#[test]
fn test_resolve_p256() {
let did: DID = "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"
.parse()
.unwrap();
let doc = did.method().resolve(&did).unwrap();
assert_eq!(
doc.id.as_str(),
"did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"
);
assert_eq!(doc.verification_method.len(), 1);
assert_eq!(doc.key_agreement.len(), 1);
}
#[test]
fn test_resolve_secp256k1() {
let did: DID = "did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1a7y8xs6zTcNNvoB5e"
.parse()
.unwrap();
let doc = did.method().resolve(&did).unwrap();
assert_eq!(doc.verification_method.len(), 1);
}
#[test]
fn test_resolve_web_requires_network() {
let did: DID = "did:web:example.com".parse().unwrap();
let result = did.method().resolve(&did);
assert!(result.is_err());
}
#[test]
fn test_resolve_peer_numalgo_0() {
let did: DID = "did:peer:0z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
.parse()
.unwrap();
let doc = did.resolve().unwrap();
assert_eq!(doc.verification_method.len(), 2); assert_eq!(doc.authentication.len(), 1);
assert_eq!(doc.key_agreement.len(), 1);
}
#[test]
fn test_resolve_peer_numalgo_2() {
let did: DID = "did:peer:2.Vz6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc"
.parse()
.unwrap();
let doc = did.resolve().unwrap();
assert_eq!(doc.verification_method.len(), 2);
assert_eq!(doc.authentication.len(), 1);
assert_eq!(doc.assertion_method.len(), 1);
assert_eq!(doc.key_agreement.len(), 1);
}
#[test]
fn test_resolve_peer_numalgo_2_with_service() {
let did: DID = "did:peer:2.Vz6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9kaWRjb21tIn0"
.parse()
.unwrap();
let doc = did.resolve().unwrap();
assert_eq!(doc.verification_method.len(), 1);
assert_eq!(doc.service.len(), 1);
assert_eq!(doc.service[0].type_, vec!["DIDCommMessaging".to_string()]);
}
}