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}