use affinidi_crypto::KeyType;
use affinidi_data_integrity::{
DataIntegrityError, DidKeyResolver, ResolvedKey, VerificationMethodResolver,
};
use async_trait::async_trait;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ProofPurpose {
AssertionMethod,
Authentication,
CapabilityInvocation,
CapabilityDelegation,
}
impl ProofPurpose {
pub const ALL: [ProofPurpose; 4] = [
ProofPurpose::AssertionMethod,
ProofPurpose::Authentication,
ProofPurpose::CapabilityInvocation,
ProofPurpose::CapabilityDelegation,
];
pub fn as_str(&self) -> &'static str {
match self {
ProofPurpose::AssertionMethod => "assertionMethod",
ProofPurpose::Authentication => "authentication",
ProofPurpose::CapabilityInvocation => "capabilityInvocation",
ProofPurpose::CapabilityDelegation => "capabilityDelegation",
}
}
pub fn parse(value: &str) -> Result<Self, DataIntegrityError> {
match value {
"assertionMethod" => Ok(ProofPurpose::AssertionMethod),
"authentication" => Ok(ProofPurpose::Authentication),
"capabilityInvocation" => Ok(ProofPurpose::CapabilityInvocation),
"capabilityDelegation" => Ok(ProofPurpose::CapabilityDelegation),
"keyAgreement" => Err(DataIntegrityError::MalformedProof(
"proofPurpose keyAgreement never authorises a signature".to_string(),
)),
_ => Err(DataIntegrityError::MalformedProof(
"proofPurpose names no signing verification relationship".to_string(),
)),
}
}
}
impl std::fmt::Display for ProofPurpose {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl std::str::FromStr for ProofPurpose {
type Err = DataIntegrityError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
#[async_trait]
pub trait ProofPurposeResolver: Send + Sync {
async fn resolve_vm_for_purpose(
&self,
vm: &str,
purpose: ProofPurpose,
) -> Result<ResolvedKey, DataIntegrityError>;
}
#[async_trait]
impl<R: ProofPurposeResolver + ?Sized> ProofPurposeResolver for &R {
async fn resolve_vm_for_purpose(
&self,
vm: &str,
purpose: ProofPurpose,
) -> Result<ResolvedKey, DataIntegrityError> {
(**self).resolve_vm_for_purpose(vm, purpose).await
}
}
#[async_trait]
impl<R: ProofPurposeResolver + ?Sized> ProofPurposeResolver for std::sync::Arc<R> {
async fn resolve_vm_for_purpose(
&self,
vm: &str,
purpose: ProofPurpose,
) -> Result<ResolvedKey, DataIntegrityError> {
(**self).resolve_vm_for_purpose(vm, purpose).await
}
}
#[async_trait]
impl ProofPurposeResolver for DidKeyResolver {
async fn resolve_vm_for_purpose(
&self,
vm: &str,
_purpose: ProofPurpose,
) -> Result<ResolvedKey, DataIntegrityError> {
let (did, fragment) = split_vm(vm)?;
let id = did.strip_prefix("did:key:").ok_or_else(|| {
DataIntegrityError::Resolver("verificationMethod is not a did:key".to_string())
})?;
if fragment != id {
return Err(DataIntegrityError::Resolver(
"verificationMethod is not the did:key's own key (fragment must repeat the key id)"
.to_string(),
));
}
let key = self.resolve_vm(vm).await?;
if key.key_type == KeyType::X25519 {
return Err(DataIntegrityError::Resolver(
"a did:key X25519 key is authorised for keyAgreement only".to_string(),
));
}
Ok(key)
}
}
pub struct PurposeBound<R> {
resolver: R,
purpose: ProofPurpose,
}
impl<R> PurposeBound<R> {
pub fn new(resolver: R, purpose: ProofPurpose) -> Self {
Self { resolver, purpose }
}
pub fn purpose(&self) -> ProofPurpose {
self.purpose
}
}
#[async_trait]
impl<R: ProofPurposeResolver> VerificationMethodResolver for PurposeBound<R> {
async fn resolve_vm(&self, vm: &str) -> Result<ResolvedKey, DataIntegrityError> {
self.resolver.resolve_vm_for_purpose(vm, self.purpose).await
}
}
pub(crate) fn split_vm(vm: &str) -> Result<(&str, &str), DataIntegrityError> {
match vm.split_once('#') {
Some((did, fragment)) if did.starts_with("did:") && !fragment.is_empty() => {
Ok((did, fragment))
}
_ => Err(DataIntegrityError::Resolver(
"verificationMethod must be an absolute DID URL with a fragment".to_string(),
)),
}
}