trustchain_ion/
resolver.rs

1//! DID resolution and `DIDResolver` implementation.
2use async_trait::async_trait;
3use ipfs_api_backend_hyper::IpfsClient;
4use serde_json::Value;
5use ssi::did::{RelativeDIDURL, ServiceEndpoint, VerificationMethod, VerificationMethodMap};
6use ssi::did_resolve::DocumentMetadata;
7use ssi::one_or_many::OneOrMany;
8use ssi::{
9    did::Document,
10    did_resolve::{DIDResolver, ResolutionInputMetadata, ResolutionMetadata},
11};
12use std::collections::HashSet;
13use std::marker::PhantomData;
14use std::str::FromStr;
15use trustchain_core::resolver::{ResolverError, TrustchainResolver};
16
17use crate::utils::{decode_ipfs_content, query_ipfs};
18use crate::{FullClient, LightClient};
19use crate::{CONTROLLER_KEY, SERVICE_TYPE_IPFS_KEY};
20
21/// Struct for performing resolution from a sidetree server to generate
22/// Trustchain DID document and DID document metadata.
23pub struct HTTPTrustchainResolver<T: DIDResolver + Sync + Send, U = FullClient> {
24    pub wrapped_resolver: T,
25    pub ipfs_client: Option<IpfsClient>,
26    _marker: PhantomData<U>,
27}
28
29impl<T: DIDResolver + Sync + Send> HTTPTrustchainResolver<T, FullClient> {
30    /// Constructs a full client Trustchain resolver.
31    pub fn new(resolver: T) -> Self {
32        Self {
33            wrapped_resolver: resolver,
34            ipfs_client: Some(IpfsClient::default()),
35            _marker: PhantomData,
36        }
37    }
38    fn ipfs_client(&self) -> &IpfsClient {
39        self.ipfs_client.as_ref().unwrap()
40    }
41}
42
43impl<T: DIDResolver + Sync + Send> HTTPTrustchainResolver<T, LightClient> {
44    /// Constructs a light client Trustchain resolver.
45    pub fn new(resolver: T) -> Self {
46        Self {
47            wrapped_resolver: resolver,
48            ipfs_client: None,
49            _marker: PhantomData,
50        }
51    }
52}
53
54#[async_trait]
55impl<T> DIDResolver for HTTPTrustchainResolver<T, FullClient>
56where
57    T: DIDResolver + Sync + Send,
58{
59    async fn resolve(
60        &self,
61        did: &str,
62        input_metadata: &ResolutionInputMetadata,
63    ) -> (
64        ResolutionMetadata,
65        Option<Document>,
66        Option<DocumentMetadata>,
67    ) {
68        self.trustchain_resolve(did, input_metadata).await
69    }
70}
71
72#[async_trait]
73impl<T> DIDResolver for HTTPTrustchainResolver<T, LightClient>
74where
75    T: DIDResolver + Sync + Send,
76{
77    async fn resolve(
78        &self,
79        did: &str,
80        input_metadata: &ResolutionInputMetadata,
81    ) -> (
82        ResolutionMetadata,
83        Option<Document>,
84        Option<DocumentMetadata>,
85    ) {
86        self.trustchain_resolve(did, input_metadata).await
87    }
88}
89
90#[async_trait]
91impl<T> TrustchainResolver for HTTPTrustchainResolver<T, FullClient>
92where
93    T: DIDResolver + Sync + Send,
94{
95    fn wrapped_resolver(&self) -> &dyn DIDResolver {
96        &self.wrapped_resolver
97    }
98
99    async fn extended_transform(
100        &self,
101        (res_meta, doc, doc_meta): (
102            ResolutionMetadata,
103            Option<Document>,
104            Option<DocumentMetadata>,
105        ),
106    ) -> (
107        ResolutionMetadata,
108        Option<Document>,
109        Option<DocumentMetadata>,
110    ) {
111        // If a document and document metadata are returned, try to convert.
112        if let (Some(did_doc), Some(did_doc_meta)) = (doc, doc_meta) {
113            // Convert to trustchain-ion version.
114            let tc_result =
115                transform_as_result(res_meta, did_doc, did_doc_meta, self.ipfs_client()).await;
116            match tc_result {
117                // Map the tuple of non-option types to have tuple with optional document metadata.
118                Ok((tc_res_meta, tc_doc, tc_doc_meta)) => {
119                    (tc_res_meta, Some(tc_doc), Some(tc_doc_meta))
120                }
121                // If failed to convert, return the relevant error.
122                Err(err) => {
123                    let res_meta = ResolutionMetadata {
124                        error: Some(err.to_string()),
125                        content_type: None,
126                        property_set: None,
127                    };
128                    (res_meta, None, None)
129                }
130            }
131        } else {
132            // If doc or doc_meta None, return sidetree resolution as is.
133            (res_meta, None, None)
134        }
135    }
136}
137
138#[async_trait]
139impl<T> TrustchainResolver for HTTPTrustchainResolver<T, LightClient>
140where
141    T: DIDResolver + Sync + Send,
142{
143    fn wrapped_resolver(&self) -> &dyn DIDResolver {
144        &self.wrapped_resolver
145    }
146}
147
148/// Converts DID Document + Metadata to the Trustchain resolved format.
149async fn transform_as_result(
150    res_meta: ResolutionMetadata,
151    doc: Document,
152    doc_meta: DocumentMetadata,
153    ipfs_client: &IpfsClient,
154) -> Result<(ResolutionMetadata, Document, DocumentMetadata), ResolverError> {
155    Ok((res_meta, transform_doc(&doc, ipfs_client).await?, doc_meta))
156}
157
158async fn transform_doc(
159    doc: &Document,
160    ipfs_client: &IpfsClient,
161) -> Result<Document, ResolverError> {
162    // Clone the passed DID document.
163    let mut doc_clone = doc.clone();
164
165    let endpoints = ipfs_key_endpoints(doc);
166    if endpoints.is_empty() {
167        return Ok(doc_clone);
168    }
169
170    // Get the existing verification methods (public keys) in the DID document.
171    let mut verification_methods = match &doc.verification_method {
172        Some(x) => x.clone(),
173        None => vec![],
174    };
175
176    // Create set of verification method ids to check if candidates are already present
177    let verification_methods_ids: HashSet<String> = verification_methods
178        .iter()
179        .map(|vm| vm.get_id(&doc.id))
180        .collect();
181
182    // Add any public keys found on IPFS.
183    for endpoint in endpoints {
184        // Download the content of the corresponding CID
185        let ipfs_file = query_ipfs(endpoint.as_str(), ipfs_client)
186            .await
187            .map_err(|err| ResolverError::FailedToConvertToTrustchain(err.to_string()))?;
188
189        let mut json = decode_ipfs_content(&ipfs_file, false)
190            .map_err(|err| ResolverError::FailedToConvertToTrustchain(err.to_string()))?;
191
192        // Add the controller in the decoded IPFS content.
193        // TODO: We are only supporting one of the possible ways to express verification methods here.
194        json.as_object_mut()
195            .ok_or(ResolverError::FailedToConvertToTrustchain(String::from(
196                "Unsupported document verification_method, use Vec<VerificationMethod::VerificationMethodMap>.",
197            )))?
198            .insert(
199                CONTROLLER_KEY.to_owned(),
200                serde_json::Value::String(doc.id.to_owned()),
201            );
202
203        // Can deserialize into untagged enum VerificationMethod from VerificationMethodMap str
204        let mut new_vm_map: VerificationMethodMap = serde_json::from_str(&json.to_string())
205            .map_err(|err| ResolverError::FailedToConvertToTrustchain(err.to_string()))?;
206
207        // Transform public key id into RelativeDIDURL format.
208        if !new_vm_map.id.starts_with('#') {
209            new_vm_map.id.insert(0, '#');
210        }
211        // Create RelativeDIDURL verification method
212        let relative_did_url: &str = new_vm_map.id.as_ref();
213        let relative_did_url_vm = VerificationMethod::RelativeDIDURL(
214            RelativeDIDURL::from_str(relative_did_url)
215                .map_err(|err| ResolverError::FailedToConvertToTrustchain(err.to_string()))?,
216        );
217
218        // Continue if verification method is already present
219        if verification_methods_ids.contains(&relative_did_url_vm.get_id(&doc.id)) {
220            continue;
221        }
222
223        // Extract the verification method purposes
224        if let Some(extra_properties) = new_vm_map.property_set.as_mut() {
225            if let Some(purposes) = extra_properties.remove("purposes") {
226                let purposes_vec = purposes
227                    .as_array()
228                    .ok_or(ResolverError::FailedToConvertToTrustchain(String::from(
229                        "Expected public key 'purposes' to be a JSON Array.",
230                    )))?
231                    .to_vec();
232
233                // TODO: consider separate function to avoid repetition here.
234                // Propagate public key purposes to associated DID fields.
235                if purposes_vec.contains(&Value::String("authentication".to_string())) {
236                    if let Some(authentication) = &doc.authentication {
237                        let mut new_authentication = authentication.to_owned();
238                        new_authentication.push(relative_did_url_vm.clone());
239                        doc_clone.authentication = Some(new_authentication);
240                    } else {
241                        doc_clone.authentication = Some(vec![relative_did_url_vm.clone()])
242                    }
243                }
244                if purposes_vec.contains(&Value::String("assertionMethod".to_string())) {
245                    if let Some(assertion_method) = &doc.assertion_method {
246                        let mut new_assertion_method = assertion_method.to_owned();
247                        new_assertion_method.push(relative_did_url_vm.clone());
248                        doc_clone.assertion_method = Some(new_assertion_method);
249                    } else {
250                        doc_clone.assertion_method = Some(vec![relative_did_url_vm.clone()])
251                    }
252                }
253                if purposes_vec.contains(&Value::String("keyAgreement".to_string())) {
254                    if let Some(key_agreement) = &doc.key_agreement {
255                        let mut new_key_agreement = key_agreement.to_owned();
256                        new_key_agreement.push(relative_did_url_vm.clone());
257                        doc_clone.key_agreement = Some(new_key_agreement);
258                    } else {
259                        doc_clone.key_agreement = Some(vec![relative_did_url_vm.clone()])
260                    }
261                }
262                if purposes_vec.contains(&Value::String("capabilityInvocation".to_string())) {
263                    if let Some(capability_invocation) = &doc.capability_invocation {
264                        let mut new_capability_invocation = capability_invocation.to_owned();
265                        new_capability_invocation.push(relative_did_url_vm.clone());
266                        doc_clone.capability_invocation = Some(new_capability_invocation);
267                    } else {
268                        doc_clone.capability_invocation = Some(vec![relative_did_url_vm.clone()])
269                    }
270                }
271                if purposes_vec.contains(&Value::String("capabilityDelegation".to_string())) {
272                    if let Some(capability_delegation) = &doc.capability_delegation {
273                        let mut new_capability_delegation = capability_delegation.to_owned();
274                        new_capability_delegation.push(relative_did_url_vm.clone());
275                        doc_clone.capability_delegation = Some(new_capability_delegation);
276                    } else {
277                        doc_clone.capability_delegation = Some(vec![relative_did_url_vm.clone()])
278                    }
279                }
280            }
281        }
282
283        verification_methods.push(VerificationMethod::Map(new_vm_map));
284    }
285
286    // Update the verification methods in the DID document.
287    doc_clone.verification_method = Some(verification_methods.to_owned());
288    Ok(doc_clone)
289}
290
291fn ipfs_key_endpoints(doc: &Document) -> Vec<String> {
292    let services = &doc.service;
293    if services.is_none() {
294        return vec![];
295    }
296    services
297        .as_ref()
298        .unwrap()
299        .iter()
300        .filter(|s| s.type_.to_single().is_some())
301        .filter_map(|s| {
302            if s.type_.to_single().unwrap().eq(SERVICE_TYPE_IPFS_KEY) {
303                match s.service_endpoint {
304                    Some(OneOrMany::One(ServiceEndpoint::URI(ref uri))) => Some(uri.to_owned()),
305                    _ => None,
306                }
307            } else {
308                None
309            }
310        })
311        .collect()
312}
313
314#[cfg(test)]
315mod tests {
316    use crate::data::{TEST_DOCUMENT_IPFS_KEY, TEST_IPFS_KEY_VM_JSON};
317
318    use super::*;
319
320    #[test]
321    fn test_ipfs_key_endpoints() {
322        let doc: Document = serde_json::from_str(TEST_DOCUMENT_IPFS_KEY).unwrap();
323        let result = ipfs_key_endpoints(&doc);
324
325        assert_eq!(
326            vec!("QmNqvEP6qmRLQ6aGz5G8fKTV7BcaBoq8gdCD5xY8PZ33aD"),
327            result
328        );
329    }
330
331    #[tokio::test]
332    #[ignore = "Integration test requires IPFS"]
333    async fn test_transform_doc() {
334        let doc: Document = serde_json::from_str(TEST_DOCUMENT_IPFS_KEY).unwrap();
335        let ipfs_client = IpfsClient::default();
336        let result = transform_doc(&doc, &ipfs_client).await.unwrap();
337
338        // Check the IPFS key is in the transformed DID doc verification methods.
339        assert!(result.verification_method.unwrap().into_iter().any(|vm| {
340            match vm {
341                VerificationMethod::Map(map) => {
342                    map.id.eq("#YGmbDaADvTGg3wopszo23Uqcgr3rNQY6njibaO9_QF4")
343                }
344                _ => false,
345            }
346        }));
347    }
348
349    #[test]
350    fn test_verification_method_deserialisation() {
351        let mut json: serde_json::Value = serde_json::from_str(TEST_IPFS_KEY_VM_JSON).unwrap();
352
353        json.as_object_mut()
354            .ok_or(ResolverError::FailedToConvertToTrustchain(String::from(
355                "Verification Method Map missing keys.",
356            )))
357            .unwrap()
358            .insert(
359                CONTROLLER_KEY.to_owned(),
360                serde_json::Value::String("did:ion:abc".to_owned()),
361            );
362
363        let _new_verification_method: ssi::did::VerificationMethod =
364            serde_json::from_str(&json.to_string()).unwrap();
365    }
366}