Skip to main content

identity_kit/methods/
key.rs

1//! `did:key`: a DID that is a public key.
2//!
3//! The [did:key method] (v0.9) expands `did:key:<multibase>` deterministically into a DID
4//! document: the method-specific id is the Multikey form of the public key (base58btc multibase
5//! of the multicodec header and the raw key, see [`Jwk::from_multikey`]), the document has one
6//! verification method whose fragment is that same Multikey string, referenced from
7//! `authentication`, `assertionMethod`, `capabilityInvocation` and `capabilityDelegation`, and
8//! optionally an X25519 key agreement method derived from an Ed25519 key. The method cannot
9//! update or deactivate.
10//!
11//! Supported key types are those of [`KeyCodec`](crate::jose::KeyCodec): Ed25519, X25519,
12//! P-256, secp256k1 (feature `secp256k1`) and RSA. The version segment of
13//! `did:key:1:<multibase>` is accepted and must be a positive integer.
14//!
15//! [did:key method]: https://w3c-ccg.github.io/did-key-spec/
16
17use core::fmt;
18
19use serde_json::Value;
20
21use crate::did::{
22    Context, DEFAULT_MAX_LEN, Did, DidDocument, DocumentError, MethodType, ResolutionError,
23    ResolutionErrorKind, ResolutionOptions, ResolutionResult, VerificationMaterial,
24    VerificationMethod, VerificationRelationship,
25};
26use crate::jose::{Jwk, JwkError};
27use crate::resolver::{DidProducer, DidResolver, Hosting, MethodCapabilities, ProduceError};
28
29use super::check_accept;
30
31/// How a verification method of a `did:key` document carries the key (the `publicKeyFormat`
32/// option of the method).
33#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
34#[non_exhaustive]
35pub enum KeyFormat {
36    /// Type `Multikey` with `publicKeyMultibase`: the Multikey string of the DID itself. The
37    /// default, and what the specification's examples show.
38    #[default]
39    Multikey,
40    /// Type `JsonWebKey` with `publicKeyJwk`.
41    JsonWebKey,
42}
43
44impl KeyFormat {
45    /// The verification method type name.
46    pub const fn as_str(self) -> &'static str {
47        match self {
48            Self::Multikey => "Multikey",
49            Self::JsonWebKey => "JsonWebKey",
50        }
51    }
52
53    const fn method_type(self) -> MethodType {
54        match self {
55            Self::Multikey => MethodType::MULTIKEY,
56            Self::JsonWebKey => MethodType::JSON_WEB_KEY,
57        }
58    }
59}
60
61impl fmt::Display for KeyFormat {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        f.write_str(self.as_str())
64    }
65}
66
67/// The `did:key` method: a [`DidResolver`] that expands the DID and a [`DidProducer`] that
68/// encodes one public key.
69///
70/// The resolver's options are those the specification defines: the [`KeyFormat`]
71/// (`publicKeyFormat`) and whether to derive an X25519 key agreement method from an Ed25519
72/// key (`enableEncryptionKeyDerivation`). Both are set here as defaults and may be overridden
73/// per resolution through [`ResolutionOptions::properties`] under those names, with a string
74/// and a boolean respectively.
75///
76/// # Examples
77///
78/// ```
79/// use identity_kit::did::Did;
80/// use identity_kit::jose::Jwk;
81/// use identity_kit::methods::DidKey;
82/// use identity_kit::methods::key::KeyFormat;
83///
84/// let jwk: Jwk = serde_json::from_str(
85///     r#"{"kty":"OKP","crv":"Ed25519","x":"Lm_M42cB3HkUiODQsXRcweM6TByfzEHGO9ND274JcOY"}"#,
86/// )?;
87/// let did = DidKey::did(&jwk)?;
88/// assert_eq!(did.as_str(), "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK");
89///
90/// let document = DidKey::new().with_format(KeyFormat::JsonWebKey).document(&did)?;
91/// let method = &document.verification_method[0];
92/// assert_eq!(method.id.fragment(), Some("z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"));
93/// assert_eq!(method.public_key_jwk(), Some(&jwk));
94/// # Ok::<(), Box<dyn std::error::Error>>(())
95/// ```
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
97pub struct DidKey {
98    format: KeyFormat,
99    key_agreement: bool,
100}
101
102impl DidKey {
103    /// The method name, `key`.
104    pub const METHOD: &'static str = "key";
105
106    /// The longest `did:key` this method can produce: the verification method id
107    /// `did#multikey` repeats the Multikey string, and must fit [`DEFAULT_MAX_LEN`] bytes.
108    pub const MAX_DID_LEN: usize = "did:key:".len() + (DEFAULT_MAX_LEN - "did:key:".len() - 1) / 2;
109
110    /// The `@context` of every `did:key` document.
111    pub const CONTEXT: [&'static str; 1] = ["https://www.w3.org/ns/did/v1.1"];
112
113    /// The method with [`KeyFormat::Multikey`] and no key agreement derivation.
114    pub const fn new() -> Self {
115        Self {
116            format: KeyFormat::Multikey,
117            key_agreement: false,
118        }
119    }
120
121    /// This method expressing keys as `format`.
122    #[must_use]
123    pub const fn with_format(mut self, format: KeyFormat) -> Self {
124        self.format = format;
125        self
126    }
127
128    /// This method deriving an X25519 key agreement method from an Ed25519 key when
129    /// `derive` is set (`enableEncryptionKeyDerivation`). The derived method is embedded under
130    /// `keyAgreement`, as in the specification's examples.
131    #[must_use]
132    pub const fn with_key_agreement(mut self, derive: bool) -> Self {
133        self.key_agreement = derive;
134        self
135    }
136
137    /// The key format.
138    pub const fn format(&self) -> KeyFormat {
139        self.format
140    }
141
142    /// Whether an X25519 key agreement method is derived from an Ed25519 key.
143    pub const fn key_agreement(&self) -> bool {
144        self.key_agreement
145    }
146
147    /// The `did:key` of the public part of `jwk`.
148    ///
149    /// # Errors
150    ///
151    /// - [`DidKeyError::Key`] with the errors of [`Jwk::to_multikey`]: an unsupported key type
152    ///   or undecodable material.
153    /// - [`DidKeyError::TooLong`] when the Multikey string is so long that the DID exceeds
154    ///   [`MAX_DID_LEN`](Self::MAX_DID_LEN) bytes, as a very large RSA key is; base58btc is
155    ///   otherwise valid DID syntax.
156    pub fn did(jwk: &Jwk) -> Result<Did, DidKeyError> {
157        let multikey = jwk.to_multikey()?;
158        Did::parse_with_max_len(&format!("did:key:{multikey}"), Self::MAX_DID_LEN).map_err(|_| {
159            DidKeyError::TooLong {
160                max: Self::MAX_DID_LEN,
161            }
162        })
163    }
164
165    /// The DID document `did` expands to with this method's options (the method's Read
166    /// operation).
167    ///
168    /// # Errors
169    ///
170    /// A [`ResolutionError`] whose kind is:
171    ///
172    /// - [`MethodNotSupported`](ResolutionErrorKind::MethodNotSupported) when `did` is not a
173    ///   `did:key`.
174    /// - [`InvalidDid`](ResolutionErrorKind::InvalidDid) when the method-specific id is not a
175    ///   Multikey string (optionally preceded by a positive integer version) with a known
176    ///   multicodec header, or the DID is so long that the verification method id
177    ///   `did#multikey` would not fit [`DEFAULT_MAX_LEN`] bytes.
178    /// - [`Other`](ResolutionErrorKind::Other) with the method's own error names otherwise:
179    ///   `unsupportedPublicKeyType` for a key type this crate cannot read,
180    ///   `invalidPublicKeyLength` when the raw key has the wrong length, and `invalidPublicKey`
181    ///   when it is not a valid key.
182    ///
183    /// A failure to decode the Multikey string has the [`JwkError`] as its source.
184    pub fn document(&self, did: &Did) -> Result<DidDocument, ResolutionError> {
185        if did.method() != Self::METHOD {
186            return Err(
187                ResolutionError::new(ResolutionErrorKind::MethodNotSupported)
188                    .with_detail(format!("`{did}` is not a did:key")),
189            );
190        }
191        let multikey = multikey_of(did).ok_or_else(|| {
192            ResolutionError::new(ResolutionErrorKind::InvalidDid).with_detail(
193                "the method-specific id is not a Multikey string after an optional version",
194            )
195        })?;
196        let jwk = Jwk::from_multikey(multikey).map_err(|error| {
197            let kind = match &error {
198                JwkError::InvalidMultikey(_) => ResolutionErrorKind::InvalidDid,
199                JwkError::UnsupportedMulticodec(_) | JwkError::FeatureDisabled { .. } => {
200                    ResolutionErrorKind::Other("unsupportedPublicKeyType".to_owned())
201                }
202                JwkError::InvalidKeyLength { .. } => {
203                    ResolutionErrorKind::Other("invalidPublicKeyLength".to_owned())
204                }
205                _ => ResolutionErrorKind::Other("invalidPublicKey".to_owned()),
206            };
207            ResolutionError::new(kind).with_source(error)
208        })?;
209        // The verification method id is `did#multikey`, which must fit a DID URL: a long
210        // version prefix can push it past the limit.
211        if did.as_str().len() + 1 + multikey.len() > DEFAULT_MAX_LEN {
212            return Err(
213                ResolutionError::new(ResolutionErrorKind::InvalidDid).with_detail(format!(
214                    "the verification method id `did#multikey` would exceed {DEFAULT_MAX_LEN} bytes"
215                )),
216            );
217        }
218        self.build(did, multikey, jwk).map_err(|error| {
219            ResolutionError::new(ResolutionErrorKind::InvalidDidDocument).with_source(error)
220        })
221    }
222
223    /// The document for `did`, whose Multikey string is `multikey` and decodes to `jwk`.
224    fn build(self, did: &Did, multikey: &str, jwk: Jwk) -> Result<DidDocument, DocumentError> {
225        let ed25519 = (self.key_agreement && jwk.crv() == Some("Ed25519"))
226            .then(|| jwk.ed25519_verifying_key().ok())
227            .flatten();
228        let signature = self.verification_method(did, multikey, jwk);
229        let mut builder = DidDocument::builder(did.clone())
230            .context(Context::uris(Self::CONTEXT))
231            .verification_method(
232                signature,
233                [
234                    VerificationRelationship::Authentication,
235                    VerificationRelationship::AssertionMethod,
236                    VerificationRelationship::CapabilityInvocation,
237                    VerificationRelationship::CapabilityDelegation,
238                ],
239            );
240        if let Some(key) = ed25519 {
241            // Derive Encryption Key Algorithm: the Montgomery form of the Edwards point.
242            let x25519 = Jwk::from_x25519_public(key.to_montgomery().as_bytes())
243                .expect("a Montgomery point is 32 bytes");
244            let multikey = x25519.to_multikey().expect("an X25519 key has a codec");
245            let method = self.verification_method(did, &multikey, x25519);
246            builder = builder.embedded_method(VerificationRelationship::KeyAgreement, method);
247        }
248        builder.build()
249    }
250
251    /// The verification method `did#multikey` carrying `jwk` in this method's format.
252    fn verification_method(self, did: &Did, multikey: &str, jwk: Jwk) -> VerificationMethod {
253        let material = match self.format {
254            KeyFormat::Multikey => VerificationMaterial::multibase(multikey),
255            KeyFormat::JsonWebKey => VerificationMaterial::PublicKeyJwk(Box::new(jwk)),
256        };
257        VerificationMethod::new(
258            did.to_url().with_fragment(multikey),
259            self.format.method_type(),
260            did.clone(),
261            material,
262        )
263    }
264
265    /// This method with the `publicKeyFormat` and `enableEncryptionKeyDerivation` resolution
266    /// options applied.
267    fn with_options(self, options: &ResolutionOptions) -> Result<Self, ResolutionError> {
268        let mut method = self;
269        if let Some(format) = options.properties.get("publicKeyFormat") {
270            method.format = match format.as_str() {
271                Some("Multikey") => KeyFormat::Multikey,
272                Some("JsonWebKey") => KeyFormat::JsonWebKey,
273                _ => {
274                    return Err(ResolutionError::new(ResolutionErrorKind::Other(
275                        "unsupportedPublicKeyType".to_owned(),
276                    ))
277                    .with_detail(format!("`publicKeyFormat` {format} is not supported")));
278                }
279            };
280        }
281        if let Some(derive) = options.properties.get("enableEncryptionKeyDerivation") {
282            method.key_agreement = match derive {
283                Value::Bool(derive) => *derive,
284                _ => {
285                    return Err(ResolutionError::new(ResolutionErrorKind::Other(
286                        "invalidOptions".to_owned(),
287                    ))
288                    .with_detail("`enableEncryptionKeyDerivation` must be a boolean"));
289                }
290            };
291        }
292        Ok(method)
293    }
294}
295
296impl Default for DidKey {
297    fn default() -> Self {
298        Self::new()
299    }
300}
301
302/// Why a key has no `did:key`.
303#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
304#[non_exhaustive]
305pub enum DidKeyError {
306    /// The key has no Multikey form: an unsupported key type or undecodable material.
307    #[error(transparent)]
308    Key(#[from] JwkError),
309    /// The Multikey string is so long that the DID would exceed `max` bytes.
310    #[error("the did:key would exceed the maximum length of {max} bytes")]
311    TooLong {
312        /// The maximum number of bytes, [`DidKey::MAX_DID_LEN`].
313        max: usize,
314    },
315}
316
317/// The Multikey string of a `did:key`, after the optional version segment, which must be a
318/// positive integer.
319fn multikey_of(did: &Did) -> Option<&str> {
320    let id = did.method_specific_id();
321    let multikey = match id.split_once(':') {
322        None => id,
323        Some((version, multikey)) => {
324            if version.bytes().all(|b| b.is_ascii_digit()) && version.bytes().any(|b| b != b'0') {
325                multikey
326            } else {
327                return None;
328            }
329        }
330    };
331    multikey.starts_with('z').then_some(multikey)
332}
333
334#[async_trait::async_trait]
335impl DidResolver for DidKey {
336    fn method(&self) -> &'static str {
337        Self::METHOD
338    }
339
340    /// Expands `did` with [`DidKey::document`], with the `publicKeyFormat` and
341    /// `enableEncryptionKeyDerivation` options of `options` overriding this method's defaults.
342    async fn resolve(
343        &self,
344        did: &Did,
345        options: &ResolutionOptions,
346    ) -> Result<ResolutionResult, ResolutionError> {
347        check_accept(options)?;
348        self.with_options(options)?
349            .document(did)
350            .map(ResolutionResult::new)
351    }
352}
353
354#[async_trait::async_trait]
355impl DidProducer for DidKey {
356    type Params = ();
357
358    fn method(&self) -> &'static str {
359        Self::METHOD
360    }
361
362    /// Embedded, one key, never updated; the fragment is the Multikey string of the key.
363    fn capabilities(&self) -> MethodCapabilities {
364        MethodCapabilities::new(Hosting::Embedded).with_single_key(true)
365    }
366
367    /// The document of [`DidKey::did`] for the one key in `keys`, with this method's options.
368    async fn produce(&self, keys: &[Jwk], _: ()) -> Result<DidDocument, ProduceError> {
369        let jwk = super::single_key(keys)?;
370        let did = Self::did(jwk).map_err(|error| ProduceError::UnsupportedKey {
371            index: 0,
372            reason: "it has no `did:key`".to_owned(),
373            source: Some(Box::new(error)),
374        })?;
375        let multikey = multikey_of(&did).expect("a produced did:key carries its Multikey");
376        Ok(self.build(&did, multikey, jwk.public())?)
377    }
378}