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