did_ion/sidetree/
client.rs

1use iref::UriBuf;
2use serde_json::Value;
3use ssi_dids_core::{
4    registration::{
5        DIDCreate, DIDDeactivate, DIDMethodRegistry, DIDRecover, DIDTransactionCreationError,
6        DIDTransactionError, DIDTransactionKind, DIDUpdate,
7    },
8    resolution::{self, DIDMethodResolver},
9    DIDMethod,
10};
11use ssi_jwk::JWK;
12
13use super::{
14    DIDStatePatch, DIDSuffix, DocumentState, HTTPSidetreeDIDResolver, Operation, PublicKeyEntry,
15    PublicKeyJwk, Sidetree, SidetreeAPIError, SidetreeDID,
16};
17
18#[derive(Debug, thiserror::Error)]
19#[error("missing Sidetree REST API endpoint")]
20pub struct MissingSidetreeApiEndpoint;
21
22/// Sidetree DID Method client implementation
23#[derive(Default, Clone)]
24pub struct SidetreeClient<S: Sidetree> {
25    pub resolver: Option<HTTPSidetreeDIDResolver<S>>,
26    pub endpoint: Option<UriBuf>,
27}
28
29impl<S: Sidetree> SidetreeClient<S> {
30    pub fn new(api_url_opt: Option<UriBuf>) -> Self {
31        let resolver_opt = api_url_opt
32            .as_deref()
33            .map(|url| HTTPSidetreeDIDResolver::new(url));
34        Self {
35            endpoint: api_url_opt,
36            resolver: resolver_opt,
37        }
38    }
39
40    // fn did_from_transaction(&self, tx: DIDTransaction) -> Result<String, OperationFromTransactionError> {
41    //     let op = Operation::from_transaction(tx.value)?;
42
43    //     let did: SidetreeDID<S> = match op {
44    //         Operation::Create(create_op) => create_op.to_sidetree_did(),
45    //         Operation::Update(update_op) => SidetreeDID::Short {
46    //             did_suffix: update_op.did_suffix,
47    //         },
48    //         Operation::Recover(recover_op) => SidetreeDID::Short {
49    //             did_suffix: recover_op.did_suffix,
50    //         },
51    //         Operation::Deactivate(deactivate_op) => SidetreeDID::Short {
52    //             did_suffix: deactivate_op.did_suffix,
53    //         },
54    //     };
55
56    //     Ok(did.to_string())
57    // }
58}
59
60impl<S: Sidetree> DIDMethod for SidetreeClient<S> {
61    const DID_METHOD_NAME: &'static str = S::METHOD;
62}
63
64impl<S: Sidetree> DIDMethodResolver for SidetreeClient<S> {
65    async fn resolve_method_representation<'a>(
66        &'a self,
67        method_specific_id: &'a str,
68        options: resolution::Options,
69    ) -> Result<resolution::Output<Vec<u8>>, resolution::Error> {
70        match &self.resolver {
71            Some(res) => {
72                res.resolve_method_representation(method_specific_id, options)
73                    .await
74            }
75            None => Err(resolution::Error::internal(MissingSidetreeApiEndpoint)),
76        }
77    }
78}
79
80#[derive(Debug, thiserror::Error)]
81pub enum TransactionSubmissionFailed {
82    #[error("HTTP client creation failed: {0}")]
83    HttpClient(reqwest::Error),
84
85    #[error("unable to send HTTP request: {0}")]
86    HttpRequest(reqwest::Error),
87
88    #[error("server returned an error: {0}")]
89    HttpServerApi(SidetreeAPIError),
90
91    #[error("server returned an error: {0}")]
92    HttpServer(reqwest::Error),
93
94    #[error("unable to read HTTP response: {0}")]
95    HttpResponse(reqwest::Error),
96
97    #[error("unable to parse HTTP response as JSON")]
98    Json,
99}
100
101impl<S: Sidetree> DIDMethodRegistry for SidetreeClient<S> {
102    /// <https://identity.foundation/sidetree/api/#sidetree-operations>
103    async fn submit_transaction(&self, tx: Value) -> Result<Value, DIDTransactionError> {
104        let op = Operation::from_transaction(tx).map_err(DIDTransactionError::invalid)?;
105        let endpoint = self
106            .endpoint
107            .as_ref()
108            .ok_or_else(|| DIDTransactionError::invalid(MissingSidetreeApiEndpoint))?;
109        let url = format!("{}operations/", endpoint);
110        let client = reqwest::Client::builder()
111            .build()
112            .map_err(|e| DIDTransactionError::failed(TransactionSubmissionFailed::HttpClient(e)))?;
113        let resp = client
114            .post(url)
115            .json(&op)
116            .header("Accept", "application/json")
117            .header("User-Agent", crate::USER_AGENT)
118            .send()
119            .await
120            .map_err(|e| {
121                DIDTransactionError::failed(TransactionSubmissionFailed::HttpRequest(e))
122            })?;
123        if resp.error_for_status_ref().is_err() {
124            let err: SidetreeAPIError = resp.json().await.map_err(|e| {
125                DIDTransactionError::failed(TransactionSubmissionFailed::HttpServer(e))
126            })?;
127            return Err(DIDTransactionError::failed(
128                TransactionSubmissionFailed::HttpServerApi(err),
129            ));
130        }
131        if resp.content_length() == Some(0) {
132            // Update operation may return empty body with 200 OK.
133            return Ok(Value::Null);
134        }
135        let bytes = resp.bytes().await.map_err(|e| {
136            DIDTransactionError::failed(TransactionSubmissionFailed::HttpResponse(e))
137        })?;
138        let resp_json: Value = serde_json::from_slice(&bytes)
139            .map_err(|_| DIDTransactionError::failed(TransactionSubmissionFailed::Json))?;
140        Ok(resp_json)
141    }
142
143    fn create(&self, create: DIDCreate) -> Result<Value, DIDTransactionCreationError> {
144        let DIDCreate {
145            recovery_key,
146            update_key,
147            verification_key,
148            options,
149        } = create;
150
151        if let Some(opt) = options.keys().next() {
152            return Err(DIDTransactionCreationError::UnsupportedOption {
153                operation: DIDTransactionKind::Create,
154                option: opt.clone(),
155            });
156        }
157
158        let (update_pk, recovery_pk, patches) =
159            new_did_state::<S>(update_key, recovery_key, verification_key)?;
160        let operation: Operation = S::create_existing(&update_pk, &recovery_pk, patches)?;
161        Ok(operation.into_transaction())
162    }
163
164    fn update(&self, update: DIDUpdate) -> Result<Value, DIDTransactionCreationError> {
165        let DIDUpdate {
166            did,
167            update_key,
168            new_update_key,
169            operation,
170            options,
171        } = update;
172        let did: SidetreeDID<S> = did.as_str().parse()?;
173
174        if let Some(opt) = options.keys().next() {
175            return Err(DIDTransactionCreationError::UnsupportedOption {
176                operation: DIDTransactionKind::Update,
177                option: opt.clone(),
178            });
179        }
180
181        let update_key = update_key.ok_or(DIDTransactionCreationError::MissingRequiredUpdateKey)?;
182        let new_update_key =
183            new_update_key.ok_or(DIDTransactionCreationError::MissingRequiredNewUpdateKey)?;
184        if !S::validate_key(&new_update_key) {
185            return Err(DIDTransactionCreationError::InvalidUpdateKey);
186        }
187        let new_update_pk = PublicKeyJwk::try_from(new_update_key.to_public())
188            .map_err(|_| DIDTransactionCreationError::InvalidUpdateKey)?;
189        let patches = vec![DIDStatePatch::try_from_with_did(operation, &did)?];
190        let did_suffix = DIDSuffix::from(did);
191        let update_operation = S::update(did_suffix, &update_key, &new_update_pk, patches)?;
192        Ok(Operation::Update(update_operation).into_transaction())
193    }
194
195    fn deactivate(&self, deactivate: DIDDeactivate) -> Result<Value, DIDTransactionCreationError> {
196        let DIDDeactivate { did, key, options } = deactivate;
197        let did: SidetreeDID<S> = did.as_str().parse()?;
198        let recovery_key = key.ok_or(DIDTransactionCreationError::MissingRequiredRecoveryKey)?;
199        if let Some(opt) = options.keys().next() {
200            return Err(DIDTransactionCreationError::UnsupportedOption {
201                operation: DIDTransactionKind::Deactivate,
202                option: opt.clone(),
203            });
204        }
205        let did_suffix = DIDSuffix::from(did);
206        let deactivate_operation = <S as Sidetree>::deactivate(did_suffix, recovery_key)?;
207        Ok(Operation::Deactivate(deactivate_operation).into_transaction())
208    }
209
210    fn recover(&self, recover: DIDRecover) -> Result<Value, DIDTransactionCreationError> {
211        let DIDRecover {
212            did,
213            recovery_key,
214            new_recovery_key,
215            new_update_key,
216            new_verification_key,
217            options,
218        } = recover;
219        let did: SidetreeDID<S> = did.as_str().parse()?;
220        let did_suffix = DIDSuffix::from(did);
221        if let Some(opt) = options.keys().next() {
222            return Err(DIDTransactionCreationError::UnsupportedOption {
223                operation: DIDTransactionKind::Recover,
224                option: opt.clone(),
225            });
226        }
227        let recovery_key =
228            recovery_key.ok_or(DIDTransactionCreationError::MissingRequiredRecoveryKey)?;
229        let (new_update_pk, new_recovery_pk, patches) =
230            new_did_state::<S>(new_update_key, new_recovery_key, new_verification_key)?;
231        let operation = S::recover_existing(
232            did_suffix,
233            &recovery_key,
234            &new_update_pk,
235            &new_recovery_pk,
236            patches,
237        )?;
238        Ok(operation.into_transaction())
239    }
240}
241
242fn new_did_state<S: Sidetree>(
243    update_key: Option<JWK>,
244    recovery_key: Option<JWK>,
245    verification_key: Option<JWK>,
246) -> Result<(PublicKeyJwk, PublicKeyJwk, Vec<DIDStatePatch>), DIDTransactionCreationError> {
247    let update_key = update_key.ok_or(DIDTransactionCreationError::MissingRequiredUpdateKey)?;
248    if !S::validate_key(&update_key) {
249        return Err(DIDTransactionCreationError::InvalidUpdateKey);
250    }
251    let update_pk = PublicKeyJwk::try_from(update_key.to_public())
252        .map_err(|_| DIDTransactionCreationError::InvalidUpdateKey)?;
253    let recovery_key =
254        recovery_key.ok_or(DIDTransactionCreationError::MissingRequiredRecoveryKey)?;
255    if !S::validate_key(&recovery_key) {
256        return Err(DIDTransactionCreationError::InvalidRecoveryKey);
257    }
258    let recovery_pk = PublicKeyJwk::try_from(recovery_key.to_public())
259        .map_err(|_| DIDTransactionCreationError::InvalidRecoveryKey)?;
260    let mut patches = vec![];
261    if let Some(verification_key) = verification_key {
262        let public_key_entry = PublicKeyEntry::try_from(verification_key)
263            .map_err(|_| DIDTransactionCreationError::InvalidVerificationKey)?;
264        let document = DocumentState {
265            public_keys: Some(vec![public_key_entry]),
266            services: None,
267        };
268        let patch = DIDStatePatch::Replace { document };
269        patches.push(patch);
270    };
271    Ok((update_pk, recovery_pk, patches))
272}