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}