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}