ssi_dids/
lib.rs

1//! DID Methods.
2//!
3//! This library provides Decentralized Identifiers (DIDs), a type of
4//! identifier defined by the W3C that enables verifiable, self-sovereign
5//! digital identities.
6//! Unlike traditional identifiers, such as email addresses or usernames,
7//! DIDs are not tied to a central authority. Instead, they are generated and
8//! managed on decentralized networks like blockchains, providing greater
9//! privacy, security, and control to the individual or entity that owns them.
10//!
11//! Each DID is an URI using the `did` scheme. This library uses the [`DID`] and
12//! [`DIDBuf`] (similar to [`str`] and [`String`]) to parse and store DIDs.
13//!
14//! ```
15//! use ssi_dids::{DID, DIDBuf};
16//!
17//! // Create a `&DID` from a `&str`.
18//! let did = DID::new("did:web:w3c-ccg.github.io:user:alice").unwrap();
19//!
20//! // Create a `DIDBuf` from a `String`.
21//! let owned_did = DIDBuf::from_string("did:web:w3c-ccg.github.io:user:alice".to_owned()).unwrap();
22//! ```
23//!
24//! Just like regular URLs, it is possible to provide the DID with a fragment
25//! part. The result is a DID URL, which can be parsed and stored using
26//! [`DIDURL`] and [`DIDURLBuf`].
27//!
28//! ```
29//! use ssi_dids::{DIDURL, DIDURLBuf};
30//!
31//! // Create a `&DIDURL` from a `&str`.
32//! let did_url = DIDURL::new("did:web:w3c-ccg.github.io:user:alice#key").unwrap();
33//!
34//! // Create a `DIDBuf` from a `String`.
35//! let owned_did_url = DIDURLBuf::from_string("did:web:w3c-ccg.github.io:user:bob#key".to_owned()).unwrap();
36//! ```
37//!
38//! Note that a DID URL, with a fragment, is not a valid DID.
39//!
40//! # DID document resolution
41//!
42//! DID resolution is the process of retrieving the DID document associated with
43//! a specific DID.
44//! A DID document is a JSON-LD formatted file that contains crucial information
45//! needed to interact with the DID, such as verification methods containing the
46//! user's public keys, and service endpoints. Here is an example DID document:
47//! ```json
48//! {
49//!   "@context": [
50//!     "https://www.w3.org/ns/did/v1",
51//!     "https://w3id.org/security/suites/jws-2020/v1"
52//!   ],
53//!   "id": "did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9",
54//!   "verificationMethod": [
55//!     {
56//!       "id": "did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0",
57//!       "type": "JsonWebKey2020",
58//!       "controller": "did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9",
59//!       "publicKeyJwk": {
60//!         "crv": "P-256",
61//!         "kty": "EC",
62//!         "x": "acbIQiuMs3i8_uszEjJ2tpTtRM4EU3yz91PH6CdH2V0",
63//!         "y": "_KcyLj9vWMptnmKtm46GqDz8wf74I5LKgrl2GzH3nSE"
64//!       }
65//!     }
66//!   ],
67//!   "assertionMethod": ["did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0"],
68//!   "authentication": ["did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0"],
69//!   "capabilityInvocation": ["did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0"],
70//!   "capabilityDelegation": ["did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0"],
71//!   "keyAgreement": ["did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0"]
72//! }
73//! ```
74//!
75//! DID documents are represented using the [`Document`] type and can be
76//! resolved from a DID using any implementation of the [`DIDResolver`] trait.
77//! The [`AnyDidMethod`] type is provided as a default implementation for
78//! [`DIDResolver`] that supports various *DID methods* (see below).
79//!
80//! ```
81//! # #[async_std::main] async fn main() {
82//! use ssi_dids::{DID, AnyDidMethod, DIDResolver};
83//!
84//! // Create a DID.
85//! let did = DID::new("did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9").unwrap();
86//!
87//! // Setup the DID resolver.
88//! let resolver = AnyDidMethod::default();
89//!
90//! // Resolve the DID document (equal to the example document above).
91//! let document = resolver.resolve(did).await.unwrap().document;
92//!
93//! // Extract the first verification method.
94//! let vm = document.verification_method.first().unwrap();
95//! # }
96//! ```
97//!
98//! Instead of resolving a DID document and extracting verification methods
99//! manually, you can use the `dereference` method to resolve a DID URL:
100//! ```
101//! # #[async_std::main] async fn main() {
102//! use ssi_dids::{DIDURL, AnyDidMethod, DIDResolver};
103//!
104//! // Create a DID URL with the fragment `#0` referencing a verification method.
105//! let did_url = DIDURL::new("did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0").unwrap();
106//!
107//! // Setup the DID resolver.
108//! let resolver = AnyDidMethod::default();
109//!
110//! // Dereference the verification method.
111//! let vm = resolver
112//!   .dereference(did_url)
113//!   .await
114//!   .unwrap()
115//!   .content
116//!   .into_verification_method()
117//!   .unwrap();
118//! # }
119//! ```
120//!
121//! # DID methods
122//!
123//! A key component of the DID system is the concept of DID methods. A DID
124//! method defines how a specific type of DID is created, resolved, updated,
125//! and deactivated on a particular decentralized network or ledger.
126//! Each DID method corresponds to a unique identifier format and a set of
127//! operations that can be performed on the associated DIDs.
128//! The general syntax of DIDs depends on the method used:
129//!
130//! ```text
131//! did:method:method-specific-id
132//! ```
133//!
134//! There exists various DID methods, each defined by their own specification.
135//! In this library, methods are defining by implementing the [`DIDMethod`]
136//! trait. Implementations are provided for the following methods:
137//! - [`did:key`](https://w3c-ccg.github.io/did-method-key/): for static
138//!   cryptographic keys, implemented by [`DIDKey`].
139//! - [`did:jwk`](https://github.com/quartzjer/did-jwk/blob/main/spec.md):
140//!   for [Json Web Keys (JWK)](https://datatracker.ietf.org/doc/html/rfc7517)
141//!   implemented by [`DIDJWK`].
142//! - [`did:web`](https://w3c-ccg.github.io/did-method-web/): for web-hosted DID
143//!   documents, implemented by [`DIDWeb`].
144//! - [`did:pkh`](https://github.com/w3c-ccg/did-pkh/blob/main/did-pkh-method-draft.md):
145//!   implemented by [`DIDPKH`].
146//! - [`did:ethr`](https://github.com/decentralized-identity/ethr-did-resolver/):
147//!   implemented by [`DIDEthr`].
148//! - [`did:ion`](https://identity.foundation/sidetree/spec/v1.0.0/#did-uri-composition):
149//!   implemented by [`DIDION`].
150//! - [`did:tz`](https://github.com/spruceid/did-tezos/): implemented by
151//!   [`DIDTz`].
152//!
153//! The [`AnyDidMethod`] regroups all those methods into one [`DIDResolver`]
154//! implementation.
155//!
156//! DID method types can also be used to generate fresh DID URLs:
157//! ```
158//! use ssi_jwk::JWK;
159//! use ssi_dids::DIDJWK;
160//!
161//! /// Generate a new JWK.
162//! let jwk = JWK::generate_p256();
163//!
164//! /// Generate a DID URL out of our JWK URL.
165//! let did_url = DIDJWK::generate_url(&jwk);
166//! ```
167
168// Re-export core definitions.
169pub use ssi_dids_core::*;
170
171// Re-export DID methods implementations.
172pub use did_ethr as ethr;
173pub use did_ion as ion;
174pub use did_jwk as jwk;
175pub use did_method_key as key;
176pub use did_pkh as pkh;
177pub use did_tz as tz;
178pub use did_web as web;
179
180pub use ethr::DIDEthr;
181pub use ion::DIDION;
182pub use jwk::DIDJWK;
183pub use key::DIDKey;
184pub use pkh::DIDPKH;
185pub use tz::DIDTz;
186pub use web::DIDWeb;
187
188/// DID generation error.
189///
190/// Error raised by the [`AnyDidMethod::generate`] method.
191#[derive(Debug, thiserror::Error)]
192pub enum GenerateError {
193    #[error(transparent)]
194    Ethr(ssi_jwk::Error),
195
196    #[error(transparent)]
197    Key(key::GenerateError),
198
199    #[error(transparent)]
200    Pkh(pkh::GenerateError),
201
202    #[error(transparent)]
203    Tz(ssi_jwk::Error),
204
205    #[error("unsupported method pattern `{0}`")]
206    UnsupportedMethodPattern(String),
207}
208
209/// DID resolver for any known DID method.
210///
211/// This type regroups all the [`DIDMethod`] implementations provided by `ssi`
212/// into a single [`DIDResolver`] trait implementation.
213///
214/// # Supported methods
215///
216/// Here is the list of DID methods currently supported by this resolver:
217/// - [`did:key`](https://w3c-ccg.github.io/did-method-key/): for static
218///   cryptographic keys, implemented by [`DIDKey`].
219/// - [`did:jwk`](https://github.com/quartzjer/did-jwk/blob/main/spec.md):
220///   for [Json Web Keys (JWK)](https://datatracker.ietf.org/doc/html/rfc7517)
221///   implemented by [`DIDJWK`].
222/// - [`did:web`](https://w3c-ccg.github.io/did-method-web/): for web-hosted DID
223///   documents, implemented by [`DIDWeb`].
224/// - [`did:pkh`](https://github.com/w3c-ccg/did-pkh/blob/main/did-pkh-method-draft.md):
225///   implemented by [`DIDPKH`].
226/// - [`did:ethr`](https://github.com/decentralized-identity/ethr-did-resolver/):
227///   implemented by [`DIDEthr`].
228/// - [`did:ion`](https://identity.foundation/sidetree/spec/v1.0.0/#did-uri-composition):
229///   implemented by [`DIDION`].
230/// - [`did:tz`](https://github.com/spruceid/did-tezos/): implemented by
231///   [`DIDTz`].
232#[derive(Default, Clone)]
233pub struct AnyDidMethod {
234    /// `did:ion` method configuration.
235    ion: DIDION,
236
237    /// `did:tz` method configuration.
238    tz: DIDTz,
239}
240
241impl AnyDidMethod {
242    /// Creates a new resolver using the following `did:ion` and `did:tz` method
243    /// resolvers.
244    ///
245    /// Use the [`Default`] implementation if you don't want to configure
246    /// `DIDION` and `DIDTz` yourself.
247    pub fn new(ion: DIDION, tz: DIDTz) -> Self {
248        Self { ion, tz }
249    }
250
251    /// Generate a new DID from a JWK.
252    ///
253    /// The `method_pattern` argument is used to select and configure the DID
254    /// method. Accepted patterns are
255    /// - `key` to generate a `did:key` DID,
256    /// - `jwk` to generate a `did:jwk` DID,
257    /// - `ethr` to generate a `did:ethr` DID,
258    /// - `pkh:{pkh_name}` to generate a `did:pkh` DID, where `{pkh_name}`
259    ///    should be replaced by the network id as specified by the
260    ///    [`DIDPKH::generate`] function.
261    /// - `tz` to generate a `did:tz` DID.
262    ///
263    /// # Example
264    ///
265    /// ```
266    /// use ssi_jwk::JWK;
267    /// use ssi_dids::AnyDidMethod;
268    ///
269    /// // Create a DID resolver.
270    /// let resolver = AnyDidMethod::default();
271    ///
272    /// // Create a JWK.
273    /// let jwk = JWK::generate_p256();
274    ///
275    /// // Generate a `did:jwk` DID for this JWK:
276    /// let did = resolver.generate(&jwk, "jwk").unwrap();
277    /// ```
278    pub fn generate(
279        &self,
280        key: &ssi_jwk::JWK,
281        method_pattern: &str,
282    ) -> Result<DIDBuf, GenerateError> {
283        match method_pattern
284            .split_once(':')
285            .map(|(m, p)| (m, Some(p)))
286            .unwrap_or((method_pattern, None))
287        {
288            ("ethr", None) => ethr::DIDEthr::generate(key).map_err(GenerateError::Ethr),
289            ("jwk", None) => Ok(jwk::DIDJWK::generate(key)),
290            ("key", None) => key::DIDKey::generate(key).map_err(GenerateError::Key),
291            ("pkh", Some(pkh_name)) => {
292                pkh::DIDPKH::generate(key, pkh_name).map_err(GenerateError::Pkh)
293            }
294            ("tz", None) => self.tz.generate(key).map_err(GenerateError::Tz),
295            _ => Err(GenerateError::UnsupportedMethodPattern(
296                method_pattern.to_string(),
297            )),
298        }
299    }
300}
301
302impl DIDResolver for AnyDidMethod {
303    async fn resolve_representation<'a>(
304        &'a self,
305        did: &'a DID,
306        options: resolution::Options,
307    ) -> Result<resolution::Output<Vec<u8>>, resolution::Error> {
308        match did.method_name() {
309            "ethr" => {
310                ethr::DIDEthr
311                    .resolve_method_representation(did.method_specific_id(), options)
312                    .await
313            }
314            "ion" => {
315                self.ion
316                    .resolve_method_representation(did.method_specific_id(), options)
317                    .await
318            }
319            "jwk" => {
320                DIDJWK
321                    .resolve_method_representation(did.method_specific_id(), options)
322                    .await
323            }
324            "key" => {
325                DIDKey
326                    .resolve_method_representation(did.method_specific_id(), options)
327                    .await
328            }
329            "pkh" => {
330                DIDPKH
331                    .resolve_method_representation(did.method_specific_id(), options)
332                    .await
333            }
334            "tz" => {
335                self.tz
336                    .resolve_method_representation(did.method_specific_id(), options)
337                    .await
338            }
339            "web" => {
340                DIDWeb
341                    .resolve_method_representation(did.method_specific_id(), options)
342                    .await
343            }
344            m => Err(resolution::Error::MethodNotSupported(m.to_owned())),
345        }
346    }
347}