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#[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 }
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 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 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}