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    ResolutionOptions, ResolutionResult, VerificationMaterial, VerificationMethod,
24    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    /// - [`ResolutionError::MethodNotSupported`] when `did` is not a `did:key`.
171    /// - [`ResolutionError::InvalidDid`] when the method-specific id is not a Multikey string
172    ///   (optionally preceded by a positive integer version) with a known multicodec header, or
173    ///   the DID is so long that the verification method id `did#multikey` would not fit
174    ///   [`DEFAULT_MAX_LEN`] bytes.
175    /// - [`ResolutionError::Other`] with the method's own error names otherwise:
176    ///   `unsupportedPublicKeyType` for a key type this crate cannot read,
177    ///   `invalidPublicKeyLength` when the raw key has the wrong length, and `invalidPublicKey`
178    ///   when it is not a valid key.
179    pub fn document(&self, did: &Did) -> Result<DidDocument, ResolutionError> {
180        if did.method() != Self::METHOD {
181            return Err(ResolutionError::MethodNotSupported);
182        }
183        let multikey = multikey_of(did).ok_or(ResolutionError::InvalidDid)?;
184        let jwk = Jwk::from_multikey(multikey).map_err(|error| match error {
185            JwkError::InvalidMultikey(_) => ResolutionError::InvalidDid,
186            JwkError::UnsupportedMulticodec(_) | JwkError::FeatureDisabled { .. } => {
187                ResolutionError::Other("unsupportedPublicKeyType".to_owned())
188            }
189            JwkError::InvalidKeyLength { .. } => {
190                ResolutionError::Other("invalidPublicKeyLength".to_owned())
191            }
192            _ => ResolutionError::Other("invalidPublicKey".to_owned()),
193        })?;
194        // The verification method id is `did#multikey`, which must fit a DID URL: a long
195        // version prefix can push it past the limit.
196        if did.as_str().len() + 1 + multikey.len() > DEFAULT_MAX_LEN {
197            return Err(ResolutionError::InvalidDid);
198        }
199        self.build(did, multikey, jwk)
200            .map_err(|_| ResolutionError::InvalidDidDocument)
201    }
202
203    /// The document for `did`, whose Multikey string is `multikey` and decodes to `jwk`.
204    fn build(&self, did: &Did, multikey: &str, jwk: Jwk) -> Result<DidDocument, DocumentError> {
205        let ed25519 = (self.key_agreement && jwk.crv() == Some("Ed25519"))
206            .then(|| jwk.ed25519_verifying_key().ok())
207            .flatten();
208        let signature = self.verification_method(did, multikey, jwk);
209        let mut builder = DidDocument::builder(did.clone())
210            .context(Context::uris(Self::CONTEXT))
211            .verification_method(
212                signature,
213                [
214                    VerificationRelationship::Authentication,
215                    VerificationRelationship::AssertionMethod,
216                    VerificationRelationship::CapabilityInvocation,
217                    VerificationRelationship::CapabilityDelegation,
218                ],
219            );
220        if let Some(key) = ed25519 {
221            // Derive Encryption Key Algorithm: the Montgomery form of the Edwards point.
222            let x25519 = Jwk::from_x25519_public(key.to_montgomery().as_bytes())
223                .expect("a Montgomery point is 32 bytes");
224            let multikey = x25519.to_multikey().expect("an X25519 key has a codec");
225            let method = self.verification_method(did, &multikey, x25519);
226            builder = builder.embedded_method(VerificationRelationship::KeyAgreement, method);
227        }
228        builder.build()
229    }
230
231    /// The verification method `did#multikey` carrying `jwk` in this method's format.
232    fn verification_method(&self, did: &Did, multikey: &str, jwk: Jwk) -> VerificationMethod {
233        let material = match self.format {
234            KeyFormat::Multikey => VerificationMaterial::multibase(multikey),
235            KeyFormat::JsonWebKey => VerificationMaterial::PublicKeyJwk(Box::new(jwk)),
236        };
237        VerificationMethod::new(
238            did.to_url().with_fragment(multikey),
239            self.format.method_type(),
240            did.clone(),
241            material,
242        )
243    }
244
245    /// This method with the `publicKeyFormat` and `enableEncryptionKeyDerivation` resolution
246    /// options applied.
247    fn with_options(self, options: &ResolutionOptions) -> Result<Self, ResolutionError> {
248        let mut method = self;
249        if let Some(format) = options.properties.get("publicKeyFormat") {
250            method.format = match format.as_str() {
251                Some("Multikey") => KeyFormat::Multikey,
252                Some("JsonWebKey") => KeyFormat::JsonWebKey,
253                _ => {
254                    return Err(ResolutionError::Other(
255                        "unsupportedPublicKeyType".to_owned(),
256                    ));
257                }
258            };
259        }
260        if let Some(derive) = options.properties.get("enableEncryptionKeyDerivation") {
261            method.key_agreement = match derive {
262                Value::Bool(derive) => *derive,
263                _ => return Err(ResolutionError::Other("invalidOptions".to_owned())),
264            };
265        }
266        Ok(method)
267    }
268}
269
270impl Default for DidKey {
271    fn default() -> Self {
272        Self::new()
273    }
274}
275
276/// Why a key has no `did:key`.
277#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
278#[non_exhaustive]
279pub enum DidKeyError {
280    /// The key has no Multikey form: an unsupported key type or undecodable material.
281    #[error(transparent)]
282    Key(#[from] JwkError),
283    /// The Multikey string is so long that the DID would exceed `max` bytes.
284    #[error("the did:key would exceed the maximum length of {max} bytes")]
285    TooLong {
286        /// The maximum number of bytes, [`DidKey::MAX_DID_LEN`].
287        max: usize,
288    },
289}
290
291/// The Multikey string of a `did:key`, after the optional version segment, which must be a
292/// positive integer.
293fn multikey_of(did: &Did) -> Option<&str> {
294    let id = did.method_specific_id();
295    let multikey = match id.split_once(':') {
296        None => id,
297        Some((version, multikey)) => {
298            if version.bytes().all(|b| b.is_ascii_digit()) && version.bytes().any(|b| b != b'0') {
299                multikey
300            } else {
301                return None;
302            }
303        }
304    };
305    multikey.starts_with('z').then_some(multikey)
306}
307
308#[async_trait::async_trait]
309impl DidResolver for DidKey {
310    fn method(&self) -> &'static str {
311        Self::METHOD
312    }
313
314    /// Expands `did` with [`DidKey::document`], with the `publicKeyFormat` and
315    /// `enableEncryptionKeyDerivation` options of `options` overriding this method's defaults.
316    async fn resolve(
317        &self,
318        did: &Did,
319        options: &ResolutionOptions,
320    ) -> Result<ResolutionResult, ResolutionError> {
321        check_accept(options)?;
322        self.with_options(options)?
323            .document(did)
324            .map(ResolutionResult::new)
325    }
326}
327
328#[async_trait::async_trait]
329impl DidProducer for DidKey {
330    type Params = ();
331
332    fn method(&self) -> &'static str {
333        Self::METHOD
334    }
335
336    /// Embedded, one key, never updated; the fragment is the Multikey string of the key.
337    fn capabilities(&self) -> MethodCapabilities {
338        MethodCapabilities::new(Hosting::Embedded).with_single_key(true)
339    }
340
341    /// The document of [`DidKey::did`] for the one key in `keys`, with this method's options.
342    async fn produce(&self, keys: &[Jwk], _: ()) -> Result<DidDocument, ProduceError> {
343        let jwk = super::single_key(keys)?;
344        let did = Self::did(jwk).map_err(|error| ProduceError::UnsupportedKey {
345            index: 0,
346            reason: error.to_string(),
347        })?;
348        let multikey = multikey_of(&did).expect("a produced did:key carries its Multikey");
349        Ok(self.build(&did, multikey, jwk.public())?)
350    }
351}