diff --git a/.gitignore b/.gitignore index 0f654a9a..cc848fb6 100644 --- a/.gitignore +++ b/.gitignore @@ -32,5 +32,8 @@ outputs/ # dist paths **/dist +# Python venv +.venv + # OS .DS_Store diff --git a/trustchain-cli/Cargo.toml b/trustchain-cli/Cargo.toml index a8dbd622..60bffaba 100644 --- a/trustchain-cli/Cargo.toml +++ b/trustchain-cli/Cargo.toml @@ -12,9 +12,12 @@ path = "src/bin/main.rs" trustchain-core = { path = "../trustchain-core" } trustchain-ion = { path = "../trustchain-ion" } trustchain-api = { path = "../trustchain-api" } +trustchain-http = { path = "../trustchain-http" } + clap = { version = "4.0.32", features = ["derive", "cargo"] } did-ion = { git = "https://github.com/alan-turing-institute/ssi.git", rev = "1aa3223a384ee71df1333bbce04af445e852eab5" } +josekit = "0.8" lazy_static = "1.4.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/trustchain-cli/src/bin/main.rs b/trustchain-cli/src/bin/main.rs index 7d47b400..ae1c1432 100644 --- a/trustchain-cli/src/bin/main.rs +++ b/trustchain-cli/src/bin/main.rs @@ -1,11 +1,13 @@ //! Trustchain CLI binary use clap::{arg, ArgAction, Command}; +use core::panic; use serde_json::to_string_pretty; use ssi::{jsonld::ContextLoader, ldp::LinkedDataDocument, vc::Credential}; use std::{ fs::File, - io::{stdin, BufReader}, + io::{self, stdin, BufReader}, path::Path, + path::PathBuf, }; use trustchain_api::{ api::{TrustchainDIDAPI, TrustchainDataAPI, TrustchainVCAPI}, @@ -13,9 +15,18 @@ use trustchain_api::{ }; use trustchain_cli::config::cli_config; use trustchain_core::{ + utils::extract_keys, vc::{CredentialError, DataCredentialError}, verifier::Verifier, - JSON_FILE_EXTENSION, + JSON_FILE_EXTENSION, TRUSTCHAIN_DATA, +}; +use trustchain_http::{ + attestation_encryption_utils::ssi_to_josekit_jwk, + attestation_utils::{ + CRState, ElementwiseSerializeDeserialize, IdentityCRInitiation, TrustchainCRError, + }, + attestor::present_identity_challenge, + requester::{identity_response, initiate_content_challenge, initiate_identity_challenge}, }; use trustchain_ion::{ attest::attest_operation, @@ -109,15 +120,71 @@ fn cli() -> Command { .arg(arg!(-v - -verbose).action(ArgAction::Count)) .arg(arg!(-f --data_file ).required(true)) .arg(arg!(-c --credential_file ).required(true)) - .arg(arg!(-t --root_event_time ).required(false)), + .arg(arg!(-t --root_event_time ).required(false)) ), ) + .subcommand( + Command::new("cr") + .about("Challenge-response functionality for attestation challenge response process (identity and content challenge-response).") + .subcommand_required(true) + .arg_required_else_help(true) + .allow_external_subcommands(true) + .subcommand( + Command::new("identity") + .about("Identity challenge-response functionality: initiate, present, respond.") + .arg(arg!(-v - -verbose).action(ArgAction::SetTrue)) + .arg(arg!(-f --file_path ).required(false)) + .subcommand( + Command::new("initiate") + .about("Initiates a new identity challenge-response process.") + .arg(arg!(-v - -verbose).action(ArgAction::Count)) + .arg(arg!(-d --did ).required(true)) + ) + .subcommand( + Command::new("present") + .about("Produce challenge for identity CR to be presented to requestor.") + .arg(arg!(-v - -verbose).action(ArgAction::Count)) + .arg(arg!(-p --path ).required(true)) + .arg(arg!(-d --did ).required(true)) + ) + .subcommand( + Command::new("respond") + .about("Produce response for identity challenge to be posted to attestor.") + .arg(arg!(-v - -verbose).action(ArgAction::Count)) + .arg(arg!(-p --path ).required(true)) + .arg(arg!(-d --did ).required(true)) + ) + ) + .subcommand( + Command::new("content") + .about("Content challenge-response functionality: initiate, respond.") + .arg(arg!(-v - -verbose).action(ArgAction::SetTrue)) + .arg(arg!(-f --file_path ).required(false)) + .subcommand( + Command::new("initiate") + .about("Initiates the content challenge-response process.") + .arg(arg!(-v - -verbose).action(ArgAction::Count)) + .arg(arg!(-d --did ).required(true)) + .arg(arg!(--ddid ).required(true)) + .arg(arg!(-p --path ).required(true)) + ) + ) + .subcommand( + Command::new("complete") + .about("Check if challenge-response for attestation request has been completed.") + .arg(arg!(-v - -verbose).action(ArgAction::SetTrue)) + .arg(arg!(-p --path ).required(true)) + .arg(arg!(-e --entity ).required(true)) + ) + + ) } #[tokio::main] async fn main() -> Result<(), Box> { let matches = cli().get_matches(); let endpoint = cli_config().ion_endpoint.to_address(); + let root_event_time: u32 = cli_config().root_event_time; let verifier = TrustchainVerifier::new(trustchain_resolver(&endpoint)); let resolver = verifier.resolver(); let mut context_loader = ContextLoader::default(); @@ -302,6 +369,189 @@ async fn main() -> Result<(), Box> { _ => panic!("Unrecognised VC subcommand."), } } + Some(("cr", sub_matches)) => match sub_matches.subcommand() { + Some(("identity", sub_matches)) => match sub_matches.subcommand() { + Some(("initiate", sub_matches)) => { + // verify DID before resolving and extracting endpoint + let did = sub_matches.get_one::("did").unwrap(); + let _result = verifier.verify(did, root_event_time.into()).await?; + let (_, doc, _) = TrustchainAPI::resolve(did, resolver).await?; + let services = doc.unwrap().service; + + // user promt for org name and operator name + println!("Please enter your organisation name: "); + let mut org_name = String::new(); + io::stdin() + .read_line(&mut org_name) + .expect("Failed to read line"); + + let mut op_name = String::new(); + println!("Please enter your operator name: "); + io::stdin() + .read_line(&mut op_name) + .expect("Failed to read line"); + + println!("Organisation name: {}", org_name); + println!("Operator name: {}", op_name); + // initiate identity challenge + let (identity_cr_initiation, path) = initiate_identity_challenge( + org_name.trim(), + op_name.trim(), + &services.unwrap(), + ) + .await?; + identity_cr_initiation.elementwise_serialize(&path)?; + println!("Successfully initiated attestation request."); + println!("You will receive more information on the challenge-response process via alternative communication channel."); + } + Some(("present", sub_matches)) => { + // get attestation request path from provided input + let trustchain_dir: String = std::env::var(TRUSTCHAIN_DATA) + .map_err(|_| TrustchainCRError::FailedAttestationRequest)?; + let path_to_check = sub_matches.get_one::("path").unwrap(); + let did = sub_matches.get_one::("did").unwrap(); + let path = PathBuf::new() + .join(trustchain_dir) + .join("attestor") + .join("attestation_requests") + .join(path_to_check); + if !path.exists() { + panic!("Provided attestation request not found. Path does not exist."); + } + let identity_initiation = IdentityCRInitiation::new() + .elementwise_deserialize(&path) + .unwrap(); + // Show requester information to user and ask for confirmation to proceed + println!("---------------------------------"); + println!("Requester information: "); + println!( + "{:?}", + identity_initiation + .as_ref() + .unwrap() + .requester_details + .as_ref() + .unwrap() + ); + println!("---------------------------------"); + println!("Recognise this attestation request and want to proceed? (y/n)"); + let mut prompt = String::new(); + io::stdin() + .read_line(&mut prompt) + .expect("Failed to read line"); + let prompt = prompt.trim(); + if prompt != "y" && prompt != "yes" { + println!("Aborting attestation request."); + return Ok(()); + } + + let temp_p_key = identity_initiation.unwrap().temp_p_key.unwrap(); + + // call function to present challenge + let identity_challenge = present_identity_challenge(did, &temp_p_key)?; + + // print signed and encrypted payload to terminal + let payload = identity_challenge + .identity_challenge_signature + .as_ref() + .unwrap(); + println!("---------------------------------"); + println!("Signed and encrypted challenge:"); + println!("{:?}", payload); + println!("---------------------------------"); + println!("Please send the above challenge to the requester via alternative channels."); + println!("Before responding using the 'respond' subcommand, the requester has to save the challenge to a file named 'identity_challenge_signature.json' in the corresponding attestation request directory."); + println!("---------------------------------"); + + // serialise struct + identity_challenge.elementwise_serialize(&path)?; + } + Some(("respond", sub_matches)) => { + // get attestation request path from provided input + let trustchain_dir: String = std::env::var(TRUSTCHAIN_DATA) + .map_err(|_| TrustchainCRError::FailedAttestationRequest)?; + let path_to_check = sub_matches.get_one::("path").unwrap(); + let path = PathBuf::new() + .join(trustchain_dir) + .join("requester") + .join("attestation_requests") + .join(path_to_check); + if !path.exists() { + panic!("Provided attestation request not found. Path does not exist."); + } + let did = sub_matches.get_one::("did").unwrap(); + let (_, doc, _) = TrustchainAPI::resolve(did, resolver).await?; + let doc = doc.unwrap(); + // extract attestor public key from did document + let public_keys = extract_keys(&doc); + let attestor_public_key_ssi = public_keys.first().unwrap(); + let public_key = ssi_to_josekit_jwk(attestor_public_key_ssi).unwrap(); + // service endpoint + let services = doc.service.unwrap(); + let identity_challenge_response = + identity_response(&path, &services, &public_key).await?; + // serialise struct + identity_challenge_response.elementwise_serialize(&path)?; + } + _ => panic!("Unrecognised CR identity subcommand."), + }, + Some(("content", sub_matches)) => match sub_matches.subcommand() { + Some(("initiate", sub_matches)) => { + let did = sub_matches.get_one::("did").unwrap(); + let ddid = sub_matches.get_one::("ddid").unwrap(); + let path_to_check = sub_matches.get_one::("path").unwrap(); + + // check attestation request path + let trustchain_dir: String = std::env::var(TRUSTCHAIN_DATA) + .map_err(|_| TrustchainCRError::FailedAttestationRequest)?; + let path = PathBuf::new() + .join(trustchain_dir) + .join("requester") + .join("attestation_requests") + .join(path_to_check); + if !path.exists() { + panic!("Provided attestation request not found. Path does not exist."); + } + + // resolve DID, get services and attestor public key + let (_, doc, _) = TrustchainAPI::resolve(did, resolver).await?; + let doc = doc.unwrap(); + let public_keys = extract_keys(&doc); + let attestor_public_key_ssi = public_keys.first().unwrap(); + let attestor_public_key = ssi_to_josekit_jwk(attestor_public_key_ssi).unwrap(); + let services = &doc.service.unwrap(); + + let (content_initiation, content_challenge) = + initiate_content_challenge(&path, ddid, services, &attestor_public_key) + .await?; + content_initiation.elementwise_serialize(&path)?; + content_challenge.elementwise_serialize(&path)?; + } + _ => panic!("Unrecognised CR content subcommand."), + }, + Some(("complete", sub_matches)) => { + let path_to_check = sub_matches.get_one::("path").unwrap(); + let entity = sub_matches.get_one::("entity").unwrap(); + let trustchain_dir: String = std::env::var(TRUSTCHAIN_DATA) + .map_err(|_| TrustchainCRError::FailedAttestationRequest)?; + let path = PathBuf::new() + .join(trustchain_dir) + .join(entity) + .join("attestation_requests") + .join(path_to_check); + let cr_state = CRState::new() + .elementwise_deserialize(&path) + .unwrap() + .unwrap(); + let current_state = cr_state.check_cr_status().unwrap(); + + println!( + "State of attestation challenge-response process: {:?}", + current_state + ); + } + _ => panic!("Unrecognised CR subcommand."), + }, Some(("data", sub_matches)) => { let verifier = TrustchainVerifier::new(trustchain_resolver(&endpoint)); let resolver = verifier.resolver(); diff --git a/trustchain-core/src/attestor.rs b/trustchain-core/src/attestor.rs index 99217048..646141fb 100644 --- a/trustchain-core/src/attestor.rs +++ b/trustchain-core/src/attestor.rs @@ -23,7 +23,7 @@ pub enum AttestorError { SigningError(String, String), } -/// An upstream entity that attests to a downstream DID. +/// An upstream entity that attests to a downstream DID (attestor). pub trait Attestor: Subject { /// Attests to a DID Document. Subject attests to a DID document by signing the document with (one of) its private signing key(s). /// It doesn't matter which signing key you use, there's the option to pick one using the key index. diff --git a/trustchain-core/src/graph.rs b/trustchain-core/src/graph.rs index 229753d4..cc3912eb 100644 --- a/trustchain-core/src/graph.rs +++ b/trustchain-core/src/graph.rs @@ -62,7 +62,7 @@ fn read_chains(chains: &Vec, label_width: usize) -> DiGraph = serde_json::from_str(ROOT_PLUS_2_SIGNING_KEYS).unwrap(); @@ -55,6 +58,8 @@ pub fn init() { false, ) .unwrap(); + let root_plus_2_candidate_signing_jwk: JWK = serde_json::from_str(root_plus_2_candidate_signing_key).unwrap(); + utils_key_manager.save_keys(root_plus_2_candidate_did_suffix, KeyType::SigningKey, &OneOrMany::One(root_plus_2_candidate_signing_jwk), false).unwrap(); }); } diff --git a/trustchain-ffi/Cargo.toml b/trustchain-ffi/Cargo.toml index 42a0e667..d323160c 100644 --- a/trustchain-ffi/Cargo.toml +++ b/trustchain-ffi/Cargo.toml @@ -13,6 +13,7 @@ crate-type = ["cdylib", "staticlib", "lib"] trustchain-core = { path = "../trustchain-core" } trustchain-ion = { path = "../trustchain-ion" } trustchain-api = { path = "../trustchain-api" } +trustchain-http = { path = "../trustchain-http" } anyhow = "1.0" chrono = "0.4.26" diff --git a/trustchain-ffi/src/gui.rs b/trustchain-ffi/src/gui.rs index 31524552..0b0e3324 100644 --- a/trustchain-ffi/src/gui.rs +++ b/trustchain-ffi/src/gui.rs @@ -4,7 +4,7 @@ use tokio::runtime::Runtime; use trustchain_api::{api::TrustchainDIDAPI, TrustchainAPI}; use trustchain_core::chain::DIDChain; use trustchain_core::verifier::Verifier; -use trustchain_ion::{get_ion_resolver, verifier::IONVerifier}; +use trustchain_ion::{get_ion_resolver, verifier::TrustchainVerifier}; /// Example greet function. pub fn greet() -> String { @@ -30,7 +30,7 @@ pub fn verify_prototype(did: String, root_timestamp: u32) -> DIDChain { rt.block_on(async { // Construct a Trustchain Resolver from a Sidetree (ION) DIDMethod. let resolver = get_ion_resolver("http://localhost:3000/"); - let verifier = IONVerifier::new(resolver); + let verifier = TrustchainVerifier::new(resolver); verifier.verify(&did, root_timestamp).await.unwrap() }) diff --git a/trustchain-ffi/src/mobile.rs b/trustchain-ffi/src/mobile.rs index fca1180e..aed6edef 100644 --- a/trustchain-ffi/src/mobile.rs +++ b/trustchain-ffi/src/mobile.rs @@ -277,6 +277,7 @@ pub fn create_operation_mnemonic(mnemonic: String) -> Result { mod tests { use ssi::vc::CredentialOrJWT; use trustchain_core::utils::canonicalize_str; + use trustchain_http::utils::init_http; use crate::config::parse_toml; @@ -418,6 +419,7 @@ mod tests { #[test] #[ignore = "integration test requires ION, MongoDB, IPFS and Bitcoin RPC"] fn test_did_resolve() { + init_http(); let did = "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q".to_string(); let ffi_opts = serde_json::to_string(&parse_toml(TEST_FFI_CONFIG)).unwrap(); did_resolve(did, ffi_opts).unwrap(); @@ -426,6 +428,7 @@ mod tests { #[test] #[ignore = "integration test requires ION, MongoDB, IPFS and Bitcoin RPC"] fn test_did_verify() { + init_http(); let did = "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q".to_string(); let ffi_opts = serde_json::to_string(&parse_toml(TEST_FFI_CONFIG)).unwrap(); did_verify(did, ffi_opts).unwrap(); @@ -434,6 +437,7 @@ mod tests { #[test] #[ignore = "integration test requires ION, MongoDB, IPFS and Bitcoin RPC"] fn test_vc_verify_credential() { + init_http(); let ffi_opts = serde_json::to_string(&parse_toml(TEST_FFI_CONFIG)).unwrap(); let credential: Credential = serde_json::from_str(TEST_CREDENTIAL).unwrap(); vc_verify_credential(serde_json::to_string(&credential).unwrap(), ffi_opts).unwrap(); @@ -506,6 +510,7 @@ mod tests { #[test] #[ignore = "integration test requires ION, MongoDB, IPFS and Bitcoin RPC"] fn test_vp_verify_presentation() { + init_http(); let ffi_opts = serde_json::to_string(&parse_toml(TEST_FFI_CONFIG)).unwrap(); vp_verify_presentation(TEST_PRESENTATION.to_string(), ffi_opts).unwrap(); } diff --git a/trustchain-http/Cargo.toml b/trustchain-http/Cargo.toml index d013025c..cfdde986 100644 --- a/trustchain-http/Cargo.toml +++ b/trustchain-http/Cargo.toml @@ -14,6 +14,7 @@ path = "src/bin/main.rs" trustchain-core = { path = "../trustchain-core" } trustchain-ion = { path = "../trustchain-ion" } trustchain-api = { path = "../trustchain-api" } + async-trait = "0.1" axum = "0.6" axum-server = { version = "0.5.1", features = ["tls-rustls"] } @@ -23,15 +24,20 @@ clap = { version = "^4", features = ["derive", "env", "cargo"] } did-ion = { git = "https://github.com/alan-turing-institute/ssi.git", rev = "1aa3223a384ee71df1333bbce04af445e852eab5" } execute = "0.2.11" gloo-console = "0.2.3" +hex = "0.4.3" hyper = "0.14.26" image = "0.23.14" +is_empty = "0.2.0" +josekit = "0.8" lazy_static = "1.4.0" log = "0.4" qrcode = "0.12.0" +rand = "0.8" reqwest = { version = "0.11.16", features = ["stream"] } serde = { version = "1.0", features = ["derive"] } serde_jcs = "0.1.0" serde_json = "1.0" +# sha2 = "0.10" shellexpand = "3.1.0" ssi = { git = "https://github.com/alan-turing-institute/ssi.git", rev = "1aa3223a384ee71df1333bbce04af445e852eab5", features = [ "http-did", @@ -45,7 +51,10 @@ tower-http = { version = "0.4.0", features = ["map-request-body", "util"] } toml = "0.7.2" tracing = "0.1" tracing-subscriber = "0.3" +serde_with = "3.4.0" uuid = { version = "1.2.2", features = ["v4", "fast-rng", "macro-diagnostics"] } [dev-dependencies] axum-test-helper = "0.2.0" +mockall = "0.11.4" +tempfile = "3.9.0" diff --git a/trustchain-http/src/attestation_encryption_utils.rs b/trustchain-http/src/attestation_encryption_utils.rs new file mode 100644 index 00000000..8127472d --- /dev/null +++ b/trustchain-http/src/attestation_encryption_utils.rs @@ -0,0 +1,178 @@ +use std::collections::HashMap; + +use josekit::jwe::ECDH_ES; +use josekit::jwk::Jwk; +use josekit::jws::{JwsHeader, ES256K}; +use josekit::jwt::{self, JwtPayload}; +use serde_json::Value; +use ssi::did::{Document, VerificationMethod}; +use ssi::jwk::JWK; + +use crate::attestation_utils::TrustchainCRError; + +pub struct Entity {} + +impl SignEncrypt for Entity {} + +impl DecryptVerify for Entity {} + +/// Interface for signing and then encrypting data. +pub trait SignEncrypt { + /// Cryptographically signs a payload with a secret key. + fn sign(&self, payload: &JwtPayload, secret_key: &Jwk) -> Result { + let mut header = JwsHeader::new(); + header.set_token_type("JWT"); + let signer = ES256K.signer_from_jwk(secret_key)?; + let signed_jwt = jwt::encode_with_signer(payload, &header, &signer)?; + Ok(signed_jwt) + } + /// `JWTPayload` is a wrapped [`Map`](https://docs.rs/serde_json/1.0.79/serde_json/struct.Map.html) + /// of claims. + /// Cryptographically encrypts a payload with a public key. + fn encrypt(&self, payload: &JwtPayload, public_key: &Jwk) -> Result { + let mut header = josekit::jwe::JweHeader::new(); + header.set_token_type("JWT"); + header.set_content_encryption("A128CBC-HS256"); + header.set_content_encryption("A256GCM"); + + let encrypter = ECDH_ES.encrypter_from_jwk(public_key)?; + let encrypted_jwt = jwt::encode_with_encrypter(payload, &header, &encrypter)?; + Ok(encrypted_jwt) + } + /// Wrapper function for signing and encrypting a payload. + fn sign_and_encrypt_claim( + &self, + payload: &JwtPayload, + secret_key: &Jwk, + public_key: &Jwk, + ) -> Result { + let signed_payload = self.sign(payload, secret_key)?; + let mut claims = JwtPayload::new(); + claims.set_claim("claim", Some(Value::from(signed_payload)))?; + self.encrypt(&claims, public_key) + } +} +/// Interface for decrypting and then verifying data. +pub trait DecryptVerify { + /// Decrypts a payload with a secret key. + fn decrypt(&self, value: &Value, secret_key: &Jwk) -> Result { + let decrypter = ECDH_ES.decrypter_from_jwk(secret_key)?; + let (payload, _) = jwt::decode_with_decrypter( + value + .as_str() + .ok_or(TrustchainCRError::FailedToConvertToStr(value.clone()))?, + &decrypter, + )?; + Ok(payload) + } + /// Wrapper function that combines decrypting a payload with a secret key and then verifying it with a public key. + fn decrypt_and_verify( + &self, + input: String, + secret_key: &Jwk, + public_key: &Jwk, + ) -> Result { + let decrypter = ECDH_ES.decrypter_from_jwk(secret_key)?; + let (payload, _) = jwt::decode_with_decrypter(input, &decrypter)?; + + let verifier = ES256K.verifier_from_jwk(public_key)?; + let claim = payload + .claim("claim") + .ok_or(TrustchainCRError::ClaimNotFound)?; + let (payload, _) = jwt::decode_with_verifier( + claim + .as_str() + .ok_or(TrustchainCRError::FailedToConvertToStr(claim.clone()))?, + &verifier, + )?; + Ok(payload) + } +} + +/// Converts key from josekit Jwk into ssi JWK +pub fn josekit_to_ssi_jwk(key: &Jwk) -> Result { + let key_as_str: &str = &serde_json::to_string(&key)?; + let ssi_key: JWK = serde_json::from_str(key_as_str)?; + Ok(ssi_key) +} +/// Converts key from ssi JWK into josekit Jwk +pub fn ssi_to_josekit_jwk(key: &JWK) -> Result { + let key_as_str: &str = &serde_json::to_string(&key)?; + let ssi_key: Jwk = serde_json::from_str(key_as_str)?; + Ok(ssi_key) +} + +/// Extracts public keys contained in DID document +pub fn extract_key_ids_and_jwk( + document: &Document, +) -> Result, TrustchainCRError> { + let mut my_map = HashMap::::new(); + if let Some(vms) = &document.verification_method { + // TODO: leave the commented code + // vms.iter().for_each(|vm| match vm { + // VerificationMethod::Map(vm_map) => { + // let id = vm_map.id; + // let key = vm_map.get_jwk().unwrap(); + // let key_jose = ssi_to_josekit_jwk(&key).unwrap(); + // my_map.insert(id, key_jose); + // } + // _ => (), + // }); + // TODO: consider rewriting functional with filter, partition, fold over returned error + // variants. + for vm in vms { + if let VerificationMethod::Map(vm_map) = vm { + let key = vm_map + .get_jwk() + .map_err(|_| TrustchainCRError::MissingJWK)?; + let id = key + .thumbprint() + .map_err(|_| TrustchainCRError::MissingJWK)?; + let key_jose = ssi_to_josekit_jwk(&key).map_err(TrustchainCRError::Serde)?; + my_map.insert(id, key_jose); + } + } + } + Ok(my_map) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::data::{TEST_CANDIDATE_DDID_DOCUMENT, TEST_SIGNING_KEY_1, TEST_SIGNING_KEY_2}; + #[test] + fn test_sign_encrypt_and_decrypt_verify() { + let entity = Entity {}; + let mut payload = JwtPayload::new(); + payload + .set_claim("test", Some(Value::from("This is a test claim."))) + .unwrap(); + // encrypt and sign payload + let secret_key_1: Jwk = serde_json::from_str(TEST_SIGNING_KEY_1).unwrap(); + let secret_key_2: Jwk = serde_json::from_str(TEST_SIGNING_KEY_2).unwrap(); + let public_key_1 = secret_key_1.to_public_key().unwrap(); + let public_key_2 = secret_key_2.to_public_key().unwrap(); + let signed_encrypted_payload = entity + .sign_and_encrypt_claim(&payload, &secret_key_1, &public_key_2) + .unwrap(); + // decrypt and verify payload + let decrypted_verified_payload = entity + .decrypt_and_verify(signed_encrypted_payload, &secret_key_2, &public_key_1) + .unwrap(); + assert_eq!( + decrypted_verified_payload + .claim("test") + .unwrap() + .as_str() + .unwrap(), + "This is a test claim." + ); + } + + #[test] + fn test_extract_key_ids_and_jwk() { + let document: Document = serde_json::from_str(TEST_CANDIDATE_DDID_DOCUMENT).unwrap(); + let key_ids_and_jwk = extract_key_ids_and_jwk(&document).unwrap(); + assert_eq!(key_ids_and_jwk.len(), 2); + } +} diff --git a/trustchain-http/src/attestation_utils.rs b/trustchain-http/src/attestation_utils.rs new file mode 100644 index 00000000..3e1ad9d7 --- /dev/null +++ b/trustchain-http/src/attestation_utils.rs @@ -0,0 +1,1283 @@ +use std::{ + collections::HashMap, + fmt::Display, + fs::{self, File}, + io::{BufWriter, Write}, + path::{Path, PathBuf}, +}; + +// use axum::response::Response; +use is_empty::IsEmpty; +use josekit::JoseError; +use josekit::{jwk::Jwk, jwt::JwtPayload}; +use rand::{distributions::Alphanumeric, thread_rng, Rng}; +use serde::{Deserialize, Serialize}; +use serde_json::{to_string_pretty as to_json, Value}; +use serde_with::skip_serializing_none; +use ssi::{did::Service, jwk::JWK}; +use ssi::{did::ServiceEndpoint, one_or_many::OneOrMany}; +use std::fs::OpenOptions; +use thiserror::Error; +use trustchain_core::{attestor::AttestorError, key_manager::KeyManagerError, TRUSTCHAIN_DATA}; + +#[derive(Error, Debug)] +pub enum TrustchainCRError { + /// Serde JSON error. + #[error("Wrapped serialization error: {0}")] + Serde(serde_json::Error), + /// Wrapped jose error. + #[error("Wrapped jose error: {0}")] + Jose(JoseError), + /// Missing JWK from verification method. + #[error("Missing JWK from verification method of a DID document.")] + MissingJWK, + /// Key not found in hashmap. + #[error("Key id not found.")] + KeyNotFound, + /// Claim not found in JWTPayload. + #[error("Claim not found in JWTPayload.")] + ClaimNotFound, + /// Claim cannot be constructed + #[error("Claim cannot be constructed from: {0}")] + ClaimCannotBeConstructed(String), + /// Nonce type invalid. + #[error("Invalid nonce type.")] + InvalidNonceType, + /// Failed to open file. + #[error("Failed to open file.")] + FailedToOpen, + /// Failed to serialize to file. + #[error("Failed to serialize to file.")] + FailedToSerialize, + /// Failed to set permissions on file. + #[error("Failed to set permissions on file.")] + FailedToSetPermissions, + /// Failed deserialize from file. + #[error("Failed to deserialize.")] + FailedToDeserialize, + /// Value is not a string. + #[error("Value is not a string: {0}")] + FailedToConvertToStr(Value), + /// Failed deserialize from file. + #[error("Failed to deserialize with error: {0}.")] + FailedToDeserializeWithError(serde_json::Error), + #[error("Wrapped SSI JWK error: {0}.")] + WrappedSSIJWKError(ssi::jwk::Error), + /// Failed to check CR status. + #[error("Failed to determine CR status.")] + FailedStatusCheck, + /// Path for CR does not exist. + #[error("Path does not exist. No challenge-response record for this temporary key id.")] + CRPathNotFound, + /// Failed to generate key. + #[error("Failed to generate key.")] + FailedToGenerateKey, + /// Reqwest error. + #[error("Network request failed.")] + Reqwest(reqwest::Error), + /// Invalid service endpoint. + #[error("Invalid service endpoint.")] + InvalidServiceEndpoint, + /// CR initiation failed + #[error("Failed to initiate challenge-response.")] + FailedToInitiateCR, + /// Failed attestation request + #[error("Failed attestation request.")] + FailedAttestationRequest, + /// Field of struct not found + #[error("Field not found.")] + FieldNotFound, + /// Field to respond + #[error("Response to challenge failed.")] + FailedToRespond(reqwest::Response), + /// Failed to verify nonce + #[error("Failed to verify nonce.")] + FailedToVerifyNonce, + /// Wrapped IO error + #[error("IO error: {0}")] + IOError(std::io::Error), + /// Wrapped KeyManager error + #[error("KeyManager error: {0}")] + KeyManagerError(#[from] KeyManagerError), + /// Wrapped Attestor error + #[error("Attestor error: {0}")] + AttestorError(#[from] AttestorError), + /// Wrapped SSI JWK error + #[error("SSI JWK error: {0}")] + SSIJwkError(#[from] ssi::jwk::Error), + /// Response from a `CustomResponse` must contain data + #[error("Must contain data but custom response contained no data")] + ResponseMustContainData, +} + +impl From for TrustchainCRError { + fn from(err: JoseError) -> Self { + Self::Jose(err) + } +} + +#[derive(Serialize, Deserialize)] +/// Type for implementing custom response returned by the server. Provides a message and optional data field. +pub struct CustomResponse { + pub message: String, + pub data: Option, +} + +#[derive(Debug, PartialEq)] +/// Enumerates the possible states of the challenge-response process. +pub enum CurrentCRState { + NotStarted, + IdentityCRInitiated, + IdentityChallengeComplete, + IdentityResponseComplete, + ContentCRInitiated, + ContentChallengeComplete, + ContentResponseComplete, +} + +// TODO: Impose additional constraints on the nonce type. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +/// Nonce type for challenge-response. +pub struct Nonce(String); + +impl Default for Nonce { + fn default() -> Self { + Self::new() + } +} + +impl Nonce { + pub fn new() -> Self { + Self( + thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(), + ) + } +} + +impl AsRef for Nonce { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl Display for Nonce { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for Nonce { + fn from(s: String) -> Self { + Self(s) + } +} + +impl TryFrom<&Nonce> for JwtPayload { + type Error = TrustchainCRError; + fn try_from(value: &Nonce) -> Result { + let mut payload = JwtPayload::new(); + payload.set_claim("nonce", Some(Value::from(value.to_string())))?; + Ok(payload) + } +} + +impl From for TrustchainCRError { + fn from(value: serde_json::Error) -> Self { + TrustchainCRError::FailedToDeserializeWithError(value) + } +} + +/// Interface for serializing and deserializing each field of structs to/from files. +pub trait ElementwiseSerializeDeserialize +where + Self: Serialize, +{ + /// Serialize each field of the struct to a file. + fn elementwise_serialize(&self, path: &PathBuf) -> Result<(), TrustchainCRError> { + let serialized = serde_json::to_value(self)?; + if let Value::Object(fields) = serialized { + for (field_name, field_value) in fields { + if !field_value.is_null() { + let json_filename = format!("{}.json", field_name); + let file_path = path.join(json_filename); + self.save_to_file(&file_path, &to_json(&field_value)?)?; + } + } + } + Ok(()) + } + /// Deserializes each field of the struct from a file. + fn elementwise_deserialize(self, path: &PathBuf) -> Result, TrustchainCRError> + where + Self: Sized; + /// Save data to file. If file already exists, do nothing. + fn save_to_file(&self, path: &PathBuf, data: &str) -> Result<(), TrustchainCRError> { + if path.exists() { + println!("File already exists: {:?}", path); + return Ok(()); + } + + // Open the new file if it doesn't exist yet + let new_file = OpenOptions::new() + .create(true) + .append(false) + .truncate(false) + .write(true) + .open(path); + + // Write key to file + match new_file { + Ok(file) => { + let mut writer = BufWriter::new(file); + match writer.write_all(data.as_bytes()) { + Ok(_) => { + // Set file permissions to read-only (user, group, and others) + let mut permissions = fs::metadata(path) + .map_err(|_| TrustchainCRError::FailedToSetPermissions)? + .permissions(); + permissions.set_readonly(true); + fs::set_permissions(path, permissions) + .map_err(|_| TrustchainCRError::FailedToSetPermissions)?; + Ok(()) + } + Err(_) => Err(TrustchainCRError::FailedToSerialize), + } + } + + Err(_) => Err(TrustchainCRError::FailedToSerialize), + } + } +} + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone)] +/// Type for storing details of the requester. +pub struct RequesterDetails { + pub requester_org: String, + pub operator_name: String, +} + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, IsEmpty, Clone)] +/// Type for storing initiation details of the attestation request. +pub struct IdentityCRInitiation { + pub temp_p_key: Option, + pub temp_s_key: Option, + pub requester_details: Option, +} + +impl Default for IdentityCRInitiation { + fn default() -> Self { + Self::new() + } +} + +impl IdentityCRInitiation { + pub fn new() -> Self { + Self { + temp_p_key: None, + temp_s_key: None, + requester_details: None, + } + } + /// Returns true if all fields required for the initiation have a non-null value. + /// Note: temp_s_key is optional since only requester has it. + pub fn is_complete(&self) -> bool { + self.temp_p_key.is_some() && self.requester_details.is_some() + } + + pub fn temp_p_key(&self) -> Result<&Jwk, TrustchainCRError> { + self.temp_p_key + .as_ref() + .ok_or(TrustchainCRError::KeyNotFound) + } + pub fn temp_s_key(&self) -> Result<&Jwk, TrustchainCRError> { + self.temp_s_key + .as_ref() + .ok_or(TrustchainCRError::KeyNotFound) + } +} + +impl ElementwiseSerializeDeserialize for IdentityCRInitiation { + /// Deserialize each field of the struct from a file. Fields are optional. If no files are found, return None. + fn elementwise_deserialize( + mut self, + path: &PathBuf, + ) -> Result, TrustchainCRError> { + let temp_p_key_path = path.join("temp_p_key.json"); + // TODO: refactor with e.g. std::fs::read_to_string + self.temp_p_key = match File::open(temp_p_key_path) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + let deserialized = serde_json::from_reader(reader)?; + Some(deserialized) + } + Err(_) => None, + }; + // TODO: complete refactor + // if !Path::new(&temp_p_key_path).exists() { + // self.temp_p_key = None; + // } + // let deserialized = serde_json::from_str( + // &fs::read_to_string(&temp_p_key_path) + // .map_err(|_| TrustchainCRError::FailedToDeserialize)?, + // ) + // .map_err(|_| TrustchainCRError::FailedToDeserialize)?; + // self.temp_p_key = Some(deserialized); + + let temp_s_key_path = path.join("temp_s_key.json"); + self.temp_s_key = match File::open(temp_s_key_path) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + let deserialized = serde_json::from_reader(reader)?; + Some(deserialized) + } + Err(_) => None, + }; + + let requester_details_path = path.join("requester_details.json"); + self.requester_details = match File::open(requester_details_path) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + let deserialized = serde_json::from_reader(reader)?; + Some(deserialized) + } + Err(_) => None, + }; + + if self.temp_p_key.is_none() + && self.temp_s_key.is_none() + && self.requester_details.is_none() + { + return Ok(None); + } + + Ok(Some(self)) + } +} + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone, IsEmpty)] +/// Type for storing details of part one (identity challenge) of the challenge-response process. +pub struct IdentityCRChallenge { + pub update_p_key: Option, + pub update_s_key: Option, + pub identity_nonce: Option, // make own Nonce type + /// Encrypted identity challenge, signed by the attestor. + pub identity_challenge_signature: Option, + pub identity_response_signature: Option, +} + +impl Default for IdentityCRChallenge { + fn default() -> Self { + Self::new() + } +} + +impl IdentityCRChallenge { + pub fn new() -> Self { + Self { + update_p_key: None, + update_s_key: None, + identity_nonce: None, + identity_challenge_signature: None, + identity_response_signature: None, + } + } + /// Returns true if all fields required for the challenge have a non-null value. + /// Note: update_s_key is optional since only attestor has it. + fn challenge_complete(&self) -> bool { + self.update_p_key.is_some() + && self.identity_nonce.is_some() + && self.identity_challenge_signature.is_some() + } + /// Returns true if challenge-response is complete. + fn is_complete(&self) -> bool { + self.challenge_complete() && self.identity_response_signature.is_some() + } +} + +impl ElementwiseSerializeDeserialize for IdentityCRChallenge { + /// Deserialize each field of the struct from a file. Fields are optional. If no files are found, return None. + fn elementwise_deserialize( + mut self, + path: &PathBuf, + ) -> Result, TrustchainCRError> { + // update public key + let full_path = path.join("update_p_key.json"); + self.update_p_key = match File::open(full_path) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + let deserialized = serde_json::from_reader(reader)?; + Some(deserialized) + } + Err(_) => None, + }; + // update secret key + let mut full_path = path.join("update_s_key.json"); + self.update_s_key = match File::open(&full_path) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + let deserialized = serde_json::from_reader(reader)?; + Some(deserialized) + } + Err(_) => None, + }; + // identity nonce + full_path = path.join("identity_nonce.json"); + self.identity_nonce = match File::open(&full_path) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + let deserialized = serde_json::from_reader(reader)?; + Some(deserialized) + } + Err(_) => None, + }; + // identity challenge signature + full_path = path.join("identity_challenge_signature.json"); + self.identity_challenge_signature = match File::open(&full_path) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + let deserialized = serde_json::from_reader(reader)?; + Some(deserialized) + } + Err(_) => None, + }; + // identity response signature + full_path = path.join("identity_response_signature.json"); + self.identity_response_signature = match File::open(&full_path) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + let deserialized = serde_json::from_reader(reader)?; + Some(deserialized) + } + Err(_) => None, + }; + + if self.update_p_key.is_none() + && self.identity_nonce.is_none() + && self.identity_challenge_signature.is_none() + && self.identity_response_signature.is_none() + { + return Ok(None); + } + + Ok(Some(self)) + } +} + +impl TryFrom<&IdentityCRChallenge> for JwtPayload { + type Error = TrustchainCRError; + fn try_from(value: &IdentityCRChallenge) -> Result { + let mut payload = JwtPayload::new(); + payload.set_claim( + "identity_nonce", + Some(Value::from( + value + .identity_nonce + .as_ref() + .ok_or(TrustchainCRError::ClaimCannotBeConstructed( + "`identity_nonce` field in `IdentityCRChallenge` is missing (`None`)" + .to_string(), + ))? + .to_string(), + )), + )?; + payload.set_claim( + "update_p_key", + Some(Value::from( + value + .update_p_key + .as_ref() + .ok_or(TrustchainCRError::ClaimCannotBeConstructed( + "`update_p_key` field in `IdentityCRChallenge` is missing (`None`)" + .to_string(), + ))? + .to_string(), + )), + )?; + Ok(payload) + } +} + +impl TryFrom<&JwtPayload> for IdentityCRChallenge { + type Error = TrustchainCRError; + fn try_from(value: &JwtPayload) -> Result { + let mut challenge = IdentityCRChallenge { + update_p_key: None, + update_s_key: None, + identity_nonce: None, + identity_challenge_signature: None, + identity_response_signature: None, + }; + challenge.update_p_key = Some(serde_json::from_str( + value + .claim("update_p_key") + .ok_or(TrustchainCRError::ClaimNotFound)? + .as_str() + .ok_or(TrustchainCRError::FailedToConvertToStr( + // Unwrap: not None since error would have propagated above if None + value.claim("update_p_key").unwrap().clone(), + ))?, + )?); + challenge.identity_nonce = Some(Nonce::from( + // TODO: refactor into function for a given payload and claim field, + // returns a Result + value + .claim("identity_nonce") + .ok_or(TrustchainCRError::ClaimNotFound)? + .as_str() + .ok_or(TrustchainCRError::FailedToConvertToStr( + // Unwrap: not None since error would have propagated above if None + value.claim("identity_nonce").unwrap().clone(), + ))? + .to_string(), + )); + Ok(challenge) + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, IsEmpty)] +/// Type for storing initiation details of part two (content challenge) of the challenge-response process. +pub struct ContentCRInitiation { + pub requester_did: Option, +} + +impl Default for ContentCRInitiation { + fn default() -> Self { + Self::new() + } +} + +impl ContentCRInitiation { + pub fn new() -> Self { + Self { + requester_did: None, + } + } + + fn is_complete(&self) -> bool { + self.requester_did.is_some() + } +} + +impl ElementwiseSerializeDeserialize for ContentCRInitiation { + /// Deserialize each field of the struct from a file. Fields are optional. If no files are found, return None. + fn elementwise_deserialize( + mut self, + path: &PathBuf, + ) -> Result, TrustchainCRError> { + let requester_details_path = path.join("requester_did.json"); + self.requester_did = match File::open(requester_details_path) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + let deserialized = serde_json::from_reader(reader)?; + Some(deserialized) + } + Err(_) => None, + }; + + if self.requester_did.is_none() { + return Ok(None); + } + + Ok(Some(self)) + } +} + +#[derive(Debug, Serialize, Deserialize, IsEmpty)] +/// Type for storing details of part two (content challenge) of the challenge-response process. +pub struct ContentCRChallenge { + pub content_nonce: Option>, + pub content_challenge_signature: Option, + pub content_response_signature: Option, +} + +impl Default for ContentCRChallenge { + fn default() -> Self { + Self::new() + } +} + +impl ContentCRChallenge { + pub fn new() -> Self { + Self { + content_nonce: None, + content_challenge_signature: None, + content_response_signature: None, + } + } + /// Returns true if all fields required for the challenge have a non-null value. + fn challenge_complete(&self) -> bool { + self.content_nonce.is_some() && self.content_challenge_signature.is_some() + } + /// Returns true if all fields required for the challenge-response have a non-null value. + fn is_complete(&self) -> bool { + self.challenge_complete() && self.content_response_signature.is_some() + } +} + +impl ElementwiseSerializeDeserialize for ContentCRChallenge { + /// Deserialize each field of the struct from a file. Fields are optional. If no files are found, return None. + fn elementwise_deserialize( + mut self, + path: &PathBuf, + ) -> Result, TrustchainCRError> { + // content nonce(s) + let mut full_path = path.join("content_nonce.json"); + self.content_nonce = match File::open(&full_path) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + let deserialized = serde_json::from_reader(reader)?; + Some(deserialized) + } + Err(_) => None, + }; + + // content challenge signature + full_path = path.join("content_challenge_signature.json"); + self.content_challenge_signature = match File::open(&full_path) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + let deserialized = serde_json::from_reader(reader)?; + Some(deserialized) + } + Err(_) => None, + }; + // content response signature + full_path = path.join("content_response_signature.json"); + self.content_response_signature = match File::open(&full_path) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + let deserialized = serde_json::from_reader(reader)?; + Some(deserialized) + } + Err(_) => None, + }; + + if self.content_nonce.is_none() + && self.content_challenge_signature.is_none() + && self.content_response_signature.is_none() + { + return Ok(None); + } + + Ok(Some(self)) + } +} + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, IsEmpty)] +/// Type for representing the state of the challenge-response process. Holds information about both +/// identity (part one) and content challenge-response (part two) and their respective initiation. +pub struct CRState { + pub identity_cr_initiation: Option, + pub identity_challenge_response: Option, + pub content_cr_initiation: Option, + pub content_challenge_response: Option, +} + +impl Default for CRState { + fn default() -> Self { + Self::new() + } +} + +impl CRState { + pub fn new() -> Self { + Self { + identity_cr_initiation: None, + identity_challenge_response: None, + content_cr_initiation: None, + content_challenge_response: None, + } + } + /// Returns true if all fields are complete. + pub fn is_complete(&self) -> bool { + if let (Some(ici), Some(icr), Some(cci), Some(ccr)) = ( + self.identity_cr_initiation.as_ref(), + self.identity_challenge_response.as_ref(), + self.content_cr_initiation.as_ref(), + self.content_challenge_response.as_ref(), + ) { + return ici.is_complete() + && icr.is_complete() + && cci.is_complete() + && ccr.is_complete(); + } + false + } + /// Determines current status of the challenge response process and accordingly prints messages to the console. + pub fn check_cr_status(&self) -> Result { + println!("Checking current challenge-response status..."); + println!(" "); + let mut current_state = CurrentCRState::NotStarted; + if self.is_empty() { + println!("{}", get_status_message(¤t_state)); + return Ok(current_state); + } + + // CR complete + if self.is_complete() { + current_state = CurrentCRState::ContentResponseComplete; + println!("{}", get_status_message(¤t_state)); + return Ok(current_state); + } + + // Identity CR initation + if self.identity_cr_initiation.is_none() + // Unwrap: first condition ensures is not None + || !self.identity_cr_initiation.as_ref().unwrap().is_complete() + { + println!("{}", get_status_message(¤t_state)); + return Ok(current_state); + } + current_state = CurrentCRState::IdentityCRInitiated; + println!("{}", get_status_message(¤t_state)); + + // Identity challenge + if self.identity_challenge_response.is_none() + // Unwrap: first condition ensures is not None + || !self + .identity_challenge_response + .as_ref() + .unwrap() + .challenge_complete() + { + return Ok(current_state); + } + current_state = CurrentCRState::IdentityChallengeComplete; + println!("{}", get_status_message(¤t_state)); + + // Identity response + if self + .identity_challenge_response + .is_none() + // Unwrap: first condition ensures is not None + || !self + .identity_challenge_response + .as_ref() + .unwrap() + .is_complete() + { + return Ok(current_state); + } + current_state = CurrentCRState::IdentityResponseComplete; + + // Content CR initation + if self.content_cr_initiation.is_none() + // Unwrap: first condition ensures is not None + || !self.content_cr_initiation.as_ref().unwrap().is_complete() + { + return Ok(current_state); + } + current_state = CurrentCRState::ContentCRInitiated; + + // Content challenge + if self.content_challenge_response.is_none() + // Unwrap: first condition ensures is not None + || !self + .content_challenge_response + .as_ref() + .unwrap() + .challenge_complete() + { + return Ok(current_state); + } + current_state = CurrentCRState::ContentChallengeComplete; + + // Content response + if self.content_challenge_response.is_none() + // Unwrap: first condition ensures is not None + || !self + .content_challenge_response + .as_ref() + .unwrap() + .is_complete() + { + return Ok(current_state); + } + + Ok(current_state) + } +} + +impl ElementwiseSerializeDeserialize for CRState { + /// Serialize each field of the struct to a file. Fields with null values are ignored. + fn elementwise_serialize(&self, path: &PathBuf) -> Result<(), TrustchainCRError> { + if let Some(identity_initiation) = &self.identity_cr_initiation { + identity_initiation.elementwise_serialize(path)?; + } + if let Some(identity_challenge_response) = &self.identity_challenge_response { + identity_challenge_response.elementwise_serialize(path)?; + } + if let Some(content_cr_initiation) = &self.content_cr_initiation { + content_cr_initiation.elementwise_serialize(path)?; + } + if let Some(content_challenge_response) = &self.content_challenge_response { + content_challenge_response.elementwise_serialize(path)?; + } + Ok(()) + } + /// Deserialize each field of the struct from a file. All fields are optional. + fn elementwise_deserialize( + mut self, + path: &PathBuf, + ) -> Result, TrustchainCRError> { + self.identity_cr_initiation = IdentityCRInitiation::new().elementwise_deserialize(path)?; + self.identity_challenge_response = + IdentityCRChallenge::new().elementwise_deserialize(path)?; + self.content_cr_initiation = ContentCRInitiation::new().elementwise_deserialize(path)?; + self.content_challenge_response = + ContentCRChallenge::new().elementwise_deserialize(path)?; + Ok(Some(self)) + } +} + +/// Returns message that corresponds to the current state of the challenge-response process. +fn get_status_message(current_state: &CurrentCRState) -> String { + match current_state { + CurrentCRState::NotStarted => { + String::from("No records found for this challenge-response identifier or entity. \nThe challenge-response process has not been initiated yet.") + } + CurrentCRState::IdentityCRInitiated => { + String::from("Identity challenge-response initiated. Await response.") + } + CurrentCRState::IdentityChallengeComplete => { + String::from("Identity challenge has been presented. Await response.") + } + CurrentCRState::IdentityResponseComplete => { + String::from("Identity challenge-response complete.") + } + CurrentCRState::ContentCRInitiated => { + String::from("Content challenge-response initiated. Await response.") + } + CurrentCRState::ContentChallengeComplete => { + String::from("Content challenge has been presented. Await response.") + } + CurrentCRState::ContentResponseComplete => { + String::from("Challenge-response complete.") + } + } +} + +/// Returns endpoint that contains the given fragment from the given list of service endpoints. +/// Throws error if no or more than one matching endpoint is found. +pub fn matching_endpoint( + services: &[Service], + fragment: &str, +) -> Result { + let mut endpoints = Vec::new(); + for service in services { + if service.id.eq(fragment) { + match &service.service_endpoint { + Some(OneOrMany::One(ServiceEndpoint::URI(uri))) => { + endpoints.push(uri.to_string()); + } + + _ => return Err(TrustchainCRError::InvalidServiceEndpoint), + } + } + } + if endpoints.len() != 1 { + return Err(TrustchainCRError::InvalidServiceEndpoint); + } + Ok(endpoints[0].clone()) +} + +/// Returns unique path name for a specific attestation request derived from public key for the interaction. +pub fn attestation_request_path(key: &JWK, prefix: &str) -> Result { + // Root path in TRUSTCHAIN_DATA + let path = attestation_request_basepath(prefix)?; + let key_id = key.thumbprint()?; // Use hash of temp_pub_key + Ok(path.join(key_id)) +} + +/// Returns the root path for storing attestation requests. +pub fn attestation_request_basepath(prefix: &str) -> Result { + // Root path in TRUSTCHAIN_DATA + let path: String = std::env::var(TRUSTCHAIN_DATA) + .expect("`TRUSTCHAIN_DATA` environment variable must be set."); + Ok(Path::new(path.as_str()) + .join(prefix) + .join("attestation_requests")) +} + +#[cfg(test)] +mod tests { + use crate::attestation_encryption_utils::extract_key_ids_and_jwk; + use crate::data::{TEST_CANDIDATE_DDID_DOCUMENT, TEST_TEMP_KEY, TEST_UPDATE_KEY}; + use ssi::did::Document; + use tempfile::tempdir; + + use super::*; + + #[test] + fn test_elementwise_serialize() { + // ==========| Identity CR | ============== + let temp_s_key: Jwk = serde_json::from_str(TEST_TEMP_KEY).unwrap(); + let initiation = IdentityCRInitiation { + temp_p_key: None, + temp_s_key: Some(temp_s_key.to_public_key().unwrap()), + requester_details: Some(RequesterDetails { + requester_org: String::from("My Org"), + operator_name: String::from("John Doe"), + }), + }; + + // identity challenge + let identity_challenge = IdentityCRChallenge { + update_p_key: serde_json::from_str(TEST_UPDATE_KEY).unwrap(), + update_s_key: None, + identity_nonce: Some(Nonce::new()), + identity_challenge_signature: Some(String::from("some challenge signature string")), + identity_response_signature: Some(String::from("some response signature string")), + }; + + // ==========| Content CR | ============== + let content_initiation = ContentCRInitiation { + // temp_p_key: Some(temp_s_key.to_public_key().unwrap()), + requester_did: Some("did:example:123456789abcdefghi".to_string()), + }; + // get signing keys for DE from did document + let doc: Document = serde_json::from_str(TEST_CANDIDATE_DDID_DOCUMENT).unwrap(); + let test_keys_map = extract_key_ids_and_jwk(&doc).unwrap(); + + // generate map with unencrypted nonces so UE can store them for later verification + let nonces: HashMap = + test_keys_map + .iter() + .fold(HashMap::new(), |mut acc, (key_id, _)| { + acc.insert(String::from(key_id), Nonce::new()); + acc + }); + let content_challenge_response = ContentCRChallenge { + content_nonce: Some(nonces), + content_challenge_signature: Some(String::from( + "some content challenge signature string", + )), + content_response_signature: Some(String::from( + "some content response signature string", + )), + }; + + // ==========| CR state | ============== + let cr_state = CRState { + identity_cr_initiation: Some(initiation), + identity_challenge_response: Some(identity_challenge), + content_cr_initiation: Some(content_initiation), + content_challenge_response: Some(content_challenge_response), + }; + // write to file + let path = tempdir().unwrap().into_path(); + let result = cr_state.elementwise_serialize(&path); + assert!(result.is_ok()); + + // try to write to file again + let result = cr_state.elementwise_serialize(&path); + assert!(result.is_ok()); + } + + #[test] + fn test_elementwise_deserialize_initiation() { + let cr_initiation = IdentityCRInitiation::new(); + let temp_path = tempdir().unwrap().into_path(); + + // Test case 1: None of the json files exist + let result = cr_initiation.elementwise_deserialize(&temp_path); + assert!(result.is_ok()); + let initiation = result.unwrap(); + assert!(initiation.is_none()); + + // Test case 2: Only one json file exists and can be deserialized + let cr_initiation = IdentityCRInitiation::new(); + let temp_p_key_path = temp_path.join("temp_p_key.json"); + let temp_p_key_file = File::create(&temp_p_key_path).unwrap(); + let temp_p_key: Jwk = serde_json::from_str(TEST_TEMP_KEY).unwrap(); + serde_json::to_writer(temp_p_key_file, &temp_p_key).unwrap(); + + let result = cr_initiation.elementwise_deserialize(&temp_path); + assert!(result.is_ok()); + let initiation = result.unwrap().unwrap(); + assert!(initiation.temp_s_key.is_none()); + assert!(initiation.temp_p_key.is_some()); + assert!(initiation.requester_details.is_none()); + + // Test case 3: Both json files exist and can be deserialized + let cr_initiation = IdentityCRInitiation::new(); + let requester_details_path = temp_path.join("requester_details.json"); + let requester_details_file = File::create(requester_details_path).unwrap(); + let requester_details = RequesterDetails { + requester_org: String::from("My Org"), + operator_name: String::from("John Doe"), + }; + serde_json::to_writer(requester_details_file, &requester_details).unwrap(); + let result = cr_initiation.elementwise_deserialize(&temp_path); + assert!(result.is_ok()); + let initiation = result.unwrap().unwrap(); + assert!(initiation.temp_p_key.is_some()); + assert!(initiation.requester_details.is_some()); + + // Test case 4: Both json files exist but one is invalid json and cannot be + // deserialized + let cr_initiation = IdentityCRInitiation::new(); + // override temp key with invalid key + let temp_p_key_file = File::create(&temp_p_key_path).unwrap(); + serde_json::to_writer(temp_p_key_file, "this is not valid json").unwrap(); + let result = cr_initiation.elementwise_deserialize(&temp_path); + assert!(result.is_err()); + } + + #[test] + fn test_elementwise_deserialize_identity_challenge() { + let identity_challenge = IdentityCRChallenge::new(); + let temp_path = tempdir().unwrap().into_path(); + + // Test case 1: None of the json files exist + let result = identity_challenge.elementwise_deserialize(&temp_path); + assert!(result.is_ok()); + let identity_challenge = result.unwrap(); + assert!(identity_challenge.is_none()); + + // Test case 2: Only one json file exists and can be deserialized + let update_p_key_path = temp_path.join("update_p_key.json"); + let update_p_key_file = File::create(update_p_key_path).unwrap(); + let update_p_key: Jwk = serde_json::from_str(TEST_UPDATE_KEY).unwrap(); + serde_json::to_writer(update_p_key_file, &update_p_key).unwrap(); + let identity_challenge = IdentityCRChallenge::new(); + let result = identity_challenge.elementwise_deserialize(&temp_path); + assert!(result.is_ok()); + let identity_challenge = result.unwrap().unwrap(); + assert_eq!(identity_challenge.update_p_key, Some(update_p_key)); + assert!(identity_challenge.identity_nonce.is_none()); + assert!(identity_challenge.identity_challenge_signature.is_none()); + assert!(identity_challenge.identity_response_signature.is_none()); + + // Test case 3: One file exists but cannot be deserialized + let identity_nonce_path = temp_path.join("identity_nonce.json"); + let identity_nonce_file = File::create(identity_nonce_path).unwrap(); + serde_json::to_writer(identity_nonce_file, &42).unwrap(); + let identity_challenge = IdentityCRChallenge::new(); + let result = identity_challenge.elementwise_deserialize(&temp_path); + assert!(result.is_err()); + println!("Error: {:?}", result.unwrap_err()); + } + + #[test] + fn test_elementwise_deserialize_content_challenge() { + let content_challenge = ContentCRChallenge::new(); + let temp_path = tempdir().unwrap().into_path(); + + // Test case 1: None of the json files exist + let result = content_challenge.elementwise_deserialize(&temp_path); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); + + // Test case 2: Only one json file exists and can be deserialized + let content_challenge = ContentCRChallenge::new(); + let content_nonce_path = temp_path.join("content_nonce.json"); + let content_nonce_file = File::create(&content_nonce_path).unwrap(); + let mut nonces_map: HashMap<&str, Nonce> = HashMap::new(); + nonces_map.insert("test_id", Nonce::new()); + serde_json::to_writer(content_nonce_file, &nonces_map).unwrap(); + let result = content_challenge.elementwise_deserialize(&temp_path); + assert!(result.is_ok()); + let content_challenge = result.unwrap().unwrap(); + assert!(content_challenge.content_nonce.is_some()); + assert!(content_challenge.content_challenge_signature.is_none()); + assert!(content_challenge.content_response_signature.is_none()); + + // Test case 3: One file exists but cannot be deserialized + let content_nonce_file = File::create(&content_nonce_path).unwrap(); + serde_json::to_writer(content_nonce_file, "thisisinvalid").unwrap(); + let result = content_challenge.elementwise_deserialize(&temp_path); + print!("Result: {:?}", result); + assert!(result.is_err()); + } + + #[test] + fn test_deserialize_challenge_state() { + let path = tempdir().unwrap().into_path(); + let challenge_state = CRState::new(); + + // Test case 1: some files exist and can be deserialised + let identity_initiatiation = IdentityCRInitiation { + temp_s_key: Some(serde_json::from_str(TEST_TEMP_KEY).unwrap()), + temp_p_key: None, + requester_details: Some(RequesterDetails { + requester_org: String::from("My Org"), + operator_name: String::from("John Doe"), + }), + }; + let _ = identity_initiatiation.elementwise_serialize(&path); + let identity_challenge = IdentityCRChallenge { + update_p_key: Some(serde_json::from_str(TEST_UPDATE_KEY).unwrap()), + update_s_key: Some(serde_json::from_str(TEST_UPDATE_KEY).unwrap()), + identity_nonce: Some(Nonce::new()), + identity_challenge_signature: Some(String::from("some challenge signature string")), + identity_response_signature: Some(String::from("some response signature string")), + }; + let _ = identity_challenge.elementwise_serialize(&path); + + let content_cr_initiation = ContentCRInitiation { + // temp_p_key: Some(serde_json::from_str(TEST_TEMP_KEY).unwrap()), + requester_did: Some("did:example:123456789abcdefghi".to_string()), + }; + let _ = content_cr_initiation.elementwise_serialize(&path); + + let result = challenge_state.elementwise_deserialize(&path); + assert!(result.is_ok()); + let challenge_state = result.unwrap().unwrap(); + println!( + "Challenge state deserialized from files: {:?}", + challenge_state + ); + assert!(challenge_state.identity_cr_initiation.is_some()); + assert!(challenge_state.identity_challenge_response.is_some()); + assert!(challenge_state.content_cr_initiation.is_some()); + assert!(challenge_state.content_challenge_response.is_none()); + + // Test case 2: one file cannot be deserialized + let identity_nonce_path = path.join("content_nonce.json"); + let identity_nonce_file = File::create(identity_nonce_path).unwrap(); + serde_json::to_writer(identity_nonce_file, &42).unwrap(); + let challenge_state = CRState::new().elementwise_deserialize(&path); + assert!(challenge_state.is_err()); + } + + #[test] + fn test_matching_endpoint() { + let services = vec![ + Service { + id: String::from("#service-1"), + service_endpoint: Some(OneOrMany::One(ServiceEndpoint::URI(String::from( + "https://example.com/endpoint-1", + )))), + type_: ssi::one_or_many::OneOrMany::One("Service1".to_string()), + property_set: None, + }, + Service { + id: String::from("#service-2"), + service_endpoint: Some(OneOrMany::One(ServiceEndpoint::URI(String::from( + "https://example.com/endpoint-2", + )))), + type_: ssi::one_or_many::OneOrMany::One("Service2".to_string()), + property_set: None, + }, + ]; + let result = matching_endpoint(&services, "#service-1"); + assert_eq!(result.unwrap(), "https://example.com/endpoint-1"); + let result = matching_endpoint(&services, "service-1"); + assert!(result.is_err()); + } + + #[test] + fn test_matching_endpoint_multiple_endpoints_found() { + // Test case: multiple endpoints found should throw error + let services = vec![ + Service { + id: String::from("#service-1"), + service_endpoint: Some(OneOrMany::One(ServiceEndpoint::URI(String::from( + "https://example.com/endpoint-1", + )))), + type_: ssi::one_or_many::OneOrMany::One("Service1".to_string()), + property_set: None, + }, + Service { + id: String::from("#service-1"), + service_endpoint: Some(OneOrMany::One(ServiceEndpoint::URI(String::from( + "https://example.com/endpoint-2", + )))), + type_: ssi::one_or_many::OneOrMany::One("Service1".to_string()), + property_set: None, + }, + ]; + let result = matching_endpoint(&services, "#service-1"); + assert!(result.is_err()); + } + + #[test] + fn test_check_cr_status() { + let mut cr_state = CRState::new(); + // Test case 1: CR State is empty + let result = cr_state.check_cr_status().unwrap(); + assert_eq!(result, CurrentCRState::NotStarted); + + // Test case 2: some, but not all, initation information exists + cr_state.identity_cr_initiation = Some(IdentityCRInitiation { + // Same key used here for testing purposes + temp_p_key: Some(serde_json::from_str(TEST_TEMP_KEY).unwrap()), + temp_s_key: None, + requester_details: None, + }); + let result = cr_state.check_cr_status(); + assert_eq!(result.unwrap(), CurrentCRState::NotStarted); + + // Test case 3: identity initiation completed, identity challenge presented + cr_state.identity_cr_initiation = Some(IdentityCRInitiation { + // Same key used here for testing purposes + temp_p_key: Some(serde_json::from_str(TEST_TEMP_KEY).unwrap()), + temp_s_key: None, + requester_details: Some(RequesterDetails { + requester_org: String::from("My Org"), + operator_name: String::from("John Doe"), + }), + }); + cr_state.identity_challenge_response = Some(IdentityCRChallenge { + update_p_key: Some(serde_json::from_str(TEST_UPDATE_KEY).unwrap()), + update_s_key: None, + identity_nonce: Some(Nonce::new()), + identity_challenge_signature: Some(String::from("some challenge signature string")), + identity_response_signature: None, + }); + let result = cr_state.check_cr_status(); + assert_eq!(result.unwrap(), CurrentCRState::IdentityChallengeComplete); + + // Test case 4: Identity challenge response complete, content challenge initiated + cr_state.identity_challenge_response = Some(IdentityCRChallenge { + // Same key used here for testing purposes + update_p_key: Some(serde_json::from_str(TEST_UPDATE_KEY).unwrap()), + update_s_key: None, + identity_nonce: Some(Nonce::new()), + identity_challenge_signature: Some(String::from("some challenge signature string")), + identity_response_signature: Some(String::from("some response signature string")), + }); + cr_state.content_cr_initiation = { + Some(ContentCRInitiation { + requester_did: Some("did:example:123456789abcdefghi".to_string()), + }) + }; + let result = cr_state.check_cr_status(); + assert_eq!(result.unwrap(), CurrentCRState::ContentCRInitiated); + + // Test case 5: Content challenge-response complete + cr_state.content_challenge_response = Some(ContentCRChallenge { + content_nonce: Some(HashMap::new()), + content_challenge_signature: Some(String::from( + "some content challenge signature string", + )), + content_response_signature: Some(String::from( + "some content response signature string", + )), + }); + let result = cr_state.check_cr_status(); + assert_eq!(result.unwrap(), CurrentCRState::ContentResponseComplete); + } + #[test] + fn test_check_cr_status_inconsistent_order() { + let mut cr_state = CRState::new(); + cr_state.identity_challenge_response = Some(IdentityCRChallenge { + update_s_key: None, + update_p_key: Some(serde_json::from_str(TEST_UPDATE_KEY).unwrap()), + identity_nonce: Some(Nonce::new()), + identity_challenge_signature: Some(String::from("some challenge signature string")), + identity_response_signature: Some(String::from("some response signature string")), + }); + let result = cr_state.check_cr_status(); + assert_eq!(result.unwrap(), CurrentCRState::NotStarted); + } +} diff --git a/trustchain-http/src/attestor.rs b/trustchain-http/src/attestor.rs new file mode 100644 index 00000000..e5281d1b --- /dev/null +++ b/trustchain-http/src/attestor.rs @@ -0,0 +1,486 @@ +use crate::attestation_encryption_utils::{ + extract_key_ids_and_jwk, josekit_to_ssi_jwk, ssi_to_josekit_jwk, DecryptVerify, Entity, + SignEncrypt, +}; +use crate::attestation_utils::{ + attestation_request_basepath, attestation_request_path, ContentCRChallenge, + ContentCRInitiation, CustomResponse, ElementwiseSerializeDeserialize, IdentityCRChallenge, + IdentityCRInitiation, Nonce, TrustchainCRError, +}; +use crate::errors::TrustchainHTTPError; +use crate::state::AppState; +use async_trait::async_trait; +use axum::extract::Path; +use axum::{response::IntoResponse, Json}; +use hyper::StatusCode; +use josekit::jwk::Jwk; +use josekit::jwt::JwtPayload; +use log::info; + +use ssi::jwk::JWK; +use ssi::vc::OneOrMany; +use trustchain_api::api::TrustchainDIDAPI; +use trustchain_api::TrustchainAPI; +use trustchain_core::attestor::AttestorError; +use trustchain_core::verifier::Verifier; + +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::Arc; + +use trustchain_core::utils::generate_key; +use trustchain_ion::attestor::IONAttestor; + +fn server_did(app_state: Arc) -> String { + app_state + .config + .server_did + .as_ref() + .expect("Server DID must be set for challenge-response content initiation.") + .to_owned() +} + +fn first_signing_key<'a>( + signing_keys: &'a OneOrMany, + did: &str, +) -> Result<&'a JWK, AttestorError> { + signing_keys + .first() + .ok_or(AttestorError::NoSigningKey(format!( + "No signing keys for ION attestor with DID: {did}" + ))) +} + +// Encryption: https://github.com/hidekatsu-izuno/josekit-rs#signing-a-jwt-by-ecdsa + +#[async_trait] +/// An API for a Trustchain attestor server. +pub trait TrustchainAttestorHTTP {} + +/// Type for implementing the TrustchainAttestorHTTP trait that will contain additional handler methods. +pub struct TrustchainAttestorHTTPHandler; + +#[async_trait] +impl TrustchainAttestorHTTP for TrustchainAttestorHTTPHandler { + // async fn issue_credential( + // credential: &Credential, + // subject_id: Option<&str>, + // issuer_did: &str, + // resolver: &Resolver, + // ) -> Result { + // let mut credential = credential.to_owned(); + // credential.issuer = Some(ssi::vc::Issuer::URI(ssi::vc::URI::String( + // issuer_did.to_string(), + // ))); + // let now = chrono::offset::Utc::now(); + // credential.issuance_date = Some(VCDateTime::from(now)); + // if let Some(subject_id_str) = subject_id { + // if let OneOrMany::One(ref mut subject) = credential.credential_subject { + // subject.id = Some(ssi::vc::URI::String(subject_id_str.to_string())); + // } + // } + // let issuer = IONAttestor::new(issuer_did); + // Ok(issuer.sign(&credential, None, resolver).await?) + // } +} + +impl TrustchainAttestorHTTPHandler { + /// Handles a POST request for identity initiation (part one attestation CR). + /// + /// This function saves the attestation initiation to a file. The directory to which the information + /// is saved is determined by the temp public key of the attestation initiation. + pub async fn post_identity_initiation( + Json(attestation_initiation): Json, + ) -> Result { + info!("Received attestation info: {:?}", attestation_initiation); + let temp_p_key_ssi = josekit_to_ssi_jwk(attestation_initiation.temp_p_key()?); + let path = attestation_request_path(&temp_p_key_ssi?, "attestor")?; + // create directory and save attestation initation to file + std::fs::create_dir_all(&path).map_err(TrustchainCRError::IOError)?; + let result = attestation_initiation.elementwise_serialize(&path); + match result { + Ok(_) => { + let response = CustomResponse { + message: "Received attestation request. Please wait for operator to contact you through an alternative channel.".to_string(), + data: None, + }; + Ok((StatusCode::OK, Json(response))) + } + Err(_) => { + let response = CustomResponse { + message: "Attestation request failed.".to_string(), + data: None, + }; + Ok((StatusCode::BAD_REQUEST, Json(response))) + } + } + } + + /// Handles a POST request for identity response. + /// + /// This function receives the key ID of the temporary public key and the response JSON. + /// It verifies the response using the attestor's secret key (assuming attestor DID is also + /// the `server_did` in the config file) and decrypts it with temporary public key + /// received in previous initiation request. + /// If the verification is successful, it saves the response to the file and returns + /// status code OK along with information for the requester on how to proceed. + pub async fn post_identity_response( + (Path(key_id), Json(response)): (Path, Json), + app_state: Arc, + ) -> Result { + let pathbase = attestation_request_basepath("attestor")?; + let path = pathbase.join(key_id); + if !path.exists() { + panic!("Provided attestation request not found. Path does not exist."); + } + let mut identity_challenge = IdentityCRChallenge::new() + .elementwise_deserialize(&path)? + .ok_or(TrustchainCRError::FailedToDeserialize)?; + // get signing key from ION attestor + let did = server_did(app_state); + let ion_attestor = IONAttestor::new(&did); + let signing_keys = ion_attestor.signing_keys()?; + // TODO: consider passing a key_id, first key used as arbitrary choice currently + let signing_key_ssi = first_signing_key(&signing_keys, &did)?; + let signing_key = ssi_to_josekit_jwk(signing_key_ssi)?; + // get temp public key + let identity_initiation = IdentityCRInitiation::new() + .elementwise_deserialize(&path)? + .ok_or(TrustchainCRError::FailedToDeserialize)?; + let temp_p_key = identity_initiation.temp_p_key()?; + // verify response + let attestor = Entity {}; + let payload = attestor.decrypt_and_verify(response.clone(), &signing_key, temp_p_key)?; + let result = verify_nonce(payload, &path); + match result { + Ok(_) => { + identity_challenge.identity_response_signature = Some(response.clone()); + identity_challenge.elementwise_serialize(&path)?; + let response = CustomResponse { + message: "\ + Verification successful. Please use the provided path to initiate the second \ + part of the attestation process." + .to_string(), + data: None, + }; + Ok((StatusCode::OK, Json(response))) + } + Err(_) => { + let response = CustomResponse { + message: "Verification failed. Please try again.".to_string(), + data: None, + }; + Ok((StatusCode::BAD_REQUEST, Json(response))) + } + } + } + + /// Handles a POST request for content initiation (part two attestation CR). + /// + /// This function receives the key ID of the temporary public key and the candidate DID. + /// It resolves the candidate DID and extracts the public signing keys from the document. + /// It generates a challenge nonce per key and encrypts it with the corresponding + /// signing key. It then signs (attestor's secret key, assuming attestor DID is also + /// the `server_did` in the config file) and encrypts (temporary public key) + /// the challenges and returns them to the requester. + pub async fn post_content_initiation( + (Path(key_id), Json(ddid)): (Path, Json), + app_state: Arc, + ) -> Result { + let pathbase = attestation_request_basepath("attestor")?; + let path = pathbase.join(&key_id); + let did = app_state + .config + .server_did + .as_ref() + .expect("Server DID must be set for challenge-response content initiation.") + .to_owned(); + // resolve candidate DID + let result = TrustchainAPI::resolve(&ddid, app_state.verifier.resolver()).await; + let candidate_doc = match result { + Ok((_, Some(doc), _)) => doc, + Ok((_, None, _)) | Err(_) => { + let response = CustomResponse { + message: "Resolution of candidate DID failed.".to_string(), + data: None, + }; + return Ok(( + StatusCode::BAD_REQUEST, + serde_json::to_string(&response).map_err(TrustchainCRError::Serde)?, + )); + } + }; + // TODO: check if resolved candidate DID contains expected update_p_key + + // serialize content initiation request + let content_initiation = ContentCRInitiation { + requester_did: Some(ddid), + }; + content_initiation + .elementwise_serialize(&path) + .map_err(TrustchainHTTPError::CRError)?; + // extract map of keys from candidate document and generate a nonce per key + let requester_keys = extract_key_ids_and_jwk(&candidate_doc)?; + let attestor = Entity {}; + let nonces: HashMap = + requester_keys + .iter() + .fold(HashMap::new(), |mut acc, (key_id, _)| { + acc.insert(String::from(key_id), Nonce::new()); + acc + }); + + // sign and encrypt nonces to generate challenges + let mut challenges = HashMap::new(); + for (key_id, nonce) in nonces.iter() { + challenges.insert( + String::from(key_id), + attestor.encrypt( + &JwtPayload::try_from(nonce)?, + requester_keys + .get(key_id) + .ok_or(TrustchainCRError::KeyNotFound)?, + )?, + ); + } + // get public and secret keys + let identity_cr_initiation = IdentityCRInitiation::new() + .elementwise_deserialize(&path)? + .ok_or(TrustchainCRError::FailedToDeserialize)?; + let ion_attestor = IONAttestor::new(&did); + let signing_keys = ion_attestor.signing_keys()?; + let signing_key_ssi = first_signing_key(&signing_keys, &did)?; + let signing_key = ssi_to_josekit_jwk(signing_key_ssi)?; + + // sign and encrypt challenges + let value: serde_json::Value = + serde_json::to_value(challenges).map_err(TrustchainCRError::Serde)?; + let mut payload = JwtPayload::new(); + payload.set_claim("challenges", Some(value))?; + let signed_encrypted_challenges = attestor.sign_and_encrypt_claim( + &payload, + &signing_key, + identity_cr_initiation.temp_p_key()?, + ); + + match signed_encrypted_challenges { + Ok(signed_encrypted_challenges) => { + let content_challenge = ContentCRChallenge { + content_nonce: Some(nonces), + content_challenge_signature: Some(signed_encrypted_challenges.clone()), + content_response_signature: None, + }; + content_challenge.elementwise_serialize(&path)?; + let response = CustomResponse { + message: "Challenges generated successfully.".to_string(), + data: Some(signed_encrypted_challenges), + }; + Ok((StatusCode::OK, serde_json::to_string(&response)?)) + } + Err(_) => { + let response = CustomResponse { + message: "Failed to generate challenges.".to_string(), + data: None, + }; + Ok((StatusCode::BAD_REQUEST, serde_json::to_string(&response)?)) + } + } + } + /// Handles a POST request for content response. + /// + /// This function receives the key ID of the temporary public key and the response JSON. + /// It verifies the response using the attestor's secret key (assuming attestor DID is also + /// the `server_did` in the config file) and decrypts it with temporary public key. It then + /// compares the received nonces with the expected nonces and if they match, it saves the + /// response to the file and returns status code OK. + pub async fn post_content_response( + (Path(key_id), Json(response)): (Path, Json), + app_state: Arc, + ) -> Result { + // deserialise expected nonce map + let pathbase = attestation_request_basepath("attestor")?; + let path = pathbase.join(key_id); + let identity_cr_initiation = IdentityCRInitiation::new() + .elementwise_deserialize(&path)? + .ok_or(TrustchainCRError::FailedToDeserialize)?; + let mut content_challenge = ContentCRChallenge::new() + .elementwise_deserialize(&path)? + .ok_or(TrustchainCRError::FailedToDeserialize)?; + let expected_nonces = content_challenge + .content_nonce + .clone() + .ok_or(TrustchainCRError::FieldNotFound)?; + // get signing key from ION attestor + let did = server_did(app_state); + let ion_attestor = IONAttestor::new(&did); + let signing_keys = ion_attestor.signing_keys()?; + let signing_key_ssi = first_signing_key(&signing_keys, &did)?; + let signing_key = ssi_to_josekit_jwk(signing_key_ssi)?; + + // decrypt and verify response => nonces map + let attestor = Entity {}; + let payload = attestor.decrypt_and_verify( + response.clone(), + &signing_key, + identity_cr_initiation.temp_p_key()?, + )?; + let nonces_map: HashMap = serde_json::from_value( + payload + .claim("nonces") + .ok_or(TrustchainCRError::ClaimNotFound)? + .clone(), + )?; + // verify nonces + if nonces_map.eq(&expected_nonces) { + content_challenge.content_response_signature = Some(response.clone()); + content_challenge.elementwise_serialize(&path)?; + let response = CustomResponse { + message: "Attestation request successful.".to_string(), + data: None, + }; + return Ok((StatusCode::OK, Json(response))); + } + + let response = CustomResponse { + message: "Verification failed. Attestation request unsuccessful.".to_string(), + data: None, + }; + Ok((StatusCode::BAD_REQUEST, Json(response))) + } +} + +/// Generates challenge for part one of attestation request (identity challenge-response). +/// +/// This function generates a new key pair for the update key and nonce for the challenge. +/// It then adds the update public key and nonce to a payload and signs it with the secret +/// signing key from provided did and encrypts it with the provided temporary public key. +/// It returns a ```CRIdentityChallenge``` struct containing the signed and encrypted challenge +/// payload. +pub fn present_identity_challenge( + did: &str, + temp_p_key: &Jwk, +) -> Result { + // generate nonce and update key + let nonce = Nonce::new(); + let update_s_key_ssi = generate_key(); + let update_p_key_ssi = update_s_key_ssi.to_public(); + let update_s_key = ssi_to_josekit_jwk(&update_s_key_ssi) + .map_err(|_| TrustchainCRError::FailedToGenerateKey)?; + let update_p_key = ssi_to_josekit_jwk(&update_p_key_ssi) + .map_err(|_| TrustchainCRError::FailedToGenerateKey)?; + + let mut identity_challenge = IdentityCRChallenge { + update_p_key: Some(update_p_key), + update_s_key: Some(update_s_key), + identity_nonce: Some(nonce), + identity_challenge_signature: None, + identity_response_signature: None, + }; + + // make payload + let payload = JwtPayload::try_from(&identity_challenge)?; + + // get signing key from ION attestor + let ion_attestor = IONAttestor::new(did); + let signing_keys = ion_attestor.signing_keys()?; + let signing_key_ssi = first_signing_key(&signing_keys, did)?; + let signing_key = + ssi_to_josekit_jwk(signing_key_ssi).map_err(|_| TrustchainCRError::FailedToGenerateKey)?; + + // sign (with pub key) and encrypt (with temp_p_key) payload + let attestor = Entity {}; + let signed_encrypted_challenge = + attestor.sign_and_encrypt_claim(&payload, &signing_key, temp_p_key); + identity_challenge.identity_challenge_signature = Some(signed_encrypted_challenge?); + + Ok(identity_challenge) +} + +/// Verifies nonce for part one of attestation request (identity challenge-response). +/// +/// This function receives a payload provided by requester and the path to the directory +/// where information about the attestation request is stored. It deserialises the expected +/// nonce from the file and compares it with the nonce from the payload. +fn verify_nonce(payload: JwtPayload, path: &PathBuf) -> Result<(), TrustchainCRError> { + // get nonce from payload + let nonce = payload + .claim("identity_nonce") + .ok_or(TrustchainCRError::ClaimNotFound)? + .as_str() + .ok_or(TrustchainCRError::FailedToConvertToStr( + // Unwrap: not None since error would have propagated above if None + payload.claim("identity_nonce").unwrap().clone(), + ))?; + // deserialise expected nonce + let identity_challenge = IdentityCRChallenge::new() + .elementwise_deserialize(path)? + .ok_or(TrustchainCRError::FailedToDeserialize)?; + let expected_nonce = identity_challenge + .identity_nonce + .ok_or(TrustchainCRError::FieldNotFound)? + .to_string(); + if nonce != expected_nonce { + return Err(TrustchainCRError::FailedToVerifyNonce); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use crate::data::TEST_UPDATE_KEY; + use crate::{ + attestation_utils::RequesterDetails, config::HTTPConfig, server::TrustchainRouter, + }; + use axum_test_helper::TestClient; + use ssi::jwk::JWK; + use tempfile::tempdir; + + use super::*; + + use crate::data::TEST_TEMP_KEY; + + // Attestor integration tests + #[tokio::test] + #[ignore = "integration test requires ION, MongoDB, IPFS and Bitcoin RPC"] + async fn test_post_initiation() { + let temp_s_key_ssi: JWK = serde_json::from_str(TEST_TEMP_KEY).unwrap(); + let temp_p_key_ssi = temp_s_key_ssi.to_public(); + let attestation_initiation: IdentityCRInitiation = IdentityCRInitiation { + temp_s_key: Some(serde_json::from_str(TEST_TEMP_KEY).unwrap()), + temp_p_key: Some(ssi_to_josekit_jwk(&temp_p_key_ssi).unwrap()), + requester_details: Some(RequesterDetails { + requester_org: "myTrustworthyEntity".to_string(), + operator_name: "trustworthyOperator".to_string(), + }), + }; + let initiation_json = serde_json::to_string_pretty(&attestation_initiation).unwrap(); + println!("Attestation initiation: {:?}", initiation_json); + let app = TrustchainRouter::from(HTTPConfig::default()).into_router(); + let uri = "/did/attestor/identity/initiate".to_string(); + let client = TestClient::new(app); + + let response = client.post(&uri).json(&attestation_initiation).send().await; + assert_eq!(response.status(), 200); + println!("Response text: {:?}", response.text().await); + } + + #[test] + fn test_verify_nonce() { + let temp_path = tempdir().unwrap().into_path(); + let expected_nonce = Nonce::from(String::from("test_nonce")); + let identity_challenge = IdentityCRChallenge { + update_p_key: serde_json::from_str(TEST_UPDATE_KEY).unwrap(), + update_s_key: None, + identity_nonce: Some(expected_nonce.clone()), + identity_challenge_signature: None, + identity_response_signature: None, + }; + identity_challenge + .elementwise_serialize(&temp_path) + .unwrap(); + // make payload + let payload = JwtPayload::try_from(&identity_challenge).unwrap(); + let result = verify_nonce(payload, &temp_path); + assert!(result.is_ok()); + } +} diff --git a/trustchain-http/src/data.rs b/trustchain-http/src/data.rs index f2a11a7b..55d1ae32 100644 --- a/trustchain-http/src/data.rs +++ b/trustchain-http/src/data.rs @@ -1,4 +1,91 @@ //! Test fixtures. + +pub(crate) const TEST_CANDIDATE_DDID_DOCUMENT: &str = r##" +{ + "authentication" : [ + "#bZdi2pQK5dk6YF8uVKz_P7SvRgZJ6DUT1KcsLM7L1QA", + "#a9vxpkAsksMUOXqjAdnZhQiVOKY-a0QDOdnrDL6lw40" + ], + "service" : [ + { + "id" : "#TrustchainID", + "serviceEndpoint" : "https://identity.foundation/ion/trustchain-root-plus-2-not-downstream-yet", + "type" : "Identity" + } + ], + "verificationMethod" : [ + { + "type" : "JsonWebSignature2020", + "controller" : "did:ion:test:EiC5GlkBZaC6SYiCexvcr2hgMPVdSoREIhK8KbekQRgphg", + "publicKeyJwk" : { + "crv" : "secp256k1", + "y" : "MSxXXbRIm3OWYgyhJBC3mpAg3uCniPsxkQs486i8XTw", + "kty" : "EC", + "x" : "0vYBCPbQLlPCTW_iTdh9ubbrQqhZh9JWyP89tDKsbew" + }, + "id" : "#bZdi2pQK5dk6YF8uVKz_P7SvRgZJ6DUT1KcsLM7L1QA" + }, + { + "type" : "JsonWebSignature2020", + "controller" : "did:ion:test:EiC5GlkBZaC6SYiCexvcr2hgMPVdSoREIhK8KbekQRgphg", + "publicKeyJwk" : { + "x" : "aeq7ALPoynBWX_QDFzJxyX8USRTHzL9lm52Orvzy-DM", + "crv" : "secp256k1", + "y" : "25MLCu-qxD_axvomnLZVgGHehJ_CO6pNE4IklQMaVzA", + "kty" : "EC" + }, + "id" : "#a9vxpkAsksMUOXqjAdnZhQiVOKY-a0QDOdnrDL6lw40" + } + ], + "assertionMethod" : [ + "#bZdi2pQK5dk6YF8uVKz_P7SvRgZJ6DUT1KcsLM7L1QA", + "#a9vxpkAsksMUOXqjAdnZhQiVOKY-a0QDOdnrDL6lw40" + ], + "@context" : [ + "https://www.w3.org/ns/did/v1", + { + "@base" : "did:ion:test:EiC5GlkBZaC6SYiCexvcr2hgMPVdSoREIhK8KbekQRgphg" + } + ], + "keyAgreement" : [ + "#bZdi2pQK5dk6YF8uVKz_P7SvRgZJ6DUT1KcsLM7L1QA", + "#a9vxpkAsksMUOXqjAdnZhQiVOKY-a0QDOdnrDL6lw40" + ], + "capabilityInvocation" : [ + "#bZdi2pQK5dk6YF8uVKz_P7SvRgZJ6DUT1KcsLM7L1QA", + "#a9vxpkAsksMUOXqjAdnZhQiVOKY-a0QDOdnrDL6lw40" + ], + "capabilityDelegation" : [ + "#bZdi2pQK5dk6YF8uVKz_P7SvRgZJ6DUT1KcsLM7L1QA", + "#a9vxpkAsksMUOXqjAdnZhQiVOKY-a0QDOdnrDL6lw40" + ], + "id" : "did:ion:test:EiC5GlkBZaC6SYiCexvcr2hgMPVdSoREIhK8KbekQRgphg" +} +"##; + +// key_id: #bZdi2pQK5dk6YF8uVKz_P7SvRgZJ6DUT1KcsLM7L1QA +pub(crate) const TEST_SIGNING_KEY_1: &str = r##" +{ + "kty": "EC", + "crv": "secp256k1", + "x": "0vYBCPbQLlPCTW_iTdh9ubbrQqhZh9JWyP89tDKsbew", + "y": "MSxXXbRIm3OWYgyhJBC3mpAg3uCniPsxkQs486i8XTw", + "d": "JqWC8hlh9KX0XaUsl6xbiYtSX0TC1cEaqb338boJHDs" + } +"##; + +// key_id: #a9vxpkAsksMUOXqjAdnZhQiVOKY-a0QDOdnrDL6lw40 +pub(crate) const TEST_SIGNING_KEY_2: &str = r##" +{ + "kty": "EC", + "crv": "secp256k1", + "x": "aeq7ALPoynBWX_QDFzJxyX8USRTHzL9lm52Orvzy-DM", + "y": "25MLCu-qxD_axvomnLZVgGHehJ_CO6pNE4IklQMaVzA", + "d": "YoSojHkEat0RefQxbzeS-X2JIW3BCJTgc8-VM6ombWk" + } +"##; +pub(crate) const TEST_TEMP_KEY: &str = r#"{"kty":"EC","crv":"secp256k1","x":"JokHTNHd1lIw2EXUTV1RJL3wvWMgoIRHPaWxTHcyH9U","y":"z737jJY7kxW_lpE1eZur-9n9_HUEGFyBGsTdChzI4Kg","d":"CfdUwQ-CcBQkWpIDPjhSJAq2SCg6hAGdcvLmCj0aA-c"}"#; +pub(crate) const TEST_UPDATE_KEY: &str = r#"{"kty":"EC","crv":"secp256k1","x":"AB1b_4-XSem0uiPGGuW_hf_AuPArukMuD2S95ypGDSE","y":"suvBnCbhicPdYZeqgxJfPFmiNHGYDjPiW8XkYHxwgBU"}"#; pub(crate) const TEST_ROOT_PLUS_2_RESOLVED: &str = r##"{"@context":"https://w3id.org/did-resolution/v1","didDocument":{"@context":["https://www.w3.org/ns/did/v1",{"@base":"did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q"}],"id":"did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q","controller":"did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A","verificationMethod":[{"id":"#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","type":"JsonWebSignature2020","controller":"did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q","publicKeyJwk":{"kty":"EC","crv":"secp256k1","x":"0nnR-pz2EZGfb7E1qfuHhnDR824HhBioxz4E-EBMnM4","y":"rWqDVJ3h16RT1N-Us7H7xRxvbC0UlMMQQgxmXOXd4bY"}},{"id":"#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA","type":"JsonWebSignature2020","controller":"did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q","publicKeyJwk":{"kty":"OKP","crv":"RSSKey2023","x":"EyGvw3AkcUf2TZToBh6pddeaaocmvTuLCSLun_yYJpL7x0W3gVEzeKlj06J5Sej9Duk0W_yGhbOKCahOx16LszwTHVgnH9FjRk0nwOer4yKaKnjTZ2FlZsYI0OI__jhCGP9cbcOEd-1rfvUFu-ghsj6oHfSXDBm0Ekplkgs1IktoicuMsF-bD7I6tZRpP9tqFGqARUqvR2daQN-scwYUNsv5ap3XakBCDvOCBc_rPAwzapY_nuC3L6x60UGBAPtUBANdaMhAU0gxd-3JMjcSjFgwzAhw5Eorr7bIp1_od6OfBRYu3sIkij5Es6RDBLghUAx2Z3dznniJRh5Xlx_8zn4SYw_xhV1X04vY5U4O7-7veKMqKxzzoGOR7O137gSTtBk66ISXfE0k6LLsZK0Qkzi0B6YQ0Xo86d-COFNhRWQ_Lq3SCSiOaJ4lFP5_RVlHzgUXm6XY1X0jrkVPWdT42VxGjFvy_KX9f50dOkdPJTax8bGv1nEpDm-55UN8nrIzsRODaxMBooRL1y4OxyW1tpHaEdsoHvsZrLzM5g7FB2ah-62TCGkPcG3Yx84MPp50eRPIlj2omMFxMpnAZKBSRMGtk35A6xAZUI6KTYGfNI-IuWKdk0UOn6xL8W3EwMTxRgx1v7iklbgxKuCBoOeAK7FhoOVzL5YnUCHb1NUwAxDs9I5pNmrvaXsDDLKLIoz50hRAdnK92whifFoWoJOOJbQTb9sx43zmB1J7G_T28MG6UetI4dZljoNfWpXePl3vNwW979nNg7GU3N_V8ZE_slRmUv-rAw9jD0w9KXVCuZuwGIKoJ2Co8qjZxnhZUtmi3wFJin73V5BC684ebh40fnA9z-H1Kwa3ItX_mQSVYeMV-_1fydNULsdhlEnpwI5XNQ25LGqMNb4v-YRBXLSmN5CituV9rPXg5ZzQvy8VVE9qxWnicCxz2TzFrxFOOIhNTxf-YQT5Re5HJAvdy7Y9szo-i_PgskFdVm4UxMgH9ddrFUhDPNmVtVY8PoXlMzuU6gKR-1np9J6FBttHOIPu7LFFdO0Vd_Y3-Dl5mdBXFcP1Do1GN7ojcuRUB4rmB__upRAQQsqCApGurtGP1zgtMQm6ozF0gt_JpoXgvZEFK5kkm92vpedrSfDPBBn5NPIgmQgKSYfvmWRmADyr2J9bc6EjJr1-YD7QR1r2g_eGRBE1S6dexWceWTq-RktXQYOSJBnKLSkbqJniuoA70BMkjU4Jsj1EJB7oxE41RRMchA4BRlClSi31ga0T_bk31rNTLQNLGSrBrh0x2nlG8IZUZLB4fIKKweFD9pL1qhLMM-SQl3YR4-v2wxjlMXTrEDjz2xdwJsQhhzM5trtqhVdxfgBwB_ZBtU9KJqYvkB_3BhY3kYQSGDLhyCHbjyIVYl7saQGkTz_owGfj8tD3gU9oJlZHDyjf4p9AObfF4YXKjVBpPrPgwgNd-G4LAgUOn4DAVwGmGBjQaNWiLet4g4lRsLS3LkM1az1w_KyYCX_k9bptp4qLgwV6HqbLx1V5WkmubxLMpHlbV0tZFLzwThEaKpqNyz7M5qIyDvaSbTFtQ9feXhRHU7VN1MgH2AQmQzHiygXHs5qafdGSsKoMm6c_6R2-NXl3asM1TSUmD82yKonGYhSHHy60KvB4M2rVTKRENxR93u7gaYr_4cqFY9LlcqGUMzxmm6TadfSHz3rSj53C8c3Z3U9x9ftbKGOZeybdWhYbRGyES_HzmlXV5MFY5qHiE6INi_ao7Xxm8VRi5rdaHlVDWfBb8gJENbUHDDcsKQfae-4j_vXmvq4s_9L5It5kVLCT9f5NEf7jsxSP3mg9hqgwdY96ob73GsHO3HRoQARhPUt-2o7i1JzScqRH38AeDr9XnxC2Qu4LT6ffOmMKzA3qngyxKmkvyKmIl3_eEhDxpdTSf2ba6EGOD2GuzvGv2a_P9QHw52mvtEoCLNJAslzsxwxbLSnLIOkbJca1Ew26womAjSgnNwUvPCkz4lmSNTbyF63wvmNJJeD0UgkBTb2MxDw_39ukWvH0mOSJegpmENWzMhvKvxxMgB5Y1VY6Hq06V9mcg4iD0AdI-dM646yU8iLfMAAkB-EvwUUMXRE3KGU9Kx6dqhsSCrow4QDpzk0B4FCATLwawfGc1_rxQyumhF9nagl8jP1ITcLi-hlUyrOsKfSK_s3WKTw4j9iBoBWCzHrX1YC_2UTnq5XIdbY9tT4NajRzqwKLV3aYWRnqXLg_-l5k0H2GmwmRnm4ZqU-9YuAy8MQR5CM93H1gxE7oL_IWIyH_tCXrVH4hRhjd7GrWcA90s1AFpCHhBZs72ORxG_Rh8VcJpB5cTpbQfk1ESme0-UTXoSnuLPfNIQb6I6fwFkIvBx9YL7gxaVmjHMgk9BLR89iwuo3VsEsAs4ktbFfZ70l821y6q_xmOBPF-BxJzlVuHMq9hfyYVA-1ka8tBBeEy8NJ1PlYBMiVjHoKWMfqDKo0ONNv1Il_ThirUq-MM4pc0ENOqwCYkomNBFfFHdbS8L1Y5yIruufFxRbRPt6xC1TnDtq3K7JCpRjsTqv_1_u81WA4UIlW49NaruM-2lPlL6P7rWtBqG4axy6U9WYqom7aXBW0cbg31hY39xZb49G_SfSYewGr_pelurFdTag1R3ZL5VuDTggqErrppxKIBYHQP7M_reJ8fQf4JcXOmMkUOap1K7QJvvENxlQ_RQRj10d-t9spgDv5gki7uMDSA3fp4q4gf3HxZhYwPaImQ9J44zCCLUdo5dyhHsyd9neEeBniNZk5LDZRfX66ERlj49CO2dHmHLe-YQACZnMQDDug7LF0il3QHinPD-nedAAxpjfUus9Ay9vRx6nB3fHr-_9C76qx_NjCehMZHlsAOgZGU-yjdwY2uu8lvnb8dvmCbkIBYn4S_aWJ0qIOEjfWuADwWO9BXI5uzQZ0EhKuhALABMhOIi4pmnHqCE0Durvn9RaPiFz6ZKFhW2d85ZAkks_-ARI0phaKzggmB4E6k5EV3cLqkI63Oiiq21QY0VCvc0LuNoAVYzG8s4bx3udSSORrRJm2fOdURg3wtPlFq21m_7y8D09xKpHkXgEbuDJV3hWk52u0Rxv1MTY2V2_LkHIDF6my-MZLQQh0dQYnUjDfvQ3bTqj6UE4MZ07R6UZzl3Vjw53lM2x4gI17Trma17Ag6Yg6XiQA7QqgXKWy3jG6AuBLjuYRPeYo18lJm00D1D_Z_C--D6zMJKr5ohYrTi4ea_dh3CI82xBNwjeTAd95r6X0wzC3xodd7FSWJMCgt0MF6pz-MEL_jNi6sK9mIn05U4icLZLjBwl2lObaoiYxpyWEpnuMGy8J7dM1Z_aRpYt3J-Zw7i3Yf4JI2JV9u1Mo-ywQyXgRcRBhK3emrFT2fxH8SqkKwJCWn7frvbukOzSQiKD8RFuXA-SWK60mJ3erCRnka-xkGg3AiBxxeE8Prk8EGzLcB1UDRGQ_x1PXmMNtdBK65dtv1b0jGTM_uSHFndWXOrFALwi66JGyIca2WnCfQRQDR5EPyD2d2Naecbj_jMwFUsbYCxGTc76n46c1pI_QH1rxDBQ7j1Tj_rcQz6Bk7DMTNnlTFhJn2h7yVnoRPenlNCWZWZPRpr4vnvS6Ii30os5W2QaGHI_TqhhaXRFU8Z7K4PUUUVEv6u3KIZpvcuVxAbcx-ppLVkj-r2vM061Nx9aXEBFd2whV1Tw2rjf-6fm10N7U3ssLGC6sfHRpSVcsENk-ZjuYH7sY-zmN7Hf8zOYHIAZDUr1rjCgG2yCujbdOPFtPs4QKC_cFSzbpOjRmJ-urzi7duH_vH3_TBhMzM4jowgM70l1LoB9sjQ68wzlaAs74T04IroWMULoZOdaeIS54ugR79EhgqvukrIDLEoCekAY7jAs-iNW14YRPrtdul8zVUjLd4I_X3efx-IX7HvR4RUp-6lqMSN46IfvlScl0qBY_SBgCpdEw66SRo1OAIAuTy7VWX_mbvLtgZPPMkaVheFwYwBZnBLKQKyJHrNrKRQ5GdrSnJP89jdh-o6VEqG_whEec3cB1LwXipXb6v1vi-7jxU4kpU_BTMtEChb21tRhmfKGiQxHbOTRJbHVoQJ4NFlS14bTYAEuJm6yXnIW-GOVCLvlHShp5jeWc_9vvvBZnk4C7bDxY80GxadNmsKy_-AcEFN_QI9pt6lckDeTOQxgVz6Anz58RIkvJ1oPL8A5FZOl4iYuQGDAqTP6Yo-SdHbuVOuV3aM9K3L6RMgj5Z9z517O3oqsmthQdy5xtxhalD2bjV4fNsQrsXIGuNa4nAnFtfsi0uN4ahR1_YYVuQgfEQLOGSzJnw-bQ7m8tOxlDOP4MsXg6BFSBvo0LPwieTdNbZR_N4FueA59bt73HfANTd-xz6ycnZNRNO9DbxBRwXJnQogguwZQdLLLuZjqoglKwi3gmMHvCR-3QngZYQw46vAkTUuYfdG0OgaYuAAqtsEvJRaBVSud7q6pgMqM5UbG9eWv20h-bMQeBEpIuVG08HOEc9TeUzDOoE87PzBkfBqVu_s1tyItQQ-DqSvfCQBobT1pYeVsuyJSGXuaF5MXooxYfRpsAuysjWDKDNxAarmMCpioPCo5ebD0elYa6S1KV52RN15vaAZLPqNRiFkek3oy_M8C9Fi2nLzXG1Bjn_JlKzni0I3pofwFNE2ZJnoLSVpLwVLQUzzCB5GoS5P5C1DcPDxpjAr7e8pWb0QAyyIuz1EvSssczBargovo8iNxthV_MgoN4UGY3RtkDRyw2DPcFdji7AYXw_q3xlxXsWEZMfjTlkG0FfwSTHbhrL-BIXXw1u88y-w5SvjBBwk2wW0SjPVgm-qq8yonWXhnVfu4xRLMY7qNRltkzyB5pQ44rJ0iFr6tXtKus3rUTx2PbQOPNCYJynCWQnA8anAlOiTmIJV8G-MYkP3hH3g-VZSnWE8gQhbvXy9OY4YtyqX96TXRGuHNuZBDEHiPmNAvKkfgVdGE1xrxPnfZ5eN2RQWXAf5a8xgISY1bXxlt1prbFSiHTMLnikDpYNy95JBQnPEqdIYRhgzh29L_RQpIM2ItE6rPrJCl-NL0Mo3YZNdFepgL-5uOjFilpmO_EfAc06pm5sP-g6S3vOx8I9j4JrOnhygXvZx4Mr2D8-R_7s2F5QOYKCpcYmhKSqaPbdAX-q6oNQQ3fesRtmDJIVbBmioMmu5k3C8hh_L2RNAe6ItXT7XVCo-QFQ8fiUIOMWASrYHiy8qsbX4kKQJ98v070GnqCMpKVtB9522SHxJWv4h6Kpsmadh9WjAmzItl4tRV763mNcLeidWzlJFUcfZIVm9OrWbHinBUjKFnoeexpecTm2ncrzpUkMmJghWKv9hUzk6wGkQhsps-94GvQJT2ou4T5xLpeATQ3oenwez9tEwxQ07tB7FHEiIBpA4PFExNwdv8sxaEe2Zaoakh1iEjIbd4uBcEAd_E8eE3VSEPvB2_zT8nek2I9pcHEIHA52Q2_j979f-vAyJci99RN1Va8nvk3TyMz_g6OCknUZcqkhXK3lqigvhkUBl-IxjWqagdTwPfwGPtwV3JT71CZDfBWujVMLPGB_gT_dhsWlIN-sC_yiWL_thQrkgKFPqXPwQKCyz8r_iv4f8NnJIh3W6_hUURFsnu0NpVAlhi7iOU-B0cqk1NHN9BgNbT_zU2aVBEFBrlQetG5pyxxgyDSvrz-igEzZ9oqa7-EIgNv8P-0T0IUrlCIQSfPsiAUsbExwg5JwdgdQ_gD9HUt4U2Npk03XtaAySY1IXJCXeJLp0OIcc8hFeaiPMMv7Caif9RsIxjwnikwLFGtpNy70Ed6CkTMtxBR4uShDzbSz7Hk90gu5-jV5WGysOA9AbW24iqgfgCKjrjgfrod_MNG939PdD9KOV0x3MqbZJmBLB7jKCINC2ilgH3Ez4crHFZJEkuJ_Qq-KDXW7l7hjHUG_debtAu6qI1edYP09UkgmQtnZgLcGAWUhDxWhdf4XYOHfqXxfhiVu8tF-ly7iqWkmRCqhRGV5NmzUWuwvQ8-Jlh4kRa7nhpwb7ivyXiDubq85_tKuha0qKFzzz8gFuiefICHX_Uy3xM8m6Gy3KfYirumMAkuB5-IY7Dgr6IZK8YXGLZb3QEXmOjuwp8Rmm-bMnCXehgCJZplNtcWi7eQxsP4y0IoEUsmmC5Y1as1sAs8-R9XlxBfP3hdGWbOupZfS6FmMRiGD9HoWesUSVtRs_tgOUPPVav2HRIK2CLYBRwgI1NaeRcpnO8cOye4UgRm_UF36pi3hJPfIdCnhxGeOH5J0r9zYEnTDs18YsIQedQOJ9jvGBLvDi8dJ3NRzof0hk9riVtSPV7H2EKhkEL67E5pccehsmZnha0ewYbZdgEstjzjwQ6qkZRmFLOBdP11yCDzgs3eDmnk0Ztewl22-WhhpumCfNgux5OEtcSu6hcC_gtsXQgTm4QV09fFZJAH8tyfFildcaycx0w6zG_tT47jBYIwVyEI-Mvv08qYw3ZN6558VgacYehFWake3ahdjDxZ8bO_tBtLMrFXmjRpibEIYbWZW2OPgBv-4-Z_EPXtLrDpJxYjD8bUxNgxwyqxAlyqZe0FUQVo1RTWV9hzvj4GcOG7wC-_t9aEEv5h9hg3sQXBxwKwIulPSsJlAeW3dygypohfIMKiUdjDERwhgvPsvB_vsJIaVpN3SJVfNWvMEFAIRxl0o0b4upYbISICcxav7YjxARlPcV_nqG6Lnj9-6MtHOzvmwMWpcM0Y_FFro9TqKAj8TkAiGaEMYyJ8Z5EMAsGd32HwMhmdeJbA9TxNpC8CIpeNlU0H9JeSDR3bl76oGAPDIc7bDmfKjcCL_8rZamAaZucmCI4Fkkjaqyl_k0TOHrxrc8EcYzbICfu2Xp9j5Bl_w7GErvNIbMsbJejezsJxt6CR71oex_OaL_DyxGJE6bOaWZFwF3WqhVWMoMEuRwy4Z11DIsqZ2pbxyArURVFG3mIHnBJ7ffjxYbofuuuw9Ce3S0W9AwEvXRlquPr3-wLesE-Y09JL2x63dPrsfx88itwaKSyGuJyvqpTu8NwpAR8d0bU6nXG38O2ysH6-xwvDGoeApjhGaTD71tv5hYcJj1X2M-GeWFi74NjG-PYBkamWVPk8v2uimVuB402YMgUAe5RtZcKVUfHczIcj7IWreTJr8JCLl4N_X48ji2KDuBuuaBRBUYdjkl8ltWE-AQzatqUi3DF2ZDEjEarQrk8K6QDaHNbMAEQwqxIcKVB7rX6pwR4EA2xN2VYmCskYAReAbKYyzbFKgx-_kbylwjO1CMcDTdhKYHnfEznxeaxzjwopfWQR5JQ_y_4OExcY6gh_FHXXyMOQdyzdcNMPFOZDvKAf4PiXg6BV6VVbvlssgImhEbhyfKlwhmbHkrD90BVSZOfwp0m_zd_xOfwSYckSwo8ef1K6DILkCmiUSc9wiCBBGHF8ex_0u3nepPICWg30NqJPii7moRYlXNi2hKgTB2Cy1njuP9pNFSD-8cOxrrAoAz6SaxdS4QqxjykSaRko3FibccYcSE_fkx7_WWBSW_1GOKTqQltkzHWMqTbu3wEjBAbnQjYGEWn8aTNzsAh1pezmZurCOdi9uL-cjIVavKPn23HhHGfS88f3pRdohcdlszyc74acnD6VgT0VnArfeYPNBWcliVDnCE3qYSvter4l5Fe4rH1qDISEq2ni1-uxNRJx6Ck3-5bWSZxHAgvc_2gC2O5qc9TU-akXvNSqLmNtKmO2FGFtBltwgyLc8bVWAJrNxuWQVCUxXlfSkxaGXtN18lGJX-SvmRn5IsqfhUitHzJjEASiI_YOVY9OoGEkK1a532FFGdO00mS07BQCPV0w_gldLncCOgt8VPaB5d5SjOF0_whIcVAIY95y5MrZEJWcbES4zg_jdGb5SRLlr9PENPbne9VYK4_ju-MCFNo0uWibQJzJcpaKU2rZ9sAsT2goR_lu-aLGCdeimhRmual5ISX_tyMRikPCDidsweqUeRzPcriSIRDKLcQfzA3P9Lt_Mo0ql-l1EX7TcwLgCsISBJ39jyhHyPvNPbBAFAlrlF9uRhz_ATonpUwgZrQHSlpsy6Mzh-O8f57HKQTRT0VigvfIeC3J1TR4EzLkHUdC7QF4JNlprKFQl-HUh9VIOpwXfQ7VwhbxUw-MThAn8fnFAKqd8S-4S76Yn4Ns3B0FA0wlDWp9AvfCSlm50bQHUgj8FEtwz8279OoIhBEIMnA_rHNwA1gPMSAl8aU4RO4L9wTbhwVEs32i77O1pQS93ZeNwOwXXoquAAVFZwusOXz2C3jxzKzB6IdrA9LE7-ALHDvmxB-y9KUe-RgCfFgjh9EE7rdwftpCOMj30we1IOtQ1XyFSwpbIK-y6e6itkyx73nB8UicYQEQHDnl2UPtxm3TLUe5bx_E0sisng5ZV2ISypN4_CiyoAbUPCapdHnGLh5VJtaPPq0NGIVA88MkPxnJC_dTfsZKzNVDywA36U6dGzcSH16QoTfJ-ZcUJhHAKJHizKtLpdxpNKlSugnNW0P0XwgrRYAehBBqJAWrmDc2vll-f5KYy6AFEWfIub9SODwuu3j3yfdoVAjpi6Tvm_e_w18ZBYKjtRrAAg38eTrwQwdDDovzBO6t7xmJkqOxsCFl0tz0WB7YxhVMfhC6qv0ojnXM4XrhX482Ew0yMUB9Ql2_2d7u9-aM7VztBqRf9dtPj0Fc1WdfiMD1d72U2D5NukpfdO0k74QL4xFcEWgq0qAPT1Xd35HaQhe9KfUYx0d7KtbBb1BrpQ3zZWS_ThLtfTHOvGZRQH9bQQyFkx7r9Lnal_GmnKw_w-Y5ecOTXwxvtB_XQNOo2i02MTPLpYHXMCWCFB6kHee4fhJVL4yQnaac8WOYkNDZeHf7y15M6Ezs0ieyusNjY-nfeAuXS1kJ_lf-qI-1xCpx4wmOy-W4Y4Xbr5YWS8Pe17115uh3ZGN9n88HuWj_fzZ0BcrgsT4p5LvSm9lntyD3oQ8pX17phhk3xqItrnJYAq8MfnLgifMDl6XucGJj1rhsvVGfr_ccjSHxohBb0HWL6g16xEvKsXnQe-PHn8Djtpc9doxqWWC1QeFnjIFJ38TnZd2v6S9irKu2D-YTw_9TvgRZTHMLgHH7pdFo2P_-mrKP74-OvYkn0O4aUVAZ6-bCXKIZ4ZzFgt-aO6l6vyUUfhcVrQKcnRdrZ4_GYfiRdxlBL1rvcZAkVpH-iitAdQ4N0xFHFL3MO3MH_EepQXLXSgciWBbbc9lzJnd4GkCRT-uH1SKKtquXZIO28ERVLB5yD9xkl6-ch9qTYNnNcBDNSAJQeFBwCHB5xZoyuYfN9p5v40vfSDAoJU9A_3_kaYMyUBVaxQWnKjZrrA5hWy2fjRUnVpeX7PDyAyb6eZDt7dKlkWGQxvhDXRFeN9yjohquhDj9OSS0JlHsPLobIYEPThAwpAYAEH9aspydpQDzH5LdB8aSUzTmFvdt87KW_OjCX2bAvPUj7a8bhfrITHuCUwOl_hNSIaxUX9EuHEifvRKi_KnQRZvkTyN6Ji93jcr1wYk2FOjZEVdUfC_lI-xzuQDSVWUUl6URvL2tfzx5FxqScbNiq3xnIqLrNONk-p4hi1QvPbgiYvXevv6-KgoCOBN5b7E0KUoVcBh8GBPzCeP2EZwA6C9k8u55Ul0Y6dohgm5HS8NQfXCSTt7QQgchGBOyOP96JR_uRbyLPJ18KaFr9QTxkQrxpuks_tWBdd9QD7GN2MU26S9veV2mrWHNXBiKY7NNZjYSkfNyzvjsg3VCwvxU9kzvkozJ_hQnkOnEmlI8bu34cFvYy1Ms4X5fLwaFLMmG3SnAIwBsCz3HxzKU05NBHikuB3B79BGskfQK_Fe-rkahNqJgG2ya6xgeIBivC2iuCuVjM1xcVN3jM0VuwQOCIVwjPpyDgWwjm5rpjX7LfEzwjyXynX5OR8PVugx7bAFwv0UNcbkBNLadJmL5hZfeXHzgPM5u8M1_PEpwxRddCDLbmbY-Y1naQwfaKRQp_c6KwJtT3IzkOJlaYsUlEeoLQKfQI-OFr7Jy6N9-tP3x_0OpecilN6J7UQLOTQEIeygISrIiIkSQgL8m7YCl7cRejrq3kF9UutkU2OIJFseVIFtIKZL92vc3WSxj6A8NkX-yqQ9LCFljVw_acJ9tUT7tNyOF7mFKBQJPa92WpaOGgzq4OCV2nJs4GFYjXgw7uE2NjQ2i9_auhXryGm3uD3G29NjUQ6Lkingi5trDZLCzoFKtQ_-2tWnf6sC4HBlShllmYDfCCorSX3Qc9WvEwxLbRvNX0CgPCEoxIKHAE9UzN9sfWZLD6BCXAtERDgNqc458B3xIrpXpk-hmIe-Res9HtuS43LqebcFiHjjKKiBuUEBCSxSEYQPYdEII9QMsBsp9IoCOKL7y6m5EgCfQzA7hiWLlE_Xrppv625MGLzebKWzu8CP1mOPWTp4FYwaXl6sm0rgbAoR5XtNLcBazT83ji0Qhc39dVR0nFyvdSe9L-EFw6dbYUPPbQDh0hQVzwnXZYFi4wgX8iFfyvfj1cAGrQNfx2yekQfLm-vhGK_sIlCRVZf2bjS6rwAbVIhhPFuTsQ5EaYCc3QbvJg-slvxMGfr3gpUkMV24EE0dCemwKRyRyf9zH-oswETPMyAFTQmlx715Ao-RESnFuc1Ebl13oTofrWpye9ZaqqsGko3Cimdifa716i5Gkq2FJNQRRRrp979uFgzdwm2AL3Wa_5I1t4aHY0hFNXzKU5u7gNmtiTDyLSOIWLGfd44msxBYFSE9YqSdU-7KpEtOLQRppx3FR1TQooT35XW13oPp37k91Uv2j8wLJPAid7msh1AUWmpGiq9vhair7EUlZhnjNIEvhlTr6sIwFzsJPRl9Dy838w_UqVXhKcA2wJpTCjgRWXL8R8b6L7Qs2v0H554fmrK3qcTm1BgmPf6d0aeO9wsgj_cSO2gI6HgI4zL6PUQTsMTzhIY8pN8MW1jPWVa89yWjGjaanxKT6WyzdkCGj6NcG3Yh5UoKGeehwa_5FQwggBfzXYMIAK3swXYvK1bVz_68c3eLtW96nYc1mnOw0QmcuQ7ajBPpwPVqQwH1iLRS3nEWbxznVbgvcdHS1Sv8LcVU8htWp9JheVP2OCiGQPFFScImnsLDC5WZxJNohrxFO6HHJ_6T3py6zz491E_zWqb0B89YapQO7LKc_D3pU7_3-ug2A-BmtjReN5-I0QAaNX86gN5o-LNW8yl7DmVU8rDBHQBV7vZ4uijVQhDvpifKk5mqhztr7B82gamJD6gUucjs6nA9V8i9496A3dTMHdtEjeEIE5zkvtbLe44WyaDxa5KiwZikk137DL-hp9w5b2-ZjwrGqcNJrYwpTQAjHigL12EWMHKEnPEsSXqmYujeWGfB2M9_VDmSgf3J-XAZroxarSzyVuead1XNLHtLqQgT0Prh-PS1lDJ8jH5y4_JzNS6lN78BaEi-rBl-hyhXqi7ZEzGEyZVB-H9rkmCE1jnuQsHj_iWUkZFeE5wJRemTSNTxF_GqZrFTkTD68qxdtMg7nWns8pXHaqDxpWAFaONRj8JdfPCeJhQ3W9qIdugEHXFlYYtZLEuXAlBGkHQQlnL2XeZ5aYE7xDC2JYQRJBj8c5fYfusrnqBgsz4EIO5ewfwmX-OAJg2d9Pm0UVxGrXtTW1H277sVslv-2FcU32cZwwls4YthQ6fyoIVLzJTyMOYJUrpFW32r5tG425wn_Q8ezmTs90EKuVrvVo8w92JL6MDKA-orDvhvQ3beb9l7Sgc5yy9cb90rjD-lyQBgcDfJ0xHFnhjnz4S8t0yga42xeRI3r_mXd0NvRzTUHkedNMtRAdU-W382jaFGRBxXL_4YziKyewh_nGh6BlW9EQ83Qf0oSwb43IN4k6GmK6KKvwr_KiERaBougue7YpwtYyqCrEoMiEEMn-Sog4CeLzg6IuYx4awivB7VYGGGwU6Bwc2IkZkKUFxVhJK63cAwQX5Gcve_j_-WcRRGlUhI9W4RvFhQFpl0YfC3cLUzRQZfV_fWH2MIwrJm6y4VCHhnvx8O87qetR0kM7el6lY4Nrk5bNtCdBeoyy_C1sz--DjsmM-z9i9IR8PqMCZcX3gBry0Sn_js4Ka0cXPsKpM-GpR6L0CLxge1FdKNDSFUOacsiEzh3-LTu-rUUYglWzQShuc8_dtZrIEvVocirTKZ3gaImQ1M1EylwXITBxzCUW19Io1X1mxKiFpXKHtzK7AvEs0kdicMBNl1HsKSn8OH3jxwLSHI4DwFIGYBxCQ0vvG3NN5ZZ_c4OnSfQ-nojlgmeCjMGykcA9E__NgeddsOdWxnG3fVQFIiMzoJ1AtYnxHoPRbtVZdyWB3dX1L9AKxlFep77w6KS48z70KzKseRnKLa6OCPZwfXgP5kEKA7FcKwpwIaMPNxCOedtULYeDhclbLeDtjK8LA2q7a8elVyK6YRvseXaZ4-nnd7iLYLZNOv807ZLaYGm51X7aFt0YRTimfsQIGztdkY9aakmyH_XQkqPmlNa75aE4xf8FqLjwa3AZ9PcIS8EpwX_Vw_pFA0NJcvJxCBgY4Iz98FxssnBRC9dJ1aAn4Kd8lgWvHIXS974MFCCGhfI8RRVDl4S0QO7W6vrGTIZB1ngY6VHZQ1JG9NJOGtomR_8RNH98FwcPzVNUzy9AhGeKBS3WECJCxk_gKjcGB-rBogS4EU0BVCfxzCoTMJF51ufpG1k4eWlEiEpOqUYgUWAN_3XYWNhphToFLg-h1xmQWWUBiVS6tV-XVvEOgKCKp_b8dMJ_99civ11moW0s3XQpzbxo02gCBR9LQYl2OPBcoRr1bVQfmS3sljBMCgtj5NodsMpz-rIZtgbzdchFe-RE6QK4qaMwAUY0oldGd7nIW9V1C3hnGg0kekWG3JKlxMhIB3IbDAVQ4jRJ90_JbLVaj8v0cNmhAwT0QwIwuTJJYFDGM1fYrocL0UKFsHEdPGZQFnfGAeFoMQwUt3I6zpmXbIqWA0VpRYwiUwTTRNTSsH1_eX-LWUnbXBsOmr6X38Sf9SQD2giVwmji2KBw4GSfRjUsbae5gpgZZbTcXH2ZF4FK79B7kM3RW1yKHcMrT3jXyZKjfEee008n6CJraHTc2sBDtV85wr-TQgic1VgACOfee02nwbPgPGhlUsN1e1cBwTGCJiIthec58AQtsEGIsqpTwh0axbKUmUaOj7zuUjDTg0imRCdYb_iMh8ya-YUncdYTabPkBJYlnbHzCB7aXmq42akqBQTTTgVgUsrRy22Q9gn7CkGltOZRbiPZ4Oa6Uzu-CYOsK-0JcD1xUgtTd9icWNNbAg5DCHh8FhryzVmRa5VUkC81OQryM3CgKdyzyw4xSH3qw2HcCMu7VHbHYhvVEXOQQtSaedW6w1shQMbPRKt0Bf_n3DTiyvSsfAgZmA3lrhQhRzd710dzxxljzkbfYEl3Q3SKg2CNM4Pu8SzAcJj9M4WubFMqDirRgVIMgL4xthq9u4qvIGxTERgAu1h7xhUcA9f0IvKiPzBkfExW_QIYR8c9kewkGILCplgqOHbvNBtqK5uXJrnscBUm-Su8yfc3gTiWWlsb1KBm2qwj6uXOBWQ-u4xyatyltsx8AJlshq-YB-K5oJuvlwCXkeXkU3hqRM4SRwLng3VyhdL0Jr5HUv_M1ENVemAJCR1W_6IXWxbChAYiRUFVnGQMCf2Jx46eQo1sNMaO-1r1LdtVSJo4ZELftKu2X0BMQC-l9iQ5EfDT2VEPZvl5JszWbqWIlkr_RY4jwbY_OeQCkPaMxE0eywBeG5zjdTYzmPLm0YjmK5J-_7tjM_678RIQ8qyuFPuNRGFUClznKIZ-T7SYMtFie6XAQ6j3q12Mh4-zEomU1jIOcy2EzZzTVgrpmqVtZUB9wzPIsNtq27VtLz231dh2i2fAfAZHdvIy_7XQsY7-JWltkQ-fY41Dw9QOIhDb_KJHhFNH2xa3g3NGh1WxZIiJNfPXXH2pMA0xU_FnJF0uPEr2u0rEcTWqTsDgHk4krHglASUYsJYneG_YgBCHWWrGXWzbQNGYsZryPJeXNcY3hw0wO49CxV7gb56BbUNBvNIfgS6SogajoeoPTkPQAICjtAVhnrgXyIFnQ38zu9Cwjwqxy10jt04Gwm1Q6xAh_CNQwcLgtJ7elaM7zi9uEGFskPfZHF35EOhpMwR6wBoPSv0ESs8PX1_WKhYSakFyW7SewR86-W3aCDR6xznTr57lJB7BnDb9_fF6rjfysDLSjofLGwjD8qC43OlMNZB9m868hgZoCUKvSnTpVW0B2NcAoM8lgXDox6cxZPtDsW65C2fMFUmt8yqLg9MOB9QRvr8jQVvgQ75GPADaHTVbcDukGOlpWsE8qHc0y8sbWnBRwGu4lUVpyOe3R-q2Y9DVCPonQoeUt3r6EfyIPeid7GaY1S-jCTuj5GlZA4Ridz6yYYZmGXzju_OqZL9TpH14-DvywWaBu8ZUqvz9kVamnK9P_M-jTDn6iz2zy37xyEGtzWT5Mv82avznCG1l0kSoG7HPg2kdA2ngIutv3-sn-D4_H3_Wzni52iLO-5CdMjEHyo8IRF2gsHDwR0mkF5uGdXv8RD_b5KZtgMy91QfiU-h1B1OTDWxxhfSPDO00EtPBW3UPQhkMJY2_MdHzKiG6i28PRjUTIYDcQjc1RrUZFuBmD6S679gKEzKw25fKmSbk6MBIhBfV1Q0h9uX9RauUq8yFRB7mV2EQgMRzrSZd0LVqNtBcOCU7TdrpzJzk0pZkfmjIVGOAJ37T234ICX4_M28IgaNiluXWNYvW8j7k_nTy6-8uRVw30AJnkQRswmxllkn8sE8pfxq2ACMG6LhiwkUeRJU7QYz8GMhtn1HcppGw27GGLZDbd1fHQ-X8EyC_pEx6wcSKdLWOZJ-TOqBWCDHZAJJ44G9MQ_eYCZKj78LA5pooQ1OQJeno7YefrhaY7gsJEY9LqHaDBBrDYPefTlMYgHPkHKxgkT6QtpbAHN81lB5uiiN-o2HPIgI45ODYY8pmvk7SY5BVsu-lJ0K3KZJOhOsfQsoK9CWB37yZj73eFNgWO9Wd5qmmiRVbUyBrjWSXc_dLnbEAKxB08xoITcG4hDIO1TSbTIF1QsBKXbyH11lwKM9Gr3bGckU_ni5H49T8MeAx2Cce-oeZ26dj5jDGQwwwgRbDf_9eKjzVzH0MtA32QPr-ZDqwIPJlpSAIswVKI7W6-TVHeKdYjBufEUoVhjsJ2kZLNnwsgUPySarkA7PjTLxcS7L5eXTIzBWpcSqQfY6eII492F_RPgaAzRnqRW7FA0lvNcCblQJoRK80DLGM_oZajzqytR-ZgfJvWQXY5UAcW0ywx1hVklrP5H9hxJBM6LujBC-bfK2gatWTUNoo7ciIWk8WPKZf9jCnGd2s9YQhwqJfIoYWLYZj2obHw-WfedxSpLOl72ucoXM_UvtvSjnnX18plcNrQ5lkO4f23N0gh_oZhdwYeyeb1N-KADIKIdY3_6tj1AFOqN_vXTuFtEAilg5YpHC5akZeMvfOGunAVza3qucicsRDEYutxcXggArT_nUZa_j9X5lp9EItKRVyGjBvRa8VKDwoHe0Qq9JYaDk2zA0Gqz2BsXKjxS5eArOJ4t-el3UdlFrsrGz0IIM53LsVDnYFGo7G8sQWzxQHD3LqVKhumuL4q0I6gBmOZBhAzzAb-j3dE8MFDXLKOzpMXj4yY_f1BqaSVhA2LxC9FXh8xlYclwHgweVkA98obGvKfW4iMNKJza4tQ5A1QDFPDwcsF1biEPK0svQmSnHNvjhOBM_hRoZK1YD_RXmIYPWzJnULt_2Nq4Fus7QlP0m4I7qSxDSUe3Ly_RtLefBaV3G7dUa62RQJfXVKgbGQTy_64COJ89TVWD5LIEPW_LRrYvSjVlsMD7LPexlQnh6J4g3zq0uRHxcWa1bDQDUQYrQp4Ud_6qc7d7FoQqYbQgib1M_MIbRyJezKZJFNXN8aZWzAkSjR6Luk43uWgogzv_PLON19AnvbC-eLg3fE4aUvJAueCiTQGGFkBb1O2IW1kc4i8wN_II3s1TkjQ6KSvre1kN4YMOTk73lEcC6L3NcgOd-o0tPDO2O9E6I8FG4yCWmnFPjPO1FFmEnjAUSgwhEs4KdKbQwRphNPnZQ6dWsjKPVM5AfmEiLx8drX7C2NFidylmW1dpC6T9L7Qcvd2YbocFGnNv3j4ztPjt-9Z2Y4fZq-02HVNkkuOO5AB4TdPTftjgiGipnbMaBmgBNMwbxkzHuWZ-avaQfSifAvfuePdugEVjmjhcS0NQuh0_hZ-K8m0-41A-EqQ6kzgfYTwKuQ8JdIWawuYoM1Q0G1bJGpwQxG9DPDB8c6y-WupSOZ8c5l2pWsRVw7UJ47hHhFIsoDHFHVDBT9N85Y2SIRbttX2pcnKj3nw7aj6ZcTRwpNPN-Qvu8YMMjMUVV0QoIn1CEyhim0x7jqidBvcSHLamlTSqYvzDfI4l9fSA8m4Yar_VZSMYMxls278D2sxVIEjXt-fqUbXc397qGzvNniARzqZcqrataPpzQoOM-bNj5LEJJdYPqSsHioJGOkhFzWXu49UuMFYUvyNxOhrbUy8h1N6GKiGDMSwe9k9wN-5WhvfEf3wPAztWl5R4PFRf306CPhL-FW83zhBr4c1UxU56taoVNnJtsblxuTTDJr8HgIiS0bqCLpL1s-ZYOgARzAgymuZCRdaxTmK4fdFhlTs6coahCbrSXO9Iehq58t6uw55hGhAqMjVvaRn2TpgwtHS2jvGMCsLFBYnkVXeeCDwA8uIEvujo_WcIUiT7STSP1IHMyllhlhU9tb0sD8wadR8caAgHBe2CuuE6YeO4qet9JIzOLTd3kJRE9Ev7aChlmuuAElJ0o-ktfVIvUbwVAwiWV3X6AcMlmVR_6HzhwZvc64Phapf84hPMYXvnIxBSI5UbvA0X5nHU2lnqPeRlhQI0mKXvLk4Z60WTgGrJoz6mjUQNep_zG1WTSkLwk4zlLwupc492MMc-M3x-vYQBmA0J2OfXEZjnuqAQ6az1hF9SaaF87c_W-Dkd5wgzUEkoUA2kjAfLtSItyltjCzxTnH5gGs7KaeoN_9V3bj_EAquWTrF9Vdr0DyN3fVdwrjU7oZhp_CVfondyy_VQO2wtxzBICKDcgraDmcBS1Pw_VPEIXvNm0ia52zwDDo6h53kRiKECACeOLLwif-WO5IBh4DZ_DFsiuaX1dJyUUO_7vk56KjmN0QEHxaNwpvKMuPtRGOMWkRAwIKezgkGJ-GRLXbeAA_1qqT0hLDsqJUal65fXdZ_J-qEnJH9xThlPem3WrWpAYKXeVOLOCxuA-7wxyxO2DxHqJdxsvzd16aErXTcIq7OgGXL14QQXLcpQIKermnxygZf06I83xy3pkfwEY07BVX6MnouU0ybMlqeFQgsWFnP_yjPuYGA0RQGOqsL_Cz_aq94VrHtzL1M8NTQt3Jhpr_L908QQMXN7kK6CKJnDkh9Rzykak8Lig_xmz8E42bPY-RWpAgAvpju1nggo6H4oH41IfQYW2gVzTviJq9EC1rP3FtJouq9gmSH5xDo5IW09XFskxJatkvOUIjgtZhCNG_VxtML1VdSDLZSrYjMT46SO8JjWJcn__4tR6gEmTrzRE2OSjbLuZpOksXgFrOgRDsZuPSeBAE8VKVpLtHvRQKWimJumFONfHJ7JxCOaUSBzpvk88Wg9em4x7YAd_SAChQoT7XRtjlwkRszQ-TwYfGsyOOGiTyG9dzCGGy_fsTugpowfedGCGBHJpuApn7cf5NNyLsafquuDtEyUly0NDpCwF2i4Dhma5jQsDEbKOlHnq8uzAkJXRe96IQBj0FWieRJyLU-pNsgXz2PqRxNXs__iId_f1X7avOZHN7FyBa-vE-u8RuYGXuLsUtQnnA0eYesQ0hCvGHa71I5E3-w1DCu9dLeY725SC1yVZ_vJ2WJmwEPXJIXKhVgTfvw8GIEml1VGxRFvb5kMQtGbXChL1tz7Y35ux-SRoX4A23pTZVEVquaXb2QjNFOprmA0tuFeYlsUdqD82ls4R1WzgzLVRRF4Z1Jh9AFgfYHqV-7UHwJAY0OpYK9iu6PPknBPAxWsxnLxyIxQ_rRnrbD-AyW-uFhBZ5d38zkvKw68Fr24Czq84U_OlBAvHtTWSzQa_6pc6tu5KT43QDCeWwiyWt1gdahuyoqGpJNgqyD6gh5xjSr1U-ahTJpXgVjnbNBkfOWecj9GK6CMLgvcI21qVrX2IHwG9kMyQgNmu--z0VHXt0WUtEuUcHMM4PzFM5AOZ_oxSVtIbvoYGDXjUgEI-xM7BOr4e1B4n8X0aoorefQhCLe1-Lv2pKRSeUlX60RlVuRN9GkoD_UoFqz59zJwL3h2uakwjt7iehx7DeI2pHUthZL03BqsYtJth9Emw5gsDKfBIR9BAjIzbSFRnnC_pthG2E1WMRMeeKThVkL_JYkmFj4Cr1xjqXXCTAI9QFwcTqRI4ZkRgem_jqVB7H9-BzVDrqgbQoxuWhNRn3_w-xfyzv_JtRcP150_7bEN2-gbBJCexcaF-0PbkopUuQqUjE3-WYKc9X9vLWcdkEehB0F7eqzdIWqRPTsnEat4SQhSvbaOp7EgY6Ypkvjkheer3fkPelAHN86SGviWWtaxDTWMBwHQjM866tuDKWOEnLQhMb_IjQDFKHrUKUnz42saPlPWfvbas8_Ymk7bX-E263Wzb5_MWXqPHMt6UTMSOtw86MTE46YEW9Ww-WW10cmatGb4jfoQHXa_JxCRry14AjwF7CmmQLP6dnm8r4_jm8AylHV8iKCG6r6csAhY1jQ3I-24iLu01EDB6H-_bIX3uiZDXpf4T1aGBJh7I7INB-Ad7d_IV7At-qaorPyE1xvTWeFVQLymsE87ZHY0J157ggITtT95e_Q8_SEiFYg0vxg89qBpuXygL2M_Pbrb5eYTCA6K6N86CxlOvFAb2AJnhAmxe8c_KHIsFZPL6lReDGQmMPBuvdCjjLPV7seEZX30ZMTuHYXNuD7IytEJ7X1o0_04eCmcqbivHBCoQGOzDhQ86DSoX2Omx-hmQl3hI2KgKnGcnfym2Ukd-3CmHAyCDAv2kDHm38H-JdcsO2DNk9QsYtAln6XRVl5kFDnWEhm9bRh-fg9Lmt_mNkwHSwZ0YrdYhAOCMkNlukUp0EYKKhBSY8lsY7a_TPbt8vkTMSCmi2sPr7NnuyaxMvw6Jblb9OD885lSOUp3oPpoH8QPkkhYUJ4-HVmmMGD8orSe0L3k7lLbyHzz5l1EmMahHWCCbnoMGGfO2QnxV4v9YcsMmIA_NX_1CjMUh_LYKrVWE2tfmhj7Zdprbop3nTylHV6YNet5h2MVUtpfj3CFTz-7V0AxKhqmTkSE9fMv5_XY9-QxFKf9B785SPTdj1xBiOsQ0uz3TJ2CPFHOtikiqYkNu9w2cUgYejqlM0crBDpQCuFmFJCFNKrfMa7eue_4H3RSh8Yu9Yw1LXbkAuGoFMGYhegcBEvcxcDSHfZ9f1HFT7IgimpuFuoGHwaNhPnlNc1uI1ILsFeRrrXide0q3L78aMAdu7eFfSSXHm-RcZypE9LHU8caoGqd0cr8hMAFvmAacrXiUE6RtzQUZjswSOziVVwlqyszgPXIuDsA4m0AcaLyEYQ8fEsRZAg7RyRbTgMGrlo-_L1Me2JMPPbiuNi2EtBXz_85Ylbaz45KQ45mdka24ouxzs3YK5aPi-Bv-fYL7FhoIWM6AiJH5ETjucj9KrhL5u-mnEi7sYh6ttj6I-MtSpCzOLrIB5HZ-tJktRhN78f2m8h6N4FBL9ooQXR4Y-QC1MG4eRlAiugn97K-r3MDGQZR5fVwC8SPW4Pt6UDvfaxXZek0HmjYPEk63MIxeMBOLaipBGR2ziR6YsoTUZ3NOopXjZr-UsGukdLw0OIJsxA-nGjmOZCr6iDgY-EfaCAVwAOxAv47u05VBTOP1xoUhMrxNefZ1lt8hEziCDaHInMkDdc4lQVeYv6H4rR2KugX0IXGsFc-C8sfQVnALLdQNjEg8_AfTsEmY3NqE_ECIUhFwxaW8s8aWBgX97Pi8SxkCwX6DyksH9fjA76rP4P5kpWl7ynaOaCfytRliE4j5uDXXywFfwN64DWKIQt4u2gDGo9d12CWUMGrWZZdn3qn8IgEDmUdr_CGXIGcPNuS-wxWoh4G8eGNhvMk1V9zhyhcxgbjoIJLl1T9MOZZ8JQVpiy-cPgClLI2jgIbKSVZTTZ8B6T93aQj5oEbOw87RZxArjYP2XeIHMNh6JUUOND97h1D-tXlI6hlFtFTouMxLzyOpVJLfdrUcr2p0bkbNPAyk3qzxwdRWegSWH2nojJVRP5dopYDUvX3a6sXVGUefUr6llKEtyQ9W84oVESDWyhWRv6GiBkpimAlkoolaGYFYCD72gUISM-ptvaWmVvNmXdZhR2JCSn3Ec5K9TZMg0ArIgFvnJeksow6nIwDSYZ_EXqtEgn9hjLaOcKZSrixLgvGqWY5phJcyYWP7kBsJTxc9U7xCIDh_RCU8fjZzAOAl4r3DtGTEntqzqhScZ_-Fx4ygPgpi4Ko84FM0RvNQGw5VSrOWADroETQVP-La2KyDOjYo4dTauA5ArmYnXyLatcyfbnvgE5KofVhMHwPq-QSV7QAaN9aM3KdDRxBXV7YtnjPx5DzLQE_61NLQkdC0iWFjHwLwM58comkNfrKAUw3vtLzWDiLHT1nPG0pxYBn0zAid0cdOFJ3JRJl2F6-GuMSeUK6kCqbX4mtShWXp1gn0YErlKR2PFjCDNj1o56a5ejMOYAB_SNIjRLO_O7uGofXv_Om9Uevp9XKu3ca86Qt6uOpwQsifkwS6j78cGRTJeU0SlIAGBjzi6b4aJN--CpFIqF6JpuZAxhiLzsHAXRAKik3Lu6Pmb_24KBL5_ktbQRcQX6GQjGi0A4gccSOF3hdJ9j1any3RaFOA1_0HRAv-ExWoiQEyUnWALcqaC1FmXgDTxYx_VUMjeb-MqxAV4eHjJsR7e1q9cJS8qhubSQbHMH72GccTJKlZYdLBHmc0Oqejf-JKgaBMxgkGX30uCXhT9B8dag8jVrDBemQV-wak7QHgbAveaWX74ZsZZF6ZuZ6YU1llAllJlLWPVNr4aaPj_wMfurz6YyOJDnCcVxcKFjBCJRuTBF1ACh9Ye1aj5wDUVwjeKXnjEy-quQNoB5c4clujc-G-ep6-EHj6WgHZefu1HYolZNprU9zHY3T_OrisT2jDBUByHv2RajGe3K7nDZprR-e1SPApINTcKQ42Fh8SfDQsXg0qOfvMdKbfKJqQizEQiCtvkQu1oXhlO8fC4J5UkN3qsPcdG_h1TQ-_zlAPDJ97B_92zV5NkIF3XFM2iQht1oWwZdN6xwKeDRqKmpER-qz7bxiy9Hh1IxU5T_Ac5c8B5xIxbQzgTJal2t1M-_cRvGT0CjpEBjRxqts-KliiGxFl48wNePKySRiGEfnn4Xfqmy4enbmmZgyHCmo-h--qxLIxBEykrcQurpumcrK29z2_jGUNichMpAaaT3UlzgVTbOVb3gVN3Qsu8ltR1RtlO5DM_Sc6q3GQ2QpdHafa2S8Z5D_A90PuohDCpyqvS7tA24KNQEKYM2W_ONMBNNEoyU2p7hZezbbj5T_HLHVRPUiVLgugGFQkNwZ5cRgrgYqstoKu9VJWFE-odBF8G9GwHGFFqyCdBL2CADSx9AnfEssP0TSarXyn-ALo1n5f6vpUFmkcuY-4gFSang5orkODd3k7hSmsCxs5NVMLfQxPtjJcTTrKR04H7xAVNnt79YJYVW73UaXEUammc_qu0GAuNwgeaX3wIQv8ieBeqJvGbfOoXd-U6c8b2xS7b_9BCWtTKZ1A8azUrXAqOr5rXlKkq6I31ht1XzyQAWq3_YWEc8MJahqr7bR5GQqOxRg_adTocY65i1qhxebStP6XWRRurHWyHzDhi9duKfGK_eC1bbuUIevXsNDHdQBDNE8_w1BBBlg4eFuM8vSDZWJEKPxvB4Vl7ciLOs6-diW3bj_JDo1BZlpdDQFKCwDuk5RtRJmr9hGUaIbF6nrjbFduzQFh6laU7VkD_3XyqJ2C3dCD1vOOhslfiVG1fBWHpTJvKsgfLa0u94IUipo6YWCz8K-LCeOymEufdrfaI1A5qutL6tF0CaPl48rmLRMayxqTf4ZGCCDe49C74wOS_kGmxchhr8DKGUgKwiWJWQjIQLIk2PzaHSQ4cE8uBQebBsCMzlrzNr1YhYzvzhje-qorpNcwCluQeaXkqp1WST9LbExS1jN8gmJhLgS8yAOd_yGdJchugXdbfPXWD_R4oVf40bCAv3HBB3MxQKq8dZeXg_9xqr_bhwqY1oUraAHLEol6kUS--0eDJ9PzaLed1ZQ_6j-pHR-mu-OkQUvtM-THVLuNMKWGSYKcBnOFYw_1NpEkwoWtcYCzk-nq-aHJ5XnijDKutRPJQ5W6RLMmhB8qFoZpRp_aDS5LJiqp-Q4g2QhtSCckgUwHN5GSDTLaYvjkR5jeIDI0Df_tQZQv7BiusW4M-iXMunM3qpOcdAdfnBTmODqjdeBAk4dRnayZtb2Ib-JKl5ywa6WUDhpA_UQA_sIlBBbTjetvlH2sChS0D17boDPANxqPYQLorzUflL42ay1DQFsRRdnxTiNvzN3nMOxzFdIUYqWEiY29KQmAFyuERLmtWNxvUB7KB9WqxV21mbJ-yIhTsuUTHve3HdcJuWPzEtbZemmvTyJr1wckTGBWVfeT20e24dPMpBbRN24Mpx_tMxfsioxNsXFYqKHzqWqZ8Tp-gj0TUMr-dATGUJHHQ2Un1nVUYhOfB-G-cycBf8zmgcnA9EsKkTOlZY1LRmvBIknw6thweHCggBJ8Ke5N7lgYjdTTPs9HXMZk-YcGJ8Q-TkB4_Dw35xq9_hnncS-Dl-_aTs3FD-V3fAbAd9eYbttpwk9kwVnc3GzF_d-eoCntwtxNH_iYmdeBZIqLZAoDwzvFnGfVunFP4RiUtLYepxu1m7HLhPSCAQn6SNcLwGg1U0jQpfYIYGZTL3Ntq91XYv3J9vy5O1apgQZic9XEMxzOuoYf0zDEU41PaVOmGv-H-mdrmH-MI0AquibmsDkD1GoUssNDqsqGVBgMMp1kc3N6irmLeIpdrSjOLUsW8eq0YGWoMXXxp32wIfDr1fad4KV22Slqlrfv4RC2v15WxVI6j8Cn2l6ymNxCj95fk55ibBk8IgObZEwbu-O4F6focQnbqXcLMSHipxWVOo0PNAnxeG8ER8AuVaimP1nXVWhNo77VuX_Yat85m9l4Avt0Q8tR6Rpqruw0cxZRH-3GRk97-svz5QsXMJgNZsDquzmeRT7ydwFrr8NK2Ei9NmlZ4pziY4xgIjVIJgIhgkY2wEH9EBDPLuqmYrA9z2RC4KUg5aMAvhRRZ1Jrxd4uv6C7iq9o9x6AOVwA3AzuM-A42325s1cNlnURin7VjQvoDg03eXsB-G-iSEUw_WoiFatKsO1U8bW4GP1-XwaZMD2w9-NXF9JCCGp2PaYNl79WZXpoNqtOv7CS-USx0vOF6DLllVZebsUhgMTBHg6I7dmJShzC1VLrCV_XjFCVlxfSdC-HkHceCUwQwQvkH7CzkW3Xxqn9onVcL1vMKgt-D7ov_952u8jsS6gkzEkUZgSFKNUMJGZv8J1rhg-ZNUi_50EsohJTlxy8H3xw8RFN9JsTZ7T7_O2yJ-yB5bCdSHldOwfQWtPvCw0df7yzUQtkMqMY384QRdKraWO3CwhrqD5_j-iqM1nw3AKDnqvUZ_pL_MrJT5OwqvaQLlIJpSymmfw642aXt7P1TzzFnwOYb0Myjc0geBp6JKLB4MetCiKUxmYP8M3hiH8FSZLv00jUmVJj-CPVj2IVml-IiAPyPU45_2W_Sek_l6JDqxgviPNU2QfLqXLOgs7-30-8ZhrtlZLC1AYco0hIEyVvFBQC5CjorAuillJuZ02YU5_kNwGG-Avbqb2zLhjw3gO7ZB1Lz68cv8F5YVsUvCvMgRhgpr5Wj_5uFtw23HGXHKY2Ejm3Kjya_Tw1EbrPl7t-UYyUxZkF6lUh-ZnndeOB7RWVO9lDvW-kuu5XuYFbAM6ouYOPd0Am1Te__qnJe0cYwKBaqopwTCE_7cu9EH37OBm3YWyGrthggmOrcK9jSI-xA40URX30vYvyuvNzZ-0f8PrZIfTtss2f0w9om6vDpwxsWhXRlTyz9qc0ntEgVwX6t6xWklLasPIwXZpahtO8PAA9Vqy2D3t-nMSyeBaPMhkZi_k5x3ckiLR9RHH1OmiAyYkGafn1_aB381MKMv_8AS4YGzeAvaHBwwfNDBlPpBhdupAGXoGPKFCM6d5W1QoDhwQyIZ9uFKuvoPtxntY8MwG5x-Vwmg3GhIDiSmoybRNIpfIqXUVzg5_a9p9b0-Go59h9B1ntMB0K1Q0X1EtZq-tVRlv1MRpSjOl8LFyGFQ8rYS0aY54cZgE_tdOaozg5NuXDJPQR515WrBf6NyJ2E66D3u1Fde7hd-zUMSiASQXMKwCLOAMNn4f3MWoj6UR3vKPjtBNwF1umNrE8P1tErywv40kYGz8-Zy5Jub9dMgKEfXbz1s6XIqZJEDSXngwVYNQx2fhaO-uGxt-eahjkVAkt1KoTe3sDxtkX7CFQNAaVBlsy4JEqRM1-Mxg0GfAP6M5l6MMhbqkJoN4oC4TVUlASghOUHqkCorULtgKctw01Ea9UnPzXz-KKpA4RllrWdUryiRH2A5RPs3KH6mTKVjJmzXvs-tHHeQphSLLm3QV1smoj9Z-oAJrz0C-f_Y0LE4Rsaw8Ag_7G9OOrBOD1odrNT2PbpvyeMCv2179maxKeUB3WRIU_Mz8b4_vi76gODzX6t-K5zDm1ukMlpNLfRtD2FZOEu2S9dGFFy-Ut3gB8Vnu_b1wnzETDDqWZJ-6bo9qRxrRAkH6q3TF5VTKv_hnYKY6QzcmotJrdTNPQvwCztcqj4c45FtJyax2tdOQo4lhoqDapMA9TawQMxunVToG8YmNP1YKJljFq-ZFttAxcnIpaTYq9scd3cfS0S63cnjaMT_H_LEBW9FedIR53Ko12fyQn9cLgErigUWMWwgdTmE2rPo3ygRky06cEcrh6zUtNb5E0Xt8FnmR0n53wZbJHsX9N6ficGSVwanB9ZBGJz5TmRHdF2aE6NrALFCVLZ_9mUP0XVz9HSUH9YbauXqYM8afLJ_R8XNm1WtqX6gWkCG4HulNtWURyTWgVuQT4jiB392QSDulnwnUnaFiroMxbHD6UENVgg78icspfeRQ3I_wEKLpCmngQSDvgNlV-vzVct_920i-n6DSDav6Ez6MgxCa0cgrF5Fbzak-koA7olgU2xqiyoAFv02H76alrTcE6Ooi0zNIBABz8McKSqmJDhJ3RTpCYQCmJ71Xq3xdeT-9-WBX9QgNEGQ9BAcZNT8IHY7yUocfYNOQS3XbCogSc0HR260BC8-8ijyyx1RfZB2kErTGpUCo3FQJLg8QNYU4cThUe1rmgzC1aJSHdYD8OLKHflJCHZiGGaYW_MA-tBWfHiEISIUcIghjbVjF2dBoMZBW5hlzvYWOV5y1QXW0zvTJ1Tw4R6kJGWNTK4wePkrh9W3t4wMu2QvyJQLGGwb4ltSDWefD44MtkWdfquG7OTbXqEiPr2KreJ2j3DASXuBDBD25RvlZc4bhLHFj9BUJ-lulsAvDWKCb2Bou0i6akOancevmmSZUwphs-hQM2b3ugNTsgsUEoF82dXWCJ70gyr1RFBfBsZCYDMDWbiqMYC221y5Pw2zoHRdQ40xDVCmTzDZZxzBr3ywIcE0Y_6c9tlm4e6EgOkdHg5KaAV9sV_uMLbBeSxyihQgJuxA4dzQnCo3Q_owAGtnkvhQp4UgYlx2AeclHenpTuFb_t-BsO1-DV6LgRplzfXH7ocQedgUXsd-gZtA61tnwNR2qRk9dbmtOikjI7qf7tFv8r0pRbe_d_mNadmgformlLzAtUn87xkZLmcMx_iH0g7gW7gbEXnkKmX9syage0xeQ12qnGvGF-p6mBKFUM7d_8ZBFt3pSd0M2Wl1zLnK9HQJVPXjWWBf8r9UecYdpyhtZAnxREWSqG1APYDP8cPpQcewy_QaCnVqyYZRFkf6X6ch-O9sJAwzR4MLElaZ31KyCxHTj8565hGC5bJUdg_I91UgH2yJArG54y_Yc5Dl6ALUn9QgPzbqDFFUOJjwU5o9uD2XyEBYzEErekT-GqxtSGOgCFSStNay_o8OmjolNWZVRc1_aFeMUOgh_GJCAnBMs8AVNU8rG-2bL8Yn_08Lfn-QpqpZIZIVsTZinG9cCIy-nuGGUtwHtPdG8xntWD7d5rNUtro9BCoxdrnbFOkSAwCQ365HHDHG-D0bnxTd70UQLYZcAb6rkxFrENHGBQFl5f1sOWZnGhofb6snJCirTWsgJcst54Dzu14XaX-57i-J3gi6pI0alrVQhxukhTtV3oj42A2TUGD6Qb2P_PjwhVbwpyfkd9tNTRT4YKbB6v7FviTl7JKRh_lMFAeLiNc10auLFBnXOdq28pbt64ilr05QoEABo-2qj0w1qRgK1RfdC_x2WRHcrI7zWIyDONsyqumIklidGqrEh8EXCSg3a1PBLMIrUfkfyV8C7LvTL_lifHl18bZO1BJtoksrMcCmPiwEJhCCMn1olm_DSh1YHahgEFrP9PhmLrFpJrymDuzXlWENX0QfqD8_bsiaIC7sqi4ZCnGI-KCnePmdiATIkO1ROI0ty_1kRce2LFztuwYFLY_z1yJlFflviLtyjU2z3F8Dl5JjO2dWm4n7bBCRT8wAqp5eztDZdaiuQUZKi9vhIuEnqFpL5zQVTUlDpMWodeYlcEZT0pQQamulicCkRslA7Z-CThZgOW3QWCv3eYTvOlZ0merHzQFxYq-8S_0rfwK9BEA1xck28GdMIXUd5cqBN1kUPd06qbwbCAgVBABucXvWbmkCeokCXOyfxb2BHl7381ZWy3_U6M0AnKzxhtYBSmBjY8sQAeJg1WTQ0ZpbMT651_b8ipPHAUl57j9rwVzxrdtmtai0VoUVNv4UEF6gDR_byb09xWMXgCWHrBMbbs7KNNC307cI7lmSHDwFDiWjxXcZtGMCix71kfh6uZsRBursMcnUoIaGvd_Pqv7SKeo3c1DXs8d4yraU5VqtmvHuodSmfcmOCEkzLb4lmVfBZPrsJQcLb9xFH8wunqxWYhr2ERzOJDZoLIKNwQnPDcxoK7UX_tLfbHKAO_CcfHWRgB_NkcPVvf8jViQRTrskD_19WqQFq241yN8yW4a61C6v-9og8yJyy8BWPQdiKESA180YGsfujYRx40jXR1u0g-WgRF35S97vOzm963EAkAmfCPBpRckAFxeDcb9DfBvhihOeaQEobt9UNhiDTNaiSN_Hl66wA5DIPIptw0_HQQLoVQ6HUevZymcwe9A5p7_AdCf86KBN-Z6cu7-5OTmctbwROcfjMYjlJLXI4vSE1fY_BdaYPBvPWsGaPKTNr9kwy0RyDrYd4a3hzDBzEOAGUJm14pdaOSbjtwoIJ0m5TeQRm-e-EBqxv4dcABhod1agzhWgyKZarIrtkDhGW7dkDqSdxHzPCxphtD1a7SD2MdKfz0IK_IkPRSr5N690e9kBMO8r0MmuMg85Jf4vA3w3-ywnIbaW865qXxkW-3CYgJ8RloGuBcJewQH13Ozoz1FAlt1Gt5Q-uHiMokLpmbCmvGVk7xPXqDu_sqRhQSjlEXRBjmGzeotBxxhTwmzqZfJxRXEdmGAtrfqva6gzYGgSdXFWo-_wfN2-DjBa1Z8FAxpmT-dRPNvaKwOmknS-tI5xi2i7kzmh-oIn8n-AJ6WanEBaFc5vTC9SnQNxnjnnbTu-bRMj_KlXXpw-ryvlGEGhdMOqfcgSWzQLPBSVMJpDU9rSZMfGl77Q-S3q9mRfjPnd6TqlNfOskpiQijqlKNvhC_D2S8SerwBOrWTSZ2i0W2NKgtAvkgn1v7wHkNIp6iJ9CU0mXIobg1uDrdvReirxIxuznqXyf9xma99oqKmQvh4dWfhlQH-a8AB1Hl624CTjEs4CcoZfCm2pMpcDie4gVvQiGkHQosnTdOA12IX3REq8peIyawJpoyI50ConQxCFuWqKfZkxvaLMfVAHcpvRNrNEF-jD1lf6R1emRB8jW6iQLCKYVueF6qfUsmb6Ql-gmKcakkB71QGMSGTa91eBg--S11MB79NFQdZhQDpYYc5GAAKTR3PF9Cj-xk_33qn0Xz3Xw5jRTZqm-qVcqPMwcdxcB9p8JhtWuhGcfyGmON9hM83JHg8xKGUn-1qPOnvF1yWoRcI6wv7Xe3jfo-_RHLEwbPTbihfw2H6ycYxEl_iz9zlG40_WNJwwWDdHn-jsau08fNxdR4WC9FEvC7lRAUeQPVxUWE3ziJjlDMeZGz2jy4daSi-LY-QZCzarHtQ4_olBcW11Q8gtV0lOBrkATxbd7YRAL7_dh54Xw9T6X0O7TlpofzzAVMZzIn0iTai8k0eAzuj3DT2FiCHAh4-RbKHr7mzyrPQ0MUmJp2PomCnzG25BUbYSlClBcjtotLGm6YuDPzB5X7Lu_vH9eRjxMEh7ZqIYO6m81D0dwZO9aVZSSwa_LBb1iBFrHijTsL8rHXXcBSnp_jIaZrGLyKkxMaJDegmLd8HdgACP3rOqVCDg1n_CVE3_jRaqwwHJVpani_j77aSGBmItjp7HqbcgZr_CVMCBHX3XfzlhuXZkvBoc8ZaYYifhvgGFGEg0jHEaxIIU0QDqm2L6dHqCH6yAlkkT8zRgWeLH4Pey8nR2KTAZP55YtaaU38cUPOqVlvTmPihzfNHH18h0vLfaPPjA712C9V3hvVACSpU5SsXQU7NfnnIO7_5ZcX-iCaEuDsSFlJcAJFaSyKJh5kcXsGdRCAM5nVfyH6_NFHzGiNWaIqc-E3Yl4a4pS07bpe74bsEUrxUfdgmY9XULfNwuGPVg4qBsSoS8coVBn5SxwVR6OITKjr8Iq6b8EZZxxc6qJJe2Xd5mExe6NxAW3sClorNhS_wwcBYwj6HUH8SmXpZ0xqADYVqky8bn-pa5j6RFNSH5zz9deI4_1ioLhkVtvpbRFHOxCPzm56wjqQnEci9QQd8axmpiKgHP8HnpTzLHO2MgqjjunSox4sXOz_BEEPWghInV_VpmFb0KN0B4UH_M0f9Yar4O1unjCGwlLF_ZfLfNfwmi8JoDRMYIyFn6D1PxQgdBBPKN0oC_Z11E28WQqTORvTJqusVY4qoZ4d1FOkd5E9srOWuvs0gBGweaIzUAZHdRGr4NygezGmf27uWSos68ZHaB2qOc79z_TpsXiVeik5uT-pSbt2R-GEIeg8cwCH1J2u7UHsWLmJFyUmBW3K372QeHxoW8UKinTNg4Zy6uF5acVZmom5E8s957-83Qcs_unrHFoUTPy_KWoiqRefrQcpmCHra-JYSYwNxfwgzoCp-EHgl2ypCIZ5BpRQHgKweWJWeRhioSBwGejT7evYEl3-L_FazZFY5W6tKyXFktO2jIySP0NMGxFL8S-PWQERH9cdm7l1KN849iSIqeMI8cROEUCWjUIhdh9pXJnY8vYhQBfbEjJ2fJFjOEtT8ARZe1jBPNUFdoRph8YXVXRkHn0uw826uIzZGnacbNgRwgNdilq-j1Rj5iirOQwXSQ1s_L2Y2Gl8O7YZ_tuEek0ovZnebzesmYKtoY_XhunbD_U-4afK57BtBTsmm1Ed_AwfhZNV_vqKC5DraEE6c6J_7d1f3NJEMVK-QDm-iMLGdLHjOr3bf8TjpeXNjITXiBZ0kJBb_qf7Y6Sze1UueGWd_23NVi5Ufe8w--C9fE3YT0Hl0wnSRJ1WvOGlLQf2Hgk8KaazMuCVbkNFzjojCQ_IrmsEz2sbWOSMDB_E2y-6JJyET54mCpfMYhdHXVhtbAH0sdBNtp2KGfh9206nOJU-lKwjo71lgNm4XoWV5Ux1LXYSeN9r7BSrpirkFIqxyQkJez9Ulcbiz5ES5t8oaTwCOnIDE28Vy324HhGPSi5W2QPkCOV_PjOWCeM8yjS_6w_FnGuO_26ecaOEkCNBZung5p0pHSmD9D0SeQ55YvwYvwMhT3smiwDo9dRcFa6sigkWHHKtBLW29sYLB4r5pNWtHd6CihJCcG9DTTbaE5qP0-eOF1l4GKEhtIUKDPGJGwEzYHjq9emeIy1uacdIcWTCJylvCVOHdWmLaD1HefI1tjSyga1LuX-uZPAYEu4H3BHd_8RhEhTIIR2W1Zi4pcy___Mg6UnxiELbieUU9M-kBKnEG8wm1_VCAJVg6GulXQG20z5Zq0Zr8HsRUEpcO6ULm-_3zF1WYWSPU-JDi_ZiKxGdLOidzU4gb-zzrrLYtA2USFwdncVimCESLHhKPSvv6r2xX5Hz0eTuLmhshN4wL2du7QNz_mLVnI0aIGrHWQgs_DEy06L1P4ANm_Y-0xdzookmfICUGKChRsnNFH5Ardfg5JWwzC_jQrW1XM_t8g-3Hnv_A-UzUyJWBl3ezae1NPikowsbMsIwLuHHteDmQmqb9-93yiUdXB9FxycWFgaPksF17KxTvI8FS2PPwZKsSOTXMQNCQyFd4fJDR60nQhm19DhQImTl_QPvqibTAg_p5zlhxlEFdMKoMEdSrqovWF0mKoOLbIHlGum-tDlq2Ll96PE2-CrnW8NyHVDdew8iZSZ5dahyl3prZnh_EiRB8nNBESy8uH9ppuSH6XlQ0TJXdhwI1ZdOJvFonZ-7IBR1TVb4ynvpzRt-oWE-tNx1-6qwSJGzrsKnn1EYkDQaRj7nfztiOa9af0LGUR5ejBaZVx-bQ-75PO-xBTxd0UpI5kyaEf9T3rUM19GzASEzvIwPCPRplhpopMmPORqBqg1oFxqI9vzahfzntnYmWEBLGc2ks1NZWq1gLcSZLw947_EEGgyqw51cFGXLaB1DeA85qa6WT1jRmS4Fjj747XLPynyNH73NU8RWsx03F0y_fvUpPGS_vaXWR8AhEy-gdBW5CCYbsPv7WB1Ls0_DJMBSHylHgNQvC_5knHobolZyERyyye0rwmLca0TnAJS0QhgywEwaoateT_H3_aqypXAFQdqP9aXzDLINETQH-jPND97CG-mhA5bh_mmulEvQMxHyt1e4d2IWPOJjYUvSj1gaxoNl8C_v-h8719rmYl7e5jedHHzYQuDgq-i4B8HlQxgLycD2vQqtt9F8fadudBvjaa4qaHQNw_AZc_8aWNUQ23FdSfC2ZSwJvYASGSz5iwwZotTwF92WMyzfnNvdjFyluEZR4D2RXnYP9GUuwGcg6LvtzjZDq4GoOG8cZEqgSQpSUFWN4-NUVBrb8GLY-SDo08tW7Q42PvN8h6h6cPCpFgrKFrqEuNupBiw_GvD-Ihj6S81070U74EpW3yin5jY5dVGJO_Q-8GBVsyfe9VyPGlDCt9p2-FwvgP6aMZnWAQys5HjDo7QxHaLXAUAJEB4HJatbd3sDYsC3S3Py-_NDzA9_JuOI4iqvOjwf96mS8xfOkoDY0CyKso6cn7BWBDbtgGL5yjjAOrsgyRzALWaUehhq0p48D45hMtJh40lBfgA2QkEqXaqlFdooXKlfyn0nePdsQPYJWxg4O42Up_ha9yeggy_bdTtWJQlR1bpgphhsDFFhPq3rrrD54e-AmMPvLS_KnhRHR22d8t80bo2yhrXzT612iv6Z_2_wxWbm8AnUB1L4t1pnI0BW9MLhU0EC55f52wZCJQ8wJdRcH4lbuUsZ4ioBA8J6X-UtP7YjjBTeXITfvyCaLvkwGseuU4DCiTHh6mkqIq6ynzsg9kXqjCB7oDfO8yZm82JEuzLWaReeZSub0J4FAyCUQImgs3Ui1shcwK6IVbk57-Gjywva17R7qQhkYxqeDCbrd64y3QLFBnhiYSN4TrR5AaPiNz3eCYFYPTdMjNCWa7HMb8wgI8Bix513uKuS7HenMc_h1QwCzrD146GKiiEZ0LT2IIDDO8h_gKx3Y-7N5B9Og7wjsDps624fXnr889NYznFOBwuVhNmT4aULq_L32VNXYO7bvGEm8T__RrBnigqlftf0nHzP2U7gN3kKnuCg0VryDRRs30No9mmIxpCzEkGfEDb3g8SxDiiyOjZEuFTG-doTdRDPfe8DqiPTfJdFWRfDkBKFbpnV46-Dy1PKe1HdpoF82ggBjtwT6N3GZ4MPq1UVYQ6aiwlk-vUpetZHohzn1AD15XlDE_NfnZHhvGrHGApPPUFCMmZRmqQTkNH4IEpUDQM4_SacoAIdkrgHO7PoUAFoHYMpumQ2pow4VTR3mj0tpvG-iIBbcxvqc5XLQQZhXuhDVAEl3p8HPTDKqFgxTxiKT_Ns2pfkp7zHS9-Qp6VzlZgoa1Kt-ipc-BOpwBzzeDqg5bOYvDF4mySuTfNy7RnMfX2F0WZKN0j0Rbo99iNUgkvxQNTAsicaZGuGWaUbgiQI5OT_kltLhbL0Lwk4AQpgKHQ0OBgIYC7ONSWNWlHqRTR0CGRYRPPB5tOfzJ9iVeKQKgTnH-PTukqdsxJyrwalRgF9I_b3qBXCFeY7Ea1JyqYhi2c1OLLoI8UJ1kNsH9Jsuww0WjthK7U5KQEHkQTZSjdEyoD3M-daQhocYGcPqRLqt_kfDWpA9fQYJVlMCUL9aQuMdYVz0ZzZwV4PhAoqep2MwxErhdjEUPhqyt4mVopZW-Zyigqpw7ef5K8lrBvtfLV3rt0hFTzuxACp1wQOWVsYvY36I0Yff9iHGHaOArfsR0KgDgbNK7E7D5CtFrHyOn5XGjWcdjLaYKvCJ8wKrIItOXpWEMxBCcKsKsj3bo_jJKiKYS5hVeaznfwc7pi0J21-4BAkb9Vs4XqIcooEFbUlqFSxWMuBokQAsxBEdeZ4ZEWbD_jZdx8NxELKLxPuKiYYmaljKyW4NqhyeGPgFxeHV7PC8fZ5O1Zg2sTMkW7J_BkZte3oGa9zeENRYMYmVp90gURGZ9vex7-GM362BBH-Uq9w9XYGL_yVfylRVU2PGoCEmMoxqgxsYTt6t--noIEO67jMxWhOdX-i2bLo4xdZnTBBDiiCwDLBM4SS5FWv9Q1b5NO8GL9ePjw0PEowJy6Lhq1MEBrQSR_AiNr7tAQPoJc-ltUMtBCn0FrDKT8UZchBVaMPazNXHJyJB__MZfJLc36Pr3xI3YG7C7plb4MOzJ2UU7knbHbcGM8WqKykYOBlde91ywezS-WEo8EUTO9rVUTDPwSPH2NjnuFnu9cEAmXYicqip9J5WLcnWxKuo51O53VaSXa3KOwkRsh86PPoxbN_6boEBx2b78eQOgVrE8T52OD8SryaCcj7GmHsA-nLWXhAZ98WTCCR_O3N3JZSMDB8NNKaTdyjILTThzcZBAMHpCZteh3JxXO2kiw9Q53cCVt-PNAVFwgANiyFFW00sGKI1VxK2SqsCXupmVQqzwJ_VN_KyQfh56xgMWxEucdcbneMoOWUzDZduKIBBhM3BiiaidHeflnpuDid8poBugQVdxNZdxxi27cdV7h0ieu0WAJj5G4DjNY5XI-S3cilYnTXUNg3nE4kQb6jVsjVPKwS7sur3AvwPld2qHJD5Zo5_63axnH-FQuiA2oF7pZxoYiz4IYY94ydG8gOOYteoiwEDD4tDi9_p-Vh19qsJ8NyAaC3sO1mKZUhLpGX4W5vXI9bONL6KfiZtpGsNOS0al73DiqdLiFtAcp68geOr3ym7Miq2xtthT-mCiNOn4HugT-rogZbzPlRK3aHEY3MsLL2BBcPue8ffnazWOosLQuThIGdGwHxSHwk9crZito6H3rfhy5FQYRZELbjkp6XwSzWqwGNh5PvS3a4WxLOImjdS_SdeFFztTbz643sos675Aodwntlo8e97352Zl54dJVBWQQQXZe92VNcHdywcaHzSA2NyLRWz9kJA4R4jHUBq0Kd_y-f_4LZMgcnSJyB_kxotskTdJvy8K4VSB7NSgMxkfzv-DWokMaWuZ6i9lhG6laXjt8SzVmZnBXx2fcGgveBZ0cEEy_ZAjwSaqkircbn6rIcmwjOLxsSvcyHHaB4371u2OZzhoM1eRQ6I_wXHJP2FW4zESJYPOhSWtJ6Apz4rHoUnlDCcg1MnT3Q6PvRNDq0jB26NCCl4ixvXlWtuWTa6_bXBARoDauSXsf9YAX-vnSTK2lOz0pOWgz_QjQw0Lx7nEi4sMXdnGvQNxkSiGAmExZzqAPZwMGbdAJUnjc0jW7Fi28MG3G8cHvO6fcGMo-IHUlH1hr7vMVCViYqjcZQOJ6YgAQNQNe6mXCcsSJij3_AeMXOJvC55N2l9GkRBkByX7-NO0zWRMGZdtYxe-25RMM46v4AZi3A2mH-31HphZ34kIlBH9yb-8Vw4cdUHpY42kEhnXusSk0gx_bGxqJRVVpVgo0EAAAkhSRkWSqJiccp5iZ1yZ2EpHOgEM1vthLyCualal7K-fTHBm5jSjNqNNiZ85xJF3tbnHSjLNdQ-sYcUnhDFedPfS1bzfVZrJBfzjp9_itNRPeJnHhYGe-K9d5TQqjrBAtwrGnMkGhpegfK6Ac2Nklvcl-yCdX0Fx_OYe6peI4slr4S9XmZBj3ZpG7PX4NdyAKDu0GwufKIcSATJlFk-1L17vj-b54H5iFj5472wPjh-E9NJ2UWS5GbEC8TPpqw5wQH_Q4KnOIE03lgzCcImIKW4jK52uCSsBljKI5CXQzgTj2lR2lf7OqqEwyuFP6KEm4Gbd98fASaqrgFmR3CBqJfFkaIeuluglEt6hbkIQU4KlhVJ1kwkOq23gcjyxC4TXYEBNake_62MYh17xz5yxky34x6cl8B-e14KXqOG5qG5ug3gsoD334ICr72xkt-m3mICgkUYOSBE83pb2AA7YuW5IqwTLStyt03wQhYmDXd_q4FBM7ZO-uwue_cT49vvpDHBAL7zwG9if6P_wwVVqO85qFfri0-S37JXpakkJ6_9SUpM18Yo4g2SbEoFLE_psEgmhRAVyGZjGMCU2Yb2Nh6eQaVhuiciWgij3Hf69IJYKZ7dgNmCuuTMp_VlJ0_bDWGlAQZUvZoXemSxVUvOEMjNj0JxhAnuo6Pi9eWLcpy018a71RUAcCrdI6NLvPBNr6qYJgZL2YE6lLe5kN2xxuxtNIm0PdkyvAo9N0OGwXOkQcY8KxwwhBPI01FGQ1ULM51ICIEBERqQD5-RkIAICNR6o8zZD-6Iqah6mvg2OOhpEWzyTuIV6y3d_hOKpYtdPZ0tYpmGdXjl0CM6UZmUyAxk43Frunx0UQg3pA_Awwu5YhXCPek64_gbjQve8bn5Dxl6ZAvBAk85VngWQNtjH4JNk2GABmghnZr2ZHWhO_GX-q3KKTyOqbUjACY1il-tUhIs0TkcQqrYLRMXRrSACeDKw1VWm6iTI_6IYfcUGs_H1Y0fgyCSI3lq3495MNy-dbp-G5WiAQCZI_mqzoxTcr0EifYsDKQuzpSs4e6e4beFerRgJmLVr9Jgo9heM988Va39i0Vo0AEIPlaZqLXrAz--eT1xxSdBi6JlxKS2uzYsl800ySl66rIKPUoXdkVni_F_20mmkwEGCAQ4ZJS1g52aDOSjCYPuP4nUfCCL1868DyocogHBIwr7PCQ4-_0e7rKflnzCoPtETbNRKJj55oRaiAlFdqaTWWSMp_LjH7w0GFXxzTtnuur3GA3QaeaCO9bIPf-kiFhBArunZ4iY6SdxqV2bu3ANgoc35zfPy7r4wZDnS2BfHFn6KXRHhns5yN5U-OVjT2pIBWbLxQj8J8TOrSGYkpcTwJ526XWPKA03qIn2pOEe4wUDkW0tkxyyIgt5cCjSPWhhQQLsYYKJ8rk2ojWvIHSdHSgIof0eVI51RGCW4jcg2pJ3I25sFIfpgqI5QipxB75eTIB32XCBtzWmK2E6dPAQfnHNPYITbjLmOrH2f6zbW1_LJ3LVtMMijseSomNhA0v4KUEBy5aOriMgwBRc2doCITBcWz0OD6TCXbcrNvW7g6BDK67Ym4Vpn6bl3B4tIH19TNQB4YhX4z2kAyhlOOlvwqMcfhtdiNxuSZ7BAqQYixn5dDpswpCqiI_MjH51TMikt-YBBCHTr-RGRIXaWxk2sTl01agDUdyWGJ8wsP1f0ndpLm3fHdejNab0MOn6osZGpP3ZgZIYoX0o7CoF_5lVDdc08Dt7L_yEmzk4ccF-JQ0JtbfYdzvc4OrUBm3zQfNVsdw_AQHE0H8y3wolZFgsPzAOF39j-_9SDKkZQAHkO42MKEBuDYNRANGd41ztyybua00Dn8XEYC7OiWofp6CNgeFts0oXhYM7YU-0A8h4n_xVYrk-0Rb-zpprX3pmPsLySXIDR0EBHRdi54BjFeutO1ODlZUI0JXKinpc3TEq1Q8Umhk5Yid-CmzYfaVtt65hsdKIybzDgZkBSqOZHNlU-qgtHZsZjB7HhlsQH_hsJMfO_GDYmvUyL61zZ_6i-kzVl9kQzarBALNWbFaReiu2SG9cY4n8raKYyXQxQXE31wFUrKaibEAXJlq26xQzmZmf12t4-3ZVxMi15PRbREWLYGzqNRARqU3mHd3_FPTeaLxcWy-KfufvSTVOIYkKoAXAbHfGckSZgQMlCPqKvao0Lss7N3bdcI04kJRmOcExYhAXvepyznGreKpfwWLm2YpoPgFuWq2cbkOg_KNOxeI-SCe8WL5geA7u7S-PPZZ89jarsvO7kPAIQXxHg7a46y9wzDLclZD7UcECTva6MEKRlMP5zsg4EfRkmZ8AQcykymQikio50dvSITkyqtD5XLkLYv2eypab6-1CHu3z-YUQSHYLOw4fsU6dR8lToK4I4pl9auL2j4z2FqwZTt-wnGkTXTevikprpz7BBaY78BYmJHquSGjIEoy59aBoFNWsKLhyB7r-JFAVRXgZAspE59-JmzJVSIfyNWXThYFzabEXW2VmUNRAcb2pRUP7KYWY8xqgZTvQZ2mtXQBY4GpAoXR6jgH-fmWg988kAQBxRnDoZgb0VqOUNQK29C5BIEt8CsHE97YSouTsqqGtATh9YQUinkIpjyHMAYRfnkMiywoFYeaJdEd4DFPIvJ_MmDWtg43nh4dbJahewqSfAzmFH1B-js9WAG7bivifCkEFdHfWcyDybAKICp2iZ4clqNYH9EoSgYJuDnUoyHrBvhWbaG4CZFi6bALdp68fj_7D6MCId76bo2D47SRj-q6bzrQFHvrbfK86EdM5KbJftG9ieNvuE7PjAEAheezl1fxBBKKZDCnxPzovqnmBX3mnEy_giFlxpBfUm7g0ot-FrszjXCMAcw4PNQchogsmtV8zQ8XZOo2Rlay3YmS9-nK2Z1jEBXckY8C8y2IavccKdbWAOUidl9LsHe0wLA0tC0YcAQH5HF1yfqhXeaUXmVA1tF7vJW6tBMsm443zWLqD3MvCjC6DoUb1O6IMaeSwvS7spYGuleZPr4OvXuWcylIBgHS8TlIwoo4P1zBFAlYOYCGsulS8TBKmLxOWskPS-grktYEBBK-uDxU9pVaKCMWy_l_LV8-r3z2HRajh54V3cEsSiG5CF5_EVeFJzAzQTGd79k-AjLERnGw7kNMs4LWMhPS-00_R3nRt_OPxiVnSY_vNyT3HHpf8Lf7NQnZQQ7jM6d3BBSmIUlvlECPBpaVgP6oc1FKSkSPs-6DGL-DkJW3Xo0WlcJKwl7rIXjCrM0t6n3ioRNkxBOg3grZKqF12fnWOn-jtqr0V0Iw4Lf-3Gh007OcyCIy1-RENp6DXM8JKsg1XwQTo7OfDfyf3ZSDWOLan4L6hrHPXKBKtk0m1fJvJQ9dwEM3jzPWJBilBQDI_09Nr2MCbLzNTGi2wzGMlMt4B8u7g6B5wmRWKDZchS0pSFgP8B6maEEZ8JH-c6p7wk6YfeMEC2Ih-KN9IEUvnsh-b6jj0FwcqtpWKlHBJFWJtGnXMT8rDuYX5Mm_-lAWornFLriTA8I9uu1ZOGiej0pWVgoQVWFawXYkYuoZRW5q4OGBwpiPtZIYAyDoZeAUOu7FAqrTBA2NfYfJr9vsXJOaDiYPDHRgf9IPb4xQHM0YSgpvkCDTERAkFVgQ0lLemlf2qcUXjgmQg2MNuI1NcMCu9A9o8-g15M6Sswsu2uLf8PD13MAUsf2bSudfdKaViZvkMCJ-VgQKsy2y-9J6nybC5tzJ9S3yfnlqMyHkbrxFAUf7NnocSzZcRtuRUpuGZsx20gb8xHIA7aUuwd41zsDvsOUpovILruvtFXnA2_18wbHXFKUGmKPHYYGLsz3rhJNtjs0dZF8EDD2XVmxsow3EHn4CXSQkJ8x3D5sDdyQE74fx_9l-BybhGK0-Ww_qLjHwwArVN6GcDacya-onH823CihgmmZKN3bg_XP0Q1c37IUApEO-R6ywQpAOWGv_re4uecj_1jmbBAxwRcvCNpNSwoGTm8_KSozpV6-vadvp_RC3TDHkH7f97yLxJ7ROIt5J8cQl-9eNJBHtVvWv0H0oe8V42gg4FsXB7_Fv8Ou9YUFWaJYb7FVU3IyWGVNYJyPoT662ImG2kQQHTzoNdHPdqTT_kh421XyfaJINAHA3KzKTcOq_4uNp3hq158xepsHM8HLizQKPI_oM3qvpSMxj-BuMVfkDGTnsX-JLAe3NA8yuFiZXyziuYw6hC4rMLuV5UTNJZnGS-3EEGSXXHCfghBQslnMt4jDj1X9FYwL8cJCmPPC9sEgpCfBdPYZCJUjoxwd2i4Nd2vweECi1KOOoFCdmTcDcp6WmlQxv06XLgfCiyC50yBmqw034Ukq2IsrYFPDsITQIQG_HBAe6k-2dxanLxJGlZK6CPCx2MKGElRlIESSqa99pCuUgzdvs-_ZbG-fjr42LTHtP0hHJy_ngCjrt8IgDmUKI3xEvlXZRnxnp4jkH-7FwZoKkh01DjFYkAscw5BjAlcWFqgQFnqle20OyaUTMaYIvjf-0ZUOpGi_wab0RYW1i5s61xvKyIk_2evZ87LyS57WccbcLy88MJ26kRxPMf9rOcEetd1aZxykk73d7A_pj7zxIrvjeExHyxUrM0XFgLN79kvoEAhyhFdZ_FZItdc98yLjaToxZPORBhTn1w0nj4spz5FjshbItFfVLfGCsAxgxRI88AO2oB8389PNPMe8tA4uMPMC2PFTqK795Hek8Vos_khmzeiXwo1BQaVfwLglOeKhUBAuoVvCyh93vTjhapy14oMAt24rP1eeHnQjee5Lfb_8p3gXOMQ39yxQ0Ts32B-CfxQzbPQrRQtJls8Y6lVDr0oOFz1gMHDWRrzA5z3tqHpj0Cxe3R1luIIQ06DHrv73dswQFCY6mYUsMfumIz3WAO0sa7s8fzbGRpG4zcA5_zxQpkwOEmTbBf8n_7vCRaS3weOMVJBuNSJCiQGBHR2eESoSSbV_ESxcoPGf-Wz_Fam4chWBty66ZX9gMqaAE1zWKAGMEF9zlemaUpKjF_NQJkTSbvh94a6Rtr-WR9QhWFzNxPBPIxItxGb5yNTiGZ6Ie-tQJE2Kyd1SmcfUY5fJnCdItfpnyXL4WSAbSsob9XVg4Op0uBGG4yXL__kme-X8WI0wABAACDV6iueeDk3PptXUV0BSR3PCdB9sa2FWGoPt81rhXS1voD5ApICH0CYlLLFnsnBNNi0fB0f7ZKC8y4286yDEl0NhkKDvq2n9HkwBGA_oiFOcGotvk5QXufiP82pBzLwQOow95Fx6OM7HK_uPVjzxxdawXQgSdHoQiMJwbUK2UYbfr0iYvGr8ERELWRTOOiBcZYsSsNhYHMvwVW5ahDFqpCiW8JJOq6gjlJmZ3cvwVWD7kgLmJXMnnRqtqaYl9Uk0EBEw6CZI8R0Fprd4sn-AM5SIgL6PkVm0AsR9FkBxFO5F6x3-DMWIZnbpEFcOjgpkwAtbmPtesiKe7w_XeKXSYKPfzCM5wyVZ7sq4BZaQSMzOEOgpFp7_W4kjVZuWL4HvPBA0eaJkqCCnO9CvTPynRPisSgqY5zcysrcKLAAHSQ247c1yi8smlgYsFznlptT_2rAD8h2xfxUSv9KDaokZ9LROVtS1pGJumZfwAKuHqEis6B5GAG1uZw8SgmRDB5-_dcAQWOP6jgn5PBB08RKA4xGMxzHTTF0iQgF1HMX4ScdvPmR2tC1g2_z9NYw5VvHewjIQTVUgKhl6WkLiggz4qCItjEQ-sQaFctZo2QgTphAAhAPbVVKGmXydWSPn9-MLyRxMEFd_MFPx0xEKWUtWopZnXoAnB6cuRUlaR7Ex1bd9kSJeRT-zS9vg6SmVVeqqF10HbBydZAp2CPsaAXMzrohNXkjT1tHa5DFsGCWN8Pl96gZ4XU0hcy0-v_g66wmMXmP7XBBUEh8wlJ2tg5_32LC9uz3mUecfSbUnNnM7jzPEBx0MWh0T5W4oXWkjl0JtkiRFaawUveTNuckzEnkGqxWKC3Pfi-4_c19f14CGUzZTVXhAWYKQD15Ldl65r6xU7U87dFAQUOHcEY6KUiQ-xEZztcLU_KDfunv1hTy9IE73SiYpIvhvSeus46KY7z9D_G1Hw7nQFhHgxspVLEjejdXY5Pms0wE_YhQ-bkrCOPXpnJxE194xSi57ykPsPH5TBygVP_fwEFAdqOPwiKKQ4MV-d2G2-omn1DCyqoL0Vc-bvCee7FYytR_RFO2_xikbrBZwnj_buFvANP_K1TtKf04nY7mjKJiSbrTdpywo8PvxNB2JpBD9gkVPuA2oMFvUFHHownN0jBA9yWmiKpQTY_ZqT2TR2bmCTmwL3sZEdPVl0oaBlPiFZbDTLGgF-4fBlm_xZl1OiAhj4KxXwB7w_DqvCS0V34A0o-Su4VjZzaEqO3cTuPCBuJRfnExkN0QMMtx-OMPaumAQSyZ7-x27l3q_-q2ABDt7hOImYxGar-1FLvfxxmv_aAUPWCKHHyEk-TpdjgaLYs3EWC2FD-DNMegViiW_kEhe5hNwBo_JVCn82HCUH14yb3mZwFNe2vAp5WvSVoSdkBCgEELEZw33U_IZSQ5fm0BtguhMiFPbE86oWsZYU3cs3LiC3hW-hEBIIiqIh3zxWg7Z8AcaoK_0hQeGI2DANl22GKyVTRdHgB6Vv2Ggz-KqB3NYkLJ3AirxooP_x_mqVVoIj"}}],"authentication":["#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA"],"assertionMethod":["#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA"],"keyAgreement":["#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA"],"capabilityInvocation":["#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA"],"capabilityDelegation":["#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA"],"service":[{"id":"#TrustchainID","type":"Identity","serviceEndpoint":"https://identity.foundation/ion/trustchain-root-plus-2"},{"id":"#RSSPublicKey","type":"IPFSKey","serviceEndpoint":"QmdPZgcyqHJTiPeGMcAu2AAkZZ1U4KtdQXid1gdJQtpvyU"}]},"didDocumentMetadata":{"method":{"updateCommitment":"EiB8B_LS_O3NWo2P8fSuRwS32GODaXoLREZHdqpg6x86yA","published":true,"recoveryCommitment":"EiCy4pW16uB7H-ijA6V6jO6ddWfGCwqNcDSJpdv_USzoRA"},"canonicalId":"did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q","proof":{"type":"JsonWebSignature2020","proofValue":"eyJhbGciOiJFUzI1NksifQ.IkVpQV91YUV2QjctR0FyRTlkeERuMk1rclRUa0t0VXN4eGJPc1NESzhwQjl0ZWci.X94wTgzsovLEAXU1CG5M0Gqs6Gu9oHklr4Zn7aEbrdtOI_WCSCrWJuYomkcdeF8X5dV_ApZ6Gh08pPcV2VSClQ","id":"did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A"}}}"##; pub(crate) const TEST_ROOT_PLUS_2_CHAIN: &str = r##"{"didChain":[{"@context":"https://w3id.org/did-resolution/v1","didDocument":{"@context":["https://www.w3.org/ns/did/v1",{"@base":"did:ion:test:EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg"}],"id":"did:ion:test:EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg","verificationMethod":[{"id":"#9CMTR3dvGvwm6KOyaXEEIOK8EOTtek-n7BV9SVBr2Es","type":"JsonWebSignature2020","controller":"did:ion:test:EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg","publicKeyJwk":{"kty":"EC","crv":"secp256k1","x":"7ReQHHysGxbyuKEQmspQOjL7oQUqDTldTHuc9V3-yso","y":"kWvmS7ZOvDUhF8syO08PBzEpEk3BZMuukkvEJOKSjqE"}}],"authentication":["#9CMTR3dvGvwm6KOyaXEEIOK8EOTtek-n7BV9SVBr2Es"],"assertionMethod":["#9CMTR3dvGvwm6KOyaXEEIOK8EOTtek-n7BV9SVBr2Es"],"keyAgreement":["#9CMTR3dvGvwm6KOyaXEEIOK8EOTtek-n7BV9SVBr2Es"],"capabilityInvocation":["#9CMTR3dvGvwm6KOyaXEEIOK8EOTtek-n7BV9SVBr2Es"],"capabilityDelegation":["#9CMTR3dvGvwm6KOyaXEEIOK8EOTtek-n7BV9SVBr2Es"],"service":[{"id":"#TrustchainID","type":"Identity","serviceEndpoint":"https://identity.foundation/ion/trustchain-root"}]},"didDocumentMetadata":{"method":{"published":true,"updateCommitment":"EiDVRETvZD9iSUnou-HUAz5Ymk_F3tpyzg7FG1jdRG-ZRg","recoveryCommitment":"EiCymv17OGBAs7eLmm4BIXDCQBVhdOUAX5QdpIrN4SDE5w"},"canonicalId":"did:ion:test:EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg"}},{"@context":"https://w3id.org/did-resolution/v1","didDocument":{"@context":["https://www.w3.org/ns/did/v1",{"@base":"did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A"}],"id":"did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A","controller":"did:ion:test:EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg","verificationMethod":[{"id":"#kjqrr3CTkmlzJZVo0uukxNs8vrK5OEsk_OcoBO4SeMQ","type":"JsonWebSignature2020","controller":"did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A","publicKeyJwk":{"kty":"EC","crv":"secp256k1","x":"aApKobPO8H8wOv-oGT8K3Na-8l-B1AE3uBZrWGT6FJU","y":"dspEqltAtlTKJ7cVRP_gMMknyDPqUw-JHlpwS2mFuh0"}}],"authentication":["#kjqrr3CTkmlzJZVo0uukxNs8vrK5OEsk_OcoBO4SeMQ"],"assertionMethod":["#kjqrr3CTkmlzJZVo0uukxNs8vrK5OEsk_OcoBO4SeMQ"],"keyAgreement":["#kjqrr3CTkmlzJZVo0uukxNs8vrK5OEsk_OcoBO4SeMQ"],"capabilityInvocation":["#kjqrr3CTkmlzJZVo0uukxNs8vrK5OEsk_OcoBO4SeMQ"],"capabilityDelegation":["#kjqrr3CTkmlzJZVo0uukxNs8vrK5OEsk_OcoBO4SeMQ"],"service":[{"id":"#TrustchainID","type":"Identity","serviceEndpoint":"https://identity.foundation/ion/trustchain-root-plus-1"},{"id":"#TrustchainAttestation","type":"AttestationEndpoint","serviceEndpoint":"http://localhost:8081"}]},"didDocumentMetadata":{"proof":{"proofValue":"eyJhbGciOiJFUzI1NksifQ.IkVpRC1tZHk5UWhoR3Nzd1lNbG9FeHR0cXFNVHlEajhUbjdRT3RpTVItalc2MWci.LutefXAigkrHZSfNkz7JQadsyTAmLGU9KeT1LDtUfs4jslp_5xfz_Y153fUTs3WiQgPLUdvuXHFjQ3INP-OfbQ","id":"did:ion:test:EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg","type":"JsonWebSignature2020"},"canonicalId":"did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A","method":{"published":true,"updateCommitment":"EiBCBZ5TkPXA7i0X_bgcY2AR3Q1mOYOdpG7AREos6GxZqA","recoveryCommitment":"EiClOaWycGv1m-QejUjB0L18G6DVFVeTQCZCuTRrmzCBQg"}}},{"@context":"https://w3id.org/did-resolution/v1","didDocument":{"@context":["https://www.w3.org/ns/did/v1",{"@base":"did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q"}],"id":"did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q","controller":"did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A","verificationMethod":[{"id":"#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","type":"JsonWebSignature2020","controller":"did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q","publicKeyJwk":{"kty":"EC","crv":"secp256k1","x":"0nnR-pz2EZGfb7E1qfuHhnDR824HhBioxz4E-EBMnM4","y":"rWqDVJ3h16RT1N-Us7H7xRxvbC0UlMMQQgxmXOXd4bY"}},{"id":"#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA","type":"JsonWebSignature2020","controller":"did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q","publicKeyJwk":{"kty":"OKP","crv":"RSSKey2023","x":"EyGvw3AkcUf2TZToBh6pddeaaocmvTuLCSLun_yYJpL7x0W3gVEzeKlj06J5Sej9Duk0W_yGhbOKCahOx16LszwTHVgnH9FjRk0nwOer4yKaKnjTZ2FlZsYI0OI__jhCGP9cbcOEd-1rfvUFu-ghsj6oHfSXDBm0Ekplkgs1IktoicuMsF-bD7I6tZRpP9tqFGqARUqvR2daQN-scwYUNsv5ap3XakBCDvOCBc_rPAwzapY_nuC3L6x60UGBAPtUBANdaMhAU0gxd-3JMjcSjFgwzAhw5Eorr7bIp1_od6OfBRYu3sIkij5Es6RDBLghUAx2Z3dznniJRh5Xlx_8zn4SYw_xhV1X04vY5U4O7-7veKMqKxzzoGOR7O137gSTtBk66ISXfE0k6LLsZK0Qkzi0B6YQ0Xo86d-COFNhRWQ_Lq3SCSiOaJ4lFP5_RVlHzgUXm6XY1X0jrkVPWdT42VxGjFvy_KX9f50dOkdPJTax8bGv1nEpDm-55UN8nrIzsRODaxMBooRL1y4OxyW1tpHaEdsoHvsZrLzM5g7FB2ah-62TCGkPcG3Yx84MPp50eRPIlj2omMFxMpnAZKBSRMGtk35A6xAZUI6KTYGfNI-IuWKdk0UOn6xL8W3EwMTxRgx1v7iklbgxKuCBoOeAK7FhoOVzL5YnUCHb1NUwAxDs9I5pNmrvaXsDDLKLIoz50hRAdnK92whifFoWoJOOJbQTb9sx43zmB1J7G_T28MG6UetI4dZljoNfWpXePl3vNwW979nNg7GU3N_V8ZE_slRmUv-rAw9jD0w9KXVCuZuwGIKoJ2Co8qjZxnhZUtmi3wFJin73V5BC684ebh40fnA9z-H1Kwa3ItX_mQSVYeMV-_1fydNULsdhlEnpwI5XNQ25LGqMNb4v-YRBXLSmN5CituV9rPXg5ZzQvy8VVE9qxWnicCxz2TzFrxFOOIhNTxf-YQT5Re5HJAvdy7Y9szo-i_PgskFdVm4UxMgH9ddrFUhDPNmVtVY8PoXlMzuU6gKR-1np9J6FBttHOIPu7LFFdO0Vd_Y3-Dl5mdBXFcP1Do1GN7ojcuRUB4rmB__upRAQQsqCApGurtGP1zgtMQm6ozF0gt_JpoXgvZEFK5kkm92vpedrSfDPBBn5NPIgmQgKSYfvmWRmADyr2J9bc6EjJr1-YD7QR1r2g_eGRBE1S6dexWceWTq-RktXQYOSJBnKLSkbqJniuoA70BMkjU4Jsj1EJB7oxE41RRMchA4BRlClSi31ga0T_bk31rNTLQNLGSrBrh0x2nlG8IZUZLB4fIKKweFD9pL1qhLMM-SQl3YR4-v2wxjlMXTrEDjz2xdwJsQhhzM5trtqhVdxfgBwB_ZBtU9KJqYvkB_3BhY3kYQSGDLhyCHbjyIVYl7saQGkTz_owGfj8tD3gU9oJlZHDyjf4p9AObfF4YXKjVBpPrPgwgNd-G4LAgUOn4DAVwGmGBjQaNWiLet4g4lRsLS3LkM1az1w_KyYCX_k9bptp4qLgwV6HqbLx1V5WkmubxLMpHlbV0tZFLzwThEaKpqNyz7M5qIyDvaSbTFtQ9feXhRHU7VN1MgH2AQmQzHiygXHs5qafdGSsKoMm6c_6R2-NXl3asM1TSUmD82yKonGYhSHHy60KvB4M2rVTKRENxR93u7gaYr_4cqFY9LlcqGUMzxmm6TadfSHz3rSj53C8c3Z3U9x9ftbKGOZeybdWhYbRGyES_HzmlXV5MFY5qHiE6INi_ao7Xxm8VRi5rdaHlVDWfBb8gJENbUHDDcsKQfae-4j_vXmvq4s_9L5It5kVLCT9f5NEf7jsxSP3mg9hqgwdY96ob73GsHO3HRoQARhPUt-2o7i1JzScqRH38AeDr9XnxC2Qu4LT6ffOmMKzA3qngyxKmkvyKmIl3_eEhDxpdTSf2ba6EGOD2GuzvGv2a_P9QHw52mvtEoCLNJAslzsxwxbLSnLIOkbJca1Ew26womAjSgnNwUvPCkz4lmSNTbyF63wvmNJJeD0UgkBTb2MxDw_39ukWvH0mOSJegpmENWzMhvKvxxMgB5Y1VY6Hq06V9mcg4iD0AdI-dM646yU8iLfMAAkB-EvwUUMXRE3KGU9Kx6dqhsSCrow4QDpzk0B4FCATLwawfGc1_rxQyumhF9nagl8jP1ITcLi-hlUyrOsKfSK_s3WKTw4j9iBoBWCzHrX1YC_2UTnq5XIdbY9tT4NajRzqwKLV3aYWRnqXLg_-l5k0H2GmwmRnm4ZqU-9YuAy8MQR5CM93H1gxE7oL_IWIyH_tCXrVH4hRhjd7GrWcA90s1AFpCHhBZs72ORxG_Rh8VcJpB5cTpbQfk1ESme0-UTXoSnuLPfNIQb6I6fwFkIvBx9YL7gxaVmjHMgk9BLR89iwuo3VsEsAs4ktbFfZ70l821y6q_xmOBPF-BxJzlVuHMq9hfyYVA-1ka8tBBeEy8NJ1PlYBMiVjHoKWMfqDKo0ONNv1Il_ThirUq-MM4pc0ENOqwCYkomNBFfFHdbS8L1Y5yIruufFxRbRPt6xC1TnDtq3K7JCpRjsTqv_1_u81WA4UIlW49NaruM-2lPlL6P7rWtBqG4axy6U9WYqom7aXBW0cbg31hY39xZb49G_SfSYewGr_pelurFdTag1R3ZL5VuDTggqErrppxKIBYHQP7M_reJ8fQf4JcXOmMkUOap1K7QJvvENxlQ_RQRj10d-t9spgDv5gki7uMDSA3fp4q4gf3HxZhYwPaImQ9J44zCCLUdo5dyhHsyd9neEeBniNZk5LDZRfX66ERlj49CO2dHmHLe-YQACZnMQDDug7LF0il3QHinPD-nedAAxpjfUus9Ay9vRx6nB3fHr-_9C76qx_NjCehMZHlsAOgZGU-yjdwY2uu8lvnb8dvmCbkIBYn4S_aWJ0qIOEjfWuADwWO9BXI5uzQZ0EhKuhALABMhOIi4pmnHqCE0Durvn9RaPiFz6ZKFhW2d85ZAkks_-ARI0phaKzggmB4E6k5EV3cLqkI63Oiiq21QY0VCvc0LuNoAVYzG8s4bx3udSSORrRJm2fOdURg3wtPlFq21m_7y8D09xKpHkXgEbuDJV3hWk52u0Rxv1MTY2V2_LkHIDF6my-MZLQQh0dQYnUjDfvQ3bTqj6UE4MZ07R6UZzl3Vjw53lM2x4gI17Trma17Ag6Yg6XiQA7QqgXKWy3jG6AuBLjuYRPeYo18lJm00D1D_Z_C--D6zMJKr5ohYrTi4ea_dh3CI82xBNwjeTAd95r6X0wzC3xodd7FSWJMCgt0MF6pz-MEL_jNi6sK9mIn05U4icLZLjBwl2lObaoiYxpyWEpnuMGy8J7dM1Z_aRpYt3J-Zw7i3Yf4JI2JV9u1Mo-ywQyXgRcRBhK3emrFT2fxH8SqkKwJCWn7frvbukOzSQiKD8RFuXA-SWK60mJ3erCRnka-xkGg3AiBxxeE8Prk8EGzLcB1UDRGQ_x1PXmMNtdBK65dtv1b0jGTM_uSHFndWXOrFALwi66JGyIca2WnCfQRQDR5EPyD2d2Naecbj_jMwFUsbYCxGTc76n46c1pI_QH1rxDBQ7j1Tj_rcQz6Bk7DMTNnlTFhJn2h7yVnoRPenlNCWZWZPRpr4vnvS6Ii30os5W2QaGHI_TqhhaXRFU8Z7K4PUUUVEv6u3KIZpvcuVxAbcx-ppLVkj-r2vM061Nx9aXEBFd2whV1Tw2rjf-6fm10N7U3ssLGC6sfHRpSVcsENk-ZjuYH7sY-zmN7Hf8zOYHIAZDUr1rjCgG2yCujbdOPFtPs4QKC_cFSzbpOjRmJ-urzi7duH_vH3_TBhMzM4jowgM70l1LoB9sjQ68wzlaAs74T04IroWMULoZOdaeIS54ugR79EhgqvukrIDLEoCekAY7jAs-iNW14YRPrtdul8zVUjLd4I_X3efx-IX7HvR4RUp-6lqMSN46IfvlScl0qBY_SBgCpdEw66SRo1OAIAuTy7VWX_mbvLtgZPPMkaVheFwYwBZnBLKQKyJHrNrKRQ5GdrSnJP89jdh-o6VEqG_whEec3cB1LwXipXb6v1vi-7jxU4kpU_BTMtEChb21tRhmfKGiQxHbOTRJbHVoQJ4NFlS14bTYAEuJm6yXnIW-GOVCLvlHShp5jeWc_9vvvBZnk4C7bDxY80GxadNmsKy_-AcEFN_QI9pt6lckDeTOQxgVz6Anz58RIkvJ1oPL8A5FZOl4iYuQGDAqTP6Yo-SdHbuVOuV3aM9K3L6RMgj5Z9z517O3oqsmthQdy5xtxhalD2bjV4fNsQrsXIGuNa4nAnFtfsi0uN4ahR1_YYVuQgfEQLOGSzJnw-bQ7m8tOxlDOP4MsXg6BFSBvo0LPwieTdNbZR_N4FueA59bt73HfANTd-xz6ycnZNRNO9DbxBRwXJnQogguwZQdLLLuZjqoglKwi3gmMHvCR-3QngZYQw46vAkTUuYfdG0OgaYuAAqtsEvJRaBVSud7q6pgMqM5UbG9eWv20h-bMQeBEpIuVG08HOEc9TeUzDOoE87PzBkfBqVu_s1tyItQQ-DqSvfCQBobT1pYeVsuyJSGXuaF5MXooxYfRpsAuysjWDKDNxAarmMCpioPCo5ebD0elYa6S1KV52RN15vaAZLPqNRiFkek3oy_M8C9Fi2nLzXG1Bjn_JlKzni0I3pofwFNE2ZJnoLSVpLwVLQUzzCB5GoS5P5C1DcPDxpjAr7e8pWb0QAyyIuz1EvSssczBargovo8iNxthV_MgoN4UGY3RtkDRyw2DPcFdji7AYXw_q3xlxXsWEZMfjTlkG0FfwSTHbhrL-BIXXw1u88y-w5SvjBBwk2wW0SjPVgm-qq8yonWXhnVfu4xRLMY7qNRltkzyB5pQ44rJ0iFr6tXtKus3rUTx2PbQOPNCYJynCWQnA8anAlOiTmIJV8G-MYkP3hH3g-VZSnWE8gQhbvXy9OY4YtyqX96TXRGuHNuZBDEHiPmNAvKkfgVdGE1xrxPnfZ5eN2RQWXAf5a8xgISY1bXxlt1prbFSiHTMLnikDpYNy95JBQnPEqdIYRhgzh29L_RQpIM2ItE6rPrJCl-NL0Mo3YZNdFepgL-5uOjFilpmO_EfAc06pm5sP-g6S3vOx8I9j4JrOnhygXvZx4Mr2D8-R_7s2F5QOYKCpcYmhKSqaPbdAX-q6oNQQ3fesRtmDJIVbBmioMmu5k3C8hh_L2RNAe6ItXT7XVCo-QFQ8fiUIOMWASrYHiy8qsbX4kKQJ98v070GnqCMpKVtB9522SHxJWv4h6Kpsmadh9WjAmzItl4tRV763mNcLeidWzlJFUcfZIVm9OrWbHinBUjKFnoeexpecTm2ncrzpUkMmJghWKv9hUzk6wGkQhsps-94GvQJT2ou4T5xLpeATQ3oenwez9tEwxQ07tB7FHEiIBpA4PFExNwdv8sxaEe2Zaoakh1iEjIbd4uBcEAd_E8eE3VSEPvB2_zT8nek2I9pcHEIHA52Q2_j979f-vAyJci99RN1Va8nvk3TyMz_g6OCknUZcqkhXK3lqigvhkUBl-IxjWqagdTwPfwGPtwV3JT71CZDfBWujVMLPGB_gT_dhsWlIN-sC_yiWL_thQrkgKFPqXPwQKCyz8r_iv4f8NnJIh3W6_hUURFsnu0NpVAlhi7iOU-B0cqk1NHN9BgNbT_zU2aVBEFBrlQetG5pyxxgyDSvrz-igEzZ9oqa7-EIgNv8P-0T0IUrlCIQSfPsiAUsbExwg5JwdgdQ_gD9HUt4U2Npk03XtaAySY1IXJCXeJLp0OIcc8hFeaiPMMv7Caif9RsIxjwnikwLFGtpNy70Ed6CkTMtxBR4uShDzbSz7Hk90gu5-jV5WGysOA9AbW24iqgfgCKjrjgfrod_MNG939PdD9KOV0x3MqbZJmBLB7jKCINC2ilgH3Ez4crHFZJEkuJ_Qq-KDXW7l7hjHUG_debtAu6qI1edYP09UkgmQtnZgLcGAWUhDxWhdf4XYOHfqXxfhiVu8tF-ly7iqWkmRCqhRGV5NmzUWuwvQ8-Jlh4kRa7nhpwb7ivyXiDubq85_tKuha0qKFzzz8gFuiefICHX_Uy3xM8m6Gy3KfYirumMAkuB5-IY7Dgr6IZK8YXGLZb3QEXmOjuwp8Rmm-bMnCXehgCJZplNtcWi7eQxsP4y0IoEUsmmC5Y1as1sAs8-R9XlxBfP3hdGWbOupZfS6FmMRiGD9HoWesUSVtRs_tgOUPPVav2HRIK2CLYBRwgI1NaeRcpnO8cOye4UgRm_UF36pi3hJPfIdCnhxGeOH5J0r9zYEnTDs18YsIQedQOJ9jvGBLvDi8dJ3NRzof0hk9riVtSPV7H2EKhkEL67E5pccehsmZnha0ewYbZdgEstjzjwQ6qkZRmFLOBdP11yCDzgs3eDmnk0Ztewl22-WhhpumCfNgux5OEtcSu6hcC_gtsXQgTm4QV09fFZJAH8tyfFildcaycx0w6zG_tT47jBYIwVyEI-Mvv08qYw3ZN6558VgacYehFWake3ahdjDxZ8bO_tBtLMrFXmjRpibEIYbWZW2OPgBv-4-Z_EPXtLrDpJxYjD8bUxNgxwyqxAlyqZe0FUQVo1RTWV9hzvj4GcOG7wC-_t9aEEv5h9hg3sQXBxwKwIulPSsJlAeW3dygypohfIMKiUdjDERwhgvPsvB_vsJIaVpN3SJVfNWvMEFAIRxl0o0b4upYbISICcxav7YjxARlPcV_nqG6Lnj9-6MtHOzvmwMWpcM0Y_FFro9TqKAj8TkAiGaEMYyJ8Z5EMAsGd32HwMhmdeJbA9TxNpC8CIpeNlU0H9JeSDR3bl76oGAPDIc7bDmfKjcCL_8rZamAaZucmCI4Fkkjaqyl_k0TOHrxrc8EcYzbICfu2Xp9j5Bl_w7GErvNIbMsbJejezsJxt6CR71oex_OaL_DyxGJE6bOaWZFwF3WqhVWMoMEuRwy4Z11DIsqZ2pbxyArURVFG3mIHnBJ7ffjxYbofuuuw9Ce3S0W9AwEvXRlquPr3-wLesE-Y09JL2x63dPrsfx88itwaKSyGuJyvqpTu8NwpAR8d0bU6nXG38O2ysH6-xwvDGoeApjhGaTD71tv5hYcJj1X2M-GeWFi74NjG-PYBkamWVPk8v2uimVuB402YMgUAe5RtZcKVUfHczIcj7IWreTJr8JCLl4N_X48ji2KDuBuuaBRBUYdjkl8ltWE-AQzatqUi3DF2ZDEjEarQrk8K6QDaHNbMAEQwqxIcKVB7rX6pwR4EA2xN2VYmCskYAReAbKYyzbFKgx-_kbylwjO1CMcDTdhKYHnfEznxeaxzjwopfWQR5JQ_y_4OExcY6gh_FHXXyMOQdyzdcNMPFOZDvKAf4PiXg6BV6VVbvlssgImhEbhyfKlwhmbHkrD90BVSZOfwp0m_zd_xOfwSYckSwo8ef1K6DILkCmiUSc9wiCBBGHF8ex_0u3nepPICWg30NqJPii7moRYlXNi2hKgTB2Cy1njuP9pNFSD-8cOxrrAoAz6SaxdS4QqxjykSaRko3FibccYcSE_fkx7_WWBSW_1GOKTqQltkzHWMqTbu3wEjBAbnQjYGEWn8aTNzsAh1pezmZurCOdi9uL-cjIVavKPn23HhHGfS88f3pRdohcdlszyc74acnD6VgT0VnArfeYPNBWcliVDnCE3qYSvter4l5Fe4rH1qDISEq2ni1-uxNRJx6Ck3-5bWSZxHAgvc_2gC2O5qc9TU-akXvNSqLmNtKmO2FGFtBltwgyLc8bVWAJrNxuWQVCUxXlfSkxaGXtN18lGJX-SvmRn5IsqfhUitHzJjEASiI_YOVY9OoGEkK1a532FFGdO00mS07BQCPV0w_gldLncCOgt8VPaB5d5SjOF0_whIcVAIY95y5MrZEJWcbES4zg_jdGb5SRLlr9PENPbne9VYK4_ju-MCFNo0uWibQJzJcpaKU2rZ9sAsT2goR_lu-aLGCdeimhRmual5ISX_tyMRikPCDidsweqUeRzPcriSIRDKLcQfzA3P9Lt_Mo0ql-l1EX7TcwLgCsISBJ39jyhHyPvNPbBAFAlrlF9uRhz_ATonpUwgZrQHSlpsy6Mzh-O8f57HKQTRT0VigvfIeC3J1TR4EzLkHUdC7QF4JNlprKFQl-HUh9VIOpwXfQ7VwhbxUw-MThAn8fnFAKqd8S-4S76Yn4Ns3B0FA0wlDWp9AvfCSlm50bQHUgj8FEtwz8279OoIhBEIMnA_rHNwA1gPMSAl8aU4RO4L9wTbhwVEs32i77O1pQS93ZeNwOwXXoquAAVFZwusOXz2C3jxzKzB6IdrA9LE7-ALHDvmxB-y9KUe-RgCfFgjh9EE7rdwftpCOMj30we1IOtQ1XyFSwpbIK-y6e6itkyx73nB8UicYQEQHDnl2UPtxm3TLUe5bx_E0sisng5ZV2ISypN4_CiyoAbUPCapdHnGLh5VJtaPPq0NGIVA88MkPxnJC_dTfsZKzNVDywA36U6dGzcSH16QoTfJ-ZcUJhHAKJHizKtLpdxpNKlSugnNW0P0XwgrRYAehBBqJAWrmDc2vll-f5KYy6AFEWfIub9SODwuu3j3yfdoVAjpi6Tvm_e_w18ZBYKjtRrAAg38eTrwQwdDDovzBO6t7xmJkqOxsCFl0tz0WB7YxhVMfhC6qv0ojnXM4XrhX482Ew0yMUB9Ql2_2d7u9-aM7VztBqRf9dtPj0Fc1WdfiMD1d72U2D5NukpfdO0k74QL4xFcEWgq0qAPT1Xd35HaQhe9KfUYx0d7KtbBb1BrpQ3zZWS_ThLtfTHOvGZRQH9bQQyFkx7r9Lnal_GmnKw_w-Y5ecOTXwxvtB_XQNOo2i02MTPLpYHXMCWCFB6kHee4fhJVL4yQnaac8WOYkNDZeHf7y15M6Ezs0ieyusNjY-nfeAuXS1kJ_lf-qI-1xCpx4wmOy-W4Y4Xbr5YWS8Pe17115uh3ZGN9n88HuWj_fzZ0BcrgsT4p5LvSm9lntyD3oQ8pX17phhk3xqItrnJYAq8MfnLgifMDl6XucGJj1rhsvVGfr_ccjSHxohBb0HWL6g16xEvKsXnQe-PHn8Djtpc9doxqWWC1QeFnjIFJ38TnZd2v6S9irKu2D-YTw_9TvgRZTHMLgHH7pdFo2P_-mrKP74-OvYkn0O4aUVAZ6-bCXKIZ4ZzFgt-aO6l6vyUUfhcVrQKcnRdrZ4_GYfiRdxlBL1rvcZAkVpH-iitAdQ4N0xFHFL3MO3MH_EepQXLXSgciWBbbc9lzJnd4GkCRT-uH1SKKtquXZIO28ERVLB5yD9xkl6-ch9qTYNnNcBDNSAJQeFBwCHB5xZoyuYfN9p5v40vfSDAoJU9A_3_kaYMyUBVaxQWnKjZrrA5hWy2fjRUnVpeX7PDyAyb6eZDt7dKlkWGQxvhDXRFeN9yjohquhDj9OSS0JlHsPLobIYEPThAwpAYAEH9aspydpQDzH5LdB8aSUzTmFvdt87KW_OjCX2bAvPUj7a8bhfrITHuCUwOl_hNSIaxUX9EuHEifvRKi_KnQRZvkTyN6Ji93jcr1wYk2FOjZEVdUfC_lI-xzuQDSVWUUl6URvL2tfzx5FxqScbNiq3xnIqLrNONk-p4hi1QvPbgiYvXevv6-KgoCOBN5b7E0KUoVcBh8GBPzCeP2EZwA6C9k8u55Ul0Y6dohgm5HS8NQfXCSTt7QQgchGBOyOP96JR_uRbyLPJ18KaFr9QTxkQrxpuks_tWBdd9QD7GN2MU26S9veV2mrWHNXBiKY7NNZjYSkfNyzvjsg3VCwvxU9kzvkozJ_hQnkOnEmlI8bu34cFvYy1Ms4X5fLwaFLMmG3SnAIwBsCz3HxzKU05NBHikuB3B79BGskfQK_Fe-rkahNqJgG2ya6xgeIBivC2iuCuVjM1xcVN3jM0VuwQOCIVwjPpyDgWwjm5rpjX7LfEzwjyXynX5OR8PVugx7bAFwv0UNcbkBNLadJmL5hZfeXHzgPM5u8M1_PEpwxRddCDLbmbY-Y1naQwfaKRQp_c6KwJtT3IzkOJlaYsUlEeoLQKfQI-OFr7Jy6N9-tP3x_0OpecilN6J7UQLOTQEIeygISrIiIkSQgL8m7YCl7cRejrq3kF9UutkU2OIJFseVIFtIKZL92vc3WSxj6A8NkX-yqQ9LCFljVw_acJ9tUT7tNyOF7mFKBQJPa92WpaOGgzq4OCV2nJs4GFYjXgw7uE2NjQ2i9_auhXryGm3uD3G29NjUQ6Lkingi5trDZLCzoFKtQ_-2tWnf6sC4HBlShllmYDfCCorSX3Qc9WvEwxLbRvNX0CgPCEoxIKHAE9UzN9sfWZLD6BCXAtERDgNqc458B3xIrpXpk-hmIe-Res9HtuS43LqebcFiHjjKKiBuUEBCSxSEYQPYdEII9QMsBsp9IoCOKL7y6m5EgCfQzA7hiWLlE_Xrppv625MGLzebKWzu8CP1mOPWTp4FYwaXl6sm0rgbAoR5XtNLcBazT83ji0Qhc39dVR0nFyvdSe9L-EFw6dbYUPPbQDh0hQVzwnXZYFi4wgX8iFfyvfj1cAGrQNfx2yekQfLm-vhGK_sIlCRVZf2bjS6rwAbVIhhPFuTsQ5EaYCc3QbvJg-slvxMGfr3gpUkMV24EE0dCemwKRyRyf9zH-oswETPMyAFTQmlx715Ao-RESnFuc1Ebl13oTofrWpye9ZaqqsGko3Cimdifa716i5Gkq2FJNQRRRrp979uFgzdwm2AL3Wa_5I1t4aHY0hFNXzKU5u7gNmtiTDyLSOIWLGfd44msxBYFSE9YqSdU-7KpEtOLQRppx3FR1TQooT35XW13oPp37k91Uv2j8wLJPAid7msh1AUWmpGiq9vhair7EUlZhnjNIEvhlTr6sIwFzsJPRl9Dy838w_UqVXhKcA2wJpTCjgRWXL8R8b6L7Qs2v0H554fmrK3qcTm1BgmPf6d0aeO9wsgj_cSO2gI6HgI4zL6PUQTsMTzhIY8pN8MW1jPWVa89yWjGjaanxKT6WyzdkCGj6NcG3Yh5UoKGeehwa_5FQwggBfzXYMIAK3swXYvK1bVz_68c3eLtW96nYc1mnOw0QmcuQ7ajBPpwPVqQwH1iLRS3nEWbxznVbgvcdHS1Sv8LcVU8htWp9JheVP2OCiGQPFFScImnsLDC5WZxJNohrxFO6HHJ_6T3py6zz491E_zWqb0B89YapQO7LKc_D3pU7_3-ug2A-BmtjReN5-I0QAaNX86gN5o-LNW8yl7DmVU8rDBHQBV7vZ4uijVQhDvpifKk5mqhztr7B82gamJD6gUucjs6nA9V8i9496A3dTMHdtEjeEIE5zkvtbLe44WyaDxa5KiwZikk137DL-hp9w5b2-ZjwrGqcNJrYwpTQAjHigL12EWMHKEnPEsSXqmYujeWGfB2M9_VDmSgf3J-XAZroxarSzyVuead1XNLHtLqQgT0Prh-PS1lDJ8jH5y4_JzNS6lN78BaEi-rBl-hyhXqi7ZEzGEyZVB-H9rkmCE1jnuQsHj_iWUkZFeE5wJRemTSNTxF_GqZrFTkTD68qxdtMg7nWns8pXHaqDxpWAFaONRj8JdfPCeJhQ3W9qIdugEHXFlYYtZLEuXAlBGkHQQlnL2XeZ5aYE7xDC2JYQRJBj8c5fYfusrnqBgsz4EIO5ewfwmX-OAJg2d9Pm0UVxGrXtTW1H277sVslv-2FcU32cZwwls4YthQ6fyoIVLzJTyMOYJUrpFW32r5tG425wn_Q8ezmTs90EKuVrvVo8w92JL6MDKA-orDvhvQ3beb9l7Sgc5yy9cb90rjD-lyQBgcDfJ0xHFnhjnz4S8t0yga42xeRI3r_mXd0NvRzTUHkedNMtRAdU-W382jaFGRBxXL_4YziKyewh_nGh6BlW9EQ83Qf0oSwb43IN4k6GmK6KKvwr_KiERaBougue7YpwtYyqCrEoMiEEMn-Sog4CeLzg6IuYx4awivB7VYGGGwU6Bwc2IkZkKUFxVhJK63cAwQX5Gcve_j_-WcRRGlUhI9W4RvFhQFpl0YfC3cLUzRQZfV_fWH2MIwrJm6y4VCHhnvx8O87qetR0kM7el6lY4Nrk5bNtCdBeoyy_C1sz--DjsmM-z9i9IR8PqMCZcX3gBry0Sn_js4Ka0cXPsKpM-GpR6L0CLxge1FdKNDSFUOacsiEzh3-LTu-rUUYglWzQShuc8_dtZrIEvVocirTKZ3gaImQ1M1EylwXITBxzCUW19Io1X1mxKiFpXKHtzK7AvEs0kdicMBNl1HsKSn8OH3jxwLSHI4DwFIGYBxCQ0vvG3NN5ZZ_c4OnSfQ-nojlgmeCjMGykcA9E__NgeddsOdWxnG3fVQFIiMzoJ1AtYnxHoPRbtVZdyWB3dX1L9AKxlFep77w6KS48z70KzKseRnKLa6OCPZwfXgP5kEKA7FcKwpwIaMPNxCOedtULYeDhclbLeDtjK8LA2q7a8elVyK6YRvseXaZ4-nnd7iLYLZNOv807ZLaYGm51X7aFt0YRTimfsQIGztdkY9aakmyH_XQkqPmlNa75aE4xf8FqLjwa3AZ9PcIS8EpwX_Vw_pFA0NJcvJxCBgY4Iz98FxssnBRC9dJ1aAn4Kd8lgWvHIXS974MFCCGhfI8RRVDl4S0QO7W6vrGTIZB1ngY6VHZQ1JG9NJOGtomR_8RNH98FwcPzVNUzy9AhGeKBS3WECJCxk_gKjcGB-rBogS4EU0BVCfxzCoTMJF51ufpG1k4eWlEiEpOqUYgUWAN_3XYWNhphToFLg-h1xmQWWUBiVS6tV-XVvEOgKCKp_b8dMJ_99civ11moW0s3XQpzbxo02gCBR9LQYl2OPBcoRr1bVQfmS3sljBMCgtj5NodsMpz-rIZtgbzdchFe-RE6QK4qaMwAUY0oldGd7nIW9V1C3hnGg0kekWG3JKlxMhIB3IbDAVQ4jRJ90_JbLVaj8v0cNmhAwT0QwIwuTJJYFDGM1fYrocL0UKFsHEdPGZQFnfGAeFoMQwUt3I6zpmXbIqWA0VpRYwiUwTTRNTSsH1_eX-LWUnbXBsOmr6X38Sf9SQD2giVwmji2KBw4GSfRjUsbae5gpgZZbTcXH2ZF4FK79B7kM3RW1yKHcMrT3jXyZKjfEee008n6CJraHTc2sBDtV85wr-TQgic1VgACOfee02nwbPgPGhlUsN1e1cBwTGCJiIthec58AQtsEGIsqpTwh0axbKUmUaOj7zuUjDTg0imRCdYb_iMh8ya-YUncdYTabPkBJYlnbHzCB7aXmq42akqBQTTTgVgUsrRy22Q9gn7CkGltOZRbiPZ4Oa6Uzu-CYOsK-0JcD1xUgtTd9icWNNbAg5DCHh8FhryzVmRa5VUkC81OQryM3CgKdyzyw4xSH3qw2HcCMu7VHbHYhvVEXOQQtSaedW6w1shQMbPRKt0Bf_n3DTiyvSsfAgZmA3lrhQhRzd710dzxxljzkbfYEl3Q3SKg2CNM4Pu8SzAcJj9M4WubFMqDirRgVIMgL4xthq9u4qvIGxTERgAu1h7xhUcA9f0IvKiPzBkfExW_QIYR8c9kewkGILCplgqOHbvNBtqK5uXJrnscBUm-Su8yfc3gTiWWlsb1KBm2qwj6uXOBWQ-u4xyatyltsx8AJlshq-YB-K5oJuvlwCXkeXkU3hqRM4SRwLng3VyhdL0Jr5HUv_M1ENVemAJCR1W_6IXWxbChAYiRUFVnGQMCf2Jx46eQo1sNMaO-1r1LdtVSJo4ZELftKu2X0BMQC-l9iQ5EfDT2VEPZvl5JszWbqWIlkr_RY4jwbY_OeQCkPaMxE0eywBeG5zjdTYzmPLm0YjmK5J-_7tjM_678RIQ8qyuFPuNRGFUClznKIZ-T7SYMtFie6XAQ6j3q12Mh4-zEomU1jIOcy2EzZzTVgrpmqVtZUB9wzPIsNtq27VtLz231dh2i2fAfAZHdvIy_7XQsY7-JWltkQ-fY41Dw9QOIhDb_KJHhFNH2xa3g3NGh1WxZIiJNfPXXH2pMA0xU_FnJF0uPEr2u0rEcTWqTsDgHk4krHglASUYsJYneG_YgBCHWWrGXWzbQNGYsZryPJeXNcY3hw0wO49CxV7gb56BbUNBvNIfgS6SogajoeoPTkPQAICjtAVhnrgXyIFnQ38zu9Cwjwqxy10jt04Gwm1Q6xAh_CNQwcLgtJ7elaM7zi9uEGFskPfZHF35EOhpMwR6wBoPSv0ESs8PX1_WKhYSakFyW7SewR86-W3aCDR6xznTr57lJB7BnDb9_fF6rjfysDLSjofLGwjD8qC43OlMNZB9m868hgZoCUKvSnTpVW0B2NcAoM8lgXDox6cxZPtDsW65C2fMFUmt8yqLg9MOB9QRvr8jQVvgQ75GPADaHTVbcDukGOlpWsE8qHc0y8sbWnBRwGu4lUVpyOe3R-q2Y9DVCPonQoeUt3r6EfyIPeid7GaY1S-jCTuj5GlZA4Ridz6yYYZmGXzju_OqZL9TpH14-DvywWaBu8ZUqvz9kVamnK9P_M-jTDn6iz2zy37xyEGtzWT5Mv82avznCG1l0kSoG7HPg2kdA2ngIutv3-sn-D4_H3_Wzni52iLO-5CdMjEHyo8IRF2gsHDwR0mkF5uGdXv8RD_b5KZtgMy91QfiU-h1B1OTDWxxhfSPDO00EtPBW3UPQhkMJY2_MdHzKiG6i28PRjUTIYDcQjc1RrUZFuBmD6S679gKEzKw25fKmSbk6MBIhBfV1Q0h9uX9RauUq8yFRB7mV2EQgMRzrSZd0LVqNtBcOCU7TdrpzJzk0pZkfmjIVGOAJ37T234ICX4_M28IgaNiluXWNYvW8j7k_nTy6-8uRVw30AJnkQRswmxllkn8sE8pfxq2ACMG6LhiwkUeRJU7QYz8GMhtn1HcppGw27GGLZDbd1fHQ-X8EyC_pEx6wcSKdLWOZJ-TOqBWCDHZAJJ44G9MQ_eYCZKj78LA5pooQ1OQJeno7YefrhaY7gsJEY9LqHaDBBrDYPefTlMYgHPkHKxgkT6QtpbAHN81lB5uiiN-o2HPIgI45ODYY8pmvk7SY5BVsu-lJ0K3KZJOhOsfQsoK9CWB37yZj73eFNgWO9Wd5qmmiRVbUyBrjWSXc_dLnbEAKxB08xoITcG4hDIO1TSbTIF1QsBKXbyH11lwKM9Gr3bGckU_ni5H49T8MeAx2Cce-oeZ26dj5jDGQwwwgRbDf_9eKjzVzH0MtA32QPr-ZDqwIPJlpSAIswVKI7W6-TVHeKdYjBufEUoVhjsJ2kZLNnwsgUPySarkA7PjTLxcS7L5eXTIzBWpcSqQfY6eII492F_RPgaAzRnqRW7FA0lvNcCblQJoRK80DLGM_oZajzqytR-ZgfJvWQXY5UAcW0ywx1hVklrP5H9hxJBM6LujBC-bfK2gatWTUNoo7ciIWk8WPKZf9jCnGd2s9YQhwqJfIoYWLYZj2obHw-WfedxSpLOl72ucoXM_UvtvSjnnX18plcNrQ5lkO4f23N0gh_oZhdwYeyeb1N-KADIKIdY3_6tj1AFOqN_vXTuFtEAilg5YpHC5akZeMvfOGunAVza3qucicsRDEYutxcXggArT_nUZa_j9X5lp9EItKRVyGjBvRa8VKDwoHe0Qq9JYaDk2zA0Gqz2BsXKjxS5eArOJ4t-el3UdlFrsrGz0IIM53LsVDnYFGo7G8sQWzxQHD3LqVKhumuL4q0I6gBmOZBhAzzAb-j3dE8MFDXLKOzpMXj4yY_f1BqaSVhA2LxC9FXh8xlYclwHgweVkA98obGvKfW4iMNKJza4tQ5A1QDFPDwcsF1biEPK0svQmSnHNvjhOBM_hRoZK1YD_RXmIYPWzJnULt_2Nq4Fus7QlP0m4I7qSxDSUe3Ly_RtLefBaV3G7dUa62RQJfXVKgbGQTy_64COJ89TVWD5LIEPW_LRrYvSjVlsMD7LPexlQnh6J4g3zq0uRHxcWa1bDQDUQYrQp4Ud_6qc7d7FoQqYbQgib1M_MIbRyJezKZJFNXN8aZWzAkSjR6Luk43uWgogzv_PLON19AnvbC-eLg3fE4aUvJAueCiTQGGFkBb1O2IW1kc4i8wN_II3s1TkjQ6KSvre1kN4YMOTk73lEcC6L3NcgOd-o0tPDO2O9E6I8FG4yCWmnFPjPO1FFmEnjAUSgwhEs4KdKbQwRphNPnZQ6dWsjKPVM5AfmEiLx8drX7C2NFidylmW1dpC6T9L7Qcvd2YbocFGnNv3j4ztPjt-9Z2Y4fZq-02HVNkkuOO5AB4TdPTftjgiGipnbMaBmgBNMwbxkzHuWZ-avaQfSifAvfuePdugEVjmjhcS0NQuh0_hZ-K8m0-41A-EqQ6kzgfYTwKuQ8JdIWawuYoM1Q0G1bJGpwQxG9DPDB8c6y-WupSOZ8c5l2pWsRVw7UJ47hHhFIsoDHFHVDBT9N85Y2SIRbttX2pcnKj3nw7aj6ZcTRwpNPN-Qvu8YMMjMUVV0QoIn1CEyhim0x7jqidBvcSHLamlTSqYvzDfI4l9fSA8m4Yar_VZSMYMxls278D2sxVIEjXt-fqUbXc397qGzvNniARzqZcqrataPpzQoOM-bNj5LEJJdYPqSsHioJGOkhFzWXu49UuMFYUvyNxOhrbUy8h1N6GKiGDMSwe9k9wN-5WhvfEf3wPAztWl5R4PFRf306CPhL-FW83zhBr4c1UxU56taoVNnJtsblxuTTDJr8HgIiS0bqCLpL1s-ZYOgARzAgymuZCRdaxTmK4fdFhlTs6coahCbrSXO9Iehq58t6uw55hGhAqMjVvaRn2TpgwtHS2jvGMCsLFBYnkVXeeCDwA8uIEvujo_WcIUiT7STSP1IHMyllhlhU9tb0sD8wadR8caAgHBe2CuuE6YeO4qet9JIzOLTd3kJRE9Ev7aChlmuuAElJ0o-ktfVIvUbwVAwiWV3X6AcMlmVR_6HzhwZvc64Phapf84hPMYXvnIxBSI5UbvA0X5nHU2lnqPeRlhQI0mKXvLk4Z60WTgGrJoz6mjUQNep_zG1WTSkLwk4zlLwupc492MMc-M3x-vYQBmA0J2OfXEZjnuqAQ6az1hF9SaaF87c_W-Dkd5wgzUEkoUA2kjAfLtSItyltjCzxTnH5gGs7KaeoN_9V3bj_EAquWTrF9Vdr0DyN3fVdwrjU7oZhp_CVfondyy_VQO2wtxzBICKDcgraDmcBS1Pw_VPEIXvNm0ia52zwDDo6h53kRiKECACeOLLwif-WO5IBh4DZ_DFsiuaX1dJyUUO_7vk56KjmN0QEHxaNwpvKMuPtRGOMWkRAwIKezgkGJ-GRLXbeAA_1qqT0hLDsqJUal65fXdZ_J-qEnJH9xThlPem3WrWpAYKXeVOLOCxuA-7wxyxO2DxHqJdxsvzd16aErXTcIq7OgGXL14QQXLcpQIKermnxygZf06I83xy3pkfwEY07BVX6MnouU0ybMlqeFQgsWFnP_yjPuYGA0RQGOqsL_Cz_aq94VrHtzL1M8NTQt3Jhpr_L908QQMXN7kK6CKJnDkh9Rzykak8Lig_xmz8E42bPY-RWpAgAvpju1nggo6H4oH41IfQYW2gVzTviJq9EC1rP3FtJouq9gmSH5xDo5IW09XFskxJatkvOUIjgtZhCNG_VxtML1VdSDLZSrYjMT46SO8JjWJcn__4tR6gEmTrzRE2OSjbLuZpOksXgFrOgRDsZuPSeBAE8VKVpLtHvRQKWimJumFONfHJ7JxCOaUSBzpvk88Wg9em4x7YAd_SAChQoT7XRtjlwkRszQ-TwYfGsyOOGiTyG9dzCGGy_fsTugpowfedGCGBHJpuApn7cf5NNyLsafquuDtEyUly0NDpCwF2i4Dhma5jQsDEbKOlHnq8uzAkJXRe96IQBj0FWieRJyLU-pNsgXz2PqRxNXs__iId_f1X7avOZHN7FyBa-vE-u8RuYGXuLsUtQnnA0eYesQ0hCvGHa71I5E3-w1DCu9dLeY725SC1yVZ_vJ2WJmwEPXJIXKhVgTfvw8GIEml1VGxRFvb5kMQtGbXChL1tz7Y35ux-SRoX4A23pTZVEVquaXb2QjNFOprmA0tuFeYlsUdqD82ls4R1WzgzLVRRF4Z1Jh9AFgfYHqV-7UHwJAY0OpYK9iu6PPknBPAxWsxnLxyIxQ_rRnrbD-AyW-uFhBZ5d38zkvKw68Fr24Czq84U_OlBAvHtTWSzQa_6pc6tu5KT43QDCeWwiyWt1gdahuyoqGpJNgqyD6gh5xjSr1U-ahTJpXgVjnbNBkfOWecj9GK6CMLgvcI21qVrX2IHwG9kMyQgNmu--z0VHXt0WUtEuUcHMM4PzFM5AOZ_oxSVtIbvoYGDXjUgEI-xM7BOr4e1B4n8X0aoorefQhCLe1-Lv2pKRSeUlX60RlVuRN9GkoD_UoFqz59zJwL3h2uakwjt7iehx7DeI2pHUthZL03BqsYtJth9Emw5gsDKfBIR9BAjIzbSFRnnC_pthG2E1WMRMeeKThVkL_JYkmFj4Cr1xjqXXCTAI9QFwcTqRI4ZkRgem_jqVB7H9-BzVDrqgbQoxuWhNRn3_w-xfyzv_JtRcP150_7bEN2-gbBJCexcaF-0PbkopUuQqUjE3-WYKc9X9vLWcdkEehB0F7eqzdIWqRPTsnEat4SQhSvbaOp7EgY6Ypkvjkheer3fkPelAHN86SGviWWtaxDTWMBwHQjM866tuDKWOEnLQhMb_IjQDFKHrUKUnz42saPlPWfvbas8_Ymk7bX-E263Wzb5_MWXqPHMt6UTMSOtw86MTE46YEW9Ww-WW10cmatGb4jfoQHXa_JxCRry14AjwF7CmmQLP6dnm8r4_jm8AylHV8iKCG6r6csAhY1jQ3I-24iLu01EDB6H-_bIX3uiZDXpf4T1aGBJh7I7INB-Ad7d_IV7At-qaorPyE1xvTWeFVQLymsE87ZHY0J157ggITtT95e_Q8_SEiFYg0vxg89qBpuXygL2M_Pbrb5eYTCA6K6N86CxlOvFAb2AJnhAmxe8c_KHIsFZPL6lReDGQmMPBuvdCjjLPV7seEZX30ZMTuHYXNuD7IytEJ7X1o0_04eCmcqbivHBCoQGOzDhQ86DSoX2Omx-hmQl3hI2KgKnGcnfym2Ukd-3CmHAyCDAv2kDHm38H-JdcsO2DNk9QsYtAln6XRVl5kFDnWEhm9bRh-fg9Lmt_mNkwHSwZ0YrdYhAOCMkNlukUp0EYKKhBSY8lsY7a_TPbt8vkTMSCmi2sPr7NnuyaxMvw6Jblb9OD885lSOUp3oPpoH8QPkkhYUJ4-HVmmMGD8orSe0L3k7lLbyHzz5l1EmMahHWCCbnoMGGfO2QnxV4v9YcsMmIA_NX_1CjMUh_LYKrVWE2tfmhj7Zdprbop3nTylHV6YNet5h2MVUtpfj3CFTz-7V0AxKhqmTkSE9fMv5_XY9-QxFKf9B785SPTdj1xBiOsQ0uz3TJ2CPFHOtikiqYkNu9w2cUgYejqlM0crBDpQCuFmFJCFNKrfMa7eue_4H3RSh8Yu9Yw1LXbkAuGoFMGYhegcBEvcxcDSHfZ9f1HFT7IgimpuFuoGHwaNhPnlNc1uI1ILsFeRrrXide0q3L78aMAdu7eFfSSXHm-RcZypE9LHU8caoGqd0cr8hMAFvmAacrXiUE6RtzQUZjswSOziVVwlqyszgPXIuDsA4m0AcaLyEYQ8fEsRZAg7RyRbTgMGrlo-_L1Me2JMPPbiuNi2EtBXz_85Ylbaz45KQ45mdka24ouxzs3YK5aPi-Bv-fYL7FhoIWM6AiJH5ETjucj9KrhL5u-mnEi7sYh6ttj6I-MtSpCzOLrIB5HZ-tJktRhN78f2m8h6N4FBL9ooQXR4Y-QC1MG4eRlAiugn97K-r3MDGQZR5fVwC8SPW4Pt6UDvfaxXZek0HmjYPEk63MIxeMBOLaipBGR2ziR6YsoTUZ3NOopXjZr-UsGukdLw0OIJsxA-nGjmOZCr6iDgY-EfaCAVwAOxAv47u05VBTOP1xoUhMrxNefZ1lt8hEziCDaHInMkDdc4lQVeYv6H4rR2KugX0IXGsFc-C8sfQVnALLdQNjEg8_AfTsEmY3NqE_ECIUhFwxaW8s8aWBgX97Pi8SxkCwX6DyksH9fjA76rP4P5kpWl7ynaOaCfytRliE4j5uDXXywFfwN64DWKIQt4u2gDGo9d12CWUMGrWZZdn3qn8IgEDmUdr_CGXIGcPNuS-wxWoh4G8eGNhvMk1V9zhyhcxgbjoIJLl1T9MOZZ8JQVpiy-cPgClLI2jgIbKSVZTTZ8B6T93aQj5oEbOw87RZxArjYP2XeIHMNh6JUUOND97h1D-tXlI6hlFtFTouMxLzyOpVJLfdrUcr2p0bkbNPAyk3qzxwdRWegSWH2nojJVRP5dopYDUvX3a6sXVGUefUr6llKEtyQ9W84oVESDWyhWRv6GiBkpimAlkoolaGYFYCD72gUISM-ptvaWmVvNmXdZhR2JCSn3Ec5K9TZMg0ArIgFvnJeksow6nIwDSYZ_EXqtEgn9hjLaOcKZSrixLgvGqWY5phJcyYWP7kBsJTxc9U7xCIDh_RCU8fjZzAOAl4r3DtGTEntqzqhScZ_-Fx4ygPgpi4Ko84FM0RvNQGw5VSrOWADroETQVP-La2KyDOjYo4dTauA5ArmYnXyLatcyfbnvgE5KofVhMHwPq-QSV7QAaN9aM3KdDRxBXV7YtnjPx5DzLQE_61NLQkdC0iWFjHwLwM58comkNfrKAUw3vtLzWDiLHT1nPG0pxYBn0zAid0cdOFJ3JRJl2F6-GuMSeUK6kCqbX4mtShWXp1gn0YErlKR2PFjCDNj1o56a5ejMOYAB_SNIjRLO_O7uGofXv_Om9Uevp9XKu3ca86Qt6uOpwQsifkwS6j78cGRTJeU0SlIAGBjzi6b4aJN--CpFIqF6JpuZAxhiLzsHAXRAKik3Lu6Pmb_24KBL5_ktbQRcQX6GQjGi0A4gccSOF3hdJ9j1any3RaFOA1_0HRAv-ExWoiQEyUnWALcqaC1FmXgDTxYx_VUMjeb-MqxAV4eHjJsR7e1q9cJS8qhubSQbHMH72GccTJKlZYdLBHmc0Oqejf-JKgaBMxgkGX30uCXhT9B8dag8jVrDBemQV-wak7QHgbAveaWX74ZsZZF6ZuZ6YU1llAllJlLWPVNr4aaPj_wMfurz6YyOJDnCcVxcKFjBCJRuTBF1ACh9Ye1aj5wDUVwjeKXnjEy-quQNoB5c4clujc-G-ep6-EHj6WgHZefu1HYolZNprU9zHY3T_OrisT2jDBUByHv2RajGe3K7nDZprR-e1SPApINTcKQ42Fh8SfDQsXg0qOfvMdKbfKJqQizEQiCtvkQu1oXhlO8fC4J5UkN3qsPcdG_h1TQ-_zlAPDJ97B_92zV5NkIF3XFM2iQht1oWwZdN6xwKeDRqKmpER-qz7bxiy9Hh1IxU5T_Ac5c8B5xIxbQzgTJal2t1M-_cRvGT0CjpEBjRxqts-KliiGxFl48wNePKySRiGEfnn4Xfqmy4enbmmZgyHCmo-h--qxLIxBEykrcQurpumcrK29z2_jGUNichMpAaaT3UlzgVTbOVb3gVN3Qsu8ltR1RtlO5DM_Sc6q3GQ2QpdHafa2S8Z5D_A90PuohDCpyqvS7tA24KNQEKYM2W_ONMBNNEoyU2p7hZezbbj5T_HLHVRPUiVLgugGFQkNwZ5cRgrgYqstoKu9VJWFE-odBF8G9GwHGFFqyCdBL2CADSx9AnfEssP0TSarXyn-ALo1n5f6vpUFmkcuY-4gFSang5orkODd3k7hSmsCxs5NVMLfQxPtjJcTTrKR04H7xAVNnt79YJYVW73UaXEUammc_qu0GAuNwgeaX3wIQv8ieBeqJvGbfOoXd-U6c8b2xS7b_9BCWtTKZ1A8azUrXAqOr5rXlKkq6I31ht1XzyQAWq3_YWEc8MJahqr7bR5GQqOxRg_adTocY65i1qhxebStP6XWRRurHWyHzDhi9duKfGK_eC1bbuUIevXsNDHdQBDNE8_w1BBBlg4eFuM8vSDZWJEKPxvB4Vl7ciLOs6-diW3bj_JDo1BZlpdDQFKCwDuk5RtRJmr9hGUaIbF6nrjbFduzQFh6laU7VkD_3XyqJ2C3dCD1vOOhslfiVG1fBWHpTJvKsgfLa0u94IUipo6YWCz8K-LCeOymEufdrfaI1A5qutL6tF0CaPl48rmLRMayxqTf4ZGCCDe49C74wOS_kGmxchhr8DKGUgKwiWJWQjIQLIk2PzaHSQ4cE8uBQebBsCMzlrzNr1YhYzvzhje-qorpNcwCluQeaXkqp1WST9LbExS1jN8gmJhLgS8yAOd_yGdJchugXdbfPXWD_R4oVf40bCAv3HBB3MxQKq8dZeXg_9xqr_bhwqY1oUraAHLEol6kUS--0eDJ9PzaLed1ZQ_6j-pHR-mu-OkQUvtM-THVLuNMKWGSYKcBnOFYw_1NpEkwoWtcYCzk-nq-aHJ5XnijDKutRPJQ5W6RLMmhB8qFoZpRp_aDS5LJiqp-Q4g2QhtSCckgUwHN5GSDTLaYvjkR5jeIDI0Df_tQZQv7BiusW4M-iXMunM3qpOcdAdfnBTmODqjdeBAk4dRnayZtb2Ib-JKl5ywa6WUDhpA_UQA_sIlBBbTjetvlH2sChS0D17boDPANxqPYQLorzUflL42ay1DQFsRRdnxTiNvzN3nMOxzFdIUYqWEiY29KQmAFyuERLmtWNxvUB7KB9WqxV21mbJ-yIhTsuUTHve3HdcJuWPzEtbZemmvTyJr1wckTGBWVfeT20e24dPMpBbRN24Mpx_tMxfsioxNsXFYqKHzqWqZ8Tp-gj0TUMr-dATGUJHHQ2Un1nVUYhOfB-G-cycBf8zmgcnA9EsKkTOlZY1LRmvBIknw6thweHCggBJ8Ke5N7lgYjdTTPs9HXMZk-YcGJ8Q-TkB4_Dw35xq9_hnncS-Dl-_aTs3FD-V3fAbAd9eYbttpwk9kwVnc3GzF_d-eoCntwtxNH_iYmdeBZIqLZAoDwzvFnGfVunFP4RiUtLYepxu1m7HLhPSCAQn6SNcLwGg1U0jQpfYIYGZTL3Ntq91XYv3J9vy5O1apgQZic9XEMxzOuoYf0zDEU41PaVOmGv-H-mdrmH-MI0AquibmsDkD1GoUssNDqsqGVBgMMp1kc3N6irmLeIpdrSjOLUsW8eq0YGWoMXXxp32wIfDr1fad4KV22Slqlrfv4RC2v15WxVI6j8Cn2l6ymNxCj95fk55ibBk8IgObZEwbu-O4F6focQnbqXcLMSHipxWVOo0PNAnxeG8ER8AuVaimP1nXVWhNo77VuX_Yat85m9l4Avt0Q8tR6Rpqruw0cxZRH-3GRk97-svz5QsXMJgNZsDquzmeRT7ydwFrr8NK2Ei9NmlZ4pziY4xgIjVIJgIhgkY2wEH9EBDPLuqmYrA9z2RC4KUg5aMAvhRRZ1Jrxd4uv6C7iq9o9x6AOVwA3AzuM-A42325s1cNlnURin7VjQvoDg03eXsB-G-iSEUw_WoiFatKsO1U8bW4GP1-XwaZMD2w9-NXF9JCCGp2PaYNl79WZXpoNqtOv7CS-USx0vOF6DLllVZebsUhgMTBHg6I7dmJShzC1VLrCV_XjFCVlxfSdC-HkHceCUwQwQvkH7CzkW3Xxqn9onVcL1vMKgt-D7ov_952u8jsS6gkzEkUZgSFKNUMJGZv8J1rhg-ZNUi_50EsohJTlxy8H3xw8RFN9JsTZ7T7_O2yJ-yB5bCdSHldOwfQWtPvCw0df7yzUQtkMqMY384QRdKraWO3CwhrqD5_j-iqM1nw3AKDnqvUZ_pL_MrJT5OwqvaQLlIJpSymmfw642aXt7P1TzzFnwOYb0Myjc0geBp6JKLB4MetCiKUxmYP8M3hiH8FSZLv00jUmVJj-CPVj2IVml-IiAPyPU45_2W_Sek_l6JDqxgviPNU2QfLqXLOgs7-30-8ZhrtlZLC1AYco0hIEyVvFBQC5CjorAuillJuZ02YU5_kNwGG-Avbqb2zLhjw3gO7ZB1Lz68cv8F5YVsUvCvMgRhgpr5Wj_5uFtw23HGXHKY2Ejm3Kjya_Tw1EbrPl7t-UYyUxZkF6lUh-ZnndeOB7RWVO9lDvW-kuu5XuYFbAM6ouYOPd0Am1Te__qnJe0cYwKBaqopwTCE_7cu9EH37OBm3YWyGrthggmOrcK9jSI-xA40URX30vYvyuvNzZ-0f8PrZIfTtss2f0w9om6vDpwxsWhXRlTyz9qc0ntEgVwX6t6xWklLasPIwXZpahtO8PAA9Vqy2D3t-nMSyeBaPMhkZi_k5x3ckiLR9RHH1OmiAyYkGafn1_aB381MKMv_8AS4YGzeAvaHBwwfNDBlPpBhdupAGXoGPKFCM6d5W1QoDhwQyIZ9uFKuvoPtxntY8MwG5x-Vwmg3GhIDiSmoybRNIpfIqXUVzg5_a9p9b0-Go59h9B1ntMB0K1Q0X1EtZq-tVRlv1MRpSjOl8LFyGFQ8rYS0aY54cZgE_tdOaozg5NuXDJPQR515WrBf6NyJ2E66D3u1Fde7hd-zUMSiASQXMKwCLOAMNn4f3MWoj6UR3vKPjtBNwF1umNrE8P1tErywv40kYGz8-Zy5Jub9dMgKEfXbz1s6XIqZJEDSXngwVYNQx2fhaO-uGxt-eahjkVAkt1KoTe3sDxtkX7CFQNAaVBlsy4JEqRM1-Mxg0GfAP6M5l6MMhbqkJoN4oC4TVUlASghOUHqkCorULtgKctw01Ea9UnPzXz-KKpA4RllrWdUryiRH2A5RPs3KH6mTKVjJmzXvs-tHHeQphSLLm3QV1smoj9Z-oAJrz0C-f_Y0LE4Rsaw8Ag_7G9OOrBOD1odrNT2PbpvyeMCv2179maxKeUB3WRIU_Mz8b4_vi76gODzX6t-K5zDm1ukMlpNLfRtD2FZOEu2S9dGFFy-Ut3gB8Vnu_b1wnzETDDqWZJ-6bo9qRxrRAkH6q3TF5VTKv_hnYKY6QzcmotJrdTNPQvwCztcqj4c45FtJyax2tdOQo4lhoqDapMA9TawQMxunVToG8YmNP1YKJljFq-ZFttAxcnIpaTYq9scd3cfS0S63cnjaMT_H_LEBW9FedIR53Ko12fyQn9cLgErigUWMWwgdTmE2rPo3ygRky06cEcrh6zUtNb5E0Xt8FnmR0n53wZbJHsX9N6ficGSVwanB9ZBGJz5TmRHdF2aE6NrALFCVLZ_9mUP0XVz9HSUH9YbauXqYM8afLJ_R8XNm1WtqX6gWkCG4HulNtWURyTWgVuQT4jiB392QSDulnwnUnaFiroMxbHD6UENVgg78icspfeRQ3I_wEKLpCmngQSDvgNlV-vzVct_920i-n6DSDav6Ez6MgxCa0cgrF5Fbzak-koA7olgU2xqiyoAFv02H76alrTcE6Ooi0zNIBABz8McKSqmJDhJ3RTpCYQCmJ71Xq3xdeT-9-WBX9QgNEGQ9BAcZNT8IHY7yUocfYNOQS3XbCogSc0HR260BC8-8ijyyx1RfZB2kErTGpUCo3FQJLg8QNYU4cThUe1rmgzC1aJSHdYD8OLKHflJCHZiGGaYW_MA-tBWfHiEISIUcIghjbVjF2dBoMZBW5hlzvYWOV5y1QXW0zvTJ1Tw4R6kJGWNTK4wePkrh9W3t4wMu2QvyJQLGGwb4ltSDWefD44MtkWdfquG7OTbXqEiPr2KreJ2j3DASXuBDBD25RvlZc4bhLHFj9BUJ-lulsAvDWKCb2Bou0i6akOancevmmSZUwphs-hQM2b3ugNTsgsUEoF82dXWCJ70gyr1RFBfBsZCYDMDWbiqMYC221y5Pw2zoHRdQ40xDVCmTzDZZxzBr3ywIcE0Y_6c9tlm4e6EgOkdHg5KaAV9sV_uMLbBeSxyihQgJuxA4dzQnCo3Q_owAGtnkvhQp4UgYlx2AeclHenpTuFb_t-BsO1-DV6LgRplzfXH7ocQedgUXsd-gZtA61tnwNR2qRk9dbmtOikjI7qf7tFv8r0pRbe_d_mNadmgformlLzAtUn87xkZLmcMx_iH0g7gW7gbEXnkKmX9syage0xeQ12qnGvGF-p6mBKFUM7d_8ZBFt3pSd0M2Wl1zLnK9HQJVPXjWWBf8r9UecYdpyhtZAnxREWSqG1APYDP8cPpQcewy_QaCnVqyYZRFkf6X6ch-O9sJAwzR4MLElaZ31KyCxHTj8565hGC5bJUdg_I91UgH2yJArG54y_Yc5Dl6ALUn9QgPzbqDFFUOJjwU5o9uD2XyEBYzEErekT-GqxtSGOgCFSStNay_o8OmjolNWZVRc1_aFeMUOgh_GJCAnBMs8AVNU8rG-2bL8Yn_08Lfn-QpqpZIZIVsTZinG9cCIy-nuGGUtwHtPdG8xntWD7d5rNUtro9BCoxdrnbFOkSAwCQ365HHDHG-D0bnxTd70UQLYZcAb6rkxFrENHGBQFl5f1sOWZnGhofb6snJCirTWsgJcst54Dzu14XaX-57i-J3gi6pI0alrVQhxukhTtV3oj42A2TUGD6Qb2P_PjwhVbwpyfkd9tNTRT4YKbB6v7FviTl7JKRh_lMFAeLiNc10auLFBnXOdq28pbt64ilr05QoEABo-2qj0w1qRgK1RfdC_x2WRHcrI7zWIyDONsyqumIklidGqrEh8EXCSg3a1PBLMIrUfkfyV8C7LvTL_lifHl18bZO1BJtoksrMcCmPiwEJhCCMn1olm_DSh1YHahgEFrP9PhmLrFpJrymDuzXlWENX0QfqD8_bsiaIC7sqi4ZCnGI-KCnePmdiATIkO1ROI0ty_1kRce2LFztuwYFLY_z1yJlFflviLtyjU2z3F8Dl5JjO2dWm4n7bBCRT8wAqp5eztDZdaiuQUZKi9vhIuEnqFpL5zQVTUlDpMWodeYlcEZT0pQQamulicCkRslA7Z-CThZgOW3QWCv3eYTvOlZ0merHzQFxYq-8S_0rfwK9BEA1xck28GdMIXUd5cqBN1kUPd06qbwbCAgVBABucXvWbmkCeokCXOyfxb2BHl7381ZWy3_U6M0AnKzxhtYBSmBjY8sQAeJg1WTQ0ZpbMT651_b8ipPHAUl57j9rwVzxrdtmtai0VoUVNv4UEF6gDR_byb09xWMXgCWHrBMbbs7KNNC307cI7lmSHDwFDiWjxXcZtGMCix71kfh6uZsRBursMcnUoIaGvd_Pqv7SKeo3c1DXs8d4yraU5VqtmvHuodSmfcmOCEkzLb4lmVfBZPrsJQcLb9xFH8wunqxWYhr2ERzOJDZoLIKNwQnPDcxoK7UX_tLfbHKAO_CcfHWRgB_NkcPVvf8jViQRTrskD_19WqQFq241yN8yW4a61C6v-9og8yJyy8BWPQdiKESA180YGsfujYRx40jXR1u0g-WgRF35S97vOzm963EAkAmfCPBpRckAFxeDcb9DfBvhihOeaQEobt9UNhiDTNaiSN_Hl66wA5DIPIptw0_HQQLoVQ6HUevZymcwe9A5p7_AdCf86KBN-Z6cu7-5OTmctbwROcfjMYjlJLXI4vSE1fY_BdaYPBvPWsGaPKTNr9kwy0RyDrYd4a3hzDBzEOAGUJm14pdaOSbjtwoIJ0m5TeQRm-e-EBqxv4dcABhod1agzhWgyKZarIrtkDhGW7dkDqSdxHzPCxphtD1a7SD2MdKfz0IK_IkPRSr5N690e9kBMO8r0MmuMg85Jf4vA3w3-ywnIbaW865qXxkW-3CYgJ8RloGuBcJewQH13Ozoz1FAlt1Gt5Q-uHiMokLpmbCmvGVk7xPXqDu_sqRhQSjlEXRBjmGzeotBxxhTwmzqZfJxRXEdmGAtrfqva6gzYGgSdXFWo-_wfN2-DjBa1Z8FAxpmT-dRPNvaKwOmknS-tI5xi2i7kzmh-oIn8n-AJ6WanEBaFc5vTC9SnQNxnjnnbTu-bRMj_KlXXpw-ryvlGEGhdMOqfcgSWzQLPBSVMJpDU9rSZMfGl77Q-S3q9mRfjPnd6TqlNfOskpiQijqlKNvhC_D2S8SerwBOrWTSZ2i0W2NKgtAvkgn1v7wHkNIp6iJ9CU0mXIobg1uDrdvReirxIxuznqXyf9xma99oqKmQvh4dWfhlQH-a8AB1Hl624CTjEs4CcoZfCm2pMpcDie4gVvQiGkHQosnTdOA12IX3REq8peIyawJpoyI50ConQxCFuWqKfZkxvaLMfVAHcpvRNrNEF-jD1lf6R1emRB8jW6iQLCKYVueF6qfUsmb6Ql-gmKcakkB71QGMSGTa91eBg--S11MB79NFQdZhQDpYYc5GAAKTR3PF9Cj-xk_33qn0Xz3Xw5jRTZqm-qVcqPMwcdxcB9p8JhtWuhGcfyGmON9hM83JHg8xKGUn-1qPOnvF1yWoRcI6wv7Xe3jfo-_RHLEwbPTbihfw2H6ycYxEl_iz9zlG40_WNJwwWDdHn-jsau08fNxdR4WC9FEvC7lRAUeQPVxUWE3ziJjlDMeZGz2jy4daSi-LY-QZCzarHtQ4_olBcW11Q8gtV0lOBrkATxbd7YRAL7_dh54Xw9T6X0O7TlpofzzAVMZzIn0iTai8k0eAzuj3DT2FiCHAh4-RbKHr7mzyrPQ0MUmJp2PomCnzG25BUbYSlClBcjtotLGm6YuDPzB5X7Lu_vH9eRjxMEh7ZqIYO6m81D0dwZO9aVZSSwa_LBb1iBFrHijTsL8rHXXcBSnp_jIaZrGLyKkxMaJDegmLd8HdgACP3rOqVCDg1n_CVE3_jRaqwwHJVpani_j77aSGBmItjp7HqbcgZr_CVMCBHX3XfzlhuXZkvBoc8ZaYYifhvgGFGEg0jHEaxIIU0QDqm2L6dHqCH6yAlkkT8zRgWeLH4Pey8nR2KTAZP55YtaaU38cUPOqVlvTmPihzfNHH18h0vLfaPPjA712C9V3hvVACSpU5SsXQU7NfnnIO7_5ZcX-iCaEuDsSFlJcAJFaSyKJh5kcXsGdRCAM5nVfyH6_NFHzGiNWaIqc-E3Yl4a4pS07bpe74bsEUrxUfdgmY9XULfNwuGPVg4qBsSoS8coVBn5SxwVR6OITKjr8Iq6b8EZZxxc6qJJe2Xd5mExe6NxAW3sClorNhS_wwcBYwj6HUH8SmXpZ0xqADYVqky8bn-pa5j6RFNSH5zz9deI4_1ioLhkVtvpbRFHOxCPzm56wjqQnEci9QQd8axmpiKgHP8HnpTzLHO2MgqjjunSox4sXOz_BEEPWghInV_VpmFb0KN0B4UH_M0f9Yar4O1unjCGwlLF_ZfLfNfwmi8JoDRMYIyFn6D1PxQgdBBPKN0oC_Z11E28WQqTORvTJqusVY4qoZ4d1FOkd5E9srOWuvs0gBGweaIzUAZHdRGr4NygezGmf27uWSos68ZHaB2qOc79z_TpsXiVeik5uT-pSbt2R-GEIeg8cwCH1J2u7UHsWLmJFyUmBW3K372QeHxoW8UKinTNg4Zy6uF5acVZmom5E8s957-83Qcs_unrHFoUTPy_KWoiqRefrQcpmCHra-JYSYwNxfwgzoCp-EHgl2ypCIZ5BpRQHgKweWJWeRhioSBwGejT7evYEl3-L_FazZFY5W6tKyXFktO2jIySP0NMGxFL8S-PWQERH9cdm7l1KN849iSIqeMI8cROEUCWjUIhdh9pXJnY8vYhQBfbEjJ2fJFjOEtT8ARZe1jBPNUFdoRph8YXVXRkHn0uw826uIzZGnacbNgRwgNdilq-j1Rj5iirOQwXSQ1s_L2Y2Gl8O7YZ_tuEek0ovZnebzesmYKtoY_XhunbD_U-4afK57BtBTsmm1Ed_AwfhZNV_vqKC5DraEE6c6J_7d1f3NJEMVK-QDm-iMLGdLHjOr3bf8TjpeXNjITXiBZ0kJBb_qf7Y6Sze1UueGWd_23NVi5Ufe8w--C9fE3YT0Hl0wnSRJ1WvOGlLQf2Hgk8KaazMuCVbkNFzjojCQ_IrmsEz2sbWOSMDB_E2y-6JJyET54mCpfMYhdHXVhtbAH0sdBNtp2KGfh9206nOJU-lKwjo71lgNm4XoWV5Ux1LXYSeN9r7BSrpirkFIqxyQkJez9Ulcbiz5ES5t8oaTwCOnIDE28Vy324HhGPSi5W2QPkCOV_PjOWCeM8yjS_6w_FnGuO_26ecaOEkCNBZung5p0pHSmD9D0SeQ55YvwYvwMhT3smiwDo9dRcFa6sigkWHHKtBLW29sYLB4r5pNWtHd6CihJCcG9DTTbaE5qP0-eOF1l4GKEhtIUKDPGJGwEzYHjq9emeIy1uacdIcWTCJylvCVOHdWmLaD1HefI1tjSyga1LuX-uZPAYEu4H3BHd_8RhEhTIIR2W1Zi4pcy___Mg6UnxiELbieUU9M-kBKnEG8wm1_VCAJVg6GulXQG20z5Zq0Zr8HsRUEpcO6ULm-_3zF1WYWSPU-JDi_ZiKxGdLOidzU4gb-zzrrLYtA2USFwdncVimCESLHhKPSvv6r2xX5Hz0eTuLmhshN4wL2du7QNz_mLVnI0aIGrHWQgs_DEy06L1P4ANm_Y-0xdzookmfICUGKChRsnNFH5Ardfg5JWwzC_jQrW1XM_t8g-3Hnv_A-UzUyJWBl3ezae1NPikowsbMsIwLuHHteDmQmqb9-93yiUdXB9FxycWFgaPksF17KxTvI8FS2PPwZKsSOTXMQNCQyFd4fJDR60nQhm19DhQImTl_QPvqibTAg_p5zlhxlEFdMKoMEdSrqovWF0mKoOLbIHlGum-tDlq2Ll96PE2-CrnW8NyHVDdew8iZSZ5dahyl3prZnh_EiRB8nNBESy8uH9ppuSH6XlQ0TJXdhwI1ZdOJvFonZ-7IBR1TVb4ynvpzRt-oWE-tNx1-6qwSJGzrsKnn1EYkDQaRj7nfztiOa9af0LGUR5ejBaZVx-bQ-75PO-xBTxd0UpI5kyaEf9T3rUM19GzASEzvIwPCPRplhpopMmPORqBqg1oFxqI9vzahfzntnYmWEBLGc2ks1NZWq1gLcSZLw947_EEGgyqw51cFGXLaB1DeA85qa6WT1jRmS4Fjj747XLPynyNH73NU8RWsx03F0y_fvUpPGS_vaXWR8AhEy-gdBW5CCYbsPv7WB1Ls0_DJMBSHylHgNQvC_5knHobolZyERyyye0rwmLca0TnAJS0QhgywEwaoateT_H3_aqypXAFQdqP9aXzDLINETQH-jPND97CG-mhA5bh_mmulEvQMxHyt1e4d2IWPOJjYUvSj1gaxoNl8C_v-h8719rmYl7e5jedHHzYQuDgq-i4B8HlQxgLycD2vQqtt9F8fadudBvjaa4qaHQNw_AZc_8aWNUQ23FdSfC2ZSwJvYASGSz5iwwZotTwF92WMyzfnNvdjFyluEZR4D2RXnYP9GUuwGcg6LvtzjZDq4GoOG8cZEqgSQpSUFWN4-NUVBrb8GLY-SDo08tW7Q42PvN8h6h6cPCpFgrKFrqEuNupBiw_GvD-Ihj6S81070U74EpW3yin5jY5dVGJO_Q-8GBVsyfe9VyPGlDCt9p2-FwvgP6aMZnWAQys5HjDo7QxHaLXAUAJEB4HJatbd3sDYsC3S3Py-_NDzA9_JuOI4iqvOjwf96mS8xfOkoDY0CyKso6cn7BWBDbtgGL5yjjAOrsgyRzALWaUehhq0p48D45hMtJh40lBfgA2QkEqXaqlFdooXKlfyn0nePdsQPYJWxg4O42Up_ha9yeggy_bdTtWJQlR1bpgphhsDFFhPq3rrrD54e-AmMPvLS_KnhRHR22d8t80bo2yhrXzT612iv6Z_2_wxWbm8AnUB1L4t1pnI0BW9MLhU0EC55f52wZCJQ8wJdRcH4lbuUsZ4ioBA8J6X-UtP7YjjBTeXITfvyCaLvkwGseuU4DCiTHh6mkqIq6ynzsg9kXqjCB7oDfO8yZm82JEuzLWaReeZSub0J4FAyCUQImgs3Ui1shcwK6IVbk57-Gjywva17R7qQhkYxqeDCbrd64y3QLFBnhiYSN4TrR5AaPiNz3eCYFYPTdMjNCWa7HMb8wgI8Bix513uKuS7HenMc_h1QwCzrD146GKiiEZ0LT2IIDDO8h_gKx3Y-7N5B9Og7wjsDps624fXnr889NYznFOBwuVhNmT4aULq_L32VNXYO7bvGEm8T__RrBnigqlftf0nHzP2U7gN3kKnuCg0VryDRRs30No9mmIxpCzEkGfEDb3g8SxDiiyOjZEuFTG-doTdRDPfe8DqiPTfJdFWRfDkBKFbpnV46-Dy1PKe1HdpoF82ggBjtwT6N3GZ4MPq1UVYQ6aiwlk-vUpetZHohzn1AD15XlDE_NfnZHhvGrHGApPPUFCMmZRmqQTkNH4IEpUDQM4_SacoAIdkrgHO7PoUAFoHYMpumQ2pow4VTR3mj0tpvG-iIBbcxvqc5XLQQZhXuhDVAEl3p8HPTDKqFgxTxiKT_Ns2pfkp7zHS9-Qp6VzlZgoa1Kt-ipc-BOpwBzzeDqg5bOYvDF4mySuTfNy7RnMfX2F0WZKN0j0Rbo99iNUgkvxQNTAsicaZGuGWaUbgiQI5OT_kltLhbL0Lwk4AQpgKHQ0OBgIYC7ONSWNWlHqRTR0CGRYRPPB5tOfzJ9iVeKQKgTnH-PTukqdsxJyrwalRgF9I_b3qBXCFeY7Ea1JyqYhi2c1OLLoI8UJ1kNsH9Jsuww0WjthK7U5KQEHkQTZSjdEyoD3M-daQhocYGcPqRLqt_kfDWpA9fQYJVlMCUL9aQuMdYVz0ZzZwV4PhAoqep2MwxErhdjEUPhqyt4mVopZW-Zyigqpw7ef5K8lrBvtfLV3rt0hFTzuxACp1wQOWVsYvY36I0Yff9iHGHaOArfsR0KgDgbNK7E7D5CtFrHyOn5XGjWcdjLaYKvCJ8wKrIItOXpWEMxBCcKsKsj3bo_jJKiKYS5hVeaznfwc7pi0J21-4BAkb9Vs4XqIcooEFbUlqFSxWMuBokQAsxBEdeZ4ZEWbD_jZdx8NxELKLxPuKiYYmaljKyW4NqhyeGPgFxeHV7PC8fZ5O1Zg2sTMkW7J_BkZte3oGa9zeENRYMYmVp90gURGZ9vex7-GM362BBH-Uq9w9XYGL_yVfylRVU2PGoCEmMoxqgxsYTt6t--noIEO67jMxWhOdX-i2bLo4xdZnTBBDiiCwDLBM4SS5FWv9Q1b5NO8GL9ePjw0PEowJy6Lhq1MEBrQSR_AiNr7tAQPoJc-ltUMtBCn0FrDKT8UZchBVaMPazNXHJyJB__MZfJLc36Pr3xI3YG7C7plb4MOzJ2UU7knbHbcGM8WqKykYOBlde91ywezS-WEo8EUTO9rVUTDPwSPH2NjnuFnu9cEAmXYicqip9J5WLcnWxKuo51O53VaSXa3KOwkRsh86PPoxbN_6boEBx2b78eQOgVrE8T52OD8SryaCcj7GmHsA-nLWXhAZ98WTCCR_O3N3JZSMDB8NNKaTdyjILTThzcZBAMHpCZteh3JxXO2kiw9Q53cCVt-PNAVFwgANiyFFW00sGKI1VxK2SqsCXupmVQqzwJ_VN_KyQfh56xgMWxEucdcbneMoOWUzDZduKIBBhM3BiiaidHeflnpuDid8poBugQVdxNZdxxi27cdV7h0ieu0WAJj5G4DjNY5XI-S3cilYnTXUNg3nE4kQb6jVsjVPKwS7sur3AvwPld2qHJD5Zo5_63axnH-FQuiA2oF7pZxoYiz4IYY94ydG8gOOYteoiwEDD4tDi9_p-Vh19qsJ8NyAaC3sO1mKZUhLpGX4W5vXI9bONL6KfiZtpGsNOS0al73DiqdLiFtAcp68geOr3ym7Miq2xtthT-mCiNOn4HugT-rogZbzPlRK3aHEY3MsLL2BBcPue8ffnazWOosLQuThIGdGwHxSHwk9crZito6H3rfhy5FQYRZELbjkp6XwSzWqwGNh5PvS3a4WxLOImjdS_SdeFFztTbz643sos675Aodwntlo8e97352Zl54dJVBWQQQXZe92VNcHdywcaHzSA2NyLRWz9kJA4R4jHUBq0Kd_y-f_4LZMgcnSJyB_kxotskTdJvy8K4VSB7NSgMxkfzv-DWokMaWuZ6i9lhG6laXjt8SzVmZnBXx2fcGgveBZ0cEEy_ZAjwSaqkircbn6rIcmwjOLxsSvcyHHaB4371u2OZzhoM1eRQ6I_wXHJP2FW4zESJYPOhSWtJ6Apz4rHoUnlDCcg1MnT3Q6PvRNDq0jB26NCCl4ixvXlWtuWTa6_bXBARoDauSXsf9YAX-vnSTK2lOz0pOWgz_QjQw0Lx7nEi4sMXdnGvQNxkSiGAmExZzqAPZwMGbdAJUnjc0jW7Fi28MG3G8cHvO6fcGMo-IHUlH1hr7vMVCViYqjcZQOJ6YgAQNQNe6mXCcsSJij3_AeMXOJvC55N2l9GkRBkByX7-NO0zWRMGZdtYxe-25RMM46v4AZi3A2mH-31HphZ34kIlBH9yb-8Vw4cdUHpY42kEhnXusSk0gx_bGxqJRVVpVgo0EAAAkhSRkWSqJiccp5iZ1yZ2EpHOgEM1vthLyCualal7K-fTHBm5jSjNqNNiZ85xJF3tbnHSjLNdQ-sYcUnhDFedPfS1bzfVZrJBfzjp9_itNRPeJnHhYGe-K9d5TQqjrBAtwrGnMkGhpegfK6Ac2Nklvcl-yCdX0Fx_OYe6peI4slr4S9XmZBj3ZpG7PX4NdyAKDu0GwufKIcSATJlFk-1L17vj-b54H5iFj5472wPjh-E9NJ2UWS5GbEC8TPpqw5wQH_Q4KnOIE03lgzCcImIKW4jK52uCSsBljKI5CXQzgTj2lR2lf7OqqEwyuFP6KEm4Gbd98fASaqrgFmR3CBqJfFkaIeuluglEt6hbkIQU4KlhVJ1kwkOq23gcjyxC4TXYEBNake_62MYh17xz5yxky34x6cl8B-e14KXqOG5qG5ug3gsoD334ICr72xkt-m3mICgkUYOSBE83pb2AA7YuW5IqwTLStyt03wQhYmDXd_q4FBM7ZO-uwue_cT49vvpDHBAL7zwG9if6P_wwVVqO85qFfri0-S37JXpakkJ6_9SUpM18Yo4g2SbEoFLE_psEgmhRAVyGZjGMCU2Yb2Nh6eQaVhuiciWgij3Hf69IJYKZ7dgNmCuuTMp_VlJ0_bDWGlAQZUvZoXemSxVUvOEMjNj0JxhAnuo6Pi9eWLcpy018a71RUAcCrdI6NLvPBNr6qYJgZL2YE6lLe5kN2xxuxtNIm0PdkyvAo9N0OGwXOkQcY8KxwwhBPI01FGQ1ULM51ICIEBERqQD5-RkIAICNR6o8zZD-6Iqah6mvg2OOhpEWzyTuIV6y3d_hOKpYtdPZ0tYpmGdXjl0CM6UZmUyAxk43Frunx0UQg3pA_Awwu5YhXCPek64_gbjQve8bn5Dxl6ZAvBAk85VngWQNtjH4JNk2GABmghnZr2ZHWhO_GX-q3KKTyOqbUjACY1il-tUhIs0TkcQqrYLRMXRrSACeDKw1VWm6iTI_6IYfcUGs_H1Y0fgyCSI3lq3495MNy-dbp-G5WiAQCZI_mqzoxTcr0EifYsDKQuzpSs4e6e4beFerRgJmLVr9Jgo9heM988Va39i0Vo0AEIPlaZqLXrAz--eT1xxSdBi6JlxKS2uzYsl800ySl66rIKPUoXdkVni_F_20mmkwEGCAQ4ZJS1g52aDOSjCYPuP4nUfCCL1868DyocogHBIwr7PCQ4-_0e7rKflnzCoPtETbNRKJj55oRaiAlFdqaTWWSMp_LjH7w0GFXxzTtnuur3GA3QaeaCO9bIPf-kiFhBArunZ4iY6SdxqV2bu3ANgoc35zfPy7r4wZDnS2BfHFn6KXRHhns5yN5U-OVjT2pIBWbLxQj8J8TOrSGYkpcTwJ526XWPKA03qIn2pOEe4wUDkW0tkxyyIgt5cCjSPWhhQQLsYYKJ8rk2ojWvIHSdHSgIof0eVI51RGCW4jcg2pJ3I25sFIfpgqI5QipxB75eTIB32XCBtzWmK2E6dPAQfnHNPYITbjLmOrH2f6zbW1_LJ3LVtMMijseSomNhA0v4KUEBy5aOriMgwBRc2doCITBcWz0OD6TCXbcrNvW7g6BDK67Ym4Vpn6bl3B4tIH19TNQB4YhX4z2kAyhlOOlvwqMcfhtdiNxuSZ7BAqQYixn5dDpswpCqiI_MjH51TMikt-YBBCHTr-RGRIXaWxk2sTl01agDUdyWGJ8wsP1f0ndpLm3fHdejNab0MOn6osZGpP3ZgZIYoX0o7CoF_5lVDdc08Dt7L_yEmzk4ccF-JQ0JtbfYdzvc4OrUBm3zQfNVsdw_AQHE0H8y3wolZFgsPzAOF39j-_9SDKkZQAHkO42MKEBuDYNRANGd41ztyybua00Dn8XEYC7OiWofp6CNgeFts0oXhYM7YU-0A8h4n_xVYrk-0Rb-zpprX3pmPsLySXIDR0EBHRdi54BjFeutO1ODlZUI0JXKinpc3TEq1Q8Umhk5Yid-CmzYfaVtt65hsdKIybzDgZkBSqOZHNlU-qgtHZsZjB7HhlsQH_hsJMfO_GDYmvUyL61zZ_6i-kzVl9kQzarBALNWbFaReiu2SG9cY4n8raKYyXQxQXE31wFUrKaibEAXJlq26xQzmZmf12t4-3ZVxMi15PRbREWLYGzqNRARqU3mHd3_FPTeaLxcWy-KfufvSTVOIYkKoAXAbHfGckSZgQMlCPqKvao0Lss7N3bdcI04kJRmOcExYhAXvepyznGreKpfwWLm2YpoPgFuWq2cbkOg_KNOxeI-SCe8WL5geA7u7S-PPZZ89jarsvO7kPAIQXxHg7a46y9wzDLclZD7UcECTva6MEKRlMP5zsg4EfRkmZ8AQcykymQikio50dvSITkyqtD5XLkLYv2eypab6-1CHu3z-YUQSHYLOw4fsU6dR8lToK4I4pl9auL2j4z2FqwZTt-wnGkTXTevikprpz7BBaY78BYmJHquSGjIEoy59aBoFNWsKLhyB7r-JFAVRXgZAspE59-JmzJVSIfyNWXThYFzabEXW2VmUNRAcb2pRUP7KYWY8xqgZTvQZ2mtXQBY4GpAoXR6jgH-fmWg988kAQBxRnDoZgb0VqOUNQK29C5BIEt8CsHE97YSouTsqqGtATh9YQUinkIpjyHMAYRfnkMiywoFYeaJdEd4DFPIvJ_MmDWtg43nh4dbJahewqSfAzmFH1B-js9WAG7bivifCkEFdHfWcyDybAKICp2iZ4clqNYH9EoSgYJuDnUoyHrBvhWbaG4CZFi6bALdp68fj_7D6MCId76bo2D47SRj-q6bzrQFHvrbfK86EdM5KbJftG9ieNvuE7PjAEAheezl1fxBBKKZDCnxPzovqnmBX3mnEy_giFlxpBfUm7g0ot-FrszjXCMAcw4PNQchogsmtV8zQ8XZOo2Rlay3YmS9-nK2Z1jEBXckY8C8y2IavccKdbWAOUidl9LsHe0wLA0tC0YcAQH5HF1yfqhXeaUXmVA1tF7vJW6tBMsm443zWLqD3MvCjC6DoUb1O6IMaeSwvS7spYGuleZPr4OvXuWcylIBgHS8TlIwoo4P1zBFAlYOYCGsulS8TBKmLxOWskPS-grktYEBBK-uDxU9pVaKCMWy_l_LV8-r3z2HRajh54V3cEsSiG5CF5_EVeFJzAzQTGd79k-AjLERnGw7kNMs4LWMhPS-00_R3nRt_OPxiVnSY_vNyT3HHpf8Lf7NQnZQQ7jM6d3BBSmIUlvlECPBpaVgP6oc1FKSkSPs-6DGL-DkJW3Xo0WlcJKwl7rIXjCrM0t6n3ioRNkxBOg3grZKqF12fnWOn-jtqr0V0Iw4Lf-3Gh007OcyCIy1-RENp6DXM8JKsg1XwQTo7OfDfyf3ZSDWOLan4L6hrHPXKBKtk0m1fJvJQ9dwEM3jzPWJBilBQDI_09Nr2MCbLzNTGi2wzGMlMt4B8u7g6B5wmRWKDZchS0pSFgP8B6maEEZ8JH-c6p7wk6YfeMEC2Ih-KN9IEUvnsh-b6jj0FwcqtpWKlHBJFWJtGnXMT8rDuYX5Mm_-lAWornFLriTA8I9uu1ZOGiej0pWVgoQVWFawXYkYuoZRW5q4OGBwpiPtZIYAyDoZeAUOu7FAqrTBA2NfYfJr9vsXJOaDiYPDHRgf9IPb4xQHM0YSgpvkCDTERAkFVgQ0lLemlf2qcUXjgmQg2MNuI1NcMCu9A9o8-g15M6Sswsu2uLf8PD13MAUsf2bSudfdKaViZvkMCJ-VgQKsy2y-9J6nybC5tzJ9S3yfnlqMyHkbrxFAUf7NnocSzZcRtuRUpuGZsx20gb8xHIA7aUuwd41zsDvsOUpovILruvtFXnA2_18wbHXFKUGmKPHYYGLsz3rhJNtjs0dZF8EDD2XVmxsow3EHn4CXSQkJ8x3D5sDdyQE74fx_9l-BybhGK0-Ww_qLjHwwArVN6GcDacya-onH823CihgmmZKN3bg_XP0Q1c37IUApEO-R6ywQpAOWGv_re4uecj_1jmbBAxwRcvCNpNSwoGTm8_KSozpV6-vadvp_RC3TDHkH7f97yLxJ7ROIt5J8cQl-9eNJBHtVvWv0H0oe8V42gg4FsXB7_Fv8Ou9YUFWaJYb7FVU3IyWGVNYJyPoT662ImG2kQQHTzoNdHPdqTT_kh421XyfaJINAHA3KzKTcOq_4uNp3hq158xepsHM8HLizQKPI_oM3qvpSMxj-BuMVfkDGTnsX-JLAe3NA8yuFiZXyziuYw6hC4rMLuV5UTNJZnGS-3EEGSXXHCfghBQslnMt4jDj1X9FYwL8cJCmPPC9sEgpCfBdPYZCJUjoxwd2i4Nd2vweECi1KOOoFCdmTcDcp6WmlQxv06XLgfCiyC50yBmqw034Ukq2IsrYFPDsITQIQG_HBAe6k-2dxanLxJGlZK6CPCx2MKGElRlIESSqa99pCuUgzdvs-_ZbG-fjr42LTHtP0hHJy_ngCjrt8IgDmUKI3xEvlXZRnxnp4jkH-7FwZoKkh01DjFYkAscw5BjAlcWFqgQFnqle20OyaUTMaYIvjf-0ZUOpGi_wab0RYW1i5s61xvKyIk_2evZ87LyS57WccbcLy88MJ26kRxPMf9rOcEetd1aZxykk73d7A_pj7zxIrvjeExHyxUrM0XFgLN79kvoEAhyhFdZ_FZItdc98yLjaToxZPORBhTn1w0nj4spz5FjshbItFfVLfGCsAxgxRI88AO2oB8389PNPMe8tA4uMPMC2PFTqK795Hek8Vos_khmzeiXwo1BQaVfwLglOeKhUBAuoVvCyh93vTjhapy14oMAt24rP1eeHnQjee5Lfb_8p3gXOMQ39yxQ0Ts32B-CfxQzbPQrRQtJls8Y6lVDr0oOFz1gMHDWRrzA5z3tqHpj0Cxe3R1luIIQ06DHrv73dswQFCY6mYUsMfumIz3WAO0sa7s8fzbGRpG4zcA5_zxQpkwOEmTbBf8n_7vCRaS3weOMVJBuNSJCiQGBHR2eESoSSbV_ESxcoPGf-Wz_Fam4chWBty66ZX9gMqaAE1zWKAGMEF9zlemaUpKjF_NQJkTSbvh94a6Rtr-WR9QhWFzNxPBPIxItxGb5yNTiGZ6Ie-tQJE2Kyd1SmcfUY5fJnCdItfpnyXL4WSAbSsob9XVg4Op0uBGG4yXL__kme-X8WI0wABAACDV6iueeDk3PptXUV0BSR3PCdB9sa2FWGoPt81rhXS1voD5ApICH0CYlLLFnsnBNNi0fB0f7ZKC8y4286yDEl0NhkKDvq2n9HkwBGA_oiFOcGotvk5QXufiP82pBzLwQOow95Fx6OM7HK_uPVjzxxdawXQgSdHoQiMJwbUK2UYbfr0iYvGr8ERELWRTOOiBcZYsSsNhYHMvwVW5ahDFqpCiW8JJOq6gjlJmZ3cvwVWD7kgLmJXMnnRqtqaYl9Uk0EBEw6CZI8R0Fprd4sn-AM5SIgL6PkVm0AsR9FkBxFO5F6x3-DMWIZnbpEFcOjgpkwAtbmPtesiKe7w_XeKXSYKPfzCM5wyVZ7sq4BZaQSMzOEOgpFp7_W4kjVZuWL4HvPBA0eaJkqCCnO9CvTPynRPisSgqY5zcysrcKLAAHSQ247c1yi8smlgYsFznlptT_2rAD8h2xfxUSv9KDaokZ9LROVtS1pGJumZfwAKuHqEis6B5GAG1uZw8SgmRDB5-_dcAQWOP6jgn5PBB08RKA4xGMxzHTTF0iQgF1HMX4ScdvPmR2tC1g2_z9NYw5VvHewjIQTVUgKhl6WkLiggz4qCItjEQ-sQaFctZo2QgTphAAhAPbVVKGmXydWSPn9-MLyRxMEFd_MFPx0xEKWUtWopZnXoAnB6cuRUlaR7Ex1bd9kSJeRT-zS9vg6SmVVeqqF10HbBydZAp2CPsaAXMzrohNXkjT1tHa5DFsGCWN8Pl96gZ4XU0hcy0-v_g66wmMXmP7XBBUEh8wlJ2tg5_32LC9uz3mUecfSbUnNnM7jzPEBx0MWh0T5W4oXWkjl0JtkiRFaawUveTNuckzEnkGqxWKC3Pfi-4_c19f14CGUzZTVXhAWYKQD15Ldl65r6xU7U87dFAQUOHcEY6KUiQ-xEZztcLU_KDfunv1hTy9IE73SiYpIvhvSeus46KY7z9D_G1Hw7nQFhHgxspVLEjejdXY5Pms0wE_YhQ-bkrCOPXpnJxE194xSi57ykPsPH5TBygVP_fwEFAdqOPwiKKQ4MV-d2G2-omn1DCyqoL0Vc-bvCee7FYytR_RFO2_xikbrBZwnj_buFvANP_K1TtKf04nY7mjKJiSbrTdpywo8PvxNB2JpBD9gkVPuA2oMFvUFHHownN0jBA9yWmiKpQTY_ZqT2TR2bmCTmwL3sZEdPVl0oaBlPiFZbDTLGgF-4fBlm_xZl1OiAhj4KxXwB7w_DqvCS0V34A0o-Su4VjZzaEqO3cTuPCBuJRfnExkN0QMMtx-OMPaumAQSyZ7-x27l3q_-q2ABDt7hOImYxGar-1FLvfxxmv_aAUPWCKHHyEk-TpdjgaLYs3EWC2FD-DNMegViiW_kEhe5hNwBo_JVCn82HCUH14yb3mZwFNe2vAp5WvSVoSdkBCgEELEZw33U_IZSQ5fm0BtguhMiFPbE86oWsZYU3cs3LiC3hW-hEBIIiqIh3zxWg7Z8AcaoK_0hQeGI2DANl22GKyVTRdHgB6Vv2Ggz-KqB3NYkLJ3AirxooP_x_mqVVoIj"}}],"authentication":["#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA"],"assertionMethod":["#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA"],"keyAgreement":["#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA"],"capabilityInvocation":["#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA"],"capabilityDelegation":["#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA"],"service":[{"id":"#TrustchainID","type":"Identity","serviceEndpoint":"https://identity.foundation/ion/trustchain-root-plus-2"},{"id":"#RSSPublicKey","type":"IPFSKey","serviceEndpoint":"QmdPZgcyqHJTiPeGMcAu2AAkZZ1U4KtdQXid1gdJQtpvyU"}]},"didDocumentMetadata":{"canonicalId":"did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q","method":{"updateCommitment":"EiB8B_LS_O3NWo2P8fSuRwS32GODaXoLREZHdqpg6x86yA","published":true,"recoveryCommitment":"EiCy4pW16uB7H-ijA6V6jO6ddWfGCwqNcDSJpdv_USzoRA"},"proof":{"id":"did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A","type":"JsonWebSignature2020","proofValue":"eyJhbGciOiJFUzI1NksifQ.IkVpQV91YUV2QjctR0FyRTlkeERuMk1rclRUa0t0VXN4eGJPc1NESzhwQjl0ZWci.X94wTgzsovLEAXU1CG5M0Gqs6Gu9oHklr4Zn7aEbrdtOI_WCSCrWJuYomkcdeF8X5dV_ApZ6Gh08pPcV2VSClQ"}}}]}"##; pub(crate) const TEST_ROOT_PLUS_2_BUNDLE: &str = r##"{"did_doc":{"@context":["https://www.w3.org/ns/did/v1",{"@base":"did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q"}],"id":"did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q","controller":"did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A","verificationMethod":[{"id":"#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","type":"JsonWebSignature2020","controller":"did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q","publicKeyJwk":{"kty":"EC","crv":"secp256k1","x":"0nnR-pz2EZGfb7E1qfuHhnDR824HhBioxz4E-EBMnM4","y":"rWqDVJ3h16RT1N-Us7H7xRxvbC0UlMMQQgxmXOXd4bY"}},{"id":"#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA","type":"JsonWebSignature2020","controller":"did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q","publicKeyJwk":{"kty":"OKP","crv":"RSSKey2023","x":"EyGvw3AkcUf2TZToBh6pddeaaocmvTuLCSLun_yYJpL7x0W3gVEzeKlj06J5Sej9Duk0W_yGhbOKCahOx16LszwTHVgnH9FjRk0nwOer4yKaKnjTZ2FlZsYI0OI__jhCGP9cbcOEd-1rfvUFu-ghsj6oHfSXDBm0Ekplkgs1IktoicuMsF-bD7I6tZRpP9tqFGqARUqvR2daQN-scwYUNsv5ap3XakBCDvOCBc_rPAwzapY_nuC3L6x60UGBAPtUBANdaMhAU0gxd-3JMjcSjFgwzAhw5Eorr7bIp1_od6OfBRYu3sIkij5Es6RDBLghUAx2Z3dznniJRh5Xlx_8zn4SYw_xhV1X04vY5U4O7-7veKMqKxzzoGOR7O137gSTtBk66ISXfE0k6LLsZK0Qkzi0B6YQ0Xo86d-COFNhRWQ_Lq3SCSiOaJ4lFP5_RVlHzgUXm6XY1X0jrkVPWdT42VxGjFvy_KX9f50dOkdPJTax8bGv1nEpDm-55UN8nrIzsRODaxMBooRL1y4OxyW1tpHaEdsoHvsZrLzM5g7FB2ah-62TCGkPcG3Yx84MPp50eRPIlj2omMFxMpnAZKBSRMGtk35A6xAZUI6KTYGfNI-IuWKdk0UOn6xL8W3EwMTxRgx1v7iklbgxKuCBoOeAK7FhoOVzL5YnUCHb1NUwAxDs9I5pNmrvaXsDDLKLIoz50hRAdnK92whifFoWoJOOJbQTb9sx43zmB1J7G_T28MG6UetI4dZljoNfWpXePl3vNwW979nNg7GU3N_V8ZE_slRmUv-rAw9jD0w9KXVCuZuwGIKoJ2Co8qjZxnhZUtmi3wFJin73V5BC684ebh40fnA9z-H1Kwa3ItX_mQSVYeMV-_1fydNULsdhlEnpwI5XNQ25LGqMNb4v-YRBXLSmN5CituV9rPXg5ZzQvy8VVE9qxWnicCxz2TzFrxFOOIhNTxf-YQT5Re5HJAvdy7Y9szo-i_PgskFdVm4UxMgH9ddrFUhDPNmVtVY8PoXlMzuU6gKR-1np9J6FBttHOIPu7LFFdO0Vd_Y3-Dl5mdBXFcP1Do1GN7ojcuRUB4rmB__upRAQQsqCApGurtGP1zgtMQm6ozF0gt_JpoXgvZEFK5kkm92vpedrSfDPBBn5NPIgmQgKSYfvmWRmADyr2J9bc6EjJr1-YD7QR1r2g_eGRBE1S6dexWceWTq-RktXQYOSJBnKLSkbqJniuoA70BMkjU4Jsj1EJB7oxE41RRMchA4BRlClSi31ga0T_bk31rNTLQNLGSrBrh0x2nlG8IZUZLB4fIKKweFD9pL1qhLMM-SQl3YR4-v2wxjlMXTrEDjz2xdwJsQhhzM5trtqhVdxfgBwB_ZBtU9KJqYvkB_3BhY3kYQSGDLhyCHbjyIVYl7saQGkTz_owGfj8tD3gU9oJlZHDyjf4p9AObfF4YXKjVBpPrPgwgNd-G4LAgUOn4DAVwGmGBjQaNWiLet4g4lRsLS3LkM1az1w_KyYCX_k9bptp4qLgwV6HqbLx1V5WkmubxLMpHlbV0tZFLzwThEaKpqNyz7M5qIyDvaSbTFtQ9feXhRHU7VN1MgH2AQmQzHiygXHs5qafdGSsKoMm6c_6R2-NXl3asM1TSUmD82yKonGYhSHHy60KvB4M2rVTKRENxR93u7gaYr_4cqFY9LlcqGUMzxmm6TadfSHz3rSj53C8c3Z3U9x9ftbKGOZeybdWhYbRGyES_HzmlXV5MFY5qHiE6INi_ao7Xxm8VRi5rdaHlVDWfBb8gJENbUHDDcsKQfae-4j_vXmvq4s_9L5It5kVLCT9f5NEf7jsxSP3mg9hqgwdY96ob73GsHO3HRoQARhPUt-2o7i1JzScqRH38AeDr9XnxC2Qu4LT6ffOmMKzA3qngyxKmkvyKmIl3_eEhDxpdTSf2ba6EGOD2GuzvGv2a_P9QHw52mvtEoCLNJAslzsxwxbLSnLIOkbJca1Ew26womAjSgnNwUvPCkz4lmSNTbyF63wvmNJJeD0UgkBTb2MxDw_39ukWvH0mOSJegpmENWzMhvKvxxMgB5Y1VY6Hq06V9mcg4iD0AdI-dM646yU8iLfMAAkB-EvwUUMXRE3KGU9Kx6dqhsSCrow4QDpzk0B4FCATLwawfGc1_rxQyumhF9nagl8jP1ITcLi-hlUyrOsKfSK_s3WKTw4j9iBoBWCzHrX1YC_2UTnq5XIdbY9tT4NajRzqwKLV3aYWRnqXLg_-l5k0H2GmwmRnm4ZqU-9YuAy8MQR5CM93H1gxE7oL_IWIyH_tCXrVH4hRhjd7GrWcA90s1AFpCHhBZs72ORxG_Rh8VcJpB5cTpbQfk1ESme0-UTXoSnuLPfNIQb6I6fwFkIvBx9YL7gxaVmjHMgk9BLR89iwuo3VsEsAs4ktbFfZ70l821y6q_xmOBPF-BxJzlVuHMq9hfyYVA-1ka8tBBeEy8NJ1PlYBMiVjHoKWMfqDKo0ONNv1Il_ThirUq-MM4pc0ENOqwCYkomNBFfFHdbS8L1Y5yIruufFxRbRPt6xC1TnDtq3K7JCpRjsTqv_1_u81WA4UIlW49NaruM-2lPlL6P7rWtBqG4axy6U9WYqom7aXBW0cbg31hY39xZb49G_SfSYewGr_pelurFdTag1R3ZL5VuDTggqErrppxKIBYHQP7M_reJ8fQf4JcXOmMkUOap1K7QJvvENxlQ_RQRj10d-t9spgDv5gki7uMDSA3fp4q4gf3HxZhYwPaImQ9J44zCCLUdo5dyhHsyd9neEeBniNZk5LDZRfX66ERlj49CO2dHmHLe-YQACZnMQDDug7LF0il3QHinPD-nedAAxpjfUus9Ay9vRx6nB3fHr-_9C76qx_NjCehMZHlsAOgZGU-yjdwY2uu8lvnb8dvmCbkIBYn4S_aWJ0qIOEjfWuADwWO9BXI5uzQZ0EhKuhALABMhOIi4pmnHqCE0Durvn9RaPiFz6ZKFhW2d85ZAkks_-ARI0phaKzggmB4E6k5EV3cLqkI63Oiiq21QY0VCvc0LuNoAVYzG8s4bx3udSSORrRJm2fOdURg3wtPlFq21m_7y8D09xKpHkXgEbuDJV3hWk52u0Rxv1MTY2V2_LkHIDF6my-MZLQQh0dQYnUjDfvQ3bTqj6UE4MZ07R6UZzl3Vjw53lM2x4gI17Trma17Ag6Yg6XiQA7QqgXKWy3jG6AuBLjuYRPeYo18lJm00D1D_Z_C--D6zMJKr5ohYrTi4ea_dh3CI82xBNwjeTAd95r6X0wzC3xodd7FSWJMCgt0MF6pz-MEL_jNi6sK9mIn05U4icLZLjBwl2lObaoiYxpyWEpnuMGy8J7dM1Z_aRpYt3J-Zw7i3Yf4JI2JV9u1Mo-ywQyXgRcRBhK3emrFT2fxH8SqkKwJCWn7frvbukOzSQiKD8RFuXA-SWK60mJ3erCRnka-xkGg3AiBxxeE8Prk8EGzLcB1UDRGQ_x1PXmMNtdBK65dtv1b0jGTM_uSHFndWXOrFALwi66JGyIca2WnCfQRQDR5EPyD2d2Naecbj_jMwFUsbYCxGTc76n46c1pI_QH1rxDBQ7j1Tj_rcQz6Bk7DMTNnlTFhJn2h7yVnoRPenlNCWZWZPRpr4vnvS6Ii30os5W2QaGHI_TqhhaXRFU8Z7K4PUUUVEv6u3KIZpvcuVxAbcx-ppLVkj-r2vM061Nx9aXEBFd2whV1Tw2rjf-6fm10N7U3ssLGC6sfHRpSVcsENk-ZjuYH7sY-zmN7Hf8zOYHIAZDUr1rjCgG2yCujbdOPFtPs4QKC_cFSzbpOjRmJ-urzi7duH_vH3_TBhMzM4jowgM70l1LoB9sjQ68wzlaAs74T04IroWMULoZOdaeIS54ugR79EhgqvukrIDLEoCekAY7jAs-iNW14YRPrtdul8zVUjLd4I_X3efx-IX7HvR4RUp-6lqMSN46IfvlScl0qBY_SBgCpdEw66SRo1OAIAuTy7VWX_mbvLtgZPPMkaVheFwYwBZnBLKQKyJHrNrKRQ5GdrSnJP89jdh-o6VEqG_whEec3cB1LwXipXb6v1vi-7jxU4kpU_BTMtEChb21tRhmfKGiQxHbOTRJbHVoQJ4NFlS14bTYAEuJm6yXnIW-GOVCLvlHShp5jeWc_9vvvBZnk4C7bDxY80GxadNmsKy_-AcEFN_QI9pt6lckDeTOQxgVz6Anz58RIkvJ1oPL8A5FZOl4iYuQGDAqTP6Yo-SdHbuVOuV3aM9K3L6RMgj5Z9z517O3oqsmthQdy5xtxhalD2bjV4fNsQrsXIGuNa4nAnFtfsi0uN4ahR1_YYVuQgfEQLOGSzJnw-bQ7m8tOxlDOP4MsXg6BFSBvo0LPwieTdNbZR_N4FueA59bt73HfANTd-xz6ycnZNRNO9DbxBRwXJnQogguwZQdLLLuZjqoglKwi3gmMHvCR-3QngZYQw46vAkTUuYfdG0OgaYuAAqtsEvJRaBVSud7q6pgMqM5UbG9eWv20h-bMQeBEpIuVG08HOEc9TeUzDOoE87PzBkfBqVu_s1tyItQQ-DqSvfCQBobT1pYeVsuyJSGXuaF5MXooxYfRpsAuysjWDKDNxAarmMCpioPCo5ebD0elYa6S1KV52RN15vaAZLPqNRiFkek3oy_M8C9Fi2nLzXG1Bjn_JlKzni0I3pofwFNE2ZJnoLSVpLwVLQUzzCB5GoS5P5C1DcPDxpjAr7e8pWb0QAyyIuz1EvSssczBargovo8iNxthV_MgoN4UGY3RtkDRyw2DPcFdji7AYXw_q3xlxXsWEZMfjTlkG0FfwSTHbhrL-BIXXw1u88y-w5SvjBBwk2wW0SjPVgm-qq8yonWXhnVfu4xRLMY7qNRltkzyB5pQ44rJ0iFr6tXtKus3rUTx2PbQOPNCYJynCWQnA8anAlOiTmIJV8G-MYkP3hH3g-VZSnWE8gQhbvXy9OY4YtyqX96TXRGuHNuZBDEHiPmNAvKkfgVdGE1xrxPnfZ5eN2RQWXAf5a8xgISY1bXxlt1prbFSiHTMLnikDpYNy95JBQnPEqdIYRhgzh29L_RQpIM2ItE6rPrJCl-NL0Mo3YZNdFepgL-5uOjFilpmO_EfAc06pm5sP-g6S3vOx8I9j4JrOnhygXvZx4Mr2D8-R_7s2F5QOYKCpcYmhKSqaPbdAX-q6oNQQ3fesRtmDJIVbBmioMmu5k3C8hh_L2RNAe6ItXT7XVCo-QFQ8fiUIOMWASrYHiy8qsbX4kKQJ98v070GnqCMpKVtB9522SHxJWv4h6Kpsmadh9WjAmzItl4tRV763mNcLeidWzlJFUcfZIVm9OrWbHinBUjKFnoeexpecTm2ncrzpUkMmJghWKv9hUzk6wGkQhsps-94GvQJT2ou4T5xLpeATQ3oenwez9tEwxQ07tB7FHEiIBpA4PFExNwdv8sxaEe2Zaoakh1iEjIbd4uBcEAd_E8eE3VSEPvB2_zT8nek2I9pcHEIHA52Q2_j979f-vAyJci99RN1Va8nvk3TyMz_g6OCknUZcqkhXK3lqigvhkUBl-IxjWqagdTwPfwGPtwV3JT71CZDfBWujVMLPGB_gT_dhsWlIN-sC_yiWL_thQrkgKFPqXPwQKCyz8r_iv4f8NnJIh3W6_hUURFsnu0NpVAlhi7iOU-B0cqk1NHN9BgNbT_zU2aVBEFBrlQetG5pyxxgyDSvrz-igEzZ9oqa7-EIgNv8P-0T0IUrlCIQSfPsiAUsbExwg5JwdgdQ_gD9HUt4U2Npk03XtaAySY1IXJCXeJLp0OIcc8hFeaiPMMv7Caif9RsIxjwnikwLFGtpNy70Ed6CkTMtxBR4uShDzbSz7Hk90gu5-jV5WGysOA9AbW24iqgfgCKjrjgfrod_MNG939PdD9KOV0x3MqbZJmBLB7jKCINC2ilgH3Ez4crHFZJEkuJ_Qq-KDXW7l7hjHUG_debtAu6qI1edYP09UkgmQtnZgLcGAWUhDxWhdf4XYOHfqXxfhiVu8tF-ly7iqWkmRCqhRGV5NmzUWuwvQ8-Jlh4kRa7nhpwb7ivyXiDubq85_tKuha0qKFzzz8gFuiefICHX_Uy3xM8m6Gy3KfYirumMAkuB5-IY7Dgr6IZK8YXGLZb3QEXmOjuwp8Rmm-bMnCXehgCJZplNtcWi7eQxsP4y0IoEUsmmC5Y1as1sAs8-R9XlxBfP3hdGWbOupZfS6FmMRiGD9HoWesUSVtRs_tgOUPPVav2HRIK2CLYBRwgI1NaeRcpnO8cOye4UgRm_UF36pi3hJPfIdCnhxGeOH5J0r9zYEnTDs18YsIQedQOJ9jvGBLvDi8dJ3NRzof0hk9riVtSPV7H2EKhkEL67E5pccehsmZnha0ewYbZdgEstjzjwQ6qkZRmFLOBdP11yCDzgs3eDmnk0Ztewl22-WhhpumCfNgux5OEtcSu6hcC_gtsXQgTm4QV09fFZJAH8tyfFildcaycx0w6zG_tT47jBYIwVyEI-Mvv08qYw3ZN6558VgacYehFWake3ahdjDxZ8bO_tBtLMrFXmjRpibEIYbWZW2OPgBv-4-Z_EPXtLrDpJxYjD8bUxNgxwyqxAlyqZe0FUQVo1RTWV9hzvj4GcOG7wC-_t9aEEv5h9hg3sQXBxwKwIulPSsJlAeW3dygypohfIMKiUdjDERwhgvPsvB_vsJIaVpN3SJVfNWvMEFAIRxl0o0b4upYbISICcxav7YjxARlPcV_nqG6Lnj9-6MtHOzvmwMWpcM0Y_FFro9TqKAj8TkAiGaEMYyJ8Z5EMAsGd32HwMhmdeJbA9TxNpC8CIpeNlU0H9JeSDR3bl76oGAPDIc7bDmfKjcCL_8rZamAaZucmCI4Fkkjaqyl_k0TOHrxrc8EcYzbICfu2Xp9j5Bl_w7GErvNIbMsbJejezsJxt6CR71oex_OaL_DyxGJE6bOaWZFwF3WqhVWMoMEuRwy4Z11DIsqZ2pbxyArURVFG3mIHnBJ7ffjxYbofuuuw9Ce3S0W9AwEvXRlquPr3-wLesE-Y09JL2x63dPrsfx88itwaKSyGuJyvqpTu8NwpAR8d0bU6nXG38O2ysH6-xwvDGoeApjhGaTD71tv5hYcJj1X2M-GeWFi74NjG-PYBkamWVPk8v2uimVuB402YMgUAe5RtZcKVUfHczIcj7IWreTJr8JCLl4N_X48ji2KDuBuuaBRBUYdjkl8ltWE-AQzatqUi3DF2ZDEjEarQrk8K6QDaHNbMAEQwqxIcKVB7rX6pwR4EA2xN2VYmCskYAReAbKYyzbFKgx-_kbylwjO1CMcDTdhKYHnfEznxeaxzjwopfWQR5JQ_y_4OExcY6gh_FHXXyMOQdyzdcNMPFOZDvKAf4PiXg6BV6VVbvlssgImhEbhyfKlwhmbHkrD90BVSZOfwp0m_zd_xOfwSYckSwo8ef1K6DILkCmiUSc9wiCBBGHF8ex_0u3nepPICWg30NqJPii7moRYlXNi2hKgTB2Cy1njuP9pNFSD-8cOxrrAoAz6SaxdS4QqxjykSaRko3FibccYcSE_fkx7_WWBSW_1GOKTqQltkzHWMqTbu3wEjBAbnQjYGEWn8aTNzsAh1pezmZurCOdi9uL-cjIVavKPn23HhHGfS88f3pRdohcdlszyc74acnD6VgT0VnArfeYPNBWcliVDnCE3qYSvter4l5Fe4rH1qDISEq2ni1-uxNRJx6Ck3-5bWSZxHAgvc_2gC2O5qc9TU-akXvNSqLmNtKmO2FGFtBltwgyLc8bVWAJrNxuWQVCUxXlfSkxaGXtN18lGJX-SvmRn5IsqfhUitHzJjEASiI_YOVY9OoGEkK1a532FFGdO00mS07BQCPV0w_gldLncCOgt8VPaB5d5SjOF0_whIcVAIY95y5MrZEJWcbES4zg_jdGb5SRLlr9PENPbne9VYK4_ju-MCFNo0uWibQJzJcpaKU2rZ9sAsT2goR_lu-aLGCdeimhRmual5ISX_tyMRikPCDidsweqUeRzPcriSIRDKLcQfzA3P9Lt_Mo0ql-l1EX7TcwLgCsISBJ39jyhHyPvNPbBAFAlrlF9uRhz_ATonpUwgZrQHSlpsy6Mzh-O8f57HKQTRT0VigvfIeC3J1TR4EzLkHUdC7QF4JNlprKFQl-HUh9VIOpwXfQ7VwhbxUw-MThAn8fnFAKqd8S-4S76Yn4Ns3B0FA0wlDWp9AvfCSlm50bQHUgj8FEtwz8279OoIhBEIMnA_rHNwA1gPMSAl8aU4RO4L9wTbhwVEs32i77O1pQS93ZeNwOwXXoquAAVFZwusOXz2C3jxzKzB6IdrA9LE7-ALHDvmxB-y9KUe-RgCfFgjh9EE7rdwftpCOMj30we1IOtQ1XyFSwpbIK-y6e6itkyx73nB8UicYQEQHDnl2UPtxm3TLUe5bx_E0sisng5ZV2ISypN4_CiyoAbUPCapdHnGLh5VJtaPPq0NGIVA88MkPxnJC_dTfsZKzNVDywA36U6dGzcSH16QoTfJ-ZcUJhHAKJHizKtLpdxpNKlSugnNW0P0XwgrRYAehBBqJAWrmDc2vll-f5KYy6AFEWfIub9SODwuu3j3yfdoVAjpi6Tvm_e_w18ZBYKjtRrAAg38eTrwQwdDDovzBO6t7xmJkqOxsCFl0tz0WB7YxhVMfhC6qv0ojnXM4XrhX482Ew0yMUB9Ql2_2d7u9-aM7VztBqRf9dtPj0Fc1WdfiMD1d72U2D5NukpfdO0k74QL4xFcEWgq0qAPT1Xd35HaQhe9KfUYx0d7KtbBb1BrpQ3zZWS_ThLtfTHOvGZRQH9bQQyFkx7r9Lnal_GmnKw_w-Y5ecOTXwxvtB_XQNOo2i02MTPLpYHXMCWCFB6kHee4fhJVL4yQnaac8WOYkNDZeHf7y15M6Ezs0ieyusNjY-nfeAuXS1kJ_lf-qI-1xCpx4wmOy-W4Y4Xbr5YWS8Pe17115uh3ZGN9n88HuWj_fzZ0BcrgsT4p5LvSm9lntyD3oQ8pX17phhk3xqItrnJYAq8MfnLgifMDl6XucGJj1rhsvVGfr_ccjSHxohBb0HWL6g16xEvKsXnQe-PHn8Djtpc9doxqWWC1QeFnjIFJ38TnZd2v6S9irKu2D-YTw_9TvgRZTHMLgHH7pdFo2P_-mrKP74-OvYkn0O4aUVAZ6-bCXKIZ4ZzFgt-aO6l6vyUUfhcVrQKcnRdrZ4_GYfiRdxlBL1rvcZAkVpH-iitAdQ4N0xFHFL3MO3MH_EepQXLXSgciWBbbc9lzJnd4GkCRT-uH1SKKtquXZIO28ERVLB5yD9xkl6-ch9qTYNnNcBDNSAJQeFBwCHB5xZoyuYfN9p5v40vfSDAoJU9A_3_kaYMyUBVaxQWnKjZrrA5hWy2fjRUnVpeX7PDyAyb6eZDt7dKlkWGQxvhDXRFeN9yjohquhDj9OSS0JlHsPLobIYEPThAwpAYAEH9aspydpQDzH5LdB8aSUzTmFvdt87KW_OjCX2bAvPUj7a8bhfrITHuCUwOl_hNSIaxUX9EuHEifvRKi_KnQRZvkTyN6Ji93jcr1wYk2FOjZEVdUfC_lI-xzuQDSVWUUl6URvL2tfzx5FxqScbNiq3xnIqLrNONk-p4hi1QvPbgiYvXevv6-KgoCOBN5b7E0KUoVcBh8GBPzCeP2EZwA6C9k8u55Ul0Y6dohgm5HS8NQfXCSTt7QQgchGBOyOP96JR_uRbyLPJ18KaFr9QTxkQrxpuks_tWBdd9QD7GN2MU26S9veV2mrWHNXBiKY7NNZjYSkfNyzvjsg3VCwvxU9kzvkozJ_hQnkOnEmlI8bu34cFvYy1Ms4X5fLwaFLMmG3SnAIwBsCz3HxzKU05NBHikuB3B79BGskfQK_Fe-rkahNqJgG2ya6xgeIBivC2iuCuVjM1xcVN3jM0VuwQOCIVwjPpyDgWwjm5rpjX7LfEzwjyXynX5OR8PVugx7bAFwv0UNcbkBNLadJmL5hZfeXHzgPM5u8M1_PEpwxRddCDLbmbY-Y1naQwfaKRQp_c6KwJtT3IzkOJlaYsUlEeoLQKfQI-OFr7Jy6N9-tP3x_0OpecilN6J7UQLOTQEIeygISrIiIkSQgL8m7YCl7cRejrq3kF9UutkU2OIJFseVIFtIKZL92vc3WSxj6A8NkX-yqQ9LCFljVw_acJ9tUT7tNyOF7mFKBQJPa92WpaOGgzq4OCV2nJs4GFYjXgw7uE2NjQ2i9_auhXryGm3uD3G29NjUQ6Lkingi5trDZLCzoFKtQ_-2tWnf6sC4HBlShllmYDfCCorSX3Qc9WvEwxLbRvNX0CgPCEoxIKHAE9UzN9sfWZLD6BCXAtERDgNqc458B3xIrpXpk-hmIe-Res9HtuS43LqebcFiHjjKKiBuUEBCSxSEYQPYdEII9QMsBsp9IoCOKL7y6m5EgCfQzA7hiWLlE_Xrppv625MGLzebKWzu8CP1mOPWTp4FYwaXl6sm0rgbAoR5XtNLcBazT83ji0Qhc39dVR0nFyvdSe9L-EFw6dbYUPPbQDh0hQVzwnXZYFi4wgX8iFfyvfj1cAGrQNfx2yekQfLm-vhGK_sIlCRVZf2bjS6rwAbVIhhPFuTsQ5EaYCc3QbvJg-slvxMGfr3gpUkMV24EE0dCemwKRyRyf9zH-oswETPMyAFTQmlx715Ao-RESnFuc1Ebl13oTofrWpye9ZaqqsGko3Cimdifa716i5Gkq2FJNQRRRrp979uFgzdwm2AL3Wa_5I1t4aHY0hFNXzKU5u7gNmtiTDyLSOIWLGfd44msxBYFSE9YqSdU-7KpEtOLQRppx3FR1TQooT35XW13oPp37k91Uv2j8wLJPAid7msh1AUWmpGiq9vhair7EUlZhnjNIEvhlTr6sIwFzsJPRl9Dy838w_UqVXhKcA2wJpTCjgRWXL8R8b6L7Qs2v0H554fmrK3qcTm1BgmPf6d0aeO9wsgj_cSO2gI6HgI4zL6PUQTsMTzhIY8pN8MW1jPWVa89yWjGjaanxKT6WyzdkCGj6NcG3Yh5UoKGeehwa_5FQwggBfzXYMIAK3swXYvK1bVz_68c3eLtW96nYc1mnOw0QmcuQ7ajBPpwPVqQwH1iLRS3nEWbxznVbgvcdHS1Sv8LcVU8htWp9JheVP2OCiGQPFFScImnsLDC5WZxJNohrxFO6HHJ_6T3py6zz491E_zWqb0B89YapQO7LKc_D3pU7_3-ug2A-BmtjReN5-I0QAaNX86gN5o-LNW8yl7DmVU8rDBHQBV7vZ4uijVQhDvpifKk5mqhztr7B82gamJD6gUucjs6nA9V8i9496A3dTMHdtEjeEIE5zkvtbLe44WyaDxa5KiwZikk137DL-hp9w5b2-ZjwrGqcNJrYwpTQAjHigL12EWMHKEnPEsSXqmYujeWGfB2M9_VDmSgf3J-XAZroxarSzyVuead1XNLHtLqQgT0Prh-PS1lDJ8jH5y4_JzNS6lN78BaEi-rBl-hyhXqi7ZEzGEyZVB-H9rkmCE1jnuQsHj_iWUkZFeE5wJRemTSNTxF_GqZrFTkTD68qxdtMg7nWns8pXHaqDxpWAFaONRj8JdfPCeJhQ3W9qIdugEHXFlYYtZLEuXAlBGkHQQlnL2XeZ5aYE7xDC2JYQRJBj8c5fYfusrnqBgsz4EIO5ewfwmX-OAJg2d9Pm0UVxGrXtTW1H277sVslv-2FcU32cZwwls4YthQ6fyoIVLzJTyMOYJUrpFW32r5tG425wn_Q8ezmTs90EKuVrvVo8w92JL6MDKA-orDvhvQ3beb9l7Sgc5yy9cb90rjD-lyQBgcDfJ0xHFnhjnz4S8t0yga42xeRI3r_mXd0NvRzTUHkedNMtRAdU-W382jaFGRBxXL_4YziKyewh_nGh6BlW9EQ83Qf0oSwb43IN4k6GmK6KKvwr_KiERaBougue7YpwtYyqCrEoMiEEMn-Sog4CeLzg6IuYx4awivB7VYGGGwU6Bwc2IkZkKUFxVhJK63cAwQX5Gcve_j_-WcRRGlUhI9W4RvFhQFpl0YfC3cLUzRQZfV_fWH2MIwrJm6y4VCHhnvx8O87qetR0kM7el6lY4Nrk5bNtCdBeoyy_C1sz--DjsmM-z9i9IR8PqMCZcX3gBry0Sn_js4Ka0cXPsKpM-GpR6L0CLxge1FdKNDSFUOacsiEzh3-LTu-rUUYglWzQShuc8_dtZrIEvVocirTKZ3gaImQ1M1EylwXITBxzCUW19Io1X1mxKiFpXKHtzK7AvEs0kdicMBNl1HsKSn8OH3jxwLSHI4DwFIGYBxCQ0vvG3NN5ZZ_c4OnSfQ-nojlgmeCjMGykcA9E__NgeddsOdWxnG3fVQFIiMzoJ1AtYnxHoPRbtVZdyWB3dX1L9AKxlFep77w6KS48z70KzKseRnKLa6OCPZwfXgP5kEKA7FcKwpwIaMPNxCOedtULYeDhclbLeDtjK8LA2q7a8elVyK6YRvseXaZ4-nnd7iLYLZNOv807ZLaYGm51X7aFt0YRTimfsQIGztdkY9aakmyH_XQkqPmlNa75aE4xf8FqLjwa3AZ9PcIS8EpwX_Vw_pFA0NJcvJxCBgY4Iz98FxssnBRC9dJ1aAn4Kd8lgWvHIXS974MFCCGhfI8RRVDl4S0QO7W6vrGTIZB1ngY6VHZQ1JG9NJOGtomR_8RNH98FwcPzVNUzy9AhGeKBS3WECJCxk_gKjcGB-rBogS4EU0BVCfxzCoTMJF51ufpG1k4eWlEiEpOqUYgUWAN_3XYWNhphToFLg-h1xmQWWUBiVS6tV-XVvEOgKCKp_b8dMJ_99civ11moW0s3XQpzbxo02gCBR9LQYl2OPBcoRr1bVQfmS3sljBMCgtj5NodsMpz-rIZtgbzdchFe-RE6QK4qaMwAUY0oldGd7nIW9V1C3hnGg0kekWG3JKlxMhIB3IbDAVQ4jRJ90_JbLVaj8v0cNmhAwT0QwIwuTJJYFDGM1fYrocL0UKFsHEdPGZQFnfGAeFoMQwUt3I6zpmXbIqWA0VpRYwiUwTTRNTSsH1_eX-LWUnbXBsOmr6X38Sf9SQD2giVwmji2KBw4GSfRjUsbae5gpgZZbTcXH2ZF4FK79B7kM3RW1yKHcMrT3jXyZKjfEee008n6CJraHTc2sBDtV85wr-TQgic1VgACOfee02nwbPgPGhlUsN1e1cBwTGCJiIthec58AQtsEGIsqpTwh0axbKUmUaOj7zuUjDTg0imRCdYb_iMh8ya-YUncdYTabPkBJYlnbHzCB7aXmq42akqBQTTTgVgUsrRy22Q9gn7CkGltOZRbiPZ4Oa6Uzu-CYOsK-0JcD1xUgtTd9icWNNbAg5DCHh8FhryzVmRa5VUkC81OQryM3CgKdyzyw4xSH3qw2HcCMu7VHbHYhvVEXOQQtSaedW6w1shQMbPRKt0Bf_n3DTiyvSsfAgZmA3lrhQhRzd710dzxxljzkbfYEl3Q3SKg2CNM4Pu8SzAcJj9M4WubFMqDirRgVIMgL4xthq9u4qvIGxTERgAu1h7xhUcA9f0IvKiPzBkfExW_QIYR8c9kewkGILCplgqOHbvNBtqK5uXJrnscBUm-Su8yfc3gTiWWlsb1KBm2qwj6uXOBWQ-u4xyatyltsx8AJlshq-YB-K5oJuvlwCXkeXkU3hqRM4SRwLng3VyhdL0Jr5HUv_M1ENVemAJCR1W_6IXWxbChAYiRUFVnGQMCf2Jx46eQo1sNMaO-1r1LdtVSJo4ZELftKu2X0BMQC-l9iQ5EfDT2VEPZvl5JszWbqWIlkr_RY4jwbY_OeQCkPaMxE0eywBeG5zjdTYzmPLm0YjmK5J-_7tjM_678RIQ8qyuFPuNRGFUClznKIZ-T7SYMtFie6XAQ6j3q12Mh4-zEomU1jIOcy2EzZzTVgrpmqVtZUB9wzPIsNtq27VtLz231dh2i2fAfAZHdvIy_7XQsY7-JWltkQ-fY41Dw9QOIhDb_KJHhFNH2xa3g3NGh1WxZIiJNfPXXH2pMA0xU_FnJF0uPEr2u0rEcTWqTsDgHk4krHglASUYsJYneG_YgBCHWWrGXWzbQNGYsZryPJeXNcY3hw0wO49CxV7gb56BbUNBvNIfgS6SogajoeoPTkPQAICjtAVhnrgXyIFnQ38zu9Cwjwqxy10jt04Gwm1Q6xAh_CNQwcLgtJ7elaM7zi9uEGFskPfZHF35EOhpMwR6wBoPSv0ESs8PX1_WKhYSakFyW7SewR86-W3aCDR6xznTr57lJB7BnDb9_fF6rjfysDLSjofLGwjD8qC43OlMNZB9m868hgZoCUKvSnTpVW0B2NcAoM8lgXDox6cxZPtDsW65C2fMFUmt8yqLg9MOB9QRvr8jQVvgQ75GPADaHTVbcDukGOlpWsE8qHc0y8sbWnBRwGu4lUVpyOe3R-q2Y9DVCPonQoeUt3r6EfyIPeid7GaY1S-jCTuj5GlZA4Ridz6yYYZmGXzju_OqZL9TpH14-DvywWaBu8ZUqvz9kVamnK9P_M-jTDn6iz2zy37xyEGtzWT5Mv82avznCG1l0kSoG7HPg2kdA2ngIutv3-sn-D4_H3_Wzni52iLO-5CdMjEHyo8IRF2gsHDwR0mkF5uGdXv8RD_b5KZtgMy91QfiU-h1B1OTDWxxhfSPDO00EtPBW3UPQhkMJY2_MdHzKiG6i28PRjUTIYDcQjc1RrUZFuBmD6S679gKEzKw25fKmSbk6MBIhBfV1Q0h9uX9RauUq8yFRB7mV2EQgMRzrSZd0LVqNtBcOCU7TdrpzJzk0pZkfmjIVGOAJ37T234ICX4_M28IgaNiluXWNYvW8j7k_nTy6-8uRVw30AJnkQRswmxllkn8sE8pfxq2ACMG6LhiwkUeRJU7QYz8GMhtn1HcppGw27GGLZDbd1fHQ-X8EyC_pEx6wcSKdLWOZJ-TOqBWCDHZAJJ44G9MQ_eYCZKj78LA5pooQ1OQJeno7YefrhaY7gsJEY9LqHaDBBrDYPefTlMYgHPkHKxgkT6QtpbAHN81lB5uiiN-o2HPIgI45ODYY8pmvk7SY5BVsu-lJ0K3KZJOhOsfQsoK9CWB37yZj73eFNgWO9Wd5qmmiRVbUyBrjWSXc_dLnbEAKxB08xoITcG4hDIO1TSbTIF1QsBKXbyH11lwKM9Gr3bGckU_ni5H49T8MeAx2Cce-oeZ26dj5jDGQwwwgRbDf_9eKjzVzH0MtA32QPr-ZDqwIPJlpSAIswVKI7W6-TVHeKdYjBufEUoVhjsJ2kZLNnwsgUPySarkA7PjTLxcS7L5eXTIzBWpcSqQfY6eII492F_RPgaAzRnqRW7FA0lvNcCblQJoRK80DLGM_oZajzqytR-ZgfJvWQXY5UAcW0ywx1hVklrP5H9hxJBM6LujBC-bfK2gatWTUNoo7ciIWk8WPKZf9jCnGd2s9YQhwqJfIoYWLYZj2obHw-WfedxSpLOl72ucoXM_UvtvSjnnX18plcNrQ5lkO4f23N0gh_oZhdwYeyeb1N-KADIKIdY3_6tj1AFOqN_vXTuFtEAilg5YpHC5akZeMvfOGunAVza3qucicsRDEYutxcXggArT_nUZa_j9X5lp9EItKRVyGjBvRa8VKDwoHe0Qq9JYaDk2zA0Gqz2BsXKjxS5eArOJ4t-el3UdlFrsrGz0IIM53LsVDnYFGo7G8sQWzxQHD3LqVKhumuL4q0I6gBmOZBhAzzAb-j3dE8MFDXLKOzpMXj4yY_f1BqaSVhA2LxC9FXh8xlYclwHgweVkA98obGvKfW4iMNKJza4tQ5A1QDFPDwcsF1biEPK0svQmSnHNvjhOBM_hRoZK1YD_RXmIYPWzJnULt_2Nq4Fus7QlP0m4I7qSxDSUe3Ly_RtLefBaV3G7dUa62RQJfXVKgbGQTy_64COJ89TVWD5LIEPW_LRrYvSjVlsMD7LPexlQnh6J4g3zq0uRHxcWa1bDQDUQYrQp4Ud_6qc7d7FoQqYbQgib1M_MIbRyJezKZJFNXN8aZWzAkSjR6Luk43uWgogzv_PLON19AnvbC-eLg3fE4aUvJAueCiTQGGFkBb1O2IW1kc4i8wN_II3s1TkjQ6KSvre1kN4YMOTk73lEcC6L3NcgOd-o0tPDO2O9E6I8FG4yCWmnFPjPO1FFmEnjAUSgwhEs4KdKbQwRphNPnZQ6dWsjKPVM5AfmEiLx8drX7C2NFidylmW1dpC6T9L7Qcvd2YbocFGnNv3j4ztPjt-9Z2Y4fZq-02HVNkkuOO5AB4TdPTftjgiGipnbMaBmgBNMwbxkzHuWZ-avaQfSifAvfuePdugEVjmjhcS0NQuh0_hZ-K8m0-41A-EqQ6kzgfYTwKuQ8JdIWawuYoM1Q0G1bJGpwQxG9DPDB8c6y-WupSOZ8c5l2pWsRVw7UJ47hHhFIsoDHFHVDBT9N85Y2SIRbttX2pcnKj3nw7aj6ZcTRwpNPN-Qvu8YMMjMUVV0QoIn1CEyhim0x7jqidBvcSHLamlTSqYvzDfI4l9fSA8m4Yar_VZSMYMxls278D2sxVIEjXt-fqUbXc397qGzvNniARzqZcqrataPpzQoOM-bNj5LEJJdYPqSsHioJGOkhFzWXu49UuMFYUvyNxOhrbUy8h1N6GKiGDMSwe9k9wN-5WhvfEf3wPAztWl5R4PFRf306CPhL-FW83zhBr4c1UxU56taoVNnJtsblxuTTDJr8HgIiS0bqCLpL1s-ZYOgARzAgymuZCRdaxTmK4fdFhlTs6coahCbrSXO9Iehq58t6uw55hGhAqMjVvaRn2TpgwtHS2jvGMCsLFBYnkVXeeCDwA8uIEvujo_WcIUiT7STSP1IHMyllhlhU9tb0sD8wadR8caAgHBe2CuuE6YeO4qet9JIzOLTd3kJRE9Ev7aChlmuuAElJ0o-ktfVIvUbwVAwiWV3X6AcMlmVR_6HzhwZvc64Phapf84hPMYXvnIxBSI5UbvA0X5nHU2lnqPeRlhQI0mKXvLk4Z60WTgGrJoz6mjUQNep_zG1WTSkLwk4zlLwupc492MMc-M3x-vYQBmA0J2OfXEZjnuqAQ6az1hF9SaaF87c_W-Dkd5wgzUEkoUA2kjAfLtSItyltjCzxTnH5gGs7KaeoN_9V3bj_EAquWTrF9Vdr0DyN3fVdwrjU7oZhp_CVfondyy_VQO2wtxzBICKDcgraDmcBS1Pw_VPEIXvNm0ia52zwDDo6h53kRiKECACeOLLwif-WO5IBh4DZ_DFsiuaX1dJyUUO_7vk56KjmN0QEHxaNwpvKMuPtRGOMWkRAwIKezgkGJ-GRLXbeAA_1qqT0hLDsqJUal65fXdZ_J-qEnJH9xThlPem3WrWpAYKXeVOLOCxuA-7wxyxO2DxHqJdxsvzd16aErXTcIq7OgGXL14QQXLcpQIKermnxygZf06I83xy3pkfwEY07BVX6MnouU0ybMlqeFQgsWFnP_yjPuYGA0RQGOqsL_Cz_aq94VrHtzL1M8NTQt3Jhpr_L908QQMXN7kK6CKJnDkh9Rzykak8Lig_xmz8E42bPY-RWpAgAvpju1nggo6H4oH41IfQYW2gVzTviJq9EC1rP3FtJouq9gmSH5xDo5IW09XFskxJatkvOUIjgtZhCNG_VxtML1VdSDLZSrYjMT46SO8JjWJcn__4tR6gEmTrzRE2OSjbLuZpOksXgFrOgRDsZuPSeBAE8VKVpLtHvRQKWimJumFONfHJ7JxCOaUSBzpvk88Wg9em4x7YAd_SAChQoT7XRtjlwkRszQ-TwYfGsyOOGiTyG9dzCGGy_fsTugpowfedGCGBHJpuApn7cf5NNyLsafquuDtEyUly0NDpCwF2i4Dhma5jQsDEbKOlHnq8uzAkJXRe96IQBj0FWieRJyLU-pNsgXz2PqRxNXs__iId_f1X7avOZHN7FyBa-vE-u8RuYGXuLsUtQnnA0eYesQ0hCvGHa71I5E3-w1DCu9dLeY725SC1yVZ_vJ2WJmwEPXJIXKhVgTfvw8GIEml1VGxRFvb5kMQtGbXChL1tz7Y35ux-SRoX4A23pTZVEVquaXb2QjNFOprmA0tuFeYlsUdqD82ls4R1WzgzLVRRF4Z1Jh9AFgfYHqV-7UHwJAY0OpYK9iu6PPknBPAxWsxnLxyIxQ_rRnrbD-AyW-uFhBZ5d38zkvKw68Fr24Czq84U_OlBAvHtTWSzQa_6pc6tu5KT43QDCeWwiyWt1gdahuyoqGpJNgqyD6gh5xjSr1U-ahTJpXgVjnbNBkfOWecj9GK6CMLgvcI21qVrX2IHwG9kMyQgNmu--z0VHXt0WUtEuUcHMM4PzFM5AOZ_oxSVtIbvoYGDXjUgEI-xM7BOr4e1B4n8X0aoorefQhCLe1-Lv2pKRSeUlX60RlVuRN9GkoD_UoFqz59zJwL3h2uakwjt7iehx7DeI2pHUthZL03BqsYtJth9Emw5gsDKfBIR9BAjIzbSFRnnC_pthG2E1WMRMeeKThVkL_JYkmFj4Cr1xjqXXCTAI9QFwcTqRI4ZkRgem_jqVB7H9-BzVDrqgbQoxuWhNRn3_w-xfyzv_JtRcP150_7bEN2-gbBJCexcaF-0PbkopUuQqUjE3-WYKc9X9vLWcdkEehB0F7eqzdIWqRPTsnEat4SQhSvbaOp7EgY6Ypkvjkheer3fkPelAHN86SGviWWtaxDTWMBwHQjM866tuDKWOEnLQhMb_IjQDFKHrUKUnz42saPlPWfvbas8_Ymk7bX-E263Wzb5_MWXqPHMt6UTMSOtw86MTE46YEW9Ww-WW10cmatGb4jfoQHXa_JxCRry14AjwF7CmmQLP6dnm8r4_jm8AylHV8iKCG6r6csAhY1jQ3I-24iLu01EDB6H-_bIX3uiZDXpf4T1aGBJh7I7INB-Ad7d_IV7At-qaorPyE1xvTWeFVQLymsE87ZHY0J157ggITtT95e_Q8_SEiFYg0vxg89qBpuXygL2M_Pbrb5eYTCA6K6N86CxlOvFAb2AJnhAmxe8c_KHIsFZPL6lReDGQmMPBuvdCjjLPV7seEZX30ZMTuHYXNuD7IytEJ7X1o0_04eCmcqbivHBCoQGOzDhQ86DSoX2Omx-hmQl3hI2KgKnGcnfym2Ukd-3CmHAyCDAv2kDHm38H-JdcsO2DNk9QsYtAln6XRVl5kFDnWEhm9bRh-fg9Lmt_mNkwHSwZ0YrdYhAOCMkNlukUp0EYKKhBSY8lsY7a_TPbt8vkTMSCmi2sPr7NnuyaxMvw6Jblb9OD885lSOUp3oPpoH8QPkkhYUJ4-HVmmMGD8orSe0L3k7lLbyHzz5l1EmMahHWCCbnoMGGfO2QnxV4v9YcsMmIA_NX_1CjMUh_LYKrVWE2tfmhj7Zdprbop3nTylHV6YNet5h2MVUtpfj3CFTz-7V0AxKhqmTkSE9fMv5_XY9-QxFKf9B785SPTdj1xBiOsQ0uz3TJ2CPFHOtikiqYkNu9w2cUgYejqlM0crBDpQCuFmFJCFNKrfMa7eue_4H3RSh8Yu9Yw1LXbkAuGoFMGYhegcBEvcxcDSHfZ9f1HFT7IgimpuFuoGHwaNhPnlNc1uI1ILsFeRrrXide0q3L78aMAdu7eFfSSXHm-RcZypE9LHU8caoGqd0cr8hMAFvmAacrXiUE6RtzQUZjswSOziVVwlqyszgPXIuDsA4m0AcaLyEYQ8fEsRZAg7RyRbTgMGrlo-_L1Me2JMPPbiuNi2EtBXz_85Ylbaz45KQ45mdka24ouxzs3YK5aPi-Bv-fYL7FhoIWM6AiJH5ETjucj9KrhL5u-mnEi7sYh6ttj6I-MtSpCzOLrIB5HZ-tJktRhN78f2m8h6N4FBL9ooQXR4Y-QC1MG4eRlAiugn97K-r3MDGQZR5fVwC8SPW4Pt6UDvfaxXZek0HmjYPEk63MIxeMBOLaipBGR2ziR6YsoTUZ3NOopXjZr-UsGukdLw0OIJsxA-nGjmOZCr6iDgY-EfaCAVwAOxAv47u05VBTOP1xoUhMrxNefZ1lt8hEziCDaHInMkDdc4lQVeYv6H4rR2KugX0IXGsFc-C8sfQVnALLdQNjEg8_AfTsEmY3NqE_ECIUhFwxaW8s8aWBgX97Pi8SxkCwX6DyksH9fjA76rP4P5kpWl7ynaOaCfytRliE4j5uDXXywFfwN64DWKIQt4u2gDGo9d12CWUMGrWZZdn3qn8IgEDmUdr_CGXIGcPNuS-wxWoh4G8eGNhvMk1V9zhyhcxgbjoIJLl1T9MOZZ8JQVpiy-cPgClLI2jgIbKSVZTTZ8B6T93aQj5oEbOw87RZxArjYP2XeIHMNh6JUUOND97h1D-tXlI6hlFtFTouMxLzyOpVJLfdrUcr2p0bkbNPAyk3qzxwdRWegSWH2nojJVRP5dopYDUvX3a6sXVGUefUr6llKEtyQ9W84oVESDWyhWRv6GiBkpimAlkoolaGYFYCD72gUISM-ptvaWmVvNmXdZhR2JCSn3Ec5K9TZMg0ArIgFvnJeksow6nIwDSYZ_EXqtEgn9hjLaOcKZSrixLgvGqWY5phJcyYWP7kBsJTxc9U7xCIDh_RCU8fjZzAOAl4r3DtGTEntqzqhScZ_-Fx4ygPgpi4Ko84FM0RvNQGw5VSrOWADroETQVP-La2KyDOjYo4dTauA5ArmYnXyLatcyfbnvgE5KofVhMHwPq-QSV7QAaN9aM3KdDRxBXV7YtnjPx5DzLQE_61NLQkdC0iWFjHwLwM58comkNfrKAUw3vtLzWDiLHT1nPG0pxYBn0zAid0cdOFJ3JRJl2F6-GuMSeUK6kCqbX4mtShWXp1gn0YErlKR2PFjCDNj1o56a5ejMOYAB_SNIjRLO_O7uGofXv_Om9Uevp9XKu3ca86Qt6uOpwQsifkwS6j78cGRTJeU0SlIAGBjzi6b4aJN--CpFIqF6JpuZAxhiLzsHAXRAKik3Lu6Pmb_24KBL5_ktbQRcQX6GQjGi0A4gccSOF3hdJ9j1any3RaFOA1_0HRAv-ExWoiQEyUnWALcqaC1FmXgDTxYx_VUMjeb-MqxAV4eHjJsR7e1q9cJS8qhubSQbHMH72GccTJKlZYdLBHmc0Oqejf-JKgaBMxgkGX30uCXhT9B8dag8jVrDBemQV-wak7QHgbAveaWX74ZsZZF6ZuZ6YU1llAllJlLWPVNr4aaPj_wMfurz6YyOJDnCcVxcKFjBCJRuTBF1ACh9Ye1aj5wDUVwjeKXnjEy-quQNoB5c4clujc-G-ep6-EHj6WgHZefu1HYolZNprU9zHY3T_OrisT2jDBUByHv2RajGe3K7nDZprR-e1SPApINTcKQ42Fh8SfDQsXg0qOfvMdKbfKJqQizEQiCtvkQu1oXhlO8fC4J5UkN3qsPcdG_h1TQ-_zlAPDJ97B_92zV5NkIF3XFM2iQht1oWwZdN6xwKeDRqKmpER-qz7bxiy9Hh1IxU5T_Ac5c8B5xIxbQzgTJal2t1M-_cRvGT0CjpEBjRxqts-KliiGxFl48wNePKySRiGEfnn4Xfqmy4enbmmZgyHCmo-h--qxLIxBEykrcQurpumcrK29z2_jGUNichMpAaaT3UlzgVTbOVb3gVN3Qsu8ltR1RtlO5DM_Sc6q3GQ2QpdHafa2S8Z5D_A90PuohDCpyqvS7tA24KNQEKYM2W_ONMBNNEoyU2p7hZezbbj5T_HLHVRPUiVLgugGFQkNwZ5cRgrgYqstoKu9VJWFE-odBF8G9GwHGFFqyCdBL2CADSx9AnfEssP0TSarXyn-ALo1n5f6vpUFmkcuY-4gFSang5orkODd3k7hSmsCxs5NVMLfQxPtjJcTTrKR04H7xAVNnt79YJYVW73UaXEUammc_qu0GAuNwgeaX3wIQv8ieBeqJvGbfOoXd-U6c8b2xS7b_9BCWtTKZ1A8azUrXAqOr5rXlKkq6I31ht1XzyQAWq3_YWEc8MJahqr7bR5GQqOxRg_adTocY65i1qhxebStP6XWRRurHWyHzDhi9duKfGK_eC1bbuUIevXsNDHdQBDNE8_w1BBBlg4eFuM8vSDZWJEKPxvB4Vl7ciLOs6-diW3bj_JDo1BZlpdDQFKCwDuk5RtRJmr9hGUaIbF6nrjbFduzQFh6laU7VkD_3XyqJ2C3dCD1vOOhslfiVG1fBWHpTJvKsgfLa0u94IUipo6YWCz8K-LCeOymEufdrfaI1A5qutL6tF0CaPl48rmLRMayxqTf4ZGCCDe49C74wOS_kGmxchhr8DKGUgKwiWJWQjIQLIk2PzaHSQ4cE8uBQebBsCMzlrzNr1YhYzvzhje-qorpNcwCluQeaXkqp1WST9LbExS1jN8gmJhLgS8yAOd_yGdJchugXdbfPXWD_R4oVf40bCAv3HBB3MxQKq8dZeXg_9xqr_bhwqY1oUraAHLEol6kUS--0eDJ9PzaLed1ZQ_6j-pHR-mu-OkQUvtM-THVLuNMKWGSYKcBnOFYw_1NpEkwoWtcYCzk-nq-aHJ5XnijDKutRPJQ5W6RLMmhB8qFoZpRp_aDS5LJiqp-Q4g2QhtSCckgUwHN5GSDTLaYvjkR5jeIDI0Df_tQZQv7BiusW4M-iXMunM3qpOcdAdfnBTmODqjdeBAk4dRnayZtb2Ib-JKl5ywa6WUDhpA_UQA_sIlBBbTjetvlH2sChS0D17boDPANxqPYQLorzUflL42ay1DQFsRRdnxTiNvzN3nMOxzFdIUYqWEiY29KQmAFyuERLmtWNxvUB7KB9WqxV21mbJ-yIhTsuUTHve3HdcJuWPzEtbZemmvTyJr1wckTGBWVfeT20e24dPMpBbRN24Mpx_tMxfsioxNsXFYqKHzqWqZ8Tp-gj0TUMr-dATGUJHHQ2Un1nVUYhOfB-G-cycBf8zmgcnA9EsKkTOlZY1LRmvBIknw6thweHCggBJ8Ke5N7lgYjdTTPs9HXMZk-YcGJ8Q-TkB4_Dw35xq9_hnncS-Dl-_aTs3FD-V3fAbAd9eYbttpwk9kwVnc3GzF_d-eoCntwtxNH_iYmdeBZIqLZAoDwzvFnGfVunFP4RiUtLYepxu1m7HLhPSCAQn6SNcLwGg1U0jQpfYIYGZTL3Ntq91XYv3J9vy5O1apgQZic9XEMxzOuoYf0zDEU41PaVOmGv-H-mdrmH-MI0AquibmsDkD1GoUssNDqsqGVBgMMp1kc3N6irmLeIpdrSjOLUsW8eq0YGWoMXXxp32wIfDr1fad4KV22Slqlrfv4RC2v15WxVI6j8Cn2l6ymNxCj95fk55ibBk8IgObZEwbu-O4F6focQnbqXcLMSHipxWVOo0PNAnxeG8ER8AuVaimP1nXVWhNo77VuX_Yat85m9l4Avt0Q8tR6Rpqruw0cxZRH-3GRk97-svz5QsXMJgNZsDquzmeRT7ydwFrr8NK2Ei9NmlZ4pziY4xgIjVIJgIhgkY2wEH9EBDPLuqmYrA9z2RC4KUg5aMAvhRRZ1Jrxd4uv6C7iq9o9x6AOVwA3AzuM-A42325s1cNlnURin7VjQvoDg03eXsB-G-iSEUw_WoiFatKsO1U8bW4GP1-XwaZMD2w9-NXF9JCCGp2PaYNl79WZXpoNqtOv7CS-USx0vOF6DLllVZebsUhgMTBHg6I7dmJShzC1VLrCV_XjFCVlxfSdC-HkHceCUwQwQvkH7CzkW3Xxqn9onVcL1vMKgt-D7ov_952u8jsS6gkzEkUZgSFKNUMJGZv8J1rhg-ZNUi_50EsohJTlxy8H3xw8RFN9JsTZ7T7_O2yJ-yB5bCdSHldOwfQWtPvCw0df7yzUQtkMqMY384QRdKraWO3CwhrqD5_j-iqM1nw3AKDnqvUZ_pL_MrJT5OwqvaQLlIJpSymmfw642aXt7P1TzzFnwOYb0Myjc0geBp6JKLB4MetCiKUxmYP8M3hiH8FSZLv00jUmVJj-CPVj2IVml-IiAPyPU45_2W_Sek_l6JDqxgviPNU2QfLqXLOgs7-30-8ZhrtlZLC1AYco0hIEyVvFBQC5CjorAuillJuZ02YU5_kNwGG-Avbqb2zLhjw3gO7ZB1Lz68cv8F5YVsUvCvMgRhgpr5Wj_5uFtw23HGXHKY2Ejm3Kjya_Tw1EbrPl7t-UYyUxZkF6lUh-ZnndeOB7RWVO9lDvW-kuu5XuYFbAM6ouYOPd0Am1Te__qnJe0cYwKBaqopwTCE_7cu9EH37OBm3YWyGrthggmOrcK9jSI-xA40URX30vYvyuvNzZ-0f8PrZIfTtss2f0w9om6vDpwxsWhXRlTyz9qc0ntEgVwX6t6xWklLasPIwXZpahtO8PAA9Vqy2D3t-nMSyeBaPMhkZi_k5x3ckiLR9RHH1OmiAyYkGafn1_aB381MKMv_8AS4YGzeAvaHBwwfNDBlPpBhdupAGXoGPKFCM6d5W1QoDhwQyIZ9uFKuvoPtxntY8MwG5x-Vwmg3GhIDiSmoybRNIpfIqXUVzg5_a9p9b0-Go59h9B1ntMB0K1Q0X1EtZq-tVRlv1MRpSjOl8LFyGFQ8rYS0aY54cZgE_tdOaozg5NuXDJPQR515WrBf6NyJ2E66D3u1Fde7hd-zUMSiASQXMKwCLOAMNn4f3MWoj6UR3vKPjtBNwF1umNrE8P1tErywv40kYGz8-Zy5Jub9dMgKEfXbz1s6XIqZJEDSXngwVYNQx2fhaO-uGxt-eahjkVAkt1KoTe3sDxtkX7CFQNAaVBlsy4JEqRM1-Mxg0GfAP6M5l6MMhbqkJoN4oC4TVUlASghOUHqkCorULtgKctw01Ea9UnPzXz-KKpA4RllrWdUryiRH2A5RPs3KH6mTKVjJmzXvs-tHHeQphSLLm3QV1smoj9Z-oAJrz0C-f_Y0LE4Rsaw8Ag_7G9OOrBOD1odrNT2PbpvyeMCv2179maxKeUB3WRIU_Mz8b4_vi76gODzX6t-K5zDm1ukMlpNLfRtD2FZOEu2S9dGFFy-Ut3gB8Vnu_b1wnzETDDqWZJ-6bo9qRxrRAkH6q3TF5VTKv_hnYKY6QzcmotJrdTNPQvwCztcqj4c45FtJyax2tdOQo4lhoqDapMA9TawQMxunVToG8YmNP1YKJljFq-ZFttAxcnIpaTYq9scd3cfS0S63cnjaMT_H_LEBW9FedIR53Ko12fyQn9cLgErigUWMWwgdTmE2rPo3ygRky06cEcrh6zUtNb5E0Xt8FnmR0n53wZbJHsX9N6ficGSVwanB9ZBGJz5TmRHdF2aE6NrALFCVLZ_9mUP0XVz9HSUH9YbauXqYM8afLJ_R8XNm1WtqX6gWkCG4HulNtWURyTWgVuQT4jiB392QSDulnwnUnaFiroMxbHD6UENVgg78icspfeRQ3I_wEKLpCmngQSDvgNlV-vzVct_920i-n6DSDav6Ez6MgxCa0cgrF5Fbzak-koA7olgU2xqiyoAFv02H76alrTcE6Ooi0zNIBABz8McKSqmJDhJ3RTpCYQCmJ71Xq3xdeT-9-WBX9QgNEGQ9BAcZNT8IHY7yUocfYNOQS3XbCogSc0HR260BC8-8ijyyx1RfZB2kErTGpUCo3FQJLg8QNYU4cThUe1rmgzC1aJSHdYD8OLKHflJCHZiGGaYW_MA-tBWfHiEISIUcIghjbVjF2dBoMZBW5hlzvYWOV5y1QXW0zvTJ1Tw4R6kJGWNTK4wePkrh9W3t4wMu2QvyJQLGGwb4ltSDWefD44MtkWdfquG7OTbXqEiPr2KreJ2j3DASXuBDBD25RvlZc4bhLHFj9BUJ-lulsAvDWKCb2Bou0i6akOancevmmSZUwphs-hQM2b3ugNTsgsUEoF82dXWCJ70gyr1RFBfBsZCYDMDWbiqMYC221y5Pw2zoHRdQ40xDVCmTzDZZxzBr3ywIcE0Y_6c9tlm4e6EgOkdHg5KaAV9sV_uMLbBeSxyihQgJuxA4dzQnCo3Q_owAGtnkvhQp4UgYlx2AeclHenpTuFb_t-BsO1-DV6LgRplzfXH7ocQedgUXsd-gZtA61tnwNR2qRk9dbmtOikjI7qf7tFv8r0pRbe_d_mNadmgformlLzAtUn87xkZLmcMx_iH0g7gW7gbEXnkKmX9syage0xeQ12qnGvGF-p6mBKFUM7d_8ZBFt3pSd0M2Wl1zLnK9HQJVPXjWWBf8r9UecYdpyhtZAnxREWSqG1APYDP8cPpQcewy_QaCnVqyYZRFkf6X6ch-O9sJAwzR4MLElaZ31KyCxHTj8565hGC5bJUdg_I91UgH2yJArG54y_Yc5Dl6ALUn9QgPzbqDFFUOJjwU5o9uD2XyEBYzEErekT-GqxtSGOgCFSStNay_o8OmjolNWZVRc1_aFeMUOgh_GJCAnBMs8AVNU8rG-2bL8Yn_08Lfn-QpqpZIZIVsTZinG9cCIy-nuGGUtwHtPdG8xntWD7d5rNUtro9BCoxdrnbFOkSAwCQ365HHDHG-D0bnxTd70UQLYZcAb6rkxFrENHGBQFl5f1sOWZnGhofb6snJCirTWsgJcst54Dzu14XaX-57i-J3gi6pI0alrVQhxukhTtV3oj42A2TUGD6Qb2P_PjwhVbwpyfkd9tNTRT4YKbB6v7FviTl7JKRh_lMFAeLiNc10auLFBnXOdq28pbt64ilr05QoEABo-2qj0w1qRgK1RfdC_x2WRHcrI7zWIyDONsyqumIklidGqrEh8EXCSg3a1PBLMIrUfkfyV8C7LvTL_lifHl18bZO1BJtoksrMcCmPiwEJhCCMn1olm_DSh1YHahgEFrP9PhmLrFpJrymDuzXlWENX0QfqD8_bsiaIC7sqi4ZCnGI-KCnePmdiATIkO1ROI0ty_1kRce2LFztuwYFLY_z1yJlFflviLtyjU2z3F8Dl5JjO2dWm4n7bBCRT8wAqp5eztDZdaiuQUZKi9vhIuEnqFpL5zQVTUlDpMWodeYlcEZT0pQQamulicCkRslA7Z-CThZgOW3QWCv3eYTvOlZ0merHzQFxYq-8S_0rfwK9BEA1xck28GdMIXUd5cqBN1kUPd06qbwbCAgVBABucXvWbmkCeokCXOyfxb2BHl7381ZWy3_U6M0AnKzxhtYBSmBjY8sQAeJg1WTQ0ZpbMT651_b8ipPHAUl57j9rwVzxrdtmtai0VoUVNv4UEF6gDR_byb09xWMXgCWHrBMbbs7KNNC307cI7lmSHDwFDiWjxXcZtGMCix71kfh6uZsRBursMcnUoIaGvd_Pqv7SKeo3c1DXs8d4yraU5VqtmvHuodSmfcmOCEkzLb4lmVfBZPrsJQcLb9xFH8wunqxWYhr2ERzOJDZoLIKNwQnPDcxoK7UX_tLfbHKAO_CcfHWRgB_NkcPVvf8jViQRTrskD_19WqQFq241yN8yW4a61C6v-9og8yJyy8BWPQdiKESA180YGsfujYRx40jXR1u0g-WgRF35S97vOzm963EAkAmfCPBpRckAFxeDcb9DfBvhihOeaQEobt9UNhiDTNaiSN_Hl66wA5DIPIptw0_HQQLoVQ6HUevZymcwe9A5p7_AdCf86KBN-Z6cu7-5OTmctbwROcfjMYjlJLXI4vSE1fY_BdaYPBvPWsGaPKTNr9kwy0RyDrYd4a3hzDBzEOAGUJm14pdaOSbjtwoIJ0m5TeQRm-e-EBqxv4dcABhod1agzhWgyKZarIrtkDhGW7dkDqSdxHzPCxphtD1a7SD2MdKfz0IK_IkPRSr5N690e9kBMO8r0MmuMg85Jf4vA3w3-ywnIbaW865qXxkW-3CYgJ8RloGuBcJewQH13Ozoz1FAlt1Gt5Q-uHiMokLpmbCmvGVk7xPXqDu_sqRhQSjlEXRBjmGzeotBxxhTwmzqZfJxRXEdmGAtrfqva6gzYGgSdXFWo-_wfN2-DjBa1Z8FAxpmT-dRPNvaKwOmknS-tI5xi2i7kzmh-oIn8n-AJ6WanEBaFc5vTC9SnQNxnjnnbTu-bRMj_KlXXpw-ryvlGEGhdMOqfcgSWzQLPBSVMJpDU9rSZMfGl77Q-S3q9mRfjPnd6TqlNfOskpiQijqlKNvhC_D2S8SerwBOrWTSZ2i0W2NKgtAvkgn1v7wHkNIp6iJ9CU0mXIobg1uDrdvReirxIxuznqXyf9xma99oqKmQvh4dWfhlQH-a8AB1Hl624CTjEs4CcoZfCm2pMpcDie4gVvQiGkHQosnTdOA12IX3REq8peIyawJpoyI50ConQxCFuWqKfZkxvaLMfVAHcpvRNrNEF-jD1lf6R1emRB8jW6iQLCKYVueF6qfUsmb6Ql-gmKcakkB71QGMSGTa91eBg--S11MB79NFQdZhQDpYYc5GAAKTR3PF9Cj-xk_33qn0Xz3Xw5jRTZqm-qVcqPMwcdxcB9p8JhtWuhGcfyGmON9hM83JHg8xKGUn-1qPOnvF1yWoRcI6wv7Xe3jfo-_RHLEwbPTbihfw2H6ycYxEl_iz9zlG40_WNJwwWDdHn-jsau08fNxdR4WC9FEvC7lRAUeQPVxUWE3ziJjlDMeZGz2jy4daSi-LY-QZCzarHtQ4_olBcW11Q8gtV0lOBrkATxbd7YRAL7_dh54Xw9T6X0O7TlpofzzAVMZzIn0iTai8k0eAzuj3DT2FiCHAh4-RbKHr7mzyrPQ0MUmJp2PomCnzG25BUbYSlClBcjtotLGm6YuDPzB5X7Lu_vH9eRjxMEh7ZqIYO6m81D0dwZO9aVZSSwa_LBb1iBFrHijTsL8rHXXcBSnp_jIaZrGLyKkxMaJDegmLd8HdgACP3rOqVCDg1n_CVE3_jRaqwwHJVpani_j77aSGBmItjp7HqbcgZr_CVMCBHX3XfzlhuXZkvBoc8ZaYYifhvgGFGEg0jHEaxIIU0QDqm2L6dHqCH6yAlkkT8zRgWeLH4Pey8nR2KTAZP55YtaaU38cUPOqVlvTmPihzfNHH18h0vLfaPPjA712C9V3hvVACSpU5SsXQU7NfnnIO7_5ZcX-iCaEuDsSFlJcAJFaSyKJh5kcXsGdRCAM5nVfyH6_NFHzGiNWaIqc-E3Yl4a4pS07bpe74bsEUrxUfdgmY9XULfNwuGPVg4qBsSoS8coVBn5SxwVR6OITKjr8Iq6b8EZZxxc6qJJe2Xd5mExe6NxAW3sClorNhS_wwcBYwj6HUH8SmXpZ0xqADYVqky8bn-pa5j6RFNSH5zz9deI4_1ioLhkVtvpbRFHOxCPzm56wjqQnEci9QQd8axmpiKgHP8HnpTzLHO2MgqjjunSox4sXOz_BEEPWghInV_VpmFb0KN0B4UH_M0f9Yar4O1unjCGwlLF_ZfLfNfwmi8JoDRMYIyFn6D1PxQgdBBPKN0oC_Z11E28WQqTORvTJqusVY4qoZ4d1FOkd5E9srOWuvs0gBGweaIzUAZHdRGr4NygezGmf27uWSos68ZHaB2qOc79z_TpsXiVeik5uT-pSbt2R-GEIeg8cwCH1J2u7UHsWLmJFyUmBW3K372QeHxoW8UKinTNg4Zy6uF5acVZmom5E8s957-83Qcs_unrHFoUTPy_KWoiqRefrQcpmCHra-JYSYwNxfwgzoCp-EHgl2ypCIZ5BpRQHgKweWJWeRhioSBwGejT7evYEl3-L_FazZFY5W6tKyXFktO2jIySP0NMGxFL8S-PWQERH9cdm7l1KN849iSIqeMI8cROEUCWjUIhdh9pXJnY8vYhQBfbEjJ2fJFjOEtT8ARZe1jBPNUFdoRph8YXVXRkHn0uw826uIzZGnacbNgRwgNdilq-j1Rj5iirOQwXSQ1s_L2Y2Gl8O7YZ_tuEek0ovZnebzesmYKtoY_XhunbD_U-4afK57BtBTsmm1Ed_AwfhZNV_vqKC5DraEE6c6J_7d1f3NJEMVK-QDm-iMLGdLHjOr3bf8TjpeXNjITXiBZ0kJBb_qf7Y6Sze1UueGWd_23NVi5Ufe8w--C9fE3YT0Hl0wnSRJ1WvOGlLQf2Hgk8KaazMuCVbkNFzjojCQ_IrmsEz2sbWOSMDB_E2y-6JJyET54mCpfMYhdHXVhtbAH0sdBNtp2KGfh9206nOJU-lKwjo71lgNm4XoWV5Ux1LXYSeN9r7BSrpirkFIqxyQkJez9Ulcbiz5ES5t8oaTwCOnIDE28Vy324HhGPSi5W2QPkCOV_PjOWCeM8yjS_6w_FnGuO_26ecaOEkCNBZung5p0pHSmD9D0SeQ55YvwYvwMhT3smiwDo9dRcFa6sigkWHHKtBLW29sYLB4r5pNWtHd6CihJCcG9DTTbaE5qP0-eOF1l4GKEhtIUKDPGJGwEzYHjq9emeIy1uacdIcWTCJylvCVOHdWmLaD1HefI1tjSyga1LuX-uZPAYEu4H3BHd_8RhEhTIIR2W1Zi4pcy___Mg6UnxiELbieUU9M-kBKnEG8wm1_VCAJVg6GulXQG20z5Zq0Zr8HsRUEpcO6ULm-_3zF1WYWSPU-JDi_ZiKxGdLOidzU4gb-zzrrLYtA2USFwdncVimCESLHhKPSvv6r2xX5Hz0eTuLmhshN4wL2du7QNz_mLVnI0aIGrHWQgs_DEy06L1P4ANm_Y-0xdzookmfICUGKChRsnNFH5Ardfg5JWwzC_jQrW1XM_t8g-3Hnv_A-UzUyJWBl3ezae1NPikowsbMsIwLuHHteDmQmqb9-93yiUdXB9FxycWFgaPksF17KxTvI8FS2PPwZKsSOTXMQNCQyFd4fJDR60nQhm19DhQImTl_QPvqibTAg_p5zlhxlEFdMKoMEdSrqovWF0mKoOLbIHlGum-tDlq2Ll96PE2-CrnW8NyHVDdew8iZSZ5dahyl3prZnh_EiRB8nNBESy8uH9ppuSH6XlQ0TJXdhwI1ZdOJvFonZ-7IBR1TVb4ynvpzRt-oWE-tNx1-6qwSJGzrsKnn1EYkDQaRj7nfztiOa9af0LGUR5ejBaZVx-bQ-75PO-xBTxd0UpI5kyaEf9T3rUM19GzASEzvIwPCPRplhpopMmPORqBqg1oFxqI9vzahfzntnYmWEBLGc2ks1NZWq1gLcSZLw947_EEGgyqw51cFGXLaB1DeA85qa6WT1jRmS4Fjj747XLPynyNH73NU8RWsx03F0y_fvUpPGS_vaXWR8AhEy-gdBW5CCYbsPv7WB1Ls0_DJMBSHylHgNQvC_5knHobolZyERyyye0rwmLca0TnAJS0QhgywEwaoateT_H3_aqypXAFQdqP9aXzDLINETQH-jPND97CG-mhA5bh_mmulEvQMxHyt1e4d2IWPOJjYUvSj1gaxoNl8C_v-h8719rmYl7e5jedHHzYQuDgq-i4B8HlQxgLycD2vQqtt9F8fadudBvjaa4qaHQNw_AZc_8aWNUQ23FdSfC2ZSwJvYASGSz5iwwZotTwF92WMyzfnNvdjFyluEZR4D2RXnYP9GUuwGcg6LvtzjZDq4GoOG8cZEqgSQpSUFWN4-NUVBrb8GLY-SDo08tW7Q42PvN8h6h6cPCpFgrKFrqEuNupBiw_GvD-Ihj6S81070U74EpW3yin5jY5dVGJO_Q-8GBVsyfe9VyPGlDCt9p2-FwvgP6aMZnWAQys5HjDo7QxHaLXAUAJEB4HJatbd3sDYsC3S3Py-_NDzA9_JuOI4iqvOjwf96mS8xfOkoDY0CyKso6cn7BWBDbtgGL5yjjAOrsgyRzALWaUehhq0p48D45hMtJh40lBfgA2QkEqXaqlFdooXKlfyn0nePdsQPYJWxg4O42Up_ha9yeggy_bdTtWJQlR1bpgphhsDFFhPq3rrrD54e-AmMPvLS_KnhRHR22d8t80bo2yhrXzT612iv6Z_2_wxWbm8AnUB1L4t1pnI0BW9MLhU0EC55f52wZCJQ8wJdRcH4lbuUsZ4ioBA8J6X-UtP7YjjBTeXITfvyCaLvkwGseuU4DCiTHh6mkqIq6ynzsg9kXqjCB7oDfO8yZm82JEuzLWaReeZSub0J4FAyCUQImgs3Ui1shcwK6IVbk57-Gjywva17R7qQhkYxqeDCbrd64y3QLFBnhiYSN4TrR5AaPiNz3eCYFYPTdMjNCWa7HMb8wgI8Bix513uKuS7HenMc_h1QwCzrD146GKiiEZ0LT2IIDDO8h_gKx3Y-7N5B9Og7wjsDps624fXnr889NYznFOBwuVhNmT4aULq_L32VNXYO7bvGEm8T__RrBnigqlftf0nHzP2U7gN3kKnuCg0VryDRRs30No9mmIxpCzEkGfEDb3g8SxDiiyOjZEuFTG-doTdRDPfe8DqiPTfJdFWRfDkBKFbpnV46-Dy1PKe1HdpoF82ggBjtwT6N3GZ4MPq1UVYQ6aiwlk-vUpetZHohzn1AD15XlDE_NfnZHhvGrHGApPPUFCMmZRmqQTkNH4IEpUDQM4_SacoAIdkrgHO7PoUAFoHYMpumQ2pow4VTR3mj0tpvG-iIBbcxvqc5XLQQZhXuhDVAEl3p8HPTDKqFgxTxiKT_Ns2pfkp7zHS9-Qp6VzlZgoa1Kt-ipc-BOpwBzzeDqg5bOYvDF4mySuTfNy7RnMfX2F0WZKN0j0Rbo99iNUgkvxQNTAsicaZGuGWaUbgiQI5OT_kltLhbL0Lwk4AQpgKHQ0OBgIYC7ONSWNWlHqRTR0CGRYRPPB5tOfzJ9iVeKQKgTnH-PTukqdsxJyrwalRgF9I_b3qBXCFeY7Ea1JyqYhi2c1OLLoI8UJ1kNsH9Jsuww0WjthK7U5KQEHkQTZSjdEyoD3M-daQhocYGcPqRLqt_kfDWpA9fQYJVlMCUL9aQuMdYVz0ZzZwV4PhAoqep2MwxErhdjEUPhqyt4mVopZW-Zyigqpw7ef5K8lrBvtfLV3rt0hFTzuxACp1wQOWVsYvY36I0Yff9iHGHaOArfsR0KgDgbNK7E7D5CtFrHyOn5XGjWcdjLaYKvCJ8wKrIItOXpWEMxBCcKsKsj3bo_jJKiKYS5hVeaznfwc7pi0J21-4BAkb9Vs4XqIcooEFbUlqFSxWMuBokQAsxBEdeZ4ZEWbD_jZdx8NxELKLxPuKiYYmaljKyW4NqhyeGPgFxeHV7PC8fZ5O1Zg2sTMkW7J_BkZte3oGa9zeENRYMYmVp90gURGZ9vex7-GM362BBH-Uq9w9XYGL_yVfylRVU2PGoCEmMoxqgxsYTt6t--noIEO67jMxWhOdX-i2bLo4xdZnTBBDiiCwDLBM4SS5FWv9Q1b5NO8GL9ePjw0PEowJy6Lhq1MEBrQSR_AiNr7tAQPoJc-ltUMtBCn0FrDKT8UZchBVaMPazNXHJyJB__MZfJLc36Pr3xI3YG7C7plb4MOzJ2UU7knbHbcGM8WqKykYOBlde91ywezS-WEo8EUTO9rVUTDPwSPH2NjnuFnu9cEAmXYicqip9J5WLcnWxKuo51O53VaSXa3KOwkRsh86PPoxbN_6boEBx2b78eQOgVrE8T52OD8SryaCcj7GmHsA-nLWXhAZ98WTCCR_O3N3JZSMDB8NNKaTdyjILTThzcZBAMHpCZteh3JxXO2kiw9Q53cCVt-PNAVFwgANiyFFW00sGKI1VxK2SqsCXupmVQqzwJ_VN_KyQfh56xgMWxEucdcbneMoOWUzDZduKIBBhM3BiiaidHeflnpuDid8poBugQVdxNZdxxi27cdV7h0ieu0WAJj5G4DjNY5XI-S3cilYnTXUNg3nE4kQb6jVsjVPKwS7sur3AvwPld2qHJD5Zo5_63axnH-FQuiA2oF7pZxoYiz4IYY94ydG8gOOYteoiwEDD4tDi9_p-Vh19qsJ8NyAaC3sO1mKZUhLpGX4W5vXI9bONL6KfiZtpGsNOS0al73DiqdLiFtAcp68geOr3ym7Miq2xtthT-mCiNOn4HugT-rogZbzPlRK3aHEY3MsLL2BBcPue8ffnazWOosLQuThIGdGwHxSHwk9crZito6H3rfhy5FQYRZELbjkp6XwSzWqwGNh5PvS3a4WxLOImjdS_SdeFFztTbz643sos675Aodwntlo8e97352Zl54dJVBWQQQXZe92VNcHdywcaHzSA2NyLRWz9kJA4R4jHUBq0Kd_y-f_4LZMgcnSJyB_kxotskTdJvy8K4VSB7NSgMxkfzv-DWokMaWuZ6i9lhG6laXjt8SzVmZnBXx2fcGgveBZ0cEEy_ZAjwSaqkircbn6rIcmwjOLxsSvcyHHaB4371u2OZzhoM1eRQ6I_wXHJP2FW4zESJYPOhSWtJ6Apz4rHoUnlDCcg1MnT3Q6PvRNDq0jB26NCCl4ixvXlWtuWTa6_bXBARoDauSXsf9YAX-vnSTK2lOz0pOWgz_QjQw0Lx7nEi4sMXdnGvQNxkSiGAmExZzqAPZwMGbdAJUnjc0jW7Fi28MG3G8cHvO6fcGMo-IHUlH1hr7vMVCViYqjcZQOJ6YgAQNQNe6mXCcsSJij3_AeMXOJvC55N2l9GkRBkByX7-NO0zWRMGZdtYxe-25RMM46v4AZi3A2mH-31HphZ34kIlBH9yb-8Vw4cdUHpY42kEhnXusSk0gx_bGxqJRVVpVgo0EAAAkhSRkWSqJiccp5iZ1yZ2EpHOgEM1vthLyCualal7K-fTHBm5jSjNqNNiZ85xJF3tbnHSjLNdQ-sYcUnhDFedPfS1bzfVZrJBfzjp9_itNRPeJnHhYGe-K9d5TQqjrBAtwrGnMkGhpegfK6Ac2Nklvcl-yCdX0Fx_OYe6peI4slr4S9XmZBj3ZpG7PX4NdyAKDu0GwufKIcSATJlFk-1L17vj-b54H5iFj5472wPjh-E9NJ2UWS5GbEC8TPpqw5wQH_Q4KnOIE03lgzCcImIKW4jK52uCSsBljKI5CXQzgTj2lR2lf7OqqEwyuFP6KEm4Gbd98fASaqrgFmR3CBqJfFkaIeuluglEt6hbkIQU4KlhVJ1kwkOq23gcjyxC4TXYEBNake_62MYh17xz5yxky34x6cl8B-e14KXqOG5qG5ug3gsoD334ICr72xkt-m3mICgkUYOSBE83pb2AA7YuW5IqwTLStyt03wQhYmDXd_q4FBM7ZO-uwue_cT49vvpDHBAL7zwG9if6P_wwVVqO85qFfri0-S37JXpakkJ6_9SUpM18Yo4g2SbEoFLE_psEgmhRAVyGZjGMCU2Yb2Nh6eQaVhuiciWgij3Hf69IJYKZ7dgNmCuuTMp_VlJ0_bDWGlAQZUvZoXemSxVUvOEMjNj0JxhAnuo6Pi9eWLcpy018a71RUAcCrdI6NLvPBNr6qYJgZL2YE6lLe5kN2xxuxtNIm0PdkyvAo9N0OGwXOkQcY8KxwwhBPI01FGQ1ULM51ICIEBERqQD5-RkIAICNR6o8zZD-6Iqah6mvg2OOhpEWzyTuIV6y3d_hOKpYtdPZ0tYpmGdXjl0CM6UZmUyAxk43Frunx0UQg3pA_Awwu5YhXCPek64_gbjQve8bn5Dxl6ZAvBAk85VngWQNtjH4JNk2GABmghnZr2ZHWhO_GX-q3KKTyOqbUjACY1il-tUhIs0TkcQqrYLRMXRrSACeDKw1VWm6iTI_6IYfcUGs_H1Y0fgyCSI3lq3495MNy-dbp-G5WiAQCZI_mqzoxTcr0EifYsDKQuzpSs4e6e4beFerRgJmLVr9Jgo9heM988Va39i0Vo0AEIPlaZqLXrAz--eT1xxSdBi6JlxKS2uzYsl800ySl66rIKPUoXdkVni_F_20mmkwEGCAQ4ZJS1g52aDOSjCYPuP4nUfCCL1868DyocogHBIwr7PCQ4-_0e7rKflnzCoPtETbNRKJj55oRaiAlFdqaTWWSMp_LjH7w0GFXxzTtnuur3GA3QaeaCO9bIPf-kiFhBArunZ4iY6SdxqV2bu3ANgoc35zfPy7r4wZDnS2BfHFn6KXRHhns5yN5U-OVjT2pIBWbLxQj8J8TOrSGYkpcTwJ526XWPKA03qIn2pOEe4wUDkW0tkxyyIgt5cCjSPWhhQQLsYYKJ8rk2ojWvIHSdHSgIof0eVI51RGCW4jcg2pJ3I25sFIfpgqI5QipxB75eTIB32XCBtzWmK2E6dPAQfnHNPYITbjLmOrH2f6zbW1_LJ3LVtMMijseSomNhA0v4KUEBy5aOriMgwBRc2doCITBcWz0OD6TCXbcrNvW7g6BDK67Ym4Vpn6bl3B4tIH19TNQB4YhX4z2kAyhlOOlvwqMcfhtdiNxuSZ7BAqQYixn5dDpswpCqiI_MjH51TMikt-YBBCHTr-RGRIXaWxk2sTl01agDUdyWGJ8wsP1f0ndpLm3fHdejNab0MOn6osZGpP3ZgZIYoX0o7CoF_5lVDdc08Dt7L_yEmzk4ccF-JQ0JtbfYdzvc4OrUBm3zQfNVsdw_AQHE0H8y3wolZFgsPzAOF39j-_9SDKkZQAHkO42MKEBuDYNRANGd41ztyybua00Dn8XEYC7OiWofp6CNgeFts0oXhYM7YU-0A8h4n_xVYrk-0Rb-zpprX3pmPsLySXIDR0EBHRdi54BjFeutO1ODlZUI0JXKinpc3TEq1Q8Umhk5Yid-CmzYfaVtt65hsdKIybzDgZkBSqOZHNlU-qgtHZsZjB7HhlsQH_hsJMfO_GDYmvUyL61zZ_6i-kzVl9kQzarBALNWbFaReiu2SG9cY4n8raKYyXQxQXE31wFUrKaibEAXJlq26xQzmZmf12t4-3ZVxMi15PRbREWLYGzqNRARqU3mHd3_FPTeaLxcWy-KfufvSTVOIYkKoAXAbHfGckSZgQMlCPqKvao0Lss7N3bdcI04kJRmOcExYhAXvepyznGreKpfwWLm2YpoPgFuWq2cbkOg_KNOxeI-SCe8WL5geA7u7S-PPZZ89jarsvO7kPAIQXxHg7a46y9wzDLclZD7UcECTva6MEKRlMP5zsg4EfRkmZ8AQcykymQikio50dvSITkyqtD5XLkLYv2eypab6-1CHu3z-YUQSHYLOw4fsU6dR8lToK4I4pl9auL2j4z2FqwZTt-wnGkTXTevikprpz7BBaY78BYmJHquSGjIEoy59aBoFNWsKLhyB7r-JFAVRXgZAspE59-JmzJVSIfyNWXThYFzabEXW2VmUNRAcb2pRUP7KYWY8xqgZTvQZ2mtXQBY4GpAoXR6jgH-fmWg988kAQBxRnDoZgb0VqOUNQK29C5BIEt8CsHE97YSouTsqqGtATh9YQUinkIpjyHMAYRfnkMiywoFYeaJdEd4DFPIvJ_MmDWtg43nh4dbJahewqSfAzmFH1B-js9WAG7bivifCkEFdHfWcyDybAKICp2iZ4clqNYH9EoSgYJuDnUoyHrBvhWbaG4CZFi6bALdp68fj_7D6MCId76bo2D47SRj-q6bzrQFHvrbfK86EdM5KbJftG9ieNvuE7PjAEAheezl1fxBBKKZDCnxPzovqnmBX3mnEy_giFlxpBfUm7g0ot-FrszjXCMAcw4PNQchogsmtV8zQ8XZOo2Rlay3YmS9-nK2Z1jEBXckY8C8y2IavccKdbWAOUidl9LsHe0wLA0tC0YcAQH5HF1yfqhXeaUXmVA1tF7vJW6tBMsm443zWLqD3MvCjC6DoUb1O6IMaeSwvS7spYGuleZPr4OvXuWcylIBgHS8TlIwoo4P1zBFAlYOYCGsulS8TBKmLxOWskPS-grktYEBBK-uDxU9pVaKCMWy_l_LV8-r3z2HRajh54V3cEsSiG5CF5_EVeFJzAzQTGd79k-AjLERnGw7kNMs4LWMhPS-00_R3nRt_OPxiVnSY_vNyT3HHpf8Lf7NQnZQQ7jM6d3BBSmIUlvlECPBpaVgP6oc1FKSkSPs-6DGL-DkJW3Xo0WlcJKwl7rIXjCrM0t6n3ioRNkxBOg3grZKqF12fnWOn-jtqr0V0Iw4Lf-3Gh007OcyCIy1-RENp6DXM8JKsg1XwQTo7OfDfyf3ZSDWOLan4L6hrHPXKBKtk0m1fJvJQ9dwEM3jzPWJBilBQDI_09Nr2MCbLzNTGi2wzGMlMt4B8u7g6B5wmRWKDZchS0pSFgP8B6maEEZ8JH-c6p7wk6YfeMEC2Ih-KN9IEUvnsh-b6jj0FwcqtpWKlHBJFWJtGnXMT8rDuYX5Mm_-lAWornFLriTA8I9uu1ZOGiej0pWVgoQVWFawXYkYuoZRW5q4OGBwpiPtZIYAyDoZeAUOu7FAqrTBA2NfYfJr9vsXJOaDiYPDHRgf9IPb4xQHM0YSgpvkCDTERAkFVgQ0lLemlf2qcUXjgmQg2MNuI1NcMCu9A9o8-g15M6Sswsu2uLf8PD13MAUsf2bSudfdKaViZvkMCJ-VgQKsy2y-9J6nybC5tzJ9S3yfnlqMyHkbrxFAUf7NnocSzZcRtuRUpuGZsx20gb8xHIA7aUuwd41zsDvsOUpovILruvtFXnA2_18wbHXFKUGmKPHYYGLsz3rhJNtjs0dZF8EDD2XVmxsow3EHn4CXSQkJ8x3D5sDdyQE74fx_9l-BybhGK0-Ww_qLjHwwArVN6GcDacya-onH823CihgmmZKN3bg_XP0Q1c37IUApEO-R6ywQpAOWGv_re4uecj_1jmbBAxwRcvCNpNSwoGTm8_KSozpV6-vadvp_RC3TDHkH7f97yLxJ7ROIt5J8cQl-9eNJBHtVvWv0H0oe8V42gg4FsXB7_Fv8Ou9YUFWaJYb7FVU3IyWGVNYJyPoT662ImG2kQQHTzoNdHPdqTT_kh421XyfaJINAHA3KzKTcOq_4uNp3hq158xepsHM8HLizQKPI_oM3qvpSMxj-BuMVfkDGTnsX-JLAe3NA8yuFiZXyziuYw6hC4rMLuV5UTNJZnGS-3EEGSXXHCfghBQslnMt4jDj1X9FYwL8cJCmPPC9sEgpCfBdPYZCJUjoxwd2i4Nd2vweECi1KOOoFCdmTcDcp6WmlQxv06XLgfCiyC50yBmqw034Ukq2IsrYFPDsITQIQG_HBAe6k-2dxanLxJGlZK6CPCx2MKGElRlIESSqa99pCuUgzdvs-_ZbG-fjr42LTHtP0hHJy_ngCjrt8IgDmUKI3xEvlXZRnxnp4jkH-7FwZoKkh01DjFYkAscw5BjAlcWFqgQFnqle20OyaUTMaYIvjf-0ZUOpGi_wab0RYW1i5s61xvKyIk_2evZ87LyS57WccbcLy88MJ26kRxPMf9rOcEetd1aZxykk73d7A_pj7zxIrvjeExHyxUrM0XFgLN79kvoEAhyhFdZ_FZItdc98yLjaToxZPORBhTn1w0nj4spz5FjshbItFfVLfGCsAxgxRI88AO2oB8389PNPMe8tA4uMPMC2PFTqK795Hek8Vos_khmzeiXwo1BQaVfwLglOeKhUBAuoVvCyh93vTjhapy14oMAt24rP1eeHnQjee5Lfb_8p3gXOMQ39yxQ0Ts32B-CfxQzbPQrRQtJls8Y6lVDr0oOFz1gMHDWRrzA5z3tqHpj0Cxe3R1luIIQ06DHrv73dswQFCY6mYUsMfumIz3WAO0sa7s8fzbGRpG4zcA5_zxQpkwOEmTbBf8n_7vCRaS3weOMVJBuNSJCiQGBHR2eESoSSbV_ESxcoPGf-Wz_Fam4chWBty66ZX9gMqaAE1zWKAGMEF9zlemaUpKjF_NQJkTSbvh94a6Rtr-WR9QhWFzNxPBPIxItxGb5yNTiGZ6Ie-tQJE2Kyd1SmcfUY5fJnCdItfpnyXL4WSAbSsob9XVg4Op0uBGG4yXL__kme-X8WI0wABAACDV6iueeDk3PptXUV0BSR3PCdB9sa2FWGoPt81rhXS1voD5ApICH0CYlLLFnsnBNNi0fB0f7ZKC8y4286yDEl0NhkKDvq2n9HkwBGA_oiFOcGotvk5QXufiP82pBzLwQOow95Fx6OM7HK_uPVjzxxdawXQgSdHoQiMJwbUK2UYbfr0iYvGr8ERELWRTOOiBcZYsSsNhYHMvwVW5ahDFqpCiW8JJOq6gjlJmZ3cvwVWD7kgLmJXMnnRqtqaYl9Uk0EBEw6CZI8R0Fprd4sn-AM5SIgL6PkVm0AsR9FkBxFO5F6x3-DMWIZnbpEFcOjgpkwAtbmPtesiKe7w_XeKXSYKPfzCM5wyVZ7sq4BZaQSMzOEOgpFp7_W4kjVZuWL4HvPBA0eaJkqCCnO9CvTPynRPisSgqY5zcysrcKLAAHSQ247c1yi8smlgYsFznlptT_2rAD8h2xfxUSv9KDaokZ9LROVtS1pGJumZfwAKuHqEis6B5GAG1uZw8SgmRDB5-_dcAQWOP6jgn5PBB08RKA4xGMxzHTTF0iQgF1HMX4ScdvPmR2tC1g2_z9NYw5VvHewjIQTVUgKhl6WkLiggz4qCItjEQ-sQaFctZo2QgTphAAhAPbVVKGmXydWSPn9-MLyRxMEFd_MFPx0xEKWUtWopZnXoAnB6cuRUlaR7Ex1bd9kSJeRT-zS9vg6SmVVeqqF10HbBydZAp2CPsaAXMzrohNXkjT1tHa5DFsGCWN8Pl96gZ4XU0hcy0-v_g66wmMXmP7XBBUEh8wlJ2tg5_32LC9uz3mUecfSbUnNnM7jzPEBx0MWh0T5W4oXWkjl0JtkiRFaawUveTNuckzEnkGqxWKC3Pfi-4_c19f14CGUzZTVXhAWYKQD15Ldl65r6xU7U87dFAQUOHcEY6KUiQ-xEZztcLU_KDfunv1hTy9IE73SiYpIvhvSeus46KY7z9D_G1Hw7nQFhHgxspVLEjejdXY5Pms0wE_YhQ-bkrCOPXpnJxE194xSi57ykPsPH5TBygVP_fwEFAdqOPwiKKQ4MV-d2G2-omn1DCyqoL0Vc-bvCee7FYytR_RFO2_xikbrBZwnj_buFvANP_K1TtKf04nY7mjKJiSbrTdpywo8PvxNB2JpBD9gkVPuA2oMFvUFHHownN0jBA9yWmiKpQTY_ZqT2TR2bmCTmwL3sZEdPVl0oaBlPiFZbDTLGgF-4fBlm_xZl1OiAhj4KxXwB7w_DqvCS0V34A0o-Su4VjZzaEqO3cTuPCBuJRfnExkN0QMMtx-OMPaumAQSyZ7-x27l3q_-q2ABDt7hOImYxGar-1FLvfxxmv_aAUPWCKHHyEk-TpdjgaLYs3EWC2FD-DNMegViiW_kEhe5hNwBo_JVCn82HCUH14yb3mZwFNe2vAp5WvSVoSdkBCgEELEZw33U_IZSQ5fm0BtguhMiFPbE86oWsZYU3cs3LiC3hW-hEBIIiqIh3zxWg7Z8AcaoK_0hQeGI2DANl22GKyVTRdHgB6Vv2Ggz-KqB3NYkLJ3AirxooP_x_mqVVoIj"}}],"authentication":["#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA"],"assertionMethod":["#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA"],"keyAgreement":["#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA"],"capabilityInvocation":["#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA"],"capabilityDelegation":["#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI","#QDsGIX_7NfNEaXdEeV7PJ5e_CwoH5LlF3srsCp5dcHA"],"service":[{"id":"#TrustchainID","type":"Identity","serviceEndpoint":"https://identity.foundation/ion/trustchain-root-plus-2"},{"id":"#RSSPublicKey","type":"IPFSKey","serviceEndpoint":"QmdPZgcyqHJTiPeGMcAu2AAkZZ1U4KtdQXid1gdJQtpvyU"}]},"did_doc_meta":{"method":{"recoveryCommitment":"EiCy4pW16uB7H-ijA6V6jO6ddWfGCwqNcDSJpdv_USzoRA","updateCommitment":"EiB8B_LS_O3NWo2P8fSuRwS32GODaXoLREZHdqpg6x86yA","published":true},"canonicalId":"did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q","proof":{"type":"JsonWebSignature2020","id":"did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A","proofValue":"eyJhbGciOiJFUzI1NksifQ.IkVpQV91YUV2QjctR0FyRTlkeERuMk1rclRUa0t0VXN4eGJPc1NESzhwQjl0ZWci.X94wTgzsovLEAXU1CG5M0Gqs6Gu9oHklr4Zn7aEbrdtOI_WCSCrWJuYomkcdeF8X5dV_ApZ6Gh08pPcV2VSClQ"}},"chunk_file":[31,139,8,0,0,0,0,0,0,3,229,147,79,115,162,76,16,198,191,11,231,240,70,197,8,230,38,50,138,18,64,16,212,184,149,178,6,152,0,242,103,6,102,64,97,43,223,125,199,36,149,125,79,123,217,220,246,48,85,93,205,211,221,195,211,191,249,41,68,40,103,144,10,143,63,126,10,4,178,48,65,31,49,12,89,138,75,225,81,168,17,201,97,136,132,59,33,194,97,83,160,146,9,143,92,218,4,121,26,26,168,251,80,167,17,87,78,231,166,231,74,81,187,108,47,197,196,176,59,120,0,96,101,27,10,176,61,134,50,177,148,213,221,116,187,83,235,17,160,188,27,235,8,226,69,107,138,203,61,10,182,105,92,66,214,212,104,52,24,13,248,215,175,254,235,75,118,155,23,214,45,23,83,20,146,209,195,36,27,114,69,198,58,158,1,115,30,94,121,32,187,200,209,245,142,46,175,65,215,24,192,41,40,113,236,243,147,140,29,191,210,188,60,242,244,38,156,238,36,177,163,152,87,220,74,179,125,91,108,229,163,221,106,126,178,80,104,103,15,148,141,218,3,2,50,73,61,154,77,147,101,45,88,219,198,246,92,1,225,237,118,163,154,96,250,238,142,0,41,69,245,205,31,19,177,4,71,188,33,108,88,194,173,73,67,248,110,27,191,30,234,102,113,141,208,187,97,119,66,8,9,12,210,60,101,221,170,108,241,151,234,119,90,67,57,138,63,210,47,111,47,119,252,87,235,54,13,209,255,236,245,234,134,242,253,192,180,92,105,191,253,91,69,183,177,220,139,175,18,80,70,4,167,183,53,9,9,99,132,62,222,223,167,159,162,255,94,113,83,70,239,83,238,111,135,125,181,20,107,140,153,240,246,242,118,155,221,16,174,65,115,92,20,41,251,88,184,0,82,109,231,2,175,61,106,211,116,235,151,184,17,117,127,214,63,60,23,217,105,33,49,210,245,177,188,88,14,207,145,187,20,143,110,204,253,250,107,154,178,115,85,215,210,220,203,138,188,95,31,119,120,192,55,114,181,168,210,214,198,131,13,104,118,178,67,172,218,227,45,50,157,239,167,9,206,136,129,131,141,173,232,202,197,110,69,188,244,20,67,178,160,168,228,162,58,156,1,169,81,143,245,126,233,77,22,107,255,147,166,136,18,80,229,108,198,114,207,88,203,225,206,221,156,98,211,204,202,78,219,84,254,69,92,235,57,185,108,71,197,162,73,6,255,8,77,34,201,27,42,14,255,8,213,124,0,162,75,239,132,149,249,108,29,78,18,172,240,209,242,97,51,158,25,246,147,20,31,156,135,77,47,205,188,116,88,157,210,217,119,64,133,54,221,129,66,171,135,74,208,236,39,177,117,128,120,121,28,200,79,166,119,141,159,230,83,67,14,3,184,58,175,210,222,91,125,63,84,131,178,116,69,210,143,192,113,249,26,200,96,88,189,54,122,82,106,174,50,26,235,137,154,226,107,63,6,34,80,205,210,28,127,66,85,239,43,109,183,150,146,225,196,245,134,150,232,83,89,151,175,238,181,13,230,3,63,55,77,199,137,175,197,193,62,68,227,224,249,159,130,106,244,71,168,84,237,149,26,173,10,183,179,103,254,128,137,44,163,205,212,210,109,226,239,93,51,9,125,203,92,91,186,167,237,29,235,50,186,220,26,253,2,202,202,18,61,4,7,0,0],"provisional_index_file":[31,139,8,0,0,0,0,0,0,3,171,86,74,206,40,205,203,46,86,178,138,174,134,48,221,50,115,82,67,139,50,149,172,148,2,115,195,83,189,77,3,146,188,29,131,43,253,178,92,35,189,189,204,140,44,243,204,42,74,203,115,19,139,162,66,34,205,10,138,82,12,45,29,253,10,74,204,10,43,253,148,106,99,107,1,80,57,150,45,78,0,0,0],"core_index_file":[31,139,8,0,0,0,0,0,0,3,133,144,221,142,162,48,0,70,223,165,215,67,34,136,69,189,107,65,20,196,65,228,71,119,55,27,211,129,162,229,183,219,34,35,24,223,125,221,125,128,153,235,47,57,231,228,123,0,46,218,158,73,214,54,164,114,154,140,222,109,86,209,88,48,176,4,65,157,159,16,209,118,114,35,121,26,133,131,184,233,173,250,81,236,131,149,231,49,168,73,161,113,180,101,163,45,59,114,15,37,120,3,45,167,130,116,47,148,4,203,7,72,5,37,29,5,203,95,15,32,111,121,206,238,22,233,200,191,33,163,85,71,54,68,94,95,142,21,195,37,58,45,6,37,146,103,201,118,99,52,150,57,26,247,219,189,227,124,224,201,116,186,175,236,201,161,234,237,133,49,100,238,229,229,16,52,109,123,42,6,179,173,107,214,213,180,233,254,83,204,161,238,85,195,95,99,36,13,234,213,181,142,157,147,101,6,56,185,102,126,140,78,179,32,227,142,120,215,67,107,53,251,4,207,231,219,55,77,184,236,39,197,108,110,96,43,140,10,183,115,122,205,114,109,223,223,148,16,106,205,34,110,175,106,223,97,242,99,234,110,209,23,77,149,79,142,67,186,238,213,90,9,104,17,23,120,226,169,243,53,180,18,59,161,81,96,254,52,111,209,65,212,163,137,131,203,247,77,86,68,108,228,159,9,133,83,87,247,118,136,163,157,178,72,80,59,111,46,179,121,20,113,109,171,104,66,109,104,9,171,224,171,159,116,126,84,225,13,27,27,133,21,8,38,176,240,97,150,29,243,181,249,249,231,61,181,66,151,103,253,57,14,199,246,128,94,77,191,159,207,191,142,167,192,117,34,2,0,0],"transaction":[2,0,0,0,1,113,221,4,189,16,26,231,2,48,224,28,93,57,7,140,195,149,161,45,117,110,230,205,103,61,52,184,254,125,243,83,89,1,0,0,0,106,71,48,68,2,32,33,204,63,234,205,220,221,165,43,15,131,19,214,231,83,195,252,217,246,170,251,83,229,47,78,58,174,92,91,222,243,186,2,32,71,116,233,174,111,54,233,197,138,99,93,100,175,153,165,194,166,101,203,26,217,146,169,131,208,230,247,254,171,12,5,2,1,33,3,210,138,101,166,212,146,135,234,245,80,56,11,62,159,113,207,113,16,105,102,75,44,32,130,109,119,241,154,12,3,85,7,255,255,255,255,2,0,0,0,0,0,0,0,0,54,106,52,105,111,110,58,51,46,81,109,82,118,103,90,109,52,74,51,74,83,120,102,107,52,119,82,106,69,50,117,50,72,105,50,85,55,86,109,111,98,89,110,112,113,104,113,72,53,81,80,54,74,57,55,109,76,238,0,0,0,0,0,25,118,169,20,199,246,99,10,196,245,226,169,38,84,22,59,206,40,9,49,99,20,24,221,136,172,0,0,0,0],"merkle_block":[0,224,228,44,50,91,136,90,53,184,101,89,134,219,136,40,143,2,100,212,246,127,92,201,14,109,13,17,39,0,0,0,0,0,0,0,105,173,156,82,17,65,101,68,32,6,152,112,104,119,198,46,124,201,58,41,245,245,163,29,5,181,212,9,82,121,206,125,61,49,81,99,192,255,63,25,113,234,45,246,29,0,0,0,6,3,211,202,105,163,97,74,203,69,161,73,102,200,18,205,158,224,52,199,5,242,15,172,61,175,143,121,108,153,244,216,5,165,253,142,118,26,226,235,158,11,14,77,98,209,149,153,88,111,185,142,138,123,230,252,113,19,68,30,85,111,179,31,248,44,156,234,132,87,199,197,126,65,242,234,243,46,166,97,119,197,11,227,194,64,83,68,66,52,146,13,149,202,60,196,157,0,163,31,110,109,24,100,1,127,156,249,212,139,81,39,72,113,196,112,14,112,145,223,239,20,175,156,146,197,52,2,21,183,216,140,200,32,33,136,227,131,123,23,29,186,20,255,237,232,241,69,178,200,124,29,188,54,66,102,153,48,81,121,88,251,117,66,156,69,172,170,81,196,22,178,131,96,77,81,95,128,249,93,219,79,97,14,141,219,120,118,152,87,19,135,118,2,175,0],"block_header":[0,224,228,44,50,91,136,90,53,184,101,89,134,219,136,40,143,2,100,212,246,127,92,201,14,109,13,17,39,0,0,0,0,0,0,0,105,173,156,82,17,65,101,68,32,6,152,112,104,119,198,46,124,201,58,41,245,245,163,29,5,181,212,9,82,121,206,125,61,49,81,99,192,255,63,25,113,234,45,246]}"##; diff --git a/trustchain-http/src/errors.rs b/trustchain-http/src/errors.rs index 1e05bfaa..0192fd9e 100644 --- a/trustchain-http/src/errors.rs +++ b/trustchain-http/src/errors.rs @@ -1,14 +1,18 @@ //! Error type and conversions. use axum::{response::IntoResponse, Json}; use hyper::StatusCode; +use josekit::JoseError; use serde_json::json; use thiserror::Error; use trustchain_core::{ - commitment::CommitmentError, issuer::IssuerError, key_manager::KeyManagerError, - resolver::ResolverError, vc::CredentialError, verifier::VerifierError, vp::PresentationError, + attestor::AttestorError, commitment::CommitmentError, issuer::IssuerError, + key_manager::KeyManagerError, resolver::ResolverError, vc::CredentialError, + verifier::VerifierError, vp::PresentationError, }; use trustchain_ion::root::TrustchainRootError; +use crate::attestation_utils::TrustchainCRError; + /// Trustchain HTTP error type. // TODO: refine and add doc comments for error variants #[derive(Error, Debug)] @@ -27,8 +31,15 @@ pub enum TrustchainHTTPError { RootError(TrustchainRootError), #[error("Trustchain presentation error: {0}")] PresentationError(PresentationError), + #[error("Trustchain attestor error: {0}")] + AttestorError(#[from] AttestorError), + // TODO: once needed in http propagate + #[error("Jose error: {0}")] + JoseError(#[from] JoseError), #[error("Trustchain key manager error: {0}")] KeyManagerError(KeyManagerError), + #[error("Trustchain challenge-response error: {0}")] + CRError(TrustchainCRError), #[error("Credential does not exist.")] CredentialDoesNotExist, #[error("No issuer available.")] @@ -42,9 +53,11 @@ pub enum TrustchainHTTPError { #[error("Request does not exist.")] RequestDoesNotExist, #[error("Could not deserialize data: {0}")] - FailedToDeserialize(serde_json::Error), + FailedToDeserialize(#[from] serde_json::Error), #[error("Root event time not configured for verification.")] RootEventTimeNotSet, + #[error("Attestation request failed.")] + FailedAttestationRequest, } impl From for TrustchainHTTPError { @@ -89,6 +102,12 @@ impl From for TrustchainHTTPError { } } +impl From for TrustchainHTTPError { + fn from(err: TrustchainCRError) -> Self { + TrustchainHTTPError::CRError(err) + } +} + // See axum IntoRespone example: // https://github.com/tokio-rs/axum/blob/main/examples/jwt/src/main.rs#L147-L160 @@ -109,6 +128,9 @@ impl IntoResponse for TrustchainHTTPError { err @ TrustchainHTTPError::IssuerError(_) => { (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) } + err @ TrustchainHTTPError::AttestorError(_) => { + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + } err @ TrustchainHTTPError::CommitmentError(_) => { (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) } @@ -127,6 +149,12 @@ impl IntoResponse for TrustchainHTTPError { err @ TrustchainHTTPError::KeyManagerError(_) => { (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) } + err @ TrustchainHTTPError::JoseError(_) => { + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + } + err @ TrustchainHTTPError::CRError(_) => { + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + } err @ TrustchainHTTPError::CredentialDoesNotExist => { (StatusCode::BAD_REQUEST, err.to_string()) } @@ -164,6 +192,9 @@ impl IntoResponse for TrustchainHTTPError { err @ TrustchainHTTPError::RootEventTimeNotSet => { (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) } + err @ TrustchainHTTPError::FailedAttestationRequest => { + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + } }; let body = Json(json!({ "error": err_message })); (status, body).into_response() diff --git a/trustchain-http/src/issuer.rs b/trustchain-http/src/issuer.rs index 226c80df..605a1fd4 100644 --- a/trustchain-http/src/issuer.rs +++ b/trustchain-http/src/issuer.rs @@ -249,6 +249,7 @@ mod tests { vc::{Credential, CredentialSubject, Issuer, URI}, }; use std::{collections::HashMap, sync::Arc}; + use trustchain_core::utils::init; use trustchain_core::{utils::canonicalize, verifier::Verifier}; use trustchain_ion::{trustchain_resolver, verifier::TrustchainVerifier}; @@ -337,6 +338,7 @@ mod tests { #[tokio::test] #[ignore = "integration test requires ION, MongoDB, IPFS and Bitcoin RPC"] async fn test_post_issuer_credential() { + init(); let app = TrustchainRouter::from(Arc::new(AppState::new_with_cache( TEST_HTTP_CONFIG.to_owned(), serde_json::from_str(CREDENTIALS).unwrap(), @@ -390,6 +392,7 @@ mod tests { #[tokio::test] #[ignore = "integration test requires ION, MongoDB, IPFS and Bitcoin RPC"] async fn test_post_issuer_rss_credential() { + init(); let app = TrustchainRouter::from(Arc::new(AppState::new_with_cache( TEST_HTTP_CONFIG.to_owned(), serde_json::from_str(CREDENTIALS).unwrap(), diff --git a/trustchain-http/src/lib.rs b/trustchain-http/src/lib.rs index 83edc97d..b8d92d72 100644 --- a/trustchain-http/src/lib.rs +++ b/trustchain-http/src/lib.rs @@ -1,4 +1,7 @@ //! Trustchain HTTP server functionality. +pub mod attestation_encryption_utils; +pub mod attestation_utils; +pub mod attestor; pub mod config; #[cfg(test)] pub(crate) mod data; @@ -7,10 +10,15 @@ pub mod ion; pub mod issuer; pub mod middleware; pub mod qrcode; +pub mod requester; pub mod resolver; pub mod root; pub mod server; pub mod state; pub mod static_handlers; pub mod store; +pub mod utils; pub mod verifier; + +/// Fragment for service ID of Trustchain attestion +pub(crate) const ATTESTATION_FRAGMENT: &str = "#TrustchainAttestation"; diff --git a/trustchain-http/src/requester.rs b/trustchain-http/src/requester.rs new file mode 100644 index 00000000..3714e05f --- /dev/null +++ b/trustchain-http/src/requester.rs @@ -0,0 +1,320 @@ +use std::{collections::HashMap, path::PathBuf}; + +use josekit::{jwk::Jwk, jwt::JwtPayload}; +use serde_json::Value; +use ssi::did::Service; +use trustchain_core::utils::generate_key; +use trustchain_ion::attestor::IONAttestor; + +use crate::{ + attestation_encryption_utils::{ + josekit_to_ssi_jwk, ssi_to_josekit_jwk, DecryptVerify, Entity, SignEncrypt, + }, + attestation_utils::{ + attestation_request_path, matching_endpoint, ContentCRChallenge, ContentCRInitiation, + ElementwiseSerializeDeserialize, IdentityCRChallenge, IdentityCRInitiation, + RequesterDetails, + }, + attestation_utils::{CustomResponse, Nonce, TrustchainCRError}, + ATTESTATION_FRAGMENT, +}; + +/// Initiates part 1 attestation request (identity challenge-response). +/// +/// This function generates a temporary key to use as an identifier throughout the challenge-response process. +/// It prompts the user to provide the organization name and operator name, which are included in the POST request +/// to the endpoint specified in the attestor's DID document. +pub async fn initiate_identity_challenge( + org_name: &str, + op_name: &str, + services: &[Service], +) -> Result<(IdentityCRInitiation, PathBuf), TrustchainCRError> { + // generate temp key + let temp_s_key_ssi = generate_key(); + let temp_p_key_ssi = temp_s_key_ssi.to_public(); + let temp_s_key = + ssi_to_josekit_jwk(&temp_s_key_ssi).map_err(|_| TrustchainCRError::FailedToGenerateKey)?; + let temp_p_key = + ssi_to_josekit_jwk(&temp_p_key_ssi).map_err(|_| TrustchainCRError::FailedToGenerateKey)?; + + // make identity_cr_initiation struct + let requester = RequesterDetails { + requester_org: org_name.to_owned(), + operator_name: op_name.to_owned(), + }; + let mut identity_cr_initiation = IdentityCRInitiation { + temp_s_key: None, + temp_p_key: Some(temp_p_key.clone()), + requester_details: Some(requester.clone()), + }; + + // get endpoint and uri + let url_path = "/did/attestor/identity/initiate"; + let endpoint = matching_endpoint(services, ATTESTATION_FRAGMENT)?; + let uri = format!("{}{}", endpoint, url_path); + + // make POST request to endpoint + let client = reqwest::Client::new(); + let result = client + .post(uri) + .json(&identity_cr_initiation) + .send() + .await + .map_err(TrustchainCRError::Reqwest)?; + + if result.status() != 200 { + return Err(TrustchainCRError::FailedToInitiateCR); + } + // create new directory for attestation request + let path = attestation_request_path(&temp_s_key_ssi.to_public(), "requester")?; + std::fs::create_dir_all(&path).map_err(|_| TrustchainCRError::FailedAttestationRequest)?; + + // Add secret key to struct + identity_cr_initiation.temp_s_key = Some(temp_s_key); + + Ok((identity_cr_initiation, path)) +} + +/// Generates and posts response for part 1 of attesation process (identity challenge-response). +/// +/// This function first decrypts and verifies the challenge received from attestor to extract +/// challenge nonce. It then signs the nonce with the requester's temporary secret key and +/// encrypts it with the attestor's public key, before posting the response to the attestor. +/// If post request is successful, the updated ```CRIdentityChallenge``` is returned. +pub async fn identity_response( + path: &PathBuf, + services: &[Service], + attestor_p_key: &Jwk, +) -> Result { + // deserialise challenge struct from file + let mut identity_challenge = IdentityCRChallenge::new() + .elementwise_deserialize(path)? + .ok_or(TrustchainCRError::FailedToDeserialize)?; + // get temp secret key from file + let identity_initiation = IdentityCRInitiation::new() + .elementwise_deserialize(path)? + .ok_or(TrustchainCRError::FailedToDeserialize)?; + let temp_s_key = identity_initiation.temp_s_key()?; + let temp_s_key_ssi = josekit_to_ssi_jwk(temp_s_key)?; + + // decrypt and verify challenge + let requester = Entity {}; + let decrypted_verified_payload = requester.decrypt_and_verify( + identity_challenge + .identity_challenge_signature + .clone() + .ok_or(TrustchainCRError::FieldNotFound)?, + temp_s_key, + attestor_p_key, + )?; + // sign and encrypt response + let signed_encrypted_response = requester.sign_and_encrypt_claim( + &decrypted_verified_payload, + temp_s_key, + attestor_p_key, + )?; + let key_id = temp_s_key_ssi.to_public().thumbprint()?; + // get uri for POST request response + let endpoint = matching_endpoint(services, ATTESTATION_FRAGMENT)?; + let url_path = "/did/attestor/identity/respond"; + let uri = format!("{}{}/{}", endpoint, url_path, key_id); + // POST response + let client = reqwest::Client::new(); + let result = client + .post(uri) + .json(&signed_encrypted_response) + .send() + .await + .map_err(TrustchainCRError::Reqwest)?; + if result.status() != 200 { + return Err(TrustchainCRError::FailedToRespond(result)); + } + // extract nonce + let nonce_str = decrypted_verified_payload + .claim("identity_nonce") + .ok_or(TrustchainCRError::ClaimNotFound)? + .as_str() + .ok_or(TrustchainCRError::FailedToConvertToStr( + // Unwrap: not None since error would have propagated above if None + decrypted_verified_payload + .claim("identity_nonce") + .unwrap() + .clone(), + ))?; + let nonce = Nonce::from(String::from(nonce_str)); + // update struct + identity_challenge.update_p_key = Some(attestor_p_key.clone()); + identity_challenge.identity_nonce = Some(nonce); + identity_challenge.identity_response_signature = Some(signed_encrypted_response); + + Ok(identity_challenge) +} + +/// Initiates part 2 attestation request (content challenge-response). +/// +/// This function posts the to be attested to candidate DID (dDID) to the attestor's endpoint. +/// If the post request is successful, the response body contains the signed and encrypted +/// challenge payload with a hashmap that contains an encrypted nonce per signing key. +/// The response to the challenge is generated and posted to the attestor's endpoint. +/// If the post request and the verification of the response are successful, the +/// ```ContentCRInitiation``` and ```CRContentChallenge``` structs are returned. +pub async fn initiate_content_challenge( + path: &PathBuf, + ddid: &str, + services: &[Service], + attestor_p_key: &Jwk, +) -> Result<(ContentCRInitiation, ContentCRChallenge), TrustchainCRError> { + // deserialise identity_cr_initiation and get key id + let identity_cr_initiation = IdentityCRInitiation::new() + .elementwise_deserialize(path)? + .ok_or(TrustchainCRError::FailedToDeserialize)?; + let temp_s_key_ssi = josekit_to_ssi_jwk(&identity_cr_initiation.temp_s_key().cloned()?)?; + let key_id = temp_s_key_ssi.to_public().thumbprint()?; + + let content_cr_initiation = ContentCRInitiation { + requester_did: Some(ddid.to_owned()), + }; + // get uri for POST request response + let endpoint = matching_endpoint(services, ATTESTATION_FRAGMENT)?; + let url_path = "/did/attestor/content/initiate"; + let uri = format!("{}{}/{}", endpoint, url_path, key_id); + // make POST request to endpoint + let client = reqwest::Client::new(); + let result = client + .post(uri) + .json(&ddid) + .send() + .await + .map_err(TrustchainCRError::Reqwest)?; + if result.status() != 200 { + println!("Status code: {}", result.status()); + return Err(TrustchainCRError::FailedToRespond(result)); + } + + let response_body: CustomResponse = result.json().await.map_err(TrustchainCRError::Reqwest)?; + let signed_encrypted_challenge = response_body + .data + .ok_or(TrustchainCRError::ResponseMustContainData)?; + + // response + let (nonces, response) = content_response( + path, + &signed_encrypted_challenge.to_string(), + services, + attestor_p_key.clone(), + ddid, + ) + .await?; + let content_challenge = ContentCRChallenge { + content_nonce: Some(nonces), + content_challenge_signature: Some(signed_encrypted_challenge.to_string()), + content_response_signature: Some(response), + }; + Ok((content_cr_initiation, content_challenge)) +} + +/// Generates the response for the content challenge-response process and makes a POST request to +/// the attestor endpoint. +/// +/// This function first decrypts (temporary secret key) and verifies (attestor's public key) the +/// challenge received from attestor to extract challenge nonces. It then decrypts each nonce with +/// the corresponding signing key from the requestor's candidate DID (dDID) document, before +/// posting the signed (temporary secret key) and encrypted (attestor's public key) response to +/// the attestor's endpoint. +/// If successful, the nonces and the (signed and encrypted) response are returned. +pub async fn content_response( + path: &PathBuf, + challenge: &str, + services: &[Service], + attestor_p_key: Jwk, + ddid: &str, +) -> Result<(HashMap, String), TrustchainCRError> { + // get keys + let identity_initiation = IdentityCRInitiation::new() + .elementwise_deserialize(path)? + .ok_or(TrustchainCRError::FailedToDeserialize)?; + let temp_s_key = identity_initiation.temp_s_key()?; + let temp_s_key_ssi = josekit_to_ssi_jwk(temp_s_key)?; + // get endpoint + let key_id = temp_s_key_ssi.to_public().thumbprint()?; + let endpoint = matching_endpoint(services, ATTESTATION_FRAGMENT)?; + let url_path = "/did/attestor/content/respond"; + let uri = format!("{}{}/{}", endpoint, url_path, key_id); + + // decrypt and verify payload + let requester = Entity {}; + let decrypted_verified_payload = + requester.decrypt_and_verify(challenge.to_owned(), temp_s_key, &attestor_p_key)?; + // extract map with decrypted nonces from payload and decrypt each nonce + let challenges_map: HashMap = serde_json::from_value( + decrypted_verified_payload + .claim("challenges") + .ok_or(TrustchainCRError::ClaimNotFound)? + .clone(), + )?; + + // keymap with requester secret keys + let ion_attestor = IONAttestor::new(ddid); + let signing_keys = ion_attestor.signing_keys()?; + // iterate over all keys, convert to Jwk (josekit) + let mut signing_keys_map: HashMap = HashMap::new(); + for key in signing_keys { + let key_id = key.thumbprint()?; + let jwk = ssi_to_josekit_jwk(&key)?; + signing_keys_map.insert(key_id, jwk); + } + + // TODO: make functional version work with error propagation for HashMap fold + // let signing_keys_map = signing_keys + // .into_iter() + // .fold(HashMap::new(), |mut acc, key| { + // let key_id = key.thumbprint().unwrap(); + // let jwk = ssi_to_josekit_jwk(&key); + // acc.insert(key_id, jwk); + // acc + // }); + + let mut decrypted_nonces: HashMap = HashMap::new(); + for (key_id, nonce) in challenges_map.iter() { + let payload = requester.decrypt( + &Value::from(nonce.clone()), + signing_keys_map + .get(key_id) + .ok_or(TrustchainCRError::KeyNotFound)?, + )?; + decrypted_nonces.insert( + String::from(key_id), + Nonce::from( + payload + .claim("nonce") + .ok_or(TrustchainCRError::ClaimNotFound)? + .as_str() + .ok_or(TrustchainCRError::FailedToConvertToStr( + // Unwrap: not None since error would have propagated above if None + payload.claim("nonce").unwrap().clone(), + ))? + .to_string(), + ), + ); + } + + // sign and encrypt response + let value: serde_json::Value = serde_json::to_value(&decrypted_nonces)?; + let mut payload = JwtPayload::new(); + payload.set_claim("nonces", Some(value))?; + let signed_encrypted_response = + requester.sign_and_encrypt_claim(&payload, temp_s_key, &attestor_p_key)?; + // post response to endpoint + let client = reqwest::Client::new(); + let result = client + .post(uri) + .json(&signed_encrypted_response) + .send() + .await + .map_err(TrustchainCRError::Reqwest)?; + if result.status() != 200 { + println!("Status code: {}", result.status()); + return Err(TrustchainCRError::FailedToRespond(result)); + } + Ok((decrypted_nonces, signed_encrypted_response)) +} diff --git a/trustchain-http/src/resolver.rs b/trustchain-http/src/resolver.rs index 92b6a84d..2581b528 100644 --- a/trustchain-http/src/resolver.rs +++ b/trustchain-http/src/resolver.rs @@ -114,7 +114,6 @@ impl TrustchainHTTPHandler { State(app_state): State>, ) -> impl IntoResponse { debug!("Received DID to get trustchain: {}", did.as_str()); - // let mut verifier = .write().await; TrustchainHTTPHandler::resolve_chain( &did, &app_state.verifier, diff --git a/trustchain-http/src/server.rs b/trustchain-http/src/server.rs index 1bcbee8f..b2553b00 100644 --- a/trustchain-http/src/server.rs +++ b/trustchain-http/src/server.rs @@ -1,4 +1,6 @@ //! Trustchain HTTP router type and functionality for spawning HTTP and HTTPS servers. + +use crate::attestor; use crate::config::http_config; use crate::middleware::validate_did; use crate::{ @@ -126,6 +128,46 @@ impl TrustchainRouter { move |operation| crate::ion::post_operation(operation, state) }), ) + .route( + "/did/attestor/identity/initiate", + post(attestor::TrustchainAttestorHTTPHandler::post_identity_initiation), + ) + .route( + "/did/attestor/identity/respond/:key_id", + post({ + let state = shared_state.clone(); + move |(key_id, response)| { + attestor::TrustchainAttestorHTTPHandler::post_identity_response( + (key_id, response), + state, + ) + } + }), + ) + .route( + "/did/attestor/content/initiate/:key_id", + // post(attestor::TrustchainAttestorHTTPHandler::post_content_initiation), + post({ + let state = shared_state.clone(); + move |(key_id, ddid)| { + attestor::TrustchainAttestorHTTPHandler::post_content_initiation( + (key_id, ddid), + state, + ) + } + }), + ) + .route( + "/did/attestor/content/respond/:key_id", + post({ + let state = shared_state.clone(); + move |key_id| { + attestor::TrustchainAttestorHTTPHandler::post_content_response( + key_id, state, + ) + } + }), + ) .with_state(shared_state), } } diff --git a/trustchain-http/src/state.rs b/trustchain-http/src/state.rs index 11d9ef4d..45aaa10b 100644 --- a/trustchain-http/src/state.rs +++ b/trustchain-http/src/state.rs @@ -38,7 +38,7 @@ impl AppState { .unwrap_or_default() .as_slice(), ) - .expect("Credential cache could not be deserialized."); + .unwrap_or_default(); let root_candidates = RwLock::new(HashMap::new()); let presentation_requests: HashMap = serde_json::from_reader( std::fs::read(std::path::Path::new(&path).join("presentations/requests/cache.json")) @@ -46,7 +46,7 @@ impl AppState { .unwrap_or_default() .as_slice(), ) - .expect("Presentation cache could not be deserialized."); + .unwrap_or_default(); Self { config, verifier, diff --git a/trustchain-http/src/temp_s_key.json b/trustchain-http/src/temp_s_key.json new file mode 100644 index 00000000..79927b1a --- /dev/null +++ b/trustchain-http/src/temp_s_key.json @@ -0,0 +1,9 @@ +{ + "temp_p_key": { + "kty": "EC", + "crv": "secp256k1", + "x": "JokHTNHd1lIw2EXUTV1RJL3wvWMgoIRHPaWxTHcyH9U", + "y": "z737jJY7kxW_lpE1eZur-9n9_HUEGFyBGsTdChzI4Kg", + "d": "CfdUwQ-CcBQkWpIDPjhSJAq2SCg6hAGdcvLmCj0aA-c" + } +} \ No newline at end of file diff --git a/trustchain-http/src/test.json b/trustchain-http/src/test.json new file mode 100644 index 00000000..0aa0af02 --- /dev/null +++ b/trustchain-http/src/test.json @@ -0,0 +1,13 @@ +{ + "temp_p_key": { + "kty": "EC", + "crv": "secp256k1", + "x": "JokHTNHd1lIw2EXUTV1RJL3wvWMgoIRHPaWxTHcyH9U", + "y": "z737jJY7kxW_lpE1eZur-9n9_HUEGFyBGsTdChzI4Kg", + "d": "CfdUwQ-CcBQkWpIDPjhSJAq2SCg6hAGdcvLmCj0aA-c" + }, + "requester_details": { + "requester_org": "myTrustworthyEntity", + "operator_name": "trustworthyOperator" + } +} \ No newline at end of file diff --git a/trustchain-http/src/test_p_key.json b/trustchain-http/src/test_p_key.json new file mode 100644 index 00000000..97bb895b --- /dev/null +++ b/trustchain-http/src/test_p_key.json @@ -0,0 +1,12 @@ +{ + "temp_p_key": { + "kty": "EC", + "crv": "secp256k1", + "x": "JokHTNHd1lIw2EXUTV1RJL3wvWMgoIRHPaWxTHcyH9U", + "y": "z737jJY7kxW_lpE1eZur-9n9_HUEGFyBGsTdChzI4Kg" + }, + "requester_details": { + "requester_org": "myTrustworthyEntity", + "operator_name": "trustworthyOperator" + } +} \ No newline at end of file diff --git a/trustchain-http/src/utils.rs b/trustchain-http/src/utils.rs new file mode 100644 index 00000000..6e29f52a --- /dev/null +++ b/trustchain-http/src/utils.rs @@ -0,0 +1,28 @@ +use crate::config::HTTPConfig; +use std::sync::Once; +use tokio::runtime::Runtime; +use trustchain_core::utils::init; + +static INIT_HTTP: Once = Once::new(); +pub fn init_http() { + INIT_HTTP.call_once(|| { + init(); + let http_config = HTTPConfig { + host: "127.0.0.1".parse().unwrap(), + port: 8081, + server_did: Some( + "did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A".to_owned(), + ), + root_event_time: Some(1666265405), + ..Default::default() + }; + + // Run test server in own thread + std::thread::spawn(|| { + let rt = Runtime::new().unwrap(); + rt.block_on(async { + crate::server::http_server(http_config).await.unwrap(); + }); + }); + }); +} diff --git a/trustchain-http/tests/attestation.rs b/trustchain-http/tests/attestation.rs new file mode 100644 index 00000000..c671a23c --- /dev/null +++ b/trustchain-http/tests/attestation.rs @@ -0,0 +1,213 @@ +/// Integration test for attestation challenge-response process. +use trustchain_core::verifier::Verifier; +use trustchain_http::attestation_encryption_utils::{josekit_to_ssi_jwk, ssi_to_josekit_jwk}; +use trustchain_http::attestation_utils::{ + attestation_request_path, CRState, ElementwiseSerializeDeserialize, IdentityCRChallenge, + IdentityCRInitiation, +}; +use trustchain_http::attestor::present_identity_challenge; +use trustchain_http::requester::{ + identity_response, initiate_content_challenge, initiate_identity_challenge, +}; + +use trustchain_http::utils::init_http; +use trustchain_ion::{trustchain_resolver, verifier::TrustchainVerifier}; + +// The root event time of DID documents used in integration test below. +const ROOT_EVENT_TIME_1: u64 = 1666265405; + +use mockall::automock; +use trustchain_core::utils::extract_keys; + +#[automock] +pub trait AttestationUtils { + fn attestation_request_path(&self) -> String; +} + +#[tokio::test] +#[ignore] +async fn attestation_challenge_response() { + // Set-up: init test paths, get upstream info + init_http(); + + // |--------------------------------------------------------------| + // |------------| Part 1: identity challenge-response |------------| + // |--------------------------------------------------------------| + + // |------------| requester |------------| + // Use ROOT_PLUS_1 as attestor. Run server on localhost:8081. + let attestor_did = "did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A"; + let resolver = trustchain_resolver("http://localhost:8081/"); + let verifier = TrustchainVerifier::new(resolver); + let resolver = verifier.resolver(); + // Verify the attestor did to make sure we can trust the endpoint. + let result = verifier.verify(attestor_did, ROOT_EVENT_TIME_1).await; + assert!(result.is_ok()); + // Resolve did document. + let result = resolver.resolve_as_result(attestor_did).await; + assert!(result.is_ok()); + // Get services from did document. + let (_, attestor_doc, _) = result.unwrap(); + let attestor_doc = attestor_doc.as_ref().unwrap(); + let services = attestor_doc.service.as_ref().unwrap(); + + // Part 1.1: The requester initiates the attestation request (identity initiation). + // The requester generates a temporary key pair and sends the public key to the attestor via + // a POST request, together with the organization name and operator name. + let expected_org_name = String::from("My Org"); + let expected_operator_name = String::from("Some Operator"); + + let result = + initiate_identity_challenge(&expected_org_name, &expected_operator_name, services).await; + // Make sure initiation was successful and information is complete before serializing. + assert!(result.is_ok()); + let (identity_initiation_requester, requester_path) = result.unwrap(); + let result = identity_initiation_requester.elementwise_serialize(&requester_path); + assert!(result.is_ok()); + + // |------------| attestor |------------| + // Part 1.2: check the serialized data matches that received in 1.1. In deployment, this step is + // done manually using `trustchain-cli`, where the attestor has to confirm that they recognize + // the requester and that they want to proceed with challenge-response process + // for attestation. + let temp_p_key = + josekit_to_ssi_jwk(&identity_initiation_requester.clone().temp_p_key.unwrap()).unwrap(); + let attestor_path = attestation_request_path(&temp_p_key, "attestor").unwrap(); + + // Deserialized received information and check that it is correct. + let identity_initiation_attestor = IdentityCRInitiation::new() + .elementwise_deserialize(&attestor_path) + .unwrap() + .unwrap(); + // Make sure that attestor has all required information about initiation (but not secret key). + assert!(identity_initiation_attestor.is_complete()); + assert!(identity_initiation_attestor.temp_s_key.is_none()); + let org_name = identity_initiation_attestor + .requester_details + .clone() + .unwrap() + .requester_org; + let operator_name = identity_initiation_attestor + .requester_details + .clone() + .unwrap() + .operator_name; + assert_eq!(expected_org_name, org_name); + assert_eq!(expected_operator_name, operator_name); + + // If data matches, proceed with presenting signed and encrypted identity challenge payload. + let temp_p_key = identity_initiation_attestor.clone().temp_p_key.unwrap(); + let result = present_identity_challenge(attestor_did, &temp_p_key); + assert!(result.is_ok()); + let identity_challenge_attestor = result.unwrap(); + let _ = identity_challenge_attestor.elementwise_serialize(&attestor_path); + + // |------------| requester |------------| + // Write signed and encrypted challenge to file to requester path (this step would done manually + // or by GUI, since in deployment + // challenge is sent via alternative channel) for use in subsequent response. + let identity_challenge_requester = IdentityCRChallenge { + update_p_key: None, + update_s_key: None, + identity_challenge_signature: identity_challenge_attestor.identity_challenge_signature, + identity_nonce: None, + identity_response_signature: None, + }; + identity_challenge_requester + .elementwise_serialize(&requester_path) + .unwrap(); + + // Part 1.3: Requester responds to challenge. The received challenge is first decrypted and + // verified, before the requester signs the challenge nonce and encrypts it with the attestor's + // public key. This response is sent to attestor via a POST request. + // Upon receiving the request, the attestor decrypts the response and verifies the signature, + // before comparing the nonce from the response with the nonce from the challenge. + + let public_keys = extract_keys(attestor_doc); + let attestor_public_key_ssi = public_keys.first().unwrap(); + let attestor_public_key = ssi_to_josekit_jwk(attestor_public_key_ssi).unwrap(); + + // Check nonce component is captured with the response being Ok + let result = identity_response(&requester_path, services, &attestor_public_key).await; + assert!(result.is_ok()); + let identity_challenge_requester = result.unwrap(); + identity_challenge_requester + .elementwise_serialize(&requester_path) + .unwrap(); + + // |--------------------------------------------------------------| + // |------------| Part 2: content challenge-response |------------| + // |--------------------------------------------------------------| + // + // |------------| requester |------------| + // After publishing a candidate DID (dDID) to be attested to (not covered in this test), + // the requester initiates the content challenge-response process by a POST with the dDID to the + // attestor's endpoint. + // Upon receiving the POST request the attestor resolves dDID, extracts the signing keys from it + // and returns to the requester a signed and encrypted challenge payload with a hashmap that + // contains an encrypted nonce pecurr signing key. + // The requester decrypts the challenge payload and verifies the signature. It then decrypts + // each nonce with the corresponding signing key and collects them in a hashmap. This + // hashmap is signed and encrypted and sent back to the attestor via POST request. + // The attestor decrypts the response and verifies the signature. It then compares the received + // hashmap of nonces with the one sent to requester. + // The entire process is automated and is kicked off with the content CR initiation request. + // let requester_did = "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q"; + let requester_did = "did:ion:test:EiCDmY0qxsde9AdIwMf2tUKOiMo4aHnoWaPBRCeGt7iMHA"; + let result = initiate_content_challenge( + &requester_path, + requester_did, + services, + &attestor_public_key, + ) + .await; + // Check nonces is captured with the response being Ok + assert!(result.is_ok()); + let (content_cr_initiation, content_cr_challenge) = result.unwrap(); + content_cr_initiation + .elementwise_serialize(&requester_path) + .unwrap(); + content_cr_challenge + .elementwise_serialize(&requester_path) + .unwrap(); + + // Check that requester has all attestation challenge-response information it should have. + let cr_state_requester = CRState::new() + .elementwise_deserialize(&requester_path) + .unwrap() + .unwrap(); + let result = cr_state_requester.is_complete(); + assert!(result); + + // Check that requester has temp_s_key but not update_s_key. + assert!(cr_state_requester + .identity_cr_initiation + .unwrap() + .temp_s_key + .is_some()); + assert!(cr_state_requester + .identity_challenge_response + .unwrap() + .update_s_key + .is_none()); + + // |------------| attestor |------------| + // Check that attestor has all attestation challenge-response information it should have. + let cr_state_attestor = CRState::new() + .elementwise_deserialize(&attestor_path) + .unwrap() + .unwrap(); + let result = cr_state_attestor.is_complete(); + assert!(result); + // Check that attestor does not have temp_s_key but update_s_key. + assert!(cr_state_attestor + .identity_cr_initiation + .unwrap() + .temp_s_key + .is_none()); + assert!(cr_state_attestor + .identity_challenge_response + .unwrap() + .update_s_key + .is_some()); +} diff --git a/trustchain-ion/src/attestor.rs b/trustchain-ion/src/attestor.rs index 2186b4b4..cfc52c86 100644 --- a/trustchain-ion/src/attestor.rs +++ b/trustchain-ion/src/attestor.rs @@ -35,11 +35,13 @@ impl IONAttestor { } } /// Gets the signing keys of the attestor. + // TODO: made public to use in challenge-response. Consider refactoring key manager. pub fn signing_keys(&self) -> Result, KeyManagerError> { self.read_signing_keys(self.did_suffix()) } /// Gets the signing key with ID `key_id` of the attestor. + // TODO: made public to use in challenge-response. Consider refactoring key manager. pub fn signing_key(&self, key_id: Option<&str>) -> Result { let keys = self.signing_keys()?; // If no key_id is given, return the first available key. diff --git a/trustchain-ion/src/lib.rs b/trustchain-ion/src/lib.rs index d48a4579..1635937b 100644 --- a/trustchain-ion/src/lib.rs +++ b/trustchain-ion/src/lib.rs @@ -27,9 +27,9 @@ use thiserror::Error; // for better handling of URLs. pub type URL = String; -/// Full client zero sized type for marker in `IONVerifier`. +/// Full client zero sized type for marker in `TrustchainVerifier`. pub struct FullClient; -/// Light client zero sized type for marker in `IONVerifier`. +/// Light client zero sized type for marker in `TrustchainVerifier`. pub struct LightClient; /// Type for representing an endpoint as a base URL and port. diff --git a/trustchain-ion/src/utils.rs b/trustchain-ion/src/utils.rs index ead01dfd..4676864c 100644 --- a/trustchain-ion/src/utils.rs +++ b/trustchain-ion/src/utils.rs @@ -331,7 +331,7 @@ pub fn time_at_block_height( /// Returns the unix timestamp at 00h:00m:00s UTC on the given date. fn first_unixtime_on(date: NaiveDate) -> i64 { let datetime = date.and_hms_opt(0, 0, 0).unwrap(); - datetime.timestamp() + datetime.and_utc().timestamp() } /// Returns the height of the last block mined before the given date. diff --git a/trustchain-ion/src/verifier.rs b/trustchain-ion/src/verifier.rs index a1e2bfe4..e5072b5b 100644 --- a/trustchain-ion/src/verifier.rs +++ b/trustchain-ion/src/verifier.rs @@ -93,7 +93,7 @@ impl TrustchainVerifier where T: Send + Sync + DIDResolver, { - /// Constructs a new IONVerifier. + /// Constructs a new TrustchainVerifier. // TODO: refactor to use config struct over direct config file lookup pub fn new(resolver: HTTPTrustchainResolver) -> Self { // Construct a Bitcoin RPC client to communicate with the ION Bitcoin node. @@ -308,7 +308,7 @@ impl TrustchainVerifier where T: Send + Sync + DIDResolver, { - /// Constructs a new IONVerifier. + /// Constructs a new TrustchainVerifier. // TODO: consider refactor to remove resolver from API pub fn with_endpoint(resolver: HTTPTrustchainResolver, endpoint: URL) -> Self { Self {