use core::fmt;
use serde_json::Value;
use crate::did::{
Context, DEFAULT_MAX_LEN, Did, DidDocument, DocumentError, MethodType, ResolutionError,
ResolutionErrorKind, ResolutionOptions, ResolutionResult, VerificationMaterial,
VerificationMethod, VerificationRelationship,
};
use crate::jose::{Jwk, JwkError};
use crate::resolver::{DidProducer, DidResolver, Hosting, MethodCapabilities, ProduceError};
use super::check_accept;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum KeyFormat {
#[default]
Multikey,
JsonWebKey,
}
impl KeyFormat {
pub const fn as_str(self) -> &'static str {
match self {
Self::Multikey => "Multikey",
Self::JsonWebKey => "JsonWebKey",
}
}
const fn method_type(self) -> MethodType {
match self {
Self::Multikey => MethodType::MULTIKEY,
Self::JsonWebKey => MethodType::JSON_WEB_KEY,
}
}
}
impl fmt::Display for KeyFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DidKey {
format: KeyFormat,
key_agreement: bool,
}
impl DidKey {
pub const METHOD: &'static str = "key";
pub const MAX_DID_LEN: usize = "did:key:".len() + (DEFAULT_MAX_LEN - "did:key:".len() - 1) / 2;
pub const CONTEXT: [&'static str; 1] = ["https://www.w3.org/ns/did/v1.1"];
pub const fn new() -> Self {
Self {
format: KeyFormat::Multikey,
key_agreement: false,
}
}
#[must_use]
pub const fn with_format(mut self, format: KeyFormat) -> Self {
self.format = format;
self
}
#[must_use]
pub const fn with_key_agreement(mut self, derive: bool) -> Self {
self.key_agreement = derive;
self
}
pub const fn format(&self) -> KeyFormat {
self.format
}
pub const fn key_agreement(&self) -> bool {
self.key_agreement
}
pub fn did(jwk: &Jwk) -> Result<Did, DidKeyError> {
let multikey = jwk.to_multikey()?;
Did::parse_with_max_len(&format!("did:key:{multikey}"), Self::MAX_DID_LEN).map_err(|_| {
DidKeyError::TooLong {
max: Self::MAX_DID_LEN,
}
})
}
pub fn document(&self, did: &Did) -> Result<DidDocument, ResolutionError> {
if did.method() != Self::METHOD {
return Err(
ResolutionError::new(ResolutionErrorKind::MethodNotSupported)
.with_detail(format!("`{did}` is not a did:key")),
);
}
let multikey = multikey_of(did).ok_or_else(|| {
ResolutionError::new(ResolutionErrorKind::InvalidDid).with_detail(
"the method-specific id is not a Multikey string after an optional version",
)
})?;
let jwk = Jwk::from_multikey(multikey).map_err(|error| {
let kind = match &error {
JwkError::InvalidMultikey(_) => ResolutionErrorKind::InvalidDid,
JwkError::UnsupportedMulticodec(_) | JwkError::FeatureDisabled { .. } => {
ResolutionErrorKind::Other("unsupportedPublicKeyType".to_owned())
}
JwkError::InvalidKeyLength { .. } => {
ResolutionErrorKind::Other("invalidPublicKeyLength".to_owned())
}
_ => ResolutionErrorKind::Other("invalidPublicKey".to_owned()),
};
ResolutionError::new(kind).with_source(error)
})?;
if did.as_str().len() + 1 + multikey.len() > DEFAULT_MAX_LEN {
return Err(
ResolutionError::new(ResolutionErrorKind::InvalidDid).with_detail(format!(
"the verification method id `did#multikey` would exceed {DEFAULT_MAX_LEN} bytes"
)),
);
}
self.build(did, multikey, jwk).map_err(|error| {
ResolutionError::new(ResolutionErrorKind::InvalidDidDocument).with_source(error)
})
}
fn build(&self, did: &Did, multikey: &str, jwk: Jwk) -> Result<DidDocument, DocumentError> {
let ed25519 = (self.key_agreement && jwk.crv() == Some("Ed25519"))
.then(|| jwk.ed25519_verifying_key().ok())
.flatten();
let signature = self.verification_method(did, multikey, jwk);
let mut builder = DidDocument::builder(did.clone())
.context(Context::uris(Self::CONTEXT))
.verification_method(
signature,
[
VerificationRelationship::Authentication,
VerificationRelationship::AssertionMethod,
VerificationRelationship::CapabilityInvocation,
VerificationRelationship::CapabilityDelegation,
],
);
if let Some(key) = ed25519 {
let x25519 = Jwk::from_x25519_public(key.to_montgomery().as_bytes())
.expect("a Montgomery point is 32 bytes");
let multikey = x25519.to_multikey().expect("an X25519 key has a codec");
let method = self.verification_method(did, &multikey, x25519);
builder = builder.embedded_method(VerificationRelationship::KeyAgreement, method);
}
builder.build()
}
fn verification_method(&self, did: &Did, multikey: &str, jwk: Jwk) -> VerificationMethod {
let material = match self.format {
KeyFormat::Multikey => VerificationMaterial::multibase(multikey),
KeyFormat::JsonWebKey => VerificationMaterial::PublicKeyJwk(Box::new(jwk)),
};
VerificationMethod::new(
did.to_url().with_fragment(multikey),
self.format.method_type(),
did.clone(),
material,
)
}
fn with_options(self, options: &ResolutionOptions) -> Result<Self, ResolutionError> {
let mut method = self;
if let Some(format) = options.properties.get("publicKeyFormat") {
method.format = match format.as_str() {
Some("Multikey") => KeyFormat::Multikey,
Some("JsonWebKey") => KeyFormat::JsonWebKey,
_ => {
return Err(ResolutionError::new(ResolutionErrorKind::Other(
"unsupportedPublicKeyType".to_owned(),
))
.with_detail(format!("`publicKeyFormat` {format} is not supported")));
}
};
}
if let Some(derive) = options.properties.get("enableEncryptionKeyDerivation") {
method.key_agreement = match derive {
Value::Bool(derive) => *derive,
_ => {
return Err(ResolutionError::new(ResolutionErrorKind::Other(
"invalidOptions".to_owned(),
))
.with_detail("`enableEncryptionKeyDerivation` must be a boolean"));
}
};
}
Ok(method)
}
}
impl Default for DidKey {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum DidKeyError {
#[error(transparent)]
Key(#[from] JwkError),
#[error("the did:key would exceed the maximum length of {max} bytes")]
TooLong {
max: usize,
},
}
fn multikey_of(did: &Did) -> Option<&str> {
let id = did.method_specific_id();
let multikey = match id.split_once(':') {
None => id,
Some((version, multikey)) => {
if version.bytes().all(|b| b.is_ascii_digit()) && version.bytes().any(|b| b != b'0') {
multikey
} else {
return None;
}
}
};
multikey.starts_with('z').then_some(multikey)
}
#[async_trait::async_trait]
impl DidResolver for DidKey {
fn method(&self) -> &'static str {
Self::METHOD
}
async fn resolve(
&self,
did: &Did,
options: &ResolutionOptions,
) -> Result<ResolutionResult, ResolutionError> {
check_accept(options)?;
self.with_options(options)?
.document(did)
.map(ResolutionResult::new)
}
}
#[async_trait::async_trait]
impl DidProducer for DidKey {
type Params = ();
fn method(&self) -> &'static str {
Self::METHOD
}
fn capabilities(&self) -> MethodCapabilities {
MethodCapabilities::new(Hosting::Embedded).with_single_key(true)
}
async fn produce(&self, keys: &[Jwk], _: ()) -> Result<DidDocument, ProduceError> {
let jwk = super::single_key(keys)?;
let did = Self::did(jwk).map_err(|error| ProduceError::UnsupportedKey {
index: 0,
reason: error.to_string(),
})?;
let multikey = multikey_of(&did).expect("a produced did:key carries its Multikey");
Ok(self.build(&did, multikey, jwk.public())?)
}
}