Skip to main content

did_scid/
lib.rs

1/*! Implementation of the
2*/
3
4use crate::errors::DIDSCIDError;
5use affinidi_did_common::Document;
6use didwebvh_rs::{DIDWebVHState, log_entry::LogEntryMethods};
7use regex::Regex;
8use std::time::Duration;
9use tracing::{debug, error};
10
11pub mod errors;
12
13#[derive(Clone, Debug)]
14pub enum ScidMethod {
15    WebVH(String),
16
17    #[cfg(feature = "did-cheqd")]
18    Cheqd(String),
19}
20
21/// Resolve a SCID DID Method
22/// did: id of the DID to resolve
23/// peer_src: Optional Source when did:scid is being used as a peer DID
24///   webvh: peer_src should be the path that gets concatenated to the SCID
25///   (did:webvh:{SCID}:<domain:path>)
26///   cheqd: peer_src should be mainnet or testnet
27/// timeout: Optional Time for timeout
28pub async fn resolve(
29    did: &str,
30    peer_src: Option<ScidMethod>,
31    timeout: Option<Duration>,
32) -> Result<Document, DIDSCIDError> {
33    if did.starts_with("did:scid:vh:1") {
34        // Implement the resolution logic here
35        match convert_scid_to_method(did, peer_src)? {
36            ScidMethod::WebVH(webvh_did) => {
37                debug!("Resolving WebVH DID: {}", webvh_did);
38                let mut method = DIDWebVHState::default();
39                match method.resolve(&webvh_did, timeout).await {
40                    Ok((log_entry, _)) => {
41                        Ok(serde_json::from_value(log_entry.get_did_document()?)?)
42                    }
43                    Err(e) => {
44                        error!("Error: {:?}", e);
45                        Err(DIDSCIDError::WebVHError(e))
46                    }
47                }
48            }
49            #[cfg(feature = "did-cheqd")]
50            ScidMethod::Cheqd(cheqd_did) => {
51                use did_resolver_cheqd::DIDCheqd;
52                use ssi_dids_core::{DID, DIDResolver};
53
54                debug!("Resolving Cheqd DID: {}", cheqd_did);
55                match DIDCheqd::default()
56                    .resolve(DID::new::<str>(&cheqd_did).unwrap())
57                    .await
58                {
59                    Ok(res) => {
60                        let doc_value = serde_json::to_value(res.document.into_document())?;
61                        Ok(serde_json::from_value(doc_value)?)
62                    }
63                    Err(e) => {
64                        error!("Error: {:?}", e);
65                        Err(DIDSCIDError::CheqdError(e.to_string()))
66                    }
67                }
68            }
69        }
70    } else {
71        Err(DIDSCIDError::UnsupportedFormat)
72    }
73}
74
75/// Converts a SCID DID to a valid Method DID Identifier
76/// peer_src: Optional meta_data if operating in peer mode
77fn convert_scid_to_method(
78    id: &str,
79    peer_src: Option<ScidMethod>,
80) -> Result<ScidMethod, DIDSCIDError> {
81    let re = Regex::new(r"^did:scid:vh:1:([^\?]*)(?:\?src=(.*))?$").unwrap();
82    if let Some(caps) = re.captures(id) {
83        if let Some(src) = caps.get(2).map(|m| m.as_str()) {
84            // Has source
85            if src.starts_with("did:cheqd") {
86                // Cheqd Method
87                let mut cheqd = String::new();
88                cheqd.push_str(src);
89                cheqd.push(':');
90                cheqd.push_str(&caps[1]);
91
92                debug!("derived cheqd DID: {cheqd}");
93                Ok(ScidMethod::Cheqd(cheqd))
94            } else if src.starts_with("did:") {
95                // Invalid DID method as source
96                Err(DIDSCIDError::UnsupportedFormat)
97            } else {
98                // WebVH URL
99                let mut webvh = String::new();
100                webvh.push_str("did:webvh:");
101                webvh.push_str(&caps[1]);
102                webvh.push(':');
103                webvh.push_str(&src.replace("/", ":"));
104
105                debug!("derived webvh DID: {webvh}");
106                Ok(ScidMethod::WebVH(webvh))
107            }
108        } else {
109            // Peer Mode
110            match peer_src {
111                Some(ScidMethod::WebVH(src)) => {
112                    let mut webvh = String::new();
113                    webvh.push_str("did:webvh:");
114                    webvh.push_str(&caps[1]);
115                    webvh.push(':');
116                    webvh.push_str(&src);
117
118                    debug!("derived peer webvh DID: {webvh}");
119                    Ok(ScidMethod::WebVH(webvh))
120                }
121                Some(ScidMethod::Cheqd(src)) => {
122                    let mut cheqd = String::new();
123                    cheqd.push_str("did:cheqd:");
124                    cheqd.push_str(&src);
125                    cheqd.push(':');
126                    cheqd.push_str(&caps[1]);
127
128                    debug!("derived peer cheqd DID: {cheqd}");
129                    Ok(ScidMethod::Cheqd(cheqd))
130                }
131                None => Err(DIDSCIDError::MissingPeerSource),
132            }
133        }
134    } else {
135        Err(DIDSCIDError::UnsupportedFormat)
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use crate::{convert_scid_to_method, errors::DIDSCIDError, resolve};
142
143    #[test]
144    fn test_cheqd_conversion() {
145        match convert_scid_to_method("did:scid:vh:1:abcde?src=did:cheqd:mainnet", None) {
146            Ok(crate::ScidMethod::Cheqd(did)) => assert_eq!(did, "did:cheqd:mainnet:abcde"),
147            _ => panic!("Incorrect conversion"),
148        }
149    }
150
151    #[test]
152    fn test_cheqd_peer_conversion() {
153        match convert_scid_to_method(
154            "did:scid:vh:1:abcde",
155            Some(crate::ScidMethod::Cheqd("mainnet".to_string())),
156        ) {
157            Ok(crate::ScidMethod::Cheqd(did)) => assert_eq!(did, "did:cheqd:mainnet:abcde"),
158            _ => panic!("Incorrect conversion"),
159        }
160    }
161
162    #[test]
163    fn test_webvh_conversion() {
164        match convert_scid_to_method(
165            "did:scid:vh:1:abcde?src=stormer78.github.io/identity/fpp",
166            None,
167        ) {
168            Ok(crate::ScidMethod::WebVH(did)) => {
169                assert_eq!(did, "did:webvh:abcde:stormer78.github.io:identity:fpp")
170            }
171            _ => panic!("Incorrect conversion"),
172        }
173    }
174
175    #[test]
176    fn test_webvhpeer_conversion() {
177        match convert_scid_to_method(
178            "did:scid:vh:1:abcde",
179            Some(crate::ScidMethod::WebVH("stormer78.github.io".to_string())),
180        ) {
181            Ok(crate::ScidMethod::WebVH(did)) => {
182                assert_eq!(did, "did:webvh:abcde:stormer78.github.io")
183            }
184            _ => panic!("Incorrect conversion"),
185        }
186    }
187
188    #[test]
189    fn test_missing_peer() {
190        match convert_scid_to_method("did:scid:vh:1:abcde", None) {
191            Err(DIDSCIDError::MissingPeerSource) => {}
192            _ => panic!("Incorrect conversion"),
193        }
194    }
195
196    #[test]
197    fn test_bad_did_method() {
198        match convert_scid_to_method("did:scid:vh:1:abcde?src=did:example:abcd", None) {
199            Err(DIDSCIDError::UnsupportedFormat) => {}
200            _ => panic!("Incorrect conversion"),
201        }
202    }
203
204    #[test]
205    fn test_bad_id() {
206        match convert_scid_to_method("did:scid:invalid:1:abcde?src=did:example:abcd", None) {
207            Err(DIDSCIDError::UnsupportedFormat) => {}
208            _ => panic!("Incorrect conversion"),
209        }
210    }
211
212    #[tokio::test]
213    async fn test_scid_webvh_resolution() {
214        match resolve("did:scid:vh:1:Qmd1FCL9Vj2vJ433UDfC9MBstK6W6QWSQvYyeNn8va2fai?src=identity.foundation/didwebvh-implementations/implementations/affinidi-didwebvh-rs", None, None).await {
215            Ok(doc) => {
216                assert_eq!(doc.id.as_str(), "did:webvh:Qmd1FCL9Vj2vJ433UDfC9MBstK6W6QWSQvYyeNn8va2fai:identity.foundation:didwebvh-implementations:implementations:affinidi-didwebvh-rs");
217            }
218            Err(_) => panic!("Couldn't resolve SCID WebVH DID")
219        }
220    }
221
222    #[tokio::test]
223    async fn test_scid_cheqd_resolution() {
224        match resolve(
225            "did:scid:vh:1:cad53e1d-71e0-48d2-9352-39cc3d0fac99?src=did:cheqd:testnet",
226            None,
227            None,
228        )
229        .await
230        {
231            Ok(doc) => {
232                assert_eq!(
233                    doc.id.as_str(),
234                    "did:cheqd:testnet:cad53e1d-71e0-48d2-9352-39cc3d0fac99"
235                );
236            }
237            Err(_) => panic!("Couldn't resolve SCID Cheqd DID"),
238        }
239    }
240}