trustchain_core/
utils.rs

1//! Core utilities.
2use crate::data::{ROOT_PLUS_1_SIGNING_KEY, ROOT_PLUS_2_SIGNING_KEYS};
3use crate::key_manager::KeyManager;
4use crate::key_manager::KeyType;
5use crate::TRUSTCHAIN_DATA;
6use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8use ssi::did::{Document, ServiceEndpoint, VerificationMethod, VerificationMethodMap};
9use ssi::jwk::JWK;
10use ssi::one_or_many::OneOrMany;
11use std::path::{Path, PathBuf};
12
13use std::sync::Once;
14
15/// Gets the type of an object as a String. For diagnostic purposes (debugging) only.
16pub fn type_of<T>(_: &T) -> String {
17    std::any::type_name::<T>().to_string()
18}
19
20/// Utility key manager.
21struct UtilsKeyManager;
22
23impl KeyManager for UtilsKeyManager {}
24
25/// Set-up tempdir and use as env var for `TRUSTCHAIN_DATA`.
26// https://stackoverflow.com/questions/58006033/how-to-run-setup-code-before-any-tests-run-in-rust
27static INIT: Once = Once::new();
28pub fn init() {
29    INIT.call_once(|| {
30        let utils_key_manager = UtilsKeyManager;
31        // initialization code here
32        let tempdir = tempfile::tempdir().unwrap();
33        std::env::set_var(TRUSTCHAIN_DATA, Path::new(tempdir.as_ref().as_os_str()));
34        // Manually drop here so additional writes in the init call are not removed
35        drop(tempdir);
36        // Include test signing keys for two resolvable DIDs
37        let root_plus_1_did_suffix = "EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A";
38        let root_plus_2_did_suffix = "EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q";
39        let root_plus_2_candidate_did_suffix = "EiCDmY0qxsde9AdIwMf2tUKOiMo4aHnoWaPBRCeGt7iMHA";
40        // TODO: move to data as for the other keys
41        let root_plus_2_candidate_signing_key: &str = r#"{"kty":"EC","crv":"secp256k1","x":"WzbWcgvvq21xKDTsvANakBSI3nJKDSmNa99usFmYJ0E","y":"vAFo1gkFqgEE3QsX1xlmHcoKxs5AuDqc18kkYEGVwDk","d":"LHt66ri5ykeVqEZwbzboJevbh5UEZkT8r8etsjg3KeE"}"#;
42        let root_plus_1_signing_jwk: JWK = serde_json::from_str(ROOT_PLUS_1_SIGNING_KEY).unwrap();
43        let root_plus_2_signing_jwks: Vec<JWK> =
44            serde_json::from_str(ROOT_PLUS_2_SIGNING_KEYS).unwrap();
45        utils_key_manager
46            .save_keys(
47                root_plus_1_did_suffix,
48                KeyType::SigningKey,
49                &OneOrMany::One(root_plus_1_signing_jwk),
50                false,
51            )
52            .unwrap();
53        utils_key_manager
54            .save_keys(
55                root_plus_2_did_suffix,
56                KeyType::SigningKey,
57                &OneOrMany::Many(root_plus_2_signing_jwks),
58                false,
59            )
60            .unwrap();
61        let root_plus_2_candidate_signing_jwk: JWK = serde_json::from_str(root_plus_2_candidate_signing_key).unwrap();
62        utils_key_manager.save_keys(root_plus_2_candidate_did_suffix, KeyType::SigningKey, &OneOrMany::One(root_plus_2_candidate_signing_jwk), false).unwrap();
63    });
64}
65
66/// Extracts a vec of public keys from a DID document.
67pub fn extract_keys(doc: &Document) -> Vec<JWK> {
68    let mut public_keys: Vec<JWK> = Vec::new();
69    if let Some(verification_methods) = doc.verification_method.as_ref() {
70        for verification_method in verification_methods {
71            if let VerificationMethod::Map(VerificationMethodMap {
72                public_key_jwk: Some(key),
73                ..
74            }) = verification_method
75            {
76                public_keys.push(key.clone());
77            } else {
78                continue;
79            }
80        }
81    }
82    public_keys
83}
84
85/// From [did-ion](https://docs.rs/did-ion/0.1.0/src/did_ion/sidetree.rs.html).
86const MULTIHASH_SHA2_256_PREFIX: &[u8] = &[0x12];
87/// From [did-ion](https://docs.rs/did-ion/0.1.0/src/did_ion/sidetree.rs.html).
88const MULTIHASH_SHA2_256_SIZE: &[u8] = &[0x20];
89/// From [did-ion](https://docs.rs/did-ion/0.1.0/src/did_ion/sidetree.rs.html#107-209).
90/// Returns multihash prefix and hash.
91///
92/// Default implementation: SHA-256 (`sha2-256`)
93fn hash_protocol_algorithm(data: &[u8]) -> (Vec<u8>, Vec<u8>) {
94    let mut hasher = Sha256::new();
95    hasher.update(data);
96    let hash = hasher.finalize().to_vec();
97    (
98        [MULTIHASH_SHA2_256_PREFIX, MULTIHASH_SHA2_256_SIZE].concat(),
99        hash,
100    )
101}
102
103/// [`DATA_ENCODING_SCHEME`](https://identity.foundation/sidetree/spec/v1.0.0/#data-encoding-scheme)
104fn data_encoding_scheme(data: &[u8]) -> String {
105    base64::encode_config(data, base64::URL_SAFE_NO_PAD)
106}
107
108/// Gets the path for storing operations and creates directories if they do not exist.
109pub fn get_operations_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
110    let path: String = std::env::var(TRUSTCHAIN_DATA)?;
111    // Make directory and operation file name
112    let path = Path::new(path.as_str()).join("operations");
113    std::fs::create_dir_all(&path)?;
114    Ok(path)
115}
116
117/// Returns the suffix of a short-form DID.
118pub fn get_did_suffix(did: &str) -> &str {
119    did.split(':').last().unwrap()
120}
121
122/// Converts a short-form DID into a complete DID.
123pub fn get_did_from_suffix(did_suffix: &str, method_and_network: &str) -> String {
124    format!("did:{method_and_network}:{did_suffix}")
125}
126
127/// [`JSON_CANONICALIZATION_SCHEME`](https://identity.foundation/sidetree/spec/v1.0.0/#json-canonicalization-scheme)
128pub fn canonicalize<T: Serialize + ?Sized>(value: &T) -> Result<String, serde_json::Error> {
129    serde_jcs::to_string(value)
130}
131
132/// [`JSON_CANONICALIZATION_SCHEME`](https://identity.foundation/sidetree/spec/v1.0.0/#json-canonicalization-scheme)
133/// from a `&str` through type `T` to canonicalized `String`.
134pub fn canonicalize_str<T>(s: &str) -> Result<String, serde_json::Error>
135where
136    T: Serialize + for<'a> Deserialize<'a>,
137{
138    canonicalize(&serde_json::from_str::<T>(s)?)
139}
140
141/// From [did-ion](https://docs.rs/did-ion/0.1.0/src/did_ion/sidetree.rs.html).
142/// [`HASH_PROTOCOL`](https://identity.foundation/sidetree/spec/v1.0.0/#hash-protocol)
143///
144/// Default implementation calls [hash_protocol_algorithm] and returns the concatenation of the
145/// prefix and hash.
146///
147/// [hash_protocol_algorithm]: hash_protocol_algorithm
148fn hash_protocol(data: &[u8]) -> Vec<u8> {
149    let (prefix, hash) = hash_protocol_algorithm(data);
150    [prefix, hash].concat()
151}
152
153/// Hash and encode data
154///
155/// [Sidetree ยง6.1 Hashing Process](https://identity.foundation/sidetree/spec/#hashing-process)
156pub fn hash(data: &str) -> String {
157    let hash = hash_protocol(data.as_bytes());
158    data_encoding_scheme(&hash)
159}
160
161/// Extracts payload from JWT and verifies signature.
162pub fn decode_verify(jwt: &str, key: &JWK) -> Result<String, ssi::jws::Error> {
163    ssi::jwt::decode_verify(jwt, key)
164}
165
166/// Extracts and decodes the payload from the JWT.
167pub fn decode(jwt: &str) -> Result<String, ssi::jws::Error> {
168    ssi::jwt::decode_unverified(jwt)
169}
170
171/// Extracts keys (`JWK`) from a type.
172pub trait HasKeys {
173    /// Gets keys.
174    fn get_keys(&self) -> Option<Vec<JWK>>;
175}
176
177/// Extracts endpoints (`ServiceEndpoint`) from a type.
178pub trait HasEndpoints {
179    /// Gets endpoints.
180    fn get_endpoints(&self) -> Option<Vec<ServiceEndpoint>>;
181}
182
183impl HasKeys for Document {
184    fn get_keys(&self) -> Option<Vec<JWK>> {
185        let verification_methods = match &self.verification_method {
186            Some(x) => x,
187            None => return None,
188        };
189
190        let verification_method_maps: Vec<&VerificationMethodMap> = verification_methods
191            .iter()
192            .filter_map(|verification_method| match verification_method {
193                VerificationMethod::Map(x) => Some(x),
194                _ => {
195                    eprintln!("Unhandled VerificationMethod variant. Expected Map.");
196                    None
197                }
198            })
199            .collect();
200
201        if verification_method_maps.is_empty() {
202            return None;
203        }
204
205        let keys: Vec<JWK> = verification_method_maps
206            .iter()
207            .filter_map(|verification_method_map| verification_method_map.public_key_jwk.to_owned())
208            .collect();
209
210        if keys.is_empty() {
211            return None;
212        }
213        Some(keys)
214    }
215}
216
217impl HasEndpoints for Document {
218    fn get_endpoints(&self) -> Option<Vec<ServiceEndpoint>> {
219        let services = match &self.service {
220            Some(x) => x,
221            None => return None,
222        };
223        let service_endpoints: Vec<ServiceEndpoint> = services
224            .iter()
225            .flat_map(|service| match service.to_owned().service_endpoint {
226                Some(endpoints) => endpoints.into_iter(),
227                None => Vec::<ServiceEndpoint>::new().into_iter(),
228            })
229            .collect();
230        if service_endpoints.is_empty() {
231            return None;
232        }
233        Some(service_endpoints)
234    }
235}
236
237/// Tests whether one JSON object contains all the elements of another.
238pub fn json_contains(candidate: &serde_json::Value, expected: &serde_json::Value) -> bool {
239    // If the expected Value is an array, recursively check each element.
240    if let serde_json::Value::Array(exp_vec) = expected {
241        return exp_vec
242            .iter()
243            .all(|exp_value| json_contains(candidate, exp_value));
244    }
245    match candidate {
246        serde_json::Value::Null => matches!(expected, serde_json::Value::Null),
247        serde_json::Value::Bool(x) => match expected {
248            serde_json::Value::Bool(y) => x == y,
249            _ => false,
250        },
251        serde_json::Value::Number(x) => match expected {
252            serde_json::Value::Number(y) => x == y,
253            _ => false,
254        },
255        serde_json::Value::String(x) => match expected {
256            serde_json::Value::String(y) => x == y,
257            _ => false,
258        },
259        serde_json::Value::Array(cand_vec) => {
260            // If the candidate is an Array, check if any value in the candidate contains the expected one.
261            return cand_vec.iter().any(|value| json_contains(value, expected));
262        }
263        serde_json::Value::Object(cand_map) => {
264            match expected {
265                serde_json::Value::Object(exp_map) => {
266                    // If both candidate and expected are Maps, check each element of the expected map
267                    // is contained in the candidate map.
268                    for exp_key in exp_map.keys() {
269                        if !cand_map.contains_key(exp_key) {
270                            // If the key is not found but the Value is itself a Map or Vector, recurse.
271                            return cand_map.keys().any(|cand_key| {
272                                match cand_map.get(cand_key).unwrap() {
273                                    serde_json::Value::Object(..)
274                                    | serde_json::Value::Array(..) => {
275                                        return json_contains(
276                                            cand_map.get(cand_key).unwrap(),
277                                            expected,
278                                        )
279                                    }
280                                    _ => false,
281                                }
282                            });
283                        } else {
284                            let exp_value = exp_map.get(exp_key).unwrap();
285                            let cand_value = cand_map.get(exp_key).unwrap();
286                            if !json_contains(cand_value, exp_value) {
287                                return false;
288                            }
289                        }
290                    }
291                    true
292                }
293                _ => {
294                    // If the candidate is a Map and the expected is a scalar, check each value inside
295                    // the candidate map.
296                    return cand_map
297                        .values()
298                        .any(|cand_value| json_contains(cand_value, expected));
299                }
300            }
301        }
302    }
303}
304/// Generates a new cryptographic key.
305pub fn generate_key() -> JWK {
306    JWK::generate_secp256k1().expect("Could not generate key.")
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312    use crate::data::{
313        TEST_ROOT_JWK_PK, TEST_ROOT_PLUS_1_DOCUMENT, TEST_ROOT_PLUS_1_JWT,
314        TEST_SIDETREE_DOCUMENT_MULTIPLE_KEYS,
315    };
316    use ssi::did::Document;
317
318    #[test]
319    fn test_get_did_from_suffix() {
320        let did_suffix = "EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg";
321        let mut method_and_network = "ion";
322        let result = get_did_from_suffix(did_suffix, method_and_network);
323        assert_eq!(
324            result,
325            "did:ion:EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg"
326        );
327
328        method_and_network = "ion:test";
329        let result = get_did_from_suffix(did_suffix, method_and_network);
330        assert_eq!(
331            result,
332            "did:ion:test:EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg"
333        );
334    }
335
336    #[test]
337    fn test_generate_key() {
338        let result = generate_key();
339
340        // Check for the expected elliptic curve (used by ION to generate keys).
341        match result.params {
342            ssi::jwk::Params::EC(ecparams) => {
343                assert_eq!(ecparams.curve, Some(String::from("secp256k1")))
344            }
345            _ => panic!(),
346        }
347    }
348
349    #[test]
350    fn test_decode_verify() -> Result<(), Box<dyn std::error::Error>> {
351        let key: JWK = serde_json::from_str(TEST_ROOT_JWK_PK)?;
352        let jwt = TEST_ROOT_PLUS_1_JWT;
353        let result = decode_verify(jwt, &key);
354        assert!(result.is_ok());
355        Ok(())
356    }
357
358    #[test]
359    fn test_decode_canonicalize_hash() -> Result<(), Box<dyn std::error::Error>> {
360        let doc: Document = serde_json::from_str(TEST_ROOT_PLUS_1_DOCUMENT)?;
361        let doc_canon = canonicalize(&doc)?;
362        let actual_hash = hash(&doc_canon);
363        let jwt = TEST_ROOT_PLUS_1_JWT;
364        let expected_hash = decode(jwt)?;
365        assert_eq!(expected_hash, actual_hash);
366        Ok(())
367    }
368
369    #[test]
370    fn test_json_contains() {
371        // Test with a JSON map.
372        let cand_str = r#"{"provisionalIndexFileUri":"QmfXAa2MsHspcTSyru4o1bjPQELLi62sr2pAKizFstaxSs","operations":{"create":[{"suffixData":{"deltaHash":"EiBkAX9y-Ts_siMzTzkfAzPKPIIbB033PlF0RlvF97ydJg","recoveryCommitment":"EiCymv17OGBAs7eLmm4BIXDCQBVhdOUAX5QdpIrN4SDE5w"}},{"suffixData":{"deltaHash":"EiBBkv0j587BDSTjJtIv2DJFOOHk662n9Uoh1vtBaY3JKA","recoveryCommitment":"EiClOaWycGv1m-QejUjB0L18G6DVFVeTQCZCuTRrmzCBQg"}},{"suffixData":{"deltaHash":"EiDTaFAO_ae63J4LMApAM-9VAo8ng58TTp2K-2r1nek6lQ","recoveryCommitment":"EiCy4pW16uB7H-ijA6V6jO6ddWfGCwqNcDSJpdv_USzoRA"}}]}}"#;
373        let candidate: serde_json::Value = serde_json::from_str(cand_str).unwrap();
374
375        let exp_str =
376            r#"{"provisionalIndexFileUri":"QmfXAa2MsHspcTSyru4o1bjPQELLi62sr2pAKizFstaxSs"}"#;
377        let expected: serde_json::Value = serde_json::from_str(exp_str).unwrap();
378        assert!(json_contains(&candidate, &expected));
379
380        // Different key.
381        let exp_str =
382            r#"{"provisionalIndeXFileUri":"QmfXAa2MsHspcTSyru4o1bjPQELLi62sr2pAKizFstaxSs"}"#;
383        let expected: serde_json::Value = serde_json::from_str(exp_str).unwrap();
384        assert!(!json_contains(&candidate, &expected));
385
386        // Different value.
387        let exp_str =
388            r#"{"provisionalIndexFileUri":"PmfXAa2MsHspcTSyru4o1bjPQELLi62sr2pAKizFstaxSs"}"#;
389        let expected: serde_json::Value = serde_json::from_str(exp_str).unwrap();
390        assert!(!json_contains(&candidate, &expected));
391
392        // Test with a JSON array.
393        let array_vec = vec!["x".to_string(), "y".to_string(), "z".to_string()];
394        let candidate = serde_json::json!(array_vec);
395        assert!(json_contains(&candidate, &serde_json::json!("x")));
396        assert!(json_contains(&candidate, &serde_json::json!("y")));
397        assert!(json_contains(&candidate, &serde_json::json!("z")));
398        assert!(!json_contains(&candidate, &serde_json::json!("X")));
399
400        // Test with a JSON map containing a JSON array.
401        let candidate: serde_json::Value =
402            serde_json::from_str(TEST_SIDETREE_DOCUMENT_MULTIPLE_KEYS).unwrap();
403
404        // Same elements but different order:
405        let exp_str = r##"{"verificationMethod" : [
406        {
407            "controller" : "did:ion:test:EiCBr7qGDecjkR2yUBhn3aNJPUR3TSEOlkpNcL0Q5Au9ZQ",
408            "id" : "#V9jt_0c-aFlq40Uti2R_WiquxuzxyB8kn1cfWmXIU85",
409            "publicKeyJwk" : {
410                "crv": "secp256k1",
411                "kty": "EC",
412                "x": "7ReQHHysGxbyuKEQmspQOjL7oQUqDTldTHuc9V3-yso",
413                "y": "kWvmS7ZOvDUhF8syO08PBzEpEk3BZMuukkvEJOKSjqE"
414            },
415            "type" : "JsonWebSignature2020"
416        },
417        {
418            "controller" : "did:ion:test:EiCBr7qGDecjkR2yUBhn3aNJPUR3TSEOlkpNcL0Q5Au9ZQ",
419            "id" : "#V8jt_0c-aFlq40Uti2R_WiquxuzxyB8kn1cfWmXIU84",
420            "publicKeyJwk" : {
421                "crv" : "secp256k1",
422                "kty" : "EC",
423                "x" : "RbIj1Y4jeqkn0cizEfxHZidD-GQouFmAtE6YCpxFjpg",
424                "y" : "ZcbgNp3hrfp3cujZFKqgFS0uFGOn2Rk16Y9nOv0h15s"
425            },
426            "type" : "JsonWebSignature2020"
427        }]
428    }"##;
429        let expected: serde_json::Value = serde_json::from_str(exp_str).unwrap();
430        assert!(json_contains(&candidate, &expected));
431
432        // Different nested key:
433        let exp_str = r##"{"verificationMethod" : [
434        {
435            "controller" : "did:ion:test:EiCBr7qGDecjkR2yUBhn3aNJPUR3TSEOlkpNcL0Q5Au9ZQ",
436            "id" : "#V9jt_0c-aFlq40Uti2R_WiquxuzxyB8kn1cfWmXIU85",
437            "publicKeyJwk" : {
438                "crv": "secp256k1",
439                "kty": "EC",
440                "x": "7ReQHHysGxbyuKEQmspQOjL7oQUqDTldTHuc9V3-yso",
441                "z": "kWvmS7ZOvDUhF8syO08PBzEpEk3BZMuukkvEJOKSjqE"
442            },
443            "type" : "JsonWebSignature2020"
444        },
445        {
446            "controller" : "did:ion:test:EiCBr7qGDecjkR2yUBhn3aNJPUR3TSEOlkpNcL0Q5Au9ZQ",
447            "id" : "#V8jt_0c-aFlq40Uti2R_WiquxuzxyB8kn1cfWmXIU84",
448            "publicKeyJwk" : {
449                "crv" : "secp256k1",
450                "kty" : "EC",
451                "x" : "RbIj1Y4jeqkn0cizEfxHZidD-GQouFmAtE6YCpxFjpg",
452                "y" : "ZcbgNp3hrfp3cujZFKqgFS0uFGOn2Rk16Y9nOv0h15s"
453            },
454            "type" : "JsonWebSignature2020"
455        }]
456    }"##;
457        let expected: serde_json::Value = serde_json::from_str(exp_str).unwrap();
458        assert!(!json_contains(&candidate, &expected));
459
460        // Different nested value:
461        let exp_str = r##"{"verificationMethod" : [
462        {
463            "controller" : "did:ion:test:EiCBr7qGDecjkR2yUBhn3aNJPUR3TSEOlkpNcL0Q5Au9ZQ",
464            "id" : "#V9jt_0c-aFlq40Uti2R_WiquxuzxyB8kn1cfWmXIU85",
465            "publicKeyJwk" : {
466                "crv": "secp256k1",
467                "kty": "EC",
468                "x": "7ReQHHysGxbyuKEQmspQOjL7oQUqDTldTHuc9V3-yso",
469                "y": "kWvmS7ZOvDUhF8syO08PBzEpEk3BZMuukkvEJOKSjqE"
470            },
471            "type" : "JsonWebSignature2020"
472        },
473        {
474            "controller" : "did:ion:test:EiCBr7qGDecjkR2yUBhn3aNJPUR3TSEOlkpNcL0Q5Au9ZQ",
475            "id" : "#V8jt_0c-aFlq40Uti2R_WiquxuzxyB8kn1cfWmXIU84",
476            "publicKeyJwk" : {
477                "crv" : "secp256k1",
478                "kty" : "EC",
479                "x" : "RbIj1Y4jeqkn0cizEfxHZidD-GQouFmAtE6YCpxFjpg",
480                "y" : "YcbgNp3hrfp3cujZFKqgFS0uFGOn2Rk16Y9nOv0h15s"
481            },
482            "type" : "JsonWebSignature2020"
483        }]
484    }"##;
485
486        let expected: serde_json::Value = serde_json::from_str(exp_str).unwrap();
487        assert!(!json_contains(&candidate, &expected));
488
489        // Entire expected object nested:
490        let exp_str = r#"{"publicKeyJwk" : {
491        "crv": "secp256k1",
492        "kty": "EC",
493        "x": "7ReQHHysGxbyuKEQmspQOjL7oQUqDTldTHuc9V3-yso",
494        "y": "kWvmS7ZOvDUhF8syO08PBzEpEk3BZMuukkvEJOKSjqE"
495    }}"#;
496
497        let expected: serde_json::Value = serde_json::from_str(exp_str).unwrap();
498        assert!(json_contains(&candidate, &expected));
499    }
500}