Skip to main content

ssi_dids_core/registration/
mod.rs

1//! DID Registration & Recovery.
2//!
3//! See: <https://identity.foundation/did-registration>
4
5use core::fmt;
6use std::{borrow::Cow, collections::HashMap};
7
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use ssi_jwk::JWK;
11use ssi_verification_methods_core::ProofPurpose;
12
13use crate::{
14    document::{self, DIDVerificationMethod, Service},
15    DIDBuf, DIDMethod, DIDURLBuf,
16};
17
18/// DID Document Operation
19///
20/// This should represent [didDocument][dd] and [didDocumentOperation][ddo] specified by DID
21/// Registration.
22///
23/// [dd]: https://identity.foundation/did-registration/#diddocumentoperation
24/// [ddo]: https://identity.foundation/did-registration/#diddocument
25#[derive(Debug, Serialize, Deserialize, Clone)]
26#[serde(tag = "didDocumentOperation", content = "didDocument")]
27#[serde(rename_all = "camelCase")]
28#[allow(clippy::large_enum_variant)]
29pub enum DIDDocumentOperation {
30    /// Set the contents of the DID document
31    ///
32    /// setDidDocument operation defined by DIF DID Registration
33    SetDidDocument(document::Represented),
34
35    /// Add properties to the DID document
36    ///
37    /// addToDidDocument operation defined by DIF DID Registration
38    AddToDidDocument(HashMap<String, Value>),
39
40    /// Remove properties from the DID document
41    ///
42    /// removeFromDidDocument operation defined by DIF Registration
43    RemoveFromDidDocument(Vec<String>),
44
45    /// Add or update a verification method in the DID document
46    SetVerificationMethod {
47        vmm: DIDVerificationMethod,
48        purposes: Vec<ProofPurpose>,
49    },
50
51    /// Add or update a service map in the DID document
52    SetService(Service),
53
54    /// Remove a verification method in the DID document
55    RemoveVerificationMethod(DIDURLBuf),
56
57    /// Add or update a service map in the DID document
58    RemoveService(DIDURLBuf),
59}
60
61#[derive(Debug)]
62pub enum DIDDocumentOperationKind {
63    SetDidDocument,
64    AddToDidDocument,
65    RemoveFromDidDocument,
66    SetVerificationMethod,
67    SetService,
68    RemoveVerificationMethod,
69    RemoveService,
70}
71
72impl DIDDocumentOperationKind {
73    pub fn name(&self) -> &'static str {
74        match self {
75            Self::SetDidDocument => "setDidDocument",
76            Self::AddToDidDocument => "addToDidDocument",
77            Self::RemoveFromDidDocument => "removeFromDidDocument",
78            Self::SetVerificationMethod => "setVerificationMethod",
79            Self::SetService => "setService",
80            Self::RemoveVerificationMethod => "removeVerificationMethod",
81            Self::RemoveService => "removeService",
82        }
83    }
84}
85
86impl fmt::Display for DIDDocumentOperationKind {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        self.name().fmt(f)
89    }
90}
91
92/// DID Create Operation.
93///
94/// <https://identity.foundation/did-registration/#create>
95pub struct DIDCreate {
96    pub update_key: Option<JWK>,
97    pub recovery_key: Option<JWK>,
98    pub verification_key: Option<JWK>,
99    pub options: HashMap<String, Value>,
100}
101
102/// DID Update Operation.
103///
104/// <https://identity.foundation/did-registration/#update>
105pub struct DIDUpdate {
106    pub did: DIDBuf,
107    pub update_key: Option<JWK>,
108    pub new_update_key: Option<JWK>,
109    pub operation: DIDDocumentOperation,
110    pub options: HashMap<String, Value>,
111}
112
113/// DID Deactivate Operation.
114///
115/// <https://identity.foundation/did-registration/#deactivate>
116pub struct DIDDeactivate {
117    pub did: DIDBuf,
118    pub key: Option<JWK>,
119    pub options: HashMap<String, Value>,
120}
121
122/// DID Recover Operation.
123///
124/// <https://www.w3.org/TR/did-core/#did-recovery>
125pub struct DIDRecover {
126    pub did: DIDBuf,
127    pub recovery_key: Option<JWK>,
128    pub new_update_key: Option<JWK>,
129    pub new_recovery_key: Option<JWK>,
130    pub new_verification_key: Option<JWK>,
131    pub options: HashMap<String, Value>,
132}
133
134#[derive(Debug, thiserror::Error)]
135pub enum DIDTransactionError {
136    #[error("unsupported DID method `{0}`")]
137    UnsupportedDIDMethod(String),
138
139    #[error("invalid transaction: {0}")]
140    InvalidTransaction(String),
141
142    #[error("transaction failed: {0}")]
143    Failed(String),
144}
145
146impl DIDTransactionError {
147    pub fn invalid(error: impl ToString) -> Self {
148        Self::InvalidTransaction(error.to_string())
149    }
150
151    pub fn failed(error: impl ToString) -> Self {
152        Self::Failed(error.to_string())
153    }
154}
155
156#[derive(Debug, thiserror::Error)]
157pub enum DIDTransactionCreationError {
158    #[error("unimplemented transaction `{0}`")]
159    UnimplementedTransaction(DIDTransactionKind),
160
161    #[error("unimplemented DID document operation: {0}")]
162    UnimplementedDocumentOperation(DIDDocumentOperationKind),
163
164    #[error("unsupported DID method `{0}`")]
165    UnsupportedDIDMethod(String),
166
167    #[error("unsupported option `{option}` for {operation} operation")]
168    UnsupportedOption {
169        operation: DIDTransactionKind,
170        option: String,
171    },
172
173    #[error("unsupported service property")]
174    UnsupportedServiceProperty,
175
176    #[error("missing required update key")]
177    MissingRequiredUpdateKey,
178
179    #[error("missing required new update key")]
180    MissingRequiredNewUpdateKey,
181
182    #[error("missing required recovery key")]
183    MissingRequiredRecoveryKey,
184
185    #[error("invalid update key")]
186    InvalidUpdateKey,
187
188    #[error("invalid recovery key")]
189    InvalidRecoveryKey,
190
191    #[error("invalid verification key")]
192    InvalidVerificationKey,
193
194    #[error("same update and recovery keys")]
195    SameUpdateAndRecoveryKeys,
196
197    #[error("update key unchanged")]
198    UpdateKeyUnchanged,
199
200    #[error("recovery key unchanged")]
201    RecoveryKeyUnchanged,
202
203    #[error("key generation failed")]
204    KeyGenerationFailed,
205
206    #[error("invalid DID")]
207    InvalidDID,
208
209    #[error("invalid DID URL")]
210    InvalidDIDURL,
211
212    #[error("invalid verification method")]
213    InvalidVerificationMethod,
214
215    #[error("signature failed")]
216    SignatureFailed,
217
218    #[error("missing service endpoint")]
219    MissingServiceEndpoint,
220
221    #[error("ambiguous service endpoint")]
222    AmbiguousServiceEndpoint,
223
224    #[error("ambiguous service type")]
225    AmbiguousServiceType,
226
227    #[error("unsupported service: {reason}")]
228    UnsupportedService { reason: Cow<'static, str> },
229
230    #[error("{0}")]
231    Internal(String),
232}
233
234impl DIDTransactionCreationError {
235    pub fn internal(e: impl ToString) -> Self {
236        Self::Internal(e.to_string())
237    }
238}
239
240#[derive(Debug)]
241pub enum DIDTransactionKind {
242    Create,
243    Update,
244    Deactivate,
245    Recover,
246}
247
248impl DIDTransactionKind {
249    pub fn name(&self) -> &'static str {
250        match self {
251            Self::Create => "create",
252            Self::Update => "update",
253            Self::Deactivate => "deactivate",
254            Self::Recover => "recover",
255        }
256    }
257}
258
259impl fmt::Display for DIDTransactionKind {
260    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261        self.name().fmt(f)
262    }
263}
264
265pub struct DIDTransaction {
266    /// DID method name.
267    pub did_method: String,
268
269    /// Method-specific transaction data.
270    pub value: Value,
271}
272
273impl DIDTransaction {
274    pub fn new(did_method: String, value: Value) -> Self {
275        Self { did_method, value }
276    }
277}
278
279pub trait DIDRegistry {
280    #[allow(async_fn_in_trait)]
281    async fn submit_transaction(
282        &self,
283        transaction: DIDTransaction,
284    ) -> Result<Value, DIDTransactionError>;
285
286    /// Create DID.
287    fn create(
288        &self,
289        _method: &str,
290        _create: DIDCreate,
291    ) -> Result<DIDTransaction, DIDTransactionCreationError> {
292        Err(DIDTransactionCreationError::UnimplementedTransaction(
293            DIDTransactionKind::Create,
294        ))
295    }
296
297    /// Update DID.
298    fn update(&self, _update: DIDUpdate) -> Result<DIDTransaction, DIDTransactionCreationError> {
299        Err(DIDTransactionCreationError::UnimplementedTransaction(
300            DIDTransactionKind::Update,
301        ))
302    }
303
304    /// Deactivate DID.
305    fn deactivate(
306        &self,
307        _deactivate: DIDDeactivate,
308    ) -> Result<DIDTransaction, DIDTransactionCreationError> {
309        Err(DIDTransactionCreationError::UnimplementedTransaction(
310            DIDTransactionKind::Deactivate,
311        ))
312    }
313
314    /// Recover DID.
315    fn recover(&self, _recover: DIDRecover) -> Result<DIDTransaction, DIDTransactionCreationError> {
316        Err(DIDTransactionCreationError::UnimplementedTransaction(
317            DIDTransactionKind::Recover,
318        ))
319    }
320}
321
322pub trait DIDMethodRegistry: DIDMethod {
323    /// Submit a transaction.
324    #[allow(async_fn_in_trait)]
325    async fn submit_transaction(&self, transaction: Value) -> Result<Value, DIDTransactionError>;
326
327    /// Create DID.
328    fn create(&self, _create: DIDCreate) -> Result<Value, DIDTransactionCreationError> {
329        Err(DIDTransactionCreationError::UnimplementedTransaction(
330            DIDTransactionKind::Create,
331        ))
332    }
333
334    /// Update DID.
335    fn update(&self, _update: DIDUpdate) -> Result<Value, DIDTransactionCreationError> {
336        Err(DIDTransactionCreationError::UnimplementedTransaction(
337            DIDTransactionKind::Update,
338        ))
339    }
340
341    /// Deactivate DID.
342    fn deactivate(&self, _deactivate: DIDDeactivate) -> Result<Value, DIDTransactionCreationError> {
343        Err(DIDTransactionCreationError::UnimplementedTransaction(
344            DIDTransactionKind::Deactivate,
345        ))
346    }
347
348    /// Recover DID.
349    fn recover(&self, _recover: DIDRecover) -> Result<Value, DIDTransactionCreationError> {
350        Err(DIDTransactionCreationError::UnimplementedTransaction(
351            DIDTransactionKind::Recover,
352        ))
353    }
354}
355
356impl<M: DIDMethodRegistry> DIDRegistry for M {
357    #[allow(async_fn_in_trait)]
358    async fn submit_transaction(
359        &self,
360        transaction: DIDTransaction,
361    ) -> Result<Value, DIDTransactionError> {
362        if transaction.did_method == Self::DID_METHOD_NAME {
363            DIDMethodRegistry::submit_transaction(self, transaction.value).await
364        } else {
365            Err(DIDTransactionError::UnsupportedDIDMethod(
366                transaction.did_method,
367            ))
368        }
369    }
370
371    /// Create DID.
372    fn create(
373        &self,
374        method: &str,
375        create: DIDCreate,
376    ) -> Result<DIDTransaction, DIDTransactionCreationError> {
377        if method == Self::DID_METHOD_NAME {
378            Ok(DIDTransaction::new(
379                Self::DID_METHOD_NAME.to_owned(),
380                DIDMethodRegistry::create(self, create)?,
381            ))
382        } else {
383            Err(DIDTransactionCreationError::UnsupportedDIDMethod(
384                method.to_owned(),
385            ))
386        }
387    }
388
389    /// Update DID.
390    fn update(&self, update: DIDUpdate) -> Result<DIDTransaction, DIDTransactionCreationError> {
391        if update.did.method_name() == Self::DID_METHOD_NAME {
392            Ok(DIDTransaction::new(
393                Self::DID_METHOD_NAME.to_owned(),
394                DIDMethodRegistry::update(self, update)?,
395            ))
396        } else {
397            Err(DIDTransactionCreationError::UnsupportedDIDMethod(
398                update.did.method_name().to_owned(),
399            ))
400        }
401    }
402
403    /// Deactivate DID.
404    fn deactivate(
405        &self,
406        deactivate: DIDDeactivate,
407    ) -> Result<DIDTransaction, DIDTransactionCreationError> {
408        if deactivate.did.method_name() == Self::DID_METHOD_NAME {
409            Ok(DIDTransaction::new(
410                Self::DID_METHOD_NAME.to_owned(),
411                DIDMethodRegistry::deactivate(self, deactivate)?,
412            ))
413        } else {
414            Err(DIDTransactionCreationError::UnsupportedDIDMethod(
415                deactivate.did.method_name().to_owned(),
416            ))
417        }
418    }
419
420    /// Recover DID.
421    fn recover(&self, _recover: DIDRecover) -> Result<DIDTransaction, DIDTransactionCreationError> {
422        Err(DIDTransactionCreationError::UnimplementedTransaction(
423            DIDTransactionKind::Recover,
424        ))
425    }
426}