Skip to main content

identity_kit/resolver/
producer.rs

1//! `DidProducer`: creating the DID document of a method from keys.
2
3use core::fmt;
4use std::error::Error;
5
6use crate::cause::cause;
7use crate::did::{DidDocument, DocumentError};
8use crate::jose::Jwk;
9
10/// Where the documents of a DID method live, which decides what creating one involves.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12#[non_exhaustive]
13pub enum Hosting {
14    /// The document is derived from the DID itself (`did:key`, `did:jwk`): nothing is published.
15    Embedded,
16    /// The document is served over HTTPS by its controller (`did:web`).
17    Web,
18    /// The document is written to a verifiable data registry (a ledger, a directory).
19    Registry,
20}
21
22impl Hosting {
23    /// The lowercase name: `embedded`, `web` or `registry`.
24    pub const fn as_str(self) -> &'static str {
25        match self {
26            Self::Embedded => "embedded",
27            Self::Web => "web",
28            Self::Registry => "registry",
29        }
30    }
31}
32
33impl fmt::Display for Hosting {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        f.write_str(self.as_str())
36    }
37}
38
39/// What a DID method can do, declared by its [`DidProducer`] so an application can pick a
40/// method without knowing it.
41///
42/// # Examples
43///
44/// ```
45/// use identity_kit::resolver::{Hosting, MethodCapabilities};
46///
47/// // did:jwk: one key, no updates, the method is always `#0`.
48/// let jwk = MethodCapabilities::new(Hosting::Embedded)
49///     .with_single_key(true)
50///     .with_fixed_fragment("0");
51/// assert!(!jwk.updatable);
52/// assert_eq!(jwk.fixed_fragment, Some("0"));
53///
54/// // did:web: any number of keys, updated by republishing the document.
55/// let web = MethodCapabilities::new(Hosting::Web).with_updatable(true);
56/// assert!(web.updatable && !web.single_key && web.fixed_fragment.is_none());
57/// ```
58#[derive(Debug, Clone, PartialEq, Eq, Hash)]
59#[non_exhaustive]
60pub struct MethodCapabilities {
61    /// Whether a document can change after creation. An embedded method never can.
62    pub updatable: bool,
63    /// Where the documents live.
64    pub hosting: Hosting,
65    /// Whether a document holds exactly one verification method.
66    pub single_key: bool,
67    /// The fragment every verification method of the method is named with (`0` for `did:jwk`),
68    /// or `None` when the fragment depends on the key or on the caller.
69    pub fixed_fragment: Option<&'static str>,
70}
71
72impl MethodCapabilities {
73    /// Capabilities of a method hosted as `hosting`: not updatable, any number of keys, no fixed
74    /// fragment.
75    pub const fn new(hosting: Hosting) -> Self {
76        Self {
77            updatable: false,
78            hosting,
79            single_key: false,
80            fixed_fragment: None,
81        }
82    }
83
84    /// These capabilities with `updatable` set.
85    #[must_use]
86    pub const fn with_updatable(mut self, updatable: bool) -> Self {
87        self.updatable = updatable;
88        self
89    }
90
91    /// These capabilities with `single_key` set.
92    #[must_use]
93    pub const fn with_single_key(mut self, single_key: bool) -> Self {
94        self.single_key = single_key;
95        self
96    }
97
98    /// These capabilities with `fixed_fragment` set.
99    #[must_use]
100    pub const fn with_fixed_fragment(mut self, fragment: &'static str) -> Self {
101        self.fixed_fragment = Some(fragment);
102        self
103    }
104}
105
106/// Why a [`DidProducer`] could not produce a document.
107#[derive(Debug, thiserror::Error)]
108#[non_exhaustive]
109pub enum ProduceError {
110    /// The method needs at least one key and got none.
111    #[error("at least one key is required")]
112    NoKeys,
113    /// More keys than the method can hold in one document.
114    #[error("at most {max} key(s) can be used, got {actual}")]
115    TooManyKeys {
116        /// How many keys the method accepts.
117        max: usize,
118        /// How many were given.
119        actual: usize,
120    },
121    /// A key the method cannot represent: a private key, an unsupported key type, a malformed
122    /// JWK. The source is the error that says why, when there is one.
123    #[error("key {index} cannot be used: {reason}{}", cause(.source.as_ref()))]
124    UnsupportedKey {
125        /// The position of the key in the `keys` slice.
126        index: usize,
127        /// Why.
128        reason: String,
129        /// The underlying error.
130        source: Option<Box<dyn Error + Send + Sync>>,
131    },
132    /// The method-specific parameters are invalid. The source is the error that says why, when
133    /// there is one.
134    #[error("invalid parameters: {reason}{}", cause(.source.as_ref()))]
135    InvalidParams {
136        /// What is wrong with them.
137        reason: String,
138        /// The underlying error.
139        source: Option<Box<dyn Error + Send + Sync>>,
140    },
141    /// The document could not be assembled.
142    #[error(transparent)]
143    Document(#[from] DocumentError),
144    /// A failure of whatever the method writes to (a registry, a file).
145    #[error("backend error: {0}")]
146    Backend(#[source] Box<dyn Error + Send + Sync>),
147}
148
149impl ProduceError {
150    /// [`ProduceError::UnsupportedKey`] for the key at `index`, without an underlying error.
151    pub fn unsupported_key(index: usize, reason: impl Into<String>) -> Self {
152        Self::UnsupportedKey {
153            index,
154            reason: reason.into(),
155            source: None,
156        }
157    }
158
159    /// [`ProduceError::InvalidParams`] without an underlying error.
160    pub fn invalid_params(reason: impl Into<String>) -> Self {
161        Self::InvalidParams {
162            reason: reason.into(),
163            source: None,
164        }
165    }
166
167    /// [`ProduceError::Backend`] wrapping `error`.
168    pub fn backend(error: impl Error + Send + Sync + 'static) -> Self {
169        Self::Backend(Box::new(error))
170    }
171}
172
173/// Produces the DID document of one method from public keys.
174///
175/// The keys are public JWKs, typically fetched from a [`KeyStore`]: producing a document never
176/// touches private material. `Params` carries whatever the method needs beyond the keys: the
177/// origin of a `did:web`, nothing (`()`) for an embedded method.
178///
179/// [`KeyStore`]: https://github.com/impierce/identity-kit/issues/8
180#[async_trait::async_trait]
181pub trait DidProducer: Send + Sync {
182    /// Method-specific parameters.
183    type Params: Send;
184
185    /// The method name: `web` for `did:web`.
186    fn method(&self) -> &'static str;
187
188    /// What the method can do.
189    fn capabilities(&self) -> MethodCapabilities;
190
191    /// Produces the document for `keys`.
192    ///
193    /// The same keys and parameters produce the same document; for a method whose DID depends
194    /// on the keys, the `id` of the document is that DID.
195    ///
196    /// # Errors
197    ///
198    /// A [`ProduceError`] when the keys or the parameters do not fit the method, or writing the
199    /// document (a registry method) fails.
200    async fn produce(
201        &self,
202        keys: &[Jwk],
203        params: Self::Params,
204    ) -> Result<DidDocument, ProduceError>;
205}