From 4f8e15bf5f4ea161736b05519a0acc782d2b3cdf Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 18 Mar 2024 14:56:39 -0400 Subject: [PATCH 01/72] Exploring how to interact with Ledger from rust code --- Cargo.lock | 224 ++++++++++++++- cmd/crates/stellar-ledger/Cargo.toml | 42 +++ cmd/crates/stellar-ledger/README.md | 0 cmd/crates/stellar-ledger/src/app.rs | 364 ++++++++++++++++++++++++ cmd/crates/stellar-ledger/src/lib.rs | 94 ++++++ cmd/crates/stellar-ledger/src/signer.rs | 271 ++++++++++++++++++ cmd/crates/stellar-ledger/src/types.rs | 245 ++++++++++++++++ 7 files changed, 1238 insertions(+), 2 deletions(-) create mode 100644 cmd/crates/stellar-ledger/Cargo.toml create mode 100644 cmd/crates/stellar-ledger/README.md create mode 100644 cmd/crates/stellar-ledger/src/app.rs create mode 100644 cmd/crates/stellar-ledger/src/lib.rs create mode 100644 cmd/crates/stellar-ledger/src/signer.rs create mode 100644 cmd/crates/stellar-ledger/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index c126a7fac..3807128cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,6 +110,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + [[package]] name = "assert_cmd" version = "2.0.14" @@ -752,6 +758,19 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "der" version = "0.7.8" @@ -953,6 +972,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1079,6 +1121,21 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.30" @@ -1086,6 +1143,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1094,6 +1152,17 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.30" @@ -1129,6 +1198,7 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -2032,6 +2102,18 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hidapi" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "798154e4b6570af74899d71155fb0072d5b17e6aa12f39c8ef22c60fb8ec99e7" +dependencies = [ + "cc", + "libc", + "pkg-config", + "winapi", +] + [[package]] name = "hmac" version = "0.9.0" @@ -2105,6 +2187,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.28" @@ -2414,6 +2502,55 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "ledger-apdu" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe435806c197dfeaa5efcded5e623c4b8230fd28fdf1e91e7a86e40ef2acbf90" +dependencies = [ + "arrayref", + "no-std-compat", + "snafu", +] + +[[package]] +name = "ledger-transport" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1117f2143d92c157197785bf57711d7b02f2cfa101e162f8ca7900fb7f976321" +dependencies = [ + "async-trait", + "ledger-apdu", +] + +[[package]] +name = "ledger-transport-hid" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ba81a1f5f24396b37211478aff7fbcd605dd4544df8dbed07b9da3c2057aee" +dependencies = [ + "byteorder", + "cfg-if", + "hex", + "hidapi", + "ledger-transport", + "libc", + "log", + "thiserror", +] + +[[package]] +name = "ledger-zondax-generic" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02036c84eab9c48e85bc568d269221ba4f5e1cfbc785c3c2c2f6bb8c131f9287" +dependencies = [ + "async-trait", + "ledger-transport", + "serde", + "thiserror", +] + [[package]] name = "libc" version = "0.2.153" @@ -2464,9 +2601,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "matchers" @@ -2547,6 +2684,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -3454,6 +3597,31 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "serial_test" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ad9342b3aaca7cb43c45c097dd008d4907070394bd0751a0aa8817e5a018d" +dependencies = [ + "dashmap", + "futures", + "lazy_static", + "log", + "parking_lot", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93fb4adc70021ac1b47f7d45e8cc4169baaa7ea58483bc5b721d19a26202212" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "sha1_smol" version = "1.0.0" @@ -3566,6 +3734,28 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +[[package]] +name = "snafu" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "socket2" version = "0.5.5" @@ -3995,6 +4185,36 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stellar-ledger" +version = "20.3.1" +dependencies = [ + "ed25519-dalek 2.0.0", + "env_logger", + "futures", + "hex", + "hidapi", + "ledger-transport", + "ledger-transport-hid", + "ledger-zondax-generic", + "log", + "once_cell", + "pretty_assertions", + "sep5", + "serde", + "serde_derive", + "serde_json", + "serial_test", + "sha2 0.9.9", + "slip10", + "soroban-env-host", + "soroban-spec", + "stellar-strkey 0.0.7", + "stellar-xdr", + "thiserror", + "tracing", +] + [[package]] name = "stellar-strkey" version = "0.0.7" diff --git a/cmd/crates/stellar-ledger/Cargo.toml b/cmd/crates/stellar-ledger/Cargo.toml new file mode 100644 index 000000000..4420ba835 --- /dev/null +++ b/cmd/crates/stellar-ledger/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "stellar-ledger" +description = "" +homepage = "https://github.com/stellar/soroban-tools" +repository = "https://github.com/stellar/soroban-tools" +authors = ["Stellar Development Foundation "] +readme = "README.md" +license = "Apache-2.0" +version.workspace = true +edition = "2021" +rust-version.workspace = true + +[dependencies] +soroban-spec = { workspace = true } +thiserror = "1.0.32" +serde = "1.0.82" +serde_derive = "1.0.82" +serde_json = "1.0.82" +sha2 = "0.9.9" +soroban-env-host = { workspace = true } +ed25519-dalek = {workspace=true} +stellar-strkey = {workspace=true} +ledger-transport-hid = "0.10.0" +sep5.workspace = true +slip10 = "0.4.3" +ledger-transport = "0.10.0" +tracing = {workspace=true} +hex = {workspace=true} + +[dependencies.stellar-xdr] +workspace = true +features = ["curr", "std", "serde"] + +[dev-dependencies] +env_logger = "0.11.3" +futures = "0.3.30" +hidapi = { version = "1.4.1", features = ["linux-static-hidraw"], default-features = false } +ledger-zondax-generic = "0.10.0" +log = "0.4.21" +once_cell = "1.19.0" +pretty_assertions = "1.2.1" +serial_test = "3.0.0" diff --git a/cmd/crates/stellar-ledger/README.md b/cmd/crates/stellar-ledger/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs new file mode 100644 index 000000000..46b590791 --- /dev/null +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -0,0 +1,364 @@ +//! NEAR <-> Ledger transport +//! +//! Provides a set of commands that can be executed to communicate with NEAR App installed on Ledger device: +//! - Read PublicKey from Ledger device by HD Path +//! - Sign a Transaction +use ledger_transport::APDUCommand; +use ledger_transport_hid::{ + hidapi::{HidApi, HidError}, + LedgerHIDError, TransportNativeHID, +}; + +// https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md +const GET_PUBLIC_KEY: u8 = 0x02; // Instruction code to get public key +const SIGN_TX: u8 = 0x04; // Instruction code to sign a transaction on the Ledger +const GET_APP_CONFIGURATION: u8 = 0x06; // Instruction code to get app configuration from the Ledger +const SIGN_TX_HASH: u8 = 0x08; // Instruction code to sign a transaction hash on the Ledger +const CLA: u8 = 0xE0; // Instruction class +const P2_GET_PUB_NO_DISPLAY: u8 = 0x00; +const P2_GET_PUB_DISPLAY: u8 = 0x01; + +/// +const INS_GET_WALLET_ID: u8 = 0x05; // Get Wallet ID +const INS_GET_VERSION: u8 = 6; // Instruction code to get app version from the Ledger +const INS_SIGN_NEP413_MESSAGE: u8 = 7; // Instruction code to sign a nep-413 message with Ledger +const INS_SIGN_NEP366_DELEGATE_ACTION: u8 = 8; // Instruction code to sign a nep-413 message with Ledger +const NETWORK_ID: u8 = 'W' as u8; // Instruction parameter 2 +const RETURN_CODE_OK: u16 = 36864; // APDUAnswer.retcode which means success from Ledger +const CHUNK_SIZE: usize = 250; // Chunk size to be sent to Ledger + +/// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with +pub type BorshSerializedUnsignedTransaction = Vec; + +const P1_GET_PUB_DISPLAY: u8 = 0; +const P1_GET_PUB_SILENT: u8 = 1; + +const P1_SIGN_NORMAL: u8 = 0; +const P1_SIGN_NORMAL_LAST_CHUNK: u8 = 0x80; + +/// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with +pub type NEARLedgerAppVersion = Vec; +/// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with +pub type SignatureBytes = Vec; + +#[derive(Debug)] +pub enum NEARLedgerError { + /// Error occuring on init of hidapid and getting current devices list + HidApiError(HidError), + /// Error occuring on creating a new hid transport, connecting to first ledger device found + LedgerHidError(LedgerHIDError), + /// Error occurred while exchanging with Ledger device + APDUExchangeError(String), + /// Error with transport + LedgerHIDError(LedgerHIDError), +} + +/// Converts BIP32Path into bytes (`Vec`) +fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { + (0..hd_path.depth()) + .map(|index| { + let value = *hd_path.index(index).unwrap(); + value.to_be_bytes() + }) + .flatten() + .collect::>() +} + +#[inline(always)] +fn log_command(index: usize, is_last_chunk: bool, command: &APDUCommand>) { + tracing::info!( + "APDU in{}: {}", + if is_last_chunk { + " (last)".to_string() + } else { + format!(" ({})", index) + }, + hex::encode(&command.serialize()) + ); +} + +/// Get the version of NEAR App installed on Ledger +/// +/// # Returns +/// +/// * A `Result` whose `Ok` value is an `NEARLedgerAppVersion` (just a `Vec` for now, where first value is a major version, second is a minor and the last is the path) +/// and whose `Err` value is a `NEARLedgerError` containing an error which occurred. +// pub fn get_version() -> Result { +// //! Something +// // instantiate the connection to Ledger +// // will return an error if Ledger is not connected +// let transport = get_transport()?; +// let command = APDUCommand { +// cla: CLA, +// ins: INS_GET_VERSION, +// p1: 0, // Instruction parameter 1 (offset) +// p2: 0, +// data: vec![], +// }; + +// tracing::info!("APDU in: {}", hex::encode(&command.serialize())); + +// match transport.exchange(&command) { +// Ok(response) => { +// log::info!( +// "APDU out: {}\nAPDU ret code: {:x}", +// hex::encode(response.apdu_data()), +// response.retcode(), +// ); +// // Ok means we successfully exchanged with the Ledger +// // but doesn't mean our request succeeded +// // we need to check it based on `response.retcode` +// if response.retcode() == RETURN_CODE_OK { +// return Ok(response.data().to_vec()); +// } else { +// let retcode = response.retcode(); + +// let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); +// return Err(NEARLedgerError::APDUExchangeError(error_string)); +// } +// } +// Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), +// }; +// } + +// /// Gets PublicKey from the Ledger on the given `hd_path` +// /// +/// # Inputs +/// * `hd_path` - seed phrase hd path `slip10::BIP32Path` for which PublicKey to look +/// +/// # Returns +/// +/// * A `Result` whose `Ok` value is an `ed25519_dalek::PublicKey` and whose `Err` value is a +/// `NEARLedgerError` containing an error which +/// occurred. +/// +/// # Examples +/// +/// ```no_run +/// use near_ledger::get_public_key; +/// use slip10::BIP32Path; +/// use std::str::FromStr; +/// +/// # fn main() { +/// let hd_path = BIP32Path::from_str("44'/397'/0'/0'/1'").unwrap(); +/// let public_key = get_public_key(hd_path).unwrap(); +/// println!("{:#?}", public_key); +/// # } +/// ``` +/// +/// # Trick +/// +/// To convert the answer into `near_crypto::PublicKey` do: +/// +/// ``` +/// # let public_key_bytes = [10u8; 32]; +/// # let public_key = ed25519_dalek::PublicKey::from_bytes(&public_key_bytes).unwrap(); +/// let public_key = near_crypto::PublicKey::ED25519( +/// near_crypto::ED25519PublicKey::from( +/// public_key.to_bytes(), +/// ) +/// ); +/// ``` +pub fn get_public_key(index: u32) -> Result { + let hd_path = bip_from_index(index); + get_public_key_with_display_flag(hd_path, true) +} + +fn bip_from_index(index: u32) -> slip10::BIP32Path { + let path = format!("m/44'/148'/{index}'"); + println!("path: {:?}", path); + path.parse().unwrap() // this is basically the same thing as slip10::BIP32Path::from_str + + // the device handles this part: https://github.com/AhaLabs/rs-sep5/blob/9d6e3886b4b424dd7b730ec24c865f6fad5d770c/src/seed_phrase.rs#L86 +} + +pub fn get_public_key_with_display_flag( + hd_path: slip10::BIP32Path, + display_and_confirm: bool, +) -> Result { + // instantiate the connection to Ledger + // will return an error if Ledger is not connected + let transport = get_transport()?; + + // hd_path must be converted into bytes to be sent as `data` to the Ledger + let hd_path_bytes = hd_path_to_bytes(&hd_path); + println!("hd_path_bytes: {:?}", hd_path_bytes); + + let command = APDUCommand { + cla: CLA, + ins: GET_PUBLIC_KEY, + p1: 0x00, // Instruction parameter 1 (offset) + p2: P2_GET_PUB_DISPLAY, + data: hd_path_bytes, + }; + tracing::info!("APDU in: {}", hex::encode(&command.serialize())); + + match transport.exchange(&command) { + Ok(response) => { + tracing::info!( + "APDU out: {}\nAPDU ret code: {:x}", + hex::encode(response.apdu_data()), + response.retcode(), + ); + // Ok means we successfully exchanged with the Ledger + // but doesn't mean our request succeeded + // we need to check it based on `response.retcode` + if response.retcode() == RETURN_CODE_OK { + return Ok( + stellar_strkey::ed25519::PublicKey::from_payload(&response.data()).unwrap(), + ); + } else { + let retcode = response.retcode(); + + let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); + return Err(NEARLedgerError::APDUExchangeError(error_string)); + } + } + Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), + }; +} + +// pub fn get_wallet_id( +// hd_path: slip10::BIP32Path, +// ) -> Result { +// // instantiate the connection to Ledger +// // will return an error if Ledger is not connected +// let transport = get_transport()?; + +// // hd_path must be converted into bytes to be sent as `data` to the Ledger +// let hd_path_bytes = hd_path_to_bytes(&hd_path); + +// let command = APDUCommand { +// cla: CLA, +// ins: INS_GET_WALLET_ID, +// p1: 0, // Instruction parameter 1 (offset) +// p2: NETWORK_ID, +// data: hd_path_bytes, +// }; +// log::info!("APDU in: {}", hex::encode(&command.serialize())); + +// match transport.exchange(&command) { +// Ok(response) => { +// log::info!( +// "APDU out: {}\nAPDU ret code: {:x}", +// hex::encode(response.apdu_data()), +// response.retcode(), +// ); +// // Ok means we successfully exchanged with the Ledger +// // but doesn't mean our request succeeded +// // we need to check it based on `response.retcode` +// if response.retcode() == RETURN_CODE_OK { +// return Ok(ed25519_dalek::PublicKey::from_bytes(&response.data()).unwrap()); +// } else { +// let retcode = response.retcode(); + +// let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); +// return Err(NEARLedgerError::APDUExchangeError(error_string)); +// } +// } +// Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), +// }; +// } + +fn get_transport() -> Result { + // instantiate the connection to Ledger + // will return an error if Ledger is not connected + let hidapi = HidApi::new().map_err(NEARLedgerError::HidApiError)?; + TransportNativeHID::new(&hidapi).map_err(NEARLedgerError::LedgerHidError) +} + +// /// Sign the transaction. Transaction should be [borsh serialized](https://github.com/near/borsh-rs) `Vec` +// /// +// /// # Inputs +// /// * `unsigned_transaction_borsh_serializer` - unsigned transaction `near_primitives::transaction::Transaction` +// /// which is serialized with `BorshSerializer` and basically is just `Vec` +// /// * `seed_phrase_hd_path` - seed phrase hd path `slip10::BIP32Path` with which to sign +// /// +// /// # Returns +// /// +// /// * A `Result` whose `Ok` value is an `Signature` (bytes) and whose `Err` value is a +// /// `NEARLedgerError` containing an error which occurred. +// /// +// /// # Examples +// /// +// /// ```no_run +// /// use near_ledger::sign_transaction; +// /// use near_primitives::{borsh, borsh::BorshSerialize}; +// /// use slip10::BIP32Path; +// /// use std::str::FromStr; +// /// +// /// # fn main() { +// /// # let near_unsigned_transaction = [10; 250]; +// /// let hd_path = BIP32Path::from_str("44'/397'/0'/0'/1'").unwrap(); +// /// let borsh_transaction = borsh::to_vec(&near_unsigned_transaction).unwrap(); +// /// let signature = sign_transaction(borsh_transaction, hd_path).unwrap(); +// /// println!("{:#?}", signature); +// /// # } +// /// ``` +// /// +// /// # Trick +// /// +// /// To convert the answer into `near_crypto::Signature` do: +// /// +// /// ``` +// /// # let signature = [10; 64].to_vec(); +// /// let signature = near_crypto::Signature::from_parts(near_crypto::KeyType::ED25519, &signature) +// /// .expect("Signature is not expected to fail on deserialization"); +// /// ``` +// pub fn sign_transaction( +// unsigned_tx: BorshSerializedUnsignedTransaction, +// seed_phrase_hd_path: slip10::BIP32Path, +// ) -> Result { +// let transport = get_transport()?; +// // seed_phrase_hd_path must be converted into bytes to be sent as `data` to the Ledger +// let hd_path_bytes = hd_path_to_bytes(&seed_phrase_hd_path); + +// let mut data: Vec = vec![]; +// data.extend(hd_path_bytes); +// data.extend(&unsigned_tx); + +// let chunks = data.chunks(CHUNK_SIZE); +// let chunks_count = chunks.len(); + +// for (i, chunk) in chunks.enumerate() { +// let is_last_chunk = chunks_count == i + 1; +// let command = APDUCommand { +// cla: CLA, +// ins: INS_SIGN_TRANSACTION, +// p1: if is_last_chunk { +// P1_SIGN_NORMAL_LAST_CHUNK +// } else { +// P1_SIGN_NORMAL +// }, // Instruction parameter 1 (offset) +// p2: NETWORK_ID, +// data: chunk.to_vec(), +// }; +// log_command(i, is_last_chunk, &command); +// match transport.exchange(&command) { +// Ok(response) => { +// log::info!( +// "APDU out: {}\nAPDU ret code: {:x}", +// hex::encode(response.apdu_data()), +// response.retcode(), +// ); +// // Ok means we successfully exchanged with the Ledger +// // but doesn't mean our request succeeded +// // we need to check it based on `response.retcode` +// if response.retcode() == RETURN_CODE_OK { +// if is_last_chunk { +// return Ok(response.data().to_vec()); +// } +// } else { +// let retcode = response.retcode(); + +// let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); +// return Err(NEARLedgerError::APDUExchangeError(error_string)); +// } +// } +// Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), +// }; +// } +// Err(NEARLedgerError::APDUExchangeError( +// "Unable to process request".to_owned(), +// )) +// } diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs new file mode 100644 index 000000000..01f1701cb --- /dev/null +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -0,0 +1,94 @@ +use ed25519_dalek::Signer; +use sha2::{Digest, Sha256}; + +use soroban_env_host::xdr::{ + self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, + InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, + ScVal, Signature, SignatureHint, SorobanAddressCredentials, SorobanAuthorizationEntry, + SorobanAuthorizedFunction, SorobanCredentials, Transaction, TransactionEnvelope, + TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, + TransactionV1Envelope, Uint256, WriteXdr, +}; + +mod app; +use app::get_public_key; + +enum Error {} + +#[cfg(test)] +mod test { + use super::*; + use hidapi::HidApi; + use ledger_transport_hid::TransportNativeHID; + use log::info; + use once_cell::sync::Lazy; + use serial_test::serial; + + fn init_logging() { + let _ = env_logger::builder().is_test(true).try_init(); + } + + fn hidapi() -> &'static HidApi { + static HIDAPI: Lazy = Lazy::new(|| HidApi::new().expect("unable to get HIDAPI")); + + &HIDAPI + } + + #[test] + #[serial] + fn list_all_devices() { + init_logging(); + let api = hidapi(); + + for device_info in api.device_list() { + println!( + "{:#?} - {:#x}/{:#x}/{:#x}/{:#x} {:#} {:#}", + device_info.path(), + device_info.vendor_id(), + device_info.product_id(), + device_info.usage_page(), + device_info.interface_number(), + device_info.manufacturer_string().unwrap_or_default(), + device_info.product_string().unwrap_or_default() + ); + } + } + + #[test] + #[serial] + fn ledger_device_path() { + init_logging(); + let api = hidapi(); + + let mut ledgers = TransportNativeHID::list_ledgers(&api); + + let a_ledger = ledgers.next().expect("could not find any ledger device"); + println!("{:?}", a_ledger.path()); + } + + // #[test] + // #[serial] + // fn exchange() { + // use ledger_zondax_generic::{App, AppExt}; + // struct Dummy; + // impl App for Dummy { + // const CLA: u8 = 0; + // } + + // init_logging(); + + // let ledger = TransportNativeHID::new(hidapi()).expect("Could not get a device"); + + // // use device info command that works in the dashboard + // let result = futures::executor::block_on(Dummy::get_device_info(&ledger)) + // .expect("Error during exchange"); + // info!("{:x?}", result); + // } + + #[test] + #[serial] + fn test_get_public_key() { + let public_key = get_public_key(0); + println!("{public_key:?}"); + } +} diff --git a/cmd/crates/stellar-ledger/src/signer.rs b/cmd/crates/stellar-ledger/src/signer.rs new file mode 100644 index 000000000..b91e1c2f6 --- /dev/null +++ b/cmd/crates/stellar-ledger/src/signer.rs @@ -0,0 +1,271 @@ +// use ed25519_dalek::Signer; +// use sha2::{Digest, Sha256}; + +// use soroban_env_host::xdr::{ +// self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, +// Limits, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, ScVal, Signature, SignatureHint, +// SorobanAddressCredentials, SorobanAuthorizationEntry, SorobanCredentials, Transaction, +// TransactionEnvelope, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, +// TransactionV1Envelope, Uint256, WriteXdr, Operation, InvokeHostFunctionOp, SorobanAuthorizedFunction, +// }; + +// enum Error {} + +// fn requires_auth(txn: &Transaction) -> Option { +// let [op @ Operation { +// body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }), +// .. +// }] = txn.operations.as_slice() +// else { +// return None; +// }; +// matches!( +// auth.first().map(|x| &x.root_invocation.function), +// Some(&SorobanAuthorizedFunction::ContractFn(_)) +// ) +// .then(move || op.clone()) +// } + +// /// A trait for signing Stellar transactions and Soroban authorization entries +// pub trait Stellar { +// /// The type of the options that can be passed when creating a new signer +// type Init; +// /// Create a new signer with the given network passphrase and options +// fn new(network_passphrase: &str, options: Option) -> Self; + +// /// Get the network hash +// fn network_hash(&self) -> xdr::Hash; + +// /// Sign a transaction hash with the given source account +// /// # Errors +// /// Returns an error if the source account is not found +// fn sign_txn_hash( +// &self, +// txn: [u8; 32], +// source_account: &stellar_strkey::Strkey, +// ) -> Result; + +// /// Sign a Soroban authorization entry with the given address +// /// # Errors +// /// Returns an error if the address is not found +// fn sign_soroban_authorization_entry( +// &self, +// unsigned_entry: &SorobanAuthorizationEntry, +// signature_expiration_ledger: u32, +// address: &[u8; 32], +// ) -> Result; + +// /// Sign a Stellar transaction with the given source account +// /// This is a default implementation that signs the transaction hash and returns a decorated signature +// /// # Errors +// /// Returns an error if the source account is not found +// fn sign_txn( +// &self, +// txn: Transaction, +// source_account: &stellar_strkey::Strkey, +// ) -> Result { +// let signature_payload = TransactionSignaturePayload { +// network_id: self.network_hash(), +// tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(txn.clone()), +// }; +// let hash = Sha256::digest(&signature_payload.to_xdr(Limits::none())?).into(); +// let decorated_signature = self.sign_txn_hash(hash, source_account)?; +// Ok(TransactionEnvelope::Tx(TransactionV1Envelope { +// tx: txn, +// signatures: vec![decorated_signature].try_into()?, +// })) +// } + +// /// Sign a Soroban authorization entries for a given transaction and set the expiration ledger +// /// # Errors +// /// Returns an error if the address is not found +// fn sign_soroban_authorizations( +// &self, +// raw: &Transaction, +// signature_expiration_ledger: u32, +// ) -> Result, Error> { +// let mut tx = raw.clone(); +// let Some(mut op) = requires_auth(&tx) else { +// return Ok(None); +// }; + +// let xdr::Operation { +// body: OperationBody::InvokeHostFunction(ref mut body), +// .. +// } = op +// else { +// return Ok(None); +// }; + +// let signed_auths = body +// .auth +// .as_slice() +// .iter() +// .map(|raw_auth| { +// self.maybe_sign_soroban_authorization_entry(raw_auth, signature_expiration_ledger) +// }) +// .collect::, Error>>()?; + +// body.auth = signed_auths.try_into()?; +// tx.operations = vec![op].try_into()?; +// Ok(Some(tx)) +// } + +// /// Sign a Soroban authorization entry if the address is public key +// /// # Errors +// /// Returns an error if the address in entry is a contract +// fn maybe_sign_soroban_authorization_entry( +// &self, +// unsigned_entry: &SorobanAuthorizationEntry, +// signature_expiration_ledger: u32, +// ) -> Result { +// if let SorobanAuthorizationEntry { +// credentials: SorobanCredentials::Address(SorobanAddressCredentials { ref address, .. }), +// .. +// } = unsigned_entry +// { +// // See if we have a signer for this authorizationEntry +// // If not, then we Error +// let needle = match address { +// ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(ref a)))) => a, +// ScAddress::Contract(Hash(c)) => { +// // This address is for a contract. This means we're using a custom +// // smart-contract account. Currently the CLI doesn't support that yet. +// return Err(Error::MissingSignerForAddress { +// address: stellar_strkey::Strkey::Contract(stellar_strkey::Contract(*c)) +// .to_string(), +// }); +// } +// }; +// self.sign_soroban_authorization_entry( +// unsigned_entry, +// signature_expiration_ledger, +// needle, +// ) +// } else { +// Ok(unsigned_entry.clone()) +// } +// } +// } + + +// use std::fmt::Debug; +// #[derive(Debug)] +// pub struct DefaultSigner { +// network_passphrase: String, +// keypairs: Vec, +// } + +// impl DefaultSigner { +// pub fn get_key( +// &self, +// key: &stellar_strkey::Strkey, +// ) -> Result<&ed25519_dalek::SigningKey, Error> { +// match key { +// stellar_strkey::Strkey::PublicKeyEd25519(stellar_strkey::ed25519::PublicKey(bytes)) => { +// self.keypairs +// .iter() +// .find(|k| k.verifying_key().to_bytes() == *bytes) +// } +// _ => None, +// } +// .ok_or_else(|| Error::MissingSignerForAddress { +// address: key.to_string(), +// }) +// } +// } + +// impl Stellar for DefaultSigner { +// type Init = Vec; +// fn new(network_passphrase: &str, options: Option>) -> Self { +// DefaultSigner { +// network_passphrase: network_passphrase.to_string(), +// keypairs: options.unwrap_or_default(), +// } +// } + +// fn sign_txn_hash( +// &self, +// txn: [u8; 32], +// source_account: &stellar_strkey::Strkey, +// ) -> Result { +// let source_account = self.get_key(source_account)?; +// let tx_signature = source_account.sign(&txn); +// Ok(DecoratedSignature { +// // TODO: remove this unwrap. It's safe because we know the length of the array +// hint: SignatureHint( +// source_account.verifying_key().to_bytes()[28..] +// .try_into() +// .unwrap(), +// ), +// signature: Signature(tx_signature.to_bytes().try_into()?), +// }) +// } + +// fn sign_soroban_authorization_entry( +// &self, +// unsigned_entry: &SorobanAuthorizationEntry, +// signature_expiration_ledger: u32, +// signer: &[u8; 32], +// ) -> Result { +// let mut auth = unsigned_entry.clone(); +// let SorobanAuthorizationEntry { +// credentials: SorobanCredentials::Address(ref mut credentials), +// .. +// } = auth +// else { +// // Doesn't need special signing +// return Ok(auth); +// }; +// let SorobanAddressCredentials { nonce, .. } = credentials; + +// let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { +// network_id: self.network_hash(), +// invocation: auth.root_invocation.clone(), +// nonce: *nonce, +// signature_expiration_ledger, +// }) +// .to_xdr(Limits::none())?; + +// let strkey = stellar_strkey::ed25519::PublicKey(*signer); +// let payload = Sha256::digest(&preimage); +// let signer = self.get_key(&stellar_strkey::Strkey::PublicKeyEd25519(strkey))?; +// let signature = signer.sign(&payload); + +// let map = ScMap::sorted_from(vec![ +// ( +// ScVal::Symbol(ScSymbol("public_key".try_into()?)), +// ScVal::Bytes( +// signer +// .verifying_key() +// .to_bytes() +// .to_vec() +// .try_into() +// .map_err(Error::Xdr)?, +// ), +// ), +// ( +// ScVal::Symbol(ScSymbol("signature".try_into()?)), +// ScVal::Bytes( +// signature +// .to_bytes() +// .to_vec() +// .try_into() +// .map_err(Error::Xdr)?, +// ), +// ), +// ]) +// .map_err(Error::Xdr)?; +// credentials.signature = ScVal::Vec(Some( +// vec![ScVal::Map(Some(map))].try_into().map_err(Error::Xdr)?, +// )); +// credentials.signature_expiration_ledger = signature_expiration_ledger; +// auth.credentials = SorobanCredentials::Address(credentials.clone()); + +// Ok(auth) +// } + +// fn network_hash(&self) -> xdr::Hash { +// xdr::Hash(Sha256::digest(self.network_passphrase.as_bytes()).into()) +// } +// } diff --git a/cmd/crates/stellar-ledger/src/types.rs b/cmd/crates/stellar-ledger/src/types.rs new file mode 100644 index 000000000..8e3b2177e --- /dev/null +++ b/cmd/crates/stellar-ledger/src/types.rs @@ -0,0 +1,245 @@ +use serde::Serialize; +use stellar_xdr::curr::{ + ScSpecEntry, ScSpecFunctionInputV0, ScSpecTypeDef, ScSpecUdtEnumCaseV0, + ScSpecUdtErrorEnumCaseV0, ScSpecUdtStructFieldV0, ScSpecUdtUnionCaseV0, +}; + +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct StructField { + pub doc: String, + pub name: String, + pub value: Type, +} + +impl From<&ScSpecUdtStructFieldV0> for StructField { + fn from(f: &ScSpecUdtStructFieldV0) -> Self { + StructField { + doc: f.doc.to_utf8_string_lossy(), + name: f.name.to_utf8_string_lossy(), + value: (&f.type_).into(), + } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FunctionInput { + pub doc: String, + pub name: String, + pub value: Type, +} + +impl From<&ScSpecFunctionInputV0> for FunctionInput { + fn from(f: &ScSpecFunctionInputV0) -> Self { + FunctionInput { + doc: f.doc.to_utf8_string_lossy(), + name: f.name.to_utf8_string_lossy(), + value: (&f.type_).into(), + } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UnionCase { + pub doc: String, + pub name: String, + pub values: Vec, +} + +impl From<&ScSpecUdtUnionCaseV0> for UnionCase { + fn from(c: &ScSpecUdtUnionCaseV0) -> Self { + let (doc, name, values) = match c { + ScSpecUdtUnionCaseV0::VoidV0(v) => ( + v.doc.to_utf8_string_lossy(), + v.name.to_utf8_string_lossy(), + vec![], + ), + ScSpecUdtUnionCaseV0::TupleV0(t) => ( + t.doc.to_utf8_string_lossy(), + t.name.to_utf8_string_lossy(), + t.type_.iter().map(Type::from).collect(), + ), + }; + UnionCase { doc, name, values } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EnumCase { + pub doc: String, + pub name: String, + pub value: u32, +} + +impl From<&ScSpecUdtEnumCaseV0> for EnumCase { + fn from(c: &ScSpecUdtEnumCaseV0) -> Self { + EnumCase { + doc: c.doc.to_utf8_string_lossy(), + name: c.name.to_utf8_string_lossy(), + value: c.value, + } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ErrorEnumCase { + pub doc: String, + pub name: String, + pub value: u32, +} + +impl From<&ScSpecUdtErrorEnumCaseV0> for EnumCase { + fn from(c: &ScSpecUdtErrorEnumCaseV0) -> Self { + EnumCase { + doc: c.doc.to_utf8_string_lossy(), + name: c.name.to_utf8_string_lossy(), + value: c.value, + } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] +#[serde(tag = "type")] +#[serde(rename_all = "camelCase")] +pub enum Type { + Void, + Val, + U64, + I64, + U32, + I32, + U128, + I128, + U256, + I256, + Bool, + Symbol, + Error, + Bytes, + String, + Address, + Timepoint, + Duration, + Map { key: Box, value: Box }, + Option { value: Box }, + Result { value: Box, error: Box }, + Vec { element: Box }, + BytesN { n: u32 }, + Tuple { elements: Vec }, + Custom { name: String }, +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] +#[serde(tag = "type")] +#[serde(rename_all = "camelCase")] +pub enum Entry { + Function { + doc: String, + name: String, + inputs: Vec, + outputs: Vec, + }, + Struct { + doc: String, + name: String, + fields: Vec, + }, + Union { + doc: String, + name: String, + cases: Vec, + }, + Enum { + doc: String, + name: String, + cases: Vec, + }, + ErrorEnum { + doc: String, + name: String, + cases: Vec, + }, +} + +impl From<&ScSpecTypeDef> for Type { + fn from(spec: &ScSpecTypeDef) -> Self { + match spec { + ScSpecTypeDef::Map(map) => Type::Map { + key: Box::new(Type::from(map.key_type.as_ref())), + value: Box::new(Type::from(map.value_type.as_ref())), + }, + ScSpecTypeDef::Option(opt) => Type::Option { + value: Box::new(Type::from(opt.value_type.as_ref())), + }, + ScSpecTypeDef::Result(res) => Type::Result { + value: Box::new(Type::from(res.ok_type.as_ref())), + error: Box::new(Type::from(res.error_type.as_ref())), + }, + ScSpecTypeDef::Tuple(tuple) => Type::Tuple { + elements: tuple.value_types.iter().map(Type::from).collect(), + }, + ScSpecTypeDef::Vec(vec) => Type::Vec { + element: Box::new(Type::from(vec.element_type.as_ref())), + }, + ScSpecTypeDef::Udt(udt) => Type::Custom { + name: udt.name.to_utf8_string_lossy(), + }, + ScSpecTypeDef::BytesN(b) => Type::BytesN { n: b.n }, + ScSpecTypeDef::Val => Type::Val, + ScSpecTypeDef::U64 => Type::U64, + ScSpecTypeDef::I64 => Type::I64, + ScSpecTypeDef::U32 => Type::U32, + ScSpecTypeDef::I32 => Type::I32, + ScSpecTypeDef::U128 => Type::U128, + ScSpecTypeDef::I128 => Type::I128, + ScSpecTypeDef::U256 => Type::U256, + ScSpecTypeDef::I256 => Type::I256, + ScSpecTypeDef::Bool => Type::Bool, + ScSpecTypeDef::Symbol => Type::Symbol, + ScSpecTypeDef::Error => Type::Error, + ScSpecTypeDef::Bytes => Type::Bytes, + ScSpecTypeDef::String => Type::String, + ScSpecTypeDef::Address => Type::Address, + ScSpecTypeDef::Void => Type::Void, + ScSpecTypeDef::Timepoint => Type::Timepoint, + ScSpecTypeDef::Duration => Type::Duration, + } + } +} + +impl From<&ScSpecEntry> for Entry { + fn from(spec: &ScSpecEntry) -> Self { + match spec { + ScSpecEntry::FunctionV0(f) => Entry::Function { + doc: f.doc.to_utf8_string_lossy(), + name: f.name.to_utf8_string_lossy(), + inputs: f.inputs.iter().map(FunctionInput::from).collect(), + outputs: f.outputs.iter().map(Type::from).collect(), + }, + ScSpecEntry::UdtStructV0(s) => Entry::Struct { + doc: s.doc.to_utf8_string_lossy(), + name: s.name.to_utf8_string_lossy(), + fields: s.fields.iter().map(StructField::from).collect(), + }, + ScSpecEntry::UdtUnionV0(u) => Entry::Union { + doc: u.doc.to_utf8_string_lossy(), + name: u.name.to_utf8_string_lossy(), + cases: u.cases.iter().map(UnionCase::from).collect(), + }, + ScSpecEntry::UdtEnumV0(e) => Entry::Enum { + doc: e.doc.to_utf8_string_lossy(), + name: e.name.to_utf8_string_lossy(), + cases: e.cases.iter().map(EnumCase::from).collect(), + }, + ScSpecEntry::UdtErrorEnumV0(e) => Entry::Enum { + doc: e.doc.to_utf8_string_lossy(), + name: e.name.to_utf8_string_lossy(), + cases: e.cases.iter().map(EnumCase::from).collect(), + }, + } + } +} From 50e5e5f7a05ba3fa72e89ec2987cebd7d415c1fd Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 19 Mar 2024 13:38:48 -0400 Subject: [PATCH 02/72] WIP: get_public_key works, but needs to be cleaned up --- Cargo.lock | 1 + cmd/crates/stellar-ledger/Cargo.toml | 1 + cmd/crates/stellar-ledger/src/lib.rs | 25 +-- cmd/crates/stellar-ledger/src/newapp.rs | 196 ++++++++++++++++++++++++ 4 files changed, 202 insertions(+), 21 deletions(-) create mode 100644 cmd/crates/stellar-ledger/src/newapp.rs diff --git a/Cargo.lock b/Cargo.lock index 3807128cf..280a87c4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4189,6 +4189,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" name = "stellar-ledger" version = "20.3.1" dependencies = [ + "byteorder", "ed25519-dalek 2.0.0", "env_logger", "futures", diff --git a/cmd/crates/stellar-ledger/Cargo.toml b/cmd/crates/stellar-ledger/Cargo.toml index 4420ba835..a3bab8424 100644 --- a/cmd/crates/stellar-ledger/Cargo.toml +++ b/cmd/crates/stellar-ledger/Cargo.toml @@ -26,6 +26,7 @@ slip10 = "0.4.3" ledger-transport = "0.10.0" tracing = {workspace=true} hex = {workspace=true} +byteorder = "1.5.0" [dependencies.stellar-xdr] workspace = true diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 01f1701cb..6acfa8d44 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -1,3 +1,5 @@ +// https://github.com/zondax/ledger-rs + use ed25519_dalek::Signer; use sha2::{Digest, Sha256}; @@ -10,8 +12,8 @@ use soroban_env_host::xdr::{ TransactionV1Envelope, Uint256, WriteXdr, }; -mod app; -use app::get_public_key; +pub mod newapp; +use newapp::get_public_key; enum Error {} @@ -66,25 +68,6 @@ mod test { println!("{:?}", a_ledger.path()); } - // #[test] - // #[serial] - // fn exchange() { - // use ledger_zondax_generic::{App, AppExt}; - // struct Dummy; - // impl App for Dummy { - // const CLA: u8 = 0; - // } - - // init_logging(); - - // let ledger = TransportNativeHID::new(hidapi()).expect("Could not get a device"); - - // // use device info command that works in the dashboard - // let result = futures::executor::block_on(Dummy::get_device_info(&ledger)) - // .expect("Error during exchange"); - // info!("{:x?}", result); - // } - #[test] #[serial] fn test_get_public_key() { diff --git a/cmd/crates/stellar-ledger/src/newapp.rs b/cmd/crates/stellar-ledger/src/newapp.rs new file mode 100644 index 000000000..735c97904 --- /dev/null +++ b/cmd/crates/stellar-ledger/src/newapp.rs @@ -0,0 +1,196 @@ +use byteorder::{BigEndian, WriteBytesExt}; +use std::{io::Write, str::FromStr}; + +use ledger_transport::APDUCommand; +use ledger_transport_hid::{ + hidapi::{HidApi, HidError}, + LedgerHIDError, TransportNativeHID, +}; +use slip10::BIP32Path; + +// https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md +const CLA: u8 = 0xE0; // Instruction class +const GET_PUBLIC_KEY: u8 = 0x02; // Instruction code to get public key + +const P2_GET_PUB_NO_DISPLAY: u8 = 0x00; +const P2_GET_PUB_DISPLAY: u8 = 0x01; + +const RETURN_CODE_OK: u16 = 36864; // APDUAnswer.retcode which means success from Ledger + +#[derive(Debug)] +pub enum LedgerError { + /// Error occuring on init of hidapid and getting current devices list + HidApiError(HidError), + /// Error occuring on creating a new hid transport, connecting to first ledger device found + LedgerHidError(LedgerHIDError), + /// Error occurred while exchanging with Ledger device + APDUExchangeError(String), + /// Error with transport + LedgerHIDError(LedgerHIDError), +} + +// this method should mimic the behavior of the splitPath function in the JS fn in https://github.com/LedgerHQ/ledger-live/blob/6f033e6b13ae1bcd960fb1cd041687fd6d0de21b/libs/ledgerjs/packages/hw-app-str/src/utils.ts#L21 +fn split_path(path: &str) -> Vec { + let mut result: Vec = Vec::new(); + + for component in path.split('/') { + // first check if the component has a digit in it + let has_a_number = component.chars().any(|c| c.is_digit(10)); + + // if it doesn't have a number (like m), skip it + if !has_a_number { + continue; + } + + // if it does have a number, remove any ' and parse it into an u32 + let component_without_quote = component.replace("'", ""); + if let Ok(mut number) = component_without_quote.parse::() { + if component.len() > 1 && component.chars().last() == Some('\'') { + number += 0x8000_0000; + } + result.push(number); + } + } + + result +} + +pub fn get_public_key(index: u32) -> Result { + let hd_path = bip_from_index(index); // this is the same result as BIP32Path::from_str("m/44'/148'/0'") + get_public_key_with_display_flag(hd_path, true) +} + +// const pathElts = splitPath(path); +// const buffer = Buffer.alloc(1 + pathElts.length * 4); + +// buffer[0] = pathElts.length; + +// pathElts.forEach((element, index) => { + +//// from docs: buf.writeUInt32BE(value[, offset]) +// buffer.writeUInt32BE(element, 1 + 4 * index); + +// }); + +// const verifyMsg = Buffer.from("via lumina", "ascii"); +// apdus.push(Buffer.concat([buffer, verifyMsg])); +// let keepAlive = false; + +// index 0 +// element 2147483692 +// thing after element in write: 1 +// index 1 +// element 2147483796 +// thing after element in write: 5 +// index 2 +// element 2147483648 +// thing after element in write: 9 + +// the buffer data (as a vec) should look like this: +// [3, 128, 0, 0, 44, 128, 0, 0, 148, 128, 0, 0, 0] +// but it looks like this: +// [3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 44, 128, 0, 0, 148, 128, 0, 0, 0] + +// there are just too many 0 spacers + +// this fn should mimic the above JS +fn create_a_data_buffer_like_in_js(path: &str) -> Vec { + let path_elts = split_path(path); + + let mut buffer = vec![0; 1 + path_elts.len() * 4]; + buffer[0] = path_elts.len() as u8; + + for (index, &element) in path_elts.iter().enumerate() { + let mut temp_buffer = vec![0; 4]; + temp_buffer + .write_u32::(element) + .expect("Failed to write element to buffer"); + println!("{temp_buffer:?}"); + + buffer.write(&temp_buffer); + } + println!("BUFFER: {:?}", buffer); + + buffer +} + +fn bip_from_index(index: u32) -> slip10::BIP32Path { + let path = format!("m/44'/148'/{index}'"); + path.parse().unwrap() // this is basically the same thing as slip10::BIP32Path::from_str + + // the device handles this part: https://github.com/AhaLabs/rs-sep5/blob/9d6e3886b4b424dd7b730ec24c865f6fad5d770c/src/seed_phrase.rs#L86 +} + +/// Converts BIP32Path into bytes (`Vec`) +fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { + (0..hd_path.depth()) + .map(|index| { + let value = *hd_path.index(index).unwrap(); + value.to_be_bytes() + }) + .flatten() + .collect::>() +} + +pub fn get_public_key_with_display_flag( + hd_path: slip10::BIP32Path, + display_and_confirm: bool, +) -> Result { + // instantiate the connection to Ledger + // will return an error if Ledger is not connected + let transport = get_transport()?; + + let hd_path_as_string = hd_path.to_string(); + let hd_path_bytes = create_a_data_buffer_like_in_js(&hd_path_as_string); + + let js_hd_path_bytes: Vec = [3, 128, 0, 0, 44, 128, 0, 0, 148, 128, 0, 0, 0].to_vec(); + + let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); + hd_path_to_bytes.insert(0, 3); + println!("hd_path_to_bytes: {:?}", hd_path_to_bytes); + + // // hd_path must be converted into bytes to be sent as `data` to the Ledger + // let hd_path_bytes = hd_path_to_bytes(&hd_path); + // println!("hd_path_bytes: {:?}", hd_path_bytes); + + let command = APDUCommand { + cla: CLA, + ins: GET_PUBLIC_KEY, + p1: 0x00, // Instruction parameter 1 (offset) + p2: P2_GET_PUB_DISPLAY, + data: js_hd_path_bytes, + }; + tracing::info!("APDU in: {}", hex::encode(&command.serialize())); + + match transport.exchange(&command) { + Ok(response) => { + tracing::info!( + "APDU out: {}\nAPDU ret code: {:x}", + hex::encode(response.apdu_data()), + response.retcode(), + ); + // Ok means we successfully exchanged with the Ledger + // but doesn't mean our request succeeded + // we need to check it based on `response.retcode` + + if response.retcode() == RETURN_CODE_OK { + return Ok( + stellar_strkey::ed25519::PublicKey::from_payload(&response.data()).unwrap(), + ); + } else { + let retcode = response.retcode(); + + let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); + return Err(LedgerError::APDUExchangeError(error_string)); + } + } + Err(err) => return Err(LedgerError::LedgerHIDError(err)), + }; +} + +fn get_transport() -> Result { + // instantiate the connection to Ledger + // will return an error if Ledger is not connected + let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; + TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) +} From 852114ea817a9d745c276cbab69fc8acf92a9518 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:21:21 -0400 Subject: [PATCH 03/72] Clean up: get_public_key --- cmd/crates/stellar-ledger/src/app.rs | 348 +++---------------- cmd/crates/stellar-ledger/src/draft-app.rs | 378 +++++++++++++++++++++ cmd/crates/stellar-ledger/src/lib.rs | 4 +- cmd/crates/stellar-ledger/src/newapp.rs | 196 ----------- 4 files changed, 429 insertions(+), 497 deletions(-) create mode 100644 cmd/crates/stellar-ledger/src/draft-app.rs delete mode 100644 cmd/crates/stellar-ledger/src/newapp.rs diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index 46b590791..f6f0a705c 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -1,48 +1,23 @@ -//! NEAR <-> Ledger transport -//! -//! Provides a set of commands that can be executed to communicate with NEAR App installed on Ledger device: -//! - Read PublicKey from Ledger device by HD Path -//! - Sign a Transaction +use byteorder::{BigEndian, WriteBytesExt}; +use std::{io::Write, str::FromStr}; + use ledger_transport::APDUCommand; use ledger_transport_hid::{ hidapi::{HidApi, HidError}, LedgerHIDError, TransportNativeHID, }; -// https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md -const GET_PUBLIC_KEY: u8 = 0x02; // Instruction code to get public key -const SIGN_TX: u8 = 0x04; // Instruction code to sign a transaction on the Ledger -const GET_APP_CONFIGURATION: u8 = 0x06; // Instruction code to get app configuration from the Ledger -const SIGN_TX_HASH: u8 = 0x08; // Instruction code to sign a transaction hash on the Ledger +// these came from https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md const CLA: u8 = 0xE0; // Instruction class -const P2_GET_PUB_NO_DISPLAY: u8 = 0x00; -const P2_GET_PUB_DISPLAY: u8 = 0x01; +const GET_PUBLIC_KEY: u8 = 0x02; // Instruction code to get public key +const P1_GET_PUBLIC_KEY: u8 = 0x00; +const P2_GET_PUBLIC_KEY_NO_DISPLAY: u8 = 0x00; +const P2_GET_PUBLIC_KEY_DISPLAY: u8 = 0x01; -/// -const INS_GET_WALLET_ID: u8 = 0x05; // Get Wallet ID -const INS_GET_VERSION: u8 = 6; // Instruction code to get app version from the Ledger -const INS_SIGN_NEP413_MESSAGE: u8 = 7; // Instruction code to sign a nep-413 message with Ledger -const INS_SIGN_NEP366_DELEGATE_ACTION: u8 = 8; // Instruction code to sign a nep-413 message with Ledger -const NETWORK_ID: u8 = 'W' as u8; // Instruction parameter 2 const RETURN_CODE_OK: u16 = 36864; // APDUAnswer.retcode which means success from Ledger -const CHUNK_SIZE: usize = 250; // Chunk size to be sent to Ledger - -/// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with -pub type BorshSerializedUnsignedTransaction = Vec; - -const P1_GET_PUB_DISPLAY: u8 = 0; -const P1_GET_PUB_SILENT: u8 = 1; - -const P1_SIGN_NORMAL: u8 = 0; -const P1_SIGN_NORMAL_LAST_CHUNK: u8 = 0x80; - -/// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with -pub type NEARLedgerAppVersion = Vec; -/// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with -pub type SignatureBytes = Vec; #[derive(Debug)] -pub enum NEARLedgerError { +pub enum LedgerError { /// Error occuring on init of hidapid and getting current devices list HidApiError(HidError), /// Error occuring on creating a new hid transport, connecting to first ledger device found @@ -53,8 +28,20 @@ pub enum NEARLedgerError { LedgerHIDError(LedgerHIDError), } -/// Converts BIP32Path into bytes (`Vec`) +pub fn get_public_key(index: u32) -> Result { + let hd_path = bip_path_from_index(index); + get_public_key_with_display_flag(hd_path, true) +} + +fn bip_path_from_index(index: u32) -> slip10::BIP32Path { + let path = format!("m/44'/148'/{index}'"); + path.parse().unwrap() // this is basically the same thing as slip10::BIP32Path::from_str + + // the device handles this part: https://github.com/AhaLabs/rs-sep5/blob/9d6e3886b4b424dd7b730ec24c865f6fad5d770c/src/seed_phrase.rs#L86 +} + fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { + println!("hd_path.depth: {:?}", hd_path.depth()); (0..hd_path.depth()) .map(|index| { let value = *hd_path.index(index).unwrap(); @@ -64,134 +51,36 @@ fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { .collect::>() } -#[inline(always)] -fn log_command(index: usize, is_last_chunk: bool, command: &APDUCommand>) { - tracing::info!( - "APDU in{}: {}", - if is_last_chunk { - " (last)".to_string() - } else { - format!(" ({})", index) - }, - hex::encode(&command.serialize()) - ); -} - -/// Get the version of NEAR App installed on Ledger -/// -/// # Returns -/// -/// * A `Result` whose `Ok` value is an `NEARLedgerAppVersion` (just a `Vec` for now, where first value is a major version, second is a minor and the last is the path) -/// and whose `Err` value is a `NEARLedgerError` containing an error which occurred. -// pub fn get_version() -> Result { -// //! Something -// // instantiate the connection to Ledger -// // will return an error if Ledger is not connected -// let transport = get_transport()?; -// let command = APDUCommand { -// cla: CLA, -// ins: INS_GET_VERSION, -// p1: 0, // Instruction parameter 1 (offset) -// p2: 0, -// data: vec![], -// }; - -// tracing::info!("APDU in: {}", hex::encode(&command.serialize())); - -// match transport.exchange(&command) { -// Ok(response) => { -// log::info!( -// "APDU out: {}\nAPDU ret code: {:x}", -// hex::encode(response.apdu_data()), -// response.retcode(), -// ); -// // Ok means we successfully exchanged with the Ledger -// // but doesn't mean our request succeeded -// // we need to check it based on `response.retcode` -// if response.retcode() == RETURN_CODE_OK { -// return Ok(response.data().to_vec()); -// } else { -// let retcode = response.retcode(); - -// let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); -// return Err(NEARLedgerError::APDUExchangeError(error_string)); -// } -// } -// Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), -// }; -// } - -// /// Gets PublicKey from the Ledger on the given `hd_path` -// /// -/// # Inputs -/// * `hd_path` - seed phrase hd path `slip10::BIP32Path` for which PublicKey to look -/// -/// # Returns -/// -/// * A `Result` whose `Ok` value is an `ed25519_dalek::PublicKey` and whose `Err` value is a -/// `NEARLedgerError` containing an error which -/// occurred. -/// -/// # Examples -/// -/// ```no_run -/// use near_ledger::get_public_key; -/// use slip10::BIP32Path; -/// use std::str::FromStr; -/// -/// # fn main() { -/// let hd_path = BIP32Path::from_str("44'/397'/0'/0'/1'").unwrap(); -/// let public_key = get_public_key(hd_path).unwrap(); -/// println!("{:#?}", public_key); -/// # } -/// ``` -/// -/// # Trick -/// -/// To convert the answer into `near_crypto::PublicKey` do: -/// -/// ``` -/// # let public_key_bytes = [10u8; 32]; -/// # let public_key = ed25519_dalek::PublicKey::from_bytes(&public_key_bytes).unwrap(); -/// let public_key = near_crypto::PublicKey::ED25519( -/// near_crypto::ED25519PublicKey::from( -/// public_key.to_bytes(), -/// ) -/// ); -/// ``` -pub fn get_public_key(index: u32) -> Result { - let hd_path = bip_from_index(index); - get_public_key_with_display_flag(hd_path, true) -} - -fn bip_from_index(index: u32) -> slip10::BIP32Path { - let path = format!("m/44'/148'/{index}'"); - println!("path: {:?}", path); - path.parse().unwrap() // this is basically the same thing as slip10::BIP32Path::from_str - - // the device handles this part: https://github.com/AhaLabs/rs-sep5/blob/9d6e3886b4b424dd7b730ec24c865f6fad5d770c/src/seed_phrase.rs#L86 -} - +/// The display_and_confirm bool determines if the Ledger will display the public key on its screen and requires user approval to share pub fn get_public_key_with_display_flag( hd_path: slip10::BIP32Path, display_and_confirm: bool, -) -> Result { - // instantiate the connection to Ledger - // will return an error if Ledger is not connected +) -> Result { + // instantiate the connect to the Ledger, return an error if not connected let transport = get_transport()?; - // hd_path must be converted into bytes to be sent as `data` to the Ledger - let hd_path_bytes = hd_path_to_bytes(&hd_path); - println!("hd_path_bytes: {:?}", hd_path_bytes); + // convert the hd_path into bytes to be sent as `data` to the Ledger + // the first element of the data should be the number of elements in the path + let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); + let hd_path_elements_count = hd_path.depth(); + hd_path_to_bytes.insert(0, hd_path_elements_count); + let p2 = if display_and_confirm { + P2_GET_PUBLIC_KEY_DISPLAY + } else { + P2_GET_PUBLIC_KEY_NO_DISPLAY + }; + + // more information about how to build this command can be found at https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md let command = APDUCommand { cla: CLA, ins: GET_PUBLIC_KEY, - p1: 0x00, // Instruction parameter 1 (offset) - p2: P2_GET_PUB_DISPLAY, - data: hd_path_bytes, + p1: P1_GET_PUBLIC_KEY, + p2: p2, + data: hd_path_to_bytes, }; - tracing::info!("APDU in: {}", hex::encode(&command.serialize())); + + tracing::info!("APDU in: {}", hex::encode(&command.serialize())); match transport.exchange(&command) { Ok(response) => { @@ -200,9 +89,8 @@ pub fn get_public_key_with_display_flag( hex::encode(response.apdu_data()), response.retcode(), ); - // Ok means we successfully exchanged with the Ledger - // but doesn't mean our request succeeded - // we need to check it based on `response.retcode` + // Ok means we successfully connected with the Ledger but it doesn't mean our request succeeded. We still need to check the response.retcode + if response.retcode() == RETURN_CODE_OK { return Ok( stellar_strkey::ed25519::PublicKey::from_payload(&response.data()).unwrap(), @@ -211,154 +99,16 @@ pub fn get_public_key_with_display_flag( let retcode = response.retcode(); let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); - return Err(NEARLedgerError::APDUExchangeError(error_string)); + return Err(LedgerError::APDUExchangeError(error_string)); } } - Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), + Err(err) => return Err(LedgerError::LedgerHIDError(err)), }; } -// pub fn get_wallet_id( -// hd_path: slip10::BIP32Path, -// ) -> Result { -// // instantiate the connection to Ledger -// // will return an error if Ledger is not connected -// let transport = get_transport()?; - -// // hd_path must be converted into bytes to be sent as `data` to the Ledger -// let hd_path_bytes = hd_path_to_bytes(&hd_path); - -// let command = APDUCommand { -// cla: CLA, -// ins: INS_GET_WALLET_ID, -// p1: 0, // Instruction parameter 1 (offset) -// p2: NETWORK_ID, -// data: hd_path_bytes, -// }; -// log::info!("APDU in: {}", hex::encode(&command.serialize())); - -// match transport.exchange(&command) { -// Ok(response) => { -// log::info!( -// "APDU out: {}\nAPDU ret code: {:x}", -// hex::encode(response.apdu_data()), -// response.retcode(), -// ); -// // Ok means we successfully exchanged with the Ledger -// // but doesn't mean our request succeeded -// // we need to check it based on `response.retcode` -// if response.retcode() == RETURN_CODE_OK { -// return Ok(ed25519_dalek::PublicKey::from_bytes(&response.data()).unwrap()); -// } else { -// let retcode = response.retcode(); - -// let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); -// return Err(NEARLedgerError::APDUExchangeError(error_string)); -// } -// } -// Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), -// }; -// } - -fn get_transport() -> Result { +fn get_transport() -> Result { // instantiate the connection to Ledger // will return an error if Ledger is not connected - let hidapi = HidApi::new().map_err(NEARLedgerError::HidApiError)?; - TransportNativeHID::new(&hidapi).map_err(NEARLedgerError::LedgerHidError) + let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; + TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) } - -// /// Sign the transaction. Transaction should be [borsh serialized](https://github.com/near/borsh-rs) `Vec` -// /// -// /// # Inputs -// /// * `unsigned_transaction_borsh_serializer` - unsigned transaction `near_primitives::transaction::Transaction` -// /// which is serialized with `BorshSerializer` and basically is just `Vec` -// /// * `seed_phrase_hd_path` - seed phrase hd path `slip10::BIP32Path` with which to sign -// /// -// /// # Returns -// /// -// /// * A `Result` whose `Ok` value is an `Signature` (bytes) and whose `Err` value is a -// /// `NEARLedgerError` containing an error which occurred. -// /// -// /// # Examples -// /// -// /// ```no_run -// /// use near_ledger::sign_transaction; -// /// use near_primitives::{borsh, borsh::BorshSerialize}; -// /// use slip10::BIP32Path; -// /// use std::str::FromStr; -// /// -// /// # fn main() { -// /// # let near_unsigned_transaction = [10; 250]; -// /// let hd_path = BIP32Path::from_str("44'/397'/0'/0'/1'").unwrap(); -// /// let borsh_transaction = borsh::to_vec(&near_unsigned_transaction).unwrap(); -// /// let signature = sign_transaction(borsh_transaction, hd_path).unwrap(); -// /// println!("{:#?}", signature); -// /// # } -// /// ``` -// /// -// /// # Trick -// /// -// /// To convert the answer into `near_crypto::Signature` do: -// /// -// /// ``` -// /// # let signature = [10; 64].to_vec(); -// /// let signature = near_crypto::Signature::from_parts(near_crypto::KeyType::ED25519, &signature) -// /// .expect("Signature is not expected to fail on deserialization"); -// /// ``` -// pub fn sign_transaction( -// unsigned_tx: BorshSerializedUnsignedTransaction, -// seed_phrase_hd_path: slip10::BIP32Path, -// ) -> Result { -// let transport = get_transport()?; -// // seed_phrase_hd_path must be converted into bytes to be sent as `data` to the Ledger -// let hd_path_bytes = hd_path_to_bytes(&seed_phrase_hd_path); - -// let mut data: Vec = vec![]; -// data.extend(hd_path_bytes); -// data.extend(&unsigned_tx); - -// let chunks = data.chunks(CHUNK_SIZE); -// let chunks_count = chunks.len(); - -// for (i, chunk) in chunks.enumerate() { -// let is_last_chunk = chunks_count == i + 1; -// let command = APDUCommand { -// cla: CLA, -// ins: INS_SIGN_TRANSACTION, -// p1: if is_last_chunk { -// P1_SIGN_NORMAL_LAST_CHUNK -// } else { -// P1_SIGN_NORMAL -// }, // Instruction parameter 1 (offset) -// p2: NETWORK_ID, -// data: chunk.to_vec(), -// }; -// log_command(i, is_last_chunk, &command); -// match transport.exchange(&command) { -// Ok(response) => { -// log::info!( -// "APDU out: {}\nAPDU ret code: {:x}", -// hex::encode(response.apdu_data()), -// response.retcode(), -// ); -// // Ok means we successfully exchanged with the Ledger -// // but doesn't mean our request succeeded -// // we need to check it based on `response.retcode` -// if response.retcode() == RETURN_CODE_OK { -// if is_last_chunk { -// return Ok(response.data().to_vec()); -// } -// } else { -// let retcode = response.retcode(); - -// let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); -// return Err(NEARLedgerError::APDUExchangeError(error_string)); -// } -// } -// Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), -// }; -// } -// Err(NEARLedgerError::APDUExchangeError( -// "Unable to process request".to_owned(), -// )) -// } diff --git a/cmd/crates/stellar-ledger/src/draft-app.rs b/cmd/crates/stellar-ledger/src/draft-app.rs new file mode 100644 index 000000000..d6aa7e3a4 --- /dev/null +++ b/cmd/crates/stellar-ledger/src/draft-app.rs @@ -0,0 +1,378 @@ +// Resources used: +// - https://github.com/khorolets/near-ledger-rs/tree/main +// - https://github.com/LedgerHQ/app-stellar +// - https://github.com/LedgerHQ/ledger-live +// - checkStellarBip32Path : https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/utils.ts#L101 +// - getPublicKey : https://github.com/LedgerHQ/ledger-live/blob/6f033e6b13ae1bcd960fb1cd041687fd6d0de21b/libs/ledgerjs/packages/hw-app-str/src/Str.ts +// - m/44'/148'/x' : https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0005.md +// - from_path_string : https://github.com/AhaLabs/rs-sep5/blob/9d6e3886b4b424dd7b730ec24c865f6fad5d770c/src/seed_phrase.rs#L84 + +// Things to look up in the future: +// - https://github.com/LedgerHQ/speculos + +// FROM https://github.com/khorolets/near-ledger-rs/blob/main/src/lib.rs + +//! NEAR <-> Ledger transport +//! +//! Provides a set of commands that can be executed to communicate with NEAR App installed on Ledger device: +//! - Read PublicKey from Ledger device by HD Path +//! - Sign a Transaction +use ledger_transport::APDUCommand; +use ledger_transport_hid::{ + hidapi::{HidApi, HidError}, + LedgerHIDError, TransportNativeHID, +}; + +// https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md +const GET_PUBLIC_KEY: u8 = 0x02; // Instruction code to get public key +const SIGN_TX: u8 = 0x04; // Instruction code to sign a transaction on the Ledger +const GET_APP_CONFIGURATION: u8 = 0x06; // Instruction code to get app configuration from the Ledger +const SIGN_TX_HASH: u8 = 0x08; // Instruction code to sign a transaction hash on the Ledger +const CLA: u8 = 0xE0; // Instruction class +const P2_GET_PUB_NO_DISPLAY: u8 = 0x00; +const P2_GET_PUB_DISPLAY: u8 = 0x01; + +/// +// const INS_GET_WALLET_ID: u8 = 0x05; // Get Wallet ID +// const INS_GET_VERSION: u8 = 6; // Instruction code to get app version from the Ledger +// const INS_SIGN_NEP413_MESSAGE: u8 = 7; // Instruction code to sign a nep-413 message with Ledger +// const INS_SIGN_NEP366_DELEGATE_ACTION: u8 = 8; // Instruction code to sign a nep-413 message with Ledger +// const NETWORK_ID: u8 = 'W' as u8; // Instruction parameter 2 +const RETURN_CODE_OK: u16 = 36864; // APDUAnswer.retcode which means success from Ledger +const CHUNK_SIZE: usize = 250; // Chunk size to be sent to Ledger + +/// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with +pub type BorshSerializedUnsignedTransaction = Vec; + +const P1_GET_PUB_DISPLAY: u8 = 0; +const P1_GET_PUB_SILENT: u8 = 1; + +const P1_SIGN_NORMAL: u8 = 0; +const P1_SIGN_NORMAL_LAST_CHUNK: u8 = 0x80; + +/// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with +// pub type NEARLedgerAppVersion = Vec; +/// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with +pub type SignatureBytes = Vec; + +#[derive(Debug)] +pub enum NEARLedgerError { + /// Error occuring on init of hidapid and getting current devices list + HidApiError(HidError), + /// Error occuring on creating a new hid transport, connecting to first ledger device found + LedgerHidError(LedgerHIDError), + /// Error occurred while exchanging with Ledger device + APDUExchangeError(String), + /// Error with transport + LedgerHIDError(LedgerHIDError), +} + +/// Converts BIP32Path into bytes (`Vec`) +fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { + (0..hd_path.depth()) + .map(|index| { + let value = *hd_path.index(index).unwrap(); + value.to_be_bytes() + }) + .flatten() + .collect::>() +} + +#[inline(always)] +fn log_command(index: usize, is_last_chunk: bool, command: &APDUCommand>) { + tracing::info!( + "APDU in{}: {}", + if is_last_chunk { + " (last)".to_string() + } else { + format!(" ({})", index) + }, + hex::encode(&command.serialize()) + ); +} + +/// Get the version of NEAR App installed on Ledger +/// +/// # Returns +/// +/// * A `Result` whose `Ok` value is an `NEARLedgerAppVersion` (just a `Vec` for now, where first value is a major version, second is a minor and the last is the path) +/// and whose `Err` value is a `NEARLedgerError` containing an error which occurred. +// pub fn get_version() -> Result { +// //! Something +// // instantiate the connection to Ledger +// // will return an error if Ledger is not connected +// let transport = get_transport()?; +// let command = APDUCommand { +// cla: CLA, +// ins: INS_GET_VERSION, +// p1: 0, // Instruction parameter 1 (offset) +// p2: 0, +// data: vec![], +// }; + +// tracing::info!("APDU in: {}", hex::encode(&command.serialize())); + +// match transport.exchange(&command) { +// Ok(response) => { +// log::info!( +// "APDU out: {}\nAPDU ret code: {:x}", +// hex::encode(response.apdu_data()), +// response.retcode(), +// ); +// // Ok means we successfully exchanged with the Ledger +// // but doesn't mean our request succeeded +// // we need to check it based on `response.retcode` +// if response.retcode() == RETURN_CODE_OK { +// return Ok(response.data().to_vec()); +// } else { +// let retcode = response.retcode(); + +// let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); +// return Err(NEARLedgerError::APDUExchangeError(error_string)); +// } +// } +// Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), +// }; +// } + +// /// Gets PublicKey from the Ledger on the given `hd_path` +// /// +/// # Inputs +/// * `hd_path` - seed phrase hd path `slip10::BIP32Path` for which PublicKey to look +/// +/// # Returns +/// +/// * A `Result` whose `Ok` value is an `ed25519_dalek::PublicKey` and whose `Err` value is a +/// `NEARLedgerError` containing an error which +/// occurred. +/// +/// # Examples +/// +/// ```no_run +/// use near_ledger::get_public_key; +/// use slip10::BIP32Path; +/// use std::str::FromStr; +/// +/// # fn main() { +/// let hd_path = BIP32Path::from_str("44'/397'/0'/0'/1'").unwrap(); +/// let public_key = get_public_key(hd_path).unwrap(); +/// println!("{:#?}", public_key); +/// # } +/// ``` +/// +/// # Trick +/// +/// To convert the answer into `near_crypto::PublicKey` do: +/// +/// ``` +/// # let public_key_bytes = [10u8; 32]; +/// # let public_key = ed25519_dalek::PublicKey::from_bytes(&public_key_bytes).unwrap(); +/// let public_key = near_crypto::PublicKey::ED25519( +/// near_crypto::ED25519PublicKey::from( +/// public_key.to_bytes(), +/// ) +/// ); +/// ``` +pub fn get_public_key(index: u32) -> Result { + let hd_path = bip_from_index(index); + get_public_key_with_display_flag(hd_path, true) +} + +fn bip_from_index(index: u32) -> slip10::BIP32Path { + let path = format!("m/44'/148'/{index}'"); + println!("path: {:?}", path); + path.parse().unwrap() // this is basically the same thing as slip10::BIP32Path::from_str + + // the device handles this part: https://github.com/AhaLabs/rs-sep5/blob/9d6e3886b4b424dd7b730ec24c865f6fad5d770c/src/seed_phrase.rs#L86 +} + +pub fn get_public_key_with_display_flag( + hd_path: slip10::BIP32Path, + display_and_confirm: bool, +) -> Result { + // instantiate the connection to Ledger + // will return an error if Ledger is not connected + let transport = get_transport()?; + + // hd_path must be converted into bytes to be sent as `data` to the Ledger + let hd_path_bytes = hd_path_to_bytes(&hd_path); + println!("hd_path_bytes: {:?}", hd_path_bytes); + + let command = APDUCommand { + cla: CLA, + ins: GET_PUBLIC_KEY, + p1: 0x00, // Instruction parameter 1 (offset) + p2: P2_GET_PUB_DISPLAY, + data: hd_path_bytes, + }; + tracing::info!("APDU in: {}", hex::encode(&command.serialize())); + + match transport.exchange(&command) { + Ok(response) => { + tracing::info!( + "APDU out: {}\nAPDU ret code: {:x}", + hex::encode(response.apdu_data()), + response.retcode(), + ); + // Ok means we successfully exchanged with the Ledger + // but doesn't mean our request succeeded + // we need to check it based on `response.retcode` + if response.retcode() == RETURN_CODE_OK { + return Ok( + stellar_strkey::ed25519::PublicKey::from_payload(&response.data()).unwrap(), + ); + } else { + let retcode = response.retcode(); + + let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); + return Err(NEARLedgerError::APDUExchangeError(error_string)); + } + } + Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), + }; +} + +// pub fn get_wallet_id( +// hd_path: slip10::BIP32Path, +// ) -> Result { +// // instantiate the connection to Ledger +// // will return an error if Ledger is not connected +// let transport = get_transport()?; + +// // hd_path must be converted into bytes to be sent as `data` to the Ledger +// let hd_path_bytes = hd_path_to_bytes(&hd_path); + +// let command = APDUCommand { +// cla: CLA, +// ins: INS_GET_WALLET_ID, +// p1: 0, // Instruction parameter 1 (offset) +// p2: NETWORK_ID, +// data: hd_path_bytes, +// }; +// log::info!("APDU in: {}", hex::encode(&command.serialize())); + +// match transport.exchange(&command) { +// Ok(response) => { +// log::info!( +// "APDU out: {}\nAPDU ret code: {:x}", +// hex::encode(response.apdu_data()), +// response.retcode(), +// ); +// // Ok means we successfully exchanged with the Ledger +// // but doesn't mean our request succeeded +// // we need to check it based on `response.retcode` +// if response.retcode() == RETURN_CODE_OK { +// return Ok(ed25519_dalek::PublicKey::from_bytes(&response.data()).unwrap()); +// } else { +// let retcode = response.retcode(); + +// let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); +// return Err(NEARLedgerError::APDUExchangeError(error_string)); +// } +// } +// Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), +// }; +// } + +fn get_transport() -> Result { + // instantiate the connection to Ledger + // will return an error if Ledger is not connected + let hidapi = HidApi::new().map_err(NEARLedgerError::HidApiError)?; + TransportNativeHID::new(&hidapi).map_err(NEARLedgerError::LedgerHidError) +} + +// /// Sign the transaction. Transaction should be [borsh serialized](https://github.com/near/borsh-rs) `Vec` +// /// +// /// # Inputs +// /// * `unsigned_transaction_borsh_serializer` - unsigned transaction `near_primitives::transaction::Transaction` +// /// which is serialized with `BorshSerializer` and basically is just `Vec` +// /// * `seed_phrase_hd_path` - seed phrase hd path `slip10::BIP32Path` with which to sign +// /// +// /// # Returns +// /// +// /// * A `Result` whose `Ok` value is an `Signature` (bytes) and whose `Err` value is a +// /// `NEARLedgerError` containing an error which occurred. +// /// +// /// # Examples +// /// +// /// ```no_run +// /// use near_ledger::sign_transaction; +// /// use near_primitives::{borsh, borsh::BorshSerialize}; +// /// use slip10::BIP32Path; +// /// use std::str::FromStr; +// /// +// /// # fn main() { +// /// # let near_unsigned_transaction = [10; 250]; +// /// let hd_path = BIP32Path::from_str("44'/397'/0'/0'/1'").unwrap(); +// /// let borsh_transaction = borsh::to_vec(&near_unsigned_transaction).unwrap(); +// /// let signature = sign_transaction(borsh_transaction, hd_path).unwrap(); +// /// println!("{:#?}", signature); +// /// # } +// /// ``` +// /// +// /// # Trick +// /// +// /// To convert the answer into `near_crypto::Signature` do: +// /// +// /// ``` +// /// # let signature = [10; 64].to_vec(); +// /// let signature = near_crypto::Signature::from_parts(near_crypto::KeyType::ED25519, &signature) +// /// .expect("Signature is not expected to fail on deserialization"); +// /// ``` +// pub fn sign_transaction( +// unsigned_tx: BorshSerializedUnsignedTransaction, +// seed_phrase_hd_path: slip10::BIP32Path, +// ) -> Result { +// let transport = get_transport()?; +// // seed_phrase_hd_path must be converted into bytes to be sent as `data` to the Ledger +// let hd_path_bytes = hd_path_to_bytes(&seed_phrase_hd_path); + +// let mut data: Vec = vec![]; +// data.extend(hd_path_bytes); +// data.extend(&unsigned_tx); + +// let chunks = data.chunks(CHUNK_SIZE); +// let chunks_count = chunks.len(); + +// for (i, chunk) in chunks.enumerate() { +// let is_last_chunk = chunks_count == i + 1; +// let command = APDUCommand { +// cla: CLA, +// ins: INS_SIGN_TRANSACTION, +// p1: if is_last_chunk { +// P1_SIGN_NORMAL_LAST_CHUNK +// } else { +// P1_SIGN_NORMAL +// }, // Instruction parameter 1 (offset) +// p2: NETWORK_ID, +// data: chunk.to_vec(), +// }; +// log_command(i, is_last_chunk, &command); +// match transport.exchange(&command) { +// Ok(response) => { +// log::info!( +// "APDU out: {}\nAPDU ret code: {:x}", +// hex::encode(response.apdu_data()), +// response.retcode(), +// ); +// // Ok means we successfully exchanged with the Ledger +// // but doesn't mean our request succeeded +// // we need to check it based on `response.retcode` +// if response.retcode() == RETURN_CODE_OK { +// if is_last_chunk { +// return Ok(response.data().to_vec()); +// } +// } else { +// let retcode = response.retcode(); + +// let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); +// return Err(NEARLedgerError::APDUExchangeError(error_string)); +// } +// } +// Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), +// }; +// } +// Err(NEARLedgerError::APDUExchangeError( +// "Unable to process request".to_owned(), +// )) +// } diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 6acfa8d44..4c372605d 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -12,8 +12,8 @@ use soroban_env_host::xdr::{ TransactionV1Envelope, Uint256, WriteXdr, }; -pub mod newapp; -use newapp::get_public_key; +pub mod app; +use app::get_public_key; enum Error {} diff --git a/cmd/crates/stellar-ledger/src/newapp.rs b/cmd/crates/stellar-ledger/src/newapp.rs deleted file mode 100644 index 735c97904..000000000 --- a/cmd/crates/stellar-ledger/src/newapp.rs +++ /dev/null @@ -1,196 +0,0 @@ -use byteorder::{BigEndian, WriteBytesExt}; -use std::{io::Write, str::FromStr}; - -use ledger_transport::APDUCommand; -use ledger_transport_hid::{ - hidapi::{HidApi, HidError}, - LedgerHIDError, TransportNativeHID, -}; -use slip10::BIP32Path; - -// https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md -const CLA: u8 = 0xE0; // Instruction class -const GET_PUBLIC_KEY: u8 = 0x02; // Instruction code to get public key - -const P2_GET_PUB_NO_DISPLAY: u8 = 0x00; -const P2_GET_PUB_DISPLAY: u8 = 0x01; - -const RETURN_CODE_OK: u16 = 36864; // APDUAnswer.retcode which means success from Ledger - -#[derive(Debug)] -pub enum LedgerError { - /// Error occuring on init of hidapid and getting current devices list - HidApiError(HidError), - /// Error occuring on creating a new hid transport, connecting to first ledger device found - LedgerHidError(LedgerHIDError), - /// Error occurred while exchanging with Ledger device - APDUExchangeError(String), - /// Error with transport - LedgerHIDError(LedgerHIDError), -} - -// this method should mimic the behavior of the splitPath function in the JS fn in https://github.com/LedgerHQ/ledger-live/blob/6f033e6b13ae1bcd960fb1cd041687fd6d0de21b/libs/ledgerjs/packages/hw-app-str/src/utils.ts#L21 -fn split_path(path: &str) -> Vec { - let mut result: Vec = Vec::new(); - - for component in path.split('/') { - // first check if the component has a digit in it - let has_a_number = component.chars().any(|c| c.is_digit(10)); - - // if it doesn't have a number (like m), skip it - if !has_a_number { - continue; - } - - // if it does have a number, remove any ' and parse it into an u32 - let component_without_quote = component.replace("'", ""); - if let Ok(mut number) = component_without_quote.parse::() { - if component.len() > 1 && component.chars().last() == Some('\'') { - number += 0x8000_0000; - } - result.push(number); - } - } - - result -} - -pub fn get_public_key(index: u32) -> Result { - let hd_path = bip_from_index(index); // this is the same result as BIP32Path::from_str("m/44'/148'/0'") - get_public_key_with_display_flag(hd_path, true) -} - -// const pathElts = splitPath(path); -// const buffer = Buffer.alloc(1 + pathElts.length * 4); - -// buffer[0] = pathElts.length; - -// pathElts.forEach((element, index) => { - -//// from docs: buf.writeUInt32BE(value[, offset]) -// buffer.writeUInt32BE(element, 1 + 4 * index); - -// }); - -// const verifyMsg = Buffer.from("via lumina", "ascii"); -// apdus.push(Buffer.concat([buffer, verifyMsg])); -// let keepAlive = false; - -// index 0 -// element 2147483692 -// thing after element in write: 1 -// index 1 -// element 2147483796 -// thing after element in write: 5 -// index 2 -// element 2147483648 -// thing after element in write: 9 - -// the buffer data (as a vec) should look like this: -// [3, 128, 0, 0, 44, 128, 0, 0, 148, 128, 0, 0, 0] -// but it looks like this: -// [3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 44, 128, 0, 0, 148, 128, 0, 0, 0] - -// there are just too many 0 spacers - -// this fn should mimic the above JS -fn create_a_data_buffer_like_in_js(path: &str) -> Vec { - let path_elts = split_path(path); - - let mut buffer = vec![0; 1 + path_elts.len() * 4]; - buffer[0] = path_elts.len() as u8; - - for (index, &element) in path_elts.iter().enumerate() { - let mut temp_buffer = vec![0; 4]; - temp_buffer - .write_u32::(element) - .expect("Failed to write element to buffer"); - println!("{temp_buffer:?}"); - - buffer.write(&temp_buffer); - } - println!("BUFFER: {:?}", buffer); - - buffer -} - -fn bip_from_index(index: u32) -> slip10::BIP32Path { - let path = format!("m/44'/148'/{index}'"); - path.parse().unwrap() // this is basically the same thing as slip10::BIP32Path::from_str - - // the device handles this part: https://github.com/AhaLabs/rs-sep5/blob/9d6e3886b4b424dd7b730ec24c865f6fad5d770c/src/seed_phrase.rs#L86 -} - -/// Converts BIP32Path into bytes (`Vec`) -fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { - (0..hd_path.depth()) - .map(|index| { - let value = *hd_path.index(index).unwrap(); - value.to_be_bytes() - }) - .flatten() - .collect::>() -} - -pub fn get_public_key_with_display_flag( - hd_path: slip10::BIP32Path, - display_and_confirm: bool, -) -> Result { - // instantiate the connection to Ledger - // will return an error if Ledger is not connected - let transport = get_transport()?; - - let hd_path_as_string = hd_path.to_string(); - let hd_path_bytes = create_a_data_buffer_like_in_js(&hd_path_as_string); - - let js_hd_path_bytes: Vec = [3, 128, 0, 0, 44, 128, 0, 0, 148, 128, 0, 0, 0].to_vec(); - - let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); - hd_path_to_bytes.insert(0, 3); - println!("hd_path_to_bytes: {:?}", hd_path_to_bytes); - - // // hd_path must be converted into bytes to be sent as `data` to the Ledger - // let hd_path_bytes = hd_path_to_bytes(&hd_path); - // println!("hd_path_bytes: {:?}", hd_path_bytes); - - let command = APDUCommand { - cla: CLA, - ins: GET_PUBLIC_KEY, - p1: 0x00, // Instruction parameter 1 (offset) - p2: P2_GET_PUB_DISPLAY, - data: js_hd_path_bytes, - }; - tracing::info!("APDU in: {}", hex::encode(&command.serialize())); - - match transport.exchange(&command) { - Ok(response) => { - tracing::info!( - "APDU out: {}\nAPDU ret code: {:x}", - hex::encode(response.apdu_data()), - response.retcode(), - ); - // Ok means we successfully exchanged with the Ledger - // but doesn't mean our request succeeded - // we need to check it based on `response.retcode` - - if response.retcode() == RETURN_CODE_OK { - return Ok( - stellar_strkey::ed25519::PublicKey::from_payload(&response.data()).unwrap(), - ); - } else { - let retcode = response.retcode(); - - let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); - return Err(LedgerError::APDUExchangeError(error_string)); - } - } - Err(err) => return Err(LedgerError::LedgerHIDError(err)), - }; -} - -fn get_transport() -> Result { - // instantiate the connection to Ledger - // will return an error if Ledger is not connected - let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; - TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) -} From db1c97635b70c6a9942e95dea490310d3a7227e4 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:06:42 -0400 Subject: [PATCH 04/72] Clean up: remove unused tests, etc --- cmd/crates/stellar-ledger/src/app.rs | 3 +-- cmd/crates/stellar-ledger/src/lib.rs | 33 +--------------------------- 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index f6f0a705c..cd33fcf23 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -30,7 +30,7 @@ pub enum LedgerError { pub fn get_public_key(index: u32) -> Result { let hd_path = bip_path_from_index(index); - get_public_key_with_display_flag(hd_path, true) + get_public_key_with_display_flag(hd_path, false) } fn bip_path_from_index(index: u32) -> slip10::BIP32Path { @@ -90,7 +90,6 @@ pub fn get_public_key_with_display_flag( response.retcode(), ); // Ok means we successfully connected with the Ledger but it doesn't mean our request succeeded. We still need to check the response.retcode - if response.retcode() == RETURN_CODE_OK { return Ok( stellar_strkey::ed25519::PublicKey::from_payload(&response.data()).unwrap(), diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 4c372605d..85bb50157 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -36,42 +36,11 @@ mod test { &HIDAPI } - #[test] - #[serial] - fn list_all_devices() { - init_logging(); - let api = hidapi(); - - for device_info in api.device_list() { - println!( - "{:#?} - {:#x}/{:#x}/{:#x}/{:#x} {:#} {:#}", - device_info.path(), - device_info.vendor_id(), - device_info.product_id(), - device_info.usage_page(), - device_info.interface_number(), - device_info.manufacturer_string().unwrap_or_default(), - device_info.product_string().unwrap_or_default() - ); - } - } - - #[test] - #[serial] - fn ledger_device_path() { - init_logging(); - let api = hidapi(); - - let mut ledgers = TransportNativeHID::list_ledgers(&api); - - let a_ledger = ledgers.next().expect("could not find any ledger device"); - println!("{:?}", a_ledger.path()); - } - #[test] #[serial] fn test_get_public_key() { let public_key = get_public_key(0); println!("{public_key:?}"); + assert!(public_key.is_ok()); } } From e24d8a6db79ac25734377409ce3340ab3802a67b Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Fri, 22 Mar 2024 11:19:18 -0400 Subject: [PATCH 05/72] WIP: start the speculos emulator via docker --- cmd/crates/stellar-ledger/Cargo.toml | 7 +- cmd/crates/stellar-ledger/src/docker.rs | 219 ++++++++++++++++++++++ cmd/crates/stellar-ledger/src/emulator.rs | 14 ++ cmd/crates/stellar-ledger/src/lib.rs | 10 + 4 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 cmd/crates/stellar-ledger/src/docker.rs create mode 100644 cmd/crates/stellar-ledger/src/emulator.rs diff --git a/cmd/crates/stellar-ledger/Cargo.toml b/cmd/crates/stellar-ledger/Cargo.toml index a3bab8424..cbf9c01f7 100644 --- a/cmd/crates/stellar-ledger/Cargo.toml +++ b/cmd/crates/stellar-ledger/Cargo.toml @@ -18,8 +18,8 @@ serde_derive = "1.0.82" serde_json = "1.0.82" sha2 = "0.9.9" soroban-env-host = { workspace = true } -ed25519-dalek = {workspace=true} -stellar-strkey = {workspace=true} +ed25519-dalek = { workspace=true } +stellar-strkey = { workspace=true } ledger-transport-hid = "0.10.0" sep5.workspace = true slip10 = "0.4.3" @@ -27,6 +27,9 @@ ledger-transport = "0.10.0" tracing = {workspace=true} hex = {workspace=true} byteorder = "1.5.0" +bollard = "0.15.0" +home = "0.5.9" +futures-util = "0.3.30" [dependencies.stellar-xdr] workspace = true diff --git a/cmd/crates/stellar-ledger/src/docker.rs b/cmd/crates/stellar-ledger/src/docker.rs new file mode 100644 index 000000000..e6104e40b --- /dev/null +++ b/cmd/crates/stellar-ledger/src/docker.rs @@ -0,0 +1,219 @@ +use std::collections::HashMap; + +use bollard::{ + container::{Config, CreateContainerOptions, StartContainerOptions}, + image::CreateImageOptions, + service::{HostConfig, PortBinding}, + ClientVersion, Docker, +}; +use futures_util::TryStreamExt; + +#[allow(unused_imports)] +// Need to add this for windows, since we are only using this crate for the unix fn try_docker_desktop_socket +use home::home_dir; + +pub const DOCKER_HOST_HELP: &str = "Optional argument to override the default docker host. This is useful when you are using a non-standard docker host path for your Docker-compatible container runtime, e.g. Docker Desktop defaults to $HOME/.docker/run/docker.sock instead of /var/run/docker.sock"; + +// DEFAULT_DOCKER_HOST is from the bollard crate on the main branch, which has not been released yet: https://github.com/fussybeaver/bollard/blob/0972b1aac0ad5c08798e100319ddd0d2ee010365/src/docker.rs#L64 +#[cfg(unix)] +pub const DEFAULT_DOCKER_HOST: &str = "unix:///var/run/docker.sock"; + +#[cfg(windows)] +pub const DEFAULT_DOCKER_HOST: &str = "npipe:////./pipe/docker_engine"; + +// DEFAULT_TIMEOUT and API_DEFAULT_VERSION are from the bollard crate +const DEFAULT_TIMEOUT: u64 = 120; +const API_DEFAULT_VERSION: &ClientVersion = &ClientVersion { + major_version: 1, + minor_version: 40, +}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("⛔ ️Failed to start container: {0}")] + BollardErr(#[from] bollard::errors::Error), + + #[error("URI scheme is not supported: {uri}")] + UnsupportedURISchemeError { uri: String }, +} + +pub struct DockerConnection { + docker: Docker, +} + +impl DockerConnection { + pub async fn new() -> Self { + DockerConnection { + docker: connect_to_docker(&Some(DEFAULT_DOCKER_HOST.to_owned())) + .await + .unwrap(), + } + } + + async fn get_image_with_defaults(&self, image_name: &str) -> Result<(), Error> { + self.docker + .create_image( + Some(CreateImageOptions { + from_image: image_name.to_string(), + ..Default::default() + }), + None, + None, + ) + .try_collect::>() + .await?; + + Ok(()) + } + + async fn get_container_with_defaults(&self, image_name: &str) -> Result { + let default_port_mappings = vec!["8000:8000", "8001:8001"]; + // The port mapping in the bollard crate is formatted differently than the docker CLI. In the docker CLI, we usually specify exposed ports as `-p HOST_PORT:CONTAINER_PORT`. But with the bollard crate, it is expecting the port mapping to be a map of the container port (with the protocol) to the host port. + let mut port_mapping_hash = HashMap::new(); + for port_mapping in default_port_mappings { + let ports_vec: Vec<&str> = port_mapping.split(':').collect(); + let from_port = ports_vec[0]; + let to_port = ports_vec[1]; + + port_mapping_hash.insert( + format!("{to_port}/tcp"), + Some(vec![PortBinding { + host_ip: None, + host_port: Some(from_port.to_string()), + }]), + ); + } + + let config = Config { + image: Some(image_name), + cmd: None, + attach_stdout: Some(true), + attach_stderr: Some(true), + host_config: Some(HostConfig { + auto_remove: Some(true), + port_bindings: Some(port_mapping_hash), + ..Default::default() + }), + ..Default::default() + }; + + let create_container_response = self + .docker + .create_container( + Some(CreateContainerOptions { + name: "FIX ME", + ..Default::default() + }), + config, + ) + .await?; + + Ok(create_container_response.id) + } + + async fn start_container_with_defaults( + &self, + container_response_id: &str, + ) -> Result<(), bollard::errors::Error> { // deal with this error + self.docker + .start_container(container_response_id, None::>) + .await + } +} + +pub async fn connect_to_docker(docker_host: &Option) -> Result { + // if no docker_host is provided, use the default docker host: + // "unix:///var/run/docker.sock" on unix machines + // "npipe:////./pipe/docker_engine" on windows machines + + let host = docker_host + .clone() + .unwrap_or(DEFAULT_DOCKER_HOST.to_string()); + + // this is based on the `connect_with_defaults` method which has not yet been released in the bollard crate + // https://github.com/fussybeaver/bollard/blob/0972b1aac0ad5c08798e100319ddd0d2ee010365/src/docker.rs#L660 + let connection = match host.clone() { + // if tcp or http, use connect_with_http_defaults + // if unix and host starts with "unix://" use connect_with_unix + // if windows and host starts with "npipe://", use connect_with_named_pipe + // else default to connect_with_unix + h if h.starts_with("tcp://") || h.starts_with("http://") => { + Docker::connect_with_http_defaults() + } + #[cfg(unix)] + h if h.starts_with("unix://") => { + Docker::connect_with_unix(&h, DEFAULT_TIMEOUT, API_DEFAULT_VERSION) + } + #[cfg(windows)] + h if h.starts_with("npipe://") => { + Docker::connect_with_named_pipe(&h, DEFAULT_TIMEOUT, API_DEFAULT_VERSION) + } + _ => { + return Err(Error::UnsupportedURISchemeError { + uri: host.to_string(), + }); + } + }?; + + match check_docker_connection(&connection).await { + Ok(()) => Ok(connection), + // If we aren't able to connect with the defaults, or with the provided docker_host + // try to connect with the default docker desktop socket since that is a common use case for devs + #[allow(unused_variables)] + Err(e) => { + // if on unix, try to connect to the default docker desktop socket + #[cfg(unix)] + { + let docker_desktop_connection = try_docker_desktop_socket(&host)?; + match check_docker_connection(&docker_desktop_connection).await { + Ok(()) => Ok(docker_desktop_connection), + Err(err) => Err(err)?, + } + } + + #[cfg(windows)] + { + Err(e)? + } + } + } +} + +#[cfg(unix)] +fn try_docker_desktop_socket(host: &str) -> Result { + let default_docker_desktop_host = + format!("{}/.docker/run/docker.sock", home_dir().unwrap().display()); + println!("Failed to connect to DOCKER_HOST: {host}.\nTrying to connect to the default Docker Desktop socket at {default_docker_desktop_host}."); + + Docker::connect_with_unix( + &default_docker_desktop_host, + DEFAULT_TIMEOUT, + API_DEFAULT_VERSION, + ) +} + +// When bollard is not able to connect to the docker daemon, it returns a generic ConnectionRefused error +// This method attempts to connect to the docker daemon and returns a more specific error message +async fn check_docker_connection(docker: &Docker) -> Result<(), bollard::errors::Error> { + // This is a bit hacky, but the `client_addr` field is not directly accessible from the `Docker` struct, but we can access it from the debug string representation of the `Docker` struct + let docker_debug_string = format!("{docker:#?}"); + let start_of_client_addr = docker_debug_string.find("client_addr: ").unwrap(); + let end_of_client_addr = docker_debug_string[start_of_client_addr..] + .find(',') + .unwrap(); + // Extract the substring containing the value of client_addr + let client_addr = &docker_debug_string + [start_of_client_addr + "client_addr: ".len()..start_of_client_addr + end_of_client_addr] + .trim() + .trim_matches('"'); + + match docker.version().await { + Ok(_version) => Ok(()), + Err(err) => { + println!( + "⛔️ Failed to connect to the Docker daemon at {client_addr:?}. Is the docker daemon running?\nℹ️ Running a local Stellar network requires a Docker-compatible container runtime.\nℹ️ Please note that if you are using Docker Desktop, you may need to utilize the `--docker-host` flag to pass in the location of the docker socket on your machine.\n" + ); + Err(err) + } + } +} diff --git a/cmd/crates/stellar-ledger/src/emulator.rs b/cmd/crates/stellar-ledger/src/emulator.rs new file mode 100644 index 000000000..78044b697 --- /dev/null +++ b/cmd/crates/stellar-ledger/src/emulator.rs @@ -0,0 +1,14 @@ +use crate::docker::DockerConnection; + +pub enum Error {} + +pub async fn run() -> Result<(), Error> { + DockerConnection::new().await; + Ok(()) +} + + + +// next steps: +// have this docker connection start the speculos emulator +// see if i can use that emulator in tests like they do with zemu diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 85bb50157..2dbcc9fc0 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -15,6 +15,11 @@ use soroban_env_host::xdr::{ pub mod app; use app::get_public_key; +mod emulator; +use emulator::run; + +mod docker; + enum Error {} #[cfg(test)] @@ -43,4 +48,9 @@ mod test { println!("{public_key:?}"); assert!(public_key.is_ok()); } + + #[test] + fn test_the_emulator() { + emulator::run(); + } } From 0d1c9e7cf2b891f2b98b8a6f40215f37767f8874 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:25:08 -0400 Subject: [PATCH 06/72] WIP: start speculos via docker and include some default elf files --- Cargo.lock | 4 ++ cmd/crates/stellar-ledger/Cargo.toml | 1 + cmd/crates/stellar-ledger/apps/demoAppS.elf | Bin 0 -> 371256 bytes cmd/crates/stellar-ledger/apps/demoAppX.elf | Bin 0 -> 631016 bytes cmd/crates/stellar-ledger/src/app.rs | 3 +- cmd/crates/stellar-ledger/src/docker.rs | 71 +++++++++++++++++--- cmd/crates/stellar-ledger/src/emulator.rs | 19 +++++- cmd/crates/stellar-ledger/src/lib.rs | 8 ++- 8 files changed, 91 insertions(+), 15 deletions(-) create mode 100755 cmd/crates/stellar-ledger/apps/demoAppS.elf create mode 100755 cmd/crates/stellar-ledger/apps/demoAppX.elf diff --git a/Cargo.lock b/Cargo.lock index 280a87c4e..05c63fd19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4189,12 +4189,15 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" name = "stellar-ledger" version = "20.3.1" dependencies = [ + "bollard", "byteorder", "ed25519-dalek 2.0.0", "env_logger", "futures", + "futures-util", "hex", "hidapi", + "home", "ledger-transport", "ledger-transport-hid", "ledger-zondax-generic", @@ -4213,6 +4216,7 @@ dependencies = [ "stellar-strkey 0.0.7", "stellar-xdr", "thiserror", + "tokio", "tracing", ] diff --git a/cmd/crates/stellar-ledger/Cargo.toml b/cmd/crates/stellar-ledger/Cargo.toml index cbf9c01f7..6dbdf93d2 100644 --- a/cmd/crates/stellar-ledger/Cargo.toml +++ b/cmd/crates/stellar-ledger/Cargo.toml @@ -30,6 +30,7 @@ byteorder = "1.5.0" bollard = "0.15.0" home = "0.5.9" futures-util = "0.3.30" +tokio = { version = "1", features = ["full"] } [dependencies.stellar-xdr] workspace = true diff --git a/cmd/crates/stellar-ledger/apps/demoAppS.elf b/cmd/crates/stellar-ledger/apps/demoAppS.elf new file mode 100755 index 0000000000000000000000000000000000000000..392fdf673e9d4301824bac5ad83e1516016c327e GIT binary patch literal 371256 zcmeEui+>c=+5efDy(c7_kiaI{gxSr7*@Vjmkc*&hc5z6!C?HxuunVGYP}@bUUF5ZH zxM)!H-GE>c5KwCyyvEeDVx@0T-@d55%n)sZ(mJu#XlvWCwcRAU+5LUbY=G$7_t*Xb zv!BoGoH=tY&-tF`Jm)#jdCuH>d9S6CdI;(vkKjEn)P&z?el1KS+~`Ft zxKIRs3|aBfotw1aSN_a+wtpo~f7B7s5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5%~8Iuy^n6-1tMIx@?3|H>7S8dC{a!PaNA#3??Zg5?9l1TdP5>A2cf=(V#9I zw8e<}(FjrhK4MU72OGs^KP%G|a|T0V^dTa!GU2m3?I#)aij=+EAe!nU3ytaz(+tmt ziMlpL1V&;J=6L(b9XAuAcK3D~p3Ql~EUszgVkEYNzmHg+-L?4cg}hiR&KFtrhY4OZ zst3|W*V@CYA7iFpE|KX(`lIq6NMmRqhob#335HH_{tD3eNQp9eorwUWDblIiTcHeMCJ5uOADq?lW6+ACB}W1T}VAiOC26Iwh^HS z=_j7KujqWK)%89{AXE){W?V>b*3z#==`2!TD!$xn=~(G%in{78F-wO@T{3J0rp#d% zX!z3*Pz6pmm%XH+>X8J)WGPqd&=CD;N(?;nPZ2FROYyWk*XSrQ*d&UjSvIwrfu(gHU{xOr$pD0}gP#jE|R>#~--?O5YqQ&%&iJhb0$l9_-(vY)(# zCoH3WmNeEg>ZeIs2d$GKBsw#dzIRt6EjL?q)SrqFPq}QEQhv56!g>lN#$%I+r H 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%0DeOpAc6QjGRkYHsYnOanmn!YuGMD{>yrq7z@ z-Ujg^QX(EX?RiZ=3f7>U6JhikCPYhNu&dmtxfH;R#MbjvS8Q z+*6PSJw^zYPQ4d7ybHeqKT_b>Kq6;5$=wcj>p<8*&v%f=HfHZ|%(m@D=!hl*$&N=b zYS>Fbz`ww6GG~fLp7^w81blip%8A4(33Cp%q`+bAiDCRB(JFpdO2j`VIdBisgv;!# zsEjPk+i^1hIdnIYf*F!qrewR5021J7W&{*!rFngvcAVkdL@cZP=W#OXj2JIRdC1BH zej5^;7IlmnHmVMjWq14-_hMbi^;>M~xw2q?N#dZ3CR+kYeDG`Ce|>?NEh#6vwQARa zg!d`^-A<)!8#HSvmuxn^rse5Z4cKNtIr0sRRg{igdy>rqa*e8djT~#>;=SBL8EF{Iju{p*h}&;-l6yg4WjY8hl0g zvO82q$#7f8#_v(bDz}bIjA<{jk}xu@2VC@F8M(XOQbFpq4sP2JAyYw7hpJ&S$v z^c-_R$hZPEh*Sf%Z~kTv`sQaS2fD{`=}o6uOg-rho*#`Wec74M7Av_SK-Ur^JSrtHcQD{NOt0^ zmCipo?~k;2q5MqbErkP)YZ|r`cH{~FQ(Prh>0-r&t5xQ$o%m z{z3%~U|*vXTDlYsurjC0>s%T*s^k)zo1$)K2Jaseze#$Wm7+OajxoL#Py;-|SBv+{ zVBt&iy%*;a^-7KgzGM1Y1G3X74@9RLy4|VVCOOq4_+@c;8V9Qh^W6e)vkYUWwxEs= z;rx~}R7a5v1bNT2gZ=9T3DC1}Y|5#!=>LM&h}DT)D0Yl#=yWh==UB4Mrw~OeaBqib zD~h<>1IlOBq+H`dU178w{%iqtI$wi;R<+Jw)f?K=cI18;c|_Dt4&Ib=zb*E+GnT&D$oW_Lg^t7q7b zL4DtMbC_MJEpAO!(8Vff^Mbm}+P;d0UNmZiZ~(&aeL^mJ&i zsxyKAnVyU2Y`%Vy)q6`SauRg1Wj;C^Q>9A2rUI@~0k4Le)T^ZJ396=pG{>*Ft*hIv z|GB!ZFpPwa3t-H(wyrN}I9w-EHndKPx}-=cx1_k;`}>oE|8bY?FyId&GCiXmc zb1To2KNh5N%OS&?yX|*TH8v6z5w^e{b^b_{B=Q<`4d8dUlz3{c@=x=$FO11jXpao zZQX}yXO(roG}1LW(PZ?oVjr>oktWz9631O?AIE|`C$cYpTRxX--jr@u>mCD6vBiz~PZU?-`(aC$+mmuTbDCOQR#rBw%sMmq zl1)xdB+-K6&fiEiyI~4!v@+6B#Q%u9yMNX;G!nQpPwi8o$@Z`S)mg^FH`?o^9=_3` zK^xt(D21PL!YiAMb<6H``Yd;KVfIY`>|Oc~!-cNZFz?O4E^~?};CPoIjDiXY@e%9Q zge-@2DkK+2%)cD;Y2~X&O$3SaLygzXk(Elk*fllhsS-qm7L!tGq0!=xyoWocE5qb` zHw%s|5Lq(sdO1S>zeYf>S)d|o)c5zJ2@d z+}Dn%`*u?As{cQO=ZsgJzTaC{q5p?41TFBZ51pwTxc6qy){L{4UmYQ+f zX}i!e&UN2XXc1KBe4Hq%-|di-n4pUakOobSw^Pe`6_a-CAsGC3q@IjPsf04l8BzCa zx3FV8)&F(?=TzXKeFse&rdLj%IeWef9BGq>$cpTX$m%X-QOytsD@W{4%rTy_U+F!S zUZQl;Ft}t!`n6}>rR0%H9$>XnG4qQmvdjKaP`p`VP2g-H5-ZHy9pdAMps}(hSBOJ_ zjSYj1^1<+Y6OS3p07cRPVX%GEB(cQ6>ujjBM^I?F#mP%Fh@QZ>K$z~z0+lw^1=U+P zfIjVkVm=YV$_3mMCny%78v@|&tcPm_xZuVs;F1SCo^c3)zLo;L(k=Eauh`48K)Iu8 zi$xW!DqNHW!1YoKfS>I+J1LW7jTJ7B%b^}=XS(Haf61cr-+OTTgPMv9oq*Ch^-LZx z9!a6Q$i_2?Ybbub8ZwMV+#BvAY@hU$B!1yu)NODe3KtXNQN*+Zb`TTsg1 z!uAu$OFYPbWI_HeRsJRyX`=_qmOq4ad8&Lp(gW6OJt!5$XlB%aERR1+u<=GdwsmpsU8vmjsgA(y+5=&>wLjQK;w zzvV+->O#KjL0*&v`JNBC)P+R1X>sBiHiYvN3#lr3noId+5ufnX5(51?1uAE+FzPgR z8UpkSw*8lE$^hl%HVgWB7U-ENQ2C_=^gIvr!z|GAQ=nU1(2G4#{JDe( zxYUBuPtc1AX`=^;OP3Jjav$<#7xFp}azhs6^*-eDF67M~R)G@$2tpxd%Qm!?49b3rfi zK;O**y~Hmcm6MQO=|R4k1-Z{y*Z@3NI{{f~G9!hkU&Ex_BS=An(qCeA0)!-GzMGgS;gR@>vTh{@>sh zzup7Y)tJnt+F(I7PhR7KzUqNqwIDoCy`I8)g$w$&2YOi+)^}2%7rCJCd7${w3=!}_ z3iMnT^kWb7>@3hvEhqwLvCDvOJjnVi$nSi}Z?TJ`tDKJGVs+K8{3|)Bb;4q7>1?O| z)P+3PgWQ${xzs|6sqf)Q8l)F_pzmgZUXlWR+XcPC1KpGb+Gs(=|5sg59B*4vUp^_! z|7$I%*RR&Q&^LL|&t@UM#X`fZzqu8=(}R363-azD-bY=$4|vcIWH@c87dXT@*f_%w`yxN6)-Gf|~1^K3h6k{)QtMrZs zdTAD8H~UC0aFK5HAeUw#-DV-x2hMVle&&Imkp=pNkF>!>D&N`af~RFco`z3rcVhn< zgGxHqA}n-k3;KEsO_zE8 zL_2D3_8_0hg1j|F@?Tw~cY2^tW`W+F0)5m4mF=i%hxKSk6mWmjW5ff1Vw3o=2YO!? z=p!l6dt9tfc%XM>fv!n`-UcW;pQi!U&gbSV&}S`ZYCrk=EPxFb01jOPhd}kZP!YT` z3v!c%OtsYS+@jz2;Fo5>FXn2X+X+79P9UhyM%O0P+i)SCDYDPApeZWf4FZhQw)bKW zpu4*(dwafT-KLRQEr2U5fY&WvcgtJhmA4^?jy+ez{N6$vT_5rK-8whR?Ov8OSy}G1 zS)l$gj6JE$Di89ZAmkE}@_>cZh`Jx7*mbT3RNZ4$5Y*D~s0G!CzS{+T(gVG7eppog zHHGz77xWnq^yVzA&!s^B;DWyBf&M-V^raL5SG%CEd7#U)2zbMS!hp+R0JVC@gS<2g z>1H4D0vB?t2e~v0a+?o%mdn)7fm9D!lm+>fg%nfkAQb+eim|Lfzx8issw*rP|LeJ# zDHV#>SH74R=qr}%XIKE?`WQoBs&X#4)Ii&^K$m*BJax?GU||;5C7Ff2l?8BRCcvv% z0N13bk&QAnyWXqWx*+H()$B$Ks_ycPOZcrG;cMrGyUXn<&?jB2cY9d>k_Eal1^TcH z`hW-eKo;mj7Bn?Z?+H@4UKBp=0eHOsQxIUQ0G_e{;{DBT6`u2|@W&viW$n5Y=#NDRwi}W~n^T|{xS$_+pyy|SerQ4IglD@3 z+@E=nXJkQs;X^h6NgIC$qp5~GeAs;-vjE3D@MU5rt@KtDSn+{R~G(CMB8 zlgobk@&$maV($epmxu=!S#U7DgS9=7mwS+J1tBfxuT0VKnoGmg9t|6_XwbFbfWGqi zEL=DFrLJ{L)$Ie(_tz}QyHch8#Vu7=h*k54gQbpARqjuLKH!2r?16GgA=I35pYQVX zZkIt%dIa2=1^QPDnwsElb>Uy|;Qx?C@QW$by68Ytz2Z@^Jc#;85&XIZRky#w1=XVg zs@tVOtkVSg4-1+SUl#-c8U*lx1t9Oaa1{HLPdoz8Iw36RK1&g}$Sw72uhhCAfwsxN zOM!mNEo17^z`3Xh{E{~-Qj_&^mA{y4%WCqT?p*wde*;-v_#OeAV*wpjqQL0T== z8!c#x+fQc!+?EOOWEQ~PnE;Pu0o<1f@IV&8!qOXD)v z=61Ut_1Yu=WZlb#G`@2-=Wt?F9|ndl97oDZz8mSo@xaq<2nW3{Wd zpMvg7dMf^>?sFDc>!p2R^j8AMAbRHlQ#C9&(`gB%;XSq4#8FDbEh>qFZO($&ZuYlR zY!4^@8mH|9$EoL)Q5`XJn5?7e4LUQv4xeWm@Oj3sJNo31yjIBcY(fw2aYU!flUkD7 z^$C6x-mf>A33{oZ-vPQ^{3BeX=pOVnI+NZO;TBb&yaCKCr!?Bjs1d$w(RDJ_{C1%$ zMt%!xb0X(7+x>k1jz6OJ!GG4X^Ua^=HRoh#J3gg;OgB-@6>l$0Z})ImCM;j8Wrxda zPG_fmrL|lc>sqdmSTveMx@uoNT6dq!G`R}QHpej&kmh%%A3+@{Kt0;Xb3yra?1KO~ z)M>YbeK@dFt%VBsrU`6|1zX~xDfg3>DoLkO?d1UGbBv>PB2|PnMY|b8w6g-^?il#t z3lG2Y{8~H1<=Ao?_Nq9G$FGX4abMvKArB!(X|7BLrQR^A8{%U*JXNLzcn|>NJ$yr^ zt)k;8o9Q|VRk0JDT#DdIucors>|JKgP5e?u9YBVEG?U?MH^Y}U!=iMCQEsmGmkZz`^v><#)2X>wnId_$(r&V8kavjQV8;r*>kye=LEfPd`j{*g zu^F>tof>_rERWhOGpLX7O?2@s<;p@%* zM=Gm?e=)8L%(3#wl*dfOy=Ui8ERxwzbcv0jJ0)Vf*EG1Zld{?If_r0%G=<>Qv&J~% zsORE=u~%EqW$xI9`%e41p>q6I1>r=P9I{*o9xJHRfFBIaaQ$s|Ri0N;dBnO<9&RScFIphaz=O#1p>Cq+!HK?H z|4^&(J^?4DSOD{wg|q8aYpbX?EOwSbfGlP2yY4cWwvg$RM=4~`UnfCyrf+#KY8DAI zhWT;xE~&bk!MxAC1zq>O2gCl|s1`4E9FcG)yB@^a4w367*{F7lEp#81(3SPaIpo4# z^9t-&-MS8jmol zVyS+xeZY-7(1&5)31W!G)HTh*Jr0KG$3^sAq@sxz(YL!qpUW*}5uHprylp=zMKrE; zM0EGqF}h=;JDZSukkqkR)DA4iM;BDdCQpW$9k1mcc*5DEj@yh4PC+UA1GlV?IMVCA zjEy%zuzf|n$|(BKq`ZAmuddXqXo%B?2jnQaH}%>FJb1Y|%xQlt$hmdF(F~iXCGx!X zIrjh5lykatO*#(Cdz=4tt2-*kxcICGZ== zgYI%Xqo^2H0%YR5f1^`sx~_B%(X+Bbun$ms%7+lRa$k|5I-ZjP zj&}3SYD?otCwiPSkZaexVJrWPWw;sR?dF0i_jOB+`S@7sCrQvQKHmC(+Zoq467)LF zZqES?SC(|YJ7Sg*!Ib`mK?W~&U1t6r!H`>4@3h5AO6Sxzxh~DTi9%k_@ZBkXtdlzw z_bMFvF^?_fD9=HDw3S059R{NxyqLJQdVI9+VaOMW6WUUeq(4w zDkM*=pWvy50(FMz@ZKL=pfs08CP8~K$6W;oIT8DJ&bmN*IUZy1FFDApgBE%C%VSDy z!_7u5B;*UA8q{etvxVj+duoO!Gr$Y+ai_UQCye+vxznj~b{4u-9=bSZMWE}1zXj0c z3f&ARHrR=to#ny zYKqz-(98NgPA8^WKD3x3D#D*VWT z-ke3B&NZJ<0Dh-*NvMAI~KQV*5Lxdjp1d9pcE_CB~FCa++DAm3yOY7~FbB zr>0YElW~n3w~WKM($fuD0`h{`@>0u?ZS(EE_;VIUTI}p>*KWnJakxz#W+8K9r#oGA zkvM7U=(SzwwZJ-qosOUzY(b*2X`%AYJC40u zoVI`XPI20#IdjU!j6JX{wOy>ni?-b@Zg(xCETNxK*Wvb>6XgYArs&90G40>~s?sUD&H-l0t z9&%uFENLt^aqiS5mE&g7b1U+~#P46xWb#+K%V{O`98Z$0T@hPt-PzoIG3A5*>HROX z%-c?XsgT{JI>&aJgxm3;mnIamA*^k8s z3$a3$Nx6;oF?IPU%f)*{zo9H8xGR9KF0g+j?B~q$9H9#PMq$?xu!q-m8asg;m7 zfrRrodQ0uR6kDL!OTw{5id|-7-O9X#D>aI{UJlgO`=^A;KTwc=bRn?~;^>dc+<|}P zj@pSQ&Ev!zg1(V;u2&fyxKf!1^3s)Q!Ur4Jaq|WwzLMqS(KbV^us_3W))uD91@R3h zdUL4CZcys0fz&I5sTV5si$H39Fm;7ew**p`22<})>LQxoFZWa52uS6wR_XL#Tw;}A~%_f?~f#|LWku~MJGsd9TsE?hv$n|gE{cWWyNmBB)-{K}m% z<=Bt&Sh!=}K=;xvb{zT&=cjbKOKs#kfyfD56||8b5ZT^^91G-GHWF9MENyVMtYPq_ z{NXQ}>-w9t{GWFy^sMBKaTBjUeVLQn07=VVG;P;it-Xa)YM+tEakBe%wRrQf&0%ZL zJ1Nxf;b86COa$Lefz-8L>U_ICxb=U!eEzYw^{wMe&Gj`rE8^|Ko0qmNt~ja_H3r_ zlK2E6`hW#PiQdFu_lVYc!m(-4qlHEaUK1Zzla4-C(SIVkleTK(m#0(el=6s8!Ln%l zBAZfY&pAG!-0Z_||ManidC{Myw)X~$7(92L|BP9ge?PP3)&N)+Am`4jm{vJ;(&YK> zTCI6_pNJi}R2cyEX>gZ!mg%_dYwx{|VIL7eseYhcto@Ydr`RqalI#N=2YR|oEw(Nd zNAYrOu+kX)BCjUncYL&jSFXMa5LAb_D75#tC`NM}olE>GE}Uc(~JJbLX)m=ySwcCsdjJ{7ZV0v^4r(gM-Fg+#p6@bcaUQg?m$gfA*umr zjQkGaAuEF#)SC&k$flU++aQKr_cQkHoYgZVwy{jL^U~o}oN4#Ki%&UPTy1U3>;nqt z>$n0Z%-W-|?Hl9_J{)b(f9Q>npV;l7lu(-2o}q@F8Z0*S7dyM^f?jAiddLTT0(YJ` z;5U=e+>~3y%Piv8Ic(?41b$JPGpm;IV>Pqv&&s^Fu{GGhy@>6xFmlz8qFH6*|Hav- zoDx*gsZI~QGK1q2647s8ssjbwL~#q#^7Ch6srkLjzbi>}_uy|b{&EdEGFZs7CyTgh z?j54qcOdpVoDQ&pAvK!8*4{dy9*u@idE}6~`5s{2)4lce(7}@b4_f}e;PUDwx!J<; zin$3tSD{~OIKftr*DU0MMcOykZ4g#!dg@V>gk!5<+0@D4&tJR{&*(bx!%SKD<3g-)b~1h`t|S2>HbZhgRk+Ztb3G>tJDVBXobKgL zH{M7yoszMfy*t+B4D2YfzQM6jeU8YA#!$8K0C~D-J8UmQL5To#daZ{a)*CGk4ei3$ z2+Y9wOS9U3$g2gNP4Bv_q!DmPoGPWRoy{a3LdnQ0=dX$( zLDq@Sx(f!Ep3FgGgC~&mLq3+&HIOQQ81^`q`V zoZ3l~k85oTYGHm0%q=}R$D54mte@$l&WNK6Gl@NgLebbBG4rdyoO0oj45)!pHAsJ} z2Bl-(@AR-I0C+}p=X3R$I|MmWki#sb&KMEXxzfCrj6@rsaE7aun8`GE&{)hJEMs{i z11e@HHekkNd&63*ieNnjYdDHo-Zb z!UoE}bva(s6V6d~R@Vyz`<6RXi0C3U?Kmt7oZZFWTv`zhF)Mqr=TCO^x30OZM@yb> zaLlly)`tK3R%@vjgEQT$MvkwkJR$8{$ePBatsfEASz80-SK5pc-#J3yYHsXTD<*Hg zT=G`YEjrO?PRam0X>x7N%o%eii>913Ysy?aY`cCsG^bb}P{lu|PYABqLHWpWw zj+{&)IOxF#C*7CTRBY2Pz>c*KE{dMT{_R%b-M`W7>*L+u!;4o_;f*tUnOQlzGAudN z$835iClF)h-9trSBo>j2t*Dt#F%KZttVTxVocc@3krNKpyUY5rN-O#90L1Ff1hPUP ze+8r?+BO|Q-G<}mLAY#$qb!aYj#{8iYp@!e@8mLHdZ7F1ibyu2_-)zG8BC4U-nOs;b}5V5FZ5zi8S zo$?e=*VD0COx1OYvgqu&oV^XFOtEE7#|E2Mg)z-^ygC@_!TW^tl5H#vi?qHFFYGLE z0Rpl!>=OB%Iox9^sbdZ9iSiWKC4lkpnA4?PFMtdNxQpgSP`;G~dqpIo)1l<%kpcGzr6KoRvC^e=M5# zaA>*te^sWKL;TwmwKke4d%-p(9xT)>-{Qhjr*i708z4&Vwy#z9enne_s~6_5P=MYy z+3D~Q3@g#)EZRjeOS#`I#Qqq|oA=lkb!o7ty;{)oMsk8zFZdwC01o&P&I}GBblkOA zK%{ilxne>%8fedfetWL?H5FdukM8?f`}s3+su|@HA}kc{QMOA+=*?-LPJ}lFv#nO@ zmCa;ZqlBy6gco3nrs@~TsUCVVf-fi1ogUWma`At~{MCYHrn$MI#p8|81O%L&{p5EF z2LGqEE?KlxOq(=+=Gcsn^Cfxxp1X+qodzA&HlcmpWfoB<5OGVu zJ+8yt+C6;GLUD>py9S_6DvfTu!7b%_w#Yf-Qd_n4?7(rppRAH;h;_w}haM~|?VIQR zRQ4P|ynvo$hK@2rbJ6)}hG=So_1w`EB?}{J%-IO?od>b}UpA(ZWWAYFRezi{ANdPp zFx^>4lHVcOtmoy_%2vmV4CQ)v=bghFUVEWZv?s)(EK=jyIC@FgP3xiJWR{m4`#gRrapr6czH$##9qNM zzhp||oc3xkl}LD{XnxfeTA$aK6NH89TqQ+LGSvcC!rV8-TzESbOHcA=@!UQq}WUUYY!euICgP!I|zP!ghw0tZX|vA?)GpR<<3`@ms8D1AX#+ z)Ca$n`T)y_Ce%|N+R!0oX54D*)t`RCFy*}gutXPC`5^qe5h4ii1+Vy69^6;M2h$U?M^&s6S9@XR!nWJG6*K@+FM4>_OUPmml z=#5;)bt6Z*)|Ncgv7g|KwR^mt!_^b2c+3_@)6M@<)HK8d&Sswh=!gB*dHE9hMcu1c z!L9)#AA#RG!U=`7s(ec@mjm-wUX}No^nMfkRtX^VYvWbp3-!X(mgA*g)@xlzFW6LI zCLjVDWsUcyjnkr0K(;Wx-NJ-Kh5E?-!ZLm{3dtna&lmp9CN8_&V5OQV4~C@ZO)2A5|jcT`MepiO#lU~^WZ7JUmjrZ~Tzll)!t>I1P z)MiZkU+P-P>6~B`n^Sdrqs(!JIK_v;ar-EQcG8Q8FGL&_^N@%Zgn6;!P=KR|zGPrBJcv0Ek|4?m#Fy2<7Kb09BvTdys%5%+Iw3Z}#BNpZqWsKb`x__4xf zmVrScK$mPT=X(IpCBjE)b%{a*bccl(-Ju=b5jE|iQ{J5@;(W+}b#juYCue$a;MJK% z!&#n-5#_AZ{C(A+G!zGzg=_-M(d1d?o)(?q--bzX>}*a*isOgMfS4Q0-3|kj&YIrj zJSJbR>-e&2ibhUmx?!)%6E3Ar$BAg8(koNTvG=g?DaN{;Ymm;6iJ`<7>@h@$|aQQoi>czYTgo&~hRz-&)q_}E9pQCHT8=3y^WUd21!+)nTCy!H}7f#}3 zC8DzB)lHc0cXcgF{zhf{o7({!M9YGv9X8^GwD!Zn8)pn=Wt+qhr>m4=lq3*p_COo= zS+?|MO5WU@_;fucucw#v?$R#U{Ub-M|MGlFsRf87e;{>@@io;Jl;R2_;~y(ji{o-I7#Me zKp|#~7U@euCXdSbJ^9wH=;{8 z`?07dNt9>dL}%QWO{4BofM}D8=o$~v%h`zj;34V@aeW}JLxS8?@F}*79n5XHsNda8 z_w)GxdXe_H`+$xn%za_NW4QrGAy&3-wX(Id-ef7nibIE+{WMN1V8VD5TVO-t*0X~u zq&U0w`W9>3B2%X*EJI|{W4bG*+RgGg`vS6@lgbht*LF0!dD^R!&xN-4$pCFv%6j)r z;YPTpGB-Q!B@)`Q>5h8WPgA|CkxlN1c#?B^dW(1WDwTLKRQ^>M<~41yQ~9Wl zOPUWCCe07md2oK%htK$R@dxR$_=8pU;VqtQdQz-www!GE6#YKD|7*)>1wKeeOMDUC zK{z|1Wb)6_n^ixrTxQe5G6Av)N>x||*`-%z0jUbMkiNbAsdO=>8T>ZgZk+vc)kD-l zTKQe+r6)qa+spT#O;2uzt_=J+M`z3s%UcsESCbfLHY?EiZh{G_-03yd=~}G`Bv#JB z0@of2^`yP@QG9EOvJQRI4MZUFxLlOE$&_2U*6`iu;?sNfDSQus@BU1DdM4XS_l~lx zVR6U2XMJX*61pg1vE4{U%oO%qre{LGz4&#UDl^4yj(X9`lQkQf-k2EGv#3YrCuQMR zVEQE3M&q)o$8Iq}vqkJrqRR3rl`6}cO(3i%q-nUP0`)l$f8=w6#f}c98P_JE+*`{l z_j7{6rkKTiVbo@(QU(0~9si<{=z_w6c9CLLlz&G@);9I$cXVOixzUE4NQol(cPjA* z!i~{|#pgyB=Pbe*b%Qgy@HH{0Ib7q2U!AkI=_r1ETq;BHJDJ9lAJ>+;T6{&S z>*=PUjV()gmy+hvo>5je(WvxD!P=&N{P=Y)^5Xjw|FMnVFN!ZMRA>Sch3~Ua*#EynY zp%y;fR)SSpX?wRX z`>+!UpX$Rda@*C1B|4JF|1E{rl2>gJrbG1_#j(gH(y!w?gO47(($6ef+w>d4!lCeH z!oB(S`-zVyufy7=9<)map3#Lp{dBTK-2RoS@94skG~Rw4*EV(W@zQ7f@{}(Ti1+c0 z0zSmQ(SBZcAJm#T%L_CWDo}N_V@83FZ_HxI<0M*ZR0b>Nr|8DY|;<(^9AT! zOT2jO_jwL}v)Z0k@zVi~l<;d)7qw`**KO9Cd?1<=s>+F&s^!YgV%RPWy7jXW&yjl=h~(?-+r42r{eWbFsh3m z-h=NzUrzN&rEdU_^7`;^6W+TSct5>=ueD9%vg%^x` z^JUwP#7p-2JP5)Iua76pZ^7L`##y~H=Se@FWJ^;JuizVipB2KZ_>YNK{r&s}ef<6d z54`!;mPhDfH^oHWtl z(Q>pr(;LrhX&w% zUaSW17#p9#E4yY1Z)SZ}uF^M>o+_6qqYK+0tIo|?Y!XKe@%s_~arT=3RLvKD*?gw^OJH3ctebc+u$>@Xeg(&);zth8 zSer*E+QJ`6{5%LP@xp6Hu3^=|K@e23`D z+GD648dUDS8Rgn}&QH^WnfmrL%{r?e65xD zUw~6>(HzgdSKUi8>z;wLwfeP|_Rqi>DsPAB4ABCbG|yT~3ze6VueG$+aQ@J=*2?== zO8cSp`)`^LvfaLCrG@I!glTE6eWmmO~R z*2)W|wO02Z+73T7-wxOBd+@Ba`t5LeA)fuvd<^mQcxO`~JpMZ5N8wp(bpBVTwYGi< zwO?y|$>4QsbqJKFr{Xf&J&>kX;#%W%DQ)7d-$xgQ+9-pTKpphlTSi)l&J0>YX|3gJ zEp3N!2I!<}nQPh~nii;oo?6SGCBSQymys68r?iavh4TFomqToEiJ$^J#Y4-;4I0MOBpyrX|3@rrJD(@ZS(}$55<|0FHrX^p6wu= zdPA(WI<%J7n#^h`pPm5Ar~?6oH3#U;uZ*-n{Tjk_?qL1ImzHSJ6J9Nqr`Nk$%BSbL zT1soJJf&rnM?hhJ{>w-U)IBTTKPfG&$B<6vYYyY|gjA|8d%9P}>+PsKV0=A@53jeQ znv2)dQ9+%)53gsVnv2(qQG5IN(OLQMdi*KCf7`yIN6$P>v+x=EIeXp`)Z?h0&-I~h zzze7TS6J@_rRwRO*(qMn1C6qHQ@Y5M?nUW(7pR|2_s#@pAJL~YJq+}lR`QY*$(V1+ z>(i$+>7{&JdfaCW;jrG*PuE*M&87SJP9?pB{OV_-Z8_mUeXY*O$F-WY;mwrg=hd@3 zyV~@0omEbd&%(8LU+Cwt5tn}%?PBc;l&<$&e&&}IIDb}}{`qGr3hPN0;Z>U{tT$GI zeMj{9`P?#7ZD#rEr}=zUSP!T)N1so>9!qI1-7iZIqx=Wu+II2i_w(v$lUr?CM%_#N zbiE?7F*`lQ8@;`;UkJDOEV}is25QYzz4V-h>gA{FAq|CnI`xFc-h_j6`mjXXzV>~> z;=AC3ZLaXpozYiQmdaH-DXfPQe$BTpD^!2Q>j{IeZG1|vof=z8*AoB-`{^EUg;#0v z;6Edj=9ecY`w5%oo{ji<-@$<=n{gHlIUYcJP(YBu6nmL6} zKIoI7Jbu09Pd*;a9?L%e2Y!684nDj*xRta3 zKQs_8Uix^J*!WCdHpi1c4x&H*d~MTe3lFW{oT0LnCdXKLHqD(E{CN4r3dXD6f|pyY z^&wn-nw(lSh0=U{azvHliPu+!Pu{1Fw0Sb?;^&i3sZlmP%}eEzf2fgxc=1N@asu@m z8=tD{=t3GwyivOTiQesn{W{4n(-h!?{mCy&?v!L_85$=OecI)HNZ)?n@#Be#1bOSz zBDX{O_Tl7cCyk$;NBE-Oi1E`mx#RB|-crHj^Ht?RJ~q zd>>KXU|Z7k?l|;hi%&x_eW@k5AT0;`Wx2ew`Vt6ja>egMy6licyu!h8r|@eCoEJ&gek7fqWA8tdkRBPvntYPGf7`#XxWPHS%_1K2 zJU4nkVb3MGXT-vJG&V+(XA5~Q24_Z|g9`gE$v-3S%-pkL2XVL%&fA~7v&h>pd2N&W z{QiaA>Ur-nU1G_Syfd)t4Chms*ORY!%G#zit(194Gi8dWo5=fGr8|G@UzpcG?Xt=c zTmc)R&A^HGt-*~1ZrcvP9Sz)I3^17BraCRg-Lf zsV?h*Ed{nC-}ILj%Z`k|Mhn;?J*FzDZptwh1*BrF{EGLN_sWvkCLvvKsdkX zT|)Xs(v{}(e)6P8v2SB0tk0nLcwyneWXNM^io`{Ox*9UMnU|nKJwE$~m*}_gXowaz(zIzl^xWEtnN=0X%$z%8${a7Qat3Z@Yi3NXsG3wWt#XPN zTQlSMNz-bkR7{>de^xC>Dr;+j@$&#T74(y4)l^i@o;`DRn*PbvlV*G5kAnJHlcrW4 zQ9GNg5gY~nV$!s^m8lQaTj^t`%(K4-KvgiuLGx-OJor_6Ks8ks@G&*BMhvG;c-ER7 znLcTrSB~O9P+M*P2(Tt{!t9#bTD~eO=G2^6NxE2AF}1RG!rbX&Yb&Sw6-g&3HZo)G z^okmrTv4(A;fL@4tBNDW{`#PbBY!pVpo)sfl$trS@OUVCE96pG6{J?>mGiwHmF-s3 z`%#4Yg!SfIJ!!_2X_ab7_v;8+JTiM;#B#7=`kblhFJ8--F_j9rfQMAp?hpA9UO$;z zGrRJDnKP23aZ^3SB;VpPpMODqTX|`Wg&Y2UTy{z}(NaeiR*>uG*2TrsMnmuze z$rY2u(n!VVnKP$(W$cf?VT)#V<*aG*Q+R|}Ju^~UIj43;Wi4#4nW5iVljcvGIcds~ z^Ji6xxAdEeirU%p4>~Gx?DSa`bic@~*_FrF%$z$15++y960!dud+!4yca`n=74Ckv z!}GF!te16IFWc+&vKH&-X*V9P7qP7WbT>BBwyUeF+a5wF_n&Han|60^b+<8yhb)8$ zA&3wLAu<{vL=ZuUWf4MzOjzPf5SbuCoCzWbkr5i95gLs~ERW2a%+Ais=X=h*RrmM% zRoguo@0<7D@RLrRbI(2J+;h+U`@6qiRS_;k@N0Gvw`vQS6g~Y;Z1f*Iymw&Wo7gc& z6u?t>OgDj4NYWvObMl9X-pt=?(McI;XYQIBovAI*Gkpw{_8l2GGE^HrRy%n3VD;eM z0UET$>l^7iTx})4(f7@LNA^~FTb!eNztLA4F1HvXSm@nb?W>g!9&T|5_6}8R2YX9n zb2Iism$iDSx_9UsH7nf7p>GcDr)r)U9_p(|V5l}!-CL=)m_c@UUm!Jtt?&}+HveLJ| zy7%zS13_xwV3a?&KT7XESlQpqmu6F@e_-#;wd#@K{Rj5$Lqb1*O*5ZjdaKVaF4&LU z)#|kpZB#!E$A~jb6|*Pmwb9rG8J@HC#`HMTO6ZHzv(pUF(8|kLEiEmeG*OzWPoJ2g z6D>C8?x>dYLmH1_nizRvz z&3NQ!UvFtqe`_;a+P?j3_S>pHEB^7_XZWF1``a6Ho!{`LgGRE(>@q*s7;W6uhDF(j zsr~#ZbnIX4S~BxmxfYnx77=P1Yt$L&tz`WSVo6%FK#@1zNOdGbZxK&JYix9(eti!P zJ5jUMfq2#zmSz?;8fO{0%*2Ze1NF(p<*Po%q_K2La~;E$4q$ z=waW=v=~MOE|JrGFF9lHoj5%@J6;=pCuN?XYZZYZiZSM*mC{-g(J~NZhxB)p^{hsy+y%0#qbCwe zvovaBjsZbmAg6z>k+s*mG(R&vPUexZlT?wt$U>@Zo48?qe6-524I8m$mHN(%&n&TY zXDvpqcloC9!Bg_1-pH|!u%(`vn`I>WF0xhlq0xD~IXqV=mJD&(=Q7xu_Fa5|oZ;E& zf63I)%&E@J9U7gz%eQSQx4(X8j@io&81aG8#)-thVfHQz2Qqr=_4$_J;n{_yc_w50 z$cEsSg}K>gJP$7HJ#nHz+bWuWf49foe6kG>ae0VeYzf`+n6z%w2gO7 z+v$7%&Dbf<2$^g)#EWbL>y3j8^rB)Zhw1y4rJcGWUbYD-wGj&SS~WX^>h7L3S*>x@ zWbJJcC`FlYZ{6&C2b! zue)}#csV)0$h~NZ<>>kzeeT$$&s{r9(+k|ToT*RP4zV;&)6`Du9+CSGo9wyG(9)4K z8aokf`ROu#Yhzi~4O4aO>Mp}NMamy#Kg7x~ZQZV}`$#R5b~nTZWodGT)!Ms7y?<%e z{*R@!IJd|ab@9}E$>g-4&(`m(8h3VSrc_JZ9-o`PD_AW-Pg679d~<_DA@%V)YI97N zlQVO7mQL>|o$g6pqc@zcCtf$SgI{3YNX%2MMOKG|6XN7i%(z0I*Nvava?8c`u5N*`tl=2h zY_i%(u{&N|q(K~U*1)~>xK63)@>YT!j_D?6!j4z=AFJ&ju8j2g-F7o|wC`YFWvE)& zI}~sCn^{AB`^!CF-}Q|hr97*WV_m;{XP))-o*gM`UxWFs%(^0r)kHm$(O%>Fp6>P< zWGthxd*?D5Sw~xie|<+!dkr!&Weg1M=&4=5dq?f&q!RrVRcj>S3~|492fSA3(m?ke z?rmjMj~*PV_Vw2KdXI9tQ)q?0iJq^2eaAm*$@fj{+_}4z%&TW?A3Acldh?M(eH9K1 z?JTF+lKYn$r|UV(#>*H_^ksMaOpS@3rGQ=6v1UiA94z-8>fN!cTg;&YdwY6zM#=*K zj~)_u1XR>F`u6YN`;EZ6Uijw9k-nkofXICVoNV>%3%TFu+tFhc#t!V3&00#!F8-a; zsS@YSb-R&^C&d%>M!CW6@(j1JHig&18KH)2WHDph!Qg2o(>xBU8)+sd472=cO~Vc# zM;AHIDrwVVlny~S64eIH#F`0XvuBg8n2@E>*@ZiG7%t!I zblzXn`iv*;xu1g6(#lxWW4`%s(KL>EO8-*(V^b&;?-so#;~e|Ju8&VFj9T~N6ntTd z^ILOFj?mKtW;kHZFI53Ht<`WCPB2DQ!ri6tm?UhA%GPY(pgH2>6Z$P(Sx2HA+AIw8 z?bC4c1+?6lTb$#BGRo@AaxXns$N2b2`_gI5776dOO0`Jc^r{;e*N}V)r{=YZ=@Yhu z#*uSq^o;7)I#gFRht&39A6%$w56JM=Mv5J)PC=(<=_LN?(ssibhB%Ljv{5ssZA`|5 ziF!R*eq*)r43pw`!Q&8Y8q-grtQBNl74y-&q7A?qwoXf93)_cEHy}}a)WX*sv3&1Wl$a7G=JeErvtn)s6V}-DB0Hp%RT^W%VCTYih&)l}R%2#? zu3Y3TqGv)nv~3!1a+mIW4x%el<#9Ovf@kg z=_9o8_-qBcY5T__d(37JGs6)v6I6pc=q2v!hj;GltMwh;f26k$dZ0=3JdE>xrs;(m zcOE=$*eUW14`e_(JK%w#$neN2+?~Mi`!a4X7iUhF7DgxQ{8ik-U9;m;rKO!??2mNf zScjfz(kP*Riks|En(mOCsXHF0^u%O>n+mO2G4Q+*@Y(Q}W{81HF4u`J={==eCZ zs9bt7vI&xp$bSlsus`N{{LyQ9HW#V3qA=^O4Ox9XwLAA`bQS9^C8u=PWBalI1EESq}BjahhId z1;q}|W;0ZsEN7US#tNCc+EXoz)#~%LGkRY`9U`O`r%%<{!0?dV3F+~Gdx}LSQd&_K zy`h6r7?n5kSBo{CCx!dgh0#;>?9D6L!B(Ug8@!&NhmV-FJWBDMCQs9O62;Th$kuxZ z>?!i>37+?l*LlBWvMI@WcOYp3Gip0Y}-tPgOaSV7}HbZK3EBN zRv^^C8ah5hC#o%H*rd(Z8Z@l-a?9(6p4NO0FM|slilQy8X3`?14Q_ib;@xLLi_k`ZDw?=K2vMx zl_-wO=4%s6Jm%2bD!e3Q+qnw}Xx7h_5yss7(!x|Ohb4p6cyW5%ZgMjfYK@9)U7F)3 z%clOL8caa0jF>Y$m&?#bamL<|pf6^v*^Ng%u>CkyuQp0_l#cDmLSBYOO_ z$%VeGJf;!5b=0}My0E$tcPO)q<2gSt!hK(3ajed9Vttd5>*MjWg=nJP!o8 z8PaBwWpbiUaXyyJmhsqQcXKm$&H>&hf z%a|TC+i)vQ=q;2uVGuqW5DO(Jy^>;61~jLlkz_kGddluGa-82_hd{H$t$|u-iuZ%; z@rD{e`xM(W#2me!GfPusn9av8ZB-VuGVqYT7M=@aR5{ja=>3(jNU<_%<+R1R(l^vs z)mqu)96D0ztMQ)I-VB$Ql==>i@a7HOD7r~^tn&~ctVX(1ZRN!oB`2GTCOJ`?A3s1>4_FHo1$lO9etaL( z%<0j+jT2?g%gL&7QeT_leITf@rRfVoR*my zuZ6`aG^nMSdNfjGpQzQ_WjzvPRs)1yl(=`@5Jpi6zm(2!{zN{lPAEq^)(akPS)9ZO{%#XfI!N|#Zy$-JD z>FgR9gPbHz^Ik}^^=zIfYklBpb!oCOcdB$^bYW)t6#FR%;&p18vLPqybxkdJg4Zl3 z(RsS8Rb=#(CX2~=N@*6Rqp_x-RW@qTJM2kqOcPz6o}Xe;BB**~t|Lj%h;Fl!uv;lZ z)Q>3G^FMoqyER?dyN|6o1Lx+(PR2sgi6S(b1u1!+X^-L4r`x=o#{@$3cpK3IxZo$l ztw*5R1gX99R+8taX&z6{&1R10lYh-$&`(U+7R!7H%R`cfDGJuxG^sIZ*eN|L%V_i9 z)EZ&A~K<{0IScLIIMxP!i_0|{0*@xTP?LOq(0duURN7UBms=RxN zGkU*th#|p>I5Vo3T^rqjg+uBxqo-Wm9NXV~DWv&4dL_pz^V8$%jl7I7Gd;^w<8V5^ zf;Ao;YZtxRQWUs_}Xi$Yjed;2PjbCsbH_MI9w_6Q6f$%8zS&`eU% z$B;fobnaC4*Yg#FQ#=%Ir5cC)AscT>*~7L*ncywxV+FNYI6ZEg;_VA7lD=y}J7`sd z=Z*egyu7!v_fYexX3E=}7qUl_sYJgmOgU3GpULap%sXD$;*K=**el1}KR3w|$SEX8 znEt?ZV(ygA9C_(=L9ZvV&x%I_yaB;2!0A&peXWE?pHSRTu$@A8w63GeMLpH4P2Cwe zwjE5;{#?!TImOIrh?8n4oobBDa!|@aKgB^V%_a#Z0Hv5nJ(GaX*%Lbiji$e`3V0@V zc|G19calA8uWf{^M2gK-w5EwP2&j%a=5LO=L+zloLw^HLXjWlm6npv3Qp3zIyErwPsPcm4ep{21vo#M4E&*ffH!#PPU zw4}&uNjB;eOKdM%Y776eS!G$_7M{jQ@?m6CF?Nz>pQFcW+Tou{jS|z4CtbcrMmLNt zYN^;x&4gLC6w90NiMdmCy^0>xwHs*14>dE*;A*5pJuKPIQCc$f+uoc?89WhC@jP9f z)AI$t8%Y^9%IeyG#x%wW3)ccSbTMA}hu?P3O$C zdVCaePEIU7@b#L+0^4yzCydr+x!WHNF}6ZQgW zd%b*KPBjep=$6Jg$2EF~B`q8_Z(s5RUD37f$unbmyU&T5Pc-k)SDUlaqMj@z8Fm=S zSrex$3wrrPx0(zN9X~lsQ`7=)p|v>7`#KuXp;j?doIB?ld=au~0q59WaDLk4O!FeQ zJ>G0%R_`e=)$panmV7ADvhL(trRI>m^3gOIMC#M?J=b4Y z{YyxSg1uF#_Y~FU^^_{dI%7{5|k^-MD(73f7i5_ z+ru+D3wcJU)jac@6*L2(28f43(-WMv&@njvGl>&3b7LH@k*d*i;-=4Kr5 z;TDn?WO5mLoHor@RS@TOehyFUyNLSEY04qgN^|CHpIVnW%2$GKb=mGG(nMLLKY@aX z_xzCDElzXKV-$~UCOPcn^(8CXo`fW#dN!(WQgVNpa0!jRvY_U0E1ra{q`Cu-Bl!Y> z1{C|hq(;iq1I0Wq2HKwR!wC`k%V$JSXmao?Y9aHn+=7%$=0pShGEkHl*5& zW)hrN&f1PrcL+|fyl9`<6pe|+k|xFM?r@RQN4=IGwu}?g+<5WaoJ|({NWNLgUQ-{l zrMeb$P5Y#5|G>LloGiew1%Wqg;vSphhA3MfXZNJV*<6n6$YSY?zANl+OfmarQl>gs zy2CJK-J!1%*pqk>VU^)1ILs=g(U}u-?A@nM>4k6JQIwu;T<1p@7UrjHFKBDh zPH{UWB@U;Uq-?rY^n$5m`fjos<3UC9m>)248_90o4Qaq}s;M^7n6fW&>B$pQAv?I) z)4CTKW2)jC2S#fkAZME@f~w^wSkl@UY1=6^cUB8iqk6s2glz>4j7h$5B4f=$^%T98 z!aUv5?}u!sMtyw_;_-yP*q}EeZ2FmO)ae@L;er-V>>Tp1H!a-7S5{BialpdN9Ni_T z(9)5LT`Rm(Ve3YyRQ(K(gzO~`d%mkkSt%hJL5d)C>${u0E|qX1Elnxf;jo?s>6D3H zMd$q5;u&sD?F+XSK7CscL%i|9IWqO9=MqZ3g292VzUr%)HMC(S)hxtR5cl+*iA7zNRuGTD{2=_N@HK zH~g77Z&Q5Jss6nO!*le30lvw_uaZ3wRU{h7_#kJL7@%X<@Z~U3GWFM)Rxb6?Sp!D zOy%^|!mNkwb6jiRk)f<`hJlQAyOxN3Urz&>h1H8&*w4O#gM3FmuF+#a>9vGXU%9(h zJu{?7o-H!)D`zU4Lf>T>fhMPyMdzS?)kDM)W*v+j>F4v{!W=)g!TWjo`YV0WI7f!7 z85X_bz>vqP<+b`sk&6NN{cWX-T$etwI7QddkZo7S^0}_whS&o$GsndmZK z+FMZLW8NSZ>^KQ{=oPKf{4n^Va zbB$(SZFMkPXUDCRnR;vCNEeZeA-ByO9~YI@ic(pc)qD1bG_RRCI;s`!?diPzGwx8z z(<-GtjaBXfOjYZc_t7Hr;B1yxvES5)YFM&P)U-3S#FD`39R(0y;s`G$830W>Hw;pK zW-v4}=sQ$IGkjcgj!{Q9<03E_Q z2YYL~6=%P(7`6!e7kPs~I}eM1rh0Awj2>E?z<1V-W>zxJUBZ9ZNUK{+rwxg*T#}Ns z=tbdvzpwT*T_KxZ3}2>?&Kz8vY&wH)edT7VtZkn48CZI?vFR~Wv|ee7TLpVo%#U61 zMiRa962w}Ah3^QJZtUL8>o~Gc5r8L#>_DpMI zPE(Q4YbH;$T=lACW__0mulGjPnpUZ4dUp(w|5#6&-HMWLjdBv2jaWMEmZPZ_rl^GL zxAph*y6wrN-wsL$#}%85%clryX7G%3xRRvk@|EE#Iv zCWE?jz_-M#lgo%VE@uj&l@?1{>0C$&;Z7%TS1=_kow47j;w3}M8Qkn=)4X-Y zq0mx;U+icVHLlygnAEr1SYevUpwFR!yu+x@anl6%bvnq6$8dgt(iG5{n_iXH%h}we zL~l|i^)PXs@7k}NG=(AqfvC5jd$Xigo=$4TDMFo%nto_(dSXYwJ^sZGW$CTZRtSt! zVCbjJFp{g7c%?co5b0PVXQk;>2yJxBe(9~BZzK#!?mHkhlgqdymw!%@#~X4JT;cMgF%h(y}J9LoW%92`uha7%+GE^rsDcE;0 zeI$BiM<-756oWGq-W5N?amfT<0AI;+(3(COXXAJH>}jMPSjC#!TSuCG7AQ@Ta0&Z% zKr6)|S~GzMUXd27z?kOu9l2pjbnH8C&GfWR?3$^d-t;L>-7N_ov_&C%m>;}nqVdO% zR*P`Ps&m}-uP$+{8BbPEPuK4xB0_d&VowixEUoX0vm)DRr}CB7(W)5Cv5|#H^CUI$ zBo>*8=-zStzGzdk#uxTTBf!*cuQN(N$kVI&-UL6gd#B}EC)Yf=#5tqw^!+(g{8mkj zJXPa3Oh2%u!)-|YMylTo(Ac3s6{l13OnOSEy$yXMQD2-e3sbHc#Ncx_t!I7KYRwcc z7{k$nG2Sz3a*G_v+0WTzE9@cDeKt$LT%d5!-~t+zRtHX$LhSF%NI<8L>wl~F=XEmin)p3M$AA=?H^Ops$=G-(Z14M5*~_*`OX-@WJ?0r82t#nKr5 zJjga^w#(pMdVfTq4zD-$d4j@IRh7^_D>2Rp#2;)?kovoh19&c&NGuQxd#|n~<1B=j zW9=uLst}<!^Xd8gNd zS#>Kf!k4&|wmG9^KPV-R#`TJ?DNOrsT>GnvCeoa6{D#ALactS`UxpKwP~){mQ=;n3 zAl9tN;@pW7j5AXUvC*h>y|$xIkHPLfb82*)@AGTz;wK_?sADf;hp*^)_iFxPnMCyU z4!!PUG)Exzh{IAdcQ$k;(qizNlrF|AdJbzNK>4kZ|^NPR(=U67F$7LdT znrC>nZK?L8QDS6!Nwd+jS$*x>vZBq#c<^F=tZ|2z5=xcr=$ajESuQ5CwV7s=@x@ik zT$;~Sby`0o88V4O6e^7_j8E&eh=A#2+@bjIPO*qEaqCxr@2;O(;uX#lzBAC{$sM(E z`;LB36Z7brU#T%Ap&YV@wL`XT#P z#+1zQmKGm)b)baXN+@V_gPM7)2Z@p6b>PJ=);+N`CASRgk0Nd>oj=F#?U(p3jM8PL zvQU?iRX**K)Y%}Vzp_9|=(v$bUjcRU4+NK$jzZa0R2Y=PRs282s?wk6-vjI_id7OZ z{UN|IVnd0T{u3c17V|nvfAX3B5F?YRe-_X`F;E4e;E}j?MUVKujMDwUWu;+N?=>fd z^fzSGp;P#Et92nv`nQnVOrt6!18A)M71Es^0sXgxU9mi=1i5vIT){8N1;5;Y|JR2a zCo!=xF;NK5Ehruah(O+i7{8(8Qdx=Pk~bBKlMjL(cM?&% zqRZcbtrnw`Y+OqRS?`^s&~lPnLHA3{txL=m`jX^c@nY8szY1Jd8WalNtWDGlUG`bz zeoOUG%muwvF=?e=GcT(`?-^NWC20i97YwK5J}?u*`4%}3`*X|6bfULboj)g0O_B5g zpJLKOCH+kuX*q)`XD4u3sk#rOfexu@ z=eU!6+7)&6w>n-%c>uBk;O=qIn3H_kC54AWVVX9P66&GqbjH$7@Y{iQ1s_aV6`R^6 zhEvN++06buE8{4VrH!U=R7E`C-%2A;x)~0SPsiQ5ag1--jGA}EQw4_GzCB6vbr+~as z1r&gGo<%v_iDGR$lT-|KC@EI$2c$m)a#NV1^D%7saGCxM$gimNbO;u+cbfI=A9#CA zz-*JX0rnQl>K0NLrcP_sYy-y53>q2R7Q}Lsx3Z+Iew}lC-D)2ynx4oKx(cu%sR{J? zj*xg6Vs~dq+z4^wAA|%_cB!&5_YA4m0nB|D^rn-1+7+d$gybZE)C@@fDA>leAH&vM zMAolCt5&s@dIa{fp7k8)h4xaLs6h{at(~fjlX<`Hh5fi^JpuZ5d#TT1uOTu)pJ3Pl29kFLfn>+zMdpI?#3}Q(FUn=#8`*CQ?E^J!N`Rl-gucgF@E=HfxHw z&iO*>494FL;ifVZ=%b;)%UwlitC3apuo)-$v`Z}-71U)eD^-M~pfV~^#S*5$M^xGl zeE?w6Iu05$GSjbj{|&Fa`(pF!)kw)&Q;u{=U02NkS_fFZh%SIJE+kJhE9?P#q0L&u zv22zb<;t=w3|1hjF>x!z?J@Bn#6vLwZeA*d80-T@GqTdWJb&m6jC%nU(_AGt!0`NT zp|b#Wx(}qHAo;XQ4cZSdZ3ua7Mua3nErf0}a#+X%J1C?=li5b)3;+aXy|C|XD?*YK z8)TP6j{!86Y$_C&mA)=?+{mLRL2kY|k4xSKo2-`92$^=NJ4AXN1T_`vHnO@4q;Tb? zYB+Yef^8EXfRVe83PRQ?XqJ}Qk(SvD_%S2}H=7x4GONJI2n(ptjYbZt#uH91g52?D zM~gL8$4_);O+k%jSCmrXa)5XUJps@la>=e#EQvx8Yqnb|wiZRgAQZ&h6rV-0A&FfH z5P6}X!93YOW`LxJ$E3A#{rsUkC1oIj^TQuBvN(q+_;cBNwKQcM*>j~JQpF&c9z z+kXaQW=J1JV|KEj`8gLn8B>Klt6Rt$H8U_Ir7ZOn`VyaZ#bS<=ngVjYL|Pg@XDlnz z?UYr9t>(@d{1sVn)6rkA_g9rEm)=)ixKOPeX}MdzR2MfYnsQbHFkx+bnBP=qbRK z#Fk2?{wn1PWCl!Sb&)It~;%g5(qkjlEpBl+_RW zfay`(Z4kEuDz2O@?kAzR4P;|R=o+9x9wb%%S0i{d4H-1MY&ABi*jynNGQr> zCf2oomTocW}*{ z!G?9WNmco@OAbg%fmP(0l|6$`ZkSm49*{J| z2H6#hlgc7M-UeaGvPnr))UtL(6%Be>$9cKsz7*P0|FZRx! zDdy_Gb^e^PlVVigH&c zVjDm?Lfe6YKbLRtsIPW8q1{GiqMntqRkX{bsy$7GOan+^BUu`st4qw>%;gf@nWROl zECrTqD7(}#Bb|7q%CMw$gQS>S-LeYiV82Oaypoi9-gQ9qO0O26tJ+MJ`MQc(~mqTT(u++0EYo$8j zY(DLZ+8V9WLPNEaBqX19MJd?~u7sRsW%j_h1t3uJGKrbnl2yB+ErqfH{ z5^6h$vT}o|TxPIl1JXr6{Mq$`PbcEH|^okldAIhET6F1AIgqD!{u@A$k(s z08nEg%^QWzdrzqTMS}>18v3{-1Nl3W1VSb zN$A=Hu>1>k0cwU!uYqURT0X;xgPKBe7Do*>)m7MSwJpZygdri9%7l0G-#&kioR}qv zmw`f`h;rU1thJvF!4i^Ot~0^0%cZQBx>RPelgMUZE07DRvU-3Jc%cD6jR~?#4ISy#5Lgh3 zQ7Ff}qMYq$qFmeA!R^^?Yn01GHQM(2*~W8&xo}j-SDLmCBw>5g6f*OA)~toirK&L} zR_00=o|T!gC8pB10K`Liw>h~8(%cJIcO_35*)8;}bDsmf;3S`R#lkcf>;c-eQ7`O% z%Nh}y0BECiuYu*BXpmjen&fT=$XhQg9V|=bbtn0>D{3f_^k4OWw*6AURdH?nRJ^V# zW_`-&&2qFkouOHM|#R^=Apk3&&OXODxz z=85&DV&@({eGk71$19nse>8N3DGUm^Lbauk>FH5HEzWc=Bn4Hn5>+e#(=J6uN+YCh z1#+QP)+Fqc?Xyn9zPp(9_(l))a`ji=q9CCWYpAN;5W3w-n0CptXy|G<%$-6~Pz?s7 ziX~jdx1LeSqg)YcnEp|rs9$NP;&C1lQfFn37TRrOX6vgm)cD8Dn?E_%(ej1_8Fs}g z^pIM{$Tehb0djGdeQDg(K5LoQlGY3w^X;`Bg8fMQtjA%8rhu|5I^W9bZkP2U%vGg7 zvTnAPXowc>Hafe@kbfT{|1r>}!as!l3BbH^P`J~B`+SFp)c`s|8teJ_MW&^rqS+fg z+aW@Nxd#}M`zdmsr8wFZEBk9wt4ymLa_nwKTD`Cj7qdFA_P)LWdHfUVGIDqTR0Y&Q zGuEQYl_obRt51Dkis9NvfhOfGnXYfHCx;h5Ta@IwCvztxl1O+3M1WrDLpO znk>7bFgK6FfDy9m_PH6`0D}O7Md_}Sm=Jhro#sz6ocGdP_(+ivId2s%3xHH zex})?(iNsu5t0&hjw+Ba>{7L)Ak*>)2U=2@;Ug&x?#(8Z37WFD!){nsZ_7Y047*}6 zdq^Dsa(Bt80am-c(Sm5x=Oda>K^OOQ7g9F>xvrvuVxqDI)F1iC*p(Ll8XT4&p{QR; zl`5{{9UvC!&JEp0UUV+ACzjkTWDWofDywoy(_Psb`w0S)_%oHnJk*E({BK2k4eJ zyG@{5=msN)g?gPUq#lx+f7Kpx*Jlzt(%eUqo+=dLvhLf^PZ?VryP}h-)fs>m(B|Yp zCxtwEcHjDzaWhl03Z8{6eP-*kpa-1f)2>*;6QsQG%xo>0jleD-H{C01E6@Ybtm}k# z1JsI7yJS4l>M3LiT?rJ%V3fPuQL-WLi`((JrggtlKF?w0;3^?R+lE zwe=#{Ygd#zcp)t*yRTItpL^T7v)0^R{tz~n?{do@!bZ6y48I!(lHMSnm+cK9h(b}W zZA4wp&Pk(OB~~`=8cDh9$)`&Tsfm~AwxKTxU9cl04!DXv-Uy2}%67?FLl1zx^Q6MLFFZD8?d@?gdluGLB zQX|sUCFi45($8EfGigMrZD=vG3rR{<$;{$!1kaL2X%n!llnqIj$~5r1E3%c9TF%zT z*wP5OK)2XpRkIf}dLWtq(ue4pEz3Zm@!;9zmhFf_a=F8lAQU$Hwv}_KOp8USq`SFP zrY$6;x+qxOe0~D*OU)@N$<&hXSDVZaW1e+{54*fmB~7;(f!w+mrM$Z|r?1>llazX8 z2)EFqW-C)&NgZxxWqPHuR6p0soL|iUgMy#;Az5e`$;nG)=GHl7skv{pEIS}@sZ5+D zwFik_lgbRhUj!eL5wGeNe28*t5k|I)%+DwBjpWwD12Le|-$ zRML%hnN(&rk(4GyH;}1iHY?*nEN6omY@Ra~8YjwmjZ)e&(S$j2CSsxvG7nJZdf|Hk z+FR&>z!rYQ*^h!=a#DW3?PRO`L6yDRT_Mk|SiUN`+N&YIrIed!<)x12XH2SmH|Sm` zgDLBNk`ntxK+ooc6{0^Zt$#sf>AFme?*{>41ui2X#+A4r3wi55k)pYmY4n+Y$TYRj?>_AQWYd+-4=l$o1>;nILW77 z3QJU24~MoF+HPcKl8#bt)^@KGNYO*=fc7END65E90{av2FMwP?+R^(yS;$ z!XwTWl6{Mi2-QI{!cPNqN1P+XVr?ZzG!3RUS6 zK48vOg+f)jXoSr<^PFymN(KbFR>N=L-GUxk6s&s?gN{)fZAFlDR`|FlFA32x;kEcC2#M}%H5@~Dt|SQdKQxoVcR=D<;*UL!}QK<<_XrCn$EoHPe+ zkXyet@~Dt6GgX!Puo=CfrLM}@CG}fPr&~ytXoIyfB}LIK1KlkH>Zo?fz&!w48=(h{ zEI$MK$jP6B)Y6xgdW1GuVdbkp`k4Sa<1L_LP9CGk8S}NQPW-r&XxWu3>SdTLP(tSc zT-_?H@?fj117`K83h)peRb9LZj>aC2Y^z$$c{Y~C^3$byy<%2}gw7dxROme;D?+nY z){xN40N$(<{*kj)?v&ML=p@M7W2jYbb7vOI4dHwn%b40E{EihiB=oM4gF!20OT$V%7yO&5*GH3)~$|qBi%}D;eFQA%0e2^jD_f>Bq-3X8?dXe;-(_| zmXkv78M#A9%QTb2PeCd?+2%^U*K*5aAa9(Cs&LY{6(Q+R=GIuIvga*(Q0QrZ?DfKL zIQw0Ym){+go@mx|P&L*3&BCg-zs|`h_ycm^2a*y0F-e~#w0ha4#p*l%v@WTIRJmR+$_-Mnl7XcW)zQ--&KLvF%$G-D=}aY#~E zz`n||M)C&4?y{_Ip=*t__fED$5@;1{m%Nd&E>hP4^a11Z5woN%HL`>b8+laj-0CEs zc0~nQ=mAg=Qq3uHBgn2Ob_*%xC0$8b+qQX&c&xK$L~g)ci*nv_cE`BhtYreVCs2z&(kk&~Z;)S=-XrXy=DhQzF0MW^fw#@?uQtNJ0jim*(+fwJiDgKs4^ zqUXWa@Y@lhyTES;difIU%gNp!hJ`{2o01YXB_(W1O4yW?(41mo9Tb1r;xy*rj%dsh z(V8WqIZH%)mdLxD7(AOAJll~NJll~NJll~NJWG`PiXN()P6A9FLTYEiwn6wAXA5no z4q^*yx5hN2rqyyFwy@Sb*sA1aZ<7te*8()|R#0fF4f6EofHZ~u*F&l!v|4Dv4Oh@& zS9z;#5WX6~xN355U<*HL?7{%(#bY0~-9i&qkE5+BH5bYl1WDayQkmI6L*pdu`z&i% zNJdz6p8>t&TpUOU9NolTBEq`>>?O^yRn4K_ zW|4^6;Jbkq{vJTnP;k^g@y;mxyU8PI-3;sOq2(o-R2_Z> z#B{zgCy@>Fbi1tg$W|qUwWVZO3#kMm6OBv43bDRj2$<|Y21qPb`4Yc6O{EZ-*Aagc zXxkUHiT$bDTd#>tcazv&pRYiaW5S7IZRVaRg@p}KO!RWrREtEeD=+seJi7vq+U#G} z!S=biut04h%NNE%ES@Dy&jS_<5ibJv?kHb%7X1>SmkCLVd>LpPw-3npA<$;}`8n)g zx6kUKg>JA`D(uI~uy6AQJPsOj@+4@+$$V@yWv^hhme_-U8ddlipv{7EcTVhS7ZcL{ zj+HZCzBJPEA2G42(DOzX`mQuywyexOil)RDV0$^4z{E1u9ocUSsV^0d60>r4I2z?L zOJ`Qj9tTD_-8*l8+dq1}-$;uhx%Y0qc!B8~%8 zO=tozzK8|@U+8q?i@4wULJvm1h$oyc^zFzO@x1eeUW|MZFF9Z6mB<(Is`G_jk9-l| zb-vJ>kuTyLfS*Ed0p`Dmw*mM<+NT;{#JkQHIv@EWe&Bqe4mk8}TsjtxL$QQBJ z`9j*5TO34O;e4ShBVRUksh_=jy+5wL#}=SF*LqGiZ#_(=e<2q;y``Pl)h1>~$g0k& z(&??jo#w%?&~3qkiip<$$q0WJU`70PCI59-K895!$~w1gpvn|Mx&Y3T)w@7`rUAC-^fcLKQ+>$PeL8ZxZ%;mD73+CiU8s)+;k3 z$1bqJDq|0g_Zc}X6e~~`A+KozIqgk0ET`2vaD`&j%OX@de4!Z7G8XNMeXobqPN1W- z{`;4Sr~m|}TlhAB&VE1W0Vjoi1R$|iI1EFDr5`{Trd_cB+1v-*71sVI?vI@VA5u%p>q$UgvwI5sQlN!j0djl!RE1$84VNm^AsP-+ z&zY@))GTbBdDxg1p=pD*>(*rPgFp*^2ry(1GoCbqwHdwlAtCKHSs%dOgO>WK<~GHu zU7wX)n{~v4*$QW3ZcAdW&=)25Q$T%DxYHajjHpjmE*2_r!BS-|t;FS4Y9aCx3+65u zlSFP?QrSWuyMkhRtf)*MJHG4uImJ)&11%$T6QC8dEMlMYh2DyM&1xDYCIq%ieK{J_ z)`LMUbU7e7doClT+`Ug!*?IEFEqvJ9qjK&0J}^Lqb|D=-e$cLo)NU zbH4{teGrgEpMi%zh5WH|KLhy;n`s%9qbhwL;LQ7Do8dhcB;plg(GOz;V2}!Z8{ocX zqwrJCR^cBwslqj3NCIS6G_;kJ*C(^Q$(!5BY5=)mr7W@UwXANTM~tkhX4=N)?oy<1 zKkNrBtK5<*l=w6f&zn>sG%v$`)3OR$3h^hFmDT#E{=(U6vg{YqI7||~7HIQJUGs>O zW7*VLQu|g}GxmZ$vBQnwPeF$}javns!5-}_HC>8n){J%3Eo9L*ZvwsQr>cYw$JKeqSytr(K-P8uw`{W1p2m< zn6WEX=4seI{TC{uS^Zja$g_hwcUEC&)T!1`Arx!G67c){kNT%(4n@{2F#=XWRDK z3A?|2)*|eCEURGfA=r<$*Ln>0tCn^24bXWf^MUyU_KyG#Yu9n&z4lw3N_`5f(QmGb zp@a@ZTOvyR#6+Zom=a%PM3nk66;WcIv#6GpMN(HY9$Orxt^t}wE3pMwX7vC6bycUp zty1$L(Ln;d|BxP4aCT5fW92(^a++ir%9TCGw-=GVSh-Q|D|^`= zOo?&%Dt_57#l*O*w4b`%g&(-crpMV{z`BZLVI0MB|FZYfmB!_-OMO>bk1J?E=31fH zzM1*&KUDj!v^_3$zxsVE_pA8*cI(Gi{pGid-=*$KQ_WHVi7vDEs>VCS? zxP0Z`Z?`Z0s@*PYdtbZKes!hoC+(-fHbE|Xzo46A_MJjeuI;{aqsiH?*G9Rv{<++c zP?XC&%!1E(7AK4Vhb+z0lJwsFud?sItA6{}rQAzhk4tTDrStz+<^Fd2)s@Ei_hCN# zUG=4v#`bsR-=!{hrDrvly4>G4dzX4_U+Va+)W6?VxxcG%d8zBW(((4yez5giYo&ej ztC;=fF}u|Ld!=plyBg1bUHn_=Jo3A0-%DNYrM9=yesHPF)foWK=!Lcb%bro_Ab~Rq zp(xk(jH1irDne1N?HPs34W)8z&nPxyh%<`aAiLrjh0ZTR-p-I^uPlmA^1AbRN`Eyk z|L*7IOWlvZviFzkUjO%TUb$3#a4NjFb+LIW%3bXIILck@{5UJO{Q0ruE_Qx=4f8DP zijdBimt9x7Om0vp%C%irw3zVDnNXB#yRNw0P%77UUAY!RtSdKy>`K>_kk@w233+YT zoRHUc&AC*4Bl^42{esIa|BUXz6}}@F<$RyF{5yitzu(n<@KVS5cU6y-w(q5`@9$f= zN&MnFU**_`pe(0F~QzU!V;O`+{vCyW$+MnpDWkJTLEBVSkKr7weBvZl&$L(zsu2yoBne z^IY3`Dau_c|8|G?t+XDAoVv)e{pa`b{v(M^T%$5;S#^bdHOjfY<@b8`7wxqzH_9dU zqFiQ7N4cbLMY*OVta$yi%^(g%ZS8j z&Lz%>Bzf=|q-U(@n$yFyR_?Wi^-W5h#3K>tLLgI-l47U_2sY})1 z(rPbUC@wM9^=~qMCVEnl>)v?XPk#>LW3wV;Wz>4rd+AlR;8TnDCsNXF!Qp-i< zv7*l|WhbG@hEi)lp;4oD(x_JjIlGJT8u*rf)DVs6a-^eBjl(-hm) z?=?sU!pCIh;vc1w0r?S9l&vg4x2;g_c#um%eWRJq zOfzuox>%|Iq2v@#y0TE)0sEa$i$qQ{qD4=HW(o9#6#qh0qkb~5qP{JN-gh$N;?+>3 zXEi4uGp*_8IxkXaTP#wIcleh+CX5G!XeBG~ppH zq{YSLMT|IK=+?*w+okTDt&h#Vs)qD!pXv1(OzSh)s?T6rANx~Qug{>+o@hse*Jm)T zkNrKK*Jm)T&tR)QgK2#RTlKMj!H)IGw9gd>&Yz>W^vrV|NsEVwUjez{8C8_cl-QEW zj8sV}Ys{p&g{A;ab;55rTj+a%{Wka$fZ69$keA#IWtW04o9b}VU{MqL#K^&A6fQsL zwd*t~TcOk`auteFU2aQgGhl5hVmm-Zg<{{z#8CQMLuPI-T@DVcHWPN+JZof?PrFpF ztduQ&E|rPso57Y`qU#E60j#M+Y;(SlTs1z}E|n7XuYyB$gsuUsnj+lpU}|?zcCQ7H z7m9W>)&FU+jt5L7Q^zQkl&-e5(nahtl|dnC8y{?!yx)1t`EyDnP3dDNQ%aOl&kKF? zMk6zkYy?|tEz9OhS;=*atd#vinM-ACp=Vd(*9I6lTR|uspI8e^s!+?fi=O$2SIKrM z7Fj9t%%w7(t^bBcEUD$?_CsPBTUjap!9bHL*!qa_KLzr;m8%E5oG$W+xzJXiP`7m=W$cXG<8%-hLB5*AZwSxASp>`QK3ws34p$_R=DA8p?d@S z0q}#y&a{vWsp9uoR`S1knK?#M3XN=2oX|-?T~XNWc0<`E2Y(W3uC^kILOn)i+9^t{ zwJhuXk}CAwO^3XXt1n?qs58iw!FH+Ct{|sO#kpIUO(`aoG|zFl0i`gS`kHR(4GMd{z_O5Kq!Vh;dcs24DK5$cohg^oqOh$-g_ zML#l~{LB&Wn8~Q!YARKs+X1VRh)L)3X_toFmqA>ogOUjJCW!f>y1uul6T*jr_LS20pu`^$F;X+-bToU0Zmnk>Oxjj2fjdGcC zPlj?mV77OVkA-sY3vySINskh`s+}A>yF9eEpvQ7;FkY;jCqp?&k9^wpek0Mc`kAD( zwdexyMoU=enoxE{sqLh80Z0iw==;crLEmxGr1^*nd!VNPmUN-hMplHL09f4(f8gv- zL0>rebC7;nm;R(5*FEOsy&x|$6LtB&2L)wm!w~%u4co5h&;ThZQv+>jjyuVxT~X>J zsmB4_7VnoOz2r7GZBkYiT@g~ zh0&Ll^E;v-ml^d@&L>m*-S&@`v-b)a-v{tcfqu_PKJ7~D5ZY7G^Jy&8oLUo|*O0BJjLrsNl1lmSe zQR@O2Lqc1O%=F4vtLM+Hg1r^s*LYu`ulbc0rM3=D-fLtv2w7i@wAvXdp|~Jq!ulme zNj9pt)!HVR0DL{Nj~k8)Q3nZkrcm{WTDKgTz0!5eOm;9cMQhKH(ilL>GrXX! z-#mZr`Q@|TCrd-Bjh0Sht^nF(ZGpY5m=(*tSX*V~mT#+d|4AW8-a&2SdmUyhULpp7 zHho$pX&K7ZVFJVIMH@Wp4bV9!`Ls(>mDFRT6g~{R0D8$uKJAK9Yl+1*9*-`N!jmiU z{oA}MSD{CikVd8XpBtX)C&m|{DGzy}JwTy{8IFj*w=L+^TCLe7KY? zZL)?ZLR*deE$01IW?=c#k&g+)L&!Ysa?gOCbCOTH6s~7)_i%Y2mfxL6IS;F~j2fU2 zn)k7%R_Ou=&G$j?I?1P9DpgWXk$M$C>NSw&9Y{Xyl2oKUfXT`61$G)1<|>-G2A3__ z=>ph`t`)u(;D1$wt~0W{8+5?P^f?a-CRs)p#*dK_v*7-u)od2)ai0F5|kp5H;`EPfKm~g((l=J`nY7uuk zU+Ap!AM6nEi1UT?*SXj^+acmv=L@~y{D0FSLVuWxpRNDRUH`wW(I0xe=X{~}0qi{A zA>v2Q7y7aDUtA;NGv^Ea%=v%XA>woA3;o>r|4)aAUpQarSI+;%nqNaulKc_UKax}K z7i&c5FK|gb+#$mMCbulK!TJAphY0$JdyVp5is2hO)zjlb|alX)Q=fAf`#0}0D+T;BHYmJC~&KEl1{D0LULVs{V zxk6>!Ip5dW1O7Jq z6%{85wFI>ny1~eb3Jhh)UDqv?An#n)Hk&t^ZejD$3tK*GfsDpP!?G)_ubK}}WcX^p z>Z_P5;Fjou*b);NO0D|8DL>CI|iNA4nt2P#A6A^Ef`HSB%Tc6jnt z4ipSa$>VRwdXigaFBhyySi1l^44-zTWv+&UJ3=m1xgGMjaVvbQ8n>V@k+RiWG9Y1*v3axqy_iyy3cqwz*k8?TRI;|GJc&l3YWQ`&S_!5`}Rf z?a;r4LhFdDE1DI(GO1AA9u#%~HVD+V8p4(U+m)K$0*4q$NwSb8!bd9Qrn^-|nO>$t zdbhRR{d%OVOcBRTWmxDLfRG z%>YzoqwqXH4>%3F$H=2XXN@dB0#XUYw-uQo%t=1&N-N@0c8hTXnHKnc5dAT6 z9K!VrohesE#gLUv$eVdc=%jP`v@4b^N4@NBq2mDY6!OBlCm}s(g|?AG(ut*%d$bD2 zth^%PUGy%&Zq1PNZFa)8O*aZsT8|nrKG(PV4W7~HnJ+D+OUAlg65qo)G)G8 zn#^nlRD`g!+Eh?t*SgqR;oT-y-UsS;Qs{O7Lu-ZIP#Mau=(c>_0wC5Y>@$iD-Wy5M zE;oS=7-=t(-v%jRVYgZ68?xzHZTqRZ4t9ms8d)%~3U*Oy9bB)Rz3G0B>z)NY8 z;4t3_#qtJ4^g3TCmNzIuy#;xpXtxkC@DTM=;#V9W|dyM;N5 zyjWjVQF2sWTwQ6clJ;6p)-o&jN0wC<`UK#7?U4=^tq9PLf7~atI!rdV*P*D zh^RVW=vL?dR~;hsSN2#Dy5IRfUoGNA=L@~!{I@zpyzhLWkDUKst`R{8w`zh`19HPntLxj#} zsh7|y4&aExI~^kYkDbdxmplKvW(IH|p zfRNA@=YPIh#I?>B+Uoo-R*SgK`9j^!e`A@W+CS^qjlc~qFSN(yA719D_K`IQ5V*zV zg~~4fbf<`l^M#H%|G(`JajWx%jywO)^GCIl&VOLd6vRn@`U=fE|KE0qXgFW!jPt+j z5OKHjg>)=SWBmE*vk><=U+6*S|JfQ54>@1x5$FG6m54{3FBFbs|EEq7PdZ=dDFFNb zxRXlwHv&8ImZAxsaAXCNv>eD3p8!S-V|IXpfPD zLaw02!me1t)ui?TZO*5XVv+0_XDu( zI=>A68e}BJb&(4AQW5rHN0-pTmInuxtoB< zzX9GEpda2X3BMoSR2%-2*m(o~TRgML1<5VJ8&j4!Ktk1 zcrC}j`ik@C4g+PN3PkyjpnnAX82BvVzX5#?Q2tL7e%}61@qLDT!7mfJUqNsCn+|2L z;5?Am7w=Ob`m^nC;U6#ooCY2Q9tL85UjJfz*?wrf!%x71wSVNW`O20*qIV~0(Nh7g zruJAK+uK;PMR)`@m5?;$C?7Bz)1E|8`4G^bZ335G?o+5cQYCvHqVT`<=hz z@G&^@e+GT;M*1f3I1u@b{}_LOv%q(N$UmL%pGf!{ucrS16`%@4`3IpN2A%-Elkk54 z{Q=Pbch8@D9fLlgm*6C{{sGM(rbaPkoQ)SzY03dkL9HNs}uQO zOMVltBLiK)7T`Ld3LFEXem+0)V>>9uil?9x9|cnZ-lYt|ZHc`&DM$U^7R&Y&@Y~v1 zPG7bce*^oHJsaR*u;58xIs1RneEITh8-K)mxefm>+P}Xl5%K+>CKJy_kAK(qJ;N1WpgY*80cR%F{ z-U{$G`1`iUw*h(+a1GFv@LxdoW#A3q>aVe;k=LK(?}WY) zxCz*k@UKO78_)pm2V#A`^G}F>fNk550rmvgiN7(vQU0}^`1$qo=Wf__{@gww^50yB z|Eud=ejTz`19?5!>H$sycLUL0%Aa3`znuKNiTrcWD!V(B9qqrk41YKL3h?caFQ2!z z(fUp;0`Ozti$wmZ zW%$2@U!lBt;DMyv$wa8Ab?0l4WQ(_iMR-|ec#9Z>9wQ_f8zeLm zQa~Uy5*9BPJ>4}kje5G9evC#0Y_K7V9Y`REHBP{R7!qQen9T|d{y-vZAS8wu9LUEc zW`Qikn1_RcNWhDi{r>K~r|VAd@JaUHt@%_}ee2x1bsy)Rd#X-%jaI#HQLA2os8z2e z)T(y^YSjw>wdw_bTJ-`yt$IP9R=uLm^8K)_%Bn?a^*%SPdbOBVy~|6hUdg3ZFU``b z*H>xP3#PQ{1^vuL1-D7UPPl+FPhP6FOpFj z?dvTuTJ`D|t$KfpR=s&e>-01Ix))omde@3py$D5XE38+s)w(ULcd*sEJ*IsY#rjKDC*)(Fv#BfVeym5c+VR!vxz}VpgY^PdJ8tE1F9zVa zeoC(&nrq#*Auq3``><_yemYk_TGqPjKar2p=r_9E`CGn{=~uT}^{ZN~`o*hO{cKh1 z6`%F%9c;C}8`gQ7@WWY4VSR*c_jQaV)2~>y-f^B^?`Lar?LQLMC)jFD&d=LZ`+KnJ z*Q6y@J5T%i`J`6;JkpMLZza>OAGPWyg57c*(~teMJ{VU0T+h;J|Nb;Dj~%zIwWsli zZ9gpC|LgG&%m4q^<7)e7!ut17KUlj>-1>f+lBhM@@{~TO5Jz*%tfW`lDg3!*H)DP4 z#4REHG-J1&;=YBKADWVUGza#kxaeShXiBns4o3v758%ppTdq} zf5!dnC=O`6hMmh{eI08z(x1=vPIx46iLEVGpY6Nn=hqPKwRg^{U;iFCtA2T0k&rEX z?Vh<@^?N^JwQnAu>`B_Ni#uvd`F@j6#U4f0{trgw(YFj%N7q;V6~ko1(&N7MrFf=8 z_)5RyKCOPeUG?dU*!5S$Dz9RnJ{#X3ih~-fJc@-HU+Z_=r(N&WNUr=8H}%PSlAHXF z`&9a`vp2F&F8>CsSfxuqeePmMF-zmS+36pCYR62r(w-lAf%~-UAGvRy{vVP2G-CDV z>WEeU$0JsG6%%E{;`>XnPlvF^hvKEiY7fOsjbHLR?o;LY1$(Voxm@|@anzn<7p%A^ z8y0^(#V#Gf(vLi@+LOEoE1qd`{d!z6P2*`?K=DoEJz>Q(ja7cdR*h#OSB%s609Y|j zvp>Mz_zpujA!Md*ayYTLm_h7c-$Jp9p`g6?zdHJL- zPerWcisAZmygn$NYy2Y;+zKz_fbxH>D}U+xu7mRQs&^hI?MY-m^7y9DKaUglBx>(G zKJ3SbVwxOg@%B;iO^2}Rtr(~A+g;_^jVba6ANO*V_W(GLhk90w(ax7#F~pb^PmEbH z#h4Y-i&?R}m=&vvSur5vgSp;gUHo_#-`Bu6OZZ7ndTwJaS2-zq4tt$|v&nQ+Ma_G_OC3by@q$ektx{tnsVZm+|4` zXN!%GE28{mU$-Bcr2Jeb^Equq}xqJZL5%Ib3 z^doY43iltCv+Vn;QF$}ucSUlIzv)r=HU3HwtNj#{W5eR@v*K?KVcBEF;EZLD6^}EP z{Z$Okcr({mEYA26SaCUH*bA*}i;ZfLCXD|Tr7(j?`% z=99Tx`R8%Qp5#r^uckgTIIjA<18<7>6`!&DwEFFc^3(X4z97$E^__EE&Z_UHBX;#Y zA(u;EZ<&{~%BPqh8y2sx_e65d|B4q%D!JN2F+-79?V?Ms=wku#;U(!LB^`TVnW8MzhXni$B>_5M8?O#wpjnpIyslCKl1od zk9!kHcltS9=|3Lv@~-@q-*J)rj4rw6=YNgl=Oh1R#93E*-5(x!N?zVN@>0ZG;Hx6m zcgJsu_-gq6h_8j8iTFnNjfj6Ap1Lf*-tF+mBi8r9`y&1hye{JV;E{+Qg0G195%`vf ze+X}n_{Z>55kCjN5wY}hUQ}MEpAozMjQB;)SFD87SbCD5!HStUgnt1mR$?suQ~c2Q z9ps9E7{3oI#$h~VD)v9(Jz)D^#=722*#390_&``O3p@W%Sg{G?qhZA&jE{#EmoV;w z6`L?V306$P_*7W23F9@e;uOXjPl|~eyWeSf;5S+RdBk4JbGhu_yAjL2)gzXDyEbCA zzhW6|SiF5xjKU$T_MH{+H(!X;ALs<6R z{%w@iPca&k%ib$iW32kxKW0*T*?+}uOs@LdKU$Jp`e6UZOsx9Z|0Wabe%1bOn7ALg z{qHZa?7#j0F7X#8sjp%{oW|not5}djxXq6Js}0FjU;B3%V)>Ky&o9KPuVOuHSbYB| zrsNP-ee8eDNUr+WzkU&`zwDp1h}B>AZ&Spoul<7)vFdC8q(pq(B=u8liDWFke)bO! z#Lm90&E+?Dov-<(Ka$H|IFdLJ8g=R-vjT6 zSoa%GMXdXc7bDhqtViX$kMrM(yd1IaKXyg@IPGJv#@F@!7Pgn&i*KmRAVui~FCiT^izJ#zKC_#kdnwvGDd$Kqq){gEFY zrT8p&($Cghg?7vOdzzX^UHxgrI+-k-tmpih?n58!F& zkMYa!tCZjWT8#S-_}M7G-T8gj#pFk|Tz&_^S4Z*%a25GJL4G=XQzX9_zATbo3EvsX z{{X%>;_YxR>8(6F;IF_|pC7}vXugzw=W$(cKNQpTX}mqNSKq4!>VJF1y;$~4F?V`k%*({q+>PEsr`PrGiSpO= z--=lCm149UX7ToNTg0mG_K5$j>w2=Ek4M*&zwyRr^Zev*%pvy5VV0gm<=YhTugGtE z#8Y^FbRaQWmz?yw!z1BmUwC`OvVS`w-XHm{h*jTvh#}kgYVTH5K6k$wvD1gB zKAImDbGGv}A1eNAtoBn3+F0|u;?c(HPkXDh>aXY3ibb3J5X!5VwDDoE;?u_8U`H`= zW7#jos*Pp86t^}$hVxzQ+8yxvqgcAhWv>)dH|}Rg@pa?lIo}qI5B1jt7v^&H*MkwC zNcs!T&*hr$wtX&V&3E@ltp2+r&E@K!9T()R{y8wpU;UBC;r;luH#)05UHeDroqrXv z<`2czm5tJC{;-$oiZy>I-tNS(+F!ADW9h$Q{KjfOdtI{5KS}M??hBn z|9&K&to*Y|`SrTXx924Jy_iMmCy~Dv$&Z~x{;!dI=_K-BM)K8@$fpnG*FSd>`HVMycNm6Fp2!GNPf*E^7|wCS0|A_7RlX9bwY<*fB!U+ z-`;ipH@jH<{c4n6e8h0xzGB6j{Uv%5@y0H$bnz2i{7e_`>f-4WspFZ|s&z5L&p7k6>Ji{($p>E(aNEdM)Z`Li+0pNv`l zbIkH@W0pS~v;5bX<&VZJ|2}5=IM zzrj11Uo?O2w;Pi+UtxOZ6M;E@HhlKsdzxeGnVR(Cb|f>CsmTTKBEGYF za!6l+_s70ILDtH@4NL!Lx%55B74TA?=UI7v2iBaeWd`>9Yw%2-FC7W1K6kmfUZ3y5vNyW^Oyp0%kM73zm_vU50N+dc^8)`I zzMb}V{Z0D!;H6yu(?P!5o=NgF@n8+J#GdlcVWR{xQHHhkrZy#6P^i|8L~|Ifh3 zMfz|q{2lto@+-kRf{}enx|KR4k>zxi?80G))L5#=G zJ9{^>qd{rNrQ?dbgH;71wn ztC6cerf|bK^K|+zwD(My7fJO_g*Dy}hu7}uzh9&L=EDy}<9RuJHvXTp2aNyq@aA=S zdu8xnVK06ol&1wBa)y8YqWpdvo{fIm___(+oAGV!`ygzK+T&69Is9=~Uat2ecs}13~3-JHKztZy$@h{-FVas1%nEXq||J)$ohlDq9{Yv12;Mb${bKrL(dvGE=dkViJ z3+eme0@pYF*(9dFZM>D>+tDBCtIG2Q_)XgDzpA|{|F!T|?2nDt+u@b7^6~Zn+`C88 zdjaRGKfVvoj_lzR@EgbxV^60h&%<$j--Kg*>R~?FJ|*duy;XjDz`IBG;$T?*32N!| zX#pI!$Eoo12k_l#8+Jk1>%vk3OTej{h3@KcdgZ&%ixMv}iv0pKxB^ogcgoAM!DNcfn!h z_Yo$_$EPQ~(g)Rl7JMc5>uL|l4~KETcIZ?3b^^RN&%>s|k}rqTC5o z7jB~O8-lzH$Nf`-_l)}YTKL&$JlzgIy|14?b-nxGrzo$k@A@CUn)$mP@_z|_34JyD zyA$R{xEFKi(og4$l+UA|kB9WL;c1M28;_^LWZua?h2#FN!OM^1w+X@D_&oe2`p4?O z4W1XB|EKWoQF}cIuVB2(A5eY%20miH{QT$P8)(0ag8U8m73%-Tfj>&uE?&iV`~x2X z|BCTy`OSg9NPSU1SKpK1hq)hg;}8Anhu@(*SB3M}!_T2_HwPYt%LDiaL4PlYe?oir z!D{a>!#kt#b}RhQ=koD-H~h88zW#;e5kCp5KlpUx^T%+;^Qwzrwa3fwz1Wv!fp^01 z9hU3&ejnp>&c6b=%5yxt`lE0t-x7E!^R?;UT3G()i!ME{28BF(fKLU>JTOWKc`Yg3qeNKmC z`+q(h`0X zOfmk1?1kFnJote$ug_-q$Y{J?4Ij_=rdTfh&G6oocT?ay;9Hr`?0VmYmm$+4d;2il zvsZ5K{|3I~P<}s!T-SdNewOj_a^Qb~&qe>XiD{poF&}5NpS8!LgJsYC^E{pZE7H$r zy!3_hKZfBsYBu?Y{yGA_p7EjiK>Bqo{1Df-{7!-8FCcc;TMHkHJ+Sf>;78F%>%Ru9 z`*pF}|10pvsQ)sZ58n*$yPDrj2L2X&HT`G$azFfER{HNNEB%u&-y!MM`6}Ow@O6=W z=;49(@z}c~Lw+;i!s2}Xo5e(Y8S{HRoIe}++EeoDEzkq;ZBOmsll~x{0q=bT<8wD( z-qYdHNWTW**RjV7NU!qMVLcDvw#n7^^YHN}^Bct={~h=s`pf3qTjATWH&)*N4nIlz zS^xY1Ue5er?eSCiZ07%J$nSOdLfX^x^OtZrYL7Oo=MBA~JTpG<-Gs^dA4y(Pk z!LM8t@<)H~glD2pI!*HX*}1wm*Ox!!e&VV~KYyr%QTcxiKNHRGFTl^yze=Y3US;R` zWAgTW7v3LxZuRS-J!VJgKLTGjh2O=7@i7yADAJb$;GT$&f{!I$a#=|KDL9@Fegl3Q z|H=A4gFlP@xbaK*TJVkZkM;MB@c#7waiRQoz&+R#n}5Cy>v>Br*VFiV2);1M_33Zn z=MVM&9aHuHIh^-LPx3Z=3FG4mZm1-G5035aN3kD!F0mp0nZ6qw@K9 z6u!F1dsQSyU%m)m@`>Eu{vmuA{rg}z|2vX1|6ddMN%-H<@1q013_qLZ{_@-KTao`i z?GS(eP4l|;ody4DpZxyl2r=bB?8o)Ncshy%3(C zP@ggQQS5Um$bSpoiM=uVunk^{{pbzyyWl%%{|$kkW_&DS{x$huAfJu>F@5_Xd@T0# zb0PizfR7?S^{>kF3-}hsm-TmYWNYrcq);j~<`$D9OLOPzW1)^NNDB2*I~^%hOO-50 z7fHHUA4`>_Pah}hV+o1tX(Ovwwx-G~Yc}1HW_h^kc18>3YPiAzoiKl%`<(BEwNi#~ zu+YpFF3=8VwMTt&cMVsdMdA3V<>8gpQn^q~2aBb8qg-vx9m&RvBZWpY8Eg*?WgHp~ z*P5Rc2WyQwBCEpuCGNuR!TlwRKT>FjknUVLZzKe zb}X&ZO2tMtnpIo5+$S%NWW~)*>fA1;g?5QDl+vu?k~Y)9cB@sZCdJ0qdaFj7+Tg_} z6vxwMt3BA9TT9q&H43BkTBTgg(vhrE&loveO7F{w4vaxHCU^+s)c>qw#ETUKpU znyc2~6BXc-hDCK~H9p;bpF7^?_80m7c51T#C8L#5CO&in{Y5M&B z(u6{cs4&A>l}4^mg+i;{>>Q>EQM{}birj7vsBtcK!)d*^IvclbS}fEPuN!H3 zv>s|E&14*SeQ?*G?^JwgQZET0%NzE|%Bp44r0#zIeht|JyK@&`}!o5lX%;y^y-5`S=sKe(hXX^m4! zTE3JPYgM(ms_05HKNQBK4GnppvQ=qhml%zdOST(zidQi%L*sC1RE#V0>Gxd))n=`^ zG}(%DOSwwU>2SMT%86^Vvt}zTWzAxv%vfmTvM?ei6KyGy$?7%z>H4f*Yqa_~JJ~pJ zYJYm_Sp$NQl{>UzXgHUzpaIIm1Fc4Ps{U-OT+H%>ImdbVqi44ZmGV$IbD8+-o|d)3 zb)43A4o-4Hlz@)a94u3x!69oVpb z&6%h9+}9SWZ8C4Q8(CwL^VY3DtAFE)4e6?Z31_Wq)JpAQYmyTNu;zuzB!bVbJe4Nb zMbm+m>(?wl!!mR#Rjvh%8c!?PaG|(WDkYm&C~jtVtekCOBi(dkF^oFD?}nvD9s|ND zmMj~J)#px2!zfTCxt?-(UfM!$le9E8T1s1(ky?@#nj@8xOP&_8!eBXt#zt!;Cvykd`H@O_ zaJX1>Gi#l+)I2R#E^U?W^2ohs~~Rt&gakP%3Px z_EOF1exhVB7nv4ikWcLEnB{~yA{)~@>GK!M9$;EXQ>H1c{Uph!HYS!WN+4P3S zD%s4cC9=kKD-~J=sxxn#>A;yMS+S5`nD%fV$|TFY+>fc$UC^CC;g<(*u9E@gXLY%h z+ca-uY;gJJaoS(TZ%msbwJphrs@gzrCJY7F6TV&y3;AuYRSi9Bj;5;fS4I8Y{>zmTGE6?1Rt~|4!b;J5|o#U?RNGqJH z>nqjpYL2Ut44ktfT|wbhptN4wf^$b18fa;!2y&6KMpMd;A{J}4&E+i7z#7FhZ8pzX zdFp&jU|JkkX+~(Bi?d=YZ;GrnQ0GKtJ6I`f&K3;1tLOQL`m57OlNE>RJ^`IPCRcfL zwlz=eegE9YZ`I`H$GtsRZjIu&SytW|B%6An+{p8xQWJ+Stt2%}tA)`l<)$Vbqt5cq zTrUr_N-Nh%Pu!4p!^i8S^upa;REIgOGC_~90x~5N%&pIEMXNGEed&KWsCI=ltsz~) zA${A-7+>u=hvb(?BPpL+X=1_s9hg%BY}2^Li8pR%ty-0yMz>IgFl8i7tZ>VXmsRDg zQtFfrYaACAlo?;*i^7NwCd7|Mr%YBa*>Ub;I^)}oS_X3w&G2_Heo%Ktd*pLyw4Sv} zBW$=*8^mF+m#b;HDbww&j>@N!GjEm3eZDZxMq#+tkP@GKg0U~w>RXw^b@SvdDg)=w zawbc?+tdt?2VN~Sw#KzrbraMMd4+OlU@_7#8lk_vCD#3gsYNi-B}VIDtN#eh%+ zKNm8owe04ub4Twc=P)K22WXP(%dQrR2aEFntTm;_l?Kl#oEzaDQmoIKbv%mf42mCl zaGJ8b?hO`(D`}-Lm{rn7tz9jlc{pTk?(eZ1>8Nh4hbB$}O^szeA>jGNAPpN|y}osc z-EC5LH^^Lb(vR!?gcB5Cf>*(FjcPS>=DR-)Eg$A^JDbz6?@`l+#}u7DISZ@8m5TXblRVw&v}yO_o^JDHs>ev@Ifdo!p3k}Q*LiM4Pg`2=AVoSX zE85pJ;yO=VqIAqPORx+w#jc+wiux>n`qAl(Ig^@=UEfB}$7B_}cT(b3aEy^9EgUS@ zd1Nv1rlZrEy78Ik92GaJ+jZyRPSi_hNyC#duF%Ma%UH6l&d8W2C!J>`Bxh<~)?5>2 zBz1Fk(%&bjkj_q1KlzEy{m<$XJnh(`=@BdTUw2LUP@#mnlWJrdE^2W1JQ4%e zs*EK=nBx(3dMEqN-9@L#dC*cG^JCa-6zz3ZyY+vS!#ufsUJc&6jVP=#Zk40cGWWP8 zJQeXBDF3q)56RTVVs(m==!_vCg(1H)1DZUuP*GmZEe;XsksTst6sTT zt2!ruSt?AY-kqmR<{WlLFFl3ov)pLl_NX(aS+UeCFhYl`x!>$k#S;vT zyrEI3Hb*JF?}G3kn7dhL+mkToI!By`qtcG3o?Z{a(6NUVm8NcWoP}`Hz8;_ZXXf%> z%%zr6G3ofsBE4Eu?J9{I0BNQ%n$Y*PwsRkt6wzhWcerWltGhdAT`_~cjd`No3N=`+ zf^!BAmaI?#!(YMEu5_Cc-}P2X?^xgm<_{&96EIYCZp*t@u1(eHbVfV)d+VLW$^3k` zs@7OzkL5c3+381$w8uPANB~OwyY7(?U^?yVZX2Ky`Oo!S5w^;yU)E%0dkj z%iQ&7AE7;7af4}MVskD^*-)WPH=2g8a}kv+cvZ=z*(g`74f~se#Ba=0YtC^7-g!6f zmcyHl+yM@c$P(`f=cL?$Jmxx4Ct+RTyO3YWVGl?YYTqt{X{n zF`l~RpDVYjpXaToC3ATw-~v!{m#<$tx6opuW)jVs$=p&l*d9)~ui`$rgXQWF&ohH) zaFA!9MhZ6B{*Z3Q3@C@~R%%7t4cQXJ{7)`~If-`q{~PY(wa~Wwk3xNR(A*a7$V&w1Gbj$K(sIMZKTt`61Bz3H^>1yN3u;f z^KOL0^CPyW@2S|n(p~wz-I70@v{yC7Hv2&0X}gPoqzhT@&r=_(iAJ;yfm2Ep7Hb!! zp-Vf7-?Ic;P_!pliPpC7>BZ(klKp+>Y`AEBf1mgF z&+o63&*x<4%$a%SneROF%*->-%+`gAM22C=wLg{!guXH4!|qx5<=QNSu*5Kn@I;4i z$s|$zPE!!1QJ(<>@fh$!0k8wmm;-S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL^-|6dSD?Ap_D->YWj>0!OHc;F$C z7cI&iLq~TKy-}(XiKA|(HE2}4LngUOG%9xvSp!7*X@n?$j2M;aLmR|;4;!W_ZX2o+ z{d@KanlNVj$JW;PCj_{&cd27Vrxckt`ATv!LD3ZuDB7H8i2kMYc z7c_+Q%E<5&C0^0Ztt_x8{euS47}Ot`gSxymK=>Sy_ifGid8f`nCX?_*Sf9^F zYNuDeH%#$9GPohbld;>g=p1oQ5O>+UqFCbQ{40r&@~*jkr_B`Hkv35*K`udb0FkGM z1?1W>NQ9ZvQP1>qbCJgsnwUOrT7g8&A*=7r>5RGXlNlq8q(X&?Xq2?>J{^8 zNnBA?9{8GZJeIUKcm@W z3^PS~$#?Prp0GOQSEEL+PB}f=Euu(5d7x$E$3Js4vUSG>T2`!><1G;_+}kxI@3ydh z@@*FzYFHO9F|K%txTc1Uif+)Tj2|>Ocrnuda>S0*jMG{XC)*a6h$U-@%PEzJX3m|y zev$)yO+R?llaAixJDD{0Jy}{J7F_@SuL0tyUUAeDA7+r|L7vD_UjGZcl60N&;HVe< zw{V3HoZml6`K7#X2d_%kcRMDCu_7DLy+9>t;ebhO-|6op!UV|xIc}8i5OwXe@5iBM zmkuQGL^s=QaaeTq9-X9fl;HOp;n~*pvaa2z%pHged}A^C-ddyZWLQ6S>qUfZ?ixmz zB(Xwm*r4o;92bamK%S0zKNz7sWshuDq^m~d=ztNe@Xl2-l}Ni0_v@=)hralF$>ux1 z4q42V4ax(^yE$xd6d>=!$X()-w)Pq(P{`L20XZ*>ic1Lp+VatALD8&C8Zs->hS-+Gsze7eSDSQ&Q}_gUq*DtsE&&Z8kAo}bnR5f6@?QZi`Nh`Kpz@-o$}+6+juu*<)x7a z_&G8WZVi`+#96w+Ydh^5m$}{N@#!;l51+2yRz1_{skW6ia3xixp3=gDbILqrg(*e4 zYQD-+z*ky;wochIlHm2*bxQq60qXgkh)JZVP8a9mZP_Sa)otS|sZ7?4*myrmePm># zDyhDaZjrV%l}Y;d<{UyoS0j;XJaG`%@2(@{K|Ie;;q5X*2F;wQwBhczI|G^9EBqXB zdVKZvx=tQ7+8gnoq_pUt(zU(Apu7>$iw5Oq5rfFSgkB$*?VkXxCUUerD4&U=#R`T?24RL23;DVF(QY|TSRutju6D|QD_JBmd}YP`9Lf21r2r1^ z9yRd>Oss!$r7m1a&Y2v~vv69B-M%s(-5HERoZiAz+ za*uqmWC>{cDjwlT?gN!PXcnk6!=sgzJ&UH>N~(y9gm3Sqc`Yi%8NE5;BJrqgVO6n8 zadfo3_)O_hZk9+X7WDoaNF`u|I`3c_3)lS4$u{}#Mt?f@%P~E0(z`^Q#gXj$)zSt> zXboCW+TYc(=1H`q^P{vLRlSQu*(O#kRLgmLw4?Y%ltW9Y>V3XsG5D^kc<4Ic=T%CN zg3@z~Tw{E{r1JfOO6|EX@%=sU-2xn`sPSURiMEAGcQQB<`d|Lftq#L4J|I;DGr zKELDHz|`{Zc;|DJU(cwoQp!2DielsXi1M2A(Lwyr{3sjpOKj(JPpxHJXuU~~?IX0) z$Zs|B2_zTd!wm&%=kb;COFVhnLf)Z1f62888o=EaZ_>Id4@xL3i+%_qTtfDo0 zOMN?^Q@8eax!tR2jec?2p!|I}TB8`XMqO%}ZEH`d^*MG^eUiVTKH=5LU#QPKwLYg% zpHs`Xjn$_f6lawBxu`@xewm{C$mI<{@1EWzVzxL7E%VP|`p}liEzkAds(3Uw|sMh4wUfMFr|4YkQZOgvY zGQS_D`3q5#!s{)wzgGvnLiJO}Y>cU*biG!wDrXk8R`XG-f7vFA(1fT}{V*HMF8|y{ zWi583s#3~chZ>z;M8f~*rFHtBUW&(GAg{#6gbLSOleLd}4NBHnjpnO0dM}!$ZQEM* zNVIhp-BhRY|7abnExfw?3$62zTBk3zPClr9gr4HA+8|Vf(Vx*8lfn1o;ooIHu>8_k zTf8)Uqb&}od^f9n2ax+ba?`eOs}l7z_&#$rDp8m5OQffe=AA{fEut;)clrNYOC)_o zOGwbd#3p6CRk=Q|zTGU>@5mU#$GAA6a&cNsb4{*ay@`ka3v&N`m6rEK_Z@a$4+f(NZ%4~3?gAOUj?_*&~Im*|A=WZbLf8lD!S8!E| z^18=*{2e0G#vUQzF2uQQb!(%2{zYKZKNM}DBo&+I)bH1@38~mbb=d!<&liFV_m{A# z0gV9G9DbB_JpHNu{cY42(HnD(AsA5#v_J6McT%$Tlz;$wrdV`!duTri;A z!)k~~)oMyMGp^TAP|=8D z&GZWOzDDpx96}lGU`2_eHVF1jm-ElRmzNNk^AKH1UF)WHG{c z%uC0IW4**>mP*A$&Y=8oNHAL8H7G-aCYjEN3LOoEI1NhYkRB`Ij*~2ALvIX3=Oc9; zhPJcL$9; z$#P(3h55=*oH}Fm-p6CDFn@^27VtZFh$!C~;IT?HDclhKWtGxV9cI6DZoF``<75q# zZm6D+KnKZ5I1qmz?!er;V%v@WjUt^-yHQ`d5-b&7u|ArN=Kr2K6pAxMNWDWN6)gab2lU&N&RX(XW~uARQUGiXvi7?~-|PK#jAJ6@!c%X#U3;JX+Mk&S*-CbbrrFp z_?ASj&=(){+v5WU<*$R+><|X!pMz05#6F2t{v2VvtkM_RhPiOW6kW5iXf=Ws@fCNA z)6_Yz9_s^xvSEPK6lT;H`mx3wc#e?7u5BI63Emg|diMb1bO4Kg53sOpyoc|Uj(H8u z;VY(el&*sv$<5;%^&(TVm5*yQyNP2$z#t?x#)4Y9ird@)yV<)tBm*jrC{!?c7z{DIj06+L@B zD($hHu_KPQnVnX9umT_j}uJ^d^^`3DT8NR(}qBy>#DXoW_D;mUi!d96$qID&5Vk52ZOnI(o6jPcK59l%v z>-c93ZpQfs-APZ3bP)d&4v*U(-xe2+J$zT%;Kc71jXP+PV-IgMmzWo)eL8V(5&de@ zncd5<)-*^-_-_vL4gNHv>@#;4IT}jLW{$`S!IY5oa0;X|A@EWCN^?#_wHd25k@Q%_ zzS`GexzJyz_b}%WBU{A@A#a9W(DxW!Ws=E7q_C`aS;J(SIjrZM_prR<9)1HkU{I0< z-<0Emby#n(LedelWD<Gb4V4=hmox0x!njgIBI(4qW(M>U#n^yN*7N*5 zr){v1-E+#uZ^+(4GMOnl>ABjMJmlh*wDlVz*{7TXRakKRIM*oDh1ZGd2_l`G0C~BD8316TgKKa?nn+mTYeCy|krb z4{zYUOwRSEb#sJq=%)k8}BGhbdo(y;4u(OVS!_`Qiie!d-;udCM3z>p`^P z7`>PSlwtz%TH3yMFW(SzcAFU;U4O|QmN)*j4dgY^tjPK8ae^bEOaQ!>l#2oZW*^*9R>@{vLBczSoE}rocBg^n(8aC))ME zZDI`43Y-yo!jftZ=5eMV&2x0ns^+n@9lf5**>JQr7GoT9P|Xo);H=2u8q4wY;L&?6 zkP+J3tSEzTxKjV4S{+J>X$I6t z+_dS^YZjfL>si2?+;cEjsp%vRBBZ|invpbG-c8`m@?h$BNq3cASun!1oLUU$}7lT5#28525Ys#XIKDd^8&FV#@USt2bxwV3$jn7!&I~7nVp@%u z$0?f)0khmawYbN+Z-H}$oA9$lLm)PgD5i?WfSI$vx^DWw8cgFjF1{t;sksyvN)=uwvBeOiw%%`hw%W;*h0-H^KG-;o=dJrFN36De0V8z{q zTvJN3Ej783xR^_jEtzr9k(a#N-OIID-CR>vD6!EQN;z^mP1odiTHV$rH~z;p@%V3P zO2q%zCNut!9x;9)z>AELQe zjL=wv8e3PMGX0Vk^nhk+i0%q4P$^neI%&b#(46$kM5LcF5`(ggVKe^U zJ;wJ;IW_pJG|D?4<-P4^X=!RX9y{BVh!IM!Oi+10mX;J`Tawyn$#El;hVAfe66HB9 zoEDtV*@L-byo?V~ZlZfs1H;jjPL-e3V4>|APZL`w$P}$;+AWyZDcBBA z2kyU0qjeY`x`zDCxQB+vhoZ>4xf{5DXV`XloLX0kY@WIow_}W=c?|c{Y1dGHd^k?! z8U0ZB+8EE%Ri0CWzgCe?zlratw5(ORP6$p50SD&{s|emcz_%u)GB z3Qh+?QBD?&r9*o{GNR~GZYH)yWhEN(X4(zBoxnS5*eTkB*AOpI(M}!SoWV?7yaC*`0?B=7mIq^mu)q2oJM>{R|jtf+Ss-eYcL z9qxM!0lvx@sH`%n8lw>N_8(R8&>^=E#s7xMdc|Eo}zg=$p+U!}D?PU_8HS zQR(gMfWG0G?5U=gJY-AeshxT}w9x^Rk(oeHq z*yFLDBO#|4yLKu&-DbY6+^%mjLc8c?Lsnm!OZ=%jSKK-&4tqVr?nya0Zp)%4?)<@8 zUCX!%x1DX>?qNc|4|AeU4q}c>97z=haMLaB+7cZXGcfWbV0Ld(vWDh}BvX~fBQ9N0dPZ|(rjbmwOO+WUmp!$%GU0bv-uA<~1(gMtg!I z#yzNpm|P;WM;^=Q3zIxU*r?KCLadTD%H+-#>A2S|-ny9(4v)1q%F85?G{s@Z%A|Za zNT$t|1_%>?Zk&=QvaM64jGBb3+r(VAej2&>Z)lFAMfz#%g*Q73C+mB!7nPJgw(!2e~gCy&uGeu8=#3m?Nea*kHR)7p(G4_7$Jtu){hX@x!Bw4(gAT z9n|rqfs*}6-r@j_XO#Cwh`^mUw38YFYfO!Od8oWaS951g7W)y$9Ln@>uX$4qr}){; z?#+btb7p@5)^QPjv5MjDv37Xb_n&n-JsNEZom!^^-V?ad9&IT1%k9zp|3d4~7iis} zQ~MQkp2I$HJ!FGq&GNC=Ee2@u-QM%4{kY=VS0a}j5 z{;MvF?xC$2#V)Jw! zmq-rhoUP)2`>1yx+g=>_#4ZMO?F$2O-l72g!YDRy5q};fp#@^XOM>jN(wYWXv9LP_ zq*=KbGolDzqV~rHgav=U#cb4|@@KrxU+k!<{b`@k|I;XkIn1=_Q4Xhp!)fktVotZc znEM*+75aTE4poGh#^oWlu{@+}G=ye=>nkIIwZ?acj|5+j93`dVD=-yr~}0Qe*X?QqwIi zAbJ(&bW!)xmg>3HoBgx>EM}G@OYeIveF^5Fta5my=ta(1p!Vs*@Lcf*m|$_BJ)QP# zBhaH?7^N1aGUwT`Hl!#mAV$zub;E!3vdy4F^Rw3_(sW%ztWYW4tRX0$G5;&AUrBG~P4s5|FZ7bWlAh&D z^ltE#Fh=K#W6g{-<-U-Bc+HWd5%f0PqkrI9q51Wyabz2{%Hr#7zsZAgjbCt{` zUeRwOwWmy$umj;b+nM@?dQ0lF9_M?|%CPS5wuS4P>dRBV=W)DODmL3jb7#A`hbMRw zxoh-^ODBQKs!0F&2pz*C{ZgbvY_JjM+x~I#UE)tynPDSyVo&mKBSyTxw#u!(8s$6S(79?YQBLzh*jM8 zYp&GX%Kj7nRI9j{*A3U>Ew9JK!p?mC{pNI**E2VNb2H8L!_9r07qGvu-KqzL*HbE& zRj#u!l_ZlBm6K`dSNl$FISbzFwhX!4RZPt!v{h=}SyODdux49LGP{gBT|?ASn&{me zd7U_X#W%j_Hq$+lWIb$0uX{FcSKr>)Olk?+%@I9pH?RAO6T9xNWMREHzlq68sa#sQ zuhPwNm5H!1F$0OPgly}GWbfQ$JcBvf%!DfQ?AJZ{64R7aBV$#&W7Ds`7Rh}`^F z-0SU3+b_~y@#rqvV6kV>dLRyQf{wF^5ptxE6e8<+Rv@aNzI zywtte_ea;siy`~*K7Usod{Oig<7i-sk~ZK!spI`8JAfC~6je-BcKWxlw-f3A&FIlB zJba!iUAnMPMX>S~DtVy$bL?q!ob+|=>WCBdqJN9uQRuc9OA=0#W!%|LlWX<`CTE7Y z+x>>a>X{+^(ZRU=u4K%i3nb;l?qZ1p8WuZnu#vQUhjSNx$HVnpOY;`eOc(zq#mUq|t}SAbJ6N~f2)mtLW`mU~ zxAzYiFQ&W{kVeQ9>&7|0ZqU(w+aA z6j58O-0rEYtNasalvEW~QknLjM9XdIb{V8?Rm<(HRGzV|(kyjD_ng||c9yzNZ;JH) z8Xj0`eziquZQs~(!Ni8!8;HXo(^j1|N?g|SpKZ~-Nu1`}TTSe{-9=}MbZ;hN*OKA4 zu|s`yFKb5!aj}vWt3O6I<3DDZ`*bHVQNPLoXU#ItGC0qX@EMaHyIbAj)6hwEs$SX- zKS3%L-COi%_iZNgdAeuHI?P4#rYL@lOwdCNzlr3sw=q@b+jo0nB|bf+CXyc$US@~> z;0r3LoJIQU;lmbP-O0`r_xH9m!{75;h=-k4&`ZQIz4C8;5&K{JND%E5BCdq6vua&c za#eZiHqYfAS7q-O)@d!eylGvPnX9ZMxfl8v{%Q7I$iXw1mFlq~*|sGyEIgR+<%mX|V&mm@8u{OW{u!C_SLhT_|K{N@a?s@G#o zm`uhVbC^G7o?eJqxlyqU>G+wnUdki%aVKi@ovqPV@a%&9_H}RsH~TKxmIY zZp@_dpQ-WihS(>(1iOv$!637r169Vsqn+ul#Cu5L3sb89Jt)A()u{9gwX1lVC82%S z&KaoZheL&qgxmsg8cNqGe;Asf;yD%jx)zS2Y6PnDFvd?9;0yNA3*W>3 z1>0;;elW_mCKTz4m|%UkP~(QB-16>5?5O-HolN~0e#r3LuJl&I8m)R=C|!S0ZB``j z8xotpu7^y%7k!R(t-)AoYBmR!Qtt?^yvnjNyCz>On7PTfxa@9s{>(CWN)aP{6ISg< z9MgRpGFaF69^Djk5F^5r`6y*#z#NzmsI+g+Xy}}9JklRRZ(}7>i#~70IdgD|JT4oS zR=FUXakz?p>d6kTv)5)=;D!9gzyu&klpkXZxflC{Ou*9awvoUDNSYJvNEDcig!1qN z$i%M(T>$r&zok1LDi<~vOnoJ+-^Kk3!M?Hz+zb~-xw+;@z-ba)@k*Kdbn$)j! zz0pyJzD4*u6~YE`YU}qcD@oXDPne!cW4B z)5gL{2+tS`#~{3NENnn{jT&aXkv_J>Tuewc-WeDH=}dt4E)sbTFO7veGrZYcg@bz7 zbVisL&F~%9DUT26(bwmoe(jM@9wk=cv4V8^_H2Z-C6|1&;zP%=?SFTC`dHOt#M=H? zf>+p)04m)omG4Fhc|YlJRF%iTFZP6^YAL*Rlr!+mp04hc$EkCKZjt)dtN}KW2z(mx z8}%1@8vP_lJ(z7|&%q@fK=Y_)6a3WRBfk}UDv~iTt4A+5)tJ(nXVkf%XUE$$G3qRN z+m6!B4Vw*_MUPXv%&8qT-_s?Vr!zYkezgDfktM3P8QS&nNdGef#WI`6_n3r?F=|n3 zPN}o5+>8{|6Sl@XP+;Cs%A4!l|)cUynD$ECNnV$GXm!lsVdeWz+ z@U#TftSU9PZF$LAJFSn&5$QJ!eB2i!B!`U{x2&*C{5VqRDJ=Ag#kRr6>3r+dow14L4Py5V*E3rE_JN!A0R}sUqy3p9`}-`y_a&<^ zONtS`iyXjyg%iH3g0rsT={~RMu}$(O?wB}ZDsOa*_E$%qM4a1}TJmpDErnKR`BeC0 zF-{BcF^+y4UyE!h-lqz<9h{g`U=y>{zUrLG0D*DN(SC7c4bm*cn)$@zQ+F77>i@;U zU!CIesfx+HSIhdY$sLu!*x^Xz9@xzVJb$i%EMhG{l;n{TF~Mu)mS>*Bu5(H_25ahe z=k7IZUR)3DJBu8Jcb8_nSAWFC&LquE89ki@rEeIetAlQl$}=rP>Dt?IMO|Ze*8n%E z8}f(s_U1c;N$}2pQ1!@vRgLp(*5#3N9del%w7sdW^@#1_qO`RvJgHB~@Tx}MGBISc zO!s%l!`Bm2Wps|Or*5FL$&Wi8q#d%Y>TpzS2x*PkUJ518g14mO2B;sN5Wih ziTJjy9LSxo*o*TtKX*2ujfgaSWwig)@Zc46f3=@7u^1cm@KeO&_9T!56Cn%lA>=Pe z@Hys)R8P=xYCCP8@Nea9^jErgC1p};r!JqIJAY-7_>paSHj}GAc<+kmY$s~}r*;8* zyY1P|DO<=C68?o;p8LnO)XVLs@{-*5SFg4|gZw{+HB8s|a~m(l6fB$6&`FfSp>LpV z+n@9JjC{ z$gz&P>b+OKh7q*TmQQPIbMLJv4R{{IsX7vVMxFpIJ2dpPT&kvM&m}p(TR}b9(FQ4$=)nIOjlqeC2O>%6?8)fP9CdIhEcSIcn!$AW`#dMfCsgY$no{|8>$-8S?K z#C`fP)!|f^V@5}eBZTDOxh4y7kePTqDR@{sIy@H050#letRkeJrDF-KO7#iAV0Py6 ziWS_tdRO)3s%HE1zBKEDK3!%S8{_sPlQ zM*UrMJrn77T=m1Yx3^;@J>zJz?)iGjZk#dPhFKETYP1gitp@mC5+NNrtlYM2C+2uE z#@3a{SRp*F75Ra6Fpq z=zYZCc=i#RdrIj!yLE>HxGx<3Asy$}vcmVq<^*kc_t>&A7ifO6anI5{<#y6UN7U}2 zMw-%=QNrvXSV@?0PRBrZpqg;_&%t0;@T;r-KFY~aUmfozyq~7l2Nv4LtFuw>kR@jv zcBkmBSk_gOs1H8%C*hgtLSCkaeu_a#cOi} zj3a8>--!vdpZYns>oM~ZhnOJS;Xg^+xE^!X32J|e%6*KKGA8xQAoW!um3r&~!*Blh zWPKEEhx=}eOn7Ta3}iaepFKqJ5uNqr^tW_xU1uWrczwW(*#zZ-$`sX0$8eZ8T3-AY z%8U8R^1eNE{PuO8b(jxg_PlqrKWp%2dY!6_>q^SRl}kGdJbr3p_d#aq-T)E4F6mq= zdLX@x3(>#hR!$YIx3;Nk0)x^v$k(jH&Zn8Hs>>JKXCB4r)r1dN^*n5bWESRx8D4mV z@SoI~`Sv=_7kr}X5v+2AOo_p`uH!i$zHiErNlRNE>6t60`skX{fc3pyCWk-CsOw~1 z)*gQIWmw7+xt4~2M;F$)5-(ui5^LVz!xq@`;tGi4l&{WL=wp1YO(sf{uD1k(bfFr8EE^n7r^28|Ri#%%lRq~7%7o#(h6o!^7M@UK#j zclxTjalT>J;0&=~H_oSP{gC~uWMu6K&ZZhTSnnyW( zQ~B=fhGX1EHtS6Gz;`^c=L&7QwujR=Ao4#0Idz2RE7_nsO&^Mr3vK7r_>TsdpxZ9{ z^({+xpGqsSIqH%`voGq8LivKe;8{gKM_z9UUhPWBKnxegnKbUye}SCgetS>Q>lH zjV{9lojTibb7YIJn84yk^=)ETYANQ3+#5Ap9X2=FtjBo83Kqyy_83pSJ>Pvf#w!g~?KgVtSxXeiZZ(+5LzHe9Ge}(ssuGu*M>fxGe%FzeTaqcw< zg873)Awime_Thx3;YfdYutJVUpGpu)!c3kXF{zO#AnF^4g`~xn zxR%CxCvy*cYceEGudIddvDrJd;)j(#tp29|f%{+&9Wr>QE)_Vjk|O=5IfP6nFMLSb z@dtQZ4-x`tB0AI&=UJATIZq|kck~<8rv~g#Fv=)=c*aT4c^jy$T0qR_*tuX{u)||X zuec(7)hGVSDxr?R?Sx7r@9j9v1Wy`bnY<^pIf$o!g0v&+(BUbLu) z$uuoK0e`haCcfx>Q?Mvd?1PNcdeeI$oIEo>6v1j`}8Z&)G=u6Wmx*6d{gd)d9zb zYN1+AkIHmEJT{vTWB&#Mp5Yubq@ExHH=eKXty%Q9LtG#}G+sj7s?n8aWW7Iw#rM}rZ_}=Q@HJ}y#dz5X#ZVJ{0ZsUFDG52+xTG4H* ztEO{V%xe92jO9CvTP|iU@!LqY1J+AMxrqB8QhMla{qC9))tBk4z0PBWF8KvcQ=eLk z+%8#i<3@rHBo;OM)rxn4a&~FMnhva@vJl3-3^hN(JezUp+vuu{#_Y!FzN8`|jq5Y< zMmP31akD@YdRkp4eOs}|Xe?qgV;3u()g}^-T=@rP@ZC0dy?fH% zuTXu_IvnXgF{(%Cz%Yyd&BNFFI7C_1*Hq5pt8iBX>^Tj1)*$@*2hi^^2TvTUf5~Vp zZZ+}Q%G48!jIwIfw|2Z>zQ8!@+(s{h{)5vDSPA(~M*8mdmHEGZ5)w>cQK z=;>6i_$DXY^;nPbBW~z}xiSk&h(Vy%&QqLq^I5e$3h(M(MRMvrJ}MVjapg(rA_=-D z(REe%%I%#k=-ZIsm35u-T<~@4naA(wyp`5*RW(nI4UwEew9XlBo9a2Zjmz~~ms(a9I-5wNXBN);TUYXkd7CS*$q?UJLs#&V z%HCdTT}eV`Cq1KbfRSKjediqPnapwQ>9j%1#&PwwQ|SV%Z+d0Va3W%?E0fc#O9fke zY5meOtK9Z8%VH2~#>|P4m>jdr<`~Dcvk|4~@f3(ugPcGED|iO*4CDD19)fiiZEyWp z4jod{hw5~imZqcWsjYij8EgP6r!BIqA}*Ii@^aw^fm;|B-z7d_t4yDlLtE*V;e79$ zl@@EBxOoLuE56w)D}9+IH7ki#_SLN9t0yhmymB`3Crk4L=7Lu=uJU5n4t9@p<}fGI z`qEM5?WGgF)(Yz~r#wfPC^Cnci*s@qpiEPrvazd@t)+DF=?Zj=Z?*)e_IjZge!?zt?pW_Q(V}|xzjSqc zdrW|@pY1j3# zydW35@-JD%g1Oebq;uATlf(jNjJVuwI-C!kGFP-_nhrl$w2CX3V`@whtw+xHO$CJ_ zP{;!XcW-5lSI|qFun$&+_jxFt;bTJ9^q8iEs$cXy;Idr0&y{e=i?W}SS0ZOlFT?Zl z!>);!st|WX_PIF8-un%N+vN55KA|@z$fUTZ4S%>q%Qyg_G9>bZQROxmR9+*I;lKPv= z_u#)3ZLU*Zj?kO3W)0M=i9J@og3fPr%8w$C*-v;3;$JVC5B(4=og>B`v*6T)UTGit zL7I7=;SdRbN2YDPL(b-7WoS99Wm)_dWQ%soxV4tHS-ylUyE|p=CLb#lNt1!~&J0UytKHIEV&4kid27%dm@bVt>j>F{=Mg-= z!E+f8^={BB!-KGIolNXX!5ZW3P8(vbsxfqKsu!DJ7yRHV-4p-aD4AjmQ=MmlMT{tZ z%r^zhs^J$2eLy`U45EDzwMEmp>(hfq3QysQYkwY@d+HG{ZwUFxZ)RobKMG1QGQ8MPcBhe9Z?_kZL z&4=k67;$8!HmHX!%Cy}ws;uU7Ayc@Q&JvdWD! z6U%zb+Lt=?O7>tT_LKFB1N7WBf?H6?h0U--m)IiCycw;%>=?EGkzqNOX7|3KQk$hx zE51%GZ;aaLK$P0hzzu5UpjK)N&3LTWI7Z9dYXYt0UfqG0Rcdh~3=fNzk6M9?`CpUz zRXFupMU;C-Otk$+$TYea=eE_Y^5eEAa}jPHx?F~q(JQ}J%h`!|u`09O63ED<{orQ< z(f;t00YNN4>WP;fVmh>6)@5rh$-=E@jr3GQCTM#{jR8YrG<@HvpziI14(=hagi7qz zJAPPbJZxx_&t8*N13iZwwTYKrAY@~m~up!sn#!e;Y`meAD zDPk5!3A1pen=7s|9ik^Q^we))8hN*%SAgdOkqUYXczp3y+ADeoRT{xd;_ZZx*gaMwr_S$IvX zSPxWxQfN7BK63e->9AnKn&M(^?~L9{YUyU&VB@y!9g{2JVp49?6+3PtT5hvAo0t~t zZO)L)x9{`Z`X1xd9niPU@bz`-#Vz)*n)}nMJW4w{jI~98IY24R5lt=TR-(KLTk6Iu z$+-_+p;8Z-icmS#DL;(RR8;Dn7c$%7+2J=GnJGUg67srAvHR*z@%wi?lE7U%;PCF z`kHY*ov(;1w+B2Nym_ep(J8w|Guw5_3+VAi>3+e~WeLH!vASFw&QoI&V4Y5ccSn+N=U9vd_oGJrH0Fgf)imRU0%@W!9rnf0-l6`_ zx#|BTH5~n)rG}&bC#vD-f4drv{vWS~qyJOYaP)taez$n{d_wLAo+sRd{1<*F0n>c^ zF2wub0@(Ht9zdKK;jIW?M*8Ovo`h#U;@?EvL8Prid?TL22)}@DKS$Vw-(I9qx*{D% zJ0@XHPCbRsI38-;2ssi*o`qad%^vCh*5LD7NPdp=gJ5NTq2$hA9@YnR2R9yj=s3w0 zpgTtTsjudOuufry9>TnOiDcLp$!)sLh_6C2!e^SD zUhj#Q9)>T1C2Y>M$`4NZ+zZ(tPGkry(WPd-257^Pq6T7%)SPD^rY!O!ffk2H`dX@6eOBB!NpcM8nK?S*zBUN#8(DSq{rBMP)V!NCt081wVY%(m!u9r) z1zm;uBf7)yoijv7;tLD<3NuKsyC|{UcQg77s=ge#dMz|?Ri|7U z$nWbGtt=t;K~95^*JF23S^cg$ho+LMQ{Eo1@P0;Ea};+AS=vcpHY}2793(pq-n7I0 z|9}-ceoQ9J(hSIiE^N-8FDGT+E*rDw%BJkum`O4Ff$M;!-EfQ*^#?zB`;$}pBYvEC zU>nUvM}^cd{W9GDr-&(=`kO~3>_RwA)?r*Fld($6F|-f&uY{gf&`xy&kJ*3d`MhU* za9k_yBxr}sw?z7vV11%fHVu#|EG*#k7FbKWe+wI;C0N4DVH5g{NuqRSflj~lgFfc) zpZjRZBmL|E5e|WJ#Xv9cCuEl$Ii%9d^F{Bi=JxT=n{YaGnEGd;M$y(X zNoP@G-0YAu`!cI^M7sM-IWtlB>6Z(tl5<#roSS^v6tdL7a(jtdk{Fy-$h^$rgoO?@ z%D6loZIgC+JX)W-?9Lbu+uk_P*%P5D7%aHX?>~A?p?J})a!mOx0lz;AU*qRb`1(bD-tWD}&+mIFKRvya9|<#c%Fl0l zS>?`~_|d8S#E+V&z3O@!7-4NDeV_MI{wSvkLfu>H(8{0ncAJdYQO3-Oa@gQqA0wOF z4S``;WKAtg*Eg;=1Zn%={&VCR?(fAc^Ss(-6#Kos#k`-)$4y4Tf_jGmts@N8@uma% zKtW{`i$tUy|GXh&Y8mdQchTOE#rM!MNobj5*_x9mn{whYyCB#xraPKC$q+QwV5Kd^ zwS>l>+q8XGMjsf!%2_ydWQ~mIX!4NpvT#gn`@#A8Bmk|`a(_G|Dz_* zpVw=m`-+-8sMci4O*P3?Ih&z!mUW%84}eK5kfoM~Era?X^u`ys!D)GLBY>N@F^L~1 z?Ep6^z;z;U9WO`qC@fy!2l6ej(yhSN0$feN+yHEKV`(VDlT^NXApy}ge;=XY{yueA z=i(I_Yeml{QX}G{^0P=(yWJ4fZ@Le=J!c$jfN9hzse>%e zKhA)xxl^%&8LBUw)q^|RgG>>njI~!2agB#R=Y6U!IH8uBs+Q^&R-%lkW?Fu}+mwp8 zryDZlh_Faadq7RAA4}`Gp7su;ov(6ND97a7s`B;7W5Jerg34#c&DL4+GGI4aonbDa#I;POl{y*=zNhvta z@0s84`99D8_xzgn+2X+9_#jmhQBS5-))R`W_nJ{avj1Gc+8t3H6>@ZmJ|6C+I)fR4Sld;w*;L8(O z8LffBTETj;s2Y9bFWYDp)Nqm9*XRy+65n@N-R^(mhZU4>oA)Q|Hg1dGt!%UIHf)RAt=|^=vwZx0E8T6f zXXP*D^V_YI&wFqGIeo9RYWjBHAfivf2(c~Pw-r5A%zZ01A$^LpOKJ(v!LHDN&t|}Q z?>ff(2V26o^L?T%;d>+RPn!aDw7RAx{7Dn5@?(8P99HA6wYP*nY|>S=gx_m=tBz)W zTf?JUu@+o#@8sdhVMchBm{NX zZ%Vs}K@0pF53a|kQnwXtfKSiQHtCA_Y7f>7w1(%l=$tIa@iC41{&K)wTj%X|5^IaL z$LzH1Vh?I79Ex!AzuzYB)a_~wm$w|ko-h4L>doMLJ^DV_%@D$#q>$va`e?U^mL|=+ zGqt2dEH2^SKwY3ig|YHG3&!IFSFGj2dO3XOu3@gi8d|}4xRqufwDnFk(6zbvj@V~` zq<&K)V%OE2n(;kzYjjvc*%tnFs{mJW%SB9bc>-}o>@2qO^^hGgkH=a^!oH1%uws2A z;ZDYWBdm@qNh-=Hnd5t6LHPoFb6X%gzNpdle6pspW_(6QjgYPTjAfgGLbjOvFn?e#a2K&`YS$eF&d8t{#dD=MZYv=Y+%Ah+_rM~AH`XYtR?p5=otn3D{ zD!*!@*K~S9idcp=xp3<8$S#@jc)-2SZaB@EbUOS}(Y+Spfbz z$Zu|{l=Bzx!y5ZSe`wtSNfF;!d_N9hoZ72>T0YPW6>EGP_$5d(=Wi{xpP#WBl(NPzTl$8ctk%{{{;TQN zUdu`B&u^%$TIV&KV4QZ^e;I7Ky^Q!-$22$AID_-EXG#D)dqZ$2(kFh>qWlcL#Lvl= zJUZRA0a8};7HJxCNqg&64|3Qsy(8{Hz-LHOuGMB1FXZ#gD0%W&#A!>N#1kK~!GosUg8{Rc|eHL$8cl_jLlwg(N_J%VxBD!G6+5wk)VVL@8AKw9cb;i>vjy+a=4 zOjgjbl4bP>24<&)$Yy9DSInv3+J(b0Xj`cS4Qx&iNm)IEa#nUwm-Qp?`m6tNj>7&u z$ZlmJ|LnG~8`2Z@)s3R3h*P^w!Efp)pH@TfMRN0#K0VP)bswwpRK0u-G*!Qdr@!iX zJU!LFK1VrKN3j}?;VC!tL|JrW)w9Gk|N9FyZ`9S^nW6I>M14o!b^4>0WQ|WYvmEU2 zMcpT;?wT6ZeRRgdJ+)$P>WLQBQL6e{+$gV*QBniTb>hY+p+*U@+=O5m&N@g59)hN~ zgn}SRjcePsuMD84Kcoa)bLafW-b>qV@?H_ z8qrp&>;1^}8Rx0gXQ3V@Y;im`NvK5y9he~#v${1@0aq%ghG_pKOxIk7>93$mqsRr@ zelB&wDP?wTAPh;D(GtUH>E4ckYX{HQqZhCmpL518Fzly^mH^{&TB)nxgmcaJ`}2!J zVpig*?Ao&QM334#w&7j&7;l?SOlH}JU_E&Kt|`jv)X;3)Tt>Z>c$AkL8Z^pKelY*` z$+;HiW5}(($gN-lofhW{X)gWY@=k+a#a`V4fA&?#4eY6I9ulhJTIolg_%Z)Ot`9V4 zEf=$TojTS*-YVzunRXg%;0x_?Gu0acPW`7GWR2+;;zF}gLM%wx5Wz+A5+JWr=P1P0 zkOP$yQ>|Dty4G8>vGzS}AGP1Ke2lh_+P`u6aqODF9%_HhC@ZwVkKV}GW`=c&ZQ-J3 zI$>zdemW@ud*WTPAA4eIjJ0Kur!}Z%4r!>V6?~?lVpji?B39~4nRd+oQoAwYyy(ir zk~(u{BEB`n2K6}Qrm>c}9FUqb(}~u+OM}QysThk|On6Fg#$vArYy9ebOh|LjY!ZCK z>Kf~`azD&ZzE5Md%%wJKwL|LZ$>@*o!foM07ewB|YS60}sI{eC`!(2EMSTUTYXzT? z_vQYap)>1IFd4YW3z))mrzfTP11`=#*Ce-r)@g!dm5ab6DXySNyB z8R^nE!5E}{JR(j?O7e*gj}yI_<{0eg!Tzn~xW^EE-j?vPHqmE7ELv-R_yYPJ7@HWd zdph4PI5#ekM*Alh`AMH?O(3m_Jo!;u8`W@Y98!j-XExRhw1xA*aTYix`&HcDAGYt2 z|EPDsyVll7?exux(6=18aEAt`PvZ3b;X+6HB+%EZ1AU7k^qDz*x_?Vw0dOX$ieHGx z^DTVWkUPLf0LONKU%3K)39zYy|BF}nKX3)S4)D+p;rm@hd10!ZZ~)(6Kc*3i|BcbL z%d#zKUBeBd>ypOo^#0KBZe0Ra?k4-X2||M!X1Fy0jC~rSZjkZ=--hq6OnEqXt@UO*I~4tRkv|C3C(`3<8DT0GXAU8 zMk~Xw*fCS&V|}^>*V-u$g~l&it%Mi7BGO>z1v+|1u0V^B;!D;0KW@(Kh|^o=n&HjN>-P@RWA5`?Dzf* zVK#0WJ7x0ScYtNknQm?21LtW48ioD2-(K#A@88&mjovI@(`G&RqeFIjOUTWMH4AOw zE$!~QcK5dMBiwcW!DQ_FSyd{iLRR`I>|wVRr&(NM3+#3y1eb!{4!dmK?A>Ow-bvUM z@w6?$zI8L5lDnCGWpnj#RehDJG;G8Pyjw8JF!nH3VGb5 z0k|g?q}UVgrtg;IqT!AM?rNLSzG3skw9Q`S^R2jjRQ`Mea2B^?#GZfPG}B1Glx8|< z9h}AKEx68Bw8^*$Gm59NQX+)+i8k~knCig{q!wrY!30hRYNYJ$oQ{cU7U%Q=aCSHG zHP%;Q+iP>pW_@Bg9oXr#d52dyv4NFOmYgRFwqSNr!Q8?l*i}U|kbVh12kn#oA2fV7nduvA)M_Cu|LCmC)i;_E4V){K;CJ!yaU~yOuPM z=)rsm*6Y8BT}?C>(49+2$Q(d)K|Um9jZ>n7IL(;Na$G^7w=)7ixf#^tT7oaIo9*3t zbgSA0Iu@m+`^WgE*f!WcTx$tE%$C@5dgN4nhunTBEd!L4!{?^7ch|B;88bJUoV9Xi zQOgRt@l}ekHuvo3H&ee`!s#+~-d;F2&^1IIjQztc{i=r5ZDkuK>#9hy|7^j!%OI<2 z4S(07_2J_~en4Nh=zQ*~p>>3#HT=gG58OV(=u+!XEZ`D|roCqW0IJ`o3YqRx1!Tt>P6+p@1H~P#e&UubJe6l#s2_(` zwo2S#jHLV++>rmEjX7~%9MjcrMBp?G+u4RxHMs#?QIHnmz1xxf>Ge2d>s*C2DJULQCHCp?7a&t0zxLRf5H- z8xNqRh5bCAZ4>TdeQXGO7wWmz@Nmq$WzK$z=SzO7C5W>~TK0Gq`1|dvHXmAx+ppHL z*&7aE{u^4A+@{rUnx2{P^#`kPFFi3PJld)YwS`BtiRitzg%|Ld1wQBH$0q^50As~` z9$NjZ#0C@eVuCI4?yZ}5Vi#tSuh6E>ZVms?{3xfUHT?Hx%HhAE_oJmpyUCPNPg+~} zbK0}VbshJv;)@i%YPb6xZWYkxYwbRTq23g0JlpJoCK|aWD!Anmb>GCGgHHGMmV|J} zC%r##2ynGV^!J7#IQIvewy?LeaTLD8n1gyw?{hezBp#yzs&S4u#W9;EXtbF8T3U22 z9;Ky=r1hCY=GdtxI5)z}Z-JM}2g9uu^gZ3BRu#l@>`Atdang)qVo)`s^>LJZpbCJt zR4M&U>2#V{{rQmEv*a_C?Z5~DaA9+_+}{LxiC3(nY0IyoH03e3VP#EQ_+9Kw2>9sC zqYN1AN#*Tj79XDme7^@RTD);>`eQ_8O_LRM2lY6Gt+76_K7;p^b|mzbVu1%{1ZEhz(VqH0+8xO*@$C%Jje}V-8WnX=@g@@8LT& zC~wbqCw{b9YRzB4s7xy>8c)Ys^Q$Dtg5>Z3m&2c1N%m{@QyJ3xt{qRSU!QfWPTFJk z!-bk!Yo;B0*)Vdz`YkKIzO6y}EGIvB#Qt>^m9EDxNWMK)U*LR3tkK*e1n^G{ z3BLHDQr4UkaXsB0PnwuH3p;s^+r6yLrrX4F%7S<1u#Y(@4w4z^xi?qqy5D%mR=!Jea@84+ZS2s@|$%Tl=`jr1~9Br0W880ed`7 zI`K>{_I+gkt*v+?_VU&b_w^a*ccgI6X~pL2t;f{iB|}&C^TZd$VZDJr^@yW(Ld^Ii zoYk>(lR;b5)73(uAWnWAWu`L+TNndZI~+>N=9W;)mjN#TtpNdvLHAFj`jke=B8B~Y**J|@u z6@y0s^@sDK^^I7>NVM&&`n2liw4J!!xGj7?b_(;jsgIS}Py^JBvSP#=3{&+!x!#BsX}WF71M)-W2XnEb$$}M3cHB|WJZk1^e0u!!Q|j;_%vvfdAE6+Df5d478>NM#hr0jmvDmR z=yod)#|6zhev9)dvA3kQPgXzd$T1GX?88YFXJh7uD1?4HYP#*Q!F66sgT{w)+PJd> zBUasNX~*)E{8d7BpHsBsLvS*OaSQGT!+Nob&Mm;#Osoj-(YY6w+9I8= zE6UGN`?G`by$GwuRDSY<&^Z2lRc#z$L`^}r8McL4voU0acE@8Lm-GndWJ1reRu!`v z_zumr*il7Vr)k-xc1EkeTbrm(wOmnu8=<>Ye=Gd`nZv-}T0KrR*+JA~q!V2RU|r++ zwzgT3dROO5X{Y-nTEHmXKQs~Dv@72Ry6xEOO|@LW?x-?_ta;c0k5(#Re-3wA8Vws_ z%h4abM6m(oGqF+gO~uSJ_Cx2|-M;tTI9a3d#wJ0{oP-lmr!ScP{B%!Rw7+LPm`p7gR=CFp z3sUdH&F?PsH_;m4=&+V>Q`3hyG0ZXH{ei?&7PT8p>*E2dHC38SW0u zJHfVK`XLo3>-vj)y^8Zo%c|bSj$5opM_rqaFGeos=mM+l6-Pd9Th?I?r4F|_OOT}4 zfVLL>GSXJsO-lQKa3fh0wRXvLqb^c$HK;4rVeF&g>quGM`#60WF?LY8T|rI4`La4n z_X~a+GRx3#>w>fg6E`i@=GVxfNo`I8 z(f;!7sF&aV+!DTu?^`)^+3n9PM7Ns?;C2`HU4>g{pFbCaZ;=_;3k!)oG88)(Tf;Xr zHHR0r(i(b##y9Cfx4zgjnbx}aM@4zZ@R^lnGFD_|Y<2>*^ElrWt-JqVbGQZlKax!W zmyHsU&F1FezbKm_?Xv0mi?W%{WpgpS67Tr^jk0$c_uAAszW$CbXz{4$_CDrsSmT-t z@niE)V`J2Z@gsJCPKx7rmtBZOO@-M`_!;4ME&TRPqLlH~<3bzibFE$Pm}VI1Sz&T` zx|{Y-;p5FIS~+dl3w(b24aEheHxy$Are6PSl4Ej&mUWz#t1nDI`{cKeEzX}j4s~nY zC<*2JZp2$zg(Mgu|;#RZIGr)t{b=_|C_4y9{ zLi;t>&YFaJ@cajnx2x>2dW>OvIo@n9Hu=`)R@q;Nn$G>MkaR9q)w_(5feVhW^aI+8i z1^}Pm-hK3{QjCTtzCBgU>3Wh}SYtfa?u^JxK~Fzn^gXtu;)SK`en*Pic6}=4OG{Wd zA73=6)H>cYj^!ADWi93ikGF=93t0Pwb9B3(moQ7E<--%_b^IJ|%7a44Jev2UKcq`z zp+970#`#*dweKJ4&$+k>PA-E&g3d*WH`qH-%q7-?+%r6n+i!*EE`> z5msw>KWgQG?*zj5VD_WUHuL(&v+2)7wblg75No3IxBC& z*<0X!PGtqFoL4^m!)!{cO}!YlU8Gu{<{!3bwnjT&Z$ztb1f28M7B+&PXVGUN-g3Af zg)W0!w0M7k&3Z5{(x1U<@$fLr8eru%R#Q`t?cBw-@ZC6@18ae?z6kRcZLQ&(Tdi-P z|KkuQX?DVIJ=isZ!-P;@A=biHU1oQDaiKFjUTrc0S59kNcvc5oW7_E41(QuFwK(z% z>TJTMjcMLB#c9GC%pqY!8cib|=}{Wg_p%-|P&#&xn>GHig~`18vq&Q}{3F8)EG>bhN2wP<16RP zW`55JN)V0C{&hitJ5%Yb+23M5pYm(Oh!LFTznU2lftq(0+v0`g))6G!`o|G(`yfG)+oJKL>RkDjG6B3MKWsrRB zC-Ei9bQm__8P{+bOhg(#-9LfgRyR9-iAbaeD!`kKuNr0O7g) zgA<2A6=DcH2DfkG_5yAn!|g;lVsQIm@PhD+hv)W9+)g|SC&zGmKJL>Md=v7mzM4^O z=LD1()~Ih1Av2a04lX^1wG)^F-{O%{>pi=D>ahDf_JRrC8NQQvLt?#?amz$v1I|A9 z6gPYFaao`sU%{6Rhf$9)+KX5%`lttM)}=RS{z`fgtEdjr+|JnZ!mfvX;(p8PgrD_} z2c9_KVfZ2{2I(wuC6!`}k=Oy>^2_48n>xmCJouq+lrLG%vE)K;4RVrYgk^_MvfNQH zwP04kjDkDu11(?Kcl(~eO(_2Ko5k(EBZXsp1qIg?WLO5vm4$;XE_tv0X8RPI$}F$f z`*z@ln8t%Q`o{T2`p)`72W7Tqe+=6S_f1~+sT{P1C1^PVO<4>~qBec4X+X+$pL*3U z-++&A@?Gz{&*Sy=VDwI7zwr(`g%b-JZVTP)+uQApe4G<}rZ$8=cRJlJEu`OsQ1tfV z&F;$#t@eI&I_irRhq%!?wQOn`!}?%p|8P#*W4>oVpVz0R?@F2AJLAdsC9=`D&!j6u zss+(hkF;YcfP7B}n+>~t<-UcAso*xn=Zg(JgI@PzUdySyDG%ifs>?n%ANAHXF59*F zELR_V#{2ivfXFqssew|z8FfoFsL%McH@nGK2o1<)f5L>;%`{8cyZ5I(*)-pqo&l^U z8-;g&=1#rGU3B!dAg~#v^Q9!(Df!WHX;{rzf5L;3@Pa z7IegW@{9_}^klnI&U*T?7kxh8%buCOtJr{ocQLq+od{$ z^lmclfIP`8)}h@@+-6dSb~AFDQ64HjW-aybJBwsV6b1ZH`uUIM`MR(kjA&<#Nlh|~ zVIng#Hyg?@FN#}Ogk1?KoAQ+%Dl?UhDa&FsyM;4tI_L|UT*sRIdRFISef4Kh!SwQZ z@68t+#a_7dHk`@#9WSuL*UnhF*SjfyAiJ7t*K6KePgQa4Lc7(r#y1qHwuLvh-R$d@ zg3<^5n)C5w-*vuKo-&`A(c8pc!(Q^mAv~dBglCG6aug-$%u<|Th7n*-;$({N9*^)q zxlibMkEh(n9$;DJ!PABtl-Po43S6O$me6mY5l?zer$|dF=Y^qrZBv0sAN-BC@w70M zYeUxM+A#jpT-Jx3^~~^n=7EOvzVXw6Y^v{BcQ{^c4y9U5|-Z&%7`58ACQ<-R%(`sO>pX+b-ur#8|UStX9ZMb<-HGf$p--unYI ztlX#e6nZU9p_IZN3L`vt*EzyU8n)BZlO?k0zE3=TSXx1p-d~-W1^?;3LF{$Uu9VL~ zckhBn&q1%yj+wYF!QR*vHhRy(JCSvTG_d(?qz>0>%pF=vJQEu%sQ}O zDagDQrJ%@KN_8Nxb0<=ZM)Za z;(COUg722THBq>#`GgiTF57ZSi~ZBjaDR91a{h9E#$RvNo%O1+4(d@h3taxH>Je~i z9r(wxf6rh1FYrg)IX$m)+Fu9lFLT;;Y>i=ba3W|f41Rb3ciTDf1!<5l{OXg!fZUoy zZ&QQRr|;=Dr^%BnCKqJO$pvwJ2A%H3tRqGkuMu9v(K{*3I(TMba@nM^`^$b`X6=6f zapHPfoWhV1LQl`9_94va?HatM$4knKY_BhcrFO3FLDO4w@3XI0OeHLZHyjD?lui#oW>D4#d2qEd5J$Bk!&l}jt<%&)1eP^T{+#%5J6 zE?!(YSIwv}u}SkPYilZJEQZ&Ns`7>9uq|BF(RF5Z<(vu}=}}X@a8c#LX-muJR9BQQ zuH=!IT#8)a9X}yL?2P&I7FX6TR;%anz#Q))mXVvih?O9Pxr@}585Jv4R#CmEW={F? zY1I|A)8;Q(JZ=8WX$#Be&8lP*m)BGdLjr76Ij+&FtW*oiYgD$lR$U6(s^`yBs~4&B z<}X$kEvc!QzYwIe36%?$R4)V%oLYF!o6$iU)$^*cL={40QPMgPzNmUdWk-HFBO}NA z+QZf?tejb0OKC3Gx%OP^m`7{`%gIqkURyG8ikg!HSMRnoQUYFk*$b$gC-}mXd{ui3m2<%=T}q?>&PW?W=SRV3vyCfaU`S4D=HRN zE?T6{oWF2x`C{ho*MGpkL4$|n4K1HOqoQ&qB|d%vn>b%(OWnEs-5@K!q-4VQ8%DC+ z+}w`X=Tr0g6dkYFj`q=ne*@Tg!M;1(E|HKVwN`3oy57hb8S5jjnox3F@?{8{s~1gXFy!HPoF8V0?vlBl?1H)T5_& zbaOmSHV&W3e^|bS#fr;q>&k7g+!LceTw}Y2+~O=pR~;%#Bda?vZ{WZ?+-_L?vutVW z%E>j)B3}axJbn+0{&1Na-6@WiFmq?JtSocm3oGVEDD62!OW8~>dD1 zU`_cwf(@Zq=Z|?iXPj~1;#EfnD@H=MSagMX|$Om6Pe|iol@Zm#cZkvTCM1FdQsm8!+?z?fqtO)a;QDX8vHeP>Pp4 z0@UG38$5V03?<6u+hW(B19^du+~Au;M&#f}j0A>%^uoF2cXY6Xm^oP@TaMeK| z{%e#hJgXvLK>FbWE;_d6(5x3eolAT}VU8}a5lObqvtIjr4%v{}tkxcyz-O(5fH

aHfh5~cg2&fC)XrH-9^jM5@-Kbq^vqpzU()FE%z8nK zh$Lpb_Q$!Ylo$(eV%8kuR?u*bjb!`X-TgR*+I8<7h8WrQ`J4Ok@PU~?l1mX;*_;WsG@q9rf3M=Lu3{uB>B#7q-e_V5b0Xep%oNv)8Dj^-y^zWJVwmx8p9FIY+-3ou zgZDzFQsWG4>$LVZ>>``!o3c6@EX# zw85^3dSjAkj_)oimR!+b9tJZ~)F~w}lVQpLS0T(IyjQ}khuI4FA-tc2c^>xH@qQa7 zD9ZXz5$6lo{{s7uqABhI>_*98PJ-zHlP&3#L6Y8hHQqkJ<6x#prnm~c7s1>CzYXx) ziuX>uAIJL{m={r|6y%#=F*23i$<97>HsIKKHo)_h9`&wb(S;0k6;oXr{dq*Av##l0 zIiW2+UpB&D`>CNv)qGKxTBGJ~eM>@(OFw^pk-R}Qx>ju~AB|VOt8Q%>_u!9!5MSq! zlgj*s-h^!)%i&%TuX+us$K|9SW*fbQ^m8(+>Sf$e<&}@DttRA6Q)yv&o&@TF6 z+PeK$Mq2M&t2hc34NHwR-c?ku=C78G&eaeI|EXnrm>|l)=mF}8W39rStn>?UJ%$Gy zW!Vy^BAEE6XmlpKJ%X`FU2R?4*h6Ik37P+zfY9TQD?{jnx1ym#ZWYO-6%Yk$wN?jZ zNf42~V&xH{(TUbNCDw_M2}hzKYZYR^eG703hoiKznJB)zfWx9XH3YJLjbJHj=O=~333P5I7Jrot+MF%L>!y^`BwGFeRVEJaZ$q7Bd)(IOvcO#kntZX zY)g^4N_Mm;u6mCUQ3u?&qvXr+Zy6kNe4*8`G7^{t)geWQ=CN@U72uTjbtBRx~ zPHVn$D9SQxil>Yj!E-1O&59P8e%dpFcm$zS@xM}ZdE~Vot)V1Ok1BiVzoj5zk)tGf zxULcJvbf5lTERG8RMRU7PVZ__GP&xk4*HJ5e>_p`(GqbIyaBh{V!6xvXi28aSnS2b&n^1ZJnAQP4CKg3kW6c`nSK~ z9*<|G*E_wG`*JN@VRP1_mUGoR2z&HbD~t4#Yc&T!wt8ygd-8zR^4h2@;OSojT3TgA zjh5!Td}|LB9{fjfwK%0MQD4KBe_ta}b45*2J$R8>S3l^I)CH=@k#TJ;Y)8z7t?%bljNa8a8G~L?X=@4pJQ28aaxbg?C~yZE@Per!U>!Mz z05lbN)uF6bm1qG9Bq|o0Rrx9+k?)E0Ls9Xl(Yp31J`_cMR?EghC$uIC<6WNVo9-&! zT9{>7V^yMY$sbB%v)No;Z!~Z;vd60M3N<9JrJm407bPd3&`^7I2T? zt5rAX_fk)g|NNM%x$GB+i>e!PAzI9tY(($HU}XAOD>HRXW5(n`EGBIG5JwZNq9-LbUOy~r6?O)MT6BK8I!w8NRm=L()=;&@$rwgxJ1g%P?l*8A9HQqSMulwU9u-0;hCJ8 zkYE`-!FdaSO!RrSxwf+U!mV3zmSoH3Xt_FeFAvJt1bXv^6^)(0(B*`3rc-a08i>b9 zl~8>B&^WJx{w*vPIiLC;syox=xxpf5Xlo z4TaZ`_2GOpYLNk-m;cBaR6VS6n)@oN`|`rP5|zz#uXiOyLby99EUeTkWW1G>*p|Zb zlA=O##a2r2PMB;nx0jF3%D8WAe@>*=H+uAh3DYSIL+iskTO1|3x5)KRw62$V@EV0Y ziqPG|ua~0Nb2;?T+9+}(I`A6~tA9_C>#>qdUS%H1yl*U+PjalKK%UHgmh@r=S&A)M zt{BjK5Qib^RmNJK-a>`dQ2l9Ohl?g~9265HT{Qig4bR&8UNlr;s{-i>8w$yWssFXs zRJ^Qpz2HQ2V2Hc57v_7Je?zaeR*j{tUn#fPRz%_vGgdA*lH6jyTD0-eBJ`@cFyZH4 z=G@@gT14uEA5V(&&-|1sB^Gf_<2vBy38Qa9%usT@EUQV){t3qL^0*L*@`ACoTV4IM zVQSsJ(f4ON3%M&RN;pi(NBr|G%zv*EkIq}5(B&<(FbItQyPaN_ld4j^rt^;!wPTKs z-DEwD-K-c_b;G#Lh4EH@CQ8>}yp<2*gZmhmiLg(_*ljxARd~;Z+aj2iFn7V+gLn_( z{W#3CFb84Ygn0+ygD}5`{S3^PFyCSv_b-eSFCtwF#$a7xoEUfIVEi={#tZuxm>UI2 zHv{9hD)`TX`*Mu;R>OS@#%vG4?HRmZ!utsPKEnGH{JzBd9|(5=Zbpp55@5Pv+~tDF zhH(QPjPckAyhp*5U@SHTa0T9T055~xk9Qs5Euy5`jW~}2{uS)|F?Kr$_aiVL!JL8l zGyEG7=O?^TQ=4Us%i<(stPSs;Fnuvz%adfi2X-Icz1CWiwvnNDD-maH$9QnFP=wY)ZH<_udY=slXsW2i ziN<>oabCY79yF2Vv00(K)ZNk@EY7c$i_7DEi#R`ah*y;0<-Jbo)>3%aug?bhz`)A+ zaIAIlS~BWi2*0r~3oB<;RF0)u_Q5`E;3tV1EL;JL?Qj`GiR*_Bv=4K7SIl5vg0*>M zqa$8mg44UNW};7MMq2 zo`*@0u4UIVADalG-S}I^(6~WFI!LJw7LrFcD`b^yDUK_dfp;;R#}?zg5aY;l?lOu^ zf@>jLgs>$rw*l7xR>212Ign$`Vf|SFzN!zwQM#0h8}3n?8@7SKGnfs683;3k+lL^| z?ZBq#vc?YvFo5@AWCZWXSSDY@c4RnfzMI?&&U{)C7cL~Pe!1NNABm|)O} z^fF=?CZh5X4Z1a&vqUeGo(c!^2zY4qkDqy9CzyVSp;_?MVOk)S=cMM;(a()?sS{WH zqJIhe4OTtAN7Hw=%i}A44K~EZ&9;aWFzjpdD!`276y(jfQL4(rldr%VO}{(b zZ7*F)zuy(%hF$@C5SHjCdV~l)li^kp!BKEU_?k%gJ)mu6Bzzs*{E_fYpizNcg>l1_ z!PLW08bo*JuTuh@66lmbrvy4B&?$jV33N)JQv#h5=#)UG1UeP6>2Mpi=^!66lmbrvy4B&?$jV33N)J zQv#h5=#)UG1Ue zP6>2Mpi=^!66lmbrvy4B&?$jV3H;wCfw9%|ZmnE6OdUJ6Q0<3nigMj*X4T@wHH(Jz z?K`V_an+LPxijX^?K@}AQtgq0i>v73q`vC(p+oY@hYlD#V9@kIc>^n^56E-(A2P6F z$c((9g9pqQGGJ)l(4hkdmiNnIGv?qzm{-;vWB)=Djo{8*poQH?=^&-FM`EdUy z@4Wsvr>g_L!Yg3%>gfO9!X^JDzS2nes>plse;NP3rSHED@BamQHvfYAe~bVBmGImD z7xLQszwrP6i=6g@&Lkox@}m=%MRsaNw49!qel`$2=a7PE{;lo)g4LADQf35n}|jiX}$3sRB}HNuKB(p7+V&4!mt!m+Y!*>&(7Dyw~QJgdkC z1GR{=9xq`Vd?#bgfC(G@%3yHuX?Ll63~bUvz%NNvkKvF_X)mzqCDTzH?kP>$g`-xb zG}tXtK2XFlnPTeRzX3NS2>OMv6A_Y2Plucm5#mstMueV@K(7N6l1;x8)=v}R*;^+& zD5jy5;Y>UjVd|4eTDA)p!IpOM$;;fqE+CGvd_{UoKrL|*C>wq<_1J)iI*0L0Y?xDH! z6x0qWr~;*6X)HjPT;X;i+DvrC6jS;`1tGRCvP3tO=#K4ylXG=LKS!Y0S+A1y0$IN! zYK9VFasMK#j{;cZ5*)CPB3#zEITX4ma>%&;8~A@Y1A=wye}Hv0NqxMo0a;>@xST8dwADH9?OE{wyt-17 zHr=y$COag?UWUa!I}$co50}H2xr~C#M{vQ9Gew0}9vqXn2JW1xXJ8?wx^jk20OAZ? zfTf+GBKUKLPCz!Cp*^q=LtQ<1Bwhf-xvGVQb2Ste&Q&t*F4DMSddkb*DAq#HlrmVm zB_mKDfu;6C4k`wHFMx-ryeH}V!@3x{C1&XkqGHes>}j}&tPZI~>!B9t`DaF}s*(~2|Ognnsmr}V)-;zkz4?mAwt=y<)NB^$jZ!w<42Ci|2w!25jYo?TTxDgA@c~&_9)sRgw~YgfX^@zRLsZl zxgF9H3dx7XzC)BO7X-^<6fI#n#nupMR!-Y;j${P|@ZXOBai)Gm&Dtjk1G6r|E1rVj zswJ&Z%WnNWuu`Fx#!FhEw(f*W!XtP{TE#2Pr+QJ29giFpDx(xsUZOy=atbOr1xg`8 zRxL-GroT;vb@tzZjp^aaRYmNXcmv9)ZU8w8rgQ`Bc|3?h(rVF>uWpHSiXIJmq>yIp#I;MTr--TnAlGI)xOI#VwX^k-x%yLm<~Kv(~}-SsKzv zLjEz+5ws}62ixJry7mT&dRT-*$+j>sQDLxAJvxFcBVid0i@gk%NwB0m1}tyF*fSK| z3IiA5cn&WpCKG;}Y+Hwbbp4&}*RaqnGq#Umc^{T;f55|TLbN}@()BzZ0g5y`+5Qg% z(V^O)Uu-V``o54kRb%?TW5JY#^iJQGq-$|=Z(@~M$w6NVE47ibOFs)%Y9pnglGaA1 zl3+9SB-}$#L|uMYs%sh%P3P5h9|XJ21A;uTpbJ3(_wc~40iP~4rV@Umd%@cMaR@8M z53nBSbv-Egm;(S$64tbRI{7<(`xsmH3bnm0?J!jr(c^dgB1Gtk(W)o<}~8yt=hipLT-8=NApejgCS*nI?L zi0PS*yFn&pupv`S@5A(dSSh$6(RmMuOQ(BOOw6TKSXNRJF1w|VA&AjRg)xCk_(zmT zkrNfIt!xL8cev;!T3h8I+r(q2?eajXAUJfAM7^hxfaI}wpk9o0N%UH32H#Nx6cd!y zNQ@~b_W@F-qMV7!A~b4}vK zK}de2YEg-Wi>!PJws14yHj2Im^^!sP4laIy86E)bg3hQcLN&pZc~tt8L>_+v)J0Nq zAthNEjx5nBz2L4_*fkV?KD7{v4cHZBEEJi9q_nApz??3S zGzHT)P_PW{K(P4&LfH%$We$O?j#7~x#BWoGp{C68I2?uFkfU8j=oo5oVuw)dK~$&2 zOr}65O_v9v2F`N)@|es#4vEaBgeF82>cbNIdJsY8U z<*b3b0P@PY+y`HC8R9(#V+q3jHHtp76z*u02xAk+dONgKSSiF2)MzE@9)hk4+XNv& z+%lXayFVY10Oj$NqR>E228YagMEnW{f!w?`*uIR?H(hdOyhYf#70!vgML7HxJk3k- zAWJN76<++7QwP1(D$pbgwC72XqrW3L9@Z{GS5hCAoCd&_jz>~19?ya^eINT6sJ*K1 zpORCE1K8oLAAp)EWgUujZ=lr=64O&z(k<}Nq5mt7@0Kd(r))vNvcp&QQdkLNzLCH$ z7l`RX%38#vOC#k%FXjLKr>-Qg^>|Rjrnr)b3~Ja6Lz5nYD>Vd0vz<2tMn?+O>_IpisUb+A zhTt4Q)DWalLqK_IWqbf+rX#F9xVcp}sS7>%q5ej?j}oQl@_Pr>7_ zv6t%oDiknrJEX_!eW{yH>MNk$F95y^#i#=9vLJQUtw9}!sun|3P{yQkSczHDCe4AB zm=${KBVEwrU=@mnD&FbA82n|Nx&hdYMV<%$qcQGDT)dsNb)v!$W^J9)|6BY(U-$n-p2N%XktL^OV;S#Vx4y zIkXX2#9Sf0)rGgNX2*(yk zUo52%u~4sHQz@7_9xXbHBh`Ab3+ZhxK=C5xO{_x^gKAMgz0h1*{eTltI|1%T@LPa4 zp>_mJBw88r`FF@Ez(icU7qECMj8UZSLHsemKIFM*5Q&8NZLy5~fczEp;vWdM#G%rF zq(zp&5M=c6H67S$};(k;W z@vVqZO9*qs25_155||#Ha5fNcN9YvdY?u~%1mN2#;8j}e?G*cFIDU%k5c9P__W*tg z3MG0uOb9Tnm`0e=!`v zm`qv1x>B~BUI6zyg$5)e#C(H*r03$DB*-qYsKaO`N%RxQk}1Z+Jl4y7;abnLM~KNn zFdnO(vMc5~fYgAA8dwXET1l~6! z0aCjyYT);X%Y|@)3*jArTnL))(*U^;P7q76(v~A^%5M<&<9tZ0%gnA34*$S2rKc?- zqSIVND*$p4og}IfZ4goX06Ti}2Z6w)6`1jWTS$pBG`6c(jlm}7vs0J4SK1&5(gA+?gjZOdoFO1Jhq-ya^fG00Upy#znHHZ`>9Y3YP z+W}9ZqWCWkCk(j;_Mf=@GLd|Sc24Y%M%{WGF}NK2%Opo;xCxC0^`C|DN~(}=q?$Jw z9s>2Bjnsdp0wPfV*+?0#k{|V-jg*}nqW-gy3L=$gf%?xzDu*1R{TA_?WO}9hH=_H|=v&PAo zO(G%IICLwqO`Q@B%_T;u3V8(xgn5^BC+1UkLiYxxL1A_8z?OOvkD6N$)+Kb$LdkLq zU4%4h$BAa4n}D>^sQzjoaijrEO{4mYOP~ipHI3>oE&>&xGi?;~gp=X`=t|p(xEyo> z%uG|GvP=iq2WpBABrf0s`O$7<=uV6(PylEuM*I$3x*=11a-t7d==H&s$;EgCB+Pc< z9qnjqB2=s>2Q%O-2wzH(C^gY2XmoW;02uvvPpAMG;9?;mSV(x{jVS$V;Y7m@$*~g`c(%**4T&kEP*LsW-XlbvboL?mQ`{Jm zc^OkeAe(b8JqB;O2UMiV(ce=H83^fi!~b7dK{rlG3W+ma!Sk(N0F2uS&qakMA_L0J?7tQEk8r@yu`$Lh(TWw1xp~3dF^^rxxo`{q9G0<)yB(Xn3(jOvrp`3HZ zVkbpKtUMk;68lP2!~y6PXDlv)M4XL6#1oH;@FKo?T_jpul>XVE#)9fku>LIRJs6PO z3lucJBv>o26o-L{<)|SzE7tiDaV)4{>}DQ*Wh6WmYhG+)(2a(kFXz7gE3x5%&5h7$ z4cpoXwy#mIaF_TV5tpe@^4ADMd~Xy`ls4%dKpa16;`d>1)K19#1*9PdthLPtP*Ty;E@KX z=(Hez1v`05D*(Dt?+YpPO;VBm_jh}X1HHOy(kPIclsI5{3sUv zgsC^8{Ss1zzDP(gt_M#%8&bytHa-cEXG1D^8yf)fEKI$QKs~7V0+qSIeN9&ZR7hD> z3Tzq&a0dy$7k!~NZ3noX3PR@11QG|UDgb6tU^Ax$4-3!8-j}(Vu0=8Zh!>d~C~6r% z>T(Nx2()ZRZ7lR9D}FJ<@YWVf#f*eiTPzhRlA4?;q>gBanTdy1oy07FRjW=SB|c^i zT(kuoG55f#E#S~c7I3VEi^$d+FV!yxkpaiEuE>@Dt)*X_4?Kja8IL0so1h>uLAMB8 zQ_hIC0>HX`04tGmqJ2DpCjtJj4q(b00u@j~86|dGO5oK1!{nRF15X1;6|mU72YmG? zTu-3A6MH8DGTaW&bEKUv3Tvnb_-C{oVjm8^3NYOOaG)Q)Oo2i~R>2qvmuWa2ex_vO zMUs_m${<)tvQlreR#96t4U8#*hgnlfCE`k&`bEQOAtg-NlL zj#;Z}*;Wapuz{va`EUmadb{_fe5jErMC2z-je(qmL}4Q03DOe~=L_(nGE;?XWaU%f zu4x;En1%qdF&hmr@<2$eAg~0Wct60TRRpdAxQAjVk+1Go05^^X*ku(6)JIZex-a3* zmD)+}kSGcaQ{KZPo*9qe6`40SYhn?pUEd2MOzr6hwux8LdP9U(FNTAh)`^_fMF2Ui z6FIF%0sh*7_>(xT-vZ1c)+cdV6LKQ7-bgfAG9e?~IJj{-XHXFRIFL{a6T@q`3XOIa zD~Mg=?gW=C?mjBZ%(@u}w4Am+2&i9em*I;^{SXMoM#KQrltuk}P&VyH4-ON& zf?N#=a@Lk{*6IOr)|L?_`6YmywOY>F=KwitwdAXtmK$O1HcCSOBCzKIyH0{TXKy7Z z=S{e~$h{EmoV~hB>}}xeQIH>DbXf?(Ik|(PGRtHV*NUj)-Q;NTz)fTAo_5x(G&}Kl z;s3|jdq7!HJm2Fp(>-qkZ{O~+%O;k%WRRRCNfZSM0)mL5WCT%62nMp`pokv{Dk2D| zWD!9nDu|+@2oh9O6cH3b0Rzha-s+is^LD@I{C|56!`$iWs$137Jv}|)A=h(??ncFw z5s);`)})Nau6ee`yR%Z1HWOI4C^rdWY86u6vC@jbYwt1Jr;sua;R3`zXYr{N2jRC= z18f%b$Iks)|E+KZ6^|aaoF&6FRsr0Fm{jf}91du^Louxucn_d*r=q3^j{tPtMK~1* zycaO!?m^24ybmzWJ%yfkf~rOW##^Cv*k|fGS)sSF%ha{2#+gAX^f8XmX{TK12zKeT z-6+mm&!EzTv)Ej)61{HwCy?UYwJP|lN3{N3J*eHoI4cN)hhfrkvr%?}f};R&A#n}l zRU>T9Ku$fOwN_)an+MzC<6~0kzsVka9MG|fIQ>u|EH^0BgV@bRmaEx}-sC{^rbylV z=uM@;X!NE52BJ3|Fc7^NfPv`E1PnxP7GS*PoF?~+*sN-eooDFq+H zl5?CY*!-G=orQGx*?_~(Nu_*;7XaeY?q*ivI>iJuzFMVql6px;oBm~~^zHjzq?gf> z+a4WW0>7L(+|>yBHmZ3AP(_n!1z*sk;MEyPN#X-U;ZsU%cU!?g9+CCy>RnVO{M8jB~&DOWy-5VucoVCs1LzfB70e1w@nl$VwNmxqc~j zj%yuKUj`~2l}Z;eEDNh+rc!YYSw0v08C+GTXzBYvr5~hH4TM?z{?SzG%u2Umvy*Io zXQf}Y`woCe{U(*}0tBXBBZQn<`e;RqB$7`)b3^kQ7j|@x|n^r&{PSVFEAXc zEb=JkMnj!Rm6p?zQa539o^116eN~lm-9COom!Xuq8WXmW6f6s92U7Mw%bCFAv$5IM zA3N8$NZBf`+XBriWU3i6D=GB`HjS9r4M?VLfJ}WO^$Df;yhu_!c3M%Yh3nSGkh6>J z(b7m=q?8@9tZrkmbF-_J>J-q`g;MSxa9Rj-1w;;2fxN1Z%~8_4i>mJkRJzls?o6rM zu<4AkQ;lFV3~}9|upo+g({;y zOa(0B_CzCzhtmMV?moYJP6v#*A7gZo(iwm$?zR32WF}xy_nX#U=`6r>_n-GYI2$m- za@I4=-o<7N8}DUT|D~P{aDNU`mYW~p1%QDFF9g(4c8dVrD7(dgaZz^9113b-y#QDw z%I-zLq$s;3fXQxAKS(bDrn*04pc}$WTndQQ>? z+=8-ofT3v4tp~&cB%hX5dDxssW2oJ({!eWQ75)nPU>F{kADU1{{tz@{T-vAkiG(l;juj{b;oAz-Pnl> zVU*G#H)WAGN`yk<+6YRG!6pZ}t1?5#tK?AhER{;B=vk^Lpc9>8rvU~gQt5!MyB_9* zM=`*V`zU(UF&fDL#O)~-Nsqg1IcItNPi!vJuM#1BNo9wksaG5|tZ3@x0^+<{CJfnY zuxUib)k5fcuJ7ooL6b1Q=jk8PJXPwJLz2Xve4uh_3e(4L*;}YaHL2 zg>;;19ExUe6G&On3~mYtmU~#~b8L1Z9IAZ?P1Yt9Jyo}pN+Xwgp}HOrO?HKq;xV&* zgy*VmtkgMBsjF0~UEHg5v#C^zGvTJ#w0jaew}+6zy+iJ|ehT+tCESIiR6lGoBiI=k z(m85q$SndOZ8c0(pYtmX2ehNPat|#u~6pTR)%gtDe%RQDq3Wym=3ic&Y%2}b}*uMeOE!7u8 z^UaZqh^f@T9Sxjn_)LI(Jjl+GxTFd(;~`|5sPKyHAT1f-wv9SJA_=`Rsj04M<&AaEz31Y{tEO+W_8t^{PTsQ4K| z5|AMhkkz%kfDDz=I6w)=-O+#yiw0zPY(PfD1ms=`NI7VbfIKJxc^Xgx@<22o_w|R% z=^vm(0y6R>;YmOV$o<()Y(O?IML?E#0l5k>@o|aptymi?#T$v-GVw;Dw%qe(qG{e0o;O;Wccpl<7#hTz*XWID&s8^pV7gt$bL>@<(RHzU zG)mU3J{Sv&?|FQ4A;ybZ{T>-1mpo&8VvOw-W33@8#y*T1``|+w8|E9^XN=L%Nio)h zDRtB{_9Vo3ZLE)q`)dGsZLE(8JPg>L@VLOMfV?)=UlFG9x>$cLySygW-^ebnhxG}O zUsD%rEVBT=<+W!TuZ8t@EQvaGGRCPN#Hos?CQkhnb?V3Sbm|C7BsETnQyEYte|V{y zojlGqUS<9PIbJs72pcRjx1MG3x>?8DG@q3p(?py0v+w}0v|tgN1hQL0TnI#LGRToW z8Ma@86WOCsCKQsymX*Pv)-27{$CP2PP|D}Dlh2{4$AK|;TjKB_hoeqDhXW9N2Ly41vCq5=%tZp-+2G0za9tk< z+{NJDz{7BY(p~L_l_CEE@NROx+2HZOquF7*2}T?jna@)q#{O`D#8!9w@4 zf6i~pp7xD&;caLV_j-$aRz@_8|J;Bo_xmnffgqPCa70`vhkY4Q9uORD%7=UkCY>HO zVe9~Lu`@al#!>ccau|!UON`DVg_`mh`_y!w!$JtkXf)k47vT7my_+1LM451Wx)8@sJpHZoIlcrzNw<~4DH@ko zV{N`>Z0_@It_j$D-Oj`cM)n2Y=ITN=Z;im_Hs5AaW6R=#430?K)B)y-jNWc@1P6y1 zCOvlg9KwqgL=dcojT+_jk*x}`fZ`0(% zQDgIfZ}Y2w&13fCwE35B^LQb@uX30^>)TAf){FI7)BcTt(?&z$7yD6mn>&FQ-Vgk% zV8p?jlKkd#GINsi!U^3ZMY_rFHg8ax5$}TiGAT`gCJE6+p&JZCUpgJ{2vs|CEd;q7 zg(JI=($VHmbYe$YM@HE%eZ3JUFv>dnXwz!qg+9qCIMOfWSWv>V3EpPHSV~M3H|`b3LEyX0wAA)46=m`h^1niJc8)nkd*FvRbMxCEqjfOt*kHxhrr zCtj-%@u|$2)&+^T4H9oxsM3Frpi)<#_!ggd*8&ml7E83dA^M$9v}b^552t(xL=&5O z5x%96&7thngM6FiAvkob9AT?wb_C`%p&o34_aLwgJVPXSnTvtB%%_JI28Sd z_aOc+2f%v+9Pe>%B*#>o)TINCD8#WHQ+u+{u^I%0<7C6B8?bPE(%?sdh2s>%@nvA) zIJE%B>9HJV7>*x7TsY1SaGdEpPmWp5J&v;qam>8~9GCeV>p)OAE;F2Z0}IFH22TLy zT8@6jaC{Y*i#d9Q!+|VU_^*_*TrNTCRZ=gP>yP?Xv(Lpx{hHb5%A~q;suQvN! zbJS~OKRP(B^Zho1<9g%w0ZZc?<=z}{(Wus#lLUNzW8^-?2CWjntd)=>hFx+522T9 zmil{V9EP~4??1%){-g1|8pOr-(*fU4Ij>`;lKm6PBrks|y7k_{dMEU`!t`CP7K>Qu8Y`C3%=qa$m&iWympDH&OpLXB|G zQqhq$LoJC1_qbLb_e@oAWW7S}wSDeoAt>By8}2s%3->yP>qubXUf19Sz{0&=fP4J_ z_XcKP+N7b;xf6AyO&Y1#Ho4aK%j9Ddb)O%?kNdwHW;B z1jUa&hWBD%@#A)b-vgF@c!%Nm3$XOVzAD-e?=-$hp57((Oh4>r_9ajIn|;aC z0cKzFbfDRnJRKza(Oeu7>-SLOcUl|I@8JQz?^dtV?+qvuzlRm_`|wiuJ=gd9V+e9J z7Dt$i(Hwf(w`nFR^Nh_)zRhO?HlI;LuymVUvaM%xej%F)Jz-(FZ?h2uQs@m$Lhqc=3r_H!rU884h(m>)IRS!l4PxtE9L`<^mTM5l;Lm|& z=}GD3EdK{sh6F7*m+RTuHDycNTNW3zbx2FMl3^jvlubsN3=8qv8x}m*616wn8P|$v z8Se5RF8q@ z{7{C8+6K1;mf^9E;d~FU_*&QaD#LX>voFJVec6wuYeU}`)9o4=U*ss+E$UXB$aPQ%BF9y`DtQ?VRfhNGc?*P6<*RB%m`n`M(CO2-?TPw%L zqz~e!hx&T^L5~cLLygbVfn{jCTiP({+i+j6@$DYt+Xje>Z}$a!8=+^?w^Jw+-|qE% zwOL_AjmrtI3j&*ATVz_=nY!hd?v6A7aI-U2>dy{c*Edhz`W6*Hwi}Dy=JbH zH~XBc$#wEu!a4u?aI0>B3^HlCMORA#{6fkluO@)r$r-jL?~Ym zER;VtF5C+&ln+WeWiA007r&75T%mu+$TsZc(LY=u^^W-TP3j#rAwLLJ!ttv>$dBo+ z6!O&^{x13aAA=z8wBU&FUj{6McF|?Q=4- znSTtYs}L7XR}Ci_mj2aEBH&cJv&ZQ_;bh`{MSlXm!T2^~r_OY};khF!@wN+&2)_q` z$FXZ{F>b05=UI1F%-r#|9dRyI+xQHNCPyxI-Js z+3uAM#x3YbJ!p}7LvYL4iZhQIM{kH-wVWTIwItc7OW13Lp<#I_;;NO6FGDY^=V>q0P0@aK+iW|96mDkFRVlKFdg(iTu z%k>t4O#yKelKakN=qULh{7}zguMUEwl6a3SAr#eCMDCICY%X`-!g7yH?ScHGa7r}{ z>C384K)#;HbBGsn^(pUig#Q~=R0BZAiVqA8?Q2JPFM?Yj_a zEqC4?ya`UY2++1d1F`QtVtOBTV;?b{fFs2)zN+EzDXL#WcVzaFNEz9VcVvd z2VWAlNlr5QN5m^s{tK%1oA-O8bIxKLzfUk zYXL*<0Jt`iTGs)_S)s?U|2+~NYGUx)NXbtL3&N0N(2$HaV@?H)W1XuG!{ zfGt_?D?rDJCgAUub2E?gm4xcev2!YfH(XDI-24cAC;HA|SP=E!i@yATous~Kzd!UK}$0s%V@sX)Lkh{|qTJ%hhfhcu_y{{zroa=@z}hX;^q1*GZ- zyNAnqmFkk6+sseXdVpxEzA&POVY37SqiPd}rfM3{(L7d1i&!1kQHQ$`9EDa(z>w?U z#=5jzE5La7EtK+QTGbjbA<%M{Eaxj8KaEWT^l)`^9LBK@0pmBJhUMlv&CiEfoRD7yzRE)d;6@nt3+Z--4LT11VG!(8Q|fVEMmVY!2# zzBhB~5kT~dds*pGZ03W8n#jx>7pOFWl`LmIrC!2jBC<=(iNlaTJs>qxROdI%EUJz+ z&1^ut9)FZNe#Ay|cCv`s`)okRbD|@^@fV1W{Onx_i0m!N@udnj?UB}M1$8V9=vWr3 zV|lEOSB#FksN)`{5l)zD6PtZaK*zdR9qVItY@m*45^e+xH2WKXX!hN-@F_Nrx53U% zvV13CVH>0Z1JriFKw|9xgs1TdsG5$=g(tDIpM%N=0UaNTj{Ma5h&n8%DJ!+d<}I9k z)iJtuFi`1`RLT$WVOEL`5MKfYTss07aP24{Tzi2QUd84bX7nk#_FcfjNk|1U`Uk*3 zM*j#1*S@C?er`!d0L+zK_q z{@=4uwkp|CFDKMP_Dcs^2`-k?g|+%%a}(!XRy?fz9k6yqtmOytU$K@yr2Pk|rPIAY zHCF-MK&SiLa@O+r+t~b%&P1p_E{A|60SgrX z0~RU*1}szp3|OcP2n&a3;X7<{aqd+0MC0v75(Dqx{5V8B8>K%`9$t_M^FZ1^o~ z)ru^e26Qx!)zLz9HX;BhM;cwD4Pd>7Li$P3rORC!lGW&c=}17cMUj8!?v zsJtH9R5xti;kZ6B9^?9mfXe%*5>IPWYBV;-IADyU;YR{eV<;6pK|KoSxRokKqSI9GDu!F9WRQG{7L*0(eV{xB}2zc)Db-u zF9i&_WqeOx28@>}w@h@F0VY_^78-mXoBo();8h0X`0MdemTMqoxsT(lAQsjFI_@5S zDzy#}r2nIi#AHlKa7m)xC+WB2-3z!dlS=Q34z6Gb+$K8meb`PN?(;b3$O(Q2V95R4 zpAPN>jC1Ru@uco9zyx>1R!?|0U=jCaTwuvn#Cw2vOnxSqsOPY01=rPqc+4pF2U_bB zNLkSY_!JQRV<#(pj7=vPQeUz0z6=EZs8q@yaF4N)n}79j98g;+T=Jb^mVJj|!jNI_ zt%f@f_z?~7x!}(Mi<;%SY!>0IXGm8o%ZEG;;mm>Of~7{<+0J;22JtdK*^ySFi)C>| zK$jnP4IY%a36NLdx&kAXHUp4X;JTu~eSq=^TBYd_PR(D(t3vDGVwUl$wqYTd$$gdT zU4YV0p{A^%)occ69{#LW0VdLmql3!|-!%sTGx)GbvG)*lU5_TYUf})Nea?_)56=%i zP$OJ=u=Ubz1LXC-zD3|HKwj_bo&q-l@_JwQ5wVkR=T}V;}wO zgPP*@tZ2hdd!7MVj1p5E+Khey!;4IsO=H> zFCbSI^j;xey$7Dl9R~P8-Rg)?t)7;}WdXfUN-v?b%yz&J1&##dvVi`m8HKOnVp0OO zUrP4_ve)U41ug`X_J{vV9e#fHmJM;tsmVOK+62HU$5$3{T%S|xxSEHl4`l-08rzRSP{DrGZl z+4;S`j7{&d{cc~@rh7Ry#KocVHa#jX&j{z)Ye72aL6ltr2X8R4GD)0&cAt^prknVj z=RlAv5jY~8Hvu2d-l7z&%xZ)Xkj><@Hw;!aJbIqr{HtaZ}m8|l=uWW**Ku-PNl<}JqNEf5#KZw=V&Y4hvyIZyaDdlj-d38z0j#J9P?w>iZ4y%ShQ zrlAEm42$J3+;I5W=WuU;!#(yR4&!_d{Dmq>{Bbr{YY^)x<_6dV z-)6kr0GlW_bB)r;qSQ3@BzrXt-U&StkSB$VmC58>Bnet(Gz`x74Niri+%=qEz{&sl z22JBWXPjK;>s=IZa)EuGHjki8oLpGQ$tE0(U-NDL;oE%81UmONELd}Becj;Jz`WGf zs|#>i8_Q{(;WP~5;_Jo$r}gs0Zq92c6HXfnacays?DaYAfuO`;ui{Zn19Rm`e<-*Fr}p|I(~0+?jO$K%e}PW>iO=7ht3NgT2SNN_^h5o5fd2uTAD_!P zg)-s)St0&|IV}9*^XI4Fh5s*xUlm~C|Es}WfhDHr3?2>46%zfM;rth{_;cRAK4zf# zJ(lwyhVx4h7tWUhoG;p6l5+-5{lfW@aL%a(ENA0CMK;*!Um}APN963R4iFR{Qk<6& zkE{oP#fMab7Xgb8MGf8!EIy<;$S5nTRbS7CbSKt_3@2u=%yeY1Jd5h$Lyi+2EVG<} z7~FIELY4TC?QrInGX_|EC@!)=A0FKbAL{r%%!i=(P{(k57nrk4UDx0rfn~t2XK>_B zTwpMF>Kh-X1555SaAF7iMzKCLHa^saxcJaC;KQ}fUiz>CW#U5<@gc~$AqU&;KIdH! z6wciZr!&A@nA1IkQ*^-Z>2ok+LNCK1?JkeQZ2=CqI{b-(oOUP^4!sL;m{trH?(sR? z1wr9(k8p_Eyf@b7ea7ZA-{u1Wn+}A^Zr6Mt1p7hiN4MKzRiio?~A~aPm@er zl<4Q>)8qpAG{xs+`tel5sTIVfEoKBbO>^dw(|D9g9HyImS_~}tG*e`QZ4*Bq9GCbU zH$zb3vczya0W5vuC4>3n57H-=7Kqz2pQCB3<%VNThzrM+0gkUYUy$RyC=-q=3dLAe7_T~0Q}hMZO?6HdDe zaay|)obW)eTv=t^4ng5`#Bi7lES!!Syc$?I9W(e4u(b7Y)7ISwdTsqxfwum}=WN>g zgyH-jstf1u1DwBg`jGQTs1nZKnYNw@ENy+VP+RZkS?-GO!wLwB4_6GwPl09P^{>Hy z0*ep-36Ay=yoqckgQl%*CF5J^K^{k~qT`#Rwvl6BlnFu45o5 zoU%9_v$B=~%XpS8ILe`TEQegf;X|K8sQ`x(N@2M*=c>=4WFZb+IX>0$IphrXIMgy6 zS^`Vk);4%3u*9W~!A}88+tyWbUPv9{wQW5WdtPYZb2e?;&~V;@>e9B?1~@lT4avDW zR7u<#3+J5nz|yu&L^jxuyK$`P=KIhef)dAWhT{xiiDP$zHv-Ffp@-mTTzmQ)y)oBt zJPvW;cw2zut!hC6IOY!ZIQA|S*ZbMF_xhX~Ku|c{YdG8i%!O`!pNZR(z+CCpBURU! zIl%)yClj{^4X4*3E}TXMI6b7kCa1F~lej%xh|}vl56tj6#og_3nqfHA0G2q-G?*Ww zl=I~*!|^d-IbY6JvFFRVu^gW=9G5~|I6f2L__XSf2#&v^OgPRf#Ig8naD3J0n1E|u ziQB7&Q!QZO_?p4JfTb;8H+USda9(XV{|79b*A(EqE|&9p!}%463+FcioHwXf$+`G& zkMqVtoVoU=_xqgdLr^&HH=J$<7S10VJQ-Lxe`4@zVB!3!;av3|kMn^7oIj7{e9&+{ zi0Z=maDej{DyaxKUk_Ch=R<`!f4CBy&-tADK~OlKGn}Ra3+LYq-T*9|&l`LUSU6uW zoO_P&IR9RN^Tk-smkehc;==i_0O!k!U(m>T5M{#o&qACh;8dVfwKrbQfuL|s)iPdg z1{Tgm4L$)ZoYMqH`*|@PGlpkq8N=i6^*Ckg=op@<4@ewQCY-W#!7==6UVU8SbLs~{ z;dG7RFdbMpRX2D8u*9u~mhtiku;gb=9owgB`y5TbuVXk~g}B76et=_LT`Y{a-Eg1B zv0fpL_kIG79ej=hASfI=7)~>Rg=5D8aq8rAFymoo!{H5x3x}Hn9J=Vi@j1pn;Bj0coT53t)VFEc?qy@MCd9?& zD*>C!bgLxT?1?h5x!ki!d#%fHp)GeWY;N*x-tXJoWa6+8SmN@g!MlKYt4MD)aXACr zkgVSlY_8w8m@DW9U5^%s*>T^diP=}i<{{taiGa&rMfZe z0YzOIYiB@Q_!o1dV{Mw-n*76~JpSo!LH?_-jG(Lg{3}9GVp!eq>j=!dY`TWQPXO~a zo33f_I^cEWT+8L^b=F1T5xn-UZSa@Cybq@9xSOh*1z?C z7D+?*Hi(-w!3ORd^npJSEdDhX|AKSXhdImZ>if_Yg1pIxBNFq`z_N&av%xO`%XLXN zgZBaRKA-L`aWBD}e7c8}mEavdeT&%_zj~Us8IILA@vB#Xw&@+)Hn*9!xd0Qw{f}k{ z+$qW6P$ovAF{uj&XIm78~hzqAh z0Zt3tcgX1q$|Noe4X03VbRhG1d`a)|_yj&4&o-9F;}iIJyrBNL3!1>k()AsJ=<6bJ1R^ziFz!$LjKU z@geN-C*#y7todP}=6r5^!0RtRSu|55g_*o}FVJ)ZRvp2p^1|AWXy zoEHJZ@;ZWAkKJDAUg|JDk*p2|Odq0Y%lQZb>Pu|wRoFSH@Sw&Caf_t&Kb_#YXPz4^Q=nbx-qP(bZBr|`7)Qh+VQ4RU+;d6DS^*+ds7?G0_l_6*}eTPS71;m*2noBiSUVb69uh>hOF17|=n zzfm9YZSe%4M{gDSP%D3SAO2pap6aQ%2P)Y8^i-igACTQoPm85L!&fO2o0&rYUC2s+ z=0xewx&k997f>RtIJ+SIThVv$!8OTArky0=QY!Zz)_J*LjpKB9jmI|W2~0J&^MEvT zUBH24yVgtUa-wg&N7f|uhTjVK|2>D^U7p6?)X0GB;#d-GulHgceqS7(09kSPgQ&xM zYN1-nt0)nN_ZFnTmObc8&)EAA+q#wq*j|xe0H4E&Mj!E-FJ0&#i=ltK9TpaX9bqX( zFnr!eLKsOOi)UUKkh2_wK;OGJ{j0A-+Up$YdjUSriYCv0dw|@VK!td9LGnBe*5uFb zqcfn`wvDOu{$ufo2ZL?fv{fwMma=KA7+O<|X`9wEOgutTIa7MLYrlzlSu>zV?8Vsy z4er_w^%=gwjS%EQ8;-EG(~kk4rbMQ}&N$2J!ET-<7z9lGa(q6fV;8q4GlBuXhA|1N z`DQ9Z4aa93849`dPQbU-0=~wm%^%5X_+tSTy+XrFBcv{Q;ik?0MP2y?Y zLZ0qpTD0{kJnvIzYbd-A%+X)B6O2%Mqey$7f*D3`5DHnp`gS^s9mC;9Tj5lZRd&3W znH{7hf?=4zFx>8QXbM3Yzi;=WY0eOReUrw`J8g~mOHj=g=HtD-n*LBDN6lNA$ll4{Q6u`q`KD5ciD0JX+1|7~htqNn5i%{$;j~;(e@wJ;I4x%`%d|X))A9vSAk*?3PRpf? z)AF30?OxenOv`gPEtj(3v|Lq#rlO5-iho~erFJTrf0B{fEz={O#%Wul0qWhil?jIzPcGQrB^{NQ|km(G@v$J)N-H|`^7{p2U>v{fUc#U zLiGZd_cU@bt$<+vlTHlVW9325uq6WW?_F4s7qt%+#k zfFklusm)R;=tS?dfqW-gzcC=+QRLYZ>q17|B=XT9D9f5rgBK9I#`P~C27}Fv+oBf` z*CVf@7Z4*Ck(aHNYHZ6h`@IoS)daszYsm-xM-es!#1#W>XZn+9EYZeJqC*pK#CtP# z7ItGUCmd$HeB@*m^W%COVQ3RD*N)~aXFH`n!sc)6sjfD@B;CoDdHA<1=^|3`WR&m= zHu2F*lfMDO_#ItHg|XR=5nJ6wr*02e=_^jXg!!>Jb*DJx)MKUVuu0v4oqO<^5_Pxj zK03~m8g8iZ=f%_@Y!)K{)fgKukv?d=vd<0OrXV9Gl zjt0b2`>UyP3pUN6MNPKx5$5r>y9t#<$3#(C8(sZt3QhvVN0@)czBu393SGqhI*fIy zW|CH=BU~vF{S^_+$Yx}ypc-sCr?>;TirDl=uBq4904oEbd6mX2=Q>Jt#AZZK>}+NO zykWchkZ#|y0p2uHgD7=BHiJq06B|#ee`LFJ#-aUL^07!on+b2kKF8zBuvxShJ11xdh@Up!r0&z+-@}eC=D{alnv#?OTk01sLZ}gc&LQ z8Zb$IMM&T`fDy~7gnpvxV$*a7cFxkRPZN0&yD^e3jP)a-);32{38mS=UHhLHmlGfRmef2DaSpGelC?< zN19+ar9Q=GFHT6f3PhqMneU~GR5GRHP3&F>0`a8W>4KA$cy4G@h{ zK|^tEY$h$jPALbeP~36X`ng*IQkHyzT`H9X#8};%l?Gwchas!$V63j-M0r$_O8l~o z@Td&vxGx|hL`M}s9c{9zfNr$OssV=N=P^Y18o;o8ykAsR2TYQW_Y156m@FUf7g!T8 zRX*M?uohsNe7s*^ZNOsk@qU4I05jpgOaHM+?0}t4jPQ+kg#gJY_B%q#l25HmLfr&N z|5<4eHWuzfs9Ra7N1#&A*h;-jr8%s$2%G2GQHDDBnSwi==pfrqD)CEJLa#rd`I!QN z0|3p>6bKv$Xnv+Z;2^;GsHcMg&Ce7_=@3A?f02TvE0v4QcQ0e-0S9O0`|wjAJuRyi zOYVmhMp1e#iH$L-C6C{PO|w0MT`oX03N6`RM|Lq28q-tV{{e&VIoCq`;1rPg4x7vnsBh6<;s z!;ZV#_N0yofdd%SB^QqZ+U}olQQ$E^@Hs^tzhiTeL&UER#^2KcK4(NnY0Lm55NBD* zO#?-NzX0M*p{i`T2H1Rw!Cl#yU8#Q^_XijjsjH&nOOO|cm&<9mFDnhhrZihLMd=nQ zNx9F$-*GHShEy=0T3F6gJpMd3XP`=zP`aYZRPt4SaVVQLTz*|f`dSX4?ViHCOWLeB zV4Qm^GErbIAiD1lRCyj7`ASJ8g`QY0pt3@&%8FEpm;Vw_sRA~ekZ-CPD>V*OY7$$i zsj1Y7m3m`ymL_jtrM7`e*T+_BZz@e>r8(F<#1n0IE{}Fq?g77nyRnkxtfJH=Y%Xra z&K+FoyiK`{5mt%!?MCWTN*%}MMUK=XDK$bx$B+Ar)FnzeiI~-Yik)%1EO=D8zafee zMs?K|n;fuHv+3%TfTd}oBVXloR*I^e0jQ!XX9D6a ze^3@*W&vVkSwssfusO-ZUr3hoRkVBj4^o!p4K%{mb`Z0U%hOppLWHY-c=H zFlk>>(a!U-=*aIp%S1}@YznmTW=I7R^esT! z{a;UyAdMHm z%$0A>L7<|gCU_UVCH4cR$pT673aD1}W|UmQj?Sf#pzYAJI9kKAeGkOs&RGrGjvA{K zW309qo9`Q|7d2Mr85(=nH&(ZxvHq)Itc_>v2*hNWsm%$xlD{O<&NF6KM6Rc?bVL3I z>N9S&-%3m2iJrqZl7p4ZrPR{p`P9I-4C$@LQvM5kw^6gFt4~30`(~rYV2@i}D3(AB zmPQx|$O`~HgpH6g1Mu)Tz@e3^2y^dUwqYJ~^Df(PGKX!mVl?9CeD3%BL+%@V?)Mq) zI3|B~pSZSAJ;`Ho3Szvf(34`@|H+spnIcV+Jjn~k^k|bzt%)(DXakf;`%f$A_A+Fp zUg#O?3Nc=s=!N3;Xh2?^=tbi8OMtvM(Tk;YA0RJI^z($#WO*@$*%D!P0kX0r^Kz8g zOZ)-mqRl3I%$62p_5*sW-r_Oq1uQ01zp<1lm-8O{ULk-w^G+tiW2-00Xxs00{E?u7 zuh%qZMVsxJ&$hDN2xhWml&fTCpb4@^LX$LgRa-h$z>~_Dfa|6{g#{4g^(l^U@WEoJ zmq5*9O|}q|Ii~NllrR{B*V#>IaKCS`RUw1*aq7g+NO+;U2*H62W>=eukeNHxviPkh z{IEn!D7*XiO{?~>M->QVjkc&X&{x|EYF-{FM`9XwaIBgk_L~LN?A(h=qkJ_($!44! zi4DhSUyo^lF~M*=76`|q_FW9eV(5|hKkkKtgYiTekmoQ3X8H!-gP?rOb*4#yUx4Lf zuCoj-HVrdEoJ{m=oBchrDe%<(z;gr_mk)u?HD%wTOg;qql(c1WUd3Zcmi3Cuk1WlT z{pf=CeBUW^?tE69${qkU!hL~oH_iM%dmjBNGTj@bo)e#gsXdIN(3`$r6(A^QhBsrI z^)26|Y1S?FDQORe=AeXU74k!W;H&8fHC$J~5pn-PU@k1^eSW%nvw)9$y~dUO#+Aho z7gs(Fxbm@$i3>iah_XuvjsC=Qh4#x;;5oA``{1`ehqDmm(gcnOhx8e^AlwT4T>%a! zV>$d_I5dH{aQG>};YYhaIoyjf;c&|1K>LG*!}(3H@R!eFIt01Wfg|J)ZNh(IZC)`p zH~2QM25kOoKS!I#e4GCjYQn_Dun@r~<12HJy97b`wr<1`ze~$*%p{9 z7I>eH=e^8(fF(6kMZUOvZ?~xINBO2Z-au>GshGo&EPFnxN{q4`4p%1SGMsJX`zy*M zMwt$I1ye4zW-aZ0EgPK9O@Vjy&x_e>IXQ~b;@XPn29ps zP_Gb&Gn`+w^Es@5;84b>or%e(z!IbD4gM3D%R0Kf;aD1TE$PBH82ksYbm1Evjz7gE zo*ksDxO}p^qv$CvpXlywk?GLX~>4 zTg~u;*9h8+;vbI0oC;?7Jk6Lf+sUjJYkPeMRC>Ok?H7Z#mlU%7CY#|k-*#WvYo9%s@dLE{%JLCxCoLL{3Ym)i~iF@W?;EHbp-{^5hyNAeuppMhLOsU8)Hqr5o_{Im2r)q=bP~L zNSJ)pH~Cf}lO5Q%_xUD2hv3)Dv3;=({E=_bc(q@(g^6GTKg{4A_tpFjHC$fC5f1s$ z?((&-$8?u(OoNrh`jABAyFi1TP@_ow29#Zb4*hMR2CJNm!{>dQ10cx7Y8;W%%uHY| zxakW9zX8k@H~o77P8VZ2T{4`$g1B({E5PZpdY+t0{Lka`XCY2oI539sL3LQRGaEus zo^uOpUXgJql5=-N$6OO9>xT-2a|m0dq_5@(YV| zZf`&=>)SB{VxIOcZp}zgUN6s2lydqzdUhI`#OsQ>;Fx@zmpn~;N|m3pEH268h+J!S z2IiW)Zt8n#o@r|CGcaRt3(d!-gNYOR0F^rXY9IF1b~c{9;H&N8d*)q~-0bT$iPTLz z%iaTVp?{0eH{))1T_GN>{ev<|q#l}$6XfvlDsUL=amb%w4HXmK`RndNmHID3rH6gu z84E0ncN=gd_N4fTPt*)iqlKua^iff2SdP)X$+A84hzE}qYJht_h5DJk!I2P@Z{g3> zqu@>E0$|>)(6fX<=1ySVuF$g${vDY2EA*TK1fPl}__T>+`Gp>j`GH8z(=*BOK9or$ zpD7fS#C5Rns?Tu-1cl?PhT}$H;rN=*!E4RcJ_pm9YYd0uK8N)I4r}!Za^M6~IIJtg z;fBTFu&ZEez87rG_X|~ea3Crj@QGK2_Oh5V;z-PN`E#GA83qsPV%1|asrdt_^u4d9 zE7XYp-y8o&0gL}9O`EI&9>W?x7&H0(_LOhO^xK~#9{CfQpLH9UHEDBN7fV1m4nUKn z&Dla}ll>+PSojGABt1T4gWw1y%aydL3M`atS0)*KfE%?y4abe0TWQxDvJ55H^@bAD zujAYY^C^Yg8%Sv)H2sU{=mfXmP_nNl4wvvLZH6FkLg5H0MNbx`e3K?CN{h+t6TaR& z(QCp`#(jb|r@`)3Ck4>IK|Q;*fHKU^Ks3*X`g_ zG^2P&cUgfXye(>l!{E_NpbT4$Vlo}3;>nT!+T3$@NOv@qZ2^c(~wOy*^&%pJhO@ma&^ zG_Y{|pTVh1JdV!^F3y{ZdO?9~S`^E7vEkbYRfX@10lv?>^%B8%F3N=O3x)Xp$qe4? z^L-71GR4|#_#6c0JxcwS!B>EJlTvRH9PL$a$8vhda4Pqb$7y?j)4T4o~q866=0cy9W?k8V3~q_Ven;Qaz0dmK-GxP`z7G7{D&Pwy{rxH0A3g;$)L(QvBi)0O zhJGBl-HHD$JOi`ATF*Z zg`z7(MMBk!pllk-#MN*}202k^9{SDo9{QD^s^P#U5BY#Z_@S#H08hh_g!Ok-_o|v5(&^BAN z=`mjQIiig>&P4fKK*!~cGg1B&pmurVOiG^ybY0#!6F3hLpYfIbH(;<1rZQ@wt*=wh z1`Iw2Da+-pGI_3K0U(}={Q>gba7-;B!Nmdj7et=7#lEGxFB_!yJ6Uez+d$r$1NjXM=jwp`T9MBW=Q_%}ywN2duLs0Kz7tV>bt6>Y&Fb3% z)ptmB-ZT=wcS?2MGm`4NO!Zx;p3(x<_tCre1J(CRb>18j?>=C4JpTiE{`9pv!)G~9 z1>{eQe12>HY~=H*;VOyIYt84ES~$^UX(`Y8aN$z;v;u_wn<4)~Eyz!D@Jz(mfc$uo z&yU6g%10k6n+S+#Jca6OL0K(y@Vh$C2C6?N)w!l3PxUNdb$sO>@>ys=wZviL2IOB5 z`TWPcUNrI-Am5s~zlD!bZ3@V57WsU7ZyEWrxFRZv23PM>vw66eMWvfOt?fcy_4&kIy({U43|ddROs3aCFQ|64%*g2?Cd z|J}%c2l{wC{_Zf8%wEx)3n`2UG;iUjz)8zXTXCe;F`f{!c*jq^hv_3(!2ND)4W>usa4J zX=TImKY$VUGR8H5R{)c(&`l`vUM=W_-Pl(P2C&|J*pwcFop6FKf|Qar76YLKQa?M+ z3q~BE?u7GTKz5B`e5Y(Tuo;xvNE1gAOs`;FK%!_}p#l7N@I@y&IP z3?f_}!U01S00V|90_rIDN`N7E0T?f$*2;kR`4_rXWCafVng2Uu8(F*^o7Ei6>LuV@ zRx4n%4k}tv!gT?W-KSaUA~sJWuT}E|JoMNk!JUc$L;}~0l`Mz9Nu`QoGZUGrZb(3P zY8#NcUQ{>so2ES=p4@EBN}aK(g?_1eFbG`&m2Q?w>$ZC_>BdU(rqTq~>kf!t`^b0m zO7K-lw=w!;hFu!{*N!a1e2}@)mFqu6~=WsVA z|Ea#*s!*Ha-pn^^nAdg^1MsQ3aFO47$ujmqf3!ZkYobf&<#D3n;|A7 z8w!mRfI_m7!1(1J$;JX}0t!jKk?xUfBD->5tEueDZLMaq%RZ=^GgQg!f4T*CqYkx< zaj2Cz)DtGep|(+nT7#68dIBZlP@94dt;fi&2Y3$s12J)E04YTBUcs}r<4`_O;EjOn zsd|vW`vKWg^L&Z_t`w? zp6c<f@WDvUY+3c+at9|shI(*>>t6p}LtliA7jOey&tCBk{uA878#Awc#_J)00A z_ufdKD=zSfCb`A*6n#KSnH7n7D}hW7JqDlkeClgsD=Tf{5%YElR6y|Wx`i0QCxhMka%>kDqL*IEM zItP3)5fW>0@`OK@ji1_8HSN(yRV$##c-qimRowl39u_lkNagY*%-{XsV5>^-OrLhQ zhf3l^ANmmb88cPNw&OUbh!ey)pqWT1&H>FtDqfetGkUgKiz5q%f=4;E6}#pWDe+y^DQr*)HA=1-EY?)BiqvTBVZu8M}AqpcN*>ALs#boxo?n7U7@WX9)WheqJFl z{JLe`@c{u{NURHZ%bS3?^da#!z_T3OOGxQNz(Fi6$x^Ebb?p^>Z{*=<6+28cxFRil zg*MZ*?2`zxo%k9mGEBOb==lur>m))JC;f*?wNWXeR>K_xRyX5ujLzKf&+fAkt)Oq> zbHjGvXbB2;HAiW7qL{w%Ssd*lN8`<}3dgYltsceL;Mb+0)NAm*kbLFQXoFWO0*R8n zDDXe^2t2jasE#nH{zb=h)Ku&%HHi^Wzo6;HI%)|HjdRrJJMiflN1f}5RB+U52+u@E zwFJ9Kj`{?IC*uWUoEgVzdBJgy8hMFp*{3`7O3enreCX|=8jDrmJIE)oSwF_|TPB8XTuEpzZ)C3OUPveZ4Ok*&7kc#awe z;o|BM94VzfM(VlhD{T9!p|Ek)w%kP^XWK>W&}IfLbOwb^=wA?bLNnSTXrb`*1Re8LVr09zOB z(0K@6vO{~|~;rfb&1#h z39}*3x2VUq!RU z#V4Cyl#<#OCCMLQFp0M#d<%Er929pIHT(La!cVD#akMDokUElktdN$F0#E*Dhp=y_ zzcikpANCV#VnD;Ih}gB#n!z_U*XFaCp3K~+%+owl;P0VNYwFcXSA4Ef!#aUB2DmXu7p8#6BJ7^G&CK#}9v zo<(66bK`9aoTfnP8Z0)80MmrEf1-xX6kwiYU4n)U6tJ?%f13EvADv%~u#L+(O`)S^ zoE({f|B}e1CFE|UsZn-HSf~tulu$_`mD^A@h2>-Ha9Eg(^_gI;9R=(c!*Ov+Z3C<%}R3_ z-2?V&06SfJ*a61YO5VQ$-N4S>i4?@i)L6GmNZk^S)Qwbt9EWRrZ|b+GU-B1h9>f@9 zm$yst{}emdE=}X?$#zLD!IoxAlek?9P?tW6p+hpWG+=z`1s~vrQkIqh43{P!va!no zMoQB!fq8(*R%#EZE!rEKl7q0R(HMd`b}9ZJZx^#mGmFGjCd4hNEvB*nwUrI>@@MyX zhsk@;kMMRxOx!$Zvnk6sb4|-0vgu^f8`$`N7|p_&cAwpHs4PdhFJ+{oOx{n>BPC^h zal20hXSlpd7+^|ZCy8T~|EJDk(QPnFWWch@QBiyqw*T}wVAsR2%b5TPA=g9Nul!12 zp37BFNyG|Rz_3&=X)UYb9CS0gmTekWnhuCQS*iRzhobbS;VBv`4!3XT@NO@;55VgFXyM@ij5KqK_Cs1Bc~ zF_np2@*y0k$!^m?r;fpX<&Mm$Dx_P58Bv9zR_c5dRjtO1x@IYMZL4~%8pxfv+!UPi z)E3Wi+jzbu;Q1E3Rs{5N}G>Fi{IgOwyNkD@jJ*Q zeG%Hg+g(1MyHsiA-piJs9ozS2OW!MD?UW*E%yP_=Es`s(IgVfOa^*cgS1w4dyeviT=j=ExMXKKw3fp^b=gkHv^$%V>3tBYje~Vr@VA|a zVe$zavTEFp1M~5(CZ{y=XUpLIj|@#otKgI`g6~6cczp|pYFY1L8$N&yj(mmxlGZ_8 zzT~^Q7>;klp<335*oF^t^F1~=e2)A7V3XAm^y5%DYbtiqxN{CWrLa?@zsD=q z)q=nZ99qxAMI4L<-CD3>2dP-`366%-{m#W61SE}?TZQM3)83%t4Xmflpj_57YT11* zq@q!-1zWo)to@$om!NJdv1Vf+v5>XqFY8W-MkvaW#{a|Zt!xnEn1jD;rb@M{99kxm z!);!I*-hNzT>&64uM_#X)J*fez2=Ccg(c?6o-2Q0={QD7u?;XBfBepl2uksrCw;qWcIOLR7iopWP=p1@gmv3sa~O;lvTK@Ro03xavd; z-}n|rL@gLisB>SyLdCgECj91^oU4s{^F8q?V+re-%0bf zqb$Fh*1=N_{LDhuL>!gw+8RowyN<^WyK7PloX`9}b+dLc@Z7{h(JW7esPyAq*vaq5 z`E{fp_h3KHTLFRCo?P}Mnl85*)1L!UUh-z+ab5%La3spR13UCO?@8>4<7=>U8b`7| z#1121)m@i?IDe;XujhWJWJ(msf~S!#>a5=oKcb@at+OJSv_Imrh15Ak9}ml+w2wc< zk)5Xf(q-nhlhY?NbKl7+a=NtOPBvho&PFg-cFd;H2du(G3}yb1SfzxGYIhzSCbI&{)|! z5$Cjnh;*gOaxTsp4lKkgYa#v&un?~z=j5D&z;a%$BIjkE@ML09O(qsrSq5k1HOvGi zbl%?(XA0NndDrvmOEZ0xG(IY` zu_T^a{XcaSzx45co?^oVPOxR5@&7!*hP~6PRnAvx_xYK=T<1luA8MZG^y@m)0@Hz8 z^%5*Cu)*hgorzu`CJlHp#n<`GpneR3ua1!;az+Vn^I}rnm6+59w%15G2c_~ofu(6{ zxtwj5zaLlvQPZWudUG*Gs$SSbZ{jq^|5FWI%!j&}iB3$k8o9~;4|9?CVCVk~Rdg;= z`%lg(PC$@hQ_T#)wC$KwVIHiSx;)nvxaMl-ra^&RS;e+t$Nv{r_5Xhu#a`cac9VTu z!83dUXW!5J?a>BwB}bok*&gT;l9A85yhtcN1*I}dzv%MHwgJ}|QZN%!FS`k{43#w; z5@vZJl>>PyuV_NreT>b8%D*kVUjh=>0hUH$5|3V39gm`r!Zb=Et8cvA| zPM#IF2D#=VZN5gu1uUm#`~=9DPktwurLRPD0aZj6arOVgvWOd&Mcl9~;)Z1rH!O>| zVY7%EHjB7nvxpl$*OQC5VY7%EZizIQg||82LzKjGXzcB~zK8$;OG|C)#QP?!boP254r187w2IN>`7$&L;$nj<@} z>i}QpixSNWmP;ZtKwmA zRXo16gUd0vtnZ5F$|@Ia<0?t!a?v(4A*Cgby6v!u?~0AN6BOSEyXH<%f?2msWsvx1 z_qiU8fv>jNO))mvMVD^(W>a&W!>CmB3t(A*o9nRCq+!w_gTvF#w-!m9_Ww(<8#<8Ojl zaxC@$T1%C;O?)$C)?8jBGRPVw93?X(#^vq*kF)oHlA_w$KbyrWPiA-W*PXY#J zhMXh_0s=}7B1jMcMS=>7pky�re_oL`94QQ81&RDCUd_6Dnp@OkB9W@7t%Ur-%9P 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�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%0DeOpAc6QjGRkYHsYnOanmn!YuGMD{>yrq7z@ z-Ujg^QX(EX?RiZ=3f7>U6JhikCPYhNu&dmtxfH;R#MbjvS8Q z+*6PSJw^zYPQ4d7ybHeqKT_b>Kq6;5$=wcj>p<8*&v%f=HfHZ|%(m@D=!hl*$&N=b zYS>Fbz`ww6GG~fLp7^w81blip%8A4(33Cp%q`+bAiDCRB(JFpdO2j`VIdBisgv;!# zsEjPk+i^1hIdnIYf*F!qrewR5021J7W&{*!rFngvcAVkdL@cZP=W#OXj2JIRdC1BH zej5^;7IlmnHmVMjWq14-_hMbi^;>M~xw2q?N#dZ3CR+kYeDG`Ce|>?NEh#6vwQARa zg!d`^-A<)!8#HSvmuxn^rse5Z4cKNtIr0sRRg{igdy>rqa*e8djT~#>;=SBL8EF{Iju{p*h}&;-l6yg4WjY8hl0g zvO82q$#7f8#_v(bDz}bIjA<{jk}xu@2VC@F8M(XOQbFpq4sP2JAyYw7hpJ&S$v z^c-_R$hZPEh*Sf%Z~kTv`sQaS2fD{`=}o6uOg-rho*#`Wec74M7Av_SK-Ur^JSrtHcQD{NOt0^ zmCipo?~k;2q5MqbErkP)YZ|r`cH{~FQ(Prh>0-r&t5xQ$o%m z{z3%~U|*vXTDlYsurjC0>s%T*s^k)zo1$)K2Jaseze#$Wm7+OajxoL#Py;-|SBv+{ zVBt&iy%*;a^-7KgzGM1Y1G3X74@9RLy4|VVCOOq4_+@c;8V9Qh^W6e)vkYUWwxEs= z;rx~}R7a5v1bNT2gZ=9T3DC1}Y|5#!=>LM&h}DT)D0Yl#=yWh==UB4Mrw~OeaBqib zD~h<>1IlOBq+H`dU178w{%iqtI$wi;R<+Jw)f?K=cI18;c|_Dt4&Ib=zb*E+GnT&D$oW_Lg^t7q7b zL4DtMbC_MJEpAO!(8Vff^Mbm}+P;d0UNmZiZ~(&aeL^mJ&i zsxyKAnVyU2Y`%Vy)q6`SauRg1Wj;C^Q>9A2rUI@~0k4Le)T^ZJ396=pG{>*Ft*hIv z|GB!ZFpPwa3t-H(wyrN}I9w-EHndKPx}-=cx1_k;`}>oE|8bY?FyId&GCiXmc zb1To2KNh5N%OS&?yX|*TH8v6z5w^e{b^b_{B=Q<`4d8dUlz3{c@=x=$FO11jXpao zZQX}yXO(roG}1LW(PZ?oVjr>oktWz9631O?AIE|`C$cYpTRxX--jr@u>mCD6vBiz~PZU?-`(aC$+mmuTbDCOQR#rBw%sMmq zl1)xdB+-K6&fiEiyI~4!v@+6B#Q%u9yMNX;G!nQpPwi8o$@Z`S)mg^FH`?o^9=_3` zK^xt(D21PL!YiAMb<6H``Yd;KVfIY`>|Oc~!-cNZFz?O4E^~?};CPoIjDiXY@e%9Q zge-@2DkK+2%)cD;Y2~X&O$3SaLygzXk(Elk*fllhsS-qm7L!tGq0!=xyoWocE5qb` zHw%s|5Lq(sdO1S>zeYf>S)d|o)c5zJ2@d z+}Dn%`*u?As{cQO=ZsgJzTaC{q5p?41TFBZ51pwTxc6qy){L{4UmYQ+f zX}i!e&UN2XXc1KBe4Hq%-|di-n4pUakOobSw^Pe`6_a-CAsGC3q@IjPsf04l8BzCa zx3FV8)&F(?=TzXKeFse&rdLj%IeWef9BGq>$cpTX$m%X-QOytsD@W{4%rTy_U+F!S zUZQl;Ft}t!`n6}>rR0%H9$>XnG4qQmvdjKaP`p`VP2g-H5-ZHy9pdAMps}(hSBOJ_ zjSYj1^1<+Y6OS3p07cRPVX%GEB(cQ6>ujjBM^I?F#mP%Fh@QZ>K$z~z0+lw^1=U+P zfIjVkVm=YV$_3mMCny%78v@|&tcPm_xZuVs;F1SCo^c3)zLo;L(k=Eauh`48K)Iu8 zi$xW!DqNHW!1YoKfS>I+J1LW7jTJ7B%b^}=XS(Haf61cr-+OTTgPMv9oq*Ch^-LZx z9!a6Q$i_2?Ybbub8ZwMV+#BvAY@hU$B!1yu)NODe3KtXNQN*+Zb`TTsg1 z!uAu$OFYPbWI_HeRsJRyX`=_qmOq4ad8&Lp(gW6OJt!5$XlB%aERR1+u<=GdwsmpsU8vmjsgA(y+5=&>wLjQK;w zzvV+->O#KjL0*&v`JNBC)P+R1X>sBiHiYvN3#lr3noId+5ufnX5(51?1uAE+FzPgR z8UpkSw*8lE$^hl%HVgWB7U-ENQ2C_=^gIvr!z|GAQ=nU1(2G4#{JDe( zxYUBuPtc1AX`=^;OP3Jjav$<#7xFp}azhs6^*-eDF67M~R)G@$2tpxd%Qm!?49b3rfi zK;O**y~Hmcm6MQO=|R4k1-Z{y*Z@3NI{{f~G9!hkU&Ex_BS=An(qCeA0)!-GzMGgS;gR@>vTh{@>sh zzup7Y)tJnt+F(I7PhR7KzUqNqwIDoCy`I8)g$w$&2YOi+)^}2%7rCJCd7${w3=!}_ z3iMnT^kWb7>@3hvEhqwLvCDvOJjnVi$nSi}Z?TJ`tDKJGVs+K8{3|)Bb;4q7>1?O| z)P+3PgWQ${xzs|6sqf)Q8l)F_pzmgZUXlWR+XcPC1KpGb+Gs(=|5sg59B*4vUp^_! z|7$I%*RR&Q&^LL|&t@UM#X`fZzqu8=(}R363-azD-bY=$4|vcIWH@c87dXT@*f_%w`yxN6)-Gf|~1^K3h6k{)QtMrZs zdTAD8H~UC0aFK5HAeUw#-DV-x2hMVle&&Imkp=pNkF>!>D&N`af~RFco`z3rcVhn< zgGxHqA}n-k3;KEsO_zE8 zL_2D3_8_0hg1j|F@?Tw~cY2^tW`W+F0)5m4mF=i%hxKSk6mWmjW5ff1Vw3o=2YO!? z=p!l6dt9tfc%XM>fv!n`-UcW;pQi!U&gbSV&}S`ZYCrk=EPxFb01jOPhd}kZP!YT` z3v!c%OtsYS+@jz2;Fo5>FXn2X+X+79P9UhyM%O0P+i)SCDYDPApeZWf4FZhQw)bKW zpu4*(dwafT-KLRQEr2U5fY&WvcgtJhmA4^?jy+ez{N6$vT_5rK-8whR?Ov8OSy}G1 zS)l$gj6JE$Di89ZAmkE}@_>cZh`Jx7*mbT3RNZ4$5Y*D~s0G!CzS{+T(gVG7eppog zHHGz77xWnq^yVzA&!s^B;DWyBf&M-V^raL5SG%CEd7#U)2zbMS!hp+R0JVC@gS<2g z>1H4D0vB?t2e~v0a+?o%mdn)7fm9D!lm+>fg%nfkAQb+eim|Lfzx8issw*rP|LeJ# zDHV#>SH74R=qr}%XIKE?`WQoBs&X#4)Ii&^K$m*BJax?GU||;5C7Ff2l?8BRCcvv% z0N13bk&QAnyWXqWx*+H()$B$Ks_ycPOZcrG;cMrGyUXn<&?jB2cY9d>k_Eal1^TcH z`hW-eKo;mj7Bn?Z?+H@4UKBp=0eHOsQxIUQ0G_e{;{DBT6`u2|@W&viW$n5Y=#NDRwi}W~n^T|{xS$_+pyy|SerQ4IglD@3 z+@E=nXJkQs;X^h6NgIC$qp5~GeAs;-vjE3D@MU5rt@KtDSn+{R~G(CMB8 zlgobk@&$maV($epmxu=!S#U7DgS9=7mwS+J1tBfxuT0VKnoGmg9t|6_XwbFbfWGqi zEL=DFrLJ{L)$Ie(_tz}QyHch8#Vu7=h*k54gQbpARqjuLKH!2r?16GgA=I35pYQVX zZkIt%dIa2=1^QPDnwsElb>Uy|;Qx?C@QW$by68Ytz2Z@^Jc#;85&XIZRky#w1=XVg zs@tVOtkVSg4-1+SUl#-c8U*lx1t9Oaa1{HLPdoz8Iw36RK1&g}$Sw72uhhCAfwsxN zOM!mNEo17^z`3Xh{E{~-Qj_&^mA{y4%WCqT?p*wde*;-v_#OeAV*wpjqQL0T== z8!c#x+fQc!+?EOOWEQ~PnE;Pu0o<1f@IV&8!qOXD)v z=61Ut_1Yu=WZlb#G`@2-=Wt?F9|ndl97oDZz8mSo@xaq<2nW3{Wd zpMvg7dMf^>?sFDc>!p2R^j8AMAbRHlQ#C9&(`gB%;XSq4#8FDbEh>qFZO($&ZuYlR zY!4^@8mH|9$EoL)Q5`XJn5?7e4LUQv4xeWm@Oj3sJNo31yjIBcY(fw2aYU!flUkD7 z^$C6x-mf>A33{oZ-vPQ^{3BeX=pOVnI+NZO;TBb&yaCKCr!?Bjs1d$w(RDJ_{C1%$ zMt%!xb0X(7+x>k1jz6OJ!GG4X^Ua^=HRoh#J3gg;OgB-@6>l$0Z})ImCM;j8Wrxda zPG_fmrL|lc>sqdmSTveMx@uoNT6dq!G`R}QHpej&kmh%%A3+@{Kt0;Xb3yra?1KO~ z)M>YbeK@dFt%VBsrU`6|1zX~xDfg3>DoLkO?d1UGbBv>PB2|PnMY|b8w6g-^?il#t z3lG2Y{8~H1<=Ao?_Nq9G$FGX4abMvKArB!(X|7BLrQR^A8{%U*JXNLzcn|>NJ$yr^ zt)k;8o9Q|VRk0JDT#DdIucors>|JKgP5e?u9YBVEG?U?MH^Y}U!=iMCQEsmGmkZz`^v><#)2X>wnId_$(r&V8kavjQV8;r*>kye=LEfPd`j{*g zu^F>tof>_rERWhOGpLX7O?2@s<;p@%* zM=Gm?e=)8L%(3#wl*dfOy=Ui8ERxwzbcv0jJ0)Vf*EG1Zld{?If_r0%G=<>Qv&J~% zsORE=u~%EqW$xI9`%e41p>q6I1>r=P9I{*o9xJHRfFBIaaQ$s|Ri0N;dBnO<9&RScFIphaz=O#1p>Cq+!HK?H z|4^&(J^?4DSOD{wg|q8aYpbX?EOwSbfGlP2yY4cWwvg$RM=4~`UnfCyrf+#KY8DAI zhWT;xE~&bk!MxAC1zq>O2gCl|s1`4E9FcG)yB@^a4w367*{F7lEp#81(3SPaIpo4# z^9t-&-MS8jmol zVyS+xeZY-7(1&5)31W!G)HTh*Jr0KG$3^sAq@sxz(YL!qpUW*}5uHprylp=zMKrE; zM0EGqF}h=;JDZSukkqkR)DA4iM;BDdCQpW$9k1mcc*5DEj@yh4PC+UA1GlV?IMVCA zjEy%zuzf|n$|(BKq`ZAmuddXqXo%B?2jnQaH}%>FJb1Y|%xQlt$hmdF(F~iXCGx!X zIrjh5lykatO*#(Cdz=4tt2-*kxcICGZ== zgYI%Xqo^2H0%YR5f1^`sx~_B%(X+Bbun$ms%7+lRa$k|5I-ZjP zj&}3SYD?otCwiPSkZaexVJrWPWw;sR?dF0i_jOB+`S@7sCrQvQKHmC(+Zoq467)LF zZqES?SC(|YJ7Sg*!Ib`mK?W~&U1t6r!H`>4@3h5AO6Sxzxh~DTi9%k_@ZBkXtdlzw z_bMFvF^?_fD9=HDw3S059R{NxyqLJQdVI9+VaOMW6WUUeq(4w zDkM*=pWvy50(FMz@ZKL=pfs08CP8~K$6W;oIT8DJ&bmN*IUZy1FFDApgBE%C%VSDy z!_7u5B;*UA8q{etvxVj+duoO!Gr$Y+ai_UQCye+vxznj~b{4u-9=bSZMWE}1zXj0c z3f&ARHrR=to#ny zYKqz-(98NgPA8^WKD3x3D#D*VWT z-ke3B&NZJ<0Dh-*NvMAI~KQV*5Lxdjp1d9pcE_CB~FCa++DAm3yOY7~FbB zr>0YElW~n3w~WKM($fuD0`h{`@>0u?ZS(EE_;VIUTI}p>*KWnJakxz#W+8K9r#oGA zkvM7U=(SzwwZJ-qosOUzY(b*2X`%AYJC40u zoVI`XPI20#IdjU!j6JX{wOy>ni?-b@Zg(xCETNxK*Wvb>6XgYArs&90G40>~s?sUD&H-l0t z9&%uFENLt^aqiS5mE&g7b1U+~#P46xWb#+K%V{O`98Z$0T@hPt-PzoIG3A5*>HROX z%-c?XsgT{JI>&aJgxm3;mnIamA*^k8s z3$a3$Nx6;oF?IPU%f)*{zo9H8xGR9KF0g+j?B~q$9H9#PMq$?xu!q-m8asg;m7 zfrRrodQ0uR6kDL!OTw{5id|-7-O9X#D>aI{UJlgO`=^A;KTwc=bRn?~;^>dc+<|}P zj@pSQ&Ev!zg1(V;u2&fyxKf!1^3s)Q!Ur4Jaq|WwzLMqS(KbV^us_3W))uD91@R3h zdUL4CZcys0fz&I5sTV5si$H39Fm;7ew**p`22<})>LQxoFZWa52uS6wR_XL#Tw;}A~%_f?~f#|LWku~MJGsd9TsE?hv$n|gE{cWWyNmBB)-{K}m% z<=Bt&Sh!=}K=;xvb{zT&=cjbKOKs#kfyfD56||8b5ZT^^91G-GHWF9MENyVMtYPq_ z{NXQ}>-w9t{GWFy^sMBKaTBjUeVLQn07=VVG;P;it-Xa)YM+tEakBe%wRrQf&0%ZL zJ1Nxf;b86COa$Lefz-8L>U_ICxb=U!eEzYw^{wMe&Gj`rE8^|Ko0qmNt~ja_H3r_ zlK2E6`hW#PiQdFu_lVYc!m(-4qlHEaUK1Zzla4-C(SIVkleTK(m#0(el=6s8!Ln%l zBAZfY&pAG!-0Z_||ManidC{Myw)X~$7(92L|BP9ge?PP3)&N)+Am`4jm{vJ;(&YK> zTCI6_pNJi}R2cyEX>gZ!mg%_dYwx{|VIL7eseYhcto@Ydr`RqalI#N=2YR|oEw(Nd zNAYrOu+kX)BCjUncYL&jSFXMa5LAb_D75#tC`NM}olE>GE}Uc(~JJbLX)m=ySwcCsdjJ{7ZV0v^4r(gM-Fg+#p6@bcaUQg?m$gfA*umr zjQkGaAuEF#)SC&k$flU++aQKr_cQkHoYgZVwy{jL^U~o}oN4#Ki%&UPTy1U3>;nqt z>$n0Z%-W-|?Hl9_J{)b(f9Q>npV;l7lu(-2o}q@F8Z0*S7dyM^f?jAiddLTT0(YJ` z;5U=e+>~3y%Piv8Ic(?41b$JPGpm;IV>Pqv&&s^Fu{GGhy@>6xFmlz8qFH6*|Hav- zoDx*gsZI~QGK1q2647s8ssjbwL~#q#^7Ch6srkLjzbi>}_uy|b{&EdEGFZs7CyTgh z?j54qcOdpVoDQ&pAvK!8*4{dy9*u@idE}6~`5s{2)4lce(7}@b4_f}e;PUDwx!J<; zin$3tSD{~OIKftr*DU0MMcOykZ4g#!dg@V>gk!5<+0@D4&tJR{&*(bx!%SKD<3g-)b~1h`t|S2>HbZhgRk+Ztb3G>tJDVBXobKgL zH{M7yoszMfy*t+B4D2YfzQM6jeU8YA#!$8K0C~D-J8UmQL5To#daZ{a)*CGk4ei3$ z2+Y9wOS9U3$g2gNP4Bv_q!DmPoGPWRoy{a3LdnQ0=dX$( zLDq@Sx(f!Ep3FgGgC~&mLq3+&HIOQQ81^`q`V zoZ3l~k85oTYGHm0%q=}R$D54mte@$l&WNK6Gl@NgLebbBG4rdyoO0oj45)!pHAsJ} z2Bl-(@AR-I0C+}p=X3R$I|MmWki#sb&KMEXxzfCrj6@rsaE7aun8`GE&{)hJEMs{i z11e@HHekkNd&63*ieNnjYdDHo-Zb z!UoE}bva(s6V6d~R@Vyz`<6RXi0C3U?Kmt7oZZFWTv`zhF)Mqr=TCO^x30OZM@yb> zaLlly)`tK3R%@vjgEQT$MvkwkJR$8{$ePBatsfEASz80-SK5pc-#J3yYHsXTD<*Hg zT=G`YEjrO?PRam0X>x7N%o%eii>913Ysy?aY`cCsG^bb}P{lu|PYABqLHWpWw zj+{&)IOxF#C*7CTRBY2Pz>c*KE{dMT{_R%b-M`W7>*L+u!;4o_;f*tUnOQlzGAudN z$835iClF)h-9trSBo>j2t*Dt#F%KZttVTxVocc@3krNKpyUY5rN-O#90L1Ff1hPUP ze+8r?+BO|Q-G<}mLAY#$qb!aYj#{8iYp@!e@8mLHdZ7F1ibyu2_-)zG8BC4U-nOs;b}5V5FZ5zi8S zo$?e=*VD0COx1OYvgqu&oV^XFOtEE7#|E2Mg)z-^ygC@_!TW^tl5H#vi?qHFFYGLE z0Rpl!>=OB%Iox9^sbdZ9iSiWKC4lkpnA4?PFMtdNxQpgSP`;G~dqpIo)1l<%kpcGzr6KoRvC^e=M5# zaA>*te^sWKL;TwmwKke4d%-p(9xT)>-{Qhjr*i708z4&Vwy#z9enne_s~6_5P=MYy z+3D~Q3@g#)EZRjeOS#`I#Qqq|oA=lkb!o7ty;{)oMsk8zFZdwC01o&P&I}GBblkOA zK%{ilxne>%8fedfetWL?H5FdukM8?f`}s3+su|@HA}kc{QMOA+=*?-LPJ}lFv#nO@ zmCa;ZqlBy6gco3nrs@~TsUCVVf-fi1ogUWma`At~{MCYHrn$MI#p8|81O%L&{p5EF z2LGqEE?KlxOq(=+=Gcsn^Cfxxp1X+qodzA&HlcmpWfoB<5OGVu zJ+8yt+C6;GLUD>py9S_6DvfTu!7b%_w#Yf-Qd_n4?7(rppRAH;h;_w}haM~|?VIQR zRQ4P|ynvo$hK@2rbJ6)}hG=So_1w`EB?}{J%-IO?od>b}UpA(ZWWAYFRezi{ANdPp zFx^>4lHVcOtmoy_%2vmV4CQ)v=bghFUVEWZv?s)(EK=jyIC@FgP3xiJWR{m4`#gRrapr6czH$##9qNM zzhp||oc3xkl}LD{XnxfeTA$aK6NH89TqQ+LGSvcC!rV8-TzESbOHcA=@!UQq}WUUYY!euICgP!I|zP!ghw0tZX|vA?)GpR<<3`@ms8D1AX#+ z)Ca$n`T)y_Ce%|N+R!0oX54D*)t`RCFy*}gutXPC`5^qe5h4ii1+Vy69^6;M2h$U?M^&s6S9@XR!nWJG6*K@+FM4>_OUPmml z=#5;)bt6Z*)|Ncgv7g|KwR^mt!_^b2c+3_@)6M@<)HK8d&Sswh=!gB*dHE9hMcu1c z!L9)#AA#RG!U=`7s(ec@mjm-wUX}No^nMfkRtX^VYvWbp3-!X(mgA*g)@xlzFW6LI zCLjVDWsUcyjnkr0K(;Wx-NJ-Kh5E?-!ZLm{3dtna&lmp9CN8_&V5OQV4~C@ZO)2A5|jcT`MepiO#lU~^WZ7JUmjrZ~Tzll)!t>I1P z)MiZkU+P-P>6~B`n^Sdrqs(!JIK_v;ar-EQcG8Q8FGL&_^N@%Zgn6;!P=KR|zGPrBJcv0Ek|4?m#Fy2<7Kb09BvTdys%5%+Iw3Z}#BNpZqWsKb`x__4xf zmVrScK$mPT=X(IpCBjE)b%{a*bccl(-Ju=b5jE|iQ{J5@;(W+}b#juYCue$a;MJK% z!&#n-5#_AZ{C(A+G!zGzg=_-M(d1d?o)(?q--bzX>}*a*isOgMfS4Q0-3|kj&YIrj zJSJbR>-e&2ibhUmx?!)%6E3Ar$BAg8(koNTvG=g?DaN{;Ymm;6iJ`<7>@h@$|aQQoi>czYTgo&~hRz-&)q_}E9pQCHT8=3y^WUd21!+)nTCy!H}7f#}3 zC8DzB)lHc0cXcgF{zhf{o7({!M9YGv9X8^GwD!Zn8)pn=Wt+qhr>m4=lq3*p_COo= zS+?|MO5WU@_;fucucw#v?$R#U{Ub-M|MGlFsRf87e;{>@@io;Jl;R2_;~y(ji{o-I7#Me zKp|#~7U@euCXdSbJ^9wH=;{8 z`?07dNt9>dL}%QWO{4BofM}D8=o$~v%h`zj;34V@aeW}JLxS8?@F}*79n5XHsNda8 z_w)GxdXe_H`+$xn%za_NW4QrGAy&3-wX(Id-ef7nibIE+{WMN1V8VD5TVO-t*0X~u zq&U0w`W9>3B2%X*EJI|{W4bG*+RgGg`vS6@lgbht*LF0!dD^R!&xN-4$pCFv%6j)r z;YPTpGB-Q!B@)`Q>5h8WPgA|CkxlN1c#?B^dW(1WDwTLKRQ^>M<~41yQ~9Wl zOPUWCCe07md2oK%htK$R@dxR$_=8pU;VqtQdQz-www!GE6#YKD|7*)>1wKeeOMDUC zK{z|1Wb)6_n^ixrTxQe5G6Av)N>x||*`-%z0jUbMkiNbAsdO=>8T>ZgZk+vc)kD-l zTKQe+r6)qa+spT#O;2uzt_=J+M`z3s%UcsESCbfLHY?EiZh{G_-03yd=~}G`Bv#JB z0@of2^`yP@QG9EOvJQRI4MZUFxLlOE$&_2U*6`iu;?sNfDSQus@BU1DdM4XS_l~lx zVR6U2XMJX*61pg1vE4{U%oO%qre{LGz4&#UDl^4yj(X9`lQkQf-k2EGv#3YrCuQMR zVEQE3M&q)o$8Iq}vqkJrqRR3rl`6}cO(3i%q-nUP0`)l$f8=w6#f}c98P_JE+*`{l z_j7{6rkKTiVbo@(QU(0~9si<{=z_w6c9CLLlz&G@);9I$cXVOixzUE4NQol(cPjA* z!i~{|#pgyB=Pbe*b%Qgy@HH{0Ib7q2U!AkI=_r1ETq;BHJDJ9lAJ>+;T6{&S z>*=PUjV()gmy+hvo>5je(WvxD!P=&N{P=Y)^5Xjw|FMnVFN!ZMRA>Sch3~Ua*#EynY zp%y;fR)SSpX?wRX z`>+!UpX$Rda@*C1B|4JF|1E{rl2>gJrbG1_#j(gH(y!w?gO47(($6ef+w>d4!lCeH z!oB(S`-zVyufy7=9<)map3#Lp{dBTK-2RoS@94skG~Rw4*EV(W@zQ7f@{}(Ti1+c0 z0zSmQ(SBZcAJm#T%L_CWDo}N_V@83FZ_HxI<0M*ZR0b>Nr|8DY|;<(^9AT! zOT2jO_jwL}v)Z0k@zVi~l<;d)7qw`**KO9Cd?1<=s>+F&s^!YgV%RPWy7jXW&yjl=h~(?-+r42r{eWbFsh3m z-h=NzUrzN&rEdU_^7`;^6W+TSct5>=ueD9%vg%^x` z^JUwP#7p-2JP5)Iua76pZ^7L`##y~H=Se@FWJ^;JuizVipB2KZ_>YNK{r&s}ef<6d z54`!;mPhDfH^oHWtl z(Q>pr(;LrhX&w% zUaSW17#p9#E4yY1Z)SZ}uF^M>o+_6qqYK+0tIo|?Y!XKe@%s_~arT=3RLvKD*?gw^OJH3ctebc+u$>@Xeg(&);zth8 zSer*E+QJ`6{5%LP@xp6Hu3^=|K@e23`D z+GD648dUDS8Rgn}&QH^WnfmrL%{r?e65xD zUw~6>(HzgdSKUi8>z;wLwfeP|_Rqi>DsPAB4ABCbG|yT~3ze6VueG$+aQ@J=*2?== zO8cSp`)`^LvfaLCrG@I!glTE6eWmmO~R z*2)W|wO02Z+73T7-wxOBd+@Ba`t5LeA)fuvd<^mQcxO`~JpMZ5N8wp(bpBVTwYGi< zwO?y|$>4QsbqJKFr{Xf&J&>kX;#%W%DQ)7d-$xgQ+9-pTKpphlTSi)l&J0>YX|3gJ zEp3N!2I!<}nQPh~nii;oo?6SGCBSQymys68r?iavh4TFomqToEiJ$^J#Y4-;4I0MOBpyrX|3@rrJD(@ZS(}$55<|0FHrX^p6wu= zdPA(WI<%J7n#^h`pPm5Ar~?6oH3#U;uZ*-n{Tjk_?qL1ImzHSJ6J9Nqr`Nk$%BSbL zT1soJJf&rnM?hhJ{>w-U)IBTTKPfG&$B<6vYYyY|gjA|8d%9P}>+PsKV0=A@53jeQ znv2)dQ9+%)53gsVnv2(qQG5IN(OLQMdi*KCf7`yIN6$P>v+x=EIeXp`)Z?h0&-I~h zzze7TS6J@_rRwRO*(qMn1C6qHQ@Y5M?nUW(7pR|2_s#@pAJL~YJq+}lR`QY*$(V1+ z>(i$+>7{&JdfaCW;jrG*PuE*M&87SJP9?pB{OV_-Z8_mUeXY*O$F-WY;mwrg=hd@3 zyV~@0omEbd&%(8LU+Cwt5tn}%?PBc;l&<$&e&&}IIDb}}{`qGr3hPN0;Z>U{tT$GI zeMj{9`P?#7ZD#rEr}=zUSP!T)N1so>9!qI1-7iZIqx=Wu+II2i_w(v$lUr?CM%_#N zbiE?7F*`lQ8@;`;UkJDOEV}is25QYzz4V-h>gA{FAq|CnI`xFc-h_j6`mjXXzV>~> z;=AC3ZLaXpozYiQmdaH-DXfPQe$BTpD^!2Q>j{IeZG1|vof=z8*AoB-`{^EUg;#0v z;6Edj=9ecY`w5%oo{ji<-@$<=n{gHlIUYcJP(YBu6nmL6} zKIoI7Jbu09Pd*;a9?L%e2Y!684nDj*xRta3 zKQs_8Uix^J*!WCdHpi1c4x&H*d~MTe3lFW{oT0LnCdXKLHqD(E{CN4r3dXD6f|pyY z^&wn-nw(lSh0=U{azvHliPu+!Pu{1Fw0Sb?;^&i3sZlmP%}eEzf2fgxc=1N@asu@m z8=tD{=t3GwyivOTiQesn{W{4n(-h!?{mCy&?v!L_85$=OecI)HNZ)?n@#Be#1bOSz zBDX{O_Tl7cCyk$;NBE-Oi1E`mx#RB|-crHj^Ht?RJ~q zd>>KXU|Z7k?l|;hi%&x_eW@k5AT0;`Wx2ew`Vt6ja>egMy6licyu!h8r|@eCoEJ&gek7fqWA8tdkRBPvntYPGf7`#XxWPHS%_1K2 zJU4nkVb3MGXT-vJG&V+(XA5~Q24_Z|g9`gE$v-3S%-pkL2XVL%&fA~7v&h>pd2N&W z{QiaA>Ur-nU1G_Syfd)t4Chms*ORY!%G#zit(194Gi8dWo5=fGr8|G@UzpcG?Xt=c zTmc)R&A^HGt-*~1ZrcvP9Sz)I3^17BraCRg-Lf zsV?h*Ed{nC-}ILj%Z`k|Mhn;?J*FzDZptwh1*BrF{EGLN_sWvkCLvvKsdkX zT|)Xs(v{}(e)6P8v2SB0tk0nLcwyneWXNM^io`{Ox*9UMnU|nKJwE$~m*}_gXowaz(zIzl^xWEtnN=0X%$z%8${a7Qat3Z@Yi3NXsG3wWt#XPN zTQlSMNz-bkR7{>de^xC>Dr;+j@$&#T74(y4)l^i@o;`DRn*PbvlV*G5kAnJHlcrW4 zQ9GNg5gY~nV$!s^m8lQaTj^t`%(K4-KvgiuLGx-OJor_6Ks8ks@G&*BMhvG;c-ER7 znLcTrSB~O9P+M*P2(Tt{!t9#bTD~eO=G2^6NxE2AF}1RG!rbX&Yb&Sw6-g&3HZo)G z^okmrTv4(A;fL@4tBNDW{`#PbBY!pVpo)sfl$trS@OUVCE96pG6{J?>mGiwHmF-s3 z`%#4Yg!SfIJ!!_2X_ab7_v;8+JTiM;#B#7=`kblhFJ8--F_j9rfQMAp?hpA9UO$;z zGrRJDnKP23aZ^3SB;VpPpMODqTX|`Wg&Y2UTy{z}(NaeiR*>uG*2TrsMnmuze z$rY2u(n!VVnKP$(W$cf?VT)#V<*aG*Q+R|}Ju^~UIj43;Wi4#4nW5iVljcvGIcds~ z^Ji6xxAdEeirU%p4>~Gx?DSa`bic@~*_FrF%$z$15++y960!dud+!4yca`n=74Ckv z!}GF!te16IFWc+&vKH&-X*V9P7qP7WbT>BBwyUeF+a5wF_n&Han|60^b+<8yhb)8$ zA&3wLAu<{vL=ZuUWf4MzOjzPf5SbuCoCzWbkr5i95gLs~ERW2a%+Ais=X=h*RrmM% zRoguo@0<7D@RLrRbI(2J+;h+U`@6qiRS_;k@N0Gvw`vQS6g~Y;Z1f*Iymw&Wo7gc& z6u?t>OgDj4NYWvObMl9X-pt=?(McI;XYQIBovAI*Gkpw{_8l2GGE^HrRy%n3VD;eM z0UET$>l^7iTx})4(f7@LNA^~FTb!eNztLA4F1HvXSm@nb?W>g!9&T|5_6}8R2YX9n zb2Iism$iDSx_9UsH7nf7p>GcDr)r)U9_p(|V5l}!-CL=)m_c@UUm!Jtt?&}+HveLJ| zy7%zS13_xwV3a?&KT7XESlQpqmu6F@e_-#;wd#@K{Rj5$Lqb1*O*5ZjdaKVaF4&LU z)#|kpZB#!E$A~jb6|*Pmwb9rG8J@HC#`HMTO6ZHzv(pUF(8|kLEiEmeG*OzWPoJ2g z6D>C8?x>dYLmH1_nizRvz z&3NQ!UvFtqe`_;a+P?j3_S>pHEB^7_XZWF1``a6Ho!{`LgGRE(>@q*s7;W6uhDF(j zsr~#ZbnIX4S~BxmxfYnx77=P1Yt$L&tz`WSVo6%FK#@1zNOdGbZxK&JYix9(eti!P zJ5jUMfq2#zmSz?;8fO{0%*2Ze1NF(p<*Po%q_K2La~;E$4q$ z=waW=v=~MOE|JrGFF9lHoj5%@J6;=pCuN?XYZZYZiZSM*mC{-g(J~NZhxB)p^{hsy+y%0#qbCwe zvovaBjsZbmAg6z>k+s*mG(R&vPUexZlT?wt$U>@Zo48?qe6-524I8m$mHN(%&n&TY zXDvpqcloC9!Bg_1-pH|!u%(`vn`I>WF0xhlq0xD~IXqV=mJD&(=Q7xu_Fa5|oZ;E& zf63I)%&E@J9U7gz%eQSQx4(X8j@io&81aG8#)-thVfHQz2Qqr=_4$_J;n{_yc_w50 z$cEsSg}K>gJP$7HJ#nHz+bWuWf49foe6kG>ae0VeYzf`+n6z%w2gO7 z+v$7%&Dbf<2$^g)#EWbL>y3j8^rB)Zhw1y4rJcGWUbYD-wGj&SS~WX^>h7L3S*>x@ zWbJJcC`FlYZ{6&C2b! zue)}#csV)0$h~NZ<>>kzeeT$$&s{r9(+k|ToT*RP4zV;&)6`Du9+CSGo9wyG(9)4K z8aokf`ROu#Yhzi~4O4aO>Mp}NMamy#Kg7x~ZQZV}`$#R5b~nTZWodGT)!Ms7y?<%e z{*R@!IJd|ab@9}E$>g-4&(`m(8h3VSrc_JZ9-o`PD_AW-Pg679d~<_DA@%V)YI97N zlQVO7mQL>|o$g6pqc@zcCtf$SgI{3YNX%2MMOKG|6XN7i%(z0I*Nvava?8c`u5N*`tl=2h zY_i%(u{&N|q(K~U*1)~>xK63)@>YT!j_D?6!j4z=AFJ&ju8j2g-F7o|wC`YFWvE)& zI}~sCn^{AB`^!CF-}Q|hr97*WV_m;{XP))-o*gM`UxWFs%(^0r)kHm$(O%>Fp6>P< zWGthxd*?D5Sw~xie|<+!dkr!&Weg1M=&4=5dq?f&q!RrVRcj>S3~|492fSA3(m?ke z?rmjMj~*PV_Vw2KdXI9tQ)q?0iJq^2eaAm*$@fj{+_}4z%&TW?A3Acldh?M(eH9K1 z?JTF+lKYn$r|UV(#>*H_^ksMaOpS@3rGQ=6v1UiA94z-8>fN!cTg;&YdwY6zM#=*K zj~)_u1XR>F`u6YN`;EZ6Uijw9k-nkofXICVoNV>%3%TFu+tFhc#t!V3&00#!F8-a; zsS@YSb-R&^C&d%>M!CW6@(j1JHig&18KH)2WHDph!Qg2o(>xBU8)+sd472=cO~Vc# zM;AHIDrwVVlny~S64eIH#F`0XvuBg8n2@E>*@ZiG7%t!I zblzXn`iv*;xu1g6(#lxWW4`%s(KL>EO8-*(V^b&;?-so#;~e|Ju8&VFj9T~N6ntTd z^ILOFj?mKtW;kHZFI53Ht<`WCPB2DQ!ri6tm?UhA%GPY(pgH2>6Z$P(Sx2HA+AIw8 z?bC4c1+?6lTb$#BGRo@AaxXns$N2b2`_gI5776dOO0`Jc^r{;e*N}V)r{=YZ=@Yhu z#*uSq^o;7)I#gFRht&39A6%$w56JM=Mv5J)PC=(<=_LN?(ssibhB%Ljv{5ssZA`|5 ziF!R*eq*)r43pw`!Q&8Y8q-grtQBNl74y-&q7A?qwoXf93)_cEHy}}a)WX*sv3&1Wl$a7G=JeErvtn)s6V}-DB0Hp%RT^W%VCTYih&)l}R%2#? zu3Y3TqGv)nv~3!1a+mIW4x%el<#9Ovf@kg z=_9o8_-qBcY5T__d(37JGs6)v6I6pc=q2v!hj;GltMwh;f26k$dZ0=3JdE>xrs;(m zcOE=$*eUW14`e_(JK%w#$neN2+?~Mi`!a4X7iUhF7DgxQ{8ik-U9;m;rKO!??2mNf zScjfz(kP*Riks|En(mOCsXHF0^u%O>n+mO2G4Q+*@Y(Q}W{81HF4u`J={==eCZ zs9bt7vI&xp$bSlsus`N{{LyQ9HW#V3qA=^O4Ox9XwLAA`bQS9^C8u=PWBalI1EESq}BjahhId z1;q}|W;0ZsEN7US#tNCc+EXoz)#~%LGkRY`9U`O`r%%<{!0?dV3F+~Gdx}LSQd&_K zy`h6r7?n5kSBo{CCx!dgh0#;>?9D6L!B(Ug8@!&NhmV-FJWBDMCQs9O62;Th$kuxZ z>?!i>37+?l*LlBWvMI@WcOYp3Gip0Y}-tPgOaSV7}HbZK3EBN zRv^^C8ah5hC#o%H*rd(Z8Z@l-a?9(6p4NO0FM|slilQy8X3`?14Q_ib;@xLLi_k`ZDw?=K2vMx zl_-wO=4%s6Jm%2bD!e3Q+qnw}Xx7h_5yss7(!x|Ohb4p6cyW5%ZgMjfYK@9)U7F)3 z%clOL8caa0jF>Y$m&?#bamL<|pf6^v*^Ng%u>CkyuQp0_l#cDmLSBYOO_ z$%VeGJf;!5b=0}My0E$tcPO)q<2gSt!hK(3ajed9Vttd5>*MjWg=nJP!o8 z8PaBwWpbiUaXyyJmhsqQcXKm$&H>&hf z%a|TC+i)vQ=q;2uVGuqW5DO(Jy^>;61~jLlkz_kGddluGa-82_hd{H$t$|u-iuZ%; z@rD{e`xM(W#2me!GfPusn9av8ZB-VuGVqYT7M=@aR5{ja=>3(jNU<_%<+R1R(l^vs z)mqu)96D0ztMQ)I-VB$Ql==>i@a7HOD7r~^tn&~ctVX(1ZRN!oB`2GTCOJ`?A3s1>4_FHo1$lO9etaL( z%<0j+jT2?g%gL&7QeT_leITf@rRfVoR*my zuZ6`aG^nMSdNfjGpQzQ_WjzvPRs)1yl(=`@5Jpi6zm(2!{zN{lPAEq^)(akPS)9ZO{%#XfI!N|#Zy$-JD z>FgR9gPbHz^Ik}^^=zIfYklBpb!oCOcdB$^bYW)t6#FR%;&p18vLPqybxkdJg4Zl3 z(RsS8Rb=#(CX2~=N@*6Rqp_x-RW@qTJM2kqOcPz6o}Xe;BB**~t|Lj%h;Fl!uv;lZ z)Q>3G^FMoqyER?dyN|6o1Lx+(PR2sgi6S(b1u1!+X^-L4r`x=o#{@$3cpK3IxZo$l ztw*5R1gX99R+8taX&z6{&1R10lYh-$&`(U+7R!7H%R`cfDGJuxG^sIZ*eN|L%V_i9 z)EZ&A~K<{0IScLIIMxP!i_0|{0*@xTP?LOq(0duURN7UBms=RxN zGkU*th#|p>I5Vo3T^rqjg+uBxqo-Wm9NXV~DWv&4dL_pz^V8$%jl7I7Gd;^w<8V5^ zf;Ao;YZtxRQWUs_}Xi$Yjed;2PjbCsbH_MI9w_6Q6f$%8zS&`eU% z$B;fobnaC4*Yg#FQ#=%Ir5cC)AscT>*~7L*ncywxV+FNYI6ZEg;_VA7lD=y}J7`sd z=Z*egyu7!v_fYexX3E=}7qUl_sYJgmOgU3GpULap%sXD$;*K=**el1}KR3w|$SEX8 znEt?ZV(ygA9C_(=L9ZvV&x%I_yaB;2!0A&peXWE?pHSRTu$@A8w63GeMLpH4P2Cwe zwjE5;{#?!TImOIrh?8n4oobBDa!|@aKgB^V%_a#Z0Hv5nJ(GaX*%Lbiji$e`3V0@V zc|G19calA8uWf{^M2gK-w5EwP2&j%a=5LO=L+zloLw^HLXjWlm6npv3Qp3zIyErwPsPcm4ep{21vo#M4E&*ffH!#PPU zw4}&uNjB;eOKdM%Y776eS!G$_7M{jQ@?m6CF?Nz>pQFcW+Tou{jS|z4CtbcrMmLNt zYN^;x&4gLC6w90NiMdmCy^0>xwHs*14>dE*;A*5pJuKPIQCc$f+uoc?89WhC@jP9f z)AI$t8%Y^9%IeyG#x%wW3)ccSbTMA}hu?P3O$C zdVCaePEIU7@b#L+0^4yzCydr+x!WHNF}6ZQgW zd%b*KPBjep=$6Jg$2EF~B`q8_Z(s5RUD37f$unbmyU&T5Pc-k)SDUlaqMj@z8Fm=S zSrex$3wrrPx0(zN9X~lsQ`7=)p|v>7`#KuXp;j?doIB?ld=au~0q59WaDLk4O!FeQ zJ>G0%R_`e=)$panmV7ADvhL(trRI>m^3gOIMC#M?J=b4Y z{YyxSg1uF#_Y~FU^^_{dI%7{5|k^-MD(73f7i5_ z+ru+D3wcJU)jac@6*L2(28f43(-WMv&@njvGl>&3b7LH@k*d*i;-=4Kr5 z;TDn?WO5mLoHor@RS@TOehyFUyNLSEY04qgN^|CHpIVnW%2$GKb=mGG(nMLLKY@aX z_xzCDElzXKV-$~UCOPcn^(8CXo`fW#dN!(WQgVNpa0!jRvY_U0E1ra{q`Cu-Bl!Y> z1{C|hq(;iq1I0Wq2HKwR!wC`k%V$JSXmao?Y9aHn+=7%$=0pShGEkHl*5& zW)hrN&f1PrcL+|fyl9`<6pe|+k|xFM?r@RQN4=IGwu}?g+<5WaoJ|({NWNLgUQ-{l zrMeb$P5Y#5|G>LloGiew1%Wqg;vSphhA3MfXZNJV*<6n6$YSY?zANl+OfmarQl>gs zy2CJK-J!1%*pqk>VU^)1ILs=g(U}u-?A@nM>4k6JQIwu;T<1p@7UrjHFKBDh zPH{UWB@U;Uq-?rY^n$5m`fjos<3UC9m>)248_90o4Qaq}s;M^7n6fW&>B$pQAv?I) z)4CTKW2)jC2S#fkAZME@f~w^wSkl@UY1=6^cUB8iqk6s2glz>4j7h$5B4f=$^%T98 z!aUv5?}u!sMtyw_;_-yP*q}EeZ2FmO)ae@L;er-V>>Tp1H!a-7S5{BialpdN9Ni_T z(9)5LT`Rm(Ve3YyRQ(K(gzO~`d%mkkSt%hJL5d)C>${u0E|qX1Elnxf;jo?s>6D3H zMd$q5;u&sD?F+XSK7CscL%i|9IWqO9=MqZ3g292VzUr%)HMC(S)hxtR5cl+*iA7zNRuGTD{2=_N@HK zH~g77Z&Q5Jss6nO!*le30lvw_uaZ3wRU{h7_#kJL7@%X<@Z~U3GWFM)Rxb6?Sp!D zOy%^|!mNkwb6jiRk)f<`hJlQAyOxN3Urz&>h1H8&*w4O#gM3FmuF+#a>9vGXU%9(h zJu{?7o-H!)D`zU4Lf>T>fhMPyMdzS?)kDM)W*v+j>F4v{!W=)g!TWjo`YV0WI7f!7 z85X_bz>vqP<+b`sk&6NN{cWX-T$etwI7QddkZo7S^0}_whS&o$GsndmZK z+FMZLW8NSZ>^KQ{=oPKf{4n^Va zbB$(SZFMkPXUDCRnR;vCNEeZeA-ByO9~YI@ic(pc)qD1bG_RRCI;s`!?diPzGwx8z z(<-GtjaBXfOjYZc_t7Hr;B1yxvES5)YFM&P)U-3S#FD`39R(0y;s`G$830W>Hw;pK zW-v4}=sQ$IGkjcgj!{Q9<03E_Q z2YYL~6=%P(7`6!e7kPs~I}eM1rh0Awj2>E?z<1V-W>zxJUBZ9ZNUK{+rwxg*T#}Ns z=tbdvzpwT*T_KxZ3}2>?&Kz8vY&wH)edT7VtZkn48CZI?vFR~Wv|ee7TLpVo%#U61 zMiRa962w}Ah3^QJZtUL8>o~Gc5r8L#>_DpMI zPE(Q4YbH;$T=lACW__0mulGjPnpUZ4dUp(w|5#6&-HMWLjdBv2jaWMEmZPZ_rl^GL zxAph*y6wrN-wsL$#}%85%clryX7G%3xRRvk@|EE#Iv zCWE?jz_-M#lgo%VE@uj&l@?1{>0C$&;Z7%TS1=_kow47j;w3}M8Qkn=)4X-Y zq0mx;U+icVHLlygnAEr1SYevUpwFR!yu+x@anl6%bvnq6$8dgt(iG5{n_iXH%h}we zL~l|i^)PXs@7k}NG=(AqfvC5jd$Xigo=$4TDMFo%nto_(dSXYwJ^sZGW$CTZRtSt! zVCbjJFp{g7c%?co5b0PVXQk;>2yJxBe(9~BZzK#!?mHkhlgqdymw!%@#~X4JT;cMgF%h(y}J9LoW%92`uha7%+GE^rsDcE;0 zeI$BiM<-756oWGq-W5N?amfT<0AI;+(3(COXXAJH>}jMPSjC#!TSuCG7AQ@Ta0&Z% zKr6)|S~GzMUXd27z?kOu9l2pjbnH8C&GfWR?3$^d-t;L>-7N_ov_&C%m>;}nqVdO% zR*P`Ps&m}-uP$+{8BbPEPuK4xB0_d&VowixEUoX0vm)DRr}CB7(W)5Cv5|#H^CUI$ zBo>*8=-zStzGzdk#uxTTBf!*cuQN(N$kVI&-UL6gd#B}EC)Yf=#5tqw^!+(g{8mkj zJXPa3Oh2%u!)-|YMylTo(Ac3s6{l13OnOSEy$yXMQD2-e3sbHc#Ncx_t!I7KYRwcc z7{k$nG2Sz3a*G_v+0WTzE9@cDeKt$LT%d5!-~t+zRtHX$LhSF%NI<8L>wl~F=XEmin)p3M$AA=?H^Ops$=G-(Z14M5*~_*`OX-@WJ?0r82t#nKr5 zJjga^w#(pMdVfTq4zD-$d4j@IRh7^_D>2Rp#2;)?kovoh19&c&NGuQxd#|n~<1B=j zW9=uLst}<!^Xd8gNd zS#>Kf!k4&|wmG9^KPV-R#`TJ?DNOrsT>GnvCeoa6{D#ALactS`UxpKwP~){mQ=;n3 zAl9tN;@pW7j5AXUvC*h>y|$xIkHPLfb82*)@AGTz;wK_?sADf;hp*^)_iFxPnMCyU z4!!PUG)Exzh{IAdcQ$k;(qizNlrF|AdJbzNK>4kZ|^NPR(=U67F$7LdT znrC>nZK?L8QDS6!Nwd+jS$*x>vZBq#c<^F=tZ|2z5=xcr=$ajESuQ5CwV7s=@x@ik zT$;~Sby`0o88V4O6e^7_j8E&eh=A#2+@bjIPO*qEaqCxr@2;O(;uX#lzBAC{$sM(E z`;LB36Z7brU#T%Ap&YV@wL`XT#P z#+1zQmKGm)b)baXN+@V_gPM7)2Z@p6b>PJ=);+N`CASRgk0Nd>oj=F#?U(p3jM8PL zvQU?iRX**K)Y%}Vzp_9|=(v$bUjcRU4+NK$jzZa0R2Y=PRs282s?wk6-vjI_id7OZ z{UN|IVnd0T{u3c17V|nvfAX3B5F?YRe-_X`F;E4e;E}j?MUVKujMDwUWu;+N?=>fd z^fzSGp;P#Et92nv`nQnVOrt6!18A)M71Es^0sXgxU9mi=1i5vIT){8N1;5;Y|JR2a zCo!=xF;NK5Ehruah(O+i7{8(8Qdx=Pk~bBKlMjL(cM?&% zqRZcbtrnw`Y+OqRS?`^s&~lPnLHA3{txL=m`jX^c@nY8szY1Jd8WalNtWDGlUG`bz zeoOUG%muwvF=?e=GcT(`?-^NWC20i97YwK5J}?u*`4%}3`*X|6bfULboj)g0O_B5g zpJLKOCH+kuX*q)`XD4u3sk#rOfexu@ z=eU!6+7)&6w>n-%c>uBk;O=qIn3H_kC54AWVVX9P66&GqbjH$7@Y{iQ1s_aV6`R^6 zhEvN++06buE8{4VrH!U=R7E`C-%2A;x)~0SPsiQ5ag1--jGA}EQw4_GzCB6vbr+~as z1r&gGo<%v_iDGR$lT-|KC@EI$2c$m)a#NV1^D%7saGCxM$gimNbO;u+cbfI=A9#CA zz-*JX0rnQl>K0NLrcP_sYy-y53>q2R7Q}Lsx3Z+Iew}lC-D)2ynx4oKx(cu%sR{J? zj*xg6Vs~dq+z4^wAA|%_cB!&5_YA4m0nB|D^rn-1+7+d$gybZE)C@@fDA>leAH&vM zMAolCt5&s@dIa{fp7k8)h4xaLs6h{at(~fjlX<`Hh5fi^JpuZ5d#TT1uOTu)pJ3Pl29kFLfn>+zMdpI?#3}Q(FUn=#8`*CQ?E^J!N`Rl-gucgF@E=HfxHw z&iO*>494FL;ifVZ=%b;)%UwlitC3apuo)-$v`Z}-71U)eD^-M~pfV~^#S*5$M^xGl zeE?w6Iu05$GSjbj{|&Fa`(pF!)kw)&Q;u{=U02NkS_fFZh%SIJE+kJhE9?P#q0L&u zv22zb<;t=w3|1hjF>x!z?J@Bn#6vLwZeA*d80-T@GqTdWJb&m6jC%nU(_AGt!0`NT zp|b#Wx(}qHAo;XQ4cZSdZ3ua7Mua3nErf0}a#+X%J1C?=li5b)3;+aXy|C|XD?*YK z8)TP6j{!86Y$_C&mA)=?+{mLRL2kY|k4xSKo2-`92$^=NJ4AXN1T_`vHnO@4q;Tb? zYB+Yef^8EXfRVe83PRQ?XqJ}Qk(SvD_%S2}H=7x4GONJI2n(ptjYbZt#uH91g52?D zM~gL8$4_);O+k%jSCmrXa)5XUJps@la>=e#EQvx8Yqnb|wiZRgAQZ&h6rV-0A&FfH z5P6}X!93YOW`LxJ$E3A#{rsUkC1oIj^TQuBvN(q+_;cBNwKQcM*>j~JQpF&c9z z+kXaQW=J1JV|KEj`8gLn8B>Klt6Rt$H8U_Ir7ZOn`VyaZ#bS<=ngVjYL|Pg@XDlnz z?UYr9t>(@d{1sVn)6rkA_g9rEm)=)ixKOPeX}MdzR2MfYnsQbHFkx+bnBP=qbRK z#Fk2?{wn1PWCl!Sb&)It~;%g5(qkjlEpBl+_RW zfay`(Z4kEuDz2O@?kAzR4P;|R=o+9x9wb%%S0i{d4H-1MY&ABi*jynNGQr> zCf2oomTocW}*{ z!G?9WNmco@OAbg%fmP(0l|6$`ZkSm49*{J| z2H6#hlgc7M-UeaGvPnr))UtL(6%Be>$9cKsz7*P0|FZRx! zDdy_Gb^e^PlVVigH&c zVjDm?Lfe6YKbLRtsIPW8q1{GiqMntqRkX{bsy$7GOan+^BUu`st4qw>%;gf@nWROl zECrTqD7(}#Bb|7q%CMw$gQS>S-LeYiV82Oaypoi9-gQ9qO0O26tJ+MJ`MQc(~mqTT(u++0EYo$8j zY(DLZ+8V9WLPNEaBqX19MJd?~u7sRsW%j_h1t3uJGKrbnl2yB+ErqfH{ z5^6h$vT}o|TxPIl1JXr6{Mq$`PbcEH|^okldAIhET6F1AIgqD!{u@A$k(s z08nEg%^QWzdrzqTMS}>18v3{-1Nl3W1VSb zN$A=Hu>1>k0cwU!uYqURT0X;xgPKBe7Do*>)m7MSwJpZygdri9%7l0G-#&kioR}qv zmw`f`h;rU1thJvF!4i^Ot~0^0%cZQBx>RPelgMUZE07DRvU-3Jc%cD6jR~?#4ISy#5Lgh3 zQ7Ff}qMYq$qFmeA!R^^?Yn01GHQM(2*~W8&xo}j-SDLmCBw>5g6f*OA)~toirK&L} zR_00=o|T!gC8pB10K`Liw>h~8(%cJIcO_35*)8;}bDsmf;3S`R#lkcf>;c-eQ7`O% z%Nh}y0BECiuYu*BXpmjen&fT=$XhQg9V|=bbtn0>D{3f_^k4OWw*6AURdH?nRJ^V# zW_`-&&2qFkouOHM|#R^=Apk3&&OXODxz z=85&DV&@({eGk71$19nse>8N3DGUm^Lbauk>FH5HEzWc=Bn4Hn5>+e#(=J6uN+YCh z1#+QP)+Fqc?Xyn9zPp(9_(l))a`ji=q9CCWYpAN;5W3w-n0CptXy|G<%$-6~Pz?s7 ziX~jdx1LeSqg)YcnEp|rs9$NP;&C1lQfFn37TRrOX6vgm)cD8Dn?E_%(ej1_8Fs}g z^pIM{$Tehb0djGdeQDg(K5LoQlGY3w^X;`Bg8fMQtjA%8rhu|5I^W9bZkP2U%vGg7 zvTnAPXowc>Hafe@kbfT{|1r>}!as!l3BbH^P`J~B`+SFp)c`s|8teJ_MW&^rqS+fg z+aW@Nxd#}M`zdmsr8wFZEBk9wt4ymLa_nwKTD`Cj7qdFA_P)LWdHfUVGIDqTR0Y&Q zGuEQYl_obRt51Dkis9NvfhOfGnXYfHCx;h5Ta@IwCvztxl1O+3M1WrDLpO znk>7bFgK6FfDy9m_PH6`0D}O7Md_}Sm=Jhro#sz6ocGdP_(+ivId2s%3xHH zex})?(iNsu5t0&hjw+Ba>{7L)Ak*>)2U=2@;Ug&x?#(8Z37WFD!){nsZ_7Y047*}6 zdq^Dsa(Bt80am-c(Sm5x=Oda>K^OOQ7g9F>xvrvuVxqDI)F1iC*p(Ll8XT4&p{QR; zl`5{{9UvC!&JEp0UUV+ACzjkTWDWofDywoy(_Psb`w0S)_%oHnJk*E({BK2k4eJ zyG@{5=msN)g?gPUq#lx+f7Kpx*Jlzt(%eUqo+=dLvhLf^PZ?VryP}h-)fs>m(B|Yp zCxtwEcHjDzaWhl03Z8{6eP-*kpa-1f)2>*;6QsQG%xo>0jleD-H{C01E6@Ybtm}k# z1JsI7yJS4l>M3LiT?rJ%V3fPuQL-WLi`((JrggtlKF?w0;3^?R+lE zwe=#{Ygd#zcp)t*yRTItpL^T7v)0^R{tz~n?{do@!bZ6y48I!(lHMSnm+cK9h(b}W zZA4wp&Pk(OB~~`=8cDh9$)`&Tsfm~AwxKTxU9cl04!DXv-Uy2}%67?FLl1zx^Q6MLFFZD8?d@?gdluGLB zQX|sUCFi45($8EfGigMrZD=vG3rR{<$;{$!1kaL2X%n!llnqIj$~5r1E3%c9TF%zT z*wP5OK)2XpRkIf}dLWtq(ue4pEz3Zm@!;9zmhFf_a=F8lAQU$Hwv}_KOp8USq`SFP zrY$6;x+qxOe0~D*OU)@N$<&hXSDVZaW1e+{54*fmB~7;(f!w+mrM$Z|r?1>llazX8 z2)EFqW-C)&NgZxxWqPHuR6p0soL|iUgMy#;Az5e`$;nG)=GHl7skv{pEIS}@sZ5+D zwFik_lgbRhUj!eL5wGeNe28*t5k|I)%+DwBjpWwD12Le|-$ zRML%hnN(&rk(4GyH;}1iHY?*nEN6omY@Ra~8YjwmjZ)e&(S$j2CSsxvG7nJZdf|Hk z+FR&>z!rYQ*^h!=a#DW3?PRO`L6yDRT_Mk|SiUN`+N&YIrIed!<)x12XH2SmH|Sm` zgDLBNk`ntxK+ooc6{0^Zt$#sf>AFme?*{>41ui2X#+A4r3wi55k)pYmY4n+Y$TYRj?>_AQWYd+-4=l$o1>;nILW77 z3QJU24~MoF+HPcKl8#bt)^@KGNYO*=fc7END65E90{av2FMwP?+R^(yS;$ z!XwTWl6{Mi2-QI{!cPNqN1P+XVr?ZzG!3RUS6 zK48vOg+f)jXoSr<^PFymN(KbFR>N=L-GUxk6s&s?gN{)fZAFlDR`|FlFA32x;kEcC2#M}%H5@~Dt|SQdKQxoVcR=D<;*UL!}QK<<_XrCn$EoHPe+ zkXyet@~Dt6GgX!Puo=CfrLM}@CG}fPr&~ytXoIyfB}LIK1KlkH>Zo?fz&!w48=(h{ zEI$MK$jP6B)Y6xgdW1GuVdbkp`k4Sa<1L_LP9CGk8S}NQPW-r&XxWu3>SdTLP(tSc zT-_?H@?fj117`K83h)peRb9LZj>aC2Y^z$$c{Y~C^3$byy<%2}gw7dxROme;D?+nY z){xN40N$(<{*kj)?v&ML=p@M7W2jYbb7vOI4dHwn%b40E{EihiB=oM4gF!20OT$V%7yO&5*GH3)~$|qBi%}D;eFQA%0e2^jD_f>Bq-3X8?dXe;-(_| zmXkv78M#A9%QTb2PeCd?+2%^U*K*5aAa9(Cs&LY{6(Q+R=GIuIvga*(Q0QrZ?DfKL zIQw0Ym){+go@mx|P&L*3&BCg-zs|`h_ycm^2a*y0F-e~#w0ha4#p*l%v@WTIRJmR+$_-Mnl7XcW)zQ--&KLvF%$G-D=}aY#~E zz`n||M)C&4?y{_Ip=*t__fED$5@;1{m%Nd&E>hP4^a11Z5woN%HL`>b8+laj-0CEs zc0~nQ=mAg=Qq3uHBgn2Ob_*%xC0$8b+qQX&c&xK$L~g)ci*nv_cE`BhtYreVCs2z&(kk&~Z;)S=-XrXy=DhQzF0MW^fw#@?uQtNJ0jim*(+fwJiDgKs4^ zqUXWa@Y@lhyTES;difIU%gNp!hJ`{2o01YXB_(W1O4yW?(41mo9Tb1r;xy*rj%dsh z(V8WqIZH%)mdLxD7(AOAJll~NJll~NJll~NJWG`PiXN()P6A9FLTYEiwn6wAXA5no z4q^*yx5hN2rqyyFwy@Sb*sA1aZ<7te*8()|R#0fF4f6EofHZ~u*F&l!v|4Dv4Oh@& zS9z;#5WX6~xN355U<*HL?7{%(#bY0~-9i&qkE5+BH5bYl1WDayQkmI6L*pdu`z&i% zNJdz6p8>t&TpUOU9NolTBEq`>>?O^yRn4K_ zW|4^6;Jbkq{vJTnP;k^g@y;mxyU8PI-3;sOq2(o-R2_Z> z#B{zgCy@>Fbi1tg$W|qUwWVZO3#kMm6OBv43bDRj2$<|Y21qPb`4Yc6O{EZ-*Aagc zXxkUHiT$bDTd#>tcazv&pRYiaW5S7IZRVaRg@p}KO!RWrREtEeD=+seJi7vq+U#G} z!S=biut04h%NNE%ES@Dy&jS_<5ibJv?kHb%7X1>SmkCLVd>LpPw-3npA<$;}`8n)g zx6kUKg>JA`D(uI~uy6AQJPsOj@+4@+$$V@yWv^hhme_-U8ddlipv{7EcTVhS7ZcL{ zj+HZCzBJPEA2G42(DOzX`mQuywyexOil)RDV0$^4z{E1u9ocUSsV^0d60>r4I2z?L zOJ`Qj9tTD_-8*l8+dq1}-$;uhx%Y0qc!B8~%8 zO=tozzK8|@U+8q?i@4wULJvm1h$oyc^zFzO@x1eeUW|MZFF9Z6mB<(Is`G_jk9-l| zb-vJ>kuTyLfS*Ed0p`Dmw*mM<+NT;{#JkQHIv@EWe&Bqe4mk8}TsjtxL$QQBJ z`9j*5TO34O;e4ShBVRUksh_=jy+5wL#}=SF*LqGiZ#_(=e<2q;y``Pl)h1>~$g0k& z(&??jo#w%?&~3qkiip<$$q0WJU`70PCI59-K895!$~w1gpvn|Mx&Y3T)w@7`rUAC-^fcLKQ+>$PeL8ZxZ%;mD73+CiU8s)+;k3 z$1bqJDq|0g_Zc}X6e~~`A+KozIqgk0ET`2vaD`&j%OX@de4!Z7G8XNMeXobqPN1W- z{`;4Sr~m|}TlhAB&VE1W0Vjoi1R$|iI1EFDr5`{Trd_cB+1v-*71sVI?vI@VA5u%p>q$UgvwI5sQlN!j0djl!RE1$84VNm^AsP-+ z&zY@))GTbBdDxg1p=pD*>(*rPgFp*^2ry(1GoCbqwHdwlAtCKHSs%dOgO>WK<~GHu zU7wX)n{~v4*$QW3ZcAdW&=)25Q$T%DxYHajjHpjmE*2_r!BS-|t;FS4Y9aCx3+65u zlSFP?QrSWuyMkhRtf)*MJHG4uImJ)&11%$T6QC8dEMlMYh2DyM&1xDYCIq%ieK{J_ z)`LMUbU7e7doClT+`Ug!*?IEFEqvJ9qjK&0J}^Lqb|D=-e$cLo)NU zbH4{teGrgEpMi%zh5WH|KLhy;n`s%9qbhwL;LQ7Do8dhcB;plg(GOz;V2}!Z8{ocX zqwrJCR^cBwslqj3NCIS6G_;kJ*C(^Q$(!5BY5=)mr7W@UwXANTM~tkhX4=N)?oy<1 zKkNrBtK5<*l=w6f&zn>sG%v$`)3OR$3h^hFmDT#E{=(U6vg{YqI7||~7HIQJUGs>O zW7*VLQu|g}GxmZ$vBQnwPeF$}javns!5-}_HC>8n){J%3Eo9L*ZvwsQr>cYw$JKeqSytr(K-P8uw`{W1p2m< zn6WEX=4seI{TC{uS^Zja$g_hwcUEC&)T!1`Arx!G67c){kNT%(4n@{2F#=XWRDK z3A?|2)*|eCEURGfA=r<$*Ln>0tCn^24bXWf^MUyU_KyG#Yu9n&z4lw3N_`5f(QmGb zp@a@ZTOvyR#6+Zom=a%PM3nk66;WcIv#6GpMN(HY9$Orxt^t}wE3pMwX7vC6bycUp zty1$L(Ln;d|BxP4aCT5fW92(^a++ir%9TCGw-=GVSh-Q|D|^`= zOo?&%Dt_57#l*O*w4b`%g&(-crpMV{z`BZLVI0MB|FZYfmB!_-OMO>bk1J?E=31fH zzM1*&KUDj!v^_3$zxsVE_pA8*cI(Gi{pGid-=*$KQ_WHVi7vDEs>VCS? zxP0Z`Z?`Z0s@*PYdtbZKes!hoC+(-fHbE|Xzo46A_MJjeuI;{aqsiH?*G9Rv{<++c zP?XC&%!1E(7AK4Vhb+z0lJwsFud?sItA6{}rQAzhk4tTDrStz+<^Fd2)s@Ei_hCN# zUG=4v#`bsR-=!{hrDrvly4>G4dzX4_U+Va+)W6?VxxcG%d8zBW(((4yez5giYo&ej ztC;=fF}u|Ld!=plyBg1bUHn_=Jo3A0-%DNYrM9=yesHPF)foWK=!Lcb%bro_Ab~Rq zp(xk(jH1irDne1N?HPs34W)8z&nPxyh%<`aAiLrjh0ZTR-p-I^uPlmA^1AbRN`Eyk z|L*7IOWlvZviFzkUjO%TUb$3#a4NjFb+LIW%3bXIILck@{5UJO{Q0ruE_Qx=4f8DP zijdBimt9x7Om0vp%C%irw3zVDnNXB#yRNw0P%77UUAY!RtSdKy>`K>_kk@w233+YT zoRHUc&AC*4Bl^42{esIa|BUXz6}}@F<$RyF{5yitzu(n<@KVS5cU6y-w(q5`@9$f= zN&MnFU**_`pe(0F~QzU!V;O`+{vCyW$+MnpDWkJTLEBVSkKr7weBvZl&$L(zsu2yoBne z^IY3`Dau_c|8|G?t+XDAoVv)e{pa`b{v(M^T%$5;S#^bdHOjfY<@b8`7wxqzH_9dU zqFiQ7N4cbLMY*OVta$yi%^(g%ZS8j z&Lz%>Bzf=|q-U(@n$yFyR_?Wi^-W5h#3K>tLLgI-l47U_2sY})1 z(rPbUC@wM9^=~qMCVEnl>)v?XPk#>LW3wV;Wz>4rd+AlR;8TnDCsNXF!Qp-i< zv7*l|WhbG@hEi)lp;4oD(x_JjIlGJT8u*rf)DVs6a-^eBjl(-hm) z?=?sU!pCIh;vc1w0r?S9l&vg4x2;g_c#um%eWRJq zOfzuox>%|Iq2v@#y0TE)0sEa$i$qQ{qD4=HW(o9#6#qh0qkb~5qP{JN-gh$N;?+>3 zXEi4uGp*_8IxkXaTP#wIcleh+CX5G!XeBG~ppH zq{YSLMT|IK=+?*w+okTDt&h#Vs)qD!pXv1(OzSh)s?T6rANx~Qug{>+o@hse*Jm)T zkNrKK*Jm)T&tR)QgK2#RTlKMj!H)IGw9gd>&Yz>W^vrV|NsEVwUjez{8C8_cl-QEW zj8sV}Ys{p&g{A;ab;55rTj+a%{Wka$fZ69$keA#IWtW04o9b}VU{MqL#K^&A6fQsL zwd*t~TcOk`auteFU2aQgGhl5hVmm-Zg<{{z#8CQMLuPI-T@DVcHWPN+JZof?PrFpF ztduQ&E|rPso57Y`qU#E60j#M+Y;(SlTs1z}E|n7XuYyB$gsuUsnj+lpU}|?zcCQ7H z7m9W>)&FU+jt5L7Q^zQkl&-e5(nahtl|dnC8y{?!yx)1t`EyDnP3dDNQ%aOl&kKF? zMk6zkYy?|tEz9OhS;=*atd#vinM-ACp=Vd(*9I6lTR|uspI8e^s!+?fi=O$2SIKrM z7Fj9t%%w7(t^bBcEUD$?_CsPBTUjap!9bHL*!qa_KLzr;m8%E5oG$W+xzJXiP`7m=W$cXG<8%-hLB5*AZwSxASp>`QK3ws34p$_R=DA8p?d@S z0q}#y&a{vWsp9uoR`S1knK?#M3XN=2oX|-?T~XNWc0<`E2Y(W3uC^kILOn)i+9^t{ zwJhuXk}CAwO^3XXt1n?qs58iw!FH+Ct{|sO#kpIUO(`aoG|zFl0i`gS`kHR(4GMd{z_O5Kq!Vh;dcs24DK5$cohg^oqOh$-g_ zML#l~{LB&Wn8~Q!YARKs+X1VRh)L)3X_toFmqA>ogOUjJCW!f>y1uul6T*jr_LS20pu`^$F;X+-bToU0Zmnk>Oxjj2fjdGcC zPlj?mV77OVkA-sY3vySINskh`s+}A>yF9eEpvQ7;FkY;jCqp?&k9^wpek0Mc`kAD( zwdexyMoU=enoxE{sqLh80Z0iw==;crLEmxGr1^*nd!VNPmUN-hMplHL09f4(f8gv- zL0>rebC7;nm;R(5*FEOsy&x|$6LtB&2L)wm!w~%u4co5h&;ThZQv+>jjyuVxT~X>J zsmB4_7VnoOz2r7GZBkYiT@g~ zh0&Ll^E;v-ml^d@&L>m*-S&@`v-b)a-v{tcfqu_PKJ7~D5ZY7G^Jy&8oLUo|*O0BJjLrsNl1lmSe zQR@O2Lqc1O%=F4vtLM+Hg1r^s*LYu`ulbc0rM3=D-fLtv2w7i@wAvXdp|~Jq!ulme zNj9pt)!HVR0DL{Nj~k8)Q3nZkrcm{WTDKgTz0!5eOm;9cMQhKH(ilL>GrXX! z-#mZr`Q@|TCrd-Bjh0Sht^nF(ZGpY5m=(*tSX*V~mT#+d|4AW8-a&2SdmUyhULpp7 zHho$pX&K7ZVFJVIMH@Wp4bV9!`Ls(>mDFRT6g~{R0D8$uKJAK9Yl+1*9*-`N!jmiU z{oA}MSD{CikVd8XpBtX)C&m|{DGzy}JwTy{8IFj*w=L+^TCLe7KY? zZL)?ZLR*deE$01IW?=c#k&g+)L&!Ysa?gOCbCOTH6s~7)_i%Y2mfxL6IS;F~j2fU2 zn)k7%R_Ou=&G$j?I?1P9DpgWXk$M$C>NSw&9Y{Xyl2oKUfXT`61$G)1<|>-G2A3__ z=>ph`t`)u(;D1$wt~0W{8+5?P^f?a-CRs)p#*dK_v*7-u)od2)ai0F5|kp5H;`EPfKm~g((l=J`nY7uuk zU+Ap!AM6nEi1UT?*SXj^+acmv=L@~y{D0FSLVuWxpRNDRUH`wW(I0xe=X{~}0qi{A zA>v2Q7y7aDUtA;NGv^Ea%=v%XA>woA3;o>r|4)aAUpQarSI+;%nqNaulKc_UKax}K z7i&c5FK|gb+#$mMCbulK!TJAphY0$JdyVp5is2hO)zjlb|alX)Q=fAf`#0}0D+T;BHYmJC~&KEl1{D0LULVs{V zxk6>!Ip5dW1O7Jq z6%{85wFI>ny1~eb3Jhh)UDqv?An#n)Hk&t^ZejD$3tK*GfsDpP!?G)_ubK}}WcX^p z>Z_P5;Fjou*b);NO0D|8DL>CI|iNA4nt2P#A6A^Ef`HSB%Tc6jnt z4ipSa$>VRwdXigaFBhyySi1l^44-zTWv+&UJ3=m1xgGMjaVvbQ8n>V@k+RiWG9Y1*v3axqy_iyy3cqwz*k8?TRI;|GJc&l3YWQ`&S_!5`}Rf z?a;r4LhFdDE1DI(GO1AA9u#%~HVD+V8p4(U+m)K$0*4q$NwSb8!bd9Qrn^-|nO>$t zdbhRR{d%OVOcBRTWmxDLfRG z%>YzoqwqXH4>%3F$H=2XXN@dB0#XUYw-uQo%t=1&N-N@0c8hTXnHKnc5dAT6 z9K!VrohesE#gLUv$eVdc=%jP`v@4b^N4@NBq2mDY6!OBlCm}s(g|?AG(ut*%d$bD2 zth^%PUGy%&Zq1PNZFa)8O*aZsT8|nrKG(PV4W7~HnJ+D+OUAlg65qo)G)G8 zn#^nlRD`g!+Eh?t*SgqR;oT-y-UsS;Qs{O7Lu-ZIP#Mau=(c>_0wC5Y>@$iD-Wy5M zE;oS=7-=t(-v%jRVYgZ68?xzHZTqRZ4t9ms8d)%~3U*Oy9bB)Rz3G0B>z)NY8 z;4t3_#qtJ4^g3TCmNzIuy#;xpXtxkC@DTM=;#V9W|dyM;N5 zyjWjVQF2sWTwQ6clJ;6p)-o&jN0wC<`UK#7?U4=^tq9PLf7~atI!rdV*P*D zh^RVW=vL?dR~;hsSN2#Dy5IRfUoGNA=L@~!{I@zpyzhLWkDUKst`R{8w`zh`19HPntLxj#} zsh7|y4&aExI~^kYkDbdxmplKvW(IH|p zfRNA@=YPIh#I?>B+Uoo-R*SgK`9j^!e`A@W+CS^qjlc~qFSN(yA719D_K`IQ5V*zV zg~~4fbf<`l^M#H%|G(`JajWx%jywO)^GCIl&VOLd6vRn@`U=fE|KE0qXgFW!jPt+j z5OKHjg>)=SWBmE*vk><=U+6*S|JfQ54>@1x5$FG6m54{3FBFbs|EEq7PdZ=dDFFNb zxRXlwHv&8ImZAxsaAXCNv>eD3p8!S-V|IXpfPD zLaw02!me1t)ui?TZO*5XVv+0_XDu( zI=>A68e}BJb&(4AQW5rHN0-pTmInuxtoB< zzX9GEpda2X3BMoSR2%-2*m(o~TRgML1<5VJ8&j4!Ktk1 zcrC}j`ik@C4g+PN3PkyjpnnAX82BvVzX5#?Q2tL7e%}61@qLDT!7mfJUqNsCn+|2L z;5?Am7w=Ob`m^nC;U6#ooCY2Q9tL85UjJfz*?wrf!%x71wSVNW`O20*qIV~0(Nh7g zruJAK+uK;PMR)`@m5?;$C?7Bz)1E|8`4G^bZ335G?o+5cQYCvHqVT`<=hz z@G&^@e+GT;M*1f3I1u@b{}_LOv%q(N$UmL%pGf!{ucrS16`%@4`3IpN2A%-Elkk54 z{Q=Pbch8@D9fLlgm*6C{{sGM(rbaPkoQ)SzY03dkL9HNs}uQO zOMVltBLiK)7T`Ld3LFEXem+0)V>>9uil?9x9|cnZ-lYt|ZHc`&DM$U^7R&Y&@Y~v1 zPG7bce*^oHJsaR*u;58xIs1RneEITh8-K)mxefm>+P}Xl5%K+>CKJy_kAK(qJ;N1WpgY*80cR%F{ z-U{$G`1`iUw*h(+a1GFv@LxdoW#A3q>aVe;k=LK(?}WY) zxCz*k@UKO78_)pm2V#A`^G}F>fNk550rmvgiN7(vQU0}^`1$qo=Wf__{@gww^50yB z|Eud=ejTz`19?5!>H$sycLUL0%Aa3`znuKNiTrcWD!V(B9qqrk41YKL3h?caFQ2!z z(fUp;0`Ozti$wmZ zW%$2@U!lBt;DMyv$wa8Ab?0l4WQ(_iMR-|ec#9Z>9wQ_f8zeLm zQa~Uy5*9BPJ>4}kje5G9evC#0Y_K7V9Y`REHBP{R7!qQen9T|d{y-vZAS8wu9LUEc zW`Qikn1_RcNWhDi{r>K~r|VAd@JaUHt@%_}ee2x1bsy)Rd#X-%jaI#HQLA2os8z2e z)T(y^YSjw>wdw_bTJ-`yt$IP9R=uLm^8K)_%Bn?a^*%SPdbOBVy~|6hUdg3ZFU``b z*H>xP3#PQ{1^vuL1-D7UPPl+FPhP6FOpFj z?dvTuTJ`D|t$KfpR=s&e>-01Ix))omde@3py$D5XE38+s)w(ULcd*sEJ*IsY#rjKDC*)(Fv#BfVeym5c+VR!vxz}VpgY^PdJ8tE1F9zVa zeoC(&nrq#*Auq3``><_yemYk_TGqPjKar2p=r_9E`CGn{=~uT}^{ZN~`o*hO{cKh1 z6`%F%9c;C}8`gQ7@WWY4VSR*c_jQaV)2~>y-f^B^?`Lar?LQLMC)jFD&d=LZ`+KnJ z*Q6y@J5T%i`J`6;JkpMLZza>OAGPWyg57c*(~teMJ{VU0T+h;J|Nb;Dj~%zIwWsli zZ9gpC|LgG&%m4q^<7)e7!ut17KUlj>-1>f+lBhM@@{~TO5Jz*%tfW`lDg3!*H)DP4 z#4REHG-J1&;=YBKADWVUGza#kxaeShXiBns4o3v758%ppTdq} zf5!dnC=O`6hMmh{eI08z(x1=vPIx46iLEVGpY6Nn=hqPKwRg^{U;iFCtA2T0k&rEX z?Vh<@^?N^JwQnAu>`B_Ni#uvd`F@j6#U4f0{trgw(YFj%N7q;V6~ko1(&N7MrFf=8 z_)5RyKCOPeUG?dU*!5S$Dz9RnJ{#X3ih~-fJc@-HU+Z_=r(N&WNUr=8H}%PSlAHXF z`&9a`vp2F&F8>CsSfxuqeePmMF-zmS+36pCYR62r(w-lAf%~-UAGvRy{vVP2G-CDV z>WEeU$0JsG6%%E{;`>XnPlvF^hvKEiY7fOsjbHLR?o;LY1$(Voxm@|@anzn<7p%A^ z8y0^(#V#Gf(vLi@+LOEoE1qd`{d!z6P2*`?K=DoEJz>Q(ja7cdR*h#OSB%s609Y|j zvp>Mz_zpujA!Md*ayYTLm_h7c-$Jp9p`g6?zdHJL- zPerWcisAZmygn$NYy2Y;+zKz_fbxH>D}U+xu7mRQs&^hI?MY-m^7y9DKaUglBx>(G zKJ3SbVwxOg@%B;iO^2}Rtr(~A+g;_^jVba6ANO*V_W(GLhk90w(ax7#F~pb^PmEbH z#h4Y-i&?R}m=&vvSur5vgSp;gUHo_#-`Bu6OZZ7ndTwJaS2-zq4tt$|v&nQ+Ma_G_OC3by@q$ektx{tnsVZm+|4` zXN!%GE28{mU$-Bcr2Jeb^Equq}xqJZL5%Ib3 z^doY43iltCv+Vn;QF$}ucSUlIzv)r=HU3HwtNj#{W5eR@v*K?KVcBEF;EZLD6^}EP z{Z$Okcr({mEYA26SaCUH*bA*}i;ZfLCXD|Tr7(j?`% z=99Tx`R8%Qp5#r^uckgTIIjA<18<7>6`!&DwEFFc^3(X4z97$E^__EE&Z_UHBX;#Y zA(u;EZ<&{~%BPqh8y2sx_e65d|B4q%D!JN2F+-79?V?Ms=wku#;U(!LB^`TVnW8MzhXni$B>_5M8?O#wpjnpIyslCKl1od zk9!kHcltS9=|3Lv@~-@q-*J)rj4rw6=YNgl=Oh1R#93E*-5(x!N?zVN@>0ZG;Hx6m zcgJsu_-gq6h_8j8iTFnNjfj6Ap1Lf*-tF+mBi8r9`y&1hye{JV;E{+Qg0G195%`vf ze+X}n_{Z>55kCjN5wY}hUQ}MEpAozMjQB;)SFD87SbCD5!HStUgnt1mR$?suQ~c2Q z9ps9E7{3oI#$h~VD)v9(Jz)D^#=722*#390_&``O3p@W%Sg{G?qhZA&jE{#EmoV;w z6`L?V306$P_*7W23F9@e;uOXjPl|~eyWeSf;5S+RdBk4JbGhu_yAjL2)gzXDyEbCA zzhW6|SiF5xjKU$T_MH{+H(!X;ALs<6R z{%w@iPca&k%ib$iW32kxKW0*T*?+}uOs@LdKU$Jp`e6UZOsx9Z|0Wabe%1bOn7ALg z{qHZa?7#j0F7X#8sjp%{oW|not5}djxXq6Js}0FjU;B3%V)>Ky&o9KPuVOuHSbYB| zrsNP-ee8eDNUr+WzkU&`zwDp1h}B>AZ&Spoul<7)vFdC8q(pq(B=u8liDWFke)bO! z#Lm90&E+?Dov-<(Ka$H|IFdLJ8g=R-vjT6 zSoa%GMXdXc7bDhqtViX$kMrM(yd1IaKXyg@IPGJv#@F@!7Pgn&i*KmRAVui~FCiT^izJ#zKC_#kdnwvGDd$Kqq){gEFY zrT8p&($Cghg?7vOdzzX^UHxgrI+-k-tmpih?n58!F& zkMYa!tCZjWT8#S-_}M7G-T8gj#pFk|Tz&_^S4Z*%a25GJL4G=XQzX9_zATbo3EvsX z{{X%>;_YxR>8(6F;IF_|pC7}vXugzw=W$(cKNQpTX}mqNSKq4!>VJF1y;$~4F?V`k%*({q+>PEsr`PrGiSpO= z--=lCm149UX7ToNTg0mG_K5$j>w2=Ek4M*&zwyRr^Zev*%pvy5VV0gm<=YhTugGtE z#8Y^FbRaQWmz?yw!z1BmUwC`OvVS`w-XHm{h*jTvh#}kgYVTH5K6k$wvD1gB zKAImDbGGv}A1eNAtoBn3+F0|u;?c(HPkXDh>aXY3ibb3J5X!5VwDDoE;?u_8U`H`= zW7#jos*Pp86t^}$hVxzQ+8yxvqgcAhWv>)dH|}Rg@pa?lIo}qI5B1jt7v^&H*MkwC zNcs!T&*hr$wtX&V&3E@ltp2+r&E@K!9T()R{y8wpU;UBC;r;luH#)05UHeDroqrXv z<`2czm5tJC{;-$oiZy>I-tNS(+F!ADW9h$Q{KjfOdtI{5KS}M??hBn z|9&K&to*Y|`SrTXx924Jy_iMmCy~Dv$&Z~x{;!dI=_K-BM)K8@$fpnG*FSd>`HVMycNm6Fp2!GNPf*E^7|wCS0|A_7RlX9bwY<*fB!U+ z-`;ipH@jH<{c4n6e8h0xzGB6j{Uv%5@y0H$bnz2i{7e_`>f-4WspFZ|s&z5L&p7k6>Ji{($p>E(aNEdM)Z`Li+0pNv`l zbIkH@W0pS~v;5bX<&VZJ|2}5=IM zzrj11Uo?O2w;Pi+UtxOZ6M;E@HhlKsdzxeGnVR(Cb|f>CsmTTKBEGYF za!6l+_s70ILDtH@4NL!Lx%55B74TA?=UI7v2iBaeWd`>9Yw%2-FC7W1K6kmfUZ3y5vNyW^Oyp0%kM73zm_vU50N+dc^8)`I zzMb}V{Z0D!;H6yu(?P!5o=NgF@n8+J#GdlcVWR{xQHHhkrZy#6P^i|8L~|Ifh3 zMfz|q{2lto@+-kRf{}enx|KR4k>zxi?80G))L5#=G zJ9{^>qd{rNrQ?dbgH;71wn ztC6cerf|bK^K|+zwD(My7fJO_g*Dy}hu7}uzh9&L=EDy}<9RuJHvXTp2aNyq@aA=S zdu8xnVK06ol&1wBa)y8YqWpdvo{fIm___(+oAGV!`ygzK+T&69Is9=~Uat2ecs}13~3-JHKztZy$@h{-FVas1%nEXq||J)$ohlDq9{Yv12;Mb${bKrL(dvGE=dkViJ z3+eme0@pYF*(9dFZM>D>+tDBCtIG2Q_)XgDzpA|{|F!T|?2nDt+u@b7^6~Zn+`C88 zdjaRGKfVvoj_lzR@EgbxV^60h&%<$j--Kg*>R~?FJ|*duy;XjDz`IBG;$T?*32N!| zX#pI!$Eoo12k_l#8+Jk1>%vk3OTej{h3@KcdgZ&%ixMv}iv0pKxB^ogcgoAM!DNcfn!h z_Yo$_$EPQ~(g)Rl7JMc5>uL|l4~KETcIZ?3b^^RN&%>s|k}rqTC5o z7jB~O8-lzH$Nf`-_l)}YTKL&$JlzgIy|14?b-nxGrzo$k@A@CUn)$mP@_z|_34JyD zyA$R{xEFKi(og4$l+UA|kB9WL;c1M28;_^LWZua?h2#FN!OM^1w+X@D_&oe2`p4?O z4W1XB|EKWoQF}cIuVB2(A5eY%20miH{QT$P8)(0ag8U8m73%-Tfj>&uE?&iV`~x2X z|BCTy`OSg9NPSU1SKpK1hq)hg;}8Anhu@(*SB3M}!_T2_HwPYt%LDiaL4PlYe?oir z!D{a>!#kt#b}RhQ=koD-H~h88zW#;e5kCp5KlpUx^T%+;^Qwzrwa3fwz1Wv!fp^01 z9hU3&ejnp>&c6b=%5yxt`lE0t-x7E!^R?;UT3G()i!ME{28BF(fKLU>JTOWKc`Yg3qeNKmC z`+q(h`0X zOfmk1?1kFnJote$ug_-q$Y{J?4Ij_=rdTfh&G6oocT?ay;9Hr`?0VmYmm$+4d;2il zvsZ5K{|3I~P<}s!T-SdNewOj_a^Qb~&qe>XiD{poF&}5NpS8!LgJsYC^E{pZE7H$r zy!3_hKZfBsYBu?Y{yGA_p7EjiK>Bqo{1Df-{7!-8FCcc;TMHkHJ+Sf>;78F%>%Ru9 z`*pF}|10pvsQ)sZ58n*$yPDrj2L2X&HT`G$azFfER{HNNEB%u&-y!MM`6}Ow@O6=W z=;49(@z}c~Lw+;i!s2}Xo5e(Y8S{HRoIe}++EeoDEzkq;ZBOmsll~x{0q=bT<8wD( z-qYdHNWTW**RjV7NU!qMVLcDvw#n7^^YHN}^Bct={~h=s`pf3qTjATWH&)*N4nIlz zS^xY1Ue5er?eSCiZ07%J$nSOdLfX^x^OtZrYL7Oo=MBA~JTpG<-Gs^dA4y(Pk z!LM8t@<)H~glD2pI!*HX*}1wm*Ox!!e&VV~KYyr%QTcxiKNHRGFTl^yze=Y3US;R` zWAgTW7v3LxZuRS-J!VJgKLTGjh2O=7@i7yADAJb$;GT$&f{!I$a#=|KDL9@Fegl3Q z|H=A4gFlP@xbaK*TJVkZkM;MB@c#7waiRQoz&+R#n}5Cy>v>Br*VFiV2);1M_33Zn z=MVM&9aHuHIh^-LPx3Z=3FG4mZm1-G5035aN3kD!F0mp0nZ6qw@K9 z6u!F1dsQSyU%m)m@`>Eu{vmuA{rg}z|2vX1|6ddMN%-H<@1q013_qLZ{_@-KTao`i z?GS(eP4l|;ody4DpZxyl2r=bB?8o)Ncshy%3(C zP@ggQQS5Um$bSpoiM=uVunk^{{pbzyyWl%%{|$kkW_&DS{x$huAfJu>F@5_Xd@T0# zb0PizfR7?S^{>kF3-}hsm-TmYWNYrcq);j~<`$D9OLOPzW1)^NNDB2*I~^%hOO-50 z7fHHUA4`>_Pah}hV+o1tX(Ovwwx-G~Yc}1HW_h^kc18>3YPiAzoiKl%`<(BEwNi#~ zu+YpFF3=8VwMTt&cMVsdMdA3V<>8gpQn^q~2aBb8qg-vx9m&RvBZWpY8Eg*?WgHp~ z*P5Rc2WyQwBCEpuCGNuR!TlwRKT>FjknUVLZzKe zb}X&ZO2tMtnpIo5+$S%NWW~)*>fA1;g?5QDl+vu?k~Y)9cB@sZCdJ0qdaFj7+Tg_} z6vxwMt3BA9TT9q&H43BkTBTgg(vhrE&loveO7F{w4vaxHCU^+s)c>qw#ETUKpU znyc2~6BXc-hDCK~H9p;bpF7^?_80m7c51T#C8L#5CO&in{Y5M&B z(u6{cs4&A>l}4^mg+i;{>>Q>EQM{}birj7vsBtcK!)d*^IvclbS}fEPuN!H3 zv>s|E&14*SeQ?*G?^JwgQZET0%NzE|%Bp44r0#zIeht|JyK@&`}!o5lX%;y^y-5`S=sKe(hXX^m4! zTE3JPYgM(ms_05HKNQBK4GnppvQ=qhml%zdOST(zidQi%L*sC1RE#V0>Gxd))n=`^ zG}(%DOSwwU>2SMT%86^Vvt}zTWzAxv%vfmTvM?ei6KyGy$?7%z>H4f*Yqa_~JJ~pJ zYJYm_Sp$NQl{>UzXgHUzpaIIm1Fc4Ps{U-OT+H%>ImdbVqi44ZmGV$IbD8+-o|d)3 zb)43A4o-4Hlz@)a94u3x!69oVpb z&6%h9+}9SWZ8C4Q8(CwL^VY3DtAFE)4e6?Z31_Wq)JpAQYmyTNu;zuzB!bVbJe4Nb zMbm+m>(?wl!!mR#Rjvh%8c!?PaG|(WDkYm&C~jtVtekCOBi(dkF^oFD?}nvD9s|ND zmMj~J)#px2!zfTCxt?-(UfM!$le9E8T1s1(ky?@#nj@8xOP&_8!eBXt#zt!;Cvykd`H@O_ zaJX1>Gi#l+)I2R#E^U?W^2ohs~~Rt&gakP%3Px z_EOF1exhVB7nv4ikWcLEnB{~yA{)~@>GK!M9$;EXQ>H1c{Uph!HYS!WN+4P3S zD%s4cC9=kKD-~J=sxxn#>A;yMS+S5`nD%fV$|TFY+>fc$UC^CC;g<(*u9E@gXLY%h z+ca-uY;gJJaoS(TZ%msbwJphrs@gzrCJY7F6TV&y3;AuYRSi9Bj;5;fS4I8Y{>zmTGE6?1Rt~|4!b;J5|o#U?RNGqJH z>nqjpYL2Ut44ktfT|wbhptN4wf^$b18fa;!2y&6KMpMd;A{J}4&E+i7z#7FhZ8pzX zdFp&jU|JkkX+~(Bi?d=YZ;GrnQ0GKtJ6I`f&K3;1tLOQL`m57OlNE>RJ^`IPCRcfL zwlz=eegE9YZ`I`H$GtsRZjIu&SytW|B%6An+{p8xQWJ+Stt2%}tA)`l<)$Vbqt5cq zTrUr_N-Nh%Pu!4p!^i8S^upa;REIgOGC_~90x~5N%&pIEMXNGEed&KWsCI=ltsz~) zA${A-7+>u=hvb(?BPpL+X=1_s9hg%BY}2^Li8pR%ty-0yMz>IgFl8i7tZ>VXmsRDg zQtFfrYaACAlo?;*i^7NwCd7|Mr%YBa*>Ub;I^)}oS_X3w&G2_Heo%Ktd*pLyw4Sv} zBW$=*8^mF+m#b;HDbww&j>@N!GjEm3eZDZxMq#+tkP@GKg0U~w>RXw^b@SvdDg)=w zawbc?+tdt?2VN~Sw#KzrbraMMd4+OlU@_7#8lk_vCD#3gsYNi-B}VIDtN#eh%+ zKNm8owe04ub4Twc=P)K22WXP(%dQrR2aEFntTm;_l?Kl#oEzaDQmoIKbv%mf42mCl zaGJ8b?hO`(D`}-Lm{rn7tz9jlc{pTk?(eZ1>8Nh4hbB$}O^szeA>jGNAPpN|y}osc z-EC5LH^^Lb(vR!?gcB5Cf>*(FjcPS>=DR-)Eg$A^JDbz6?@`l+#}u7DISZ@8m5TXblRVw&v}yO_o^JDHs>ev@Ifdo!p3k}Q*LiM4Pg`2=AVoSX zE85pJ;yO=VqIAqPORx+w#jc+wiux>n`qAl(Ig^@=UEfB}$7B_}cT(b3aEy^9EgUS@ zd1Nv1rlZrEy78Ik92GaJ+jZyRPSi_hNyC#duF%Ma%UH6l&d8W2C!J>`Bxh<~)?5>2 zBz1Fk(%&bjkj_q1KlzEy{m<$XJnh(`=@BdTUw2LUP@#mnlWJrdE^2W1JQ4%e zs*EK=nBx(3dMEqN-9@L#dC*cG^JCa-6zz3ZyY+vS!#ufsUJc&6jVP=#Zk40cGWWP8 zJQeXBDF3q)56RTVVs(m==!_vCg(1H)1DZUuP*GmZEe;XsksTst6sTT zt2!ruSt?AY-kqmR<{WlLFFl3ov)pLl_NX(aS+UeCFhYl`x!>$k#S;vT zyrEI3Hb*JF?}G3kn7dhL+mkToI!By`qtcG3o?Z{a(6NUVm8NcWoP}`Hz8;_ZXXf%> z%%zr6G3ofsBE4Eu?J9{I0BNQ%n$Y*PwsRkt6wzhWcerWltGhdAT`_~cjd`No3N=`+ zf^!BAmaI?#!(YMEu5_Cc-}P2X?^xgm<_{&96EIYCZp*t@u1(eHbVfV)d+VLW$^3k` zs@7OzkL5c3+381$w8uPANB~OwyY7(?U^?yVZX2Ky`Oo!S5w^;yU)E%0dkj z%iQ&7AE7;7af4}MVskD^*-)WPH=2g8a}kv+cvZ=z*(g`74f~se#Ba=0YtC^7-g!6f zmcyHl+yM@c$P(`f=cL?$Jmxx4Ct+RTyO3YWVGl?YYTqt{X{n zF`l~RpDVYjpXaToC3ATw-~v!{m#<$tx6opuW)jVs$=p&l*d9)~ui`$rgXQWF&ohH) zaFA!9MhZ6B{*Z3Q3@C@~R%%7t4cQXJ{7)`~If-`q{~PY(wa~Wwk3xNR(A*a7$V&w1Gbj$K(sIMZKTt`61Bz3H^>1yN3u;f z^KOL0^CPyW@2S|n(p~wz-I70@v{yC7Hv2&0X}gPoqzhT@&r=_(iAJ;yfm2Ep7Hb!! zp-Vf7-?Ic;P_!pliPpC7>BZ(klKp+>Y`AEBf1mgF z&+o63&*x<4%$a%SneROF%*->-%+`gAM22C=wLg{!guXH4!|qx5<=QNSu*5Kn@I;4i z$s|$zPE!!1QJ(<>@fh$!0k8wmm;-S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S z4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL@S4FL^-|6dSD?Ap_D->YWj>0!OHc;F$C z7cI&iLq~TKy-}(XiKA|(HE2}4LngUOG%9xvSp!7*X@n?$j2M;aLmR|;4;!W_ZX2o+ z{d@KanlNVj$JW;PCj_{&cd27Vrxckt`ATv!LD3ZuDB7H8i2kMYc z7c_+Q%E<5&C0^0Ztt_x8{euS47}Ot`gSxymK=>Sy_ifGid8f`nCX?_*Sf9^F zYNuDeH%#$9GPohbld;>g=p1oQ5O>+UqFCbQ{40r&@~*jkr_B`Hkv35*K`udb0FkGM z1?1W>NQ9ZvQP1>qbCJgsnwUOrT7g8&A*=7r>5RGXlNlq8q(X&?Xq2?>J{^8 zNnBA?9{8GZJeIUKcm@W z3^PS~$#?Prp0GOQSEEL+PB}f=Euu(5d7x$E$3Js4vUSG>T2`!><1G;_+}kxI@3ydh z@@*FzYFHO9F|K%txTc1Uif+)Tj2|>Ocrnuda>S0*jMG{XC)*a6h$U-@%PEzJX3m|y zev$)yO+R?llaAixJDD{0Jy}{J7F_@SuL0tyUUAeDA7+r|L7vD_UjGZcl60N&;HVe< zw{V3HoZml6`K7#X2d_%kcRMDCu_7DLy+9>t;ebhO-|6op!UV|xIc}8i5OwXe@5iBM zmkuQGL^s=QaaeTq9-X9fl;HOp;n~*pvaa2z%pHged}A^C-ddyZWLQ6S>qUfZ?ixmz zB(Xwm*r4o;92bamK%S0zKNz7sWshuDq^m~d=ztNe@Xl2-l}Ni0_v@=)hralF$>ux1 z4q42V4ax(^yE$xd6d>=!$X()-w)Pq(P{`L20XZ*>ic1Lp+VatALD8&C8Zs->hS-+Gsze7eSDSQ&Q}_gUq*DtsE&&Z8kAo}bnR5f6@?QZi`Nh`Kpz@-o$}+6+juu*<)x7a z_&G8WZVi`+#96w+Ydh^5m$}{N@#!;l51+2yRz1_{skW6ia3xixp3=gDbILqrg(*e4 zYQD-+z*ky;wochIlHm2*bxQq60qXgkh)JZVP8a9mZP_Sa)otS|sZ7?4*myrmePm># zDyhDaZjrV%l}Y;d<{UyoS0j;XJaG`%@2(@{K|Ie;;q5X*2F;wQwBhczI|G^9EBqXB zdVKZvx=tQ7+8gnoq_pUt(zU(Apu7>$iw5Oq5rfFSgkB$*?VkXxCUUerD4&U=#R`T?24RL23;DVF(QY|TSRutju6D|QD_JBmd}YP`9Lf21r2r1^ z9yRd>Oss!$r7m1a&Y2v~vv69B-M%s(-5HERoZiAz+ za*uqmWC>{cDjwlT?gN!PXcnk6!=sgzJ&UH>N~(y9gm3Sqc`Yi%8NE5;BJrqgVO6n8 zadfo3_)O_hZk9+X7WDoaNF`u|I`3c_3)lS4$u{}#Mt?f@%P~E0(z`^Q#gXj$)zSt> zXboCW+TYc(=1H`q^P{vLRlSQu*(O#kRLgmLw4?Y%ltW9Y>V3XsG5D^kc<4Ic=T%CN zg3@z~Tw{E{r1JfOO6|EX@%=sU-2xn`sPSURiMEAGcQQB<`d|Lftq#L4J|I;DGr zKELDHz|`{Zc;|DJU(cwoQp!2DielsXi1M2A(Lwyr{3sjpOKj(JPpxHJXuU~~?IX0) z$Zs|B2_zTd!wm&%=kb;COFVhnLf)Z1f62888o=EaZ_>Id4@xL3i+%_qTtfDo0 zOMN?^Q@8eax!tR2jec?2p!|I}TB8`XMqO%}ZEH`d^*MG^eUiVTKH=5LU#QPKwLYg% zpHs`Xjn$_f6lawBxu`@xewm{C$mI<{@1EWzVzxL7E%VP|`p}liEzkAds(3Uw|sMh4wUfMFr|4YkQZOgvY zGQS_D`3q5#!s{)wzgGvnLiJO}Y>cU*biG!wDrXk8R`XG-f7vFA(1fT}{V*HMF8|y{ zWi583s#3~chZ>z;M8f~*rFHtBUW&(GAg{#6gbLSOleLd}4NBHnjpnO0dM}!$ZQEM* zNVIhp-BhRY|7abnExfw?3$62zTBk3zPClr9gr4HA+8|Vf(Vx*8lfn1o;ooIHu>8_k zTf8)Uqb&}od^f9n2ax+ba?`eOs}l7z_&#$rDp8m5OQffe=AA{fEut;)clrNYOC)_o zOGwbd#3p6CRk=Q|zTGU>@5mU#$GAA6a&cNsb4{*ay@`ka3v&N`m6rEK_Z@a$4+f(NZ%4~3?gAOUj?_*&~Im*|A=WZbLf8lD!S8!E| z^18=*{2e0G#vUQzF2uQQb!(%2{zYKZKNM}DBo&+I)bH1@38~mbb=d!<&liFV_m{A# z0gV9G9DbB_JpHNu{cY42(HnD(AsA5#v_J6McT%$Tlz;$wrdV`!duTri;A z!)k~~)oMyMGp^TAP|=8D z&GZWOzDDpx96}lGU`2_eHVF1jm-ElRmzNNk^AKH1UF)WHG{c z%uC0IW4**>mP*A$&Y=8oNHAL8H7G-aCYjEN3LOoEI1NhYkRB`Ij*~2ALvIX3=Oc9; zhPJcL$9; z$#P(3h55=*oH}Fm-p6CDFn@^27VtZFh$!C~;IT?HDclhKWtGxV9cI6DZoF``<75q# zZm6D+KnKZ5I1qmz?!er;V%v@WjUt^-yHQ`d5-b&7u|ArN=Kr2K6pAxMNWDWN6)gab2lU&N&RX(XW~uARQUGiXvi7?~-|PK#jAJ6@!c%X#U3;JX+Mk&S*-CbbrrFp z_?ASj&=(){+v5WU<*$R+><|X!pMz05#6F2t{v2VvtkM_RhPiOW6kW5iXf=Ws@fCNA z)6_Yz9_s^xvSEPK6lT;H`mx3wc#e?7u5BI63Emg|diMb1bO4Kg53sOpyoc|Uj(H8u z;VY(el&*sv$<5;%^&(TVm5*yQyNP2$z#t?x#)4Y9ird@)yV<)tBm*jrC{!?c7z{DIj06+L@B zD($hHu_KPQnVnX9umT_j}uJ^d^^`3DT8NR(}qBy>#DXoW_D;mUi!d96$qID&5Vk52ZOnI(o6jPcK59l%v z>-c93ZpQfs-APZ3bP)d&4v*U(-xe2+J$zT%;Kc71jXP+PV-IgMmzWo)eL8V(5&de@ zncd5<)-*^-_-_vL4gNHv>@#;4IT}jLW{$`S!IY5oa0;X|A@EWCN^?#_wHd25k@Q%_ zzS`GexzJyz_b}%WBU{A@A#a9W(DxW!Ws=E7q_C`aS;J(SIjrZM_prR<9)1HkU{I0< z-<0Emby#n(LedelWD<Gb4V4=hmox0x!njgIBI(4qW(M>U#n^yN*7N*5 zr){v1-E+#uZ^+(4GMOnl>ABjMJmlh*wDlVz*{7TXRakKRIM*oDh1ZGd2_l`G0C~BD8316TgKKa?nn+mTYeCy|krb z4{zYUOwRSEb#sJq=%)k8}BGhbdo(y;4u(OVS!_`Qiie!d-;udCM3z>p`^P z7`>PSlwtz%TH3yMFW(SzcAFU;U4O|QmN)*j4dgY^tjPK8ae^bEOaQ!>l#2oZW*^*9R>@{vLBczSoE}rocBg^n(8aC))ME zZDI`43Y-yo!jftZ=5eMV&2x0ns^+n@9lf5**>JQr7GoT9P|Xo);H=2u8q4wY;L&?6 zkP+J3tSEzTxKjV4S{+J>X$I6t z+_dS^YZjfL>si2?+;cEjsp%vRBBZ|invpbG-c8`m@?h$BNq3cASun!1oLUU$}7lT5#28525Ys#XIKDd^8&FV#@USt2bxwV3$jn7!&I~7nVp@%u z$0?f)0khmawYbN+Z-H}$oA9$lLm)PgD5i?WfSI$vx^DWw8cgFjF1{t;sksyvN)=uwvBeOiw%%`hw%W;*h0-H^KG-;o=dJrFN36De0V8z{q zTvJN3Ej783xR^_jEtzr9k(a#N-OIID-CR>vD6!EQN;z^mP1odiTHV$rH~z;p@%V3P zO2q%zCNut!9x;9)z>AELQe zjL=wv8e3PMGX0Vk^nhk+i0%q4P$^neI%&b#(46$kM5LcF5`(ggVKe^U zJ;wJ;IW_pJG|D?4<-P4^X=!RX9y{BVh!IM!Oi+10mX;J`Tawyn$#El;hVAfe66HB9 zoEDtV*@L-byo?V~ZlZfs1H;jjPL-e3V4>|APZL`w$P}$;+AWyZDcBBA z2kyU0qjeY`x`zDCxQB+vhoZ>4xf{5DXV`XloLX0kY@WIow_}W=c?|c{Y1dGHd^k?! z8U0ZB+8EE%Ri0CWzgCe?zlratw5(ORP6$p50SD&{s|emcz_%u)GB z3Qh+?QBD?&r9*o{GNR~GZYH)yWhEN(X4(zBoxnS5*eTkB*AOpI(M}!SoWV?7yaC*`0?B=7mIq^mu)q2oJM>{R|jtf+Ss-eYcL z9qxM!0lvx@sH`%n8lw>N_8(R8&>^=E#s7xMdc|Eo}zg=$p+U!}D?PU_8HS zQR(gMfWG0G?5U=gJY-AeshxT}w9x^Rk(oeHq z*yFLDBO#|4yLKu&-DbY6+^%mjLc8c?Lsnm!OZ=%jSKK-&4tqVr?nya0Zp)%4?)<@8 zUCX!%x1DX>?qNc|4|AeU4q}c>97z=haMLaB+7cZXGcfWbV0Ld(vWDh}BvX~fBQ9N0dPZ|(rjbmwOO+WUmp!$%GU0bv-uA<~1(gMtg!I z#yzNpm|P;WM;^=Q3zIxU*r?KCLadTD%H+-#>A2S|-ny9(4v)1q%F85?G{s@Z%A|Za zNT$t|1_%>?Zk&=QvaM64jGBb3+r(VAej2&>Z)lFAMfz#%g*Q73C+mB!7nPJgw(!2e~gCy&uGeu8=#3m?Nea*kHR)7p(G4_7$Jtu){hX@x!Bw4(gAT z9n|rqfs*}6-r@j_XO#Cwh`^mUw38YFYfO!Od8oWaS951g7W)y$9Ln@>uX$4qr}){; z?#+btb7p@5)^QPjv5MjDv37Xb_n&n-JsNEZom!^^-V?ad9&IT1%k9zp|3d4~7iis} zQ~MQkp2I$HJ!FGq&GNC=Ee2@u-QM%4{kY=VS0a}j5 z{;MvF?xC$2#V)Jw! zmq-rhoUP)2`>1yx+g=>_#4ZMO?F$2O-l72g!YDRy5q};fp#@^XOM>jN(wYWXv9LP_ zq*=KbGolDzqV~rHgav=U#cb4|@@KrxU+k!<{b`@k|I;XkIn1=_Q4Xhp!)fktVotZc znEM*+75aTE4poGh#^oWlu{@+}G=ye=>nkIIwZ?acj|5+j93`dVD=-yr~}0Qe*X?QqwIi zAbJ(&bW!)xmg>3HoBgx>EM}G@OYeIveF^5Fta5my=ta(1p!Vs*@Lcf*m|$_BJ)QP# zBhaH?7^N1aGUwT`Hl!#mAV$zub;E!3vdy4F^Rw3_(sW%ztWYW4tRX0$G5;&AUrBG~P4s5|FZ7bWlAh&D z^ltE#Fh=K#W6g{-<-U-Bc+HWd5%f0PqkrI9q51Wyabz2{%Hr#7zsZAgjbCt{` zUeRwOwWmy$umj;b+nM@?dQ0lF9_M?|%CPS5wuS4P>dRBV=W)DODmL3jb7#A`hbMRw zxoh-^ODBQKs!0F&2pz*C{ZgbvY_JjM+x~I#UE)tynPDSyVo&mKBSyTxw#u!(8s$6S(79?YQBLzh*jM8 zYp&GX%Kj7nRI9j{*A3U>Ew9JK!p?mC{pNI**E2VNb2H8L!_9r07qGvu-KqzL*HbE& zRj#u!l_ZlBm6K`dSNl$FISbzFwhX!4RZPt!v{h=}SyODdux49LGP{gBT|?ASn&{me zd7U_X#W%j_Hq$+lWIb$0uX{FcSKr>)Olk?+%@I9pH?RAO6T9xNWMREHzlq68sa#sQ zuhPwNm5H!1F$0OPgly}GWbfQ$JcBvf%!DfQ?AJZ{64R7aBV$#&W7Ds`7Rh}`^F z-0SU3+b_~y@#rqvV6kV>dLRyQf{wF^5ptxE6e8<+Rv@aNzI zywtte_ea;siy`~*K7Usod{Oig<7i-sk~ZK!spI`8JAfC~6je-BcKWxlw-f3A&FIlB zJba!iUAnMPMX>S~DtVy$bL?q!ob+|=>WCBdqJN9uQRuc9OA=0#W!%|LlWX<`CTE7Y z+x>>a>X{+^(ZRU=u4K%i3nb;l?qZ1p8WuZnu#vQUhjSNx$HVnpOY;`eOc(zq#mUq|t}SAbJ6N~f2)mtLW`mU~ zxAzYiFQ&W{kVeQ9>&7|0ZqU(w+aA z6j58O-0rEYtNasalvEW~QknLjM9XdIb{V8?Rm<(HRGzV|(kyjD_ng||c9yzNZ;JH) z8Xj0`eziquZQs~(!Ni8!8;HXo(^j1|N?g|SpKZ~-Nu1`}TTSe{-9=}MbZ;hN*OKA4 zu|s`yFKb5!aj}vWt3O6I<3DDZ`*bHVQNPLoXU#ItGC0qX@EMaHyIbAj)6hwEs$SX- zKS3%L-COi%_iZNgdAeuHI?P4#rYL@lOwdCNzlr3sw=q@b+jo0nB|bf+CXyc$US@~> z;0r3LoJIQU;lmbP-O0`r_xH9m!{75;h=-k4&`ZQIz4C8;5&K{JND%E5BCdq6vua&c za#eZiHqYfAS7q-O)@d!eylGvPnX9ZMxfl8v{%Q7I$iXw1mFlq~*|sGyEIgR+<%mX|V&mm@8u{OW{u!C_SLhT_|K{N@a?s@G#o zm`uhVbC^G7o?eJqxlyqU>G+wnUdki%aVKi@ovqPV@a%&9_H}RsH~TKxmIY zZp@_dpQ-WihS(>(1iOv$!637r169Vsqn+ul#Cu5L3sb89Jt)A()u{9gwX1lVC82%S z&KaoZheL&qgxmsg8cNqGe;Asf;yD%jx)zS2Y6PnDFvd?9;0yNA3*W>3 z1>0;;elW_mCKTz4m|%UkP~(QB-16>5?5O-HolN~0e#r3LuJl&I8m)R=C|!S0ZB``j z8xotpu7^y%7k!R(t-)AoYBmR!Qtt?^yvnjNyCz>On7PTfxa@9s{>(CWN)aP{6ISg< z9MgRpGFaF69^Djk5F^5r`6y*#z#NzmsI+g+Xy}}9JklRRZ(}7>i#~70IdgD|JT4oS zR=FUXakz?p>d6kTv)5)=;D!9gzyu&klpkXZxflC{Ou*9awvoUDNSYJvNEDcig!1qN z$i%M(T>$r&zok1LDi<~vOnoJ+-^Kk3!M?Hz+zb~-xw+;@z-ba)@k*Kdbn$)j! zz0pyJzD4*u6~YE`YU}qcD@oXDPne!cW4B z)5gL{2+tS`#~{3NENnn{jT&aXkv_J>Tuewc-WeDH=}dt4E)sbTFO7veGrZYcg@bz7 zbVisL&F~%9DUT26(bwmoe(jM@9wk=cv4V8^_H2Z-C6|1&;zP%=?SFTC`dHOt#M=H? zf>+p)04m)omG4Fhc|YlJRF%iTFZP6^YAL*Rlr!+mp04hc$EkCKZjt)dtN}KW2z(mx z8}%1@8vP_lJ(z7|&%q@fK=Y_)6a3WRBfk}UDv~iTt4A+5)tJ(nXVkf%XUE$$G3qRN z+m6!B4Vw*_MUPXv%&8qT-_s?Vr!zYkezgDfktM3P8QS&nNdGef#WI`6_n3r?F=|n3 zPN}o5+>8{|6Sl@XP+;Cs%A4!l|)cUynD$ECNnV$GXm!lsVdeWz+ z@U#TftSU9PZF$LAJFSn&5$QJ!eB2i!B!`U{x2&*C{5VqRDJ=Ag#kRr6>3r+dow14L4Py5V*E3rE_JN!A0R}sUqy3p9`}-`y_a&<^ zONtS`iyXjyg%iH3g0rsT={~RMu}$(O?wB}ZDsOa*_E$%qM4a1}TJmpDErnKR`BeC0 zF-{BcF^+y4UyE!h-lqz<9h{g`U=y>{zUrLG0D*DN(SC7c4bm*cn)$@zQ+F77>i@;U zU!CIesfx+HSIhdY$sLu!*x^Xz9@xzVJb$i%EMhG{l;n{TF~Mu)mS>*Bu5(H_25ahe z=k7IZUR)3DJBu8Jcb8_nSAWFC&LquE89ki@rEeIetAlQl$}=rP>Dt?IMO|Ze*8n%E z8}f(s_U1c;N$}2pQ1!@vRgLp(*5#3N9del%w7sdW^@#1_qO`RvJgHB~@Tx}MGBISc zO!s%l!`Bm2Wps|Or*5FL$&Wi8q#d%Y>TpzS2x*PkUJ518g14mO2B;sN5Wih ziTJjy9LSxo*o*TtKX*2ujfgaSWwig)@Zc46f3=@7u^1cm@KeO&_9T!56Cn%lA>=Pe z@Hys)R8P=xYCCP8@Nea9^jErgC1p};r!JqIJAY-7_>paSHj}GAc<+kmY$s~}r*;8* zyY1P|DO<=C68?o;p8LnO)XVLs@{-*5SFg4|gZw{+HB8s|a~m(l6fB$6&`FfSp>LpV z+n@9JjC{ z$gz&P>b+OKh7q*TmQQPIbMLJv4R{{IsX7vVMxFpIJ2dpPT&kvM&m}p(TR}b9(FQ4$=)nIOjlqeC2O>%6?8)fP9CdIhEcSIcn!$AW`#dMfCsgY$no{|8>$-8S?K z#C`fP)!|f^V@5}eBZTDOxh4y7kePTqDR@{sIy@H050#letRkeJrDF-KO7#iAV0Py6 ziWS_tdRO)3s%HE1zBKEDK3!%S8{_sPlQ zM*UrMJrn77T=m1Yx3^;@J>zJz?)iGjZk#dPhFKETYP1gitp@mC5+NNrtlYM2C+2uE z#@3a{SRp*F75Ra6Fpq z=zYZCc=i#RdrIj!yLE>HxGx<3Asy$}vcmVq<^*kc_t>&A7ifO6anI5{<#y6UN7U}2 zMw-%=QNrvXSV@?0PRBrZpqg;_&%t0;@T;r-KFY~aUmfozyq~7l2Nv4LtFuw>kR@jv zcBkmBSk_gOs1H8%C*hgtLSCkaeu_a#cOi} zj3a8>--!vdpZYns>oM~ZhnOJS;Xg^+xE^!X32J|e%6*KKGA8xQAoW!um3r&~!*Blh zWPKEEhx=}eOn7Ta3}iaepFKqJ5uNqr^tW_xU1uWrczwW(*#zZ-$`sX0$8eZ8T3-AY z%8U8R^1eNE{PuO8b(jxg_PlqrKWp%2dY!6_>q^SRl}kGdJbr3p_d#aq-T)E4F6mq= zdLX@x3(>#hR!$YIx3;Nk0)x^v$k(jH&Zn8Hs>>JKXCB4r)r1dN^*n5bWESRx8D4mV z@SoI~`Sv=_7kr}X5v+2AOo_p`uH!i$zHiErNlRNE>6t60`skX{fc3pyCWk-CsOw~1 z)*gQIWmw7+xt4~2M;F$)5-(ui5^LVz!xq@`;tGi4l&{WL=wp1YO(sf{uD1k(bfFr8EE^n7r^28|Ri#%%lRq~7%7o#(h6o!^7M@UK#j zclxTjalT>J;0&=~H_oSP{gC~uWMu6K&ZZhTSnnyW( zQ~B=fhGX1EHtS6Gz;`^c=L&7QwujR=Ao4#0Idz2RE7_nsO&^Mr3vK7r_>TsdpxZ9{ z^({+xpGqsSIqH%`voGq8LivKe;8{gKM_z9UUhPWBKnxegnKbUye}SCgetS>Q>lH zjV{9lojTibb7YIJn84yk^=)ETYANQ3+#5Ap9X2=FtjBo83Kqyy_83pSJ>Pvf#w!g~?KgVtSxXeiZZ(+5LzHe9Ge}(ssuGu*M>fxGe%FzeTaqcw< zg873)Awime_Thx3;YfdYutJVUpGpu)!c3kXF{zO#AnF^4g`~xn zxR%CxCvy*cYceEGudIddvDrJd;)j(#tp29|f%{+&9Wr>QE)_Vjk|O=5IfP6nFMLSb z@dtQZ4-x`tB0AI&=UJATIZq|kck~<8rv~g#Fv=)=c*aT4c^jy$T0qR_*tuX{u)||X zuec(7)hGVSDxr?R?Sx7r@9j9v1Wy`bnY<^pIf$o!g0v&+(BUbLu) z$uuoK0e`haCcfx>Q?Mvd?1PNcdeeI$oIEo>6v1j`}8Z&)G=u6Wmx*6d{gd)d9zb zYN1+AkIHmEJT{vTWB&#Mp5Yubq@ExHH=eKXty%Q9LtG#}G+sj7s?n8aWW7Iw#rM}rZ_}=Q@HJ}y#dz5X#ZVJ{0ZsUFDG52+xTG4H* ztEO{V%xe92jO9CvTP|iU@!LqY1J+AMxrqB8QhMla{qC9))tBk4z0PBWF8KvcQ=eLk z+%8#i<3@rHBo;OM)rxn4a&~FMnhva@vJl3-3^hN(JezUp+vuu{#_Y!FzN8`|jq5Y< zMmP31akD@YdRkp4eOs}|Xe?qgV;3u()g}^-T=@rP@ZC0dy?fH% zuTXu_IvnXgF{(%Cz%Yyd&BNFFI7C_1*Hq5pt8iBX>^Tj1)*$@*2hi^^2TvTUf5~Vp zZZ+}Q%G48!jIwIfw|2Z>zQ8!@+(s{h{)5vDSPA(~M*8mdmHEGZ5)w>cQK z=;>6i_$DXY^;nPbBW~z}xiSk&h(Vy%&QqLq^I5e$3h(M(MRMvrJ}MVjapg(rA_=-D z(REe%%I%#k=-ZIsm35u-T<~@4naA(wyp`5*RW(nI4UwEew9XlBo9a2Zjmz~~ms(a9I-5wNXBN);TUYXkd7CS*$q?UJLs#&V z%HCdTT}eV`Cq1KbfRSKjediqPnapwQ>9j%1#&PwwQ|SV%Z+d0Va3W%?E0fc#O9fke zY5meOtK9Z8%VH2~#>|P4m>jdr<`~Dcvk|4~@f3(ugPcGED|iO*4CDD19)fiiZEyWp z4jod{hw5~imZqcWsjYij8EgP6r!BIqA}*Ii@^aw^fm;|B-z7d_t4yDlLtE*V;e79$ zl@@EBxOoLuE56w)D}9+IH7ki#_SLN9t0yhmymB`3Crk4L=7Lu=uJU5n4t9@p<}fGI z`qEM5?WGgF)(Yz~r#wfPC^Cnci*s@qpiEPrvazd@t)+DF=?Zj=Z?*)e_IjZge!?zt?pW_Q(V}|xzjSqc zdrW|@pY1j3# zydW35@-JD%g1Oebq;uATlf(jNjJVuwI-C!kGFP-_nhrl$w2CX3V`@whtw+xHO$CJ_ zP{;!XcW-5lSI|qFun$&+_jxFt;bTJ9^q8iEs$cXy;Idr0&y{e=i?W}SS0ZOlFT?Zl z!>);!st|WX_PIF8-un%N+vN55KA|@z$fUTZ4S%>q%Qyg_G9>bZQROxmR9+*I;lKPv= z_u#)3ZLU*Zj?kO3W)0M=i9J@og3fPr%8w$C*-v;3;$JVC5B(4=og>B`v*6T)UTGit zL7I7=;SdRbN2YDPL(b-7WoS99Wm)_dWQ%soxV4tHS-ylUyE|p=CLb#lNt1!~&J0UytKHIEV&4kid27%dm@bVt>j>F{=Mg-= z!E+f8^={BB!-KGIolNXX!5ZW3P8(vbsxfqKsu!DJ7yRHV-4p-aD4AjmQ=MmlMT{tZ z%r^zhs^J$2eLy`U45EDzwMEmp>(hfq3QysQYkwY@d+HG{ZwUFxZ)RobKMG1QGQ8MPcBhe9Z?_kZL z&4=k67;$8!HmHX!%Cy}ws;uU7Ayc@Q&JvdWD! z6U%zb+Lt=?O7>tT_LKFB1N7WBf?H6?h0U--m)IiCycw;%>=?EGkzqNOX7|3KQk$hx zE51%GZ;aaLK$P0hzzu5UpjK)N&3LTWI7Z9dYXYt0UfqG0Rcdh~3=fNzk6M9?`CpUz zRXFupMU;C-Otk$+$TYea=eE_Y^5eEAa}jPHx?F~q(JQ}J%h`!|u`09O63ED<{orQ< z(f;t00YNN4>WP;fVmh>6)@5rh$-=E@jr3GQCTM#{jR8YrG<@HvpziI14(=hagi7qz zJAPPbJZxx_&t8*N13iZwwTYKrAY@~m~up!sn#!e;Y`meAD zDPk5!3A1pen=7s|9ik^Q^we))8hN*%SAgdOkqUYXczp3y+ADeoRT{xd;_ZZx*gaMwr_S$IvX zSPxWxQfN7BK63e->9AnKn&M(^?~L9{YUyU&VB@y!9g{2JVp49?6+3PtT5hvAo0t~t zZO)L)x9{`Z`X1xd9niPU@bz`-#Vz)*n)}nMJW4w{jI~98IY24R5lt=TR-(KLTk6Iu z$+-_+p;8Z-icmS#DL;(RR8;Dn7c$%7+2J=GnJGUg67srAvHR*z@%wi?lE7U%;PCF z`kHY*ov(;1w+B2Nym_ep(J8w|Guw5_3+VAi>3+e~WeLH!vASFw&QoI&V4Y5ccSn+N=U9vd_oGJrH0Fgf)imRU0%@W!9rnf0-l6`_ zx#|BTH5~n)rG}&bC#vD-f4drv{vWS~qyJOYaP)taez$n{d_wLAo+sRd{1<*F0n>c^ zF2wub0@(Ht9zdKK;jIW?M*8Ovo`h#U;@?EvL8Prid?TL22)}@DKS$Vw-(I9qx*{D% zJ0@XHPCbRsI38-;2ssi*o`qad%^vCh*5LD7NPdp=gJ5NTq2$hA9@YnR2R9yj=s3w0 zpgTtTsjudOuufry9>TnOiDcLp$!)sLh_6C2!e^SD zUhj#Q9)>T1C2Y>M$`4NZ+zZ(tPGkry(WPd-257^Pq6T7%)SPD^rY!O!ffk2H`dX@6eOBB!NpcM8nK?S*zBUN#8(DSq{rBMP)V!NCt081wVY%(m!u9r) z1zm;uBf7)yoijv7;tLD<3NuKsyC|{UcQg77s=ge#dMz|?Ri|7U z$nWbGtt=t;K~95^*JF23S^cg$ho+LMQ{Eo1@P0;Ea};+AS=vcpHY}2793(pq-n7I0 z|9}-ceoQ9J(hSIiE^N-8FDGT+E*rDw%BJkum`O4Ff$M;!-EfQ*^#?zB`;$}pBYvEC zU>nUvM}^cd{W9GDr-&(=`kO~3>_RwA)?r*Fld($6F|-f&uY{gf&`xy&kJ*3d`MhU* za9k_yBxr}sw?z7vV11%fHVu#|EG*#k7FbKWe+wI;C0N4DVH5g{NuqRSflj~lgFfc) zpZjRZBmL|E5e|WJ#Xv9cCuEl$Ii%9d^F{Bi=JxT=n{YaGnEGd;M$y(X zNoP@G-0YAu`!cI^M7sM-IWtlB>6Z(tl5<#roSS^v6tdL7a(jtdk{Fy-$h^$rgoO?@ z%D6loZIgC+JX)W-?9Lbu+uk_P*%P5D7%aHX?>~A?p?J})a!mOx0lz;AU*qRb`1(bD-tWD}&+mIFKRvya9|<#c%Fl0l zS>?`~_|d8S#E+V&z3O@!7-4NDeV_MI{wSvkLfu>H(8{0ncAJdYQO3-Oa@gQqA0wOF z4S``;WKAtg*Eg;=1Zn%={&VCR?(fAc^Ss(-6#Kos#k`-)$4y4Tf_jGmts@N8@uma% zKtW{`i$tUy|GXh&Y8mdQchTOE#rM!MNobj5*_x9mn{whYyCB#xraPKC$q+QwV5Kd^ zwS>l>+q8XGMjsf!%2_ydWQ~mIX!4NpvT#gn`@#A8Bmk|`a(_G|Dz_* zpVw=m`-+-8sMci4O*P3?Ih&z!mUW%84}eK5kfoM~Era?X^u`ys!D)GLBY>N@F^L~1 z?Ep6^z;z;U9WO`qC@fy!2l6ej(yhSN0$feN+yHEKV`(VDlT^NXApy}ge;=XY{yueA z=i(I_Yeml{QX}G{^0P=(yWJ4fZ@Le=J!c$jfN9hzse>%e zKhA)xxl^%&8LBUw)q^|RgG>>njI~!2agB#R=Y6U!IH8uBs+Q^&R-%lkW?Fu}+mwp8 zryDZlh_Faadq7RAA4}`Gp7su;ov(6ND97a7s`B;7W5Jerg34#c&DL4+GGI4aonbDa#I;POl{y*=zNhvta z@0s84`99D8_xzgn+2X+9_#jmhQBS5-))R`W_nJ{avj1Gc+8t3H6>@ZmJ|6C+I)fR4Sld;w*;L8(O z8LffBTETj;s2Y9bFWYDp)Nqm9*XRy+65n@N-R^(mhZU4>oA)Q|Hg1dGt!%UIHf)RAt=|^=vwZx0E8T6f zXXP*D^V_YI&wFqGIeo9RYWjBHAfivf2(c~Pw-r5A%zZ01A$^LpOKJ(v!LHDN&t|}Q z?>ff(2V26o^L?T%;d>+RPn!aDw7RAx{7Dn5@?(8P99HA6wYP*nY|>S=gx_m=tBz)W zTf?JUu@+o#@8sdhVMchBm{NX zZ%Vs}K@0pF53a|kQnwXtfKSiQHtCA_Y7f>7w1(%l=$tIa@iC41{&K)wTj%X|5^IaL z$LzH1Vh?I79Ex!AzuzYB)a_~wm$w|ko-h4L>doMLJ^DV_%@D$#q>$va`e?U^mL|=+ zGqt2dEH2^SKwY3ig|YHG3&!IFSFGj2dO3XOu3@gi8d|}4xRqufwDnFk(6zbvj@V~` zq<&K)V%OE2n(;kzYjjvc*%tnFs{mJW%SB9bc>-}o>@2qO^^hGgkH=a^!oH1%uws2A z;ZDYWBdm@qNh-=Hnd5t6LHPoFb6X%gzNpdle6pspW_(6QjgYPTjAfgGLbjOvFn?e#a2K&`YS$eF&d8t{#dD=MZYv=Y+%Ah+_rM~AH`XYtR?p5=otn3D{ zD!*!@*K~S9idcp=xp3<8$S#@jc)-2SZaB@EbUOS}(Y+Spfbz z$Zu|{l=Bzx!y5ZSe`wtSNfF;!d_N9hoZ72>T0YPW6>EGP_$5d(=Wi{xpP#WBl(NPzTl$8ctk%{{{;TQN zUdu`B&u^%$TIV&KV4QZ^e;I7Ky^Q!-$22$AID_-EXG#D)dqZ$2(kFh>qWlcL#Lvl= zJUZRA0a8};7HJxCNqg&64|3Qsy(8{Hz-LHOuGMB1FXZ#gD0%W&#A!>N#1kK~!GosUg8{Rc|eHL$8cl_jLlwg(N_J%VxBD!G6+5wk)VVL@8AKw9cb;i>vjy+a=4 zOjgjbl4bP>24<&)$Yy9DSInv3+J(b0Xj`cS4Qx&iNm)IEa#nUwm-Qp?`m6tNj>7&u z$ZlmJ|LnG~8`2Z@)s3R3h*P^w!Efp)pH@TfMRN0#K0VP)bswwpRK0u-G*!Qdr@!iX zJU!LFK1VrKN3j}?;VC!tL|JrW)w9Gk|N9FyZ`9S^nW6I>M14o!b^4>0WQ|WYvmEU2 zMcpT;?wT6ZeRRgdJ+)$P>WLQBQL6e{+$gV*QBniTb>hY+p+*U@+=O5m&N@g59)hN~ zgn}SRjcePsuMD84Kcoa)bLafW-b>qV@?H_ z8qrp&>;1^}8Rx0gXQ3V@Y;im`NvK5y9he~#v${1@0aq%ghG_pKOxIk7>93$mqsRr@ zelB&wDP?wTAPh;D(GtUH>E4ckYX{HQqZhCmpL518Fzly^mH^{&TB)nxgmcaJ`}2!J zVpig*?Ao&QM334#w&7j&7;l?SOlH}JU_E&Kt|`jv)X;3)Tt>Z>c$AkL8Z^pKelY*` z$+;HiW5}(($gN-lofhW{X)gWY@=k+a#a`V4fA&?#4eY6I9ulhJTIolg_%Z)Ot`9V4 zEf=$TojTS*-YVzunRXg%;0x_?Gu0acPW`7GWR2+;;zF}gLM%wx5Wz+A5+JWr=P1P0 zkOP$yQ>|Dty4G8>vGzS}AGP1Ke2lh_+P`u6aqODF9%_HhC@ZwVkKV}GW`=c&ZQ-J3 zI$>zdemW@ud*WTPAA4eIjJ0Kur!}Z%4r!>V6?~?lVpji?B39~4nRd+oQoAwYyy(ir zk~(u{BEB`n2K6}Qrm>c}9FUqb(}~u+OM}QysThk|On6Fg#$vArYy9ebOh|LjY!ZCK z>Kf~`azD&ZzE5Md%%wJKwL|LZ$>@*o!foM07ewB|YS60}sI{eC`!(2EMSTUTYXzT? z_vQYap)>1IFd4YW3z))mrzfTP11`=#*Ce-r)@g!dm5ab6DXySNyB z8R^nE!5E}{JR(j?O7e*gj}yI_<{0eg!Tzn~xW^EE-j?vPHqmE7ELv-R_yYPJ7@HWd zdph4PI5#ekM*Alh`AMH?O(3m_Jo!;u8`W@Y98!j-XExRhw1xA*aTYix`&HcDAGYt2 z|EPDsyVll7?exux(6=18aEAt`PvZ3b;X+6HB+%EZ1AU7k^qDz*x_?Vw0dOX$ieHGx z^DTVWkUPLf0LONKU%3K)39zYy|BF}nKX3)S4)D+p;rm@hd10!ZZ~)(6Kc*3i|BcbL z%d#zKUBeBd>ypOo^#0KBZe0Ra?k4-X2||M!X1Fy0jC~rSZjkZ=--hq6OnEqXt@UO*I~4tRkv|C3C(`3<8DT0GXAU8 zMk~Xw*fCS&V|}^>*V-u$g~l&it%Mi7BGO>z1v+|1u0V^B;!D;0KW@(Kh|^o=n&HjN>-P@RWA5`?Dzf* zVK#0WJ7x0ScYtNknQm?21LtW48ioD2-(K#A@88&mjovI@(`G&RqeFIjOUTWMH4AOw zE$!~QcK5dMBiwcW!DQ_FSyd{iLRR`I>|wVRr&(NM3+#3y1eb!{4!dmK?A>Ow-bvUM z@w6?$zI8L5lDnCGWpnj#RehDJG;G8Pyjw8JF!nH3VGb5 z0k|g?q}UVgrtg;IqT!AM?rNLSzG3skw9Q`S^R2jjRQ`Mea2B^?#GZfPG}B1Glx8|< z9h}AKEx68Bw8^*$Gm59NQX+)+i8k~knCig{q!wrY!30hRYNYJ$oQ{cU7U%Q=aCSHG zHP%;Q+iP>pW_@Bg9oXr#d52dyv4NFOmYgRFwqSNr!Q8?l*i}U|kbVh12kn#oA2fV7nduvA)M_Cu|LCmC)i;_E4V){K;CJ!yaU~yOuPM z=)rsm*6Y8BT}?C>(49+2$Q(d)K|Um9jZ>n7IL(;Na$G^7w=)7ixf#^tT7oaIo9*3t zbgSA0Iu@m+`^WgE*f!WcTx$tE%$C@5dgN4nhunTBEd!L4!{?^7ch|B;88bJUoV9Xi zQOgRt@l}ekHuvo3H&ee`!s#+~-d;F2&^1IIjQztc{i=r5ZDkuK>#9hy|7^j!%OI<2 z4S(07_2J_~en4Nh=zQ*~p>>3#HT=gG58OV(=u+!XEZ`D|roCqW0IJ`o3YqRx1!Tt>P6+p@1H~P#e&UubJe6l#s2_(` zwo2S#jHLV++>rmEjX7~%9MjcrMBp?G+u4RxHMs#?QIHnmz1xxf>Ge2d>s*C2DJULQCHCp?7a&t0zxLRf5H- z8xNqRh5bCAZ4>TdeQXGO7wWmz@Nmq$WzK$z=SzO7C5W>~TK0Gq`1|dvHXmAx+ppHL z*&7aE{u^4A+@{rUnx2{P^#`kPFFi3PJld)YwS`BtiRitzg%|Ld1wQBH$0q^50As~` z9$NjZ#0C@eVuCI4?yZ}5Vi#tSuh6E>ZVms?{3xfUHT?Hx%HhAE_oJmpyUCPNPg+~} zbK0}VbshJv;)@i%YPb6xZWYkxYwbRTq23g0JlpJoCK|aWD!Anmb>GCGgHHGMmV|J} zC%r##2ynGV^!J7#IQIvewy?LeaTLD8n1gyw?{hezBp#yzs&S4u#W9;EXtbF8T3U22 z9;Ky=r1hCY=GdtxI5)z}Z-JM}2g9uu^gZ3BRu#l@>`Atdang)qVo)`s^>LJZpbCJt zR4M&U>2#V{{rQmEv*a_C?Z5~DaA9+_+}{LxiC3(nY0IyoH03e3VP#EQ_+9Kw2>9sC zqYN1AN#*Tj79XDme7^@RTD);>`eQ_8O_LRM2lY6Gt+76_K7;p^b|mzbVu1%{1ZEhz(VqH0+8xO*@$C%Jje}V-8WnX=@g@@8LT& zC~wbqCw{b9YRzB4s7xy>8c)Ys^Q$Dtg5>Z3m&2c1N%m{@QyJ3xt{qRSU!QfWPTFJk z!-bk!Yo;B0*)Vdz`YkKIzO6y}EGIvB#Qt>^m9EDxNWMK)U*LR3tkK*e1n^G{ z3BLHDQr4UkaXsB0PnwuH3p;s^+r6yLrrX4F%7S<1u#Y(@4w4z^xi?qqy5D%mR=!Jea@84+ZS2s@|$%Tl=`jr1~9Br0W880ed`7 zI`K>{_I+gkt*v+?_VU&b_w^a*ccgI6X~pL2t;f{iB|}&C^TZd$VZDJr^@yW(Ld^Ii zoYk>(lR;b5)73(uAWnWAWu`L+TNndZI~+>N=9W;)mjN#TtpNdvLHAFj`jke=B8B~Y**J|@u z6@y0s^@sDK^^I7>NVM&&`n2liw4J!!xGj7?b_(;jsgIS}Py^JBvSP#=3{&+!x!#BsX}WF71M)-W2XnEb$$}M3cHB|WJZk1^e0u!!Q|j;_%vvfdAE6+Df5d478>NM#hr0jmvDmR z=yod)#|6zhev9)dvA3kQPgXzd$T1GX?88YFXJh7uD1?4HYP#*Q!F66sgT{w)+PJd> zBUasNX~*)E{8d7BpHsBsLvS*OaSQGT!+Nob&Mm;#Osoj-(YY6w+9I8= zE6UGN`?G`by$GwuRDSY<&^Z2lRc#z$L`^}r8McL4voU0acE@8Lm-GndWJ1reRu!`v z_zumr*il7Vr)k-xc1EkeTbrm(wOmnu8=<>Ye=Gd`nZv-}T0KrR*+JA~q!V2RU|r++ zwzgT3dROO5X{Y-nTEHmXKQs~Dv@72Ry6xEOO|@LW?x-?_ta;c0k5(#Re-3wA8Vws_ z%h4abM6m(oGqF+gO~uSJ_Cx2|-M;tTI9a3d#wJ0{oP-lmr!ScP{B%!Rw7+LPm`p7gR=CFp z3sUdH&F?PsH_;m4=&+V>Q`3hyG0ZXH{ei?&7PT8p>*E2dHC38SW0u zJHfVK`XLo3>-vj)y^8Zo%c|bSj$5opM_rqaFGeos=mM+l6-Pd9Th?I?r4F|_OOT}4 zfVLL>GSXJsO-lQKa3fh0wRXvLqb^c$HK;4rVeF&g>quGM`#60WF?LY8T|rI4`La4n z_X~a+GRx3#>w>fg6E`i@=GVxfNo`I8 z(f;!7sF&aV+!DTu?^`)^+3n9PM7Ns?;C2`HU4>g{pFbCaZ;=_;3k!)oG88)(Tf;Xr zHHR0r(i(b##y9Cfx4zgjnbx}aM@4zZ@R^lnGFD_|Y<2>*^ElrWt-JqVbGQZlKax!W zmyHsU&F1FezbKm_?Xv0mi?W%{WpgpS67Tr^jk0$c_uAAszW$CbXz{4$_CDrsSmT-t z@niE)V`J2Z@gsJCPKx7rmtBZOO@-M`_!;4ME&TRPqLlH~<3bzibFE$Pm}VI1Sz&T` zx|{Y-;p5FIS~+dl3w(b24aEheHxy$Are6PSl4Ej&mUWz#t1nDI`{cKeEzX}j4s~nY zC<*2JZp2$zg(Mgu|;#RZIGr)t{b=_|C_4y9{ zLi;t>&YFaJ@cajnx2x>2dW>OvIo@n9Hu=`)R@q;Nn$G>MkaR9q)w_(5feVhW^aI+8i z1^}Pm-hK3{QjCTtzCBgU>3Wh}SYtfa?u^JxK~Fzn^gXtu;)SK`en*Pic6}=4OG{Wd zA73=6)H>cYj^!ADWi93ikGF=93t0Pwb9B3(moQ7E<--%_b^IJ|%7a44Jev2UKcq`z zp+970#`#*dweKJ4&$+k>PA-E&g3d*WH`qH-%q7-?+%r6n+i!*EE`> z5msw>KWgQG?*zj5VD_WUHuL(&v+2)7wblg75No3IxBC& z*<0X!PGtqFoL4^m!)!{cO}!YlU8Gu{<{!3bwnjT&Z$ztb1f28M7B+&PXVGUN-g3Af zg)W0!w0M7k&3Z5{(x1U<@$fLr8eru%R#Q`t?cBw-@ZC6@18ae?z6kRcZLQ&(Tdi-P z|KkuQX?DVIJ=isZ!-P;@A=biHU1oQDaiKFjUTrc0S59kNcvc5oW7_E41(QuFwK(z% z>TJTMjcMLB#c9GC%pqY!8cib|=}{Wg_p%-|P&#&xn>GHig~`18vq&Q}{3F8)EG>bhN2wP<16RP zW`55JN)V0C{&hitJ5%Yb+23M5pYm(Oh!LFTznU2lftq(0+v0`g))6G!`o|G(`yfG)+oJKL>RkDjG6B3MKWsrRB zC-Ei9bQm__8P{+bOhg(#-9LfgRyR9-iAbaeD!`kKuNr0O7g) zgA<2A6=DcH2DfkG_5yAn!|g;lVsQIm@PhD+hv)W9+)g|SC&zGmKJL>Md=v7mzM4^O z=LD1()~Ih1Av2a04lX^1wG)^F-{O%{>pi=D>ahDf_JRrC8NQQvLt?#?amz$v1I|A9 z6gPYFaao`sU%{6Rhf$9)+KX5%`lttM)}=RS{z`fgtEdjr+|JnZ!mfvX;(p8PgrD_} z2c9_KVfZ2{2I(wuC6!`}k=Oy>^2_48n>xmCJouq+lrLG%vE)K;4RVrYgk^_MvfNQH zwP04kjDkDu11(?Kcl(~eO(_2Ko5k(EBZXsp1qIg?WLO5vm4$;XE_tv0X8RPI$}F$f z`*z@ln8t%Q`o{T2`p)`72W7Tqe+=6S_f1~+sT{P1C1^PVO<4>~qBec4X+X+$pL*3U z-++&A@?Gz{&*Sy=VDwI7zwr(`g%b-JZVTP)+uQApe4G<}rZ$8=cRJlJEu`OsQ1tfV z&F;$#t@eI&I_irRhq%!?wQOn`!}?%p|8P#*W4>oVpVz0R?@F2AJLAdsC9=`D&!j6u zss+(hkF;YcfP7B}n+>~t<-UcAso*xn=Zg(JgI@PzUdySyDG%ifs>?n%ANAHXF59*F zELR_V#{2ivfXFqssew|z8FfoFsL%McH@nGK2o1<)f5L>;%`{8cyZ5I(*)-pqo&l^U z8-;g&=1#rGU3B!dAg~#v^Q9!(Df!WHX;{rzf5L;3@Pa z7IegW@{9_}^klnI&U*T?7kxh8%buCOtJr{ocQLq+od{$ z^lmclfIP`8)}h@@+-6dSb~AFDQ64HjW-aybJBwsV6b1ZH`uUIM`MR(kjA&<#Nlh|~ zVIng#Hyg?@FN#}Ogk1?KoAQ+%Dl?UhDa&FsyM;4tI_L|UT*sRIdRFISef4Kh!SwQZ z@68t+#a_7dHk`@#9WSuL*UnhF*SjfyAiJ7t*K6KePgQa4Lc7(r#y1qHwuLvh-R$d@ zg3<^5n)C5w-*vuKo-&`A(c8pc!(Q^mAv~dBglCG6aug-$%u<|Th7n*-;$({N9*^)q zxlibMkEh(n9$;DJ!PABtl-Po43S6O$me6mY5l?zer$|dF=Y^qrZBv0sAN-BC@w70M zYeUxM+A#jpT-Jx3^~~^n=7EOvzVXw6Y^v{BcQ{^c4y9U5|-Z&%7`58ACQ<-R%(`sO>pX+b-ur#8|UStX9ZMb<-HGf$p--unYI ztlX#e6nZU9p_IZN3L`vt*EzyU8n)BZlO?k0zE3=TSXx1p-d~-W1^?;3LF{$Uu9VL~ zckhBn&q1%yj+wYF!QR*vHhRy(JCSvTG_d(?qz>0>%pF=vJQEu%sQ}O zDagDQrJ%@KN_8Nxb0<=ZM)Za z;(COUg722THBq>#`GgiTF57ZSi~ZBjaDR91a{h9E#$RvNo%O1+4(d@h3taxH>Je~i z9r(wxf6rh1FYrg)IX$m)+Fu9lFLT;;Y>i=ba3W|f41Rb3ciTDf1!<5l{OXg!fZUoy zZ&QQRr|;=Dr^%BnCKqJO$pvwJ2A%H3tRqGkuMu9v(K{*3I(TMba@nM^`^$b`X6=6f zapHPfoWhV1LQl`9_94va?HatM$4knKY_BhcrFO3FLDO4w@3XI0OeHLZHyjD?lui#oW>D4#d2qEd5J$Bk!&l}jt<%&)1eP^T{+#%5J6 zE?!(YSIwv}u}SkPYilZJEQZ&Ns`7>9uq|BF(RF5Z<(vu}=}}X@a8c#LX-muJR9BQQ zuH=!IT#8)a9X}yL?2P&I7FX6TR;%anz#Q))mXVvih?O9Pxr@}585Jv4R#CmEW={F? zY1I|A)8;Q(JZ=8WX$#Be&8lP*m)BGdLjr76Ij+&FtW*oiYgD$lR$U6(s^`yBs~4&B z<}X$kEvc!QzYwIe36%?$R4)V%oLYF!o6$iU)$^*cL={40QPMgPzNmUdWk-HFBO}NA z+QZf?tejb0OKC3Gx%OP^m`7{`%gIqkURyG8ikg!HSMRnoQUYFk*$b$gC-}mXd{ui3m2<%=T}q?>&PW?W=SRV3vyCfaU`S4D=HRN zE?T6{oWF2x`C{ho*MGpkL4$|n4K1HOqoQ&qB|d%vn>b%(OWnEs-5@K!q-4VQ8%DC+ z+}w`X=Tr0g6dkYFj`q=ne*@Tg!M;1(E|HKVwN`3oy57hb8S5jjnox3F@?{8{s~1gXFy!HPoF8V0?vlBl?1H)T5_& zbaOmSHV&W3e^|bS#fr;q>&k7g+!LceTw}Y2+~O=pR~;%#Bda?vZ{WZ?+-_L?vutVW z%E>j)B3}axJbn+0{&1Na-6@WiFmq?JtSocm3oGVEDD62!OW8~>dD1 zU`_cwf(@Zq=Z|?iXPj~1;#EfnD@H=MSagMX|$Om6Pe|iol@Zm#cZkvTCM1FdQsm8!+?z?fqtO)a;QDX8vHeP>Pp4 z0@UG38$5V03?<6u+hW(B19^du+~Au;M&#f}j0A>%^uoF2cXY6Xm^oP@TaMeK| z{%e#hJgXvLK>FbWE;_d6(5x3eolAT}VU8}a5lObqvtIjr4%v{}tkxcyz-O(5fH

=hf!Jy;Cq=rX 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 Result { - // instantiate the connection to Ledger - // will return an error if Ledger is not connected + // instantiate the connection to Ledger, this will return an error if Ledger is not connected let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) } diff --git a/cmd/crates/stellar-ledger/src/docker.rs b/cmd/crates/stellar-ledger/src/docker.rs index e6104e40b..854dae3be 100644 --- a/cmd/crates/stellar-ledger/src/docker.rs +++ b/cmd/crates/stellar-ledger/src/docker.rs @@ -1,7 +1,7 @@ -use std::collections::HashMap; +use std::{collections::HashMap, path::PathBuf}; use bollard::{ - container::{Config, CreateContainerOptions, StartContainerOptions}, + container::{Config, CreateContainerOptions, LogsOptions, StartContainerOptions}, image::CreateImageOptions, service::{HostConfig, PortBinding}, ClientVersion, Docker, @@ -28,6 +28,10 @@ const API_DEFAULT_VERSION: &ClientVersion = &ClientVersion { minor_version: 40, }; +const BOLOS_SDK: &str = "/project/deps/nanos-secure-sdk"; +const DEFAULT_APP_PATH: &str = "/project/app/bin"; +const BOLOS_ENV: &str = "/opt/bolos"; + #[derive(thiserror::Error, Debug)] pub enum Error { #[error("⛔ ️Failed to start container: {0}")] @@ -50,7 +54,7 @@ impl DockerConnection { } } - async fn get_image_with_defaults(&self, image_name: &str) -> Result<(), Error> { + pub async fn get_image_with_defaults(&self, image_name: &str) -> Result<(), Error> { self.docker .create_image( Some(CreateImageOptions { @@ -66,8 +70,22 @@ impl DockerConnection { Ok(()) } - async fn get_container_with_defaults(&self, image_name: &str) -> Result { - let default_port_mappings = vec!["8000:8000", "8001:8001"]; + // docker run --rm -it -v "$(pwd)"/apps:/speculos/apps \ + // -p 1234:1234 -p 5001:5000 -p 40000:40000 -p 41000:41000 ghcr.io/ledgerhq/speculos:latest \ + // --model nanos ./apps/btc.elf --sdk 2.0 --seed "secret" --display headless --apdu-port 40000 \ + // --vnc-port 41000 + + // docker run --rm -it -v $(pwd)/apps:/speculos/apps \ + // -p 5001:5000 --publish 41000:41000 speculos \ + // --model nanos ./apps/btc.elf --display headless --vnc-port 41000 + + // docker run --rm -it -p 5001:5000 --publish 41000:41000 zondax/builder-zemu \ + // --model nanos ./apps/btc.elf --display headless --vnc-port 41000 + + // docker run --rm -it -v $(pwd)/apps:/speculos/apps -p 5001:5000 --publish 41000:41000 speculos --display headless --vnc-port 41000 --model nanos ./apps/btc.elf + + pub async fn get_container_with_defaults(&self, image_name: &str) -> Result { + let default_port_mappings = vec!["5001:5000", "9998:9998", "41000:41000"]; // The port mapping in the bollard crate is formatted differently than the docker CLI. In the docker CLI, we usually specify exposed ports as `-p HOST_PORT:CONTAINER_PORT`. But with the bollard crate, it is expecting the port mapping to be a map of the container port (with the protocol) to the host port. let mut port_mapping_hash = HashMap::new(); for port_mapping in default_port_mappings { @@ -84,16 +102,37 @@ impl DockerConnection { ); } + // const displaySetting = "--display headless"; + // const command = `/home/zondax/speculos/speculos.py --log-level speculos:DEBUG --color JADE_GREEN ${displaySetting} ${customOptions} -m ${modelOptions} ${DEFAULT_APP_PATH}/${appFilename} ${libArgs}`; + + let container_elf_path = format!("{DEFAULT_APP_PATH}/demoAppS.elf"); + let command_string = format!("/home/zondax/speculos/speculos.py --log-level speculos:DEBUG --color JADE_GREEN --display headless -s \"other base behind follow wet put glad muscle unlock sell income october\" -m nanos {container_elf_path}"); + let command_args = vec![command_string.as_str()]; + + let apps_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("apps"); + // let volume_bind_string = format!("{}:/speculos/apps", apps_dir.display()); + let volume_bind_string = format!("{}:/project/app/bin", apps_dir.display()); + println!("volume_bind_string: {volume_bind_string}"); + + let env_vars = vec![ + "BOLOS_SDK=/project/deps/nanos-secure-sdk", + "BOLOS_ENV=/opt/bolos", + "DISPLAY=host.docker.internal:0", + ]; + let config = Config { image: Some(image_name), - cmd: None, + tty: Some(true), attach_stdout: Some(true), attach_stderr: Some(true), + env: Some(env_vars), host_config: Some(HostConfig { auto_remove: Some(true), port_bindings: Some(port_mapping_hash), + binds: Some(vec![volume_bind_string]), ..Default::default() }), + cmd: Some(command_args), ..Default::default() }; @@ -101,7 +140,7 @@ impl DockerConnection { .docker .create_container( Some(CreateContainerOptions { - name: "FIX ME", + name: "FIX_ME", ..Default::default() }), config, @@ -111,14 +150,28 @@ impl DockerConnection { Ok(create_container_response.id) } - async fn start_container_with_defaults( + pub async fn start_container_with_defaults( &self, container_response_id: &str, - ) -> Result<(), bollard::errors::Error> { // deal with this error + ) -> Result<(), bollard::errors::Error> { + // deal with this error self.docker .start_container(container_response_id, None::>) .await } + + pub async fn stream_logs(&self, container_response_id: &str) { + let log_options = Some(LogsOptions:: { + follow: true, + stdout: true, + stderr: true, + ..Default::default() + }); + + let logs = self.docker.logs(container_response_id, log_options); + let logs = logs.try_collect::>().await; + println!("{logs:?}"); + } } pub async fn connect_to_docker(docker_host: &Option) -> Result { diff --git a/cmd/crates/stellar-ledger/src/emulator.rs b/cmd/crates/stellar-ledger/src/emulator.rs index 78044b697..8327d1287 100644 --- a/cmd/crates/stellar-ledger/src/emulator.rs +++ b/cmd/crates/stellar-ledger/src/emulator.rs @@ -3,7 +3,24 @@ use crate::docker::DockerConnection; pub enum Error {} pub async fn run() -> Result<(), Error> { - DockerConnection::new().await; + let d = DockerConnection::new().await; + let zondax_speculos_image = + "docker.io/zondax/builder-zemu:speculos-3a3439f6b45eca7f56395673caaf434c202e7005"; + d.get_image_with_defaults(zondax_speculos_image) + .await + .unwrap(); + + let container_id = d + .get_container_with_defaults(zondax_speculos_image) + .await + .unwrap(); + + // This is starting up, but i think it fails pretty quickly, and i think we have it configured to delete itself once it starts. yep, when auto_remove is set to false, it sticks around but it exits right away + d.start_container_with_defaults(&container_id) + .await + .unwrap(); + + d.stream_logs(&container_id).await; Ok(()) } diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 2dbcc9fc0..2ae158533 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -49,8 +49,10 @@ mod test { assert!(public_key.is_ok()); } - #[test] - fn test_the_emulator() { - emulator::run(); + // #[test] + #[tokio::test] + async fn test_my_emulator() { + let r = emulator::run().await; + assert!(r.is_ok()); } } From 37b2a24d534a9de3ee377b372c7738cba10a1429 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:08:20 -0400 Subject: [PATCH 07/72] Allow for stopping the emulator --- cmd/crates/stellar-ledger/src/docker.rs | 36 ++++-------- cmd/crates/stellar-ledger/src/emulator.rs | 67 ++++++++++++++++------- cmd/crates/stellar-ledger/src/lib.rs | 11 +++- 3 files changed, 67 insertions(+), 47 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/docker.rs b/cmd/crates/stellar-ledger/src/docker.rs index 854dae3be..3049e7a6b 100644 --- a/cmd/crates/stellar-ledger/src/docker.rs +++ b/cmd/crates/stellar-ledger/src/docker.rs @@ -70,20 +70,6 @@ impl DockerConnection { Ok(()) } - // docker run --rm -it -v "$(pwd)"/apps:/speculos/apps \ - // -p 1234:1234 -p 5001:5000 -p 40000:40000 -p 41000:41000 ghcr.io/ledgerhq/speculos:latest \ - // --model nanos ./apps/btc.elf --sdk 2.0 --seed "secret" --display headless --apdu-port 40000 \ - // --vnc-port 41000 - - // docker run --rm -it -v $(pwd)/apps:/speculos/apps \ - // -p 5001:5000 --publish 41000:41000 speculos \ - // --model nanos ./apps/btc.elf --display headless --vnc-port 41000 - - // docker run --rm -it -p 5001:5000 --publish 41000:41000 zondax/builder-zemu \ - // --model nanos ./apps/btc.elf --display headless --vnc-port 41000 - - // docker run --rm -it -v $(pwd)/apps:/speculos/apps -p 5001:5000 --publish 41000:41000 speculos --display headless --vnc-port 41000 --model nanos ./apps/btc.elf - pub async fn get_container_with_defaults(&self, image_name: &str) -> Result { let default_port_mappings = vec!["5001:5000", "9998:9998", "41000:41000"]; // The port mapping in the bollard crate is formatted differently than the docker CLI. In the docker CLI, we usually specify exposed ports as `-p HOST_PORT:CONTAINER_PORT`. But with the bollard crate, it is expecting the port mapping to be a map of the container port (with the protocol) to the host port. @@ -102,23 +88,18 @@ impl DockerConnection { ); } - // const displaySetting = "--display headless"; - // const command = `/home/zondax/speculos/speculos.py --log-level speculos:DEBUG --color JADE_GREEN ${displaySetting} ${customOptions} -m ${modelOptions} ${DEFAULT_APP_PATH}/${appFilename} ${libArgs}`; - let container_elf_path = format!("{DEFAULT_APP_PATH}/demoAppS.elf"); let command_string = format!("/home/zondax/speculos/speculos.py --log-level speculos:DEBUG --color JADE_GREEN --display headless -s \"other base behind follow wet put glad muscle unlock sell income october\" -m nanos {container_elf_path}"); let command_args = vec![command_string.as_str()]; let apps_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("apps"); - // let volume_bind_string = format!("{}:/speculos/apps", apps_dir.display()); let volume_bind_string = format!("{}:/project/app/bin", apps_dir.display()); - println!("volume_bind_string: {volume_bind_string}"); - let env_vars = vec![ - "BOLOS_SDK=/project/deps/nanos-secure-sdk", - "BOLOS_ENV=/opt/bolos", - "DISPLAY=host.docker.internal:0", - ]; + println!("volume_bind_string: {volume_bind_string}"); + let bolos_sdk = format!("BOLOS_SDK={BOLOS_SDK}"); + let bolos_env = format!("BOLOS_ENV={BOLOS_ENV}"); + let display = format!("DISPLAY=host.docker.internal:0"); // TODO: this should be condiditional depending on os i think + let env_vars: Vec<&str> = vec![&bolos_sdk, &bolos_env, &display]; let config = Config { image: Some(image_name), @@ -172,6 +153,13 @@ impl DockerConnection { let logs = logs.try_collect::>().await; println!("{logs:?}"); } + + pub async fn stop_container(&self, container_response_id: &str) { + self.docker + .stop_container(container_response_id, None) + .await + .unwrap(); + } } pub async fn connect_to_docker(docker_host: &Option) -> Result { diff --git a/cmd/crates/stellar-ledger/src/emulator.rs b/cmd/crates/stellar-ledger/src/emulator.rs index 8327d1287..482860712 100644 --- a/cmd/crates/stellar-ledger/src/emulator.rs +++ b/cmd/crates/stellar-ledger/src/emulator.rs @@ -2,29 +2,56 @@ use crate::docker::DockerConnection; pub enum Error {} -pub async fn run() -> Result<(), Error> { - let d = DockerConnection::new().await; - let zondax_speculos_image = - "docker.io/zondax/builder-zemu:speculos-3a3439f6b45eca7f56395673caaf434c202e7005"; - d.get_image_with_defaults(zondax_speculos_image) - .await - .unwrap(); - - let container_id = d - .get_container_with_defaults(zondax_speculos_image) - .await - .unwrap(); - - // This is starting up, but i think it fails pretty quickly, and i think we have it configured to delete itself once it starts. yep, when auto_remove is set to false, it sticks around but it exits right away - d.start_container_with_defaults(&container_id) - .await - .unwrap(); - - d.stream_logs(&container_id).await; - Ok(()) +pub struct Emulator { + docker: DockerConnection, + container_id: Option, } +impl Emulator { + pub async fn new() -> Self { + let d = DockerConnection::new().await; + + Self { + docker: d, + container_id: None, + } + } + + pub async fn run(&mut self) -> Result<(), Error> { + let zondax_speculos_image = + "docker.io/zondax/builder-zemu:speculos-3a3439f6b45eca7f56395673caaf434c202e7005"; + self.docker + .get_image_with_defaults(zondax_speculos_image) + .await + .unwrap(); + + let container_id = self + .docker + .get_container_with_defaults(zondax_speculos_image) + .await + .unwrap(); + + self.container_id = Some(container_id.clone()); + + // This is starting up, but i think it fails pretty quickly, and i think we have it configured to delete itself once it starts. yep, when auto_remove is set to false, it sticks around but it exits right away + self.docker + .start_container_with_defaults(&container_id) + .await + .unwrap(); + + // self.docker.stream_logs(&container_id).await; + Ok(()) + } + + pub async fn stop(&self) -> Result<(), Error> { + if let Some(container_id) = &self.container_id { + self.docker.stop_container(container_id).await; + } + Ok(()) + } +} +// ------------------------------------------------------------- // next steps: // have this docker connection start the speculos emulator diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 2ae158533..cae291b39 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -16,7 +16,6 @@ pub mod app; use app::get_public_key; mod emulator; -use emulator::run; mod docker; @@ -24,6 +23,8 @@ enum Error {} #[cfg(test)] mod test { + use crate::emulator::Emulator; + use super::*; use hidapi::HidApi; use ledger_transport_hid::TransportNativeHID; @@ -52,7 +53,11 @@ mod test { // #[test] #[tokio::test] async fn test_my_emulator() { - let r = emulator::run().await; - assert!(r.is_ok()); + let mut e = Emulator::new().await; + let start_result = e.run().await; + assert!(start_result.is_ok()); + + let stop_result = e.stop().await; + assert!(stop_result.is_ok()); } } From c68c40d921998bedb466464032d5c9e8c7138661 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 28 Mar 2024 11:24:00 -0400 Subject: [PATCH 08/72] Make get_public_key async so that is can use the ledger-transport::Exchange trait for the transport which is async using this trait allows us to use the Zemu transport which can connect to the Speculos emulator --- cmd/crates/stellar-ledger/src/app.rs | 6 +++--- cmd/crates/stellar-ledger/src/lib.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index f4afc4730..da8603301 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -28,9 +28,9 @@ pub enum LedgerError { LedgerHIDError(LedgerHIDError), } -pub fn get_public_key(index: u32) -> Result { +pub async fn get_public_key(index: u32) -> Result { let hd_path = bip_path_from_index(index); - get_public_key_with_display_flag(hd_path, false) + get_public_key_with_display_flag(hd_path, false).await } fn bip_path_from_index(index: u32) -> slip10::BIP32Path { @@ -52,7 +52,7 @@ fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { } /// The display_and_confirm bool determines if the Ledger will display the public key on its screen and requires user approval to share -pub fn get_public_key_with_display_flag( +pub async fn get_public_key_with_display_flag( hd_path: slip10::BIP32Path, display_and_confirm: bool, ) -> Result { diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index cae291b39..432e68e00 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -42,10 +42,10 @@ mod test { &HIDAPI } - #[test] + #[tokio::test] #[serial] - fn test_get_public_key() { - let public_key = get_public_key(0); + async fn test_get_public_key() { + let public_key = get_public_key(0).await; println!("{public_key:?}"); assert!(public_key.is_ok()); } From 1f51f3ebbf11d417245843055dbdea807feea9ae Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 28 Mar 2024 14:52:49 -0400 Subject: [PATCH 09/72] WIP: Allow get_public_key to use Exchange trait to get the transport still need to handle errors properly --- cmd/crates/stellar-ledger/src/app.rs | 38 +++++++++++++++++++--------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index da8603301..3c3c56171 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -1,7 +1,7 @@ use byteorder::{BigEndian, WriteBytesExt}; use std::{io::Write, str::FromStr}; -use ledger_transport::APDUCommand; +use ledger_transport::{APDUCommand, Exchange}; use ledger_transport_hid::{ hidapi::{HidApi, HidError}, LedgerHIDError, TransportNativeHID, @@ -16,16 +16,19 @@ const P2_GET_PUBLIC_KEY_DISPLAY: u8 = 0x01; const RETURN_CODE_OK: u16 = 36864; // APDUAnswer.retcode which means success from Ledger -#[derive(Debug)] +#[derive(thiserror::Error, Debug)] pub enum LedgerError { - /// Error occuring on init of hidapid and getting current devices list - HidApiError(HidError), - /// Error occuring on creating a new hid transport, connecting to first ledger device found - LedgerHidError(LedgerHIDError), - /// Error occurred while exchanging with Ledger device + #[error("Error occurred while initializing HIDAPI: {0}")] + HidApiError(#[from] HidError), + + #[error("Error occurred while initializing Ledger HID transport: {0}")] + LedgerHidError(#[from] LedgerHIDError), + + #[error("Error with ADPU exchange with Ledger device: {0}")] //TODO update this message APDUExchangeError(String), - /// Error with transport - LedgerHIDError(LedgerHIDError), + + #[error("Error occurred while exchanging with Ledger device: {0}")] + LedgerConnectionError(String), } pub async fn get_public_key(index: u32) -> Result { @@ -57,7 +60,7 @@ pub async fn get_public_key_with_display_flag( display_and_confirm: bool, ) -> Result { // instantiate the connect to the Ledger, return an error if not connected - let transport = get_transport()?; + let transport = new_get_transport()?; // convert the hd_path into bytes to be sent as `data` to the Ledger // the first element of the data should be the number of elements in the path @@ -82,7 +85,8 @@ pub async fn get_public_key_with_display_flag( tracing::info!("APDU in: {}", hex::encode(&command.serialize())); - match transport.exchange(&command) { + // transport.exchange as is using the default implementation which is synchronous. but, we need to use the async version of exchange here so that we are conforming to the Exchange trait + match transport.exchange(&command).await { Ok(response) => { tracing::info!( "APDU out: {}\nAPDU ret code: {:x}", @@ -101,7 +105,11 @@ pub async fn get_public_key_with_display_flag( return Err(LedgerError::APDUExchangeError(error_string)); } } - Err(err) => return Err(LedgerError::LedgerHIDError(err)), + Err(err) => { + //FIX ME!!!! + return Err(LedgerError::LedgerConnectionError("test".to_string())); + //FIX ME!!! + } }; } @@ -110,3 +118,9 @@ fn get_transport() -> Result { let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) } + +fn new_get_transport() -> Result { + // instantiate the connection to Ledger, this will return an error if Ledger is not connected + let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; + TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) +} From b74f9720f812512c19a8754a160816bf4f8835ff Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 28 Mar 2024 14:57:39 -0400 Subject: [PATCH 10/72] Pass transport into get_public_key_with_display_flag --- cmd/crates/stellar-ledger/src/app.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index 3c3c56171..8b32a67aa 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -33,7 +33,8 @@ pub enum LedgerError { pub async fn get_public_key(index: u32) -> Result { let hd_path = bip_path_from_index(index); - get_public_key_with_display_flag(hd_path, false).await + let transport = new_get_transport()?; + get_public_key_with_display_flag(transport, hd_path, false).await } fn bip_path_from_index(index: u32) -> slip10::BIP32Path { @@ -55,13 +56,11 @@ fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { } /// The display_and_confirm bool determines if the Ledger will display the public key on its screen and requires user approval to share -pub async fn get_public_key_with_display_flag( +async fn get_public_key_with_display_flag( + transport: impl Exchange, hd_path: slip10::BIP32Path, display_and_confirm: bool, ) -> Result { - // instantiate the connect to the Ledger, return an error if not connected - let transport = new_get_transport()?; - // convert the hd_path into bytes to be sent as `data` to the Ledger // the first element of the data should be the number of elements in the path let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); From 7c1898b385a22316e75c6c520bdb688179840f1f Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 28 Mar 2024 15:30:11 -0400 Subject: [PATCH 11/72] Create a Ledger struct that takes in a generic that implements Exchange --- cmd/crates/stellar-ledger/src/app.rs | 36 +++++++++++++++++++++++----- cmd/crates/stellar-ledger/src/lib.rs | 7 +++--- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index 8b32a67aa..4bf749c30 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -31,10 +31,27 @@ pub enum LedgerError { LedgerConnectionError(String), } -pub async fn get_public_key(index: u32) -> Result { - let hd_path = bip_path_from_index(index); - let transport = new_get_transport()?; - get_public_key_with_display_flag(transport, hd_path, false).await +pub struct Ledger { + transport: T, +} + +impl Ledger +where + T: Exchange, +{ + pub fn new(transport: T) -> Ledger { + Ledger { + transport: transport, + } + } + + pub async fn get_public_key( + &self, + index: u32, + ) -> Result { + let hd_path = bip_path_from_index(index); + get_public_key_with_display_flag(&self.transport, hd_path, false).await + } } fn bip_path_from_index(index: u32) -> slip10::BIP32Path { @@ -57,7 +74,7 @@ fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { /// The display_and_confirm bool determines if the Ledger will display the public key on its screen and requires user approval to share async fn get_public_key_with_display_flag( - transport: impl Exchange, + transport: &impl Exchange, hd_path: slip10::BIP32Path, display_and_confirm: bool, ) -> Result { @@ -118,8 +135,15 @@ fn get_transport() -> Result { TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) } -fn new_get_transport() -> Result { +pub fn new_get_transport() -> Result { // instantiate the connection to Ledger, this will return an error if Ledger is not connected let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) } + +// fn get_zemu_transport() -> Result { +// // instantiate the connection to Ledger, this will return an error if Ledger is not connected +// // let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; +// // TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError); +// TransportZemuHttp::new("http://localhost:9999").map_err(LedgerError::LedgerHidError) +// } diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 432e68e00..2d8c8fd31 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -13,7 +13,6 @@ use soroban_env_host::xdr::{ }; pub mod app; -use app::get_public_key; mod emulator; @@ -23,7 +22,7 @@ enum Error {} #[cfg(test)] mod test { - use crate::emulator::Emulator; + use crate::{app::new_get_transport, emulator::Emulator}; use super::*; use hidapi::HidApi; @@ -45,7 +44,9 @@ mod test { #[tokio::test] #[serial] async fn test_get_public_key() { - let public_key = get_public_key(0).await; + let transport = new_get_transport().unwrap(); + let ledger = app::Ledger::new(transport); + let public_key = ledger.get_public_key(0).await; println!("{public_key:?}"); assert!(public_key.is_ok()); } From d477ce81dcb99bdae85047aec8006ae5b1d7c871 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 28 Mar 2024 15:38:59 -0400 Subject: [PATCH 12/72] Add get_public_key_with_display_flag to Ledger implementation --- cmd/crates/stellar-ledger/src/app.rs | 116 +++++++++++++-------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index 4bf749c30..cad01a360 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -50,7 +50,64 @@ where index: u32, ) -> Result { let hd_path = bip_path_from_index(index); - get_public_key_with_display_flag(&self.transport, hd_path, false).await + Self::get_public_key_with_display_flag(self, hd_path, false).await + } + + /// The display_and_confirm bool determines if the Ledger will display the public key on its screen and requires user approval to share + async fn get_public_key_with_display_flag( + &self, + hd_path: slip10::BIP32Path, + display_and_confirm: bool, + ) -> Result { + // convert the hd_path into bytes to be sent as `data` to the Ledger + // the first element of the data should be the number of elements in the path + let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); + let hd_path_elements_count = hd_path.depth(); + hd_path_to_bytes.insert(0, hd_path_elements_count); + + let p2 = if display_and_confirm { + P2_GET_PUBLIC_KEY_DISPLAY + } else { + P2_GET_PUBLIC_KEY_NO_DISPLAY + }; + + // more information about how to build this command can be found at https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md + let command = APDUCommand { + cla: CLA, + ins: GET_PUBLIC_KEY, + p1: P1_GET_PUBLIC_KEY, + p2: p2, + data: hd_path_to_bytes, + }; + + tracing::info!("APDU in: {}", hex::encode(&command.serialize())); + + // transport.exchange as is using the default implementation which is synchronous. but, we need to use the async version of exchange here so that we are conforming to the Exchange trait + match self.transport.exchange(&command).await { + Ok(response) => { + tracing::info!( + "APDU out: {}\nAPDU ret code: {:x}", + hex::encode(response.apdu_data()), + response.retcode(), + ); + // Ok means we successfully connected with the Ledger but it doesn't mean our request succeeded. We still need to check the response.retcode + if response.retcode() == RETURN_CODE_OK { + return Ok( + stellar_strkey::ed25519::PublicKey::from_payload(&response.data()).unwrap(), + ); + } else { + let retcode = response.retcode(); + + let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); + return Err(LedgerError::APDUExchangeError(error_string)); + } + } + Err(err) => { + //FIX ME!!!! + return Err(LedgerError::LedgerConnectionError("test".to_string())); + //FIX ME!!! + } + }; } } @@ -72,63 +129,6 @@ fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { .collect::>() } -/// The display_and_confirm bool determines if the Ledger will display the public key on its screen and requires user approval to share -async fn get_public_key_with_display_flag( - transport: &impl Exchange, - hd_path: slip10::BIP32Path, - display_and_confirm: bool, -) -> Result { - // convert the hd_path into bytes to be sent as `data` to the Ledger - // the first element of the data should be the number of elements in the path - let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); - let hd_path_elements_count = hd_path.depth(); - hd_path_to_bytes.insert(0, hd_path_elements_count); - - let p2 = if display_and_confirm { - P2_GET_PUBLIC_KEY_DISPLAY - } else { - P2_GET_PUBLIC_KEY_NO_DISPLAY - }; - - // more information about how to build this command can be found at https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md - let command = APDUCommand { - cla: CLA, - ins: GET_PUBLIC_KEY, - p1: P1_GET_PUBLIC_KEY, - p2: p2, - data: hd_path_to_bytes, - }; - - tracing::info!("APDU in: {}", hex::encode(&command.serialize())); - - // transport.exchange as is using the default implementation which is synchronous. but, we need to use the async version of exchange here so that we are conforming to the Exchange trait - match transport.exchange(&command).await { - Ok(response) => { - tracing::info!( - "APDU out: {}\nAPDU ret code: {:x}", - hex::encode(response.apdu_data()), - response.retcode(), - ); - // Ok means we successfully connected with the Ledger but it doesn't mean our request succeeded. We still need to check the response.retcode - if response.retcode() == RETURN_CODE_OK { - return Ok( - stellar_strkey::ed25519::PublicKey::from_payload(&response.data()).unwrap(), - ); - } else { - let retcode = response.retcode(); - - let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); - return Err(LedgerError::APDUExchangeError(error_string)); - } - } - Err(err) => { - //FIX ME!!!! - return Err(LedgerError::LedgerConnectionError("test".to_string())); - //FIX ME!!! - } - }; -} - fn get_transport() -> Result { // instantiate the connection to Ledger, this will return an error if Ledger is not connected let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; From 6780f4d403dc65588dba67a20cc5e61aa67abbd7 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:12:31 -0400 Subject: [PATCH 13/72] WIP: try to connect ledger struct to emulator transport intead of the ledger device --- cmd/crates/stellar-ledger/Cargo.toml | 1 + cmd/crates/stellar-ledger/src/app.rs | 15 +- cmd/crates/stellar-ledger/src/lib.rs | 16 ++- .../stellar-ledger/src/transport_zemu_http.rs | 130 ++++++++++++++++++ 4 files changed, 148 insertions(+), 14 deletions(-) create mode 100644 cmd/crates/stellar-ledger/src/transport_zemu_http.rs diff --git a/cmd/crates/stellar-ledger/Cargo.toml b/cmd/crates/stellar-ledger/Cargo.toml index 6dbdf93d2..c454a8f15 100644 --- a/cmd/crates/stellar-ledger/Cargo.toml +++ b/cmd/crates/stellar-ledger/Cargo.toml @@ -31,6 +31,7 @@ bollard = "0.15.0" home = "0.5.9" futures-util = "0.3.30" tokio = { version = "1", features = ["full"] } +reqwest = { version = "0.11", features = ["json"]} [dependencies.stellar-xdr] workspace = true diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index cad01a360..795078d76 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -129,21 +129,12 @@ fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { .collect::>() } -fn get_transport() -> Result { - // instantiate the connection to Ledger, this will return an error if Ledger is not connected - let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; - TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) -} - pub fn new_get_transport() -> Result { // instantiate the connection to Ledger, this will return an error if Ledger is not connected let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) } -// fn get_zemu_transport() -> Result { -// // instantiate the connection to Ledger, this will return an error if Ledger is not connected -// // let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; -// // TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError); -// TransportZemuHttp::new("http://localhost:9999").map_err(LedgerError::LedgerHidError) -// } +pub fn get_zemu_transport(host: &str, port: u16) -> Result { + Ok(TransportZemuHttp::new(host, port)) +} diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 2d8c8fd31..9a4e32821 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -18,11 +18,14 @@ mod emulator; mod docker; +mod transport_zemu_http; + +use crate::app::get_zemu_transport; +use crate::{app::new_get_transport, emulator::Emulator}; enum Error {} #[cfg(test)] mod test { - use crate::{app::new_get_transport, emulator::Emulator}; use super::*; use hidapi::HidApi; @@ -51,7 +54,6 @@ mod test { assert!(public_key.is_ok()); } - // #[test] #[tokio::test] async fn test_my_emulator() { let mut e = Emulator::new().await; @@ -61,4 +63,14 @@ mod test { let stop_result = e.stop().await; assert!(stop_result.is_ok()); } + + // // this may give an error because the get_pub_key is specific to app-stellar and i think im currently using a filecoin app elf + #[tokio::test] + async fn test_my_em_with_get_pub_key() { + let transport = get_zemu_transport("localhost", 9998).unwrap(); + let ledger = app::Ledger::new(transport); + let public_key = ledger.get_public_key(0).await; + println!("{public_key:?}"); + assert!(public_key.is_ok()); + } } diff --git a/cmd/crates/stellar-ledger/src/transport_zemu_http.rs b/cmd/crates/stellar-ledger/src/transport_zemu_http.rs new file mode 100644 index 000000000..e6e9861f3 --- /dev/null +++ b/cmd/crates/stellar-ledger/src/transport_zemu_http.rs @@ -0,0 +1,130 @@ +// I was getting an error when trying to install this as a crate, so i'm just going to take the pieces that i need an put them here since i am not using the gRpc transport right now anyway +// error: failed to run custom build command for `ledger-transport-zemu v0.10.0 (/Users/elizabethengelman/Projects/Aha-Labs/ledger-rs/ledger-transport-zemu)` + +// Caused by: +// process didn't exit successfully: `/Users/elizabethengelman/Projects/Aha-Labs/ledger-rs/target/debug/build/ledger-transport-zemu-e14fd4e52eee79e2/build-script-build` (exit status: 101) +// --- stdout +// cargo:rerun-if-changed=zemu.proto + +// --- stderr +// thread 'main' panicked at /Users/elizabethengelman/.cargo/registry/src/index.crates.io-6f17d22bba15001f/protoc-2.18.2/src/lib.rs:203:17: +// protoc binary not found: cannot find binary path +// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +// warning: build failed, waiting for other jobs to finish... + +// this if from: https://github.com/Zondax/ledger-rs/blob/master/ledger-transport-zemu/src/lib.rs +// removed the grpc stuff +/******************************************************************************* +* (c) 2022 Zondax AG +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE}; +use reqwest::{Client as HttpClient, Response}; +use serde::{Deserialize, Serialize}; +use std::ops::Deref; +use std::time::Duration; + +use ledger_transport::{async_trait, APDUAnswer, APDUCommand, Exchange}; + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum LedgerZemuError { + /// Device not found error + #[error("Ledger connect error")] + ConnectError, + /// zemu reponse error + #[error("Zemu response error")] + ResponseError, + /// Inner error + #[error("Ledger inner error")] + InnerError, +} + +pub struct TransportZemuHttp { + url: String, +} + +#[derive(Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +struct ZemuRequest { + apdu_hex: String, +} + +#[derive(Deserialize, Debug, Clone)] +struct ZemuResponse { + data: String, + error: Option, +} + +impl TransportZemuHttp { + pub fn new(host: &str, port: u16) -> Self { + Self { + url: format!("http://{}:{}", host, port), + } + } +} + +#[async_trait] +impl Exchange for TransportZemuHttp { + type Error = LedgerZemuError; + type AnswerType = Vec; + + async fn exchange( + &self, + command: &APDUCommand, + ) -> Result, Self::Error> + where + I: Deref + Send + Sync, + { + let raw_command = hex::encode(command.serialize()); + let request = ZemuRequest { + apdu_hex: raw_command, + }; + + let mut headers = HeaderMap::new(); + headers.insert(ACCEPT, HeaderValue::from_static("application/json")); + headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + + let resp: Response = HttpClient::new() + .post(&self.url) + .headers(headers) + .timeout(Duration::from_secs(5)) + .json(&request) + .send() + .await + .map_err(|e| { + tracing::error!("create http client error: {:?}", e); + LedgerZemuError::InnerError + })?; + tracing::debug!("http response: {:?}", resp); + + if resp.status().is_success() { + let result: ZemuResponse = resp.json().await.map_err(|e| { + tracing::error!("error response: {:?}", e); + LedgerZemuError::ResponseError + })?; + if result.error.is_none() { + APDUAnswer::from_answer(hex::decode(result.data).expect("decode error")) + .map_err(|_| LedgerZemuError::ResponseError) + } else { + Err(LedgerZemuError::ResponseError) + } + } else { + tracing::error!("error response: {:?}", resp.status()); + Err(LedgerZemuError::ResponseError) + } + } +} From b968fc78d8f97068a0400d4e966e7c1483a5b166 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:26:28 -0400 Subject: [PATCH 14/72] WIP: connected to the emulator --- cmd/crates/stellar-ledger/src/app.rs | 2 ++ cmd/crates/stellar-ledger/src/docker.rs | 2 +- cmd/crates/stellar-ledger/src/lib.rs | 9 ++++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index 795078d76..84e11dcb4 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -7,6 +7,8 @@ use ledger_transport_hid::{ LedgerHIDError, TransportNativeHID, }; +use crate::transport_zemu_http::TransportZemuHttp; + // these came from https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md const CLA: u8 = 0xE0; // Instruction class const GET_PUBLIC_KEY: u8 = 0x02; // Instruction code to get public key diff --git a/cmd/crates/stellar-ledger/src/docker.rs b/cmd/crates/stellar-ledger/src/docker.rs index 3049e7a6b..8a55297fd 100644 --- a/cmd/crates/stellar-ledger/src/docker.rs +++ b/cmd/crates/stellar-ledger/src/docker.rs @@ -108,7 +108,7 @@ impl DockerConnection { attach_stderr: Some(true), env: Some(env_vars), host_config: Some(HostConfig { - auto_remove: Some(true), + // auto_remove: Some(true), port_bindings: Some(port_mapping_hash), binds: Some(vec![volume_bind_string]), ..Default::default() diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 9a4e32821..e84325edd 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -67,10 +67,17 @@ mod test { // // this may give an error because the get_pub_key is specific to app-stellar and i think im currently using a filecoin app elf #[tokio::test] async fn test_my_em_with_get_pub_key() { - let transport = get_zemu_transport("localhost", 9998).unwrap(); + // let mut e = Emulator::new().await; + // let start_result = e.run().await; + // assert!(start_result.is_ok()); + + let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); let ledger = app::Ledger::new(transport); let public_key = ledger.get_public_key(0).await; println!("{public_key:?}"); assert!(public_key.is_ok()); + + // let stop_result = e.stop().await; + // assert!(stop_result.is_ok()); } } From 614e75be58851497edff7ae6891c1dde8a3fb429 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 1 Apr 2024 12:46:47 -0400 Subject: [PATCH 15/72] Add stellarNanoApp.elf --- .../stellar-ledger/apps/stellarNanosApp.elf | Bin 0 -> 180952 bytes cmd/crates/stellar-ledger/src/docker.rs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100755 cmd/crates/stellar-ledger/apps/stellarNanosApp.elf diff --git a/cmd/crates/stellar-ledger/apps/stellarNanosApp.elf b/cmd/crates/stellar-ledger/apps/stellarNanosApp.elf new file mode 100755 index 0000000000000000000000000000000000000000..9163d930d1f0fdee5ec62e04c28e8134f45d4f34 GIT binary patch literal 180952 zcmeEviGLf_)%KmyYI#$ke1p+7u z6elUI69U8uOQ5AVp`^yeeJRClptNOc-;opA1iC2D)RG)tT>@x|A6uD zH`V;HCr*(M5my3BN13q~i?b{Nfm6bQzS4sppmNOu`3U%!~^p z@W-($|MZnj%GU}1Ot`mBNYidL1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR z1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR z1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR z1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR z1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR z1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR z1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR z1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR z1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR z1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR1T+LR z1T+NxI|QtKdU4|qO!8Qg%`{1zz<5VsyDvv#aw4gZ8vM+`1v%LkXM(j~5IOml_;Il# z2X}p}%1~E)B5&*JX;1nJBbE>!ToLjGcZYTa-w3@Dv~18x`2$Q>po1%*ZFTX5G z>2o44Bmc~SodGsusl+{?+sC%@o&L)04O};qpBSDO-5Cm3mx+$Xu)7R-^BTi5OC$FL z*vu+vWiPj1&EMi{Y51V`l^lBJ-QhBYGLPKMZICSft)7DDW?+yT-5y|W;IUVIigF(w z;iJ5t$puM$Pf!1SH!wEZ$L(V^e{E!L&q=O!k?DuNnZM=S)$g)SKE!Iw(;xL!zVi(q z_bxZ5{9XN=yx)GqXMBep2(K^Uoi_8L+w8iiLHNbg7riF&?rByr?wwYx_nQ2nWo$k7 zNZZ=Y_TLXa>f_&G0@vkVJ5v&4!jTW(qHQ99!0I}1H#p)=^>VEP#CrlFO ziVJPbJw>|Dt~>O&ujG`zQxI!HvvMrW+m>Cso0o&_abBtFaZ?UCujo0Q22A2~ChkB-n1)@PL z?iL(o`G1n8?+U=`n?r~1y{DJWG)ks!=GcDkqyuL852F=Ph7kP1uz*U*#H7H9TafuGd$nTRqpcfjWdOq5tdtkCyh?*9C*X0mI zvpkgO7ml;)CQIuq!OP{?1U&K8BquW0l8F9c-Q$J_jS3by87zzn7FKZIGCDbWk*I&% z2sEx5wRW3q+G#2_DKKMcpi9KVp`j9#Lz*VAQD8o}OPaTGZ_V-oH#3 zGqtZ!YVR03pkqS3FCW;Nh|RboQ|~G zWuy9@F!V~Go9g;_54YDSeo^2!=Sml9dNAR@2;g4h@VnQPD&^jh;Jceq{#Sv)tIS>I zLEZmKtt8d0Cp6~RJZQUQtRcZ%W~7yN)7=NSGfLrJ7A=GZ-8?!C9RCm)*4av*<&U_K zV@cBGpb|GExC3ZY$sqgVe;H)*{bL^tQoTMjF!m8H(z^-~_MM3u0d zySh2razkK}t>rTG$rBs>3!-dxrPP92IM%H7bNJsc{G2^eMrD$8Ii)8Rz8yA7`7_}! z<^S?!+uP-Se;xSANtmO{n>Ms^?!t&*a|g^aOKc8ATN=vFTuGefNI3%2@zDl9i(ZVn zB}b@kOPU4IDeC2g&@~&RC6{x;;k6euNzxbhuJ%dZTZ{9e0B{&0laWrJRA7gG;o)JD`k27D(Qp%YgQzMFCBaZz8=i*ptEOb)To zaT6DrR9#U7VNStphAd>}I%}*ABb7xI=I-!WB80!o=ER5~ zIL&h5m_e=>;>r4vr;JeVIxbF>!Rx-v*lSNQ7Vb02-yaS4$NT=i(iJLo-6iF_SY&6k zz~z=cYG%_7E++jndOb!{AxOF-HlzoJ8;9H481l_~?_vD+yw{Fg|2Sik7mm{PO3kO2 zfAuQLcX=w`G$o(?z0xy(UCC;10yS5VdW)n`yI?rbMig>Ep%fHKM_;La|8gdH6dE(} zE0r~N=3!S|WcbW$f~U3qn(aPz;?px``Qst{hO*7_wxOfYf9Ui0WYV9{Y-yQ$%_`1p zld3*h$Dm0MET?gE?loIlI2RY8Ux-)FHD0^ts#j}*EzA{(ym{tt{_s}LrHgcLWhQp= z%`=0*V7&Grh2Qsw)Y?CP23$wxfG-v~cINlZuht$06&5)R9);FdYBnfbc7eCY6fXDU zn>)qja)rx1XP#);nBwyNEG{=*^F$Vx?~k;B%eJe2H8{@6JcX0TzzHGuqbRif2wkt# zyp+PeGsWcw1-CCKxNT85S_eMXDjZ#fZ_&7lTLPc3cF)we+eXtGg4>)d>Tb}iRj60v+jA-E|EEx&`d?AMaRPOAMlb$W_}WVMI_55vEaIl6Y+$Zf zw`!ByeCm{=9jSUL>O8uxZWR+cma~~GG8lb$rYrKt=xc(j^_6NjYF#s6mfwvtmje=V zJ$TXK^Im+GMUJ%8rRw{atQM%d=15k3og*&P*LBsawcV)iiHJhi(SG&!qHUR0e+s3I zxB6pIv-~ZT$D}TNo1H?TSwW#Q`rowr2gB)BH_5w)>3XH+(p1|%87B$+iuADSW~na9 zc+?xZjCuyOZ~%3^7G-aWnBYF!Iaa%<$jY1{7(XSeOiNJErphf&5!p{7|_$6Pw; z!%&miB)>nbO1w)NuelRtPe<96DEs?L*(N!H&&iShrp`B|>O3&0P;_j#dOJoz6MQ+7 zJc_Z1165&&6XN{_!6ci9;LD|WTb2j=w$C7%1ecdpa~RKgSYG4)>{bQyErHp%{^ zG1?$(XV8pJll;4+9$raLKMz{(!7udYl3!TZ>C20clpG^$cTVM%r<)6a4JQ)zg;;duobgfJbp8&``cz@Urbb%c&i9M-z>S zu3^G4p!{Zt=3)$Hazh-}xublgyUpfzBG1#qu0YuCYMZQ#ygI2=`7DZ*fjDBx8>!!~JsKH+x)uHr>?b@|%LnCSjh>m! zvsT`@%pttYh|U*=_+wP(yHMwFuL0vXb1jPS-6r|}5_z2_xjXIxl~2Z4$lD9M>Lhn8 zEO;==!O|Gxob~uDPB6hJH$#4na?_Ar{>?}-wi1&4RDxIR<2=;+2<%g;_fMtCQC_jS zDfO`g{Xdj=0TvB;C3Lr6N!y!9#&#tOdV>8%Ih;H$Qa}Ceql|qWmI|xgEYc`jGt!+x zAee=~S5gS%M7Y|I1_^;1fq)DT!z5dQK?^wh_E^4)KS8KWPqYQlqa?j6SJJuu7KsMH z#|HeDFNmKBbZjum{~n{eo8rJ(`PP9}JuT^B4{<>~ELRTp;#>Ijy)m+@Nbcf&Owe@- zu;P{VNUc?zk%m|F);4w>!31g?!CqEp#DMKgW-VV4&zZKpF&-8rGojU zF5`i@PPU)hr#p)HlKerOtGU#RHbBIMv0&c;JRK0*z{jHaciKssOe3sjv1`t$XuDbF zMv7f^rvjqYvjp;DmLtQ9UCvWyz)6#*1a{r)!^N)hQ;s_h2W~)gxJ? zgsV%6)Fy(%IlR0hW9LzEMgOCnakYhAr~WxZMcHFD%d@|7Is$oDA6y88B;+Z()-8d-#^ z7L?ji5&>a59WwQ#Z`AubB`!$sd z#7_oG+t#$@xVaNdko!4YJ%}~ttt@ETx~v2qKH6)z#FaNtvcQ6r79}M<)Z)?&2xy&r z=yKj~lz#wxsqC5!j3vLu*uQXv`*NEaTA7>2*c4ip6E!w@ZCuD3LPRS9E@8JLh9+MZ zC(Nl1#P?pFu|+z@;^3j?3PRgqi>~Kg3s11U&Xv`~Q!PofS^mLLiEH7&AUs5~d}^r9 zHm^JsVh+9hcgS^}G^M$m^45^2W|nV5UIF}?<)02YZ8ORjD>+XiXQ{*vuuzy4BDZ;+ zNxpA{$rbT%5ybiZFojVJxBGssdkD#ynf1xr)*Wu)!fsFL+K;THC4js7W`qD z!+7?>RvKHaz$xBO?ZNiCL*B{`U+(Xg1juvM$)}+K;Dv+E$Ai!y{p>&-@n2r%M&td| z9(RJ*4-?7cZ>CefM7v(@f!;>lR=U=a)fVjc!k;(ETN5`4CPYeB+bwJ0kpwwJP?E9l zCS7g8{xsi2k7!sxBdI)_vr>Kf>h#lQBaAO!Cn(e4a|$ z{4|b)*_105gCD0fd1Xnp?ee>pQGBOodC7tnaM!cE2wo@M=cjTnS(oGQSndjuUvFu) zG?h51ZJ&*oxcC7<5N?h48_`l<9##FpZ2r^j_N8sAjMc83L2{x<_5FC>J5c2pY%IjP zrbhM($62k-(*Y@ovk0PI7 z1OHAp`u!&O!NpQOC}+z*MSu{?NW}Zw78>yk<@Shw9yEaW0`N|FQ=d-vZ<4p`?m_=x zBxkVg6y)?x%xPK0!IrrKwoJON*1r5^wFeV20&12&P%@t+x<$ciO1#CTA22H%Je7cz zhcZu&jQ8P(b~5$=uCW}(UP7BsT3M39$s`XYOI(u&3j9tRX+DQd_j+-t#aXg|J;?2I zLz17g>E%D5Z|NnK{;i3ZlpgTy6n)Z5RF|EgztVCp{ns<}NynZ||0-vZLZ9LWY5JF@ z=znDbeHZ920DY2Kh0?lOTQFd5oGf|Tx3oK(xyI>Il~OAKE%^0B>uj!V81m(?>gip%=3{#n49GFNy40T zVKVj?j2V)Yh`*C&Png!mo+tDOnn4+0(N0U5Hp{e5h@|fC=^6+>&{%Sj8 zO`vsaE@PzEohxfHbtwh4#n9^xg+tN;d7w6DfW|D6RPs0N3b*^m5L3UMxyvKmHbl!K z++ECFg2)$XcWcC@eC9ENxQ_S} zsAZ4qXn#Jjv!)##&(a*Rc{;4cdTE zyM5FWE%KKt(q@7_C~?^o%Aby?@}&*e*|^ei_f$B#xay|HqK9hN*d>RDQq@o(dxa6u@#_GQT*TPh;1n2Y#s5x zu+S#zh<4;UTSt5wxl|o->qxeaSd-#;Tt{4{@T}^H)5uHpC;va)o?F55cX74j+Jx&l zU@=iYyFpde&!4S73(+sq`tzv~RX?AEk4fdE_4E68GWI86b2iSyqZ4udcyt`+yGK-c zK9-_IeR~Dy=poaWmQ18|@*G-kX3=U%(dx*emB6?ifgHA+L+jW%w4TbMH7`YLV-~Gm z(CP-QwV*YzZ`=oJXZMY}Kuzr%4-Bhvd0`fCQ~L1L?H`(~$lq(y6bi<54Nn%az{x6QtWR={ZO* z7$`;l?<1YYeKr5sNDJ(%Nl1TskoOzpm*9t|<3>ht^%^(7H8?mM%qWZWgW8p!F~G(e3BZT6YeuE3#<)Zb+?TUKTAQ zX#EVd9<@!Z42Z{4Oq5%iB>%%WA_ieq_n|HSu}TpW(6bp{~E@IqAY9iJ$R2c#ag5h!-3TchE9E_ zt~1+e)E(8!KZjqhSL~?w<3?zH9a?!$B3mCVLm8y&@>kMWO*T7Xub^HyK%AN(_qVm89r3gI-mN-)x({5Hvq)%y_~%z@D_} z9N}bC#D$XonuuQUt%t5^&^ydfkc)cMIKD@UPK<|De}p8720%1C*NXt zNdJ)TAxCJjnCHo_(#Z$VVophih>POaBLCC@vO?7REx0cpaD;S6^Q#tv{&wU>q%W0w zBYx!%=mrw8Cz2*aVF|;g42I!{4k)&&4Z7~Du&GSACbrFusAVB+{IlEU9z}}C`c1_C zo>beWB11RbHa)kUzik>o^Z(a2(=%<8i1jDcw)tzyhfKFk8|a_EZLI&XZBCD>ZPPb; zcH8_7waRXrpTej7&u#MtyteVSc?S8j+vZ!i&u*LVBlmdQ?8mQa+nh+G+vbkZw0=mp z%{J)Ccf*X`h->1Q{wTcQv&VFzNn?5-8KI#Q7-&?!J0 zV~!~0wYwonW3Z@HPbWXsH)f`2<)!*wDQJcJu5{#Z7QQ zSpBuOxmXuymbL^+7Pz3FTSh&Y330D6M@tr{(FaQXH03WcxiB77TCO+c$1Y3N^&of% zDSQ}}Sg;} zYsv3KgihzOz*5eKrEGpDNl)Dt4IY;4R zx)L)n$e+vdzvqJA1-Qy^)q}5Gd^&M0#kCDQRLPv(uI+}E<5B$-XS&*@3jB*4ttpm9jGs!|6 zw`e{DcdA8`h+PH#NHgq6@s}RchQT9^X=jhaWKW!L96p{!u?`ezd~eC{m)2{?K$C2w z1Tb`Dwe_8#xp1J;KV7k4gmo`Zv8SvT|Ij8USuxc^7o26iv;vI^iFy}VA4YjDy!+CD zs}u?50$!|ZqL@@(Dkh~vOezuEG?M7gRb=xtz7dCM|DWod9_NyXJ#7S^vvJ*v`2N{; zyiuO6U`jSI;`~{5{87cqCOaNu9{hfipHo?W?7;t|?RbNj*Q}010ys}9-gS=OtyspC z@^5&F`T>>FJtJ!Cd_PWYogVLYgVwZ^EaqiwcB6b+@vXDRwC;=?{s~?%wevSpJf+*D z3ACt=lbY2yG;z=eI#b|TVE(L%WB8YMSh705Uj8HeNwN{BjfmI# zZer{UxF+g~9Hrh*2rsj?bsKC=7y;CA`oT=Si*a@1zHYi-ci*V?>$CkTqx>V#ELHrfb7NcNSLx&@p)b=~V=>xC zkD6bv@JY1OHqt84r#?Q>Pch27QNm~WDF*qfB-z3Sc~0_dTljjEI&KRa<%{5hpWhZ% z{f7{Fv_*aZO?cqM!6FACGb-67Nz}^S;(KZg)uvX-&I?9<&bI%>*arCeC4!` z2tuy^4>sop#fHuH^Nm<9c|L!amkSlSQG0cl;_vF^*RuTGC)>~G?;7QQ{D1sio&4*J z^n9ZDyL$PN^Z2`;qFt!X$9+?y{Bp)O{nk0YblNw4I!mT50^cO>U(TXW^4wfivOJcib6bke zv<#gVh0fV+(*!!!fh%b&%+lSw{1)h_F_=W`%@n2KtanVdP(zDm!ft;@kt4**U!V(7`#O50MR*{uW%M$cc&;F%i`-1(AvVQ`g=UqWR&0j z40`Lb=zTs#?`v7~>OrslJoKLZ40@g{dUI0rzK})l4AxOSs?a-IF75?ARsSp4rDk(^ z`A`bCG0btRaCG*KzGvbpZfX8PZ zJ}H80lyiFXto&jHl18$*#%O1OU`TY#7=M_(1FRxc zf0?$Gt^_@5uWTJU4YWRM&R*4_yllussm5Z5Qi#7XVNTr!+K!YwjO#vw{15nz=j$tj zDSu!r79Ugl$`cB&ijG115ue#~-ptTBTNkH&_p#W?6rDRWbUwV6=w$0qgG~Paxp8cs zW*SpEbS&mZ9Z9RN%bJ59*fehcj>lXL^4$twXUFw;`2eI!wKc|KJmiXOje3PIvNh6n z@G{UOTZ8&?QO15X$ZJ8*l#fRY)m^l zJSVdJ)!%N!`Y*^2`Kr_(#@pQ>SD_4QcZwg;Y8ll!d=(|+Vq_bO%>w-#g+9p^&HsOL zg0#Y~QuNa@bq~r|j4}x4!i?``kUxQ6S(KhHP<7jU(rr%EV~yf%ku5P6yCv0XPiFCb z2<@iE&JCiqnS6Iz$77IR2Th8d*V$+cbf?#s%%}B+%*o5oWud+tbcx^FQgqYyqXgQj zoSselP=+??^mA$N%%WYEqV1nRdwYs@0$7wbt7}7u@6DiHIZ))EZKM7=M~QD2u3$AY z@Y|wc)ov^vo<|;{QkOblbeU2f`SoM5Go$41>1AtHd1+g2F3PMy`zHEKVrgbQib0-- zGN+-;#Wuc^W*RQE35cqrR7BMc^61b;#acZz#2*}Aqca!f7NT68v=|aFGga!_89z8( z=4+7KiS14P|7W##t73Vp?R^Klf0EOqS@DEtzI6WfCjb9Gwf7H|K1=?>7-Al3d*3x7 z4p0kT&)44H%FsT4d+*7j?MTsHHGy^ne5ZBZcr3&q-wN7lEaY5YgyeL6M)%#W_?>#W zDuXfUza5}U{nffsm3``gmw_hvH$rQe(fTd(I=y@m_AILYph3O>5d;Gw)=aj>$6~K3 zezzW@arSs}0qFi1wtu)U4>31`+k#QKAaV%)rx$TE<}xU8q_DCYC)`i*xUkYzH2+WQ z&4{<{9M%j$-l}kA&;J|biSz#kd3^qVET+!?>*ehE{}-S;zK5$HG=qHydk#6?VB1^2 zX5-wGv8#N6q;oFg+Xi|99|u&gk(c?QMbY}(7KLR>H*q4r;Itz-7B1va=boW58-;7uG}ODC-~#n{sU3SO;m4{otWMS@%eY z(^^eV{s`z96-gV5eFc8FvFaa#dcO{+5Dz402e4+DWbH3)j7PYNUfWcokl1 z*+3yM$idD(_r$s53h?qu}B5)WDNEvVt$@VN5_XpJ_-o+;XtjjQHLzC$>)I*SqQpozc~8a!n$^x>LBW%ZhL0faXdBS9|)3mHODF zV$7eUaV?d`{a5IJ#jj4|%E`|`2bfY4KcduNA^sC`(-XhW;`>WqP<1QuoS#{HVUSmX z9?dJd){SGNu0K#wh@_~o6u&QlCdpYn=xtVb?gI9{b(h-O1Cts{rTk3%)*w%t!0R;% zuPWWA6kcggjHE1*@jstgua0{`j8Q8EBS*VFr$~hv4+VQB;Efi+<6(#qs&fjQJX_I( zD&-Jng=nnG!za=HR))3@w5cyn)H!d0qN;QDrgRSf89Ju`5ub@Vr)O+j=PZS%pVm3o zE9GYEoIkfh?+qlAbVfv_I7Q6EE_MA8CNGFHCv(k~bPwyhp7Yh6($#jDa0ZH$Q}JA* zOB{(UKGkVfl{%XJ{a$}V~-wpBpLff zlI!AWp9gs0oK`RG|81&V;akz*4BGp*_0dj!%J=;wcCNUVOWXlZP=^*rK13Nn&2iuEd*G$OsIMSv{?bxlAjD-~)gxwd|ZRK@Y zPswc!KAqFzon&KssSJuL8B)*pw6U(Y1_f~&xTModh)X&jgpbhP{vt`hZiSK9^<%7- zJg36}2l#Se&?UYncHwXn^2)Z@~Hg<319;uI#_d^!f1<3I% zxOGW&qIK>Jan?KnPIdb;~EXy;0<1!_sR;5WTQ z>GvZ2MVb}suYrDZ;YTY0n;cR&CJiBucu9V4;1 zqtr^)bi23d6cSQP0Z5bCV}Lm!~{elJ(p-vz~h?BkLcHrCXdkk`rOimn`6Z7bVXlaXAe*+PoZNYv?8jkbz&c@ zD!)I4-;%Cdx_7^VG$UkMA60WdsO%|@EB(=^^v45ADbN<^ktN`Y+V3&6UjbU#sHWNc9NDtLF4;mt;g2Ra>$=DX~LG397)iXKX9fhe! z7Mn8eY``lU7 z>d&Hf^ek#s=b^Spp+?-Oyv+eM;zs4of+tLpkdp45pqAz)EsKXtHVzWDxUCMS zM=euUBY8x^Wb+7}bCBpCi6@iK#*)d0ac#$SBd)c$F2VI9j326{L%sTPMQ4roY8Rw3 z-K+nC^2U4h6147kuO^M1?$vhWJG)ok4ZB{4(FuDj&_*6f2Ryi1-ko=X#z(TK^suH3 zuBwP?3H@zkYE=F9Z}`#JKZu@R8X+z74y4EuDNOx36(!}QdY>M;CmDM~k+S)ywH_n< zxCJ*bOeGUqo**;krZoHpJP-N9bH_5$xvgIvO_Qxxh0*l)_fUt$k~%JJ8TCZnu;xf=e~t8+nO--2q$)Z`G%r%+^)>ufiQkr1 zkd`AWXmF@FLvepHA0sA7Z=J$_PoN(5M0cB-=Fae+JxUrB{>vo}7K`(I@Ytd-Pyl6~zFhhu$cdD5^dn)jhx(va$iAI9Cdh8#k# z8P|~i#ND}Nz~V!#rcEe=v?)= z8JMl`wsPD~$>Lrw<+?XWdG2eae0RH4;NC11BI;pv|Bp1uT_hE`w@AhATcr|rzEtY| zsx;Ys2j)f%5g|27A064&Pos4D{>zvlOn<*EIsW}AUs;hgsiW z&iejZ*0bA&#-G(^<#{?Qy)`TS+OhHcf6n^8C@cThv-tmR*7u)eeLsco$xo8Wv`>0x zT(xZV64zDb=1w_+oM4C5ppJ>|A(DnFw7jhJ)$7owNY>O5_e!)HNrh?+bmER=ZIzPq zZ@4o_pQfbZwT!+S?+qhyqCGA-b$B8zIkY#N{7jPi3o&Y!NYeupvlE=OX8?8wc^x9= z=rU=)Knd`Y1=v2dX8296AbFp|v06M0s?dV*{tMSJ=Ep^Aj$oJV)pySDPrBN;cQSVV zTP4~t?y%``PRzIPOubZtovg;L`Y;!2vg2&m8h4b|wF$PkgX=^THE}=grrQJuada?I zj+1YcXOpUprtj7rQO^g-$M_>dQ|3WSmPhp6To>&WXRe<|;1R<{Q`QeDr~loI)PGC* zZXQoQhwm~pKJWUI^jE}fdHH9fn-FiJ^8{#)tr}}h5Wj#8Hxm2i7@fE}5?eb;XPJik z@5Ow2PhV#boqJ<#p0tw3-p*X*sA!#6WQ-1g+QqXpHvqFSa@0y)vx0q7jH9p`Pn$oiKxaPBF?K zCUxL%BsQqn7V&;M+mLu6p6Dz+Dxb$E`KBLTreGp(0S3TE-j({~lusk}>(j|jSPe0v zrxFAn^mk#;KAksm;Y#IMfyq2_(>w`c)Hnyh15Pg*rZf0>iDn--WxYWG&O<2i^7 zyg22Ui5wL7C(gMjozo#KD1d)}8K*cCX#SY$a#b>@;3a;Jv0s4K2(IUF{RmfGGxl8z zI{E2g;$xiGvMgTbrFiW`-fexkI3ZO)*?-3gfm}_%%N_Fs>Hg0{^*~gISxnmBkc=IK zCOa;6uf^J5l=D*NxdA-KI~u&)vu@?NH-jD9j9GRi@5RM9@#8RzMON^uenKJ z)$<|kGgp`R^}!Oq*P)a5jM5yI=lV%lHAI}#`XE)hS0oHkT1#Stgx@wy)-#=*lkRUM z>uDLMzMtlI8`6pY&7;ZWcP6w=5N%^u+6MNqds2j2jn>Q70sG+E>1dxxI2VUv!Z#z; zEPZ9I5w<*&Z^WGqxe299a5e(@$4qWRY91o$=fk)y5$yj8ayyY*#dI9Um;Ngpt$maH zlbVWF>~7`jiX)a*R%eaeBv_kRb>5oI0tbym`oOH{0~N2{G2n%C!t3`#gqQU*@LDjE zOx^%&tjalK%*{n!ZiF@LxXC%S`CF|mwkOuS-pjjT2fJrP)ZO#ccbXHVlZtUd6wRYK zfWQC{D2`xeDypu*r(8=Cq)jbKE_JQNaBLU!7e#@sn9m)Fbq=X#J*&8ozS2QQk)HaM z$k?|pWf+6*?XiocI2qU^cGz{0lHqum-Q)A0z{$9s{C>n6YPilm-}HfYJ99qg(+%h! zzoYetor@OZRNQUrpY!npx3{jevuM6}iDQy?>J@h{+uHha@CozQRxVh^ueKE#_7%L? zx+CbsDaN~Wy9&0pHUwG4+k-k-jXF6Q?_i{@%I~q^S9EaV~JZZUv1otSVJKo$%WKK6s$Fr{7^Eow~b+dv?hTimF#dOGL*4?&zf8m8hRkr;~QJ?pd?i zJ~YT3bAZESOiH?f%n}GzITqJ=Fc!?fX#UPngPo%48`d(MXzf)_)V9F$vLbGBqSxaf z%kr(HHFf`ja{p@P{(y4-67K1QRYVts@6;_n@qEBLbJ;02(=2U$gIJGBTA38@>?zr&R<_qmM^XfatDr=ZJ*j{ zryZoDL(@e=lz$XubrDCOVtxGwYuUm5IB_w3(t5COztS6QkFnWU$9S{}HcAxsV4+lY zfOT@+ZnXbAaQ{Nmgm_C8IF2Yi3>K77g7y!>+9^R_VlxV*3PeMfIF(2~Jkt{8`Fcfu zlCc9x17;COO8%UnXRV*{>>pqOPJH(E%(K#eeD?M8JlmRi_QlMza^EvO1)}Hi(`B#Q z_xcJ?kv8CtdyDppc7VvKMXE`ojVk42rRB3X>ty+hja(H&T8}n zm4n$yk_OUSdwLKR5*Op_9_|5tpP@TXEQR*c4Pm^f;lw44E~#|Zd`bVHrH0k&I+x)2 zd06lC`J@SRd^oS?QMwBzbzSCikArJ8bT{Ax|Ha~TF*?X<`56@_>>|xkSjBS4$~_~8 zeZ?n=qa04_=eu$S4Bg&JSc`Q=XbWg5`|3()sahSdA=%?#<>!LSccEXceQXBilH8`Z zXH}O3S+&>q^@i0rZ;eibd=wf0Z4W#ihSW_KOZS#^E_F`5%vQd;!EmCmy9D()6fc>n z8zB393MiIz8;@X5dz@_he@VFq44sy)oP*{=rpJv3n(SD$vX6fNGnSpXenVHTjVR=- zxP2Kf)BdC1oh(Fjj&_Jwpfp=mRclbfj2Nsc>n%1|3;J`@x8y~6Hy<$qHx8$~5qi`o|1{j~V*@mj z_w@gC8Dp>FlKXZkJ7Pp&5P2f5agH@c zfcybZh%9unW!3br4^wQ4a@;qJXcPJ}jSx9N>jsoVk#CAToADIp!It5}m@6ag zL{Hv;J8l402ihapzcBS2`JTs3h=ySFr+joynG%yQnck+e*wi#Sd$CjsB8^spsA;b% zevJodH2r& z`yS|F4{-Zvej>ly=HPL@fD5{SIm^&zZC&(Ui)oEqB&TlPJCnrRC3$ZBskQbaI90P$ zG(}5AT{Qnli8#|~SvzIchTrNtE!C{H45d=<+dF3HH11WuKQrcWusK}BEb>wPL2J3W z(Rj%4Nd6K3TH`ETB)@yMiYghc}q;L9Z=7IJ*uf4q|>JA9JKM1{@fA!8c`q ztEIWBV##!LN)>}wOzSst6$w*wT{C1#<^S9e^dEY6F{JCbnCnyecU8HF_i#w}vlwnI7>Ac;zY|5-aYpO&AjTcABK0g5X&&)6lWRz(t&Zv>2hLK)#M265@Yh$#w=0p2yL?aEC)z>#dm!{fu|EjPlb3>@QRA_8|{xN+!=A zQ%(`YJ4NWUz9O_{0pcJg@sUL*Ap@%tY&Mhb+E5eZgvIdMm^^Qk^7FW8CF3Ikhm-rC zuX$@$uGH2-@k?)jmwz|Ri;k^=*ty)i;A70<)7uQ_|1>bKX2b5h?959_00f%8&b^HC2P0q@0ID)jqIN(ua4=B~t?>htq4ay_J*wH)hl ziB& z(fweMq--`$l;Mo+`BC=Jk8xHk?Ov3J8CI+w z^I^v1fH&|pXl$Gwi(g1We>Kb=yS1$%RV((`c)d2J>UA6|%Es&U*wDm!{c6~A2ZyyI z;9ci@!}|xN8?ufUpkbJ|K&p#{NE8b z;s29yJ^ueX&f)(>N}o#2xpzY^hA~2WZtN(xaCNm3UpEUSSU|O2GSy9%3hO)(rtn*i zvk*`B8r)6rT2~-Gei`E9&4`b$gq>4~Jl;S~)PeH>Z4ob804J;7SQ%wX@BD3?7lm!y zkw^4`dz;Tw4QCx{Po-5ty6Ki|RX{w{sc z!CZflR1JqT@Xq98*zw2EUtS-?|G>}j|9If1_|MvpAE$h?8Bzl>Y1xo5dljOO}NvsK$=<;x|J4Z2Q0?f}>Rs zAMxniMqq&!n2nQ7FvghiCX*tmEzsjDmtO9rwHZGeyRJ=r_r;+x-i7y*C?(vEGT}+K z@pwl4_31Gx^A@sX<={{dF~m1IxH?PZ5$BFpU0n`Z2Is>%wzqMZ2hpJ&TSmhDTm|Pu zPaUH5MI^&QYr@HU`8q1)5fA4$(^ezqEeHcs3ovH#s-ZIhUNAT6qja~J){hI(HXbXE zsn!;(h$K4x4i2)LFMeCme6FongLmpU%yyDV((2&=C;u~vHpNq&%ZZXrd6Hu03J(s> z%j2}j0O(9rI3d2zPv=)>(fJgs5LI428YW)S6sa%K^Tk-cF8vAhw;v8~f!$hksxH)4RqPdJ!-*53=q^GUSOY_w1WC-~+@E{&=w^+Q9DoS&bWhwt5o=!Jp(wY1hC6#YN z9J`HgGPebnUzm&8e_ZxH^nb_8IIDO&JgjBGE6|hl4oSZ{Zy7 zEqYKg)E*=H)iZ-6Bf2^R+T8$IF-ADpwY-Bxb&i0zeM><6=@v6MWH@(xTR+u->Oz`0 z0A81E@jAFIKMU$mpJl;SsErYMKT5ca+}nuAM{&A*;BIHBmvbKVVx=~8%1ixeEXHYs z&qL$2^U~Pc$el&QjB*SpN9X55cf)SM42#$5&Bi+*a$9sjoX`7JLiH8^eG9?Y>cC^fk^q3#U8nOl%ZdtX5GZSg2Msm9cT={g4$dYt%O!MLKR z6`oxkM>b+Krz$s$?-3pzP;mJs#Gv(y7&|wg^PGHtTByX;-MR?PE^zWSux;qLni z+VozIZd{x~PcQCt=xV&R6nmicnxt>k@4r*N(~10dLk2Jcr0j-ppDx2&eu}r6LgG!A z;!UUUwkgA#4!jx9;%$+_TP}DroR_zc3vec*U=DK%ddJ10#W%Ii{1zGpz3HL_a zTe5!FPx$@MXp8LUe~tSkS^2jwpf<3CR`;?x{v_5JVO8vk&{ym7U~9x8D>_OpJLvl) z@vj)uf(3UJ$9WQZ3}>V(y9m&p_&hF>Op+`ZQC1LT9mIuRoxz+3 zAC|29xwwa3lxxV&!J6J8sSxiSTV0t~byedVwkzA2J4bq8PthL4oFb2~O}6T3)qy|S znPlxh=rauLV^`X8rsV{7+5e6C6`G3(V=l>v`4Y~};Z2CahIRRWbAL) zQ%2?4r2Glx6{0+fT@~$Vt=#W3|L#!b13vxlsH7&8!?Mbuc^T%$EI8)BCfQ!wwQCnv zte@rteUw(NCt)=)!P^TzPMP1Lb>-?@f2ou^q0Bs~c+b8)55Uep$abyMIR_f|`uJ03 zw8T$fH|wV_5lq;fN;2Agw4X+HDQx1%)E^t9LT|YTe_FCPETM-2T|!>?hSM=C%cTn z_qzt~F`)9+Pp0(@?v*o9r;icixJ=qqN#)P71>l7&la4~V8YK6gy}pYF=7=SWW{b9k zL=EHA2w9E=@UiO^`kWjaCfn~*_==c~z?khu87|T8p_zhd()LPE$9CV;cb5isvO_@r z4?zBqy*RS7w+pRtNJ%?nH$?C*vhb(ycChL`+F-8_2X<0Vx&^bQYFpEqiC{lP#wK6x zsVGNJ#d_OW|AODo$v+qlZg_6b?%sWD$GX*+HE6Oww=*`#X41-74)fP~(X(eud$>2u z%=LX+pxDVIM1M`v6HZ;z?$$|leB=cuf7zbZDs4-c^o|~=zm+8VdeD~~ zdW9mjYyF5-;o)bXg-B59&6-K9_R4AA5O7vlaC#<_R}57Pfkwf0(jl&r9PFTP6-7*n zN7Qyy>F-E31cw63E}{qaj51CN05v7$>Z>eiAErIM*7;R3284u96n{ z$-k|aE{hgUUyL0%%Omsry4`F>qjdKScH-_THme3wjyWXRH#9?>dASqbHdHR zWx-Az(uu*qa{;d%^HO`cS@TcTg0Fdy(-P1Zq-jn5z^*_ORxp%fp4}ED49YL}towKu z_WpJ*5QPO{$5cE)?X)ualCWS^{T)lZ#r{G!lfw2F9KqETEiAZvO91;}J3XS{xy&AR z6j1v`Y22d}@({9-QYroyEB_1e<{KZ*7@R7tt}MZ-mJZ;elbQmw`ho*q-@)jeha5Wt*Hj+F+p?0_ zYZLBYZTGcNU!k==$ygmyj*HoSlll$K5tWLp>VfT7ZC_#M_vxe?0=wa@JQCIKr7}2q zf82-&HDPmEQ^7jOg#5n=kImfa_p>_+9XWf=UP~utJVjlnp_6-%J!C#`pYLYOTjA}v z4_{<6cir!sGvKo`M+x@5OfgQCriFw#1t;$6WwUcnE{3fAxJJK13@hIQL5Q#8!ZTIOSfdF=> z`Ru1I!L#>arrvPCYd3T%-%sf9oknI|=L3UygNm|ajcEKnV(fZr>brx61C4f56uA(l zxo11;+FIVQo!$#hGh1r=R(Qzf=Ea<)oWR%VP6%#Jn&oDZ;PNjnp|`IgVukUBR-E25 zhSL|WxKLF4M?G*}G7=0;LIjK=s8&P`v3eIRd)Ek7eFcn=;^&~V^b+2=6m#v3bME~^jeL`5=%1q$`dcPA(aYv)XI&ciD2(w(Y zwr5;#_r%Bbw(b-&Gy@ z;(6&brs%w$!J=mrC+nSs#kIhKM@wK|80vp)m`1|)v9=a5@9G={3(lBRh?XKm)}>o2 zU4JvyT_NIxSj=%xHAe6Gh{CjOIJ_acffIzFw^D7B9*jTviVZnQ+GyYH%Z*e!HZ)#p z!~Dk3D+1OfK^I{L09F&dpT4KRA#~I|5@~ld1l4hs>PV3?ya^Nbu+FcRl^9Bog=A;x z1%|U*XabdM6SN z(pw{>TXlY2Q%*T2KZH2oagm|y7vO_XdT5ye+WIx@F+DD>nMLDSsUL49S0Yy>STB#2 z=&mV|NzEMmo3RU)<#zf!%(x1w_3M`9JgPNAfV$=@7?P1BdF zND@Y|exqai7Wh~}Q`7FyWlFT`PI!fPI(^~S?K(dve}Wa3M2F~k5q}ByRWzAH1+a4` z!?V~OYQh`YciF5ihg3*w(y+!Z8T(RFh?e-b2R7AjZ=38WfY;-@nxcf8>WW2nYE#vw z3mR8nTW3A7t5P1sY*_3~@SBW1k5v#lH?4S}yIT--LM^}<*^lfl0N-^?7a(MRn?jHzxKK1oZJXEO(4KYxP?nXAOQkI$@M@WK!Bj2 zC!>;Cj0dIa|J z)3A5Ixg_oat`5ZG`A-ec-i@gE_oh`9QYxv_?PsI%yZ(Ol)CPdRANO-ExdOPyf#-DGfdpJl!ipN?`H~y=UG{yBZ&)k+ zyRrHP@uXzOJ+SO=ZT)A)55KQgVSJaPj-2SXRk$L~tGE?-Zjz-ri+f;^GbtS(i0`t~ ztJ@im9(-)Zvxk%Lm%o9=w>7G8Mq%{LZ-(<)3g2(R_Z`nHIpNKC0K1HJ@qv=4T#jaU z3BEN1&-OV=a!Mj+-yA#AFJN=$i_s%aIc0635d*-?2)rMTL%Jhz-OG*WPf zuA~Xi5Hmg80p$I^b(fq;yz5y(Gh zg!XG&RNL&2sWoJG`CD`0t+uD`!e!OYr4e*CVa9?Ow9_FTypcl(2fFJ2gbNoz0J zk~O61Fh>N(`KZfow9CgIyWk&s*@3_Q%SNB%9K<$%z=7|Bz_YYm4?8amyBslxwa1aH zF7da@y{B=7J1cCamV;;6`Bp#fXmnoKf0@^G4w?NMzgn*DO3<=!74fuVCiePwVAjCQ z!8|q{?HoPmv~ee&*IfO$Ld+|zO$iCPFV?noU7_WDFy4Pv9fv+H@u+C5KCyS=+t9Up zHtF-FG3VZc68uuXPJ(Ci9^(BOHO3j-u|;c+tuXe~8u!u7Gv~dK6CDi9%^JyfsZ_^= zPaM0lFb_`+79k1Ii+oWtj)vm zCPLPbdk#-5h^TlN&lGtp?mj$ilzMfV|GvZHaSe9EpbWgr@WPrq*Ic*ea%~p+>%JLz z?HA)+37r>;FOjwOVR#mL-wgDk_G241;C_cMcIZs4M61wtJ(!bnTB{fg+R!*v?^T0m z^)4A$pmv3qI4T@FUmo(s!@Kw-Q^l6UQW@f^x`p2 zE5Uv6cs$w-tGMgXhKIE~hU4lz=5$<_E5SAVk|dmAvE9qiHv8cY!X1L+_2VOO#tMLy zs)Vj~26ZT>-o>LMXZ5cbIXez}kOa)ALxQS#MB%DI(?%Hg;_tfbpXK)Nt4)M8zVL)v zRZ&tKU*N4!;qA5F3jZ2kMGRs_2B+a2$deS>4*6}uH@niQ05a%tyui2u!IHGo&G4EP-(&jw4 zGB}nA*EsPOw`aB*7GL6?9S1j{#En|+yzr=gK23dD4|gBxz%LW`eVDV>lW#`_utK#x z@peD-PCPf;5qY^{u&Y_M?R+~e(D#6^88ctAYY%4t)%GuMjLW4iGf%=A>w_Q?e>>T&PQtpGL7|bdw1l ztl9Cf7D#L?LrC)IkAg9Q)Qs??KCccRogExKdPp#3^vE`UT`F+rcMoe2xnpe$L_Xs{>J*GuizeoZ)f-* z-$T9%&x1cYH*<9V;NsD-!4;zuu(uc+d`w>jDn>^IYe&0+^`lkWp7zGkTkteqSo?5) z-}Yu*uJ&wi*ZlZZ;T_r_--D{ci?df}bIgdib}J;d*0m!GXPziA&Q>bUUFwp&cx_JX ztSs+Ct-YLxr8S3-*n~7a&DwCKk6eDYgX;Lz-)0SdFx$JUImdGvzg^K5wzFVx*n{rC z`E$-4*az>{aHmVPf0T>VcQ~6PP~z6Zh@a$q%-t4#AQ{hLX(K85aqL5bqelBqYl-s* zd@;uz41e0aBZ#M!*-A{wN$rFe7u^-Ff%>)Gq}vkH?%lz8=>Xm>!8=ADd3Izi4S(vy zBICKK$3Joh?uu8*=i6M5e-mrhxIp+*jyLOk z+tqiF@8O>u?p@&+b1R;B#5bsn@jT*bQ$v5e^yYXq`s236gR#D*IChK*h6kKGjv>vA zz}@lM(9^m2?V=r?J>K27#Hy4Vf}TgUq#Iug3tZEf>I-`i&uTAq?|!#kfenGrv&$_|w=UjU{d)Xs3^vKEjn)Z#49lO^JbUivg_(ojV z4m>q^`muMycH|Fq?Kz6Pan&X<7CnCm%JcZg(*o-T-Vx_|RHY0EZhnW)H#&B!;9c*~ zuKS_w(Y0AmpGT^b1FjuS=;yESibAt{$C%jL-ckPOLmkH*$F)P=8IR#4suO>4P5;85^qD`{`lD|ITeY`1Dr;wWv1&c{ zk@l8x-j64_nzd%U!GbrS|GksHZk?g2l6IWCFqH4`{})-}4f^x;s*)Hy*WarpYdms8YEUJc56N^_B8Aq#cXwH3G}5@a^Iu-L``+&9&PTf6 z+1x$d8J$gsSxaZ{>#jQuy3c+-v<#_w89WhMSJ{l39)?mIzd8QJ7FD7~r3ba4Dlz8B zmYlJ#YQu(weL2VV%H6nTXY5LE=&ueW1eR#*;_uF$ZD&~0Y3{5|SU3Ss&f*N%(=)^UQ7dxYm=j3i35R3B~ zH7Yw8bHt?zv-SsQ6MEM#3UV-`{30vov$n1*fU5||$F?#OR??;mx}EMZ5MfU`uUuE=pV|-IjweUEm)%yoV)%y;A`2c zW9dbgic-$qYRs6Rwp;CbBdo|9o2%_!;&twB2|S-OR*yI8^aM3c^%?NRY;D9tu{UR{ zkcM$rpzWcpy6*scXT(CCf^1 z(x&(vqZ+1$zjW@oxVy5?oTLA}a~F!tM+;Ug5iiI+==hx567H z@g=vEur~0!qghVXthf8aS#f&1zltNDn^CxbuW%0UPj$3=kzNa~vb6^I#HaRP94_`a zA4W-=V-$Xc{dM)Ywnq)SJ?BU^p0VD}5~0>cg}v_F9jV^Pbw2D=Dq(TXm$TI#ZLhZ5 zts>RV9Q9gFJ95~)1HRkZ*#eyxUg~r-(>~k_>2!3pfaBrL3t#WlcC%bNbLO6Q=&@#< z)>A_(ICf1M_|5Fj3p)`mL4&A-EdI<7yluaP_OV8i2Mru3sZ$`0w$}jTevpI&foPZXh4` z4BC%+DrQuyoRx|)umsnBqK3NQOI4+_Ixob0a?N1%`g85eW}l0FJ0Vs1G@ggOop7MU zr=>#s-OG_GGrX>EcEDF~pW2bOGa**(f{yF* zy@3_+I|Hq1ah_9ajddqDQadkvbZK^A;DDW|g`z;58Z+yjoF&YM?=4pvb z0$<3vIs1^hKkks46gx@#YusOBcMiQ-wcs6@w9L-TA+wWIOW@|fn9|t+e5dEom3h^r z=S#;d?>PRQz~R7Cfql3_{+%#i@I*p}DuB(GFY#`{wDe)IZ=WkeSm%Wgc@1OKU)tU~ z7d880EWRu4l1J5^bIfiDgwI}?cP=*XT+@6_RsAhoJv-H*4t`0gBW^s;0sluhZpZh& z6#d|G=k1WE9(9h6e9fsMa(9QtwK*#yE54G`zs)t(mFgPr9R1hRiw3LFxRyQC*?v>} z<2jySU-iAECzn2vb0HWrkatH?UsyEwPv)R`Ruqy(Q-=V$$C#iAgKZ|8rY@px4mIVcNuG ztst&%R(@Q+oW$U^B?srbCbbN7j{WGgYuawFmbP2H7yELobLfDq8(P(!5zRNX4YRkn`uZ z@+->I`|p7n7-FLlqBrq5wIA+qyy#6JNN**csMM=zr!-%6z zp02$S*K1^C;>fJTtW3pcrLQ0IocD;@9~hwG2bJadzxPDWr8egbX+uc-gvg>tzgWzMmNglYTJ1V5t+-K}pvL~SZNY%&VpZaR+Ej%+M{pGzb8%Aa1-*}9 zfZ@zwE6eMinU!4G5i2Tm^iTkQE_HTExUMFX^qju*mJU!|oj9XzZN z^DXlJ;2iQCI;Qr6bLD}*A-$NR_lDs~N##snDiJsn9DwI*KWd8|fIB%Vt|D?)RN)t5 zzmS`l)2nSsg>RNC%^)wx-9_&QapyCQF+Xm5`&{QIpM27UPs~x|h57d7PhzlQD}`+m z4=S?BS*xO3J3FRlU_TR$acq3>SR9D=Sd_}x8EnmhgV&Ci6>%}_QtJmDnH|ngwB#P+ z)gn%#jMt>S)4C=jBkYnI+!WT~{Mn0dXViLPnXI0dmEQrxIV9sj9?1xwya+oxoF`RU zD^^SWBkgQQoOeUw4Q?M2AMISREpA0*Jt$sL)ST8gCC(ReMH!i}Q1;R}=H+N0##h;sm?JOJ)7|HB?M{d`4L3_dmF}>60N>#8bP2tzNk7 zoZP(pz>Jv%vt}30nLDp&{(^#Zo%9c_$ko3y7KCVstx|Kn(}&obxni6er;`SO6!%3rh65B4vtSzEWFTu6#H)RwELRl~yC_!t&}e{f~c6X`@UZ-*^N~>3OfswW$VQ$E#fl+eSZa_L!RaWjrrS{!*jzZc*7~W&VYgtEy2EX04c{7rtoi@~X-e{@LXll+H6t>noBP zM;TC5SDu``wxPnHe6uU)(aZcrr5mbhN>TTE+Ik9RMHkntMY+w)3@EHzv$nFVvSEY2 zsHUdMpIu*H-k@}mzp!$3WrNZ~{MqI)N9k&T@^b$I^aM3rWmi?Pj`WoEzcqEHZf;Fk zxuTUdb(I^-_3Bk-&+*SGuUl1)`o!O^-qh7RdTZpBHk4YX>VE$5-%wh$wp`g>f70^O z`tnKXqtHuMqt}+NSzFG|r&Dna641Mye-2v2?7X>p?_XY3-qlm}5dW3oW>5AP zPg+d z(-YGB5TZBj+|t#UYpP1uFE3qjdzYuHy#g3UcIcJu#=&fL;cBDf)vKGUYpTm{3C%ID zytbwuy&O}NKYKMNHFR|WISM_;UsP9#KE+_e7h?8}l?@eTb*1Z-k%At@P7Lu)$We34 z8`js<-L8zmt%@-XRb#v@s=mFtW_`8duwu`}04%MpFU9nSDLK!aq$xCKWm*EngoeqZ zxKXNOOs%eA)#}IM@hFRovK>8+(5F8h>GV z*($W(jM7@Ygbd2&AU4H}WUAz#BFpOi9CRTeSP3wME6e=ZD>=(1Eh^3(WrpOGudKm{ zGJ?j(23zlhdlT-uxM1r?aQOp*t?$D92}AF;@)ZsEU3;wM2cE#5 z2X6f5f~_ZT)E963Vc33rpVo#A!Pc`PJnBYUhq{*_!``LyJkmM_zjkM^wcl5Qt&wn) z4+_yAi*V|n#D1yO)Xzg$rm24lVdvrE!G)&oQz2>8n&E6)e?1le%vIgmbvag>9FD4m z4H%B4b*i%7kA=5vgSw%-t{(kOO-xBmNkg0luIBY>{#p!=x$}zs`PHSEG|O~TLn$o)q79)bLjS%1S277s`L=R@=}P5q^)kFO#Q>#rDLg{J-` zgpGxNsi}7a_53u#4#Tm{Hko=%`#T8xKHQ>4?C(VQ??UoCWTv$pdA?^jIyiwNs5^|#;{gxg2{YOobQt)YJ~#aEWER{`vvs<1e!TyB}FjDD8{ zy>(wU%8&&=?dL~ZV*APbPB*LGtAyU$A$sqH@OJ3Z2=>cyBCmt+?>E!A91`b*8Har{ z1Af*^JC3JK{X!g2toF(u0u|D;`>Vb}v7M49^`?Dt4JL&zS# z$<_-kcbU)|b#r&Ui6OiVx>mWZ@wgvhEoK_|A^OKneK{UaA?&27pM+x`Trpe~+$Ok# zaA)9{&!_L{jV=7Diptw=uUcJQQ@f_FzG3aU^^F@g+S~jw<8U!=cxrlC^6=CN#vea@ zQ@j2s?z}N)EiRm+aGAibvKJN4T(%$|3-W^eJd19am&;Y$z=An*78TowHU7q&?A+PQ z^5-(ph%;|N!A<#j%ZleMo0DBIw-|0&VL^6IL1960LHx7FQiigLupxK+k;-t?z@ z`(}Lc^1gLj`af5$w81JK;cu0^d3tO9j|y8}DSv!V*%$WWiA2v~wO$p}1zVTERl;q6 z+X}Z6?kTwE;rck|sD&z^igDdM75~<&hiIbahJ{<;;ko5#m8Z(pIy|Jd0>=VXtr~Ew zQ!7=e9x_8MLTH};TP{V=zXNHdsWLSQf7A6;lhp*3iz~5{aU++xq$1q%rNWnvbS9~Z zaOrT9b^m0%j4DS?EsDr`1awNk5k=PFn7_y99>2Kvl;gU!v2#s-vR6L zaX!l{-xG8zXj2aSv@>0ojfXEAIt)#Qt&!Zc6TV*{qws(x?&>T+9 z<@C9torP$rGPO!?uW097v{<#OQGT>}xYKBf@w^{|7I*1&t9!N3I%TL*K1Y^9(ybGG zxT@g56&OcX4-RwRB7eXkMhS zUN%Y%uUEQH4NnsRjDIy60f<;t|YwET*dt5$7US+T03A}y_aW%&(HH?Cj5 zW#!5(Thj9zSAKo#&h5bA%A85%5^aBv#uL=Oa>YPcXdPRCcLX|_^h+hHdL|d-m1IL+`e8h7~xmiXPT?R zVn(5Dd$7+`2SZ-0Jq+$aBX?%ex#eK_QUAAK|( zk2Lb*3U@i0a1j`8dv;ZACC(gI;7Zmi|2ku1>>ob?JLBBs|~<+ zJ3-HQ-ky^h-gMUgCOsT%HP!+h(MK@_saLw>>5${Pch|>|zlg+)_?bP*4H@0=CR^xEw3TAQKD(_^s_N$ zG3@ucW(rfUhpT~ODW_e>a_oTH4(Gi{t?PO{55JGmebm>Bs=}wDl)nL9QA!jReFEil zy$x}6FD7CpWES@cgqX$cg|WusS|jlgME`JcJE5#i^;mIbaiapT#Ex$raM(a+Er++L8kp!m~P4BM@L30?g0)7_k`2zebXD&eSlKh@(paUWLPD_RoF&Ck`Au08HnZ@rGq!@c}hm>5E=F~VT=wj zVb2oA>d-H&7vm)B5F7RgAw`Gyux*5KI{1~>Lz)-)8??Gr6POMZ-AeKi%7*EEu;5Lg#8ZQ-@-+0MBl>0Xew%p zJ6Vgcp&ub8y&j1X;OjEu&#z*y{00OX*MVC0L5* z7ZEf;Y2%_9lz?c1;5bpF>N8?7C^7c1fGJ!#FYvSpWDSQq!z>PQY(<6S#Q&!!==L71AY#>zN=VoZVrU* zGpWZFb}s_Gu4ami`NmDZ*Y%kG_Zj>Rdjfxa;at$2LwwY?4AffYAuu8lc5CAY+y{wP z6gh}AA?^U^FYx{zE^-gU()5b0!d7@DTvQqExN14RFF@};bda@hQT26LwQ_s4Ak6DS zDZ;qP;bot+_RkfO z{{?mH`XVf{s+ImQ*F*Z>uh3E-_Y6+IUZa*-T1!4G59cKh%zCVA>?VDrNzspkim=ho zf{XYHYhbcoqi4a#;Ue`Ko$q5iBw)iH1sAE;=z?Bs%~!$SVYptS3w_M#%h$%&TtovKAfri_T#ie~7x$-QGXIgh4pNwNlF!x{dyY*fkHWyYn>%F%l#5zaRT4u6E z^lq6P!#4QCmAVDaR|#viNxc|$ z5d0*=^r1VY597QJJ`HCLRHL-5c4=$iWj`2eUYGcHRLW;oTKmMtJOiA%@czZ9m7;a#PtzQcD!qpGkR*ow1_DY1?c@QQ-W#UouW;Tvt%RV7&j~S zv79&!Y967ml;6QdLtr?Vat&nhBv;t<^z29tqK{!1%10&kFjj4n}a^Q zhlwB}{Yt$^goRERQ7E}GCWUBg-1M5Pn;K)6{{Ln!uu=_alaO-t*eX5P>*JXI=O-q! z5sY3EW|Y%wS*WKS-o5LDm>JQn`Z;@c3os_;s2=Q&?x8)dRxvF5y{sGG-?g7zrK`6& zV1M@l8TR+sPp+!{M*Vv%!^8fr&E9U^n`?2m0K?|+9_qZOF@QQ(F&+#ljq5YkX}8{; zdJ5}W#TQ-MTa&9e$GN?rySgvvZmUopqp$sE%4QVB*F#ZQJiFZty$E~KiWHO9e{vze zN`=9^)kCXvZz(igPt9~yh5^_6f3B)StM#8Z$L!VJJ9D?$yT@6s+mJN7fZep=qG==S z8QEAs(06|Y=Tryr*P(qJ9i`0IK5^!yjpNGz3x_o_A+8lb#;O%77?8|| zoRfJ2dYo}fdR%4oimJ6`<>qbaWvk0quc)d^!4(7C9&0GWizF!(IAzCbs42IDHw#PV_&#Xtf(Yt!TV+=$zKQXhxx4(F^s=|Je58 z`!b(as!Wz@YEPv)^*_9+@S4e|6^ge3^+NS$FN{tZjvnZ@y5LB3T)d8lzn{(M!pSwC zRsF{Y)>RqPU8A(Tu@-OrR5mPIj`^YH${I4vtH+?_l`wtK@~)sWN^?6{TfN~!s)kCurExWjZ%u7iH_`eH^;b1PTy+`WuIfIDfryVoFy zhPq=wo=msfGtM2QYbWSIPS0?624dmDk0Vq6<96cULdMtXM_i$BxfXCs4>b=3f&kKIM&eh83}YJL-#s&m>$Q{H8k}+GTp2oWUQ0lP_**6 z&OOkGXQ=2oG< zjaO$XYTOy4_3hjDT2#BU0t3q#>vzYo7E|2)jYEWIk~>(J9NZuKqN zJ9_g(erA)t*nN&mN#Xzh<`t6-*3-7nFiSQVZ|6GG*kI9Yuxr?0x3j_eXS(B!mf~EX zHJ~cix(US=c$$?~X)nKF>6?qFYNtcUY3?aVBE=WU$`@xK(pUBgF5H za<26|@JfKzAM1=$ef+<8AojfTy|Q6>SJ^q44U4PmoCwjd%h|Al{rakek;pX*bsOdx zk5e0LminP$qx`ttnraM0)T?JO!Zc^HKKO3I@S=*xIWW^w?#CgTs8gR5L*Jj`PGri? zax*7B+=T3Ud8*xh#D@XBFxEfa+k2pU@NiwHH+#}Go?G3$(UZoGa$l3+9ykP3I;5#G zf!=y&8hE{XtRFhGBZ(%Vh*rly??A`+-#kP2c=B*%zu+x%XA=5F-~Q+p{oOvaFJ?>4 z9qzuL+ZgIJ4xSm#ZVE`b9Blz?!JZ; zi=#Wv_Vz=+xyBvw4fjBVaf<)6V-%X)<9ElVp%oK`4)P53%!2;9E`7`!%*Bl4oA+`anR*DtxuFZuZeQ^;cSZKSwA!C&yhRWTwGHz`1@iJ zT|)zgx%GiFD8<+{{mcIYZTF9DlKA|EGg;%Tf|g+Ou@U;|%bKx<#(8c-1!{iJ7 z_i#^&duWO~p6bphoUlS08(=K~&86>xJ@NY4qt;tL?c#jLlS$3rl_KU@%&u|$c*g4X zI~y~NCi#lqB>xX>WI4dftN)QR4?Yj#OxM>nYf;1K@{M$d^ITzqxiZAN!%}rGt`hXk z)VGqjnCo-i=J#;<)5q9i{USgFE*s$@0~Qk_l$%nV_4N?yL=O1*O{#h_9e8n8OV*nS z=j5N!GGojx|83Xf{*lXYJxqXjG{rd>m*lt=jqwk4JM0UXxI>8rk1w3zI>x5x$S8&9 zn)HwLx=wbqceCS5T90~3(B!3T+`m?TPp=4aUL5|9^ctaOy>^lFckXqA^)z#KbmV5| zeXNBmb^QqA1IE_lx!KYE>|Ao9W4Cy&jKlZ4$`HqT0JU439`d06cIaDAUH(V*{M(HXxv(V(seXJqb$dR#aT4Z>j2&SNoG! z`IF~O@F%aVu1V%iE_{KA!qblB%NnlE1z54=9H+l!Y2VH&;}?4U)7PT)J969_+V{_r zt4phELT;())i7?|q~z7(s%uu(e(GJhsv5jCT2_NE@G4i!mPtz`V$ZB}P5lqwi}|PR zH*Xt;-X0W{q%Z#U#&^0&lWI3elX$}Y8CqeU&wqBsG|mgJO8Um6KJJS9F?Nfst>*XZ zl$ZJcubS>EbUky4?6PM?1-{m&yWtAoUx)7i>{6l0Mi6~?;itaqriN-*y=*;S0m5C2 zWwl+s1K$z1POYw~#&^!)y(G%5ZbZrInli?#KnLpz=hw=u10jcH_@=%3hDqs|2sYq5 z_srBeFIb6G*@;xv^GRx?wt;$Ar7?K}W}LWd^G_#w6jO zVp%Z!#HswV3O(6j#{KB~&91b9L_xJn*DeQ}cOf*1g4cHpR^5034$6U}SLv)XWcGQ)AK|b;HB1 z8y|LA;Y!^D?*u)jP^b7dxCS`n$-`Z64z)Ixner|;{Rpx?dIe`9{3atU9!lU`YTfzh zz4Wni!&GfQ&rk#qn3ASst_b)Z68r%i8j}Y`M1|VwJyhFS=mm7WVjlSZms2&Q>3K96 z;XL5Ma)#&c-3*VBcQImGpROLF)L#*H2@W>!P@<=CGY_=Gp=x7ztkr|(^S~4}bu$n5 z8=*WHdE9JezyuMhV(_PruCz4OYF$o;r%t!z&E&se4_Q>A|3*8S9E?| z-B6k@mAVaKqx??silJirqC@e(vvQ}ZbM2*v;{f42jMlx2c%bvBwfNqV_`UQ`fC~fp z*Q&(5gzJSu4*Yp=NQMWREe4$f$PiYS@exJzBg7~oeTD%=wgI)sGO^15FYW<>hibY7 znTUYjs?arPfsrjol>RZx&O3)KaEQUf6I}v$><{Z)RD3tT zsbv&?0S~NEAAb9|P9-*=-H9x1L|4$zn7!<;i{Ui2kcXvk9@N%4=+l2IoY&y=_`Rf+ zLWBMWU87D-AHX=Ib#Ul9JZ#dvH}G%|oCh-YK>9bs={;~AdIVvM9#E(J3}8Jy1c!8Z zVC`Tw;-SR|<$)E2P##!cC<6ZK9|!-+iUQI!v>^|MS&Mj}GX_TDUixW?S05?J;oxiu=j!5o;W2#V+m8tU51fb{Nx-n|US=pGokmjRjG17q}F zHw%Fxn_}Q%I8+=DbZi2)JN@istZ*>0Cshj#`q{N2RHK$YtX3s5n%xELy@63^tGL#& ztJs*TS$dWZ3y)n)fL%O*T`YiI97JD$s75j7+r?NDQVgBVV$>pn2X#%0>1Qz_^`ayxj?oNO3wXFj_b%jtCOTF9U2t=hOX5D*2KWp;A+%0F%W2f7>1vp z^+vb>4q5TQ8AwcV#0t1>Q(O}QIBqaMSi{Gf;yCAg0nVP{NStvj4f^-Nd0~Jt#gSNZ zKCCwjj3DBZa4?DoqfT$I>Qv54T)wQzvyOmsM#`Y$WyovCKJXp<$)46*~AB7SFdW*5@N(u{C4a78`RK zEjG;j2N!6=Ol!I${ga*fQzisa)}G?tzhe8Ni+>G-LKM zU>%&urU__t%tbuVS@R(MM#r?~LDDXvLH`qQ5o(h?587SsKb!}xVq+q+imT6qRx$N? zkmY4*^m))KmOc+!#X~o7C37jq@Trz)Qe%wgLXN3mrGE^4voK>pv-CI%c_@U_PiE+uYk+qzJ+uds$i4J% z4*@VXu*(6cFuSQ1vZ?40YPFi4=q7|21}?A-?AkVZAT>VCIIxR)I|uBgpS2Rk6AT6r zSbR$XUgKkx2oFJ2!(l4rp$?9nv}iAV>kI)8pX(A>30P|b@5LG^%$F(5*HhS_r!b!f zD}`<+i+W;Ap-5L;z=LJO0+GT3nSy=&$AMboyl>nQ&d}2+;$gAwUC6_&aC*m~$1rpu zvuJ>m3y4`R8dI!`=JQaYdlxXm-z|$Pj28l>HB1Oi$^$JzliszLo~dx*7+QYp2f1*P z={VWqgu@ET3sIX1HmqM{S+6H)nynuK(76o`yA_shli`&bSNg*dglgbnwC>ecZBD5c;^=}4hcgRpzRGM#T{y1>rV*m^Ki=7t_*XFA_1J3{S(&(DLN zk?8{X^pAc5qleH?`=XdkHMOrC=K)EGVJ{EV@Ib?zO7G zih;_L-8@{5tCy_>n7tCCVm4exxVc7b-G6K_>4&zD<&>qS(-YfG&MbAVtV@Wva%@| z<0ogVC|#a5W%7jdw3Xv0uS}aTetGGtDo!w*TWwQ&@)>d`ieP?%JmN(u1N$kz!PccK zi`iTCkE!prSU)<^FXYFGjw3%LXDyuk+g(cxs?b z`}j?C_|l&)j?p=;i+k%lw2S-boYcj5-5#HDUEEJ+40JPne0>!@=xV3&HolQrauvD#0g?JB0X&9v}{VCImnY`Nawd)b@-d8|lp1bDIF zF_eq+Qo%5{RX@PGiH1l2OZ%99hLGom$Z3CuX2+*~mC!E=(dVV;Lqh*naHilDA^J@3 zh>+jjgZx7wZ-6}9Y1_m4@HlMd9&4~*-jA3inDz0H;LYGOg13UL59{Y4K#Rza>8A^R z%u>dY`Yi80A*X%E1T+4-g4tfmW!t}pY<{fv87t&ZT0$HtXZr+&oc5k{+v(};E9A82 zlwkIsOM+>iU&Q|=*!o!cc|G0J4+{MsS;{zC`ujulBL(wY*sYJHpXu$M{t=;n+ET{R z(ogbr*RK-sJ3{pT0&b4B_4z77h8SP$zpf}-&hc6%Sf5cue2&*b!L08rVGsMmOu<~W zcL;rc`*@MapY0JT@?-y~63qS^DdMyL7732(p+4Kid_en-3r+x^6&xQDpZb3j^0Xf0 z)qU*x;x|%SAFF**``U8e2g?)8{#qrN`5hGLC4e)8oc(jA;I&B48?O9Z$u#tcM|;RW zzMq{x)2|ir`2@Z7p}mx62>nALa*ntCLVg1BElGCzKL;NX{7Z0$;5WhXqilUX@48s< zAHkchvE}cBHw!txdAC(CzgYXcU=No3q|tW#C~&dhe&Bd`^Vax z3a%178GKCGdmZ?=kmrFTlkNDk!Ak`%0IL*Rz64w%xCDGa@G9_!f~&zr%1fLfCui%U{TmReOErMSJ z2L+!1$EVx+KL>v(?EM9}Ldeg6j|l!P*fr6P{|9if;P=7%1oOMe&kODZFP&t^_h8HD zoosUyxL9yM@JYb~!AY^UKEI^hpJ8(nc$469j4yZ+xZf08pI_=;BADO5c~J0d>QA-x z7l0cCF9DwvTml|8&DLK9t`PRF1~&2~~9@XRcm`7;V91^*13k!{QQs|g1Lp8-f7b0oM*a9{8V!2`fq`L=!n_>4$z zBsg_|E$8~uF6QTC$WIA*8hFbMoG#X=f8+T{p2!d1C8vsnK7N`}?GSt|_?Y0qU?tY`;b6buvET~9Y2Yn_r-Flmv%wvL zXM!`tdOwfxg}sZwi-mkCxK%KJqTsCH+ZbQ$7uJAN#rod}ZWMeM_@vNJa|G(un&AjFn-ohjTP(vwcrN9gTXHe z9uDp&*88#GGQs@r{}{2pOaUJh@+|O0!85>3lkD*}7aS}01B<{Vf|r8N3SI$D73=?P z;AX*Vz^4T_g8gFsPrz_FBF5icOi$Q<4&0+9CSi{g7`H@@?RLVt?=uxJ2*{aJ<+L@OL3L3EmGrA^2%ZV>!4aI4@`;7GCFzYbn3xE*{z@H=3ySntn+iv<4} zyif2&@Fl^YfP=zbei?m{*dO@7hXnTm?-2Wg{^0Y12Z9U5ejx#zF4p^z;7x*)!6yW# zf!`H81)M6@@9E%U<a3h`Y>c4)`PVpF-3-f1`LgxE$-vOq1^gyTK(U?*%_2^bdpA zU_GzzJB7Eq)!m7JSgkFI=e&LO&7wGWx5PUp9CS)+;N&+rf_rehmC3`lqG;HSl=M z_a$cf-vy6C|Fh)n;MX9x(u;~v>OsMY;PIHBEPZ}A`BUhRR(gxTOTbortq0#J?0pRU z9@b+^|KGqLVg9hvYX|Q{f3oVs9f|UxKfP<(I~M#NPd>`17{~270{%ZBdaNP3w2>rFhOn(G;3HDo7edmF{3b_@(6#M|{XN#%d z0DcDJ>yXJ0fUiS)TIoLtzKr?CivNA^vzSk;^nU}M54Q5>k0ig0_KL-MCfhF(JUkDN zB$(-s1m6j^%2xnhgZ3yh^4B!O7TPTJpZQKwgghp~cg|Z(_V#`PG5%#(r^!Y410|A3=YM$$tR%2V45Ram(&^ z*e_cBYby8|$Sqz0j)UCFZyR_m?6KlM2mXQJ--1h_zuBs z>x-3N6L=@sO78%8tBC&-aHWXhEpvnkn}9x(xmS*cuN*a06wnn4hMBM`1oHG1I#l zoB(~RJvV^kFyC!4<=ZI7__E{&z;n=^n=E_4uVFkknfxYr<`YKxT%X?sPsDh(%A?_y z?o<7Ptx2YS9N7Bs{s6CctAb#w5vD0#KN>sF<`n3kEVh}~Q(jtRGq0y?zQN{rB(yl+ zX5KG4CAb0HKEsyt{*hy*%{+h46U_7DNMT<(?Aa`s=cC63PlfzL!94$)Fw0Iq3-TR; z6JXD;g?&8V`n8Y;pg(%H9iQhnGX)nyzD_XDCw?iIuMtfuwBs)ZuN2Jv{4v3|Lf$TT z1ng4*J3jaODRXQtgZ?JL-2WdJd>iDi3+DNNnrp|eg}hL-H_rzS3SI~Kup(QZ=Lea> z9-bfEB;=c*|6Rd6KlnuOJ(SP4)8qL<_yU`GzK|(+3-mV%9)tXsi~M;$@oORf67RNHmWa>&{V~CtA^)D>UC95X zXN>ud9N053C<5G{}RYgF0sqc`n@Qa<@4TT%Xz)HN-)c-gg(m~ zDVX-R3ub$q7fkyv38wv#BK@0TZxrOGpvV4L4KBs~Y*ZV_rPha(hxdmwmfH5ReEyql zX8E!NvwTH@S-uv*EMJSThvhpUnCs6mq0jOr31)fI1+%<)f?3{T!7T4nNWTesEbl|$ z=ON!=GG9x!KGyh)yu~gb?=K!KwRs=n7cIB>NpP#+gW$0%Z24hunP85;ERp^($WI9Q zcfrHT?D#J+zTh8&x0Ty+)@PAuAJ*T+TWvYV$F0k3=K8%yFvrX5g1MewQ)26LJzpT0 z>-iSJT+d$;%=O$W>MH^E^b_Uf`h7^qxqhD#%=LPys86oXCj@hSP8IdV^|(SX*W1&A zxxS`~`U|4|Y6WwBZ5H*({*@=}<@~TmF#Fp#1an^hu3+}Rp9$U!d9`SdUn2j=uLWDP z5zh7H9r6MD`ppHf^|780IV9}kc*$C6w-4*LTCih)729}TiT$Ha$Ql0$!R#+TTxF-n z{<5jUX7-m`D{W?f*&~?!Wxrtdmji;?Uk(c9cz#{v&-(sQFzfRZ!K}}8VIS-BHo?(7 zl=lxJKbBYBX4}v5{qm;Xb-EPrO5Ezd^$V!<=OWrF8`8w7Jb*({jr$#%h9PxcGudU8ZC z*ON?P57(2Igq-V1M!jtx*OLQ+xt^R5yb$)C7R>d1u}Gim`#!;J-w#E5aD1H+a<+H7 zV7B)~!EEoD!d|xTV!=EgtPsrhju-K{el!X>*N-iNxqj>r%=P1dV6Gp>1atj3A(-n& zp{Nh8AIAlA{WvLjBkao)_Hn!~70mH{P%!)7X~9cS|KGrRzVrm{w?rE z$Z?rgmj}U>LjEW479sbbLv9dqKX|L)Dd4ftx9rIWKMLmg8_RPe*!o!e!K4Oz{Co`I z(zQ18`Mwi^`TShQI$O^DklrC7=k>wb4Yob(f6aoK zUr^|?{~Xw8>$CsF-(fTR&$~jO*>4ie{gD#sv;7+d)BYnupY69@$k~3aB7e5uX`#>g z^1R?PFyJ+CKZLV=+=Fm^81bu2P6odRIp-_N=YStUeq&K?auwM6u>DvcTQ=F{rM+WC z`DstHkbfTW9|R9ZxZXcP(&K*VlF(=RnRnXhv;GbVIrCFOzQ2d`4hT8ZJ0qCwe9i}_1iu|(&w20#Gd|l}L&F~t>5*GQ_+$v5 z4&k#Q+#%S7^!^sYmqPdtA$&fBe;vZ@A^b`Rp9JOuUy@3ZxXgX8bFnf-sCu$SwvOW4EuNEdRhw@Zbb>+KoA+#g>O z%=uOc`?)`k5zPJYLBZVr<_Y_`U)?X5_Yd9`%<(!?*vIAOFz16Nq0f0g_<36&x7XAq!L09LLZ9{B zD$+}UJX6S7{>_4UzPMd5&)3h3^jTi7NN;WrqL&{|M@b`)AhAa`0CL zSBIpx1bW*$ zA?Nd$C4wCopN)d~yyZ5*e4cTi;8#(eV}f4?2L-_B@_$zpr;LG4l z!5nX=M0s385MX||p0Ix|7W%y)Un+QH5Bg_?d_3gu3ZB`6{p^}% z$hn@M5={F~3$E=Ueut1Ve!phh9>$Lqd`}PYQ-yq65AsGKXZjeUL8FS@YF7{XH#}(P z|3nY+EkgcG5AuCN{;eM5$Amo8o)>%2KmU+z5A|aow)rPL=<_#QjPhyf^&aHuLjL<6 zp{-nVxc}~e7*dS*gVXy3-mu$di?zrBUDq# ze#6JVmYl!2VuWhyPd(Vn-&e8Zoju6;dn%TEn&0s8ua!Q3IL-*w)GWW@<6le8-&Zk0 zjq)Vzuz88!knpdi&+nKpLN&F*Z}|AvlJhrKj8ILj_8UI_wdDLw6(iKhZ`e+od;1Lu z|62O|Z51O_Q+N0cAOBLmIfU6B8A6|&6~aqHxHg0vLwI`#9|++?A$&4~+e7%>5O(dd z%cqZz5cY>~W(XIB@Zu0&8p5?9+!(@}Lby4EcZBef5IzyY!4N(l!fJQ-@_R#gSO{l? z@RAU|Eriiky5>v0KYXKme(OW@`Mek6;A8YpKA)|l$$Y*|zLi4Z;&!e>MH zd0tcn?rbe2=5Q!gCTq@ginO<=@4!Y;dev$VhHzpvU~l+hj3B|XM}KG z2p5NNSqN8!@TL&n7Q!tdyg!5whVbzaJ{iKNLikJwpATU^&miY7KF=UIHiUV9O3HIX zcvA@TewWne{V~bBKPH*?n|61O@Wfzy-cw z>q|I~CLaQC@hLwpGpOF!F?}D*^`GbYy&?Y<_&r#r5S$$IR9Sb{tM-W;5X}S`)&cR5%!mX4@N3K%+>9? z2mJnx#{C?|e;Ca9DpHqg>InGq661Oc<)^{7SK&Jo&HUd5YXRf^2g)yjpGSH4{sGH_ zpB7hKFJjE}MuDGyz%JipFxOYBeinoK*BJK?nBFRI=gMH~Bs0G|z_%J4 zXYzjV!?1Uj$wwH!*0_Jo^j-yXy|v2w9{3s9n`G*L49>qL*m{%6o?*B?g!Z-CV*q%~ zBIErMrk4bM5a-ucf1C&oL;gBLJ~tWftKj_SpqXAiKA7tP`#0O86wLY3 zvTrN+{Vl=PMyFA~4}n*&wfpa0Fz07W{{(nSKf69ogPR=6??wOO_<9rk)k?d)-UD;} zwBoym<9;0K$Li1VVD9&<@~4Bf+k&kN%<|-dd*k{C`xDEv5d0$Ue`cAy3e5G=ioXr) z#`Rs^52yZi@FVx*eIxdN@Ylc-7Y18fO#T+Q9PQ^f^|F$Y3-d>znSV9-|5SD^P?lEJ zo&TC{Uh?WTf;a+#!bk+#uCJ?K29mDss_w4P-85C*7(nLZKEA5D>HFe7>Y`|Pv7@7!Cr3UXQ8 zy8rL&^V(;>&OYbfUK7;sf*Wtn<@Yx5pI?>tcOUq(C)2*K|0r1e(KfKG_mg0;*LQmS zBe2AWwjRF%-^=;h#lHUO??yha*73=BN5Ox!JJf-wl@d!{p=J;Fp5>&w^$DWcKA};ODW2#{c8a zCx1nKo8JZC$p4<>+GuqYgC!m?{$CFMJo0*s@4pCsWL>_#bKu8=_4o+*8T5I(r~edK z;%RH|9`Iwqc)tz)0{UqBdL;?@1@awV^!0y0`2zv{FTf`T?Vb0V=+hr99vOvR_;(rj zcWR4AFy)Tl*MPU506)Q^e|7Nv_>bd!`=0}!gnUSSS>T zVsD&1q?6~s(|dD${6}z?{p1?jTNj-k{VVwCdS3sO_i+A*KicE_I}7|5fqc9NJOVw_ zbmJKT-;2JMJ)Qwy$#~`k(|-&6z zYr&_oo@e{=I`}chYxQTr_g|6Q_uIkO2l99?_)Prqg}(kG_z?3qe!T#e{iKXv^m`42 z_aeXMzt)1s1Al%2c-tE6oo{apyg)wne+q`b6JW^?actdX9M@RLRT{-WUD2LFiqSNQS|fF-|W_NoE?e9+&YfJ6ED zELieaYy5b>3Z4$)uZO_z3iRs<@DS^{NBUzup8@}p`06O%|4+bA60dIc_ypqTZ=ruD zdwdr7Ui{0)J$@e;wz$_?@{8l(lUP6F&wX21FXTn|Df6vUehT_)cmez^@)m^@qv zmVBl0`+Z={)e(6YdDsn>{p4D($ip=FIpkfYEbT3TCI4gPw}K_VXYI*vZ$HO+jC%U7 zfqy=i`@1K=Ut)bMe))UwLwj@meGweW_bcF61N;Afz?WeE%)cHx#CcyZzjMIP)82mH z|2FUg#IxHy9tZOs{@miA@I$_ z3tE3oQ~*nU#>O`bmVArhJHe7~vi=?gA5Z*Z^0)}jHr)Tck||JY&d z*(bfcVlUQ$pKR&(yQTlL!0SGe`-^Sh^I7s^X@6bx8_^DMnfAZu@S12pc++&gA1s2A zjYW}{b^KQaU-uii|D6Lr8rZ*2fbT~h&h_K}Q}DCc3&VGTB|mBF`vCa1H21Ghf}^8y zfAP0q$(Pys{|qeobd%RLn;7rvJU%!Dd=dON`91@D(p8H`ZuR4NFZdGdx%t=M1xtT7 z@<;faf+hcES8tzy%Vpz z`a*rBUmKkP{x5+&+zj4_eD9&Y^uG&y2lo7bGhkW&1K^(!znd3&D*uR;6L`Zt0vLf&+X0WS0^f$eGXBkh_g$9zr_X>N z8PCh_1K&%zt;gfwZSWr(;>P()DDrE8ew+>#fBi|J zN59SiKN#5OOTdrgufE~in*f)Q*X>|u&%s03t8;z%0{HZRe;)%sdU>9&xeF}kQO3{D zuz!&K$8Oi&s_2`P-$}Wx=ih<9^ryMM{XSUELf|%Pmrld`LI`i{SENj*5>;5DEKM#=gra|`m+drp7vP=*Z)6&pTnM= z?eQ761wVFk z9?yIfd;<3V{7A?5N${E2$F&}R11$GbOy2$${22bv{Sl0 ziS7oU5!lnGz+ryle}Luwg^mB1ovbJNYU}xKu-xCV{)WMFAIJ1{Klm=@Z@2}P`$0zk z2jHjhw;y0UqF)ol2iM^LANKer%3r=Am$y5>dxQ0QGlKmv{si(!@>dT?`(W*FP4o!Z z?q^AS_BRyRzb7O3uPMKn`Pukh1TO~m{-41w-IVL=X`@l}{@c7hpsyEzM}zviz?*K& z`BMTvio9%Rd@{d30PkZy*LwUZ@bT!^gC73{_+H}CJ3M|2{B$$7w|@^lllDcQrM;hn zSJB>HkB=Rrf9&0jV98&f5B@CY9kA8W?*aeg!JOYk@EO7Seh~aN^ye;5|Ht4H@dvtI zYobqrzrg${cJ}Jez;Nt{yi2_JMX=nTF@8P{KJ&VK{ayx7UYpyiV;Gp6->5vUicSSD z+>+bF^T79j-T2}6CO*8x_)eogng4F^m1}c-yc#@4JPBJ|c~{EE^8CUl1hXGI=*zzh zmit6D-tU6{g!+U9uKtg~a$m~IR}*L~1orY|FlFA}pAwx4u3)dk-^=*k34RKGpXKom z@L}R1llMd5XV9m!eR)(XRO;(X{HDkHA$c2?x6SglH9EJmeuKQl^0ralHp$zTsL(8T zlV+jSPFqwhSK7@&r!?b0vCvL8#@e8(Hxv~sP4}eOotknqv@zrwrn8Q8)>*%eFTuBJ zg^pI-6t&Vqna^{rN+;!SskPATH25%`c9KG~nUos!smgS>Rp?Y2GJJX7>=vt)QgS$5 z;6b}GT~9i#LcLum2~9FnXwUGxBCW_|836A(L}t#AHb(8VUQSwRvr%tLuQP>uxtb=- ztaLajl*_HO-OeAoE?bp)r%;5bP-}GSGJIdDJy&QZ{e#f1H%7aYyttKix~+OLTc~zv zMLUUHvFn#A4s<(}s&pPsNmA ztR$P2WV4cNRucB2GGhf>lyHj@Zc)N5zDxr6qw<_UQCETinTa{#+l5A5FvdzNhZA!Auktm~L<$bKY zkCpea@;+AH$IAOyc^?nCwqoUdth`rl$G%BJN~pY#mG`moK33kx%KKP(A1m);t){$> zmG`moJ|1=xh?V!T@;+AH$IAOydG9-Q6V)!YN9|Di)9$n%?M8diPP7m0LVM5-w0&J# zT~l3BT~S@o&8}ZvPF+o1OkGP|N=LHQt&*;zE~BoZE~2iXE}^cVE+Ff-f9&ANC& z>$+*Zl(kz7A0`fU8djr3U@)Eb&LMmTOJv4LwQ^mzQm%9sl4he(^;0VhYGuV5#eYMM zsWh8H&xop(S|#ImsamMi68G)`o?@X|2uKcR~nvHTtYE;gi1WsYSgkm2uV9cXe!%-pY{){r^+CiFDb>6LG?Aiq}&GGN7qCV zDzhnqS*<1wiMC&t05M&7eWGTol8dr4=}t5RfYH<0G_ z+Gsbrt$g&A`c$>Y?o`wAblOUan5=TvQP0DqR+vxpiOsH0OUvvT+PSU_F6~$CTqTyF zmu_`mkz@wo^+_|$UxBn|J(r`y5UxZwSLw`@TZOr0SYpL}I?>lzM%DLxS>9w7Y7Fmn zm@|kr<4&zmFJO-m|6Wb$e;z8xlCmEDEGZQ=H17Dqa-owgW$;M1e|@w|wb<9l3kD1> zC>%7ppjbX;U8c3nQwe8rZHcg_$!@sN%G`q$dG(lPkGl*-y<6)qs;8Z~Mk}-B^>n_I zv^#0j#?WY{x}Q&aHMakGf4d|fX2?b?)KX%Ob_Y|{!C#kBETJ{M0?8n=yb}2vXj#$- zEA{Ns3H}JArL&F0_ydh++B21m*-K0MUE2VPC4R_;up+Hw zK`^j=p7V`V@iocl_*m!P(qB-yv(U^$VL-p4ly6{oj?BH)3$>Nn>$PPojM=$pH9tYn zGhl>DC<+TBbK5w8(#5lQn);GZ<#mW9gtnpf2euMjH<~VuR;s3jR)Pzi&V1$I(ta%s zsAekDGf8KLn0lsB%`|0Mf|Xio)Y7EfkQ`<{_H3*(dFcvmWC6h-OArjo?Lbx-bWiyT z{nZK>y;d@p!i9k2*{FNUS869MS1__G@v+uNvO;Sz(dAY(D_g0)Q}O|e;MGtAx#i3I z+(^K8-@tPG8X$)4B&{{sKouM1EVh*fL*3{V_nxw4L_Daygi%3R#;kCzLAm>wZ#X*@ zB)B*B3?8}2Q?NA_rhoflU@C zkP@`i!uV)%;Lwh}<2#c*Ba?f=#|OtI59&ibTEOcU%H&r9L^dQqFuGCgeX!7BQ?D8R zCfRqk{1K`63T9qT0J;@R$+P2{uIdxwjIKe+HxCP0$SzMR=OWd@LgE7*T8tzr5)aZ} zLk=gbNjX<=RUDxtiOHSuPCANr5>LF7v?;Hbd=l@jRPsrDc4;ZnvO7j5$2JcqBYO|* z8QC#*(D1&?Mw5dhJNAx6-FC5@%v9uz#eFO_T5i7bxYVh(((A0GLP-YX*d;5dHH9H` zD=9eM-Jm4(#z3}4JCnIeowk!{(iu6Tu`anEv9+aEg+rZIUM8^{DVIS-b0r$tgD6a} z(`t1_sU01f+%cN$xNK5@Z^}K|IW?V^?{o)6lbu#S)o41)o;D}UaoCnVy}XM^OjT0X zi1xcX?f8C@3XI4e4yX_iFaxC=ucMVyg;F|TWP3>zjgRIdQ#^oh-^l($yGC{%JTx&j zkxU+(7~j8JTi;ixcWGQQSFHi{4oqA&dT8gtWY=U*tplw_xm)TCs4$tfDuwERg7=T@ zfaTI@GC4LeKC;&u?9Ss&NZF-z@@Y=8`~y`rr(#hrQBtW_{D!~^oEmV#mbU7Js*s~j zjcz9?7DWhiC?|k9Y!>KqdcY%gOPvl!fu%yVDp{qZxZu=#N!=xcrADpRs4t}xuhnWS z-;OU|t~$?~U1w-2?S#Ei8QCZ47WUIS(`MhyoAutQ+bJQ;a8?{9l;mT#*k0d|kC>@i zv*E(SnY7A=3zLh&Mh!VzY_yX~gRLJ^nqQbHcq zT4=hqz^1d_5hF33G)sHZdHW03n^Ch;qNF`ra%;el()m*A0!w{3Z_nEcZCNE56`jX= z5)|v1PppSNv7Y+Gdejr^Sx+p7J-8mtf5)2cjy2yMYr;F$1b3_%?wAyJ(k)kJ+m(%s zXTATJv=3LB36D1UB+=!BDd!|)(?Ine@P^=_XD%BcgDbokt z;7Lt@bneChS-CO{DmJO7nu|lEC26wO^dFiF7l}N9s+D3=xQOb%PhrDgrzNzF zsNHE*OYT75eR5-vPifuiPgfV3GqS>RpkE{VEe;PDXmZg-oUI1O_DMR+{+^?AkqeT< zlACqM==hHceko~5Jk23MwL!Q8z4#tHoHHw2dB?*F7o{YZaAdiYq|Ky-lE?u8j@)^7 zx1n%R&(ff=w*hZbB#M%OJEMm(PgPn-jq-*u2g7Nl(zy6JSTWt1an~m#On{gp5w=w{ z@z7GAr+4Fsvqeewe|5l7{~-)=&qSxH&3etve5sEZO15Qz{^z z1gAC-xBGAnutX#N(ub^qgx*dl-C{HBLap61sV)(%t0==J2-#C9=aG#@8q2PxTW8mU z6IYI|I5Cs^GEy*Gz{kr?7Qe23sFt)tfz*;!+btVVJ53KKrEbgJ)=3yyQbdJv=@row zjFh?n*CfUZugS9$vZ!5G&mk3@OyfIe2h|=dZ+fAo(~_->@Mw3^ozH}DvfFM-PHw<7 zO`)9H%Mzi8fu7lr{ly$r1zorfjGvuIuVbgQAPU=&d?xlJn}FCBxYH7|Mb9g#thS4a zu|esC#>=veroKKyn$2vyJ=wt?jvdIJfLmfF1C!$iK~&?8gjTwtq|Im>NqrXa)BTFp zY55yX^D>{zVz{-HUAh=GXDf8HJ!gsTh&D=6;ga2v5(c?N4{lIN%VzbuO#&2H(ke^V zOU#I~z3z^dOKGN92r=`;hO?SkbeN>oRI)aJ3;8xdPKDxGey zZI?JFn!I9XvXiy(vu!!GUUGL$f-+^`OhN7p>`n-92@1tK$YO8`JV%tKTL4G)hYI$U``k0hGBMrk%-VnwITS zoi3PM(i4N@Wy>q2nmyl|bg*>IgimQ{s;P^F)Xd6htYn6>`HJ=?T}(b!$fz||g|b73iBVHi!0 zg8i7Y$C()Ptcl}|&6U_xrcR@dH-SFlKd{Q&q+ngz8E>GM0C=lTXE(cemmu)0;($Z% z8>mVTJZ`qMo053u=EsP^z)DT$Syj5-$cQg1Tq$*VCB2qviy0=6yoyPUB-B)i!LW$! z2HQjwAVr?>-*U={!Vq+LPi|(gt~N82xgG;zOjTA8t4&_EOS+h@Hn_EsG&u$&lC=Sr z{Uu!MJ5WKGE88aBxtQ5;8|?X{Sn1T*_+=r>D8bq|4NK01k@dOV?9q~y_t7GY>hAL- z^EO-7m|)w5%2V7ZsuITcs<1LXuqgwX&=)z6i?>-KseZriHY~cT1a^fM=FDrJm#%CY z-YjGTk^&R$82B?Yw%nW9GsURN_r$-ybf8 z@~yn}o25x{A0fYjK*RdRHdU*IR&JVnluJKm-g@ITXJB?#TfO^TIK|DMBrUc{^3Nyp z!`!B14Rj;RR#fth&hPoD1)^$`*2P$I^FqJXfIMJ)-OdhoC-){~oH93aafFpwFxneK zgs#%1HvAaU1-FSZGZ8Q+v&G&@ffGGQ@sN6-Q5dGW(HTs6gp_s>&*R ztL?YiDrj92dc7Npe$6rElSEN-VJpON0LIDYl~1aGK`<{5S2AXStU|d$DBxAaE}Wt} zNjkuGs&I6w6M8Gen+%+>G16KXHmWY}2fi7#~+R&M*371)${97&N2m0K( z%A1IrPDyz$(vq1oms21tain??6|XNYSacl9mA2U?<+gR|RgW2%;=XW{bcZ&(R0gMi zKDp($q&6<*6CCNP1$M!HkXmdvE4JevJgmOa$lzTJE2kcje2%kg5Xasv**u+Oub zSl|bYqrJ&_zhM3vu4Nhr%a|J(Mb2C-&8OH1E%m+$qn2k=Wpu*%mXX3c}xPQtm0GC{{ewLzZU8z#4V@WG6KWTwwQ4aOC2wbi_-7eYW=1(*4h zi!^?F)oW;+540eF)wrz7;ZNUoBiwebU~`?VGLP=hD~4i!V4=@-)4*j{+-JGMAzK=L zBR6vm6MSdI9|W;a%1;UWTJ#$+ujvxbmS(X{FqsoiAvXD70eiOCSx*vXFlCK1GOZyZ z&eLArdKa@}6~7!}*68TBIMKS;Gz6h@E zGLeJwWfu7wRj*0)GN5Gc1X5K<6JuuSxByXgfwsB9gp8|P&J!0aRx%4AoJ}MFrws;{ zXuRInvzpS>`HWt3A}n?WIy8itn#rlPTLF0jKb<)WvNb@T->&EYeHqTIak>%0~S86Q{XsWX0d-z_i{C5T+qMSp#;|EiSF$WXWllIkndvm4rA_aYeJ7D|C|ReVU#sq3?BO zJfl~mF3o30?-s|pT>(6oEJkR`4VVp)L`zRJ(W4sudkm{Bp^1xrIOhu)u4yfK6c{h3 zmd^aS9h=LqaOTO0n0)6I+Lbg$%AKdoIp{BRs7+Fj?~bv!$x*^DuxiuXag|*GHYM>l z#g^};&&2dy5DTr0Trdc}5=xVqq=k2VB!KkBVHwLw{(7p=Mcd6|9MEqnP53YVAWzw@ zNp3VHJ0>R)p=lV=li1JMtpY%dZ@hH34}J2^bg%czxlTAVJ+G4tZvopmQNXrP7UaHE zi)}hry!rA%gP$}h=tVe}is6=*Pq51B?C9`4w5c$*Mj@b#$3R zTJnW2oLp}FL}|sTWd6uwvDr}iBVDWsf{cMGklW^XMs9P-oXD@)gz*=Sn_SdQk#}X5 zW(ern-TG~I!d<>Tlr)drM(p9i^19sDBXws<^|~IzMbNM%=N+OVSJ@;-pu0agzL%|= zUOQd7vRVFAwu6}fI;%hFQf>6+l(m(rbVt$p_Ck#xis5gkrGIDaliN0~cm8-i5r`!5 z*Gn#Il)JUNqxE9^MP}EJOzc}<=)f!@AnvTMFQ>)sbb@)}Yvmc_%Pd@C^hL#@T%qwu z?tI$kDLTmjKIo+ep8BnU*J9m+}j%e@f`jN-D<^)kKZRKe;Il= zkK}io9G(0IpZtiGHNr=u1(Z0%Un z@{^zPYyu+`ZW+^w3m&q$b%lb z`&UKRtTU>-tlmd=`+wti<%9WJ8`t;It^V$+=pFLIWBz4yH-qi76rKAL|L#aIKI6;n zp3eC4^&hW_{z`th(!Zn)`TtJ;mp|?`_bUG`%7A{qLaFr&4;KIO_@knueI5B@We)|P z<+pQ>KqtS?WiO-q4iD@zzhAic9q0nKWM$6 Date: Mon, 1 Apr 2024 12:47:07 -0400 Subject: [PATCH 16/72] Cleanup --- cmd/crates/stellar-ledger/src/docker.rs | 3 +- cmd/crates/stellar-ledger/src/lib.rs | 52 +++++++++++-------------- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/docker.rs b/cmd/crates/stellar-ledger/src/docker.rs index ace132b65..5bcb2f890 100644 --- a/cmd/crates/stellar-ledger/src/docker.rs +++ b/cmd/crates/stellar-ledger/src/docker.rs @@ -95,7 +95,6 @@ impl DockerConnection { let apps_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("apps"); let volume_bind_string = format!("{}:/project/app/bin", apps_dir.display()); - println!("volume_bind_string: {volume_bind_string}"); let bolos_sdk = format!("BOLOS_SDK={BOLOS_SDK}"); let bolos_env = format!("BOLOS_ENV={BOLOS_ENV}"); let display = format!("DISPLAY=host.docker.internal:0"); // TODO: this should be condiditional depending on os i think @@ -108,7 +107,7 @@ impl DockerConnection { attach_stderr: Some(true), env: Some(env_vars), host_config: Some(HostConfig { - // auto_remove: Some(true), + auto_remove: Some(true), port_bindings: Some(port_mapping_hash), binds: Some(vec![volume_bind_string]), ..Default::default() diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index e84325edd..bcffb4fb8 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -27,26 +27,19 @@ enum Error {} #[cfg(test)] mod test { + use std::time::Duration; + use super::*; - use hidapi::HidApi; - use ledger_transport_hid::TransportNativeHID; - use log::info; use once_cell::sync::Lazy; use serial_test::serial; + use tokio::time::sleep; - fn init_logging() { - let _ = env_logger::builder().is_test(true).try_init(); - } - - fn hidapi() -> &'static HidApi { - static HIDAPI: Lazy = Lazy::new(|| HidApi::new().expect("unable to get HIDAPI")); - - &HIDAPI - } + // TODO: create setup and cleanup functions to start and then stop the emulator at the beginning and end of the test run + #[ignore] #[tokio::test] #[serial] - async fn test_get_public_key() { + async fn test_get_public_key_with_ledger_device() { let transport = new_get_transport().unwrap(); let ledger = app::Ledger::new(transport); let public_key = ledger.get_public_key(0).await; @@ -55,21 +48,9 @@ mod test { } #[tokio::test] - async fn test_my_emulator() { + async fn test_get_public_key() { let mut e = Emulator::new().await; - let start_result = e.run().await; - assert!(start_result.is_ok()); - - let stop_result = e.stop().await; - assert!(stop_result.is_ok()); - } - - // // this may give an error because the get_pub_key is specific to app-stellar and i think im currently using a filecoin app elf - #[tokio::test] - async fn test_my_em_with_get_pub_key() { - // let mut e = Emulator::new().await; - // let start_result = e.run().await; - // assert!(start_result.is_ok()); + start_emulator(&mut e).await; let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); let ledger = app::Ledger::new(transport); @@ -77,7 +58,20 @@ mod test { println!("{public_key:?}"); assert!(public_key.is_ok()); - // let stop_result = e.stop().await; - // assert!(stop_result.is_ok()); + stop_emulator(&mut e).await; + } + + async fn start_emulator(e: &mut Emulator) { + let start_result = e.run().await; + assert!(start_result.is_ok()); + + //TODO: handle this in a different way + // perhaps i can check the endpoint to see if its up before trying to get the public key + sleep(Duration::from_secs(2)).await; + } + + async fn stop_emulator(e: &mut Emulator) { + let stop_result = e.stop().await; + assert!(stop_result.is_ok()); } } From 9c36b4cf69c7653f3c03ef3635ec97ff1333dac5 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 1 Apr 2024 17:08:54 -0400 Subject: [PATCH 17/72] Add get_app_configuration fn --- cmd/crates/stellar-ledger/src/app.rs | 35 ++++++++++++++++++--- cmd/crates/stellar-ledger/src/lib.rs | 47 ++++++++++++++++++++++++---- 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index 84e11dcb4..518d68a0c 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -11,11 +11,16 @@ use crate::transport_zemu_http::TransportZemuHttp; // these came from https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md const CLA: u8 = 0xE0; // Instruction class + const GET_PUBLIC_KEY: u8 = 0x02; // Instruction code to get public key const P1_GET_PUBLIC_KEY: u8 = 0x00; const P2_GET_PUBLIC_KEY_NO_DISPLAY: u8 = 0x00; const P2_GET_PUBLIC_KEY_DISPLAY: u8 = 0x01; +const GET_APP_CONFIGURATION: u8 = 0x06; +const P1_GET_APP_CONFIGURATION: u8 = 0x00; +const P2_GET_APP_CONFIGURATION: u8 = 0x00; + const RETURN_CODE_OK: u16 = 36864; // APDUAnswer.retcode which means success from Ledger #[derive(thiserror::Error, Debug)] @@ -55,6 +60,17 @@ where Self::get_public_key_with_display_flag(self, hd_path, false).await } + pub async fn get_app_configuration(&self) -> Result, LedgerError> { + let command = APDUCommand { + cla: CLA, + ins: GET_APP_CONFIGURATION, + p1: P1_GET_APP_CONFIGURATION, + p2: P2_GET_APP_CONFIGURATION, + data: vec![], + }; + self.send_command_to_ledger(command).await + } + /// The display_and_confirm bool determines if the Ledger will display the public key on its screen and requires user approval to share async fn get_public_key_with_display_flag( &self, @@ -84,7 +100,20 @@ where tracing::info!("APDU in: {}", hex::encode(&command.serialize())); - // transport.exchange as is using the default implementation which is synchronous. but, we need to use the async version of exchange here so that we are conforming to the Exchange trait + match self.send_command_to_ledger(command).await { + Ok(value) => { + return Ok(stellar_strkey::ed25519::PublicKey::from_payload(&value).unwrap()); + } + Err(err) => { + return Err(err); + } + } + } + + async fn send_command_to_ledger( + &self, + command: APDUCommand>, + ) -> Result, LedgerError> { match self.transport.exchange(&command).await { Ok(response) => { tracing::info!( @@ -94,9 +123,7 @@ where ); // Ok means we successfully connected with the Ledger but it doesn't mean our request succeeded. We still need to check the response.retcode if response.retcode() == RETURN_CODE_OK { - return Ok( - stellar_strkey::ed25519::PublicKey::from_payload(&response.data()).unwrap(), - ); + return Ok(response.data().to_vec()); } else { let retcode = response.retcode(); diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index bcffb4fb8..1b61726ea 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -49,16 +49,51 @@ mod test { #[tokio::test] async fn test_get_public_key() { - let mut e = Emulator::new().await; - start_emulator(&mut e).await; + let mut emulator = Emulator::new().await; + start_emulator(&mut emulator).await; + + let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); + let ledger = app::Ledger::new(transport); + + match ledger.get_public_key(0).await { + Ok(public_key) => { + let public_key_string = public_key.to_string(); + // This is determined by the seed phrase used to start up the emulator + // TODO: make the seed phrase configurable + let expected_public_key = + "GDUTHCF37UX32EMANXIL2WOOVEDZ47GHBTT3DYKU6EKM37SOIZXM2FN7"; + assert_eq!(public_key_string, expected_public_key); + } + Err(e) => { + println!("{e}"); + assert!(false); + stop_emulator(&mut emulator).await; + } + } + + stop_emulator(&mut emulator).await; + } + + #[tokio::test] + async fn test_get_app_configuration() { + let mut emulator = Emulator::new().await; + start_emulator(&mut emulator).await; let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); let ledger = app::Ledger::new(transport); - let public_key = ledger.get_public_key(0).await; - println!("{public_key:?}"); - assert!(public_key.is_ok()); - stop_emulator(&mut e).await; + match ledger.get_app_configuration().await { + Ok(config) => { + assert_eq!(config, vec![0, 5, 0, 3]); + } + Err(e) => { + println!("{e}"); + assert!(false); + stop_emulator(&mut emulator).await; + } + }; + + stop_emulator(&mut emulator).await; } async fn start_emulator(e: &mut Emulator) { From e39d95b0458d5417b5124c3c78a3258339becbec Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:41:30 -0400 Subject: [PATCH 18/72] Add sign_transaction_hash fn --- cmd/crates/stellar-ledger/src/app.rs | 34 +++++++++++++++++++++++++ cmd/crates/stellar-ledger/src/lib.rs | 38 ++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index 518d68a0c..376c3176e 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -21,6 +21,10 @@ const GET_APP_CONFIGURATION: u8 = 0x06; const P1_GET_APP_CONFIGURATION: u8 = 0x00; const P2_GET_APP_CONFIGURATION: u8 = 0x00; +const SIGN_TX_HASH: u8 = 0x08; +const P1_SIGN_TX_HASH: u8 = 0x00; +const P2_SIGN_TX_HASH: u8 = 0x00; + const RETURN_CODE_OK: u16 = 36864; // APDUAnswer.retcode which means success from Ledger #[derive(thiserror::Error, Debug)] @@ -71,6 +75,36 @@ where self.send_command_to_ledger(command).await } + // based on impl from https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166 + pub async fn sign_transaction_hash( + &self, + hd_path: slip10::BIP32Path, + transaction_hash: Vec, + ) -> Result, LedgerError> { + // convert the hd_path into bytes to be sent as `data` to the Ledger + // the first element of the data should be the number of elements in the path + let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); + let hd_path_elements_count = hd_path.depth(); + hd_path_to_bytes.insert(0, hd_path_elements_count); + + let mut data = hd_path_to_bytes; + data.append(&mut transaction_hash.clone()); + + let command = APDUCommand { + cla: CLA, + ins: SIGN_TX_HASH, + p1: P1_SIGN_TX_HASH, + p2: P2_SIGN_TX_HASH, + data: data, + }; + + self.send_command_to_ledger(command).await + } + + + + + /// The display_and_confirm bool determines if the Ledger will display the public key on its screen and requires user approval to share async fn get_public_key_with_display_flag( &self, diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 1b61726ea..5c9bb5563 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -27,14 +27,19 @@ enum Error {} #[cfg(test)] mod test { - use std::time::Duration; + use std::{str::FromStr, time::Duration}; use super::*; use once_cell::sync::Lazy; use serial_test::serial; use tokio::time::sleep; - // TODO: create setup and cleanup functions to start and then stop the emulator at the beginning and end of the test run + use crate::app::LedgerError::APDUExchangeError; + + // TODO: + // - create setup and cleanup functions to start and then stop the emulator at the beginning and end of the test run + // - test each of the device models + // - handle the sleep differently #[ignore] #[tokio::test] @@ -96,6 +101,35 @@ mod test { stop_emulator(&mut emulator).await; } + #[tokio::test] + async fn test_sign_tx_hash_when_hash_signing_is_not_enabled() { + //when hash signing isnt enabled on the device we expect an error + let mut emulator = Emulator::new().await; + start_emulator(&mut emulator).await; + + let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); + let ledger = app::Ledger::new(transport); + + let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); + let test_hash = + "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889".as_bytes(); + + let result = ledger.sign_transaction_hash(path, test_hash.into()).await; + if let Err(APDUExchangeError(msg)) = result { + assert_eq!(msg, "Ledger APDU retcode: 0x6C66"); + } else { + stop_emulator(&mut emulator).await; + panic!("Unexpected result"); + } + + stop_emulator(&mut emulator).await; + } + + //TODO: implement this test + // not sure how to enable hash signing on the emulator yet. zemu has methods that emulate pressing the buttons to choose the option + #[tokio::test] + async fn test_sign_tx_hash_when_hash_signing_is_enabled() {} + async fn start_emulator(e: &mut Emulator) { let start_result = e.run().await; assert!(start_result.is_ok()); From 63bafad59b29d57424790cf6e4214869a935ca10 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 4 Apr 2024 10:25:29 -0400 Subject: [PATCH 19/72] Allow for enabling tx hash signing on the emulator --- cmd/crates/stellar-ledger/Cargo.toml | 4 +- cmd/crates/stellar-ledger/src/lib.rs | 100 ++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 5 deletions(-) diff --git a/cmd/crates/stellar-ledger/Cargo.toml b/cmd/crates/stellar-ledger/Cargo.toml index c454a8f15..c19dd559e 100644 --- a/cmd/crates/stellar-ledger/Cargo.toml +++ b/cmd/crates/stellar-ledger/Cargo.toml @@ -21,11 +21,11 @@ soroban-env-host = { workspace = true } ed25519-dalek = { workspace=true } stellar-strkey = { workspace=true } ledger-transport-hid = "0.10.0" +ledger-transport = "0.10.0" sep5.workspace = true slip10 = "0.4.3" -ledger-transport = "0.10.0" tracing = {workspace=true} -hex = {workspace=true} +hex.workspace = true byteorder = "1.5.0" bollard = "0.15.0" home = "0.5.9" diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 5c9bb5563..9c2493471 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -27,7 +27,7 @@ enum Error {} #[cfg(test)] mod test { - use std::{str::FromStr, time::Duration}; + use std::{collections::HashMap, str::FromStr, time::Duration}; use super::*; use once_cell::sync::Lazy; @@ -117,9 +117,10 @@ mod test { let result = ledger.sign_transaction_hash(path, test_hash.into()).await; if let Err(APDUExchangeError(msg)) = result { assert_eq!(msg, "Ledger APDU retcode: 0x6C66"); + // this error code is SW_TX_HASH_SIGNING_MODE_NOT_ENABLED from https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md } else { stop_emulator(&mut emulator).await; - panic!("Unexpected result"); + panic!("Unexpected result: {:?}", result); } stop_emulator(&mut emulator).await; @@ -128,7 +129,43 @@ mod test { //TODO: implement this test // not sure how to enable hash signing on the emulator yet. zemu has methods that emulate pressing the buttons to choose the option #[tokio::test] - async fn test_sign_tx_hash_when_hash_signing_is_enabled() {} + async fn test_sign_tx_hash_when_hash_signing_is_enabled() { + //when hash signing isnt enabled on the device we expect an error + let mut emulator = Emulator::new().await; + start_emulator(&mut emulator).await; + enable_hash_signing().await; + + let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); + let ledger = app::Ledger::new(transport); + + let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); + let mut test_hash = vec![0u8; 32]; + + match hex::decode_to_slice( + "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889", + &mut test_hash as &mut [u8], + ) { + Ok(()) => {} + Err(e) => { + stop_emulator(&mut emulator).await; + panic!("Unexpected result: {e}"); + } + } + + // this is what the js code is doing: + // let hash_as_bytes = [ 51, 137, 233, 240, 241, 166, 95, 25, 115, 108, 172, 245, 68, 194, 232, 37, 49, 62, 132, 71, 245, 105, 35, 59, 184, 219, 57, 170, 96, 124, 136, 137 ].to_vec(); + match ledger.sign_transaction_hash(path, test_hash).await { + Ok(result) => { + println!("this is the response from signing the hash: {result:?}"); + } + Err(e) => { + stop_emulator(&mut emulator).await; + panic!("Unexpected result: {e}"); + } + } + + stop_emulator(&mut emulator).await; + } async fn start_emulator(e: &mut Emulator) { let start_result = e.run().await; @@ -143,4 +180,61 @@ mod test { let stop_result = e.stop().await; assert!(stop_result.is_ok()); } + + // FIXME lol/sob + async fn enable_hash_signing() { + // let client = reqwest::Client::new(); + // client.post("http://localhost:5001/button/right") + let mut map = HashMap::new(); + map.insert("action", "press-and-release"); + + let client = reqwest::Client::new(); + // right button press + client + .post("http://localhost:5001/button/right") + .json(&map) + .send() + .await + .unwrap(); + + // both button press + client + .post("http://localhost:5001/button/both") + .json(&map) + .send() + .await + .unwrap(); + + // both button press + client + .post("http://localhost:5001/button/both") + .json(&map) + .send() + .await + .unwrap(); + + // right button press + client + .post("http://localhost:5001/button/right") + .json(&map) + .send() + .await + .unwrap(); + + // right button press + client + .post("http://localhost:5001/button/right") + .json(&map) + .send() + .await + .unwrap(); + + // both button press + client + .post("http://localhost:5001/button/both") + .json(&map) + .send() + .await + .unwrap(); + } } From 0f964201ae0f1f0cd7c0a8f19a8d3c84d73b49d6 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 4 Apr 2024 12:09:00 -0400 Subject: [PATCH 20/72] WIP - approve the tx on the device --- cmd/crates/stellar-ledger/src/app.rs | 17 ++++- cmd/crates/stellar-ledger/src/lib.rs | 109 ++++++++++++++++++++++++--- 2 files changed, 114 insertions(+), 12 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index 376c3176e..e03190cd7 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -1,5 +1,7 @@ use byteorder::{BigEndian, WriteBytesExt}; -use std::{io::Write, str::FromStr}; +use reqwest::Response; +use std::{io::Write, str::FromStr, thread::sleep, time::Duration, vec}; +use stellar_xdr::curr::ReadXdr; use ledger_transport::{APDUCommand, Exchange}; use ledger_transport_hid::{ @@ -7,7 +9,11 @@ use ledger_transport_hid::{ LedgerHIDError, TransportNativeHID, }; -use crate::transport_zemu_http::TransportZemuHttp; +use soroban_env_host::xdr::Transaction; + +use crate::transport_zemu_http::{LedgerZemuError, TransportZemuHttp}; + +const APDU_MAX_SIZE: u8 = 150; // from https://github.com/LedgerHQ/ledger-live/blob/36cfbf3fa3300fd99bcee2ab72e1fd8f280e6280/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L181 // these came from https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md const CLA: u8 = 0xE0; // Instruction class @@ -148,7 +154,12 @@ where &self, command: APDUCommand>, ) -> Result, LedgerError> { - match self.transport.exchange(&command).await { + let response = self.transport.exchange(&command).await; + println!("SLEEPING for 10..."); + sleep(Duration::from_secs(10)); + println!("sleep over, checking the response"); + + match response { Ok(response) => { tracing::info!( "APDU out: {}\nAPDU ret code: {:x}", diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 9c2493471..d240901c0 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -154,15 +154,19 @@ mod test { // this is what the js code is doing: // let hash_as_bytes = [ 51, 137, 233, 240, 241, 166, 95, 25, 115, 108, 172, 245, 68, 194, 232, 37, 49, 62, 132, 71, 245, 105, 35, 59, 184, 219, 57, 170, 96, 124, 136, 137 ].to_vec(); - match ledger.sign_transaction_hash(path, test_hash).await { - Ok(result) => { - println!("this is the response from signing the hash: {result:?}"); - } - Err(e) => { - stop_emulator(&mut emulator).await; - panic!("Unexpected result: {e}"); - } - } + let result = ledger.sign_transaction_hash(path, test_hash).await; + + // approve_tx_hash_signature().await; + + // match result { + // Ok(result) => { + // println!("this is the response from signing the hash: {result:?}"); + // } + // Err(e) => { + // stop_emulator(&mut emulator).await; + // panic!("Unexpected result: {e}"); + // } + // } stop_emulator(&mut emulator).await; } @@ -237,4 +241,91 @@ mod test { .await .unwrap(); } + + async fn approve_tx_hash_signature() { + // let client = reqwest::Client::new(); + // client.post("http://localhost:5001/button/right") + let mut map = HashMap::new(); + map.insert("action", "press-and-release"); + + let client = reqwest::Client::new(); + // right button press + client + .post("http://localhost:5001/button/right") + .json(&map) + .send() + .await + .unwrap(); + + // right button press + client + .post("http://localhost:5001/button/right") + .json(&map) + .send() + .await + .unwrap(); + // right button press + client + .post("http://localhost:5001/button/right") + .json(&map) + .send() + .await + .unwrap(); + // right button press + client + .post("http://localhost:5001/button/right") + .json(&map) + .send() + .await + .unwrap(); + // right button press + client + .post("http://localhost:5001/button/right") + .json(&map) + .send() + .await + .unwrap(); + // right button press + client + .post("http://localhost:5001/button/right") + .json(&map) + .send() + .await + .unwrap(); + // right button press + client + .post("http://localhost:5001/button/right") + .json(&map) + .send() + .await + .unwrap(); + // right button press + client + .post("http://localhost:5001/button/right") + .json(&map) + .send() + .await + .unwrap(); + // right button press + client + .post("http://localhost:5001/button/right") + .json(&map) + .send() + .await + .unwrap(); + // right button press + client + .post("http://localhost:5001/button/right") + .json(&map) + .send() + .await + .unwrap(); + // both button press + client + .post("http://localhost:5001/button/both") + .json(&map) + .send() + .await + .unwrap(); + } } From d249506740303b4916e939c416f48681bfad60e5 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 8 Apr 2024 16:13:10 -0400 Subject: [PATCH 21/72] WIP signing a tx --- cmd/crates/stellar-ledger/src/app.rs | 140 ++++++++++++++++++++++++++- cmd/crates/stellar-ledger/src/lib.rs | 73 +++++++++++--- 2 files changed, 195 insertions(+), 18 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index e03190cd7..1b868b632 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -1,7 +1,7 @@ use byteorder::{BigEndian, WriteBytesExt}; use reqwest::Response; use std::{io::Write, str::FromStr, thread::sleep, time::Duration, vec}; -use stellar_xdr::curr::ReadXdr; +use stellar_xdr::curr::{Limits, ReadXdr, WriteXdr}; use ledger_transport::{APDUCommand, Exchange}; use ledger_transport_hid::{ @@ -23,6 +23,12 @@ const P1_GET_PUBLIC_KEY: u8 = 0x00; const P2_GET_PUBLIC_KEY_NO_DISPLAY: u8 = 0x00; const P2_GET_PUBLIC_KEY_DISPLAY: u8 = 0x01; +const SIGN_TX: u8 = 0x04; +const P1_SIGN_TX_FIRST: u8 = 0x00; +const P1_SIGN_TX_NOT_FIRST: u8 = 0x80; +const P2_SIGN_TX_LAST: u8 = 0x80; +const P2_SIGN_TX_MORE: u8 = 0x80; + const GET_APP_CONFIGURATION: u8 = 0x06; const P1_GET_APP_CONFIGURATION: u8 = 0x00; const P2_GET_APP_CONFIGURATION: u8 = 0x00; @@ -89,6 +95,7 @@ where ) -> Result, LedgerError> { // convert the hd_path into bytes to be sent as `data` to the Ledger // the first element of the data should be the number of elements in the path + let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); let hd_path_elements_count = hd_path.depth(); hd_path_to_bytes.insert(0, hd_path_elements_count); @@ -107,9 +114,131 @@ where self.send_command_to_ledger(command).await } + pub async fn sign_transaction( + &self, + hd_path: slip10::BIP32Path, + transaction: Transaction, + ) -> Result, LedgerError> { + let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); + let hd_path_elements_count = hd_path.depth(); + hd_path_to_bytes.insert(0, hd_path_elements_count); + + // let mut tx_as_bytes = vec![0u8; 32]; + + // let tx_as_hex = "7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a979000000020000000020da998b75e42b1f7f85d075c127f5b246df12ad96f010bcf7f76f72b16e57130000006400c5b4a5000000190000000000000000000000010000000000000001000000009541f02746240c1e9f3843d28e56f0a583ecd27502fb0f4a27d4d0922fe064a200000000000000000098968000000000"; + // match hex::decode_to_slice( + // tx_as_hex, + // &mut tx_as_bytes as &mut [u8], + // ) { + // Ok(()) => {} + // Err(e) => { + // panic!("Unexpected result: {e}"); + // } + // } + + let mut b: Vec = [ + 122, 195, 57, 151, 84, 78, 49, 117, 210, 102, 189, 2, 36, 57, 178, 44, 219, 22, 80, + 140, 1, 22, 63, 38, 229, 203, 42, 62, 16, 69, 169, 121, 0, 0, 0, 2, 0, 0, 0, 0, 32, + 218, 153, 139, 117, 228, 43, 31, 127, 133, 208, 117, 193, 39, 245, 178, 70, 223, 18, + 173, 150, 240, 16, 188, 247, 247, 111, 114, 177, 110, 87, 19, 0, 0, 0, 100, 0, 197, + 180, 165, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 149, 65, 240, 39, 70, 36, 12, 30, 159, 56, 67, 210, 142, 86, 240, 165, 131, + 236, 210, 117, 2, 251, 15, 74, 39, 212, 208, 146, 47, 224, 100, 162, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 152, 150, 128, 0, 0, 0, 0, + ] + .to_vec(); + + let buffer_size = 1 + hd_path_elements_count * 4; + + let chunk_size = APDU_MAX_SIZE - buffer_size; + + let mut data = hd_path_to_bytes; + data.append(&mut b); + + let chunks = data.chunks(chunk_size as usize); + let chunks_count = chunks.len(); + + let mut result = Vec::new(); + println!("chunks_count: {:?}", chunks_count); + for (i, chunk) in chunks.enumerate() { + let is_first_chunk = i == 0; + let is_last_chunk = chunks_count == i + 1; + let command = APDUCommand { + cla: CLA, + ins: SIGN_TX, + p1: if is_first_chunk { + P1_SIGN_TX_FIRST + } else { + P1_SIGN_TX_NOT_FIRST + }, + p2: if is_last_chunk { + P2_SIGN_TX_LAST + } else { + P2_SIGN_TX_MORE + }, + data: chunk.to_vec(), + }; + + println!("command: {:?}", command); + match self.send_command_to_ledger(command).await { + Ok(mut r) => { + result.append(&mut r); + } + Err(e) => { + return Err(e); + } + } + } + + Ok(result) + } + + // // should this take in a transaction or an assembled transaction? + // // i think i need to convert this tranaction to a byte array + // pub async fn sign_transaction( + // &self, + // hd_path: slip10::BIP32Path, + // transaction: Transaction, + // ) -> Result, LedgerError> { + // // TODO: check tx size and make sure it is less than TX_MAX_SIZE + + // // convert the hd_path into bytes to be sent as `data` to the Ledger + // // the first element of the data should be the number of elements in the path + // let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); + // let hd_path_elements_count = hd_path.depth(); + // hd_path_to_bytes.insert(0, hd_path_elements_count); + // let buffer_size = 1 + hd_path_elements_count * 4; + // let chunkSize = APDU_MAX_SIZE - buffer_size; + + // // if (transaction.length <= chunkSize) { + // // // it fits in a single apdu + // // apdus.push(Buffer.concat([buffer, transaction])); + // // } + + // // this is copilot generated: + // // let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); + // // let hd_path_elements_count = hd_path.depth(); + // // hd_path_to_bytes.insert(0, hd_path_elements_count); + + // // let mut data = Vec::new(); + // // data.write_u32::(transaction.len() as u32) + // // .unwrap(); + // // data.extend(transaction); + + // let command = APDUCommand { + // cla: CLA, + // ins: SIGN_TX, + // p1: 0x00, + // p2: 0x00, + // data: vec![], + // }; + + // self.send_command_to_ledger(command).await + + // } /// The display_and_confirm bool determines if the Ledger will display the public key on its screen and requires user approval to share async fn get_public_key_with_display_flag( @@ -123,6 +252,10 @@ where let hd_path_elements_count = hd_path.depth(); hd_path_to_bytes.insert(0, hd_path_elements_count); + println!("data: {:?}", hd_path_to_bytes); + // data: [3, 128, 0, 0, 44, 128, 0, 0, 148, 128, 0, 0, 0] + // in json: data: [ 3, 128, 0, 0, 44, 128, 0, 0, 148, 128, 0, 0, 0 ] + let p2 = if display_and_confirm { P2_GET_PUBLIC_KEY_DISPLAY } else { @@ -157,6 +290,8 @@ where let response = self.transport.exchange(&command).await; println!("SLEEPING for 10..."); sleep(Duration::from_secs(10)); + // this is when the user is supposed to confirm the transaction on the Ledger + // how do i do this programatically with the emulator? println!("sleep over, checking the response"); match response { @@ -167,6 +302,8 @@ where response.retcode(), ); // Ok means we successfully connected with the Ledger but it doesn't mean our request succeeded. We still need to check the response.retcode + println!("RETCODE: {:?}", response.retcode()); + println!("response: {:?}", response.data()); if response.retcode() == RETURN_CODE_OK { return Ok(response.data().to_vec()); } else { @@ -179,7 +316,6 @@ where Err(err) => { //FIX ME!!!! return Err(LedgerError::LedgerConnectionError("test".to_string())); - //FIX ME!!! } }; } diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index d240901c0..77944b377 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -27,11 +27,18 @@ enum Error {} #[cfg(test)] mod test { - use std::{collections::HashMap, str::FromStr, time::Duration}; + use std::{collections::HashMap, path::PathBuf, str::FromStr, thread, time::Duration}; use super::*; + use once_cell::sync::Lazy; use serial_test::serial; + + use stellar_xdr::curr::{ + HostFunction, InvokeContractArgs, Memo, MuxedAccount, Preconditions, SequenceNumber, + StringM, TransactionExt, VecM, + }; + // should MuxedAccount be stellar_strkey::ed25519::MuxedAccount; instead? use tokio::time::sleep; use crate::app::LedgerError::APDUExchangeError; @@ -101,6 +108,42 @@ mod test { stop_emulator(&mut emulator).await; } + #[tokio::test] + async fn test_sign_tx() { + let mut emulator = Emulator::new().await; + start_emulator(&mut emulator).await; + + let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); + let ledger = app::Ledger::new(transport); + + let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); + + let tx = Transaction { + source_account: MuxedAccount::Ed25519(Uint256([0; 32])), + fee: 0, + seq_num: SequenceNumber(1), + cond: Preconditions::None, + memo: Memo::Text("Stellar".as_bytes().try_into().unwrap()), + operations: [].to_vec().try_into().unwrap(), + ext: TransactionExt::V0, + }; + + let result = ledger.sign_transaction(path, tx).await; + println!("result: {result:?}"); + // match ledger.sign_transaction(hd_path, transaction).await { + // Ok(config) => { + // assert_eq!(config, vec![0, 5, 0, 3]); + // } + // Err(e) => { + // println!("{e}"); + // assert!(false); + // stop_emulator(&mut emulator).await; + // } + // }; + + stop_emulator(&mut emulator).await; + } + #[tokio::test] async fn test_sign_tx_hash_when_hash_signing_is_not_enabled() { //when hash signing isnt enabled on the device we expect an error @@ -126,8 +169,6 @@ mod test { stop_emulator(&mut emulator).await; } - //TODO: implement this test - // not sure how to enable hash signing on the emulator yet. zemu has methods that emulate pressing the buttons to choose the option #[tokio::test] async fn test_sign_tx_hash_when_hash_signing_is_enabled() { //when hash signing isnt enabled on the device we expect an error @@ -152,21 +193,19 @@ mod test { } } - // this is what the js code is doing: - // let hash_as_bytes = [ 51, 137, 233, 240, 241, 166, 95, 25, 115, 108, 172, 245, 68, 194, 232, 37, 49, 62, 132, 71, 245, 105, 35, 59, 184, 219, 57, 170, 96, 124, 136, 137 ].to_vec(); - let result = ledger.sign_transaction_hash(path, test_hash).await; + let result = ledger.sign_transaction_hash(path, test_hash); - // approve_tx_hash_signature().await; + approve_tx_hash_signature().await; - // match result { - // Ok(result) => { - // println!("this is the response from signing the hash: {result:?}"); - // } - // Err(e) => { - // stop_emulator(&mut emulator).await; - // panic!("Unexpected result: {e}"); - // } - // } + match result.await { + Ok(result) => { + println!("this is the response from signing the hash: {result:?}"); + } + Err(e) => { + stop_emulator(&mut emulator).await; + panic!("Unexpected result: {e}"); + } + } stop_emulator(&mut emulator).await; } @@ -243,6 +282,8 @@ mod test { } async fn approve_tx_hash_signature() { + println!("approving tx hash sig"); + // let client = reqwest::Client::new(); // client.post("http://localhost:5001/button/right") let mut map = HashMap::new(); From 691102bcf49e7b1bc0522b20669c3e4ea146f706 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 8 Apr 2024 17:46:32 -0400 Subject: [PATCH 22/72] wip sign tx --- cmd/crates/stellar-ledger/src/app.rs | 38 ++++++++++++++++++---------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index 1b868b632..603890681 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -26,7 +26,7 @@ const P2_GET_PUBLIC_KEY_DISPLAY: u8 = 0x01; const SIGN_TX: u8 = 0x04; const P1_SIGN_TX_FIRST: u8 = 0x00; const P1_SIGN_TX_NOT_FIRST: u8 = 0x80; -const P2_SIGN_TX_LAST: u8 = 0x80; +const P2_SIGN_TX_LAST: u8 = 0x00; const P2_SIGN_TX_MORE: u8 = 0x80; const GET_APP_CONFIGURATION: u8 = 0x06; @@ -119,10 +119,6 @@ where hd_path: slip10::BIP32Path, transaction: Transaction, ) -> Result, LedgerError> { - let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); - let hd_path_elements_count = hd_path.depth(); - hd_path_to_bytes.insert(0, hd_path_elements_count); - // let mut tx_as_bytes = vec![0u8; 32]; // let tx_as_hex = "7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a979000000020000000020da998b75e42b1f7f85d075c127f5b246df12ad96f010bcf7f76f72b16e57130000006400c5b4a5000000190000000000000000000000010000000000000001000000009541f02746240c1e9f3843d28e56f0a583ecd27502fb0f4a27d4d0922fe064a200000000000000000098968000000000"; @@ -136,7 +132,7 @@ where // } // } - let mut b: Vec = [ + let mut tx_as_bytes: Vec = [ 122, 195, 57, 151, 84, 78, 49, 117, 210, 102, 189, 2, 36, 57, 178, 44, 219, 22, 80, 140, 1, 22, 63, 38, 229, 203, 42, 62, 16, 69, 169, 121, 0, 0, 0, 2, 0, 0, 0, 0, 32, 218, 153, 139, 117, 228, 43, 31, 127, 133, 208, 117, 193, 39, 245, 178, 70, 223, 18, @@ -148,21 +144,37 @@ where ] .to_vec(); - let buffer_size = 1 + hd_path_elements_count * 4; + // data + // data[0] = pathElts.length + // data pathElts + // chunk - let chunk_size = APDU_MAX_SIZE - buffer_size; + let mut data: Vec = Vec::new(); - let mut data = hd_path_to_bytes; - data.append(&mut b); + let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); + let hd_path_elements_count = hd_path.depth(); + + data.insert(0, hd_path_elements_count); + data.append(&mut hd_path_to_bytes); - let chunks = data.chunks(chunk_size as usize); + let buffer_size = 1 + hd_path_elements_count * 4; + let chunk_size = APDU_MAX_SIZE - buffer_size; + + let chunks = tx_as_bytes.chunks(chunk_size as usize); let chunks_count = chunks.len(); let mut result = Vec::new(); println!("chunks_count: {:?}", chunks_count); + for (i, chunk) in chunks.enumerate() { let is_first_chunk = i == 0; let is_last_chunk = chunks_count == i + 1; + println!("is_first_chunk {is_first_chunk:?}"); + println!("is_last_chunk {is_last_chunk:?}"); + let mut data = data.clone(); + data.append(&mut chunk.to_vec()); + println!("DATA BEING SENT for i {i}: {data:?}"); + let command = APDUCommand { cla: CLA, ins: SIGN_TX, @@ -176,7 +188,7 @@ where } else { P2_SIGN_TX_MORE }, - data: chunk.to_vec(), + data: data.to_vec(), }; println!("command: {:?}", command); @@ -253,8 +265,6 @@ where hd_path_to_bytes.insert(0, hd_path_elements_count); println!("data: {:?}", hd_path_to_bytes); - // data: [3, 128, 0, 0, 44, 128, 0, 0, 148, 128, 0, 0, 0] - // in json: data: [ 3, 128, 0, 0, 44, 128, 0, 0, 148, 128, 0, 0, 0 ] let p2 = if display_and_confirm { P2_GET_PUBLIC_KEY_DISPLAY From 98a0ffad78f34e5a054c5d15900df1dc9ab85d95 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:21:41 -0400 Subject: [PATCH 23/72] not sure which is right --- cmd/crates/stellar-ledger/src/app.rs | 39 ++++++++++++++++++---------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index 603890681..165e11e91 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -24,10 +24,10 @@ const P2_GET_PUBLIC_KEY_NO_DISPLAY: u8 = 0x00; const P2_GET_PUBLIC_KEY_DISPLAY: u8 = 0x01; const SIGN_TX: u8 = 0x04; -const P1_SIGN_TX_FIRST: u8 = 0x00; -const P1_SIGN_TX_NOT_FIRST: u8 = 0x80; -const P2_SIGN_TX_LAST: u8 = 0x00; -const P2_SIGN_TX_MORE: u8 = 0x80; +const P1_SIGN_TX_FIRST: u8 = 0x00; // 0 +const P1_SIGN_TX_NOT_FIRST: u8 = 0x80; // 128 +const P2_SIGN_TX_LAST: u8 = 0x00; // 0 +const P2_SIGN_TX_MORE: u8 = 0x80; // 128 const GET_APP_CONFIGURATION: u8 = 0x06; const P1_GET_APP_CONFIGURATION: u8 = 0x00; @@ -144,11 +144,6 @@ where ] .to_vec(); - // data - // data[0] = pathElts.length - // data pathElts - // chunk - let mut data: Vec = Vec::new(); let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); @@ -166,11 +161,29 @@ where let mut result = Vec::new(); println!("chunks_count: {:?}", chunks_count); - for (i, chunk) in chunks.enumerate() { + let chunks_hardcoded: Vec> = [ + [ + 3, 128, 0, 0, 44, 128, 0, 0, 148, 128, 0, 0, 0, 122, 195, 57, 151, 84, 78, 49, 117, + 210, 102, 189, 2, 36, 57, 178, 44, 219, 22, 80, 140, 1, 22, 63, 38, 229, 203, 42, + 62, 16, 69, 169, 121, 0, 0, 0, 2, 0, 0, 0, 0, 32, 218, 153, 139, 117, 228, 43, 31, + 127, 133, 208, 117, 193, 39, 245, 178, 70, 223, 18, 173, 150, 240, 16, 188, 247, + 247, 111, 114, 177, 110, 87, 19, 0, 0, 0, 100, 0, 197, 180, 165, 0, 0, 0, 25, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 149, 65, 240, 39, + 70, 36, 12, 30, 159, 56, 67, 210, 142, 86, 240, 165, 131, 236, 210, 117, 2, 251, + 15, 74, 39, 212, 208, 146, 4, + ] + .to_vec(), + [ + 224, 100, 162, 0, 0, 0, 0, 0, 0, 0, 0, 0, 152, 150, 128, 0, 0, 0, 0, + ] + .to_vec(), + ] + .to_vec(); + + for (i, chunk) in chunks_hardcoded.iter().enumerate() { let is_first_chunk = i == 0; let is_last_chunk = chunks_count == i + 1; - println!("is_first_chunk {is_first_chunk:?}"); - println!("is_last_chunk {is_last_chunk:?}"); + let mut data = data.clone(); data.append(&mut chunk.to_vec()); println!("DATA BEING SENT for i {i}: {data:?}"); @@ -188,7 +201,7 @@ where } else { P2_SIGN_TX_MORE }, - data: data.to_vec(), + data: chunk.to_vec(), }; println!("command: {:?}", command); From 28f999d8431d7505f8785bfd893d86971e97624c Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 11 Apr 2024 10:18:41 -0400 Subject: [PATCH 24/72] Making progress MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i needed to look at the js implementation again (hw-app-str) - they have a note that says to pass the `signatureBase` into the `signTransaction` fn. I had forgotten about this. The `signatureBase` fn is defined in js-stellar-base and is the value that should be sent to the network. it is TransactionSignaturePayload.toXDR(). Which is also in the signer train and i didn't notice it. 🙈 --- cmd/crates/stellar-ledger/src/app.rs | 182 +++++++++++++-------------- cmd/crates/stellar-ledger/src/lib.rs | 96 +++++++++++--- 2 files changed, 170 insertions(+), 108 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index 165e11e91..ffb2eb971 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -1,7 +1,18 @@ use byteorder::{BigEndian, WriteBytesExt}; use reqwest::Response; -use std::{io::Write, str::FromStr, thread::sleep, time::Duration, vec}; -use stellar_xdr::curr::{Limits, ReadXdr, WriteXdr}; +use sha2::{Digest, Sha256}; +use std::{ + io::{Cursor, Write}, + str::FromStr, + thread::sleep, + time::Duration, + vec, +}; +use stellar_xdr::curr::{ + self, Hash, Limited, Limits, ReadXdr, TransactionEnvelope, TransactionSignaturePayload, + TransactionSignaturePayloadTaggedTransaction, TransactionV0, TransactionV0Envelope, + TransactionV1Envelope, WriteXdr, +}; use ledger_transport::{APDUCommand, Exchange}; use ledger_transport_hid::{ @@ -119,31 +130,86 @@ where hd_path: slip10::BIP32Path, transaction: Transaction, ) -> Result, LedgerError> { - // let mut tx_as_bytes = vec![0u8; 32]; - - // let tx_as_hex = "7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a979000000020000000020da998b75e42b1f7f85d075c127f5b246df12ad96f010bcf7f76f72b16e57130000006400c5b4a5000000190000000000000000000000010000000000000001000000009541f02746240c1e9f3843d28e56f0a583ecd27502fb0f4a27d4d0922fe064a200000000000000000098968000000000"; - // match hex::decode_to_slice( - // tx_as_hex, - // &mut tx_as_bytes as &mut [u8], - // ) { - // Ok(()) => {} + let tagged_transaction = + TransactionSignaturePayloadTaggedTransaction::Tx(transaction.clone()); + // FIXME + let testnet_passphrase = "Test SDF Network ; September 2015"; + let network_hash = Hash(Sha256::digest(testnet_passphrase.as_bytes()).into()); + + let signature_payload = TransactionSignaturePayload { + network_id: network_hash, + tagged_transaction: tagged_transaction, + }; + + let mut signature_payload_as_bytes = signature_payload.to_xdr(Limits::none()).unwrap(); + + // let tx_env = TransactionEnvelope::TxV0(TransactionV0Envelope { + // tx: &transaction, + // signatures: vec![].try_into().unwrap(), + // }); + + // let tx_as_bytes = &transaction.to_xdr(Limits::none()).unwrap(); + // // let mut tx_env_as_xdr = tx_env.to_xdr(Limits::none()).unwrap(); + + // match TransactionV0::from_xdr(tx_as_bytes, Limits::none()) { + // Ok(tx) => { + // println!("tx: {:?}", tx); + // } // Err(e) => { - // panic!("Unexpected result: {e}"); + // println!("error: {:?}", e); // } // } + // the test tx from // https://github.com/LedgerHQ/ledger-live/blob/bd5188b5368849cccab74c11cbc64870bd5edbcd/libs/ledgerjs/packages/hw-app-str/tests/Str.test.ts#L47 works. I converted the hex into a byte vec in js: + // const transaction = Buffer.from( + // "7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a979000000020000000020da998b75e42b1f7f85d075c127f5b246df12ad96f010bcf7f76f72b16e57130000006400c5b4a5000000190000000000000000000000010000000000000001000000009541f02746240c1e9f3843d28e56f0a583ecd27502fb0f4a27d4d0922fe064a200000000000000000098968000000000", + // "hex" + // ); + + // console.dir(transaction, {'maxArrayLength': 200}); + + // which is this byte vec here: + + // let mut tx_as_bytes: Vec = [ + // 122, 195, 57, 151, 84, 78, 49, 117, 210, 102, 189, 2, 36, 57, 178, 44, 219, 22, 80, + // 140, 1, 22, 63, 38, 229, 203, 42, 62, 16, 69, 169, 121, 0, 0, 0, 2, 0, 0, 0, 0, 32, + // 218, 153, 139, 117, 228, 43, 31, 127, 133, 208, 117, 193, 39, 245, 178, 70, 223, 18, + // 173, 150, 240, 16, 188, 247, 247, 111, 114, 177, 110, 87, 19, 0, 0, 0, 100, 0, 197, + // 180, 165, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, + // 0, 0, 0, 149, 65, 240, 39, 70, 36, 12, 30, 159, 56, 67, 210, 142, 86, 240, 165, 131, + // 236, 210, 117, 2, 251, 15, 74, 39, 212, 208, 146, 47, 224, 100, 162, 0, 0, 0, 0, 0, 0, + // 0, 0, 0, 152, 150, 128, 0, 0, 0, 0, + // ] + // .to_vec(); + + //this is from js-stellar-base tests somewhere (transaction builder test) AHHH THIS ONE WORKED!!! but also seems to be v1 :thinkin: + // i bet that `signatureBase` is doing something that I need to bake in here somewhere. + + // ah it makes a sig payload... i think that we have that in signer too. i just didnt realize how to put the two things togheter let mut tx_as_bytes: Vec = [ - 122, 195, 57, 151, 84, 78, 49, 117, 210, 102, 189, 2, 36, 57, 178, 44, 219, 22, 80, - 140, 1, 22, 63, 38, 229, 203, 42, 62, 16, 69, 169, 121, 0, 0, 0, 2, 0, 0, 0, 0, 32, - 218, 153, 139, 117, 228, 43, 31, 127, 133, 208, 117, 193, 39, 245, 178, 70, 223, 18, - 173, 150, 240, 16, 188, 247, 247, 111, 114, 177, 110, 87, 19, 0, 0, 0, 100, 0, 197, - 180, 165, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, - 0, 0, 0, 149, 65, 240, 39, 70, 36, 12, 30, 159, 56, 67, 210, 142, 86, 240, 165, 131, - 236, 210, 117, 2, 251, 15, 74, 39, 212, 208, 146, 47, 224, 100, 162, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 152, 150, 128, 0, 0, 0, 0, + 206, 224, 48, 45, 89, 132, 77, 50, 189, 202, 145, 92, 130, 3, 221, 68, 179, 63, 187, + 126, 220, 25, 5, 30, 163, 122, 190, 223, 40, 236, 212, 114, 0, 0, 0, 2, 0, 0, 0, 0, + 137, 155, 40, 64, 237, 86, 54, 197, 109, 220, 95, 20, 178, 57, 117, 247, 159, 27, 162, + 56, 141, 38, 148, 228, 197, 110, 205, 221, 201, 96, 229, 239, 0, 0, 0, 100, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 210, 152, 197, + 129, 87, 17, 101, 145, 233, 50, 9, 233, 92, 122, 91, 255, 18, 27, 46, 29, 63, 97, 139, + 104, 208, 252, 200, 239, 229, 229, 228, 112, 0, 0, 0, 0, 0, 0, 0, 2, 84, 11, 228, 0, 0, + 0, 0, 0, ] .to_vec(); + // from what i can tell, this tx does this: + // max fee: 0.00001 XLM + // tx source: GAQNVGMLOXSCWH37QXIHLQJH6WZENXYSVWLPAEF4673W64VRNZLRHMFM + // send 1xlm + // tx destination:GCKUD4BHIYSAYHU7HBB5FDSW6CSYH3GSOUBPWD2KE7KNBERP4BSKEJDV + + // i feel like i replicated that, but when i try to send the Transaction that is passed into this function, i keep getting back 0x6C24 SW_UNKNOWN_OP Unknown Stellar operation + // i wish i could figure out how to decode that tx into a Transaction, so i could see what the heck it is doing differently + + // i wonder if speculos is using a specific xdr version, or maybe its not speculos but the elf file that i created... somehow + let mut data: Vec = Vec::new(); let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); @@ -151,43 +217,25 @@ where data.insert(0, hd_path_elements_count); data.append(&mut hd_path_to_bytes); + data.append(&mut signature_payload_as_bytes); let buffer_size = 1 + hd_path_elements_count * 4; let chunk_size = APDU_MAX_SIZE - buffer_size; - let chunks = tx_as_bytes.chunks(chunk_size as usize); + let chunks = data.chunks(chunk_size as usize); let chunks_count = chunks.len(); let mut result = Vec::new(); println!("chunks_count: {:?}", chunks_count); - let chunks_hardcoded: Vec> = [ - [ - 3, 128, 0, 0, 44, 128, 0, 0, 148, 128, 0, 0, 0, 122, 195, 57, 151, 84, 78, 49, 117, - 210, 102, 189, 2, 36, 57, 178, 44, 219, 22, 80, 140, 1, 22, 63, 38, 229, 203, 42, - 62, 16, 69, 169, 121, 0, 0, 0, 2, 0, 0, 0, 0, 32, 218, 153, 139, 117, 228, 43, 31, - 127, 133, 208, 117, 193, 39, 245, 178, 70, 223, 18, 173, 150, 240, 16, 188, 247, - 247, 111, 114, 177, 110, 87, 19, 0, 0, 0, 100, 0, 197, 180, 165, 0, 0, 0, 25, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 149, 65, 240, 39, - 70, 36, 12, 30, 159, 56, 67, 210, 142, 86, 240, 165, 131, 236, 210, 117, 2, 251, - 15, 74, 39, 212, 208, 146, 4, - ] - .to_vec(), - [ - 224, 100, 162, 0, 0, 0, 0, 0, 0, 0, 0, 0, 152, 150, 128, 0, 0, 0, 0, - ] - .to_vec(), - ] - .to_vec(); + // notes: + // the first chunk has the hd_path_elements_count and the hd_path at the beginning, before the tx [3, 128...122...47] + // the second chunk has just the end of the tx [224, 100... 0, 0, 0, 0] - for (i, chunk) in chunks_hardcoded.iter().enumerate() { + for (i, chunk) in chunks.enumerate() { let is_first_chunk = i == 0; let is_last_chunk = chunks_count == i + 1; - let mut data = data.clone(); - data.append(&mut chunk.to_vec()); - println!("DATA BEING SENT for i {i}: {data:?}"); - let command = APDUCommand { cla: CLA, ins: SIGN_TX, @@ -204,7 +252,6 @@ where data: chunk.to_vec(), }; - println!("command: {:?}", command); match self.send_command_to_ledger(command).await { Ok(mut r) => { result.append(&mut r); @@ -218,53 +265,6 @@ where Ok(result) } - // // should this take in a transaction or an assembled transaction? - // // i think i need to convert this tranaction to a byte array - // pub async fn sign_transaction( - // &self, - // hd_path: slip10::BIP32Path, - // transaction: Transaction, - // ) -> Result, LedgerError> { - // // TODO: check tx size and make sure it is less than TX_MAX_SIZE - - // // convert the hd_path into bytes to be sent as `data` to the Ledger - // // the first element of the data should be the number of elements in the path - // let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); - // let hd_path_elements_count = hd_path.depth(); - - // hd_path_to_bytes.insert(0, hd_path_elements_count); - - // let buffer_size = 1 + hd_path_elements_count * 4; - - // let chunkSize = APDU_MAX_SIZE - buffer_size; - - // // if (transaction.length <= chunkSize) { - // // // it fits in a single apdu - // // apdus.push(Buffer.concat([buffer, transaction])); - // // } - - // // this is copilot generated: - // // let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); - // // let hd_path_elements_count = hd_path.depth(); - // // hd_path_to_bytes.insert(0, hd_path_elements_count); - - // // let mut data = Vec::new(); - // // data.write_u32::(transaction.len() as u32) - // // .unwrap(); - // // data.extend(transaction); - - // let command = APDUCommand { - // cla: CLA, - // ins: SIGN_TX, - // p1: 0x00, - // p2: 0x00, - // data: vec![], - // }; - - // self.send_command_to_ledger(command).await - - // } - /// The display_and_confirm bool determines if the Ledger will display the public key on its screen and requires user approval to share async fn get_public_key_with_display_flag( &self, diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 77944b377..bdc3f36cd 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -35,8 +35,8 @@ mod test { use serial_test::serial; use stellar_xdr::curr::{ - HostFunction, InvokeContractArgs, Memo, MuxedAccount, Preconditions, SequenceNumber, - StringM, TransactionExt, VecM, + HostFunction, InvokeContractArgs, Memo, MuxedAccount, PaymentOp, Preconditions, + SequenceNumber, StringM, TransactionExt, TransactionV0, TransactionV0Ext, VecM, }; // should MuxedAccount be stellar_strkey::ed25519::MuxedAccount; instead? use tokio::time::sleep; @@ -118,28 +118,90 @@ mod test { let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); + // this transaction came from https://github.com/stellar/rs-stellar-xdr/blob/main/tests/tx_small.rs + // and i am getting a retcode of 27684 which is unknown op + // built this tx with https://laboratory.stellar.org/#xdr-viewer?input=AAAAAgAAAAAg2pmLdeQrH3%2BF0HXBJ%2FWyRt8SrZbwELz3929ysW5XEwAAAGQAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAACVQfAnRiQMHp84Q9KOVvClg%2BzSdQL7D0on1NCSL%2BBkogAAAAAAAAAAAJiWgAAAAAAAAAAA&type=TransactionEnvelope + + let source_account_str = "GAQNVGMLOXSCWH37QXIHLQJH6WZENXYSVWLPAEF4673W64VRNZLRHMFM"; + let source_account_bytes = match stellar_strkey::Strkey::from_string(source_account_str) { + Ok(key) => match key { + stellar_strkey::Strkey::PublicKeyEd25519(p) => p.0, + _ => { + eprintln!("Error decoding public key: {:?}", key); + return; + } + }, + Err(err) => { + eprintln!("Error decoding public key: {}", err); + return; + } + }; + + let destination_account_str = "GCKUD4BHIYSAYHU7HBB5FDSW6CSYH3GSOUBPWD2KE7KNBERP4BSKEJDV"; + let destination_account_bytes = + match stellar_strkey::Strkey::from_string(source_account_str) { + Ok(key) => match key { + stellar_strkey::Strkey::PublicKeyEd25519(p) => p.0, + _ => { + eprintln!("Error decoding public key: {:?}", key); + return; + } + }, + Err(err) => { + eprintln!("Error decoding public key: {}", err); + return; + } + }; + + // let tx_v0 = TransactionV0 { + // source_account_ed25519: Uint256(source_account_bytes), + // fee: 100, + // seq_num: SequenceNumber(1), + // time_bounds: None, + // memo: Memo::Text("Stellar".as_bytes().try_into().unwrap()), + // operations: vec![Operation { + // source_account: Some(MuxedAccount::Ed25519(Uint256(source_account_bytes))), + // body: OperationBody::Payment(PaymentOp { + // destination: MuxedAccount::Ed25519(Uint256(destination_account_bytes)), + // asset: xdr::Asset::Native, + // amount: 100, + // }), + // }] + // .try_into() + // .unwrap(), + // ext: TransactionV0Ext::V0, + // }; + let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256([0; 32])), - fee: 0, + source_account: MuxedAccount::Ed25519(Uint256(source_account_bytes)), // was struggling to create a real account in this way with the G... address + fee: 100, seq_num: SequenceNumber(1), cond: Preconditions::None, memo: Memo::Text("Stellar".as_bytes().try_into().unwrap()), - operations: [].to_vec().try_into().unwrap(), ext: TransactionExt::V0, + operations: [Operation { + source_account: Some(MuxedAccount::Ed25519(Uint256(source_account_bytes))), + body: OperationBody::Payment(PaymentOp { + destination: MuxedAccount::Ed25519(Uint256(destination_account_bytes)), + asset: xdr::Asset::Native, + amount: 100, + }), + }] + .try_into() + .unwrap(), }; - let result = ledger.sign_transaction(path, tx).await; - println!("result: {result:?}"); - // match ledger.sign_transaction(hd_path, transaction).await { - // Ok(config) => { - // assert_eq!(config, vec![0, 5, 0, 3]); - // } - // Err(e) => { - // println!("{e}"); - // assert!(false); - // stop_emulator(&mut emulator).await; - // } - // }; + match ledger.sign_transaction(path, tx).await { + Ok(response) => { + stop_emulator(&mut emulator).await; + assert_eq!( hex::encode(response), "ab5de404a9a28ef6cee7387610d7a4c876f5a4051647eeaf077b909eb77ab309ca6dad4ec127da3537d2663204e4a8f4d0e2163d63af9e9d33471069e1d5c90b"); + } + Err(e) => { + stop_emulator(&mut emulator).await; + println!("{e}"); + assert!(false); + } + }; stop_emulator(&mut emulator).await; } From 5f422ef78fed3ce859df027886d88a45517c7877 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 11 Apr 2024 11:03:32 -0400 Subject: [PATCH 25/72] cleanup --- cmd/crates/stellar-ledger/src/app.rs | 82 ++-------------------------- 1 file changed, 6 insertions(+), 76 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index ffb2eb971..2d8a0beb9 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -130,9 +130,13 @@ where hd_path: slip10::BIP32Path, transaction: Transaction, ) -> Result, LedgerError> { + // TODO: in js-stellar-base signatureBase fn, they update the tx if the envelope type is V0 - do i need to take care of that here? + // i think not because in generated.rs the TransactionSignaturePayload has a note that says "Backwards Compatibility: Use ENVELOPE_TYPE_TX to sign ENVELOPE_TYPE_TX_V0" + let tagged_transaction = TransactionSignaturePayloadTaggedTransaction::Tx(transaction.clone()); - // FIXME + + // TODO: do not hardcode this passphrase let testnet_passphrase = "Test SDF Network ; September 2015"; let network_hash = Hash(Sha256::digest(testnet_passphrase.as_bytes()).into()); @@ -143,73 +147,6 @@ where let mut signature_payload_as_bytes = signature_payload.to_xdr(Limits::none()).unwrap(); - // let tx_env = TransactionEnvelope::TxV0(TransactionV0Envelope { - // tx: &transaction, - // signatures: vec![].try_into().unwrap(), - // }); - - // let tx_as_bytes = &transaction.to_xdr(Limits::none()).unwrap(); - // // let mut tx_env_as_xdr = tx_env.to_xdr(Limits::none()).unwrap(); - - // match TransactionV0::from_xdr(tx_as_bytes, Limits::none()) { - // Ok(tx) => { - // println!("tx: {:?}", tx); - // } - // Err(e) => { - // println!("error: {:?}", e); - // } - // } - - // the test tx from // https://github.com/LedgerHQ/ledger-live/blob/bd5188b5368849cccab74c11cbc64870bd5edbcd/libs/ledgerjs/packages/hw-app-str/tests/Str.test.ts#L47 works. I converted the hex into a byte vec in js: - // const transaction = Buffer.from( - // "7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a979000000020000000020da998b75e42b1f7f85d075c127f5b246df12ad96f010bcf7f76f72b16e57130000006400c5b4a5000000190000000000000000000000010000000000000001000000009541f02746240c1e9f3843d28e56f0a583ecd27502fb0f4a27d4d0922fe064a200000000000000000098968000000000", - // "hex" - // ); - - // console.dir(transaction, {'maxArrayLength': 200}); - - // which is this byte vec here: - - // let mut tx_as_bytes: Vec = [ - // 122, 195, 57, 151, 84, 78, 49, 117, 210, 102, 189, 2, 36, 57, 178, 44, 219, 22, 80, - // 140, 1, 22, 63, 38, 229, 203, 42, 62, 16, 69, 169, 121, 0, 0, 0, 2, 0, 0, 0, 0, 32, - // 218, 153, 139, 117, 228, 43, 31, 127, 133, 208, 117, 193, 39, 245, 178, 70, 223, 18, - // 173, 150, 240, 16, 188, 247, 247, 111, 114, 177, 110, 87, 19, 0, 0, 0, 100, 0, 197, - // 180, 165, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, - // 0, 0, 0, 149, 65, 240, 39, 70, 36, 12, 30, 159, 56, 67, 210, 142, 86, 240, 165, 131, - // 236, 210, 117, 2, 251, 15, 74, 39, 212, 208, 146, 47, 224, 100, 162, 0, 0, 0, 0, 0, 0, - // 0, 0, 0, 152, 150, 128, 0, 0, 0, 0, - // ] - // .to_vec(); - - //this is from js-stellar-base tests somewhere (transaction builder test) AHHH THIS ONE WORKED!!! but also seems to be v1 :thinkin: - // i bet that `signatureBase` is doing something that I need to bake in here somewhere. - - // ah it makes a sig payload... i think that we have that in signer too. i just didnt realize how to put the two things togheter - let mut tx_as_bytes: Vec = [ - 206, 224, 48, 45, 89, 132, 77, 50, 189, 202, 145, 92, 130, 3, 221, 68, 179, 63, 187, - 126, 220, 25, 5, 30, 163, 122, 190, 223, 40, 236, 212, 114, 0, 0, 0, 2, 0, 0, 0, 0, - 137, 155, 40, 64, 237, 86, 54, 197, 109, 220, 95, 20, 178, 57, 117, 247, 159, 27, 162, - 56, 141, 38, 148, 228, 197, 110, 205, 221, 201, 96, 229, 239, 0, 0, 0, 100, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, - 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 210, 152, 197, - 129, 87, 17, 101, 145, 233, 50, 9, 233, 92, 122, 91, 255, 18, 27, 46, 29, 63, 97, 139, - 104, 208, 252, 200, 239, 229, 229, 228, 112, 0, 0, 0, 0, 0, 0, 0, 2, 84, 11, 228, 0, 0, - 0, 0, 0, - ] - .to_vec(); - - // from what i can tell, this tx does this: - // max fee: 0.00001 XLM - // tx source: GAQNVGMLOXSCWH37QXIHLQJH6WZENXYSVWLPAEF4673W64VRNZLRHMFM - // send 1xlm - // tx destination:GCKUD4BHIYSAYHU7HBB5FDSW6CSYH3GSOUBPWD2KE7KNBERP4BSKEJDV - - // i feel like i replicated that, but when i try to send the Transaction that is passed into this function, i keep getting back 0x6C24 SW_UNKNOWN_OP Unknown Stellar operation - // i wish i could figure out how to decode that tx into a Transaction, so i could see what the heck it is doing differently - - // i wonder if speculos is using a specific xdr version, or maybe its not speculos but the elf file that i created... somehow - let mut data: Vec = Vec::new(); let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); @@ -310,14 +247,7 @@ where &self, command: APDUCommand>, ) -> Result, LedgerError> { - let response = self.transport.exchange(&command).await; - println!("SLEEPING for 10..."); - sleep(Duration::from_secs(10)); - // this is when the user is supposed to confirm the transaction on the Ledger - // how do i do this programatically with the emulator? - println!("sleep over, checking the response"); - - match response { + match self.transport.exchange(&command).await { Ok(response) => { tracing::info!( "APDU out: {}\nAPDU ret code: {:x}", From b3a1a1c6bfb8a2305dce1b160b63b5914a10ac3b Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 11 Apr 2024 11:36:15 -0400 Subject: [PATCH 26/72] Up the TransportZemuHttp timeout --- cmd/crates/stellar-ledger/src/transport_zemu_http.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/crates/stellar-ledger/src/transport_zemu_http.rs b/cmd/crates/stellar-ledger/src/transport_zemu_http.rs index e6e9861f3..68ab59cd4 100644 --- a/cmd/crates/stellar-ledger/src/transport_zemu_http.rs +++ b/cmd/crates/stellar-ledger/src/transport_zemu_http.rs @@ -101,7 +101,7 @@ impl Exchange for TransportZemuHttp { let resp: Response = HttpClient::new() .post(&self.url) .headers(headers) - .timeout(Duration::from_secs(5)) + .timeout(Duration::from_secs(10)) .json(&request) .send() .await From 16ca7a57a13562968bf833d670602939b8f770e9 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:05:19 -0400 Subject: [PATCH 27/72] Cleanup --- cmd/crates/stellar-ledger/apps/demoAppS.elf | Bin 371256 -> 0 bytes cmd/crates/stellar-ledger/apps/demoAppX.elf | Bin 631016 -> 0 bytes cmd/crates/stellar-ledger/src/draft-app.rs | 378 -------------------- cmd/crates/stellar-ledger/src/types.rs | 245 ------------- 4 files changed, 623 deletions(-) delete mode 100755 cmd/crates/stellar-ledger/apps/demoAppS.elf delete mode 100755 cmd/crates/stellar-ledger/apps/demoAppX.elf delete mode 100644 cmd/crates/stellar-ledger/src/draft-app.rs delete mode 100644 cmd/crates/stellar-ledger/src/types.rs diff --git a/cmd/crates/stellar-ledger/apps/demoAppS.elf b/cmd/crates/stellar-ledger/apps/demoAppS.elf deleted file mode 100755 index 392fdf673e9d4301824bac5ad83e1516016c327e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 371256 zcmeEui+>c=+5efDy(c7_kiaI{gxSr7*@Vjmkc*&hc5z6!C?HxuunVGYP}@bUUF5ZH zxM)!H-GE>c5KwCyyvEeDVx@0T-@d55%n)sZ(mJu#XlvWCwcRAU+5LUbY=G$7_t*Xb zv!BoGoH=tY&-tF`Jm)#jdCuH>d9S6CdI;(vkKjEn)P&z?el1KS+~`Ft zxKIRs3|aBfotw1aSN_a+wtpo~f7B7s5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP5zrCP z5zrCP5%~8Iuy^n6-1tMIx@?3|H>7S8dC{a!PaNA#3??Zg5?9l1TdP5>A2cf=(V#9I zw8e<}(FjrhK4MU72OGs^KP%G|a|T0V^dTa!GU2m3?I#)aij=+EAe!nU3ytaz(+tmt ziMlpL1V&;J=6L(b9XAuAcK3D~p3Ql~EUszgVkEYNzmHg+-L?4cg}hiR&KFtrhY4OZ zst3|W*V@CYA7iFpE|KX(`lIq6NMmRqhob#335HH_{tD3eNQp9eorwUWDblIiTcHeMCJ5uOADq?lW6+ACB}W1T}VAiOC26Iwh^HS z=_j7KujqWK)%89{AXE){W?V>b*3z#==`2!TD!$xn=~(G%in{78F-wO@T{3J0rp#d% zX!z3*Pz6pmm%XH+>X8J)WGPqd&=CD;N(?;nPZ2FROYyWk*XSrQ*d&UjSvIwrfu(gHU{xOr$pD0}gP#jE|R>#~--?O5YqQ&%&iJhb0$l9_-(vY)(# zCoH3WmNeEg>ZeIs2d$GKBsw#dzIRt6EjL?q)SrqFPq}QEQhv56!g>lN#$%I+r H zvnfvfG-Y)0Vmf{@ZBl%tn~@|>Dl4}BDsRLjd`mV=|MuB%gb^l5tZ*kNe`n-rfk+Q4 z^FaCDH09!5icx)cgj4sYOc))~2$@Et9f*5-c?&;VA;L`bgBmVyy%-LMyZ9MN z^ViiqI@j;_*VzIyw(udVmk(NjE2Dmp$f=Jy8TI`HZQcJ28bxb;tyqol4TF5hSHuTR z=)a#Oiufq%JDs>)*Gbn(jA14hw zr!E``1sjoOPCuj0#%wZs1eEWhZ|012-b{I{zyDGE|F*w#GktomU)?*!wPjtjtJzO_ zd;2-{FG<8pzd&eh@@c^UK8@l|r2p=Jar0mIE@v!`nYWS}gVef+2xjT8dtcnl2#ieQ z4eEnwb+6Bp$JwiL@mag&!*Z5rkK zN2$7u(kx%8(Kbh;O^`m>>se2cEI-4kOF-2mp!cK1(*lS4Z*ZR||7(gqOY5b!?`}z|lgWT_xvB>>K;nbl7 z?dv{-Hi0_Y*AL)Mq+crkn$81b=T z8`9dn5xsJrU)G_9dr`w`)bPjQOuyuzhUNc8zm$$?@2})J3Og!SLTEK7eI9ABFlr!4 zk@k$Kk+w-3ZBy51jeWTPJDl+Qk&B!_+a#=`e9823YH5N~OHe8!GiuQ=!!wW_RK_vt zf&{%XTyS}Uj-OAZM}+~2()Ifb{P_W>N9=w2h(a*YYa^WXj9Z zqf>q;F=}dt4TRg-&i$w%Qqa|PC+DXoSHivhJGtJdP>gE zZ{HlGGT+{fu{%sEcxdGN2hv9M@KA31xDNW%1a08J&=jmP?!$_M(wmOInEs!E^UvmCs_pP!(RNrl zyTcGQDf#sV-#EU9_sNuMM4FuW;%ILz(r; zL5Zk;NHg_B9Z2uMoSilkLUBQiiwTDp?Lau%&7luB50Xe-S#w?VWY1Qq$KJi8lX;d8 z8}MfRAmgTb-`@sVtbBSz`d;nc-Dwa}PUST7i+<*8PvBC;aDaGbN~ES%B9Bx?ERzY( z?PK>dEr!E;F}BF`O35(Y*0cHvc}g>4sUYiPM+`k+q!aum@1;=h_{yw$fTNs))fqUp=D$wM2~3K z>fhNpNhJHmiEPZM{xm_RUn&g}CPrLjYPHB7nkJP+ax3PDlYNHi&Y|wQ# zF}}Pz!i&zQ31iJ* z6rERj?>=QV%OzK-%`Y}viy!wFzeg%O%ID2p%`1zaz*EX?_j&IaU(ceX@hFLSSjiho z$3IBZRbM*(eww&GkA_dyhAa+`=*BwmjkHnmhJ4x+Mqi?{lxemWF;QL&kD(E3io4%5 z6Pc;MNR!&lg-RpqoQB|}s%gH7)3`oTJ|TEbuue&rvolMkUC}p3bX>typ7dEuj{9f# z7Ed!uMbmO6$FxGZJd&rlA~!}#*fowPBOga*vwy=XZjB@B72}0?>nmB=#8r6Vd2BlC zE9Kj6-bQnMZ`;7OE7+eD%{HKfSH=fd1UD2hK~nAz)srQqiGfqw-!wCF)AnJHFT_M9 z0sZ3YH_h2{U1WzQ{a%ZZZG??Fh{5L|uMk&w(Zx61wi348k#B&Ee8sbzbkJ>f7Y2tcJ>O_{`HP`W?wL3CXHk`H6#$WKaM@^fd4u*zk@gAG=*CmSouG8wD7 zt*nb2$_l)`)uDXg;roh8KN}#flIod#OvPmJQQyR>yNq-EM)`8)hq;p^((Vqtwv}nm zkL=jmH$V;%m!tZf-lA%g0cWsNuf` z9MxElBGrzjN!2g+?cTbkw0oeS`UmpgK?i7QdVb&o&&jiG&Zh^W-Ay6lF-VMS4@=aN z!RSejkDlxVUXX7gCRACv8S_Cp{brI%j}uk$5hP_AMm~IviWC7A7DI1J(xMCdL`yK%|CyT2(#qd z%Q(y)=#dT&>gvOq^&gT)@d?av7SkYWV_Ei{v95z ztwL+P$bD^!v_BDK4iSEZvq)^J%kB+@yg>$Q!3ij@CvYqn4EdbF;N#mZ%H!LQ0TWXF zr+2O;PX&tuW{HQ?QnpgMje}GcrCpD29oCR;a2lj^{N)q_4vC&LowkA#AMpQO%8R5u zf0a*d50+>7np_PeSo3FT=k}hOKT2t>$5uN1!KUEvQAR_kE=YMgdJ;Xiy~ksec7(2R zveL@39YKrKrvX;*SGhXS-E2maTyibtFuzXV|(Y1 z+qpN0+p^%0nSHk>@MeH}!w$WL;dijZ1Jq8ktCPwv8)hO#vEZNVXCC36El2x;gYL)* z|B5=g;yP_MX!GO!So=2Nq6kkADOdDtH=y5lm@V%Rk40i#mVmM~gCC<1j1c3`(v#Ua zOvtj}0e`l{myVC5YqI1OPRI}58>D=ejyI>x;#`rUskq1cp4h%07MIP4kB$(*AQ4w> z@XrHj=br{hEBdL8cyeWTXhSGJw6b`I|HEESuzx!X$^YTj4Uo84Ax`caVEFslcfkh_ zV%9Y5<63rXx3~GK%`aeG0jt+Maoy>4rY>B^eHtgr4-9dnq>)DO$uHYF$LN3gJR2ccK(UnxF^~BG@HED+zV_ zv(Flj6W$U&pNnx{%x7pd0fj8In`w=rKd9B!s9K=G+0jGcMHJ`zJ5A4B%L`%S0fxui z+krW^f;_Sb_86b*Xt^8x^bmAySBTE?F9N5!m@ywLeMJ2b2H1zBjcsFsoiNTHp2m zW_`E(llo5g8jjAI<5(K4BvriV5oe&@r%>x<+Z?m#=(-wNxZkAY)Hl!anX?wUeqIUFe}lreh1^J+Wmvhx@zW3Horol zW1D^63!UDV?u2#UEU}s;F}syj*Cca!pTc&q>Z)XR415}MRT1YJq<|!Mi6s z;|0ePbu!jWQ)CxUT>LXOJXhkm0?+*M2~RWSM9ssq6a8DBWHpN&E#H%z&>^@C>d#X- z9X#%DrdZ8_XOa2imfAY>qe1OVar{gmLDceLwfm2q4|piu-&IP*iI5KrZ(@axTGaRM z5FIz|N{z@@G6(n$5)(QY*QI##nfB%(Md%=^GL+LHHZ(=W`W(Ec`A*>Ny)vmH2bqIH zN6yoxavFOEv3JVC3v&_v=@4_kf$|s^kI`I%nAh?4CYhG^@{j_ElcwABrc%{C~Dv(}V)g;j=R!8pT?Y=9fFuhpU_C3)>Tx;DWc$_;P3Agbo?;nCM%iS4b?NwSX!ql1(lXoqIfG|Nei6tSIQflcb8c^4C?lg z3BZr2ozMopE*p3zX6^75k=O+AksJL+VCyBqN_hhM{nbHFX_wP{_(|kU#|wwN+N${U zNVMx~Xwys1iAOIHCmd-hqa1w0h*z8&U=O$@bL%O@5YLY#o15E5D$OxikXz<~rwr;} zhK(^p3)fD=)`Yj%3ysO#%7jgAK4(Lj?eZ7uj7JPdl{2j3qnz)QZuYq}nR8kL)AW*;1gap$vOH>mFKnlTQe&g#0A1nlLRyf#hpr+FP*C(9=bA zR)$Z<&!y@3*waZWG3#RoCigJ4BkocxF~cKEmgcmYAKBzYkNVh0IGN7XM7T>r+G8=bvcy0>bU z4>D?sMVf?_)<24TSm|ymBf{V10%ewHI?@fDhE&@n%VX(l%umit z6P-oOwBA1KdQ{F*%vk4o`yTN#k4@`+qS#2OKEk})<)19?fVG_!yZn~Jqz_}Swb@_V z_praD&wNxZYW7d;+vFH>gdNBH`F)T3?R|&+IlY#{Px`ZZANN~(v5VEqwo>c6p_K_1 zqW77{1ks8;hj>k7^uIU_U zG}IpPbOY5N9rZmCPB5QigmJK&mCJ?othy1lCxcKdS%eZf56`RdoV%34ZW{~hGyShl zX4f}ugMLU?Pj@mqP6g{WG;KJwf!T$e$Z;8R7}1BrDabs`t$h~4zr+##Ug(-857)tBo9>&o-7+YHqO5{uOoQ(Y=ImD2iC!_db)6Z#gD>?Q3EQoeu3 zj?yf|Z%fd4Tc3IHN=IPZ-8H25?(*?lca%~J#^YV4ZdyLAKir+JF6}E6b1w}UNUq0nF301Pay?UJrh0~)jt9~+6}zWK zn&7FBoefzLrh?Se$hPvVu&MmPE%#KD-h14OqoJ6$gT&9e#ZwhUyZ3^-(|5{RQ zmNr$77fIiy@=fytD>qf!&+aM9_k1q1d@BAPESn~UgYH2pSS1}_nPy;Nge?&*r9D!c zI^PCrYm34LIX^v4V0+WBgq}&8>n#mT&{AIkUE%03fjry9R^}HwD-NiRD>eVR^YP>5=#|Uk67;U{W1#}{e`fbcuwer2Z z$u~_>KwT*;*>h4x(KwBjk9`Mv6{t0+55TKoyhdNG=o~}A(zFv}fl=$zFLnuHmSU@S zR_+cHS6!I4h0-`bO?6Y0nGT<%p>+ICgfjd{dF51NW}O)BhQt!eqzT|j5-~$_V4<5+ z>{Mv&`4ap;hQ)x=d>_^!wZ-@FgsBfa{v8hb+~apQn5;RQ+=~N+4gq#3LQs}aDP@VQ zn7@?!nPma2kg&UL2s#3!hL!jrS_k=58N4Z&b=6%?Z{RV^9Hjilpv7bDH7{KnV9L$Q zo`vP|u$d3MZ*C357KKmJ{-nMD&piF~)7M>l?X@f<2yNfnsno!OS5o=h)R}t~Q!y+2 zqLZ!@?{-pLo&b{J@TJA~7Y#Y7g}puXa(Jra`Jykvj2cc6*Nay#da*4DB7zg_$&s< z{b_f~h8E*KMtuN0XOXCe4=z#vGsW;kB}0E5I2Lk6tRC)B%WrHRLwk9t#s|8e^r0Y281%;1L{}0+%Y+J6w=z#L9Zf zz>3KCJ-HH#wKG;cQedDZawcL65b5#(GjH~#SG9 zqAb=$>l*Hgz?PabF!FJ***7WmF_m4tiFEuYDFZ^kNU->SJ8@nfjg@ITvFA^~o5c$W z2_XFE0JS$8#N1JgZXd~7cD^_69T6ghf(2{4CLeqXu&yIhFe>=Ylj(Tr2(=Dij|lw9 zhk5drKf~{2pHW=A^l^VKwCMV!KCDY?AuB9{q}>O(Jp=nn^YCmPtXp~vD|*{GB21GQ zVY93_4X$*2UBU<1;Z;~+3Oq}`F7w;^wVkFpSJj5d;umpnyAZhqUwxFQVya8k&~MmdT3?Lz9;j!3yfa+5@)N z*yN?Rl+Lf33(Pt7sx-gY$R7)rgD$Jh6(*rzxU|6%A~q##3GrL3Av?pDX zO(@&v#2x6y7SXnyraSb`E{zWBP&KnFOiS*HWR}Dsij`sr&+}0` z!)LYGO0)LmhJG?|i^qCyqbK)VJ?ef+S&f`k{S415cX%eA3nA{P681PGXa9`|A5k{p z{e=FkR;Dzog@jH`^g9;&PUT|#5wlGmr)))1z0sfYA!R1-NIgwag)>3C{kNMfYJIaXXFOvBnsWQN#XnX{F?=6g? z2ACLk$lU5HCLYsat8})DRCztKM1!1te5%_PW~xkwo65#{vij=EtWtJIwCKGqPBo;= ztzHKM>%3S-rf}Flo$6}3_AV!xS}ZLPscz3o!K?3u9rp(O+q6DEJ4|;s*CitBvX3`k zOZTTZ^_RnUIiK|##Xp_39R419x=PGGZf&I;`^%B~orkwKjQj~aT>`3mz|rQ`PJiy%-6a;R(n#+mS}HnXmW(`i}s>}7;}f=j|BTu#X4xafSb54ymj1hvGPhRIa; zC^XPrgLCi&6JUYw5$9?$>JV%z6B`)S4v#@j{d|Z_bIaqAl2NTgH18)v#sg&PM667w zi>3}^n2WIogmPxTxr4-py7IIgl;S~}w-;3*ud%;m+2_#IW9_kOtqLvQ^ z`7rfv%+&q9wx?`svBvCT5lVP389ch-@LY{c-bsE;d;6eQG#{Y)r!?|AJX;>6Z`1KN zM{azKR80$i&|rmj^3tFgx%UroYRi!A&{02s*3@b}Xl_egZG6OVVB=$4P0=}~=F)Sd z=5mqhJ4_ArHJdKRQDQp&Y6OJ{c}48Pz*W6h#-1})DC#fUtm z{eRSQJTP)Azl(U+_^_*-*Ka#8vtzl7Q@1CxU>D)kZzsKLULQ*-WmC!h{=t2pqa=wJMqa|E7T0(Ar+iYLi&aRoyF4yRrA(r%;Gdje!=S4oO`sKxybtjEbz3YQ~M)jrWmO*wch6^ z@4v6u+~$2`GUbT*NHufF7PGdP+i7^&XlpI4RPxytoUF4Qwm$aZ8B1GpQ4T!X$yr-} zVgGAd-QP|=G}_0L&gSuj{Z{urrfVIk_DGib(23s9>Sx^3M8jj%;d5O#I$zfEtWUzq zeBhaSmuP7>9wO>Ji6JGQk6+-BhD3(Pu1ruK%j(PO@Vc0&4BqYt$IJOL>a_ZKELG(Ev$69)C{mfW}l0PNy?97g= z=jgatpU7V@&Ba{nDI!HaXMw^lAmkzFr*s_8?0^j=9Bp8t`L67e-A*r7E>zBcHY^ln z9p}_560}bjLK1mJBfP;m^<(&3QEsK);M68|zrw`?!z8JrD;GIg^(W~s`-9Jg|B(&; z^*Ne1_)N_k+@N`bGqR->`-boa=hSD1xz>%!I6kOkWX@Cb33(XT0$c^Srh+r>a#{`= zF=L$X?T_C3a$gtWl*0MG{@Ng4TrECOCr|V_>U~}>`uJMwtb6M2a*iME_upLT_mZ<2 zYwHYHTW4z4*1MdYnzc0t)>z7s|JVD>xHT%Z=r;>2?&*?_k zL!=QeiT<-FGc&w#m_vy6!}ej?4^epK z<5Yy!4{og=xY)&>SU&*GRbD%Az9)vh;+e1YL>u_LuYSNK+K$YDZI1E)eC>&M8X?!r zA>`6a3Hil5LMGsTC+<#MU-J=iBkuNYQqBbfLAHYW1(0f1+7V@fPW1UpDQe}(60+ex zJn-Ew(@?KbMdBf4p#+R3Ka@&(dN!VV-0VF2(9)nOv>Kem*b!SLZjxFG@YLzSy{RBi-}cxt-K>ZKT@j%b_6V zu%n6(b56J7-FTY+e6uC^dFj;Jzm|Gtka9sRg$yqmgbbGsE8QFZz8PK|kmr9>zS!U+ zjA*MhxmyC8#XB}^7FQKNg;g{4ss7zyWYp8^i{N*iLZ~z7Bi;=@=WckSQ<>8{nC5+? zw$b9Na~}7<*jUon70NReN}U^4l)c&2;QVXp)ERdy*}kwak_*pC$GTuxusYW9;qPKM zWTlh9=b^Kaz#okf)y*X2BTcZ&ysW4pPE8G}8`Z|{&9s(dgQTK%b9VUlvW~9Kjg;yi zC`gY{@B;49t`|46>RlsO20q`sWXuVb+RgOL&z8+}Bpgy+-2AQ0je)<1=qVK<|0mA; zY~4()Ko2Nt;{(d4+fSQI;TJIp$bGTlE7(;CVmajTcF5y$_+zlOu&GP)8=CzN*;0|G z(DM#fcvFnT9~${|`yQsD>+QUNZw_L4@x{&Q_?1Hh*3sl7V}Q1o zS}fJlsa{UKe@tsVgJav;FVU9%7XO)cvj3lUzI}GIm8DZJq3xUq`Ab_lURE2&E4dda zN)C1pGCG$tNwVe5xXyD><8l%$Dwrm7FY;boHZa*$H|!zgV12gBm-n4 z_t=kRRxwo)#atdfTbsUP;34Km*BF)P_sRd_QJ zzb;8VRrn9Ba~YwakLLQI%ImYaqC&20ZprDe0K>f_W;b?jN9&v*z9Cya%&9Vci2aEV zEyzIxMlv5s^&9sY4q7_qp|Adk6DY)Fim~d+gbDNz=`Z~>Wm@%K6rx66)oO{E;CAbE#>1)v6nZ|GJHa#e7RUZ+tN|^jJa(F zYLn4>xud0-r8j_x35a}6Vb$*>x(3WuD-~1Ka`;8&SFysf||d9$0#*_0W}lGP$O#8_>*R8TLI-6nPNIZq%S_6t6@m#oQY7+_9pcHwEiA5 zCxhxe*qc{qOI9b$?aMc{Y+}_v4Aa)oHkN3mPDC4jMbiT9AIOXND7oA*S8Vk*yNu|K z#7GlwKFGzq!AuG}QWidAY%{ly#Iw-Ki`@A~^hF-}A|I`@E9R;k#jNdrS~1^qlCjkk z!AepZSA|go+ZF)=PYu&kWqqLUJnCmOT7LnX^+Os(v{k1O_u6*pLVJvOHpWKc*g0=M zvwa`4vg=IV3g2=c+81mIvg+Lz+V_lB+V?dJ_)VjINTj-fTlt4vQL=~3b$rJG&!^I6 z7mJqOH)1@2ujWWjt%D(v6+?oyCaiUATVuWj@9us4!sTCPevmDfn8|8ok z=^;=v9;j!i=@Dst@OT70HqgULvw?bs8uJ+BKf$WrsZo;_CTg@4XtbR9SnCUy8C=$g z_mrD%dZ)*1rD^wY@C9O_gW9Upt|CZ4%2!i{VLNCswi>qHRzKHqdNLE62kI4gI{k1c zt7;<8wfTy%H`~@+N6zf}m}|v((SS?Bx`pZ$@^-DhH1slB)){EK*d^EEe1aL=eJjG} zCE86NGoY)-JV~Ra9DPfS^sg5dA^qGa>ab44sS-{(vTTD z`=s~Mk53Di^bz4oso+sdN5RvioM{`0AJNcw`=f%^(}K`f*g;~Ln@VF29 z59odwRvYNqKc%A7cNJU?TWURXJRMilhEBR?KbIlFD7;4C#}s4h8)2>bU7E$41uTuf zuakMk{5pK?k>l5C6XNgXE~e+S^lzuB7YQxv>Z2rR?{4n29fl=qKhAtmKN~IYiL?bC zv9DZ1^X}?|56Wm=&uLU7JHA@x;}^>^JQq?tn|9s-;~8@SdM*X0&28ZA3RL+nd;N=T`5-N z_)`qS2RN=i2@erkAGuTNsW;PolVseg`BNleW1)T($@n$tp5{#Gsx%2v--=|sA#DgH z<6`=?W*clhsrY1^h=Om7Y^qPiFF}}5r=($Hm#BC0w{XUQ+DuQw2L%2Oq5)~XhWV6{ zjPSjt9`*2r!8^JKJxPSN)q->P&lBuv7uMIJ?79)78+%Xhz)L{>w96Z5_80ZSi==g2 zd-fsnD6^m4$Hi>*2H2Zu-KXF!XX>Sz15N{DzXLvj3Vga0&TSVn{-(4pwgEoHLHJK$ z&+8pLOStSflgzqK!JnZiOez+JuZAxH-7zeLFM%C2W_%cSblxtKiYZ$-{u7)d?g-x& zz8ZF1BX*|Kae{9>d=jQ{(~y(K#9PdwGyJLpn z?c%dr8n-aKu*Kl|G;FMTp4J49Yo^+Gj#OjDtG2;U!KAR&oWxZd;Cn)S&~VCYOGtcu ziv@I_NTHpmo3@R%k$QrS6Y*t>iiKOS7rf=av7g=)UKpl6I3}cjJSCWzcX2LT!FK^d z_qq#xjQeSyEx-n88#+&($7yUi(SK6TJ^!?vmH_jWaxAC~`;`h;&%(=+@iBa}&+Z%B zA3HijBC~}hULUaNVjY@;cH#RVs%I!i$%M`fTSr43!v-aa zzNT&dk=AB<5`x`FYe>$B50LrBb>(s9}0JpgAI^r zW7}I7SuVNP$xR=Sr=y0w-llJGo@$v5)rB5_f8feuHuwOv4IB27(06S_ojFpXXt-(n z5k!3(3cbTj-R&MU_ThIDRlnq0- zHWUrrxC4|DRZdPWx}^ zj;-l|R&18$p?=z)2hO)=_Qm#`O3vL5tyu=j{M@Y>4!Ov^G2VwBc5NJ6_9ZCWEFD`z zd1V7ybe@lCnUu%ECz(5`j&=UZl*X^)ghF|2Ie93@f^vF*5k2uU|4Y<;rj^8c%zbYX zpW{l`9^zX~{XjD1M!OeB4Aqw3n5%Qo8R4gBsxFb?F*FE2TKI-C)e|8L^AS@sRG?TO zC9Tz2c$~`b{#x*JTeLOgt8S=b&+JQo+RZ~^E3u+W91SB zs}rF0)-d}2KbYgOMKy{KJ>!vF|3N&Hq@K-X3JsZM8jBr)TPJAG<1la15hNt=)e%pX zWbjl=M$Z>0H`@5?0h;#D61kNqQCO%B4>O>-c@h0*o9WE(Ni*%I9pHPG#ziSV?N3nI z2Pv=I=fl+~sU0Df!YhjB*rsYdP zoa-sZ*Ey&ZcxmM3ARRl9TO%y2Gntk@gF1<8Ly$$D%-dsHoolhkPOO83r*cr(#tP&5 zdSL9za4VJdQS|yRhv=yHKn~N=Mzq`%p(GDF;D-w@FFAy=oEkNh_W!G-r@vHEA4LKo(m;t+@baBOT!%6Nn7>>MgVOa)ej7!b=$p==N7@xn|b@u z&{o)z#`jf6g5~3Vd`*u07I{LOo6fAJzOXU0boH=`ncoS{o-`puvH50sjP*8w^gx(;lSfb8?l<7$NYP#`vHq+7lEn26*)i&=fV; zPom?*7GcASBmVFMk+-$;qyDIE+Y;?$?T+XJ+unpf*B-~IXn2tw+z^E}vNA#7_mz%c zm7u3-cXrZKBy0yXxhT_N2wNjT;Axk5VUCP15s9{WtY=0qE0jna{1h|u_sk)-LQuky zEMYwMZE_?NPIRgfQsDw>Zo!S&e1gnsXLS}siZRC-cMh!T^c2rHB@-_UGUBDpYIA1X zUy4&NL~R|yKE>$FZxDC5xGG=e4D6r6|6rKaX4uTrYr}N5rKcOh*t?^;0+r#67yyTx z7w4~oepAg*%|x-5XGIZ~sUNE5Zw$Titx|$LlK-97{ZG<bjwD#YB;#un(7-yj zaP=eqVK;#c6bL8eJa@pQnohU0)kR*NYlmS@f*oKc(wll$Fn0qRc3E@0Af^81sA% z&pBhBrLR2y0?)QF?|=4{_s@Uj`3RopjY;3&q}bacE4#37`A#~13@g9Ik#FN%h^aa* zFK#Ya((eD8{8n>5yeRYC`8en3R32zHRGSbdAWm@SATA$qMlEh1;`X_1h_fKhT>UQ6 zbs?_HZ9*JT*TA0gXAXOPsCOM`xeoVta8Vh$3_9M#daB(_NlbuG;Fr%ZkSQgS9bS+0 zy)%(ZOM}is`E%y*IPA@2H2Tmm!`tvgwfl|e({%iH$U~fqi7&t%zVP5p$`h}Z5HbaP zcO5RQdoJMr6Fl3-JpUQbo$&Md!o`*cmhaf`+6G4aqV{JuGMiyN>$8dbyGSMd9zi<( z#t`*DqG|v4yB})ruuoh;={k)OXvdo43zxy|UryuP*e`?6&V_j6g?N1RLyM&n#3bHa zZ!YEC)X$PppB%n%sjKlAd?^{Jc@d{LU3CE--$O{nAJhC!jw_HikM@L8@dGI)hIEtZ zK7^|gd-m7^XkKJe=EC#EJSJZgtN_Pw2DqUNmh3a-_%a1jJLySgC-h!}meMIYv^s80 znMKkbj`#%YIbLv`vtuV2%ThT69p^TAg%TC`b9LBR_6xVjjg62Mo?GQlL2F1R({Gk& z9XCo(KpziELx^23&uy$#T*3^{C9${W7Vd!Ll** z9h@ze+zqFrbCgW^^KlYrou@HI76iOweY-9dn z`$l>`-XxJ`x!LNFqbv4UikCMDC_PKExNM7Cc1lr`5)3^a@|EF>KhQ(X%7-D0l~zuI z&8k$uOiraKJ!6`Ui>{}PV%^pA8ec}e7kg%|hMF|OR1o2tl1ZFbzuRB%8~Q&%nv4Hp z?SG@@XMCOX5_;{G5k?#iX*$Q?wMuQnLw<|2+rL8I7J6Chp(SU0P7b@+1bzd)vX_I& zIG3iEu#8lEw#$H1CtSMYW+KeRSBGY6Umcn`=4(h(&!po21wQe}sd!C_oiVIvUqiAeVbEZdQt=I-xHHteh+y}Y&KDF9m{_B@d<-rO`zY?(d0fiRIDr&M4XcFVN^Ta4sdq>YfzjAIO?23s^%}dXW?DWn}bepJ{|p+3?UY0z(`xY zlan5XZ!~c|9-M+xYtyf`@{v`Wc+NEsUkhVem0;Bdwv`V~fpndMvQKQR!T-D)BR4L< zchIPgnTr2@#DE>mRQ$G75EyPq(JQr!#@vV)a+nBHfnbgFvUY}haPPeJneU3F;*)^i zB;fWSJnAWaFXBE?KCyXN`_kF`F<&|x-h1N0m(GZKa&Qn|Ivd<|0$)1&wM@CEN2FL$ z9{Mii(!;oJN~P06F(E&Iue`n6w3PZunxLUoczJm2A0A|QKI|*AYkb$)ouj>{-;%@^ zW=#01L6@(rj@f`O=)JY8^W@W&CZ&R}gzlZmeb3-cVS=*(R36egsm7874)9vScObd? zOsdVW&}H6-yQc#`qFz4&JpdX+X>v;Co{HZ(l8k>Hetpz4JQd$GVyO4s zPIo4vI3q*brJWJLo#F`JZ}k*(ermh??~y3hCDd;Q`a9-y+NK{Qrc_S3(59LTZJPH_ z+EhN;rvDsmQ#bDa`s{u(zUQB|$^NA_F=*4@N6*lw;=@V$bu0^JJ%Vq>-UrW;M)W(+ zv#w;^n+lKi4{ay)SHGc%+6y!7S%UU>F0@Cu&>s21w*fBp(T}mCM*E0;WvVXHZ zu7BE|abIdrTQV7c=t6t;B~A6aa3ZUbuHdv*{CwnMUuDj|nYDhYOfo!S*Sgrn%U7<( z>H54?@XtqI{&FIL4@Fbw8k|hYUbmVbw~n45dCHGfZ7ux_o(ziy?o-EZk#?cTX= zO@(3olYZ{Z_0FBEh}*x)uH}71%eR`hG+5THb?;qg2;jUmN?~9trM&feB3QPoE@LOA zb%l{Wh%4OvaOdi({AJIqXP%wm$^+F{dB)#@MVY9_6KgAk^_pd{Ot>~nE9Yp!rgR|A zjPc!ZZ3dqMi8L=JRHA{Jn zw`ytiIi)-1ij#5 zSJ87EHR8+2^_tAJ1>t=kTxC7_(Ry~O9cyHw&Q6``%Jby)nOoiK$bq0^OYt>M>O+)_ zznl7KJ@s9O&X`iruiwKr*RCHrGC=$B>gxzuz7l(r%LzF!fe;h$ei!$JxcYF(xagVT zu#2c|BkB0($!O+#gG-jqG`|j4X_P@_oe`D}JHF>-1zIL;rF;KK{wy<|#%WJce6!kx zFWY@l!474IO(Gq?KgB5N_~n@SSnV{OT_F|l zcECzE8DF2c$MthkS7fYj!VYRC?wW+Jh;2B1BmJ)5%UOg_xlG4zI{sUng!`IIXYG|& zn=Z`SE6|@SK;?;z^#A-!O?3o1!{iKS=IVpPbgq7Im|mH=`XR*7xq3V1>TTLweV258 zuHFJ&rvZF@J7in;P)5eRkNZvka;{FpduQxiy%cRrbwRRLT#&34!~c8s%R#MF zvfe#f>oMH_^-PhDKbySxHN}gwpY=}F?6x%jth|AlgnHg4`1O2V7a48L8AnE#({m)n2j9!1$n4atPfeXOwLZOj z5ca5KI?m(k7n!j)Wz5*CK^m$be_2aDKlYZ7V$nB9vH1BQy)sz*5-}7DFUH<9ZS2)t z7<*M1dk@04@0Lg6@pJ}@QeaX1Z;U<9m$AUuJ2*lSp)%yweE3xYi%Ze340L2*0-F!T zg^`l+yYRh>9`XD+RC*qsxC534{de|q)(8Dx@8xU{`WNTrW7E(7XJB}*FhC>Fj@{R{Gb*NXyGMVm{N`$TKILe1?h>FUJE~{g=rg^ z-X&VNu9%!*#Fce+F}x@}z(6k~s%ek~F2oa;?ivU$qPDsgtjc1(!y&&Y)j!u@IV@9nIY%Zu?UHS%YKqtKGj47m4^XIB65- zXCD^F8%nZENe6xR zIp*+x40;F}3(@iU;LPGyk)MA^ocYdL@fvZz&o53Q^j=0@@L$@8lW=lGxnBHk$;moW zL*DzALfoVry25^@d@WMZyB~M5Ur=`Ve|h%&TX-wfFp@L4K1krJgSFtRKNh>h4Yz@B9HnG75~;ZpUe#We4s}h9PRTKu~nq~ zLe$F#&FFKMWRZHY4pQxlR7LSab(TYONiCt+W%_gwed=^EE`!lk_FhAzQO^>2 zXuJh0V0x!Gk9bkb>*?#ol46WOaM!KxtQJ>@O}?O*Md;l|evI>1cPRLmw<)xoH6ra( zjHmbRREP_!y(-!hYs80r%)t#JQ}wV9W8)z4d|f(gzRr>p___tL;Kgj^1@Of8{I)*I zqqOgtdEa%cM@c5>1;2WhnWyoC=ga)C?EPsl3;fpi|JZx)_$sROfBc+jcW!P;CAkS9 z+~lSa0wfS1C_*Sw6p${K2!RBMQX*Y(rRdtQp{}}CWbL}FyLQ)tWo>K0vWkkzy7soX z7Hq5E_xqWd6hHcD-|s)a--`=#=4t0S&v{OtIdd{^%&rF1k=`?2t5cVuG@Hj<>(>Kw z_bnrT^9mYCF>4`q7b{F9CD&bR^V~7xP)nwaITvO93ZDgSR=-6qN44j2=b1R)6MB#v zFjNwjK1j9_vT6qsZy6Tl&fqtcg#x-)v9Pz zVCxO9l0Q&gfD~Vc&Amtc?md(-<`iK(+W@qu$iJs>k6;a$sqm;^Pd}f!0QVX^v(-bk z%9B5Px7n!cVvRSA{A$On?lt$RRL1T2GXJ@Z`<^=Tk?DHC*U*k}`ISwHi$|I-s8ne8 zrXx?9uJ`-y`9R{5k>5V|^?Rn;m^dHt+Q#O$=>gxow`co^F$bYfIdaV6F{{R$xV^G| z&*NAvj?Jh!;qi0PCv6>b)0o@GxQ~oEU-$0W}J#-}D@1C1g z)CTT|1V*wQ1$gVGpG#oBq8{)StV=`5{J8f4)as_@=4G=Nw5*u4a`E^TEsK|{fIuu+ z*4(eGS&e{9%kt%Fa?^^1ihp=yDxM+t!}MxibIbe%3tNvqX3^p$OWTfJwtU6PRjbz= zcYOZ=bprEV-;@Rcp&?oPi~+Zas<= zW4ORLZyLu$1iui7QRVST3wZ8&{H>D(JGPUb_yv9=_(2@R zd_c%}z6L>FwBaeld$Cho>WHon?J<9N@W(yF_n1$q0ozf($l6fs^X49gTEoyAj^5lfp#jPs@n!K!KaqG&( z0*`5FNsel2QxjWOEL_^GrY~z+vV4BaGL+4-CX}9cOZGv@@sqVlOOq|jmMvXIVDZvr zEy?Dl6-{dVlGYWiO^aHOZ)r}pEN)wITyoyhB`aFitWeFZ%i9(;9XGqRdClylD_6{3 zI)642SkThWXlq)~(v>q~Nz0m+ww8G-psW>ImMv*oJb`fVd{Ps%6>o)}>35tzN|CE8E(ZE<>W7<(a$W zisRZ^IujRrp6aA_?#lUaSrV18v)n52d?&FbD;Lj&*wXpQR%scXSo0Rb-N^E?<()Om zZ*5uB?3G{=-eXM9d*hctd24f$Z3_)4Ie+Q0#Z4=e@AYKcvX=R+YnZcEPq3Y{vcnh4 zQC#hGUoJ^ImaC~P$F6K$2LFoz@GC3QPl)YaSk^Lc>4GI**|Tb(?3-KEbU1B4sWd;S zudLwV;!Le{Nr`f{{210(>9E;NmH((#y1!}I;D)VsWV5^7P9LI-M24;E?{MpG2yYp? z>EByco!0G+7Nv_+A?R<;d41>U2R=68vgcbazoPk!E7AI$KdIGf<)j_ke~s%mxURu< zJFdUr`WvoyaAlbj)igAy=@{v2@NYE^t;0{-XmzIn2;r(yqg9Jqh3#7Na38Ohs1>*` zQ}b1mP{yhmppI6{A&bWF%y!Rr5+&1+%ZfvO&M z4MD23f;No=>_Oz!f;IwoQv1Nx!H6*$F=`PGl(+{1_AFNFqX|2<_n5e2`=Pi8wjBq2 zGp+#V{mVmkY!5Db@{aAVgC9n@#Bc?2nSgtB!hbGamd-%Os2VP+&dkt;HcV}qVJmJ< zKuI;L1yWvda}r8y3C=D|qRi7wuOw>mNFP6KiEjneKlLf8sINwYg6i%4!F8u6`tA2s+jUJ&9any}w&XwdB#7|rA;h~Xng;~}w^6iYcoFnN;ceRbQP%@4g z(SMB3v6H`0aU8n;6Mjh?bD>}6ds==zTsXU_t!>%TRV`}a>}4%SV~hZR5d>3!*{v9Z zIxvg@pv+s=(zKkyzD#Foa?&9kjGq^6ZuvO{0kw;QcH;QvHSF~`to7-m*w5pJo*g62 z?5271TH00wn4bakwDXQh`YdLPQ<&K++c5OB%$D(|PjYO>bn1WuCe4^IL3#Y7ml~&3 z%NH(P?I(a6lf#(pX_e7NO+E0S14m8wD#}iZ;cw=vZ`g#(l(46bC26k9lmr)bJLo> z^A@TJt@B!zEXQ!5Mzl4}TiBB958){*rM&$7&Rw};1w6E?w=Qm5)V02Rv0%sc``UJF=X1MazaiXrl-IA2_b7P%JMn_piC?>oe?kL}Q@kML}%=G+c+rqLh!LH@Jfe>JDIInhMF!EuYfpxvYQ65P%_ z4)>qqd!tM!iMb~FC9jw1h|hk5ehd{WwELgu)0cR2qKbZje<=9>rTzG$6F+aC13Ea# z|H3}-|5N*p4eY~yZ~OxPq^<9-Xdh;(O>-Av`nT`5&jb5Bu+IbgJh0CL`#iAE1N%I% z&jb5Bu+IbgJh0CL`#iAE1N%I%&jb5Bu+IbgJh0CL`#iAE1N%I%&jb5Bu+IbgJh0CL z`#iAE1Ha&bd5f^`wSCiREw;e)tw~lcT(P2U`F{QSEofb_aOK>-^Oi2|w`kESFX)4< zPTb?vFFAL};QFQ^0|pHkICo%uUGv-l^)>wm*EJ8GS3hLXfO&%l45=S7q^_>1wwJ;O z0r*UwrqAMM77}l9O%gWK+be@^e1-jhZg?0hAwW;O? zoD$QXgJ2KY|IPi#!X54Vn*VR~(5Rn_zwm#ke}yl<-oKyuKaBrNcKpAhU;GR9T>T65 z|5xe$|4q*={~z_&y|5dfzqbEygb~{1pHB_i<-dmQ*v@YQg5N;^xVaR3X~2ME&@s82Hf-h~OvB$LMx(`P!%C$qYYv4L0(P~t3^0u_OC4?; zrV+*v+cUyB2iQ``nF;3-fQtbdz3tf&Gjt@*%wCU6H!|B+LSz4~(k|q18axU4kW=B+ z2=ND0wNBgUK}Ud^w#kDR5Iz8L@CR4ZK}x%b^b&nC=~sHNs6jWWRAdgw7J1r|pv})a<)$p{AVG4#vmlVuSDVUlmMDL`=O#Lg|RD>F0kC4(|>-eMr1eiknIibCd zFuq&BZwbC5APq~uY6N75Qy?S#6OtRKD9`M$oWV4 z^4;En9-7=i83CDRLBUTl#s+H*jAj1{bjjE?0GP2n$<$N8BvW4iXwTF*@Fi1E!9S9z zO8_uac?}4%zW^pVTLwUKHUNO+EZq)rrtl{u=xa2o(tx7NL?%`|uwYh) z1s*6C%n}P8q2v*4l;NEO8+SmP!ru)DDnr&AAlm~-*5Rf70N0C1h~7HErsoTo_mklH zAK?he^GQt6?e8ch4)oAjKqcjqm~!++puc0xlLsKo-iZ`*jLp_ul1{0DU%OpP@Y$=a z9ow_l(71z(z4+55K+$FyCF-zD1~Xb=bU&DVHi>!yrqL{_gAuG({zSy_5(_PWa&2U}-y)83gRq3RJk@WH9qUcfC0ToARf6E zQaf-(qo~4nOaXo1!OUGO3FpKQLNn7iog0F=P}P7dIy9||U?dsdvHc)i(W|>^P?1@o z9oskJiuMmU)uKD{1;mTez}WK>#)pv3!O3&cSplQgi*W{|Z)1$yAa$Q{9;EMOj1z+x z!@U?Ak;ct5>&dPR6{b_p6~~-S$BfgXkzt)S7n;Q}Q(}2!k_Q#Q}(iuZV>CVd; zt4Zq&_ItW%35qH01xT~s^HB?5R4l7+>f~5f4HtG=KgqJ%=M-w;2d>=OEZ0Zn693RpXYl>k=;hcL-XCtG|5kuYJFU=xf~P9g$KbWSuq@a-Iub3R#DAmGF%l*kHw)n-Y9ZEjz_W4pN- z=$+Ko4cfBpcL0jE9BOkZ98P1)D3#XnG$qS)gjl^$c9GI+KPD3;WtLDsCbf^&vDs*Q z_Z?1zTH}~^{CzaAzEs@)C6vNa6S*)C@zjvTpKi<`b(+RvZ?Y(vMkUJe#yYh>v#F-T z-GK*7A%84pX7nHp`52cG0J^ajY@N`bQnz_3EVDXQa^ImaE}q#@0V~m&;=lLPjTh1OY&{+Ew0RtmTfx!Y zY;!ce>a)hd;(v?8YzSD#0S#f))7i~LuLPPyGzVYbD*Q1$v|B;RwZ8(IBAUxNHtLS| zv-`)D`UDhRLOK*afWZALf=m^D1h+IVFA;C5V6eOw#o3KNO-rW#)Yh54t(#Ft#MbFz z>q9`r*6CvF`#=}dni*niJVUAFf{LwmK*iQWX;XAJ0=o?~v2z|poKl!D30Lf`1E8hZ zvt7ltk(a&^cB%BUXr0P96LDEoRwCmf+*nj*?=-KdPz>HrH{MF=fp{7)3-0JmO9pPD z6W0OlFQ}M(EzqGv2Z`BF13gesG5ZK)Y7WsMV)o-m%l;7}Q%KuS%nnkt8z4K2^f4Sr zVh2?M9D53L)-F;8kq7&MK9o^a^cLz{6%Y^ZqAxa|x+eH;K-sZ~zLjEnmswkltZjk^ zC2OlCYkvYNSzAp_)=r?3wKbA89{V9#TSKnB0lt!~tz{C<3DEo%F1rkL$=(TK&MBaM zO!|D#C3~lJWbbsz9!2h@$iWbi?48J{IFAU;z`iUXo<(BxYb54n?d=*aCG8nLtMEikrW=&u_6dnU~Q0V7UOn`W!B?%s%0L6+i8~BireXyxe>QBEc0UAHd*GMa9i(~kK?w%F<&RDnC~FW zH}@bcFuz0C-E=U3^fdDj+GaVzIc6QgAF(bAqmRS00__X~K|9R|4d>Sq-i*+4{)#Z^ ze1xz;8Q+TZR0l^aptjZ`Y;Z;+oZ}pdaJ_a~aNDAtV{zNAonPU$TRUgs_N8_%!|f~W z+<@C&?fe>AN0?A^X-;BC#zO0|KS-|*1ktX`kIm&$& z4gPCn&3t(mIN`lO?*zwueF}Ppw8tg_eES2`j5N;e&36QB%m(kP_0^teoIc| zH_*|5BoD%6eiDmr1+HS3no7L|%7>)(*4?7lg8=u8MMBzWbJB5FPKuZ_5QZDhIE1Ei z1VYPcMQA%K5jxIb$l#c_mys5mxm_853m}`hbyB)FLy3O@3j32#BK{*l_9ss7bZ@YW zXCmd7$*hr>-t0YI0qRn!9vbfta4|rqHGL*2Zo1csGBU@2lkQCe;@z5{SwGMrv$;d&e3DiC zNrdbOL#6S*0o+E94N3Pp!t8UY;7VQCq2S661y_-*y0LpWBZD)K%_c?^#NC^g}F)`ZAv$d^L@P+aCc z8F_m8$)F^Tg48)QAhHYL1Tfc7;9d%RK!FP&z^C6T!fL|U<0xFb;4IpL>wrsh!k=q` z3%eVeU1@BmX|^_%eIX0(IlT>t&XE@O9DS?Oy_HAd)#Uduywr6MiQB`FxcD2ns0L`c z`teMzn2SC^@CxL0neNGZhAz}eCeD_ui?o49aV%*Xu6qKtl23puH8Tm+O}+qBDq%6u zXp;8O8eIZ3o@BlRO#w|;nKPj_>j(rz3lQ9oxY&ZwlXn+85|XS%l2jhVmBf~$5|XfzNDGS*zE{{A9}NBo^E_ZNG0s$>ZPZnOzIfu z5l%@Ut{dYR98x!d(E~t?eO2%?Kh}2YBXFd{Ql(V1P?X(6>t=e^6*#%RaWkd-%Bq2L zEH`IKMJ(sDR`W0^EmiRkdZbo+g)2n|jMYQ)(-awf{EE-r@KAOl8UWAE+4;L7mnZ%Y_7a=_Z~mv+ROPpopOGU)zF^v zKU

aHfh5~cg2&fC)XrH-9^jM5@-Kbq^vqpzU()FE%z8nK zh$Lpb_Q$!Ylo$(eV%8kuR?u*bjb!`X-TgR*+I8<7h8WrQ`J4Ok@PU~?l1mX;*_;WsG@q9rf3M=Lu3{uB>B#7q-e_V5b0Xep%oNv)8Dj^-y^zWJVwmx8p9FIY+-3ou zgZDzFQsWG4>$LVZ>>``!o3c6@EX# zw85^3dSjAkj_)oimR!+b9tJZ~)F~w}lVQpLS0T(IyjQ}khuI4FA-tc2c^>xH@qQa7 zD9ZXz5$6lo{{s7uqABhI>_*98PJ-zHlP&3#L6Y8hHQqkJ<6x#prnm~c7s1>CzYXx) ziuX>uAIJL{m={r|6y%#=F*23i$<97>HsIKKHo)_h9`&wb(S;0k6;oXr{dq*Av##l0 zIiW2+UpB&D`>CNv)qGKxTBGJ~eM>@(OFw^pk-R}Qx>ju~AB|VOt8Q%>_u!9!5MSq! zlgj*s-h^!)%i&%TuX+us$K|9SW*fbQ^m8(+>Sf$e<&}@DttRA6Q)yv&o&@TF6 z+PeK$Mq2M&t2hc34NHwR-c?ku=C78G&eaeI|EXnrm>|l)=mF}8W39rStn>?UJ%$Gy zW!Vy^BAEE6XmlpKJ%X`FU2R?4*h6Ik37P+zfY9TQD?{jnx1ym#ZWYO-6%Yk$wN?jZ zNf42~V&xH{(TUbNCDw_M2}hzKYZYR^eG703hoiKznJB)zfWx9XH3YJLjbJHj=O=~333P5I7Jrot+MF%L>!y^`BwGFeRVEJaZ$q7Bd)(IOvcO#kntZX zY)g^4N_Mm;u6mCUQ3u?&qvXr+Zy6kNe4*8`G7^{t)geWQ=CN@U72uTjbtBRx~ zPHVn$D9SQxil>Yj!E-1O&59P8e%dpFcm$zS@xM}ZdE~Vot)V1Ok1BiVzoj5zk)tGf zxULcJvbf5lTERG8RMRU7PVZ__GP&xk4*HJ5e>_p`(GqbIyaBh{V!6xvXi28aSnS2b&n^1ZJnAQP4CKg3kW6c`nSK~ z9*<|G*E_wG`*JN@VRP1_mUGoR2z&HbD~t4#Yc&T!wt8ygd-8zR^4h2@;OSojT3TgA zjh5!Td}|LB9{fjfwK%0MQD4KBe_ta}b45*2J$R8>S3l^I)CH=@k#TJ;Y)8z7t?%bljNa8a8G~L?X=@4pJQ28aaxbg?C~yZE@Per!U>!Mz z05lbN)uF6bm1qG9Bq|o0Rrx9+k?)E0Ls9Xl(Yp31J`_cMR?EghC$uIC<6WNVo9-&! zT9{>7V^yMY$sbB%v)No;Z!~Z;vd60M3N<9JrJm407bPd3&`^7I2T? zt5rAX_fk)g|NNM%x$GB+i>e!PAzI9tY(($HU}XAOD>HRXW5(n`EGBIG5JwZNq9-LbUOy~r6?O)MT6BK8I!w8NRm=L()=;&@$rwgxJ1g%P?l*8A9HQqSMulwU9u-0;hCJ8 zkYE`-!FdaSO!RrSxwf+U!mV3zmSoH3Xt_FeFAvJt1bXv^6^)(0(B*`3rc-a08i>b9 zl~8>B&^WJx{w*vPIiLC;syox=xxpf5Xlo z4TaZ`_2GOpYLNk-m;cBaR6VS6n)@oN`|`rP5|zz#uXiOyLby99EUeTkWW1G>*p|Zb zlA=O##a2r2PMB;nx0jF3%D8WAe@>*=H+uAh3DYSIL+iskTO1|3x5)KRw62$V@EV0Y ziqPG|ua~0Nb2;?T+9+}(I`A6~tA9_C>#>qdUS%H1yl*U+PjalKK%UHgmh@r=S&A)M zt{BjK5Qib^RmNJK-a>`dQ2l9Ohl?g~9265HT{Qig4bR&8UNlr;s{-i>8w$yWssFXs zRJ^Qpz2HQ2V2Hc57v_7Je?zaeR*j{tUn#fPRz%_vGgdA*lH6jyTD0-eBJ`@cFyZH4 z=G@@gT14uEA5V(&&-|1sB^Gf_<2vBy38Qa9%usT@EUQV){t3qL^0*L*@`ACoTV4IM zVQSsJ(f4ON3%M&RN;pi(NBr|G%zv*EkIq}5(B&<(FbItQyPaN_ld4j^rt^;!wPTKs z-DEwD-K-c_b;G#Lh4EH@CQ8>}yp<2*gZmhmiLg(_*ljxARd~;Z+aj2iFn7V+gLn_( z{W#3CFb84Ygn0+ygD}5`{S3^PFyCSv_b-eSFCtwF#$a7xoEUfIVEi={#tZuxm>UI2 zHv{9hD)`TX`*Mu;R>OS@#%vG4?HRmZ!utsPKEnGH{JzBd9|(5=Zbpp55@5Pv+~tDF zhH(QPjPckAyhp*5U@SHTa0T9T055~xk9Qs5Euy5`jW~}2{uS)|F?Kr$_aiVL!JL8l zGyEG7=O?^TQ=4Us%i<(stPSs;Fnuvz%adfi2X-Icz1CWiwvnNDD-maH$9QnFP=wY)ZH<_udY=slXsW2i ziN<>oabCY79yF2Vv00(K)ZNk@EY7c$i_7DEi#R`ah*y;0<-Jbo)>3%aug?bhz`)A+ zaIAIlS~BWi2*0r~3oB<;RF0)u_Q5`E;3tV1EL;JL?Qj`GiR*_Bv=4K7SIl5vg0*>M zqa$8mg44UNW};7MMq2 zo`*@0u4UIVADalG-S}I^(6~WFI!LJw7LrFcD`b^yDUK_dfp;;R#}?zg5aY;l?lOu^ zf@>jLgs>$rw*l7xR>212Ign$`Vf|SFzN!zwQM#0h8}3n?8@7SKGnfs683;3k+lL^| z?ZBq#vc?YvFo5@AWCZWXSSDY@c4RnfzMI?&&U{)C7cL~Pe!1NNABm|)O} z^fF=?CZh5X4Z1a&vqUeGo(c!^2zY4qkDqy9CzyVSp;_?MVOk)S=cMM;(a()?sS{WH zqJIhe4OTtAN7Hw=%i}A44K~EZ&9;aWFzjpdD!`276y(jfQL4(rldr%VO}{(b zZ7*F)zuy(%hF$@C5SHjCdV~l)li^kp!BKEU_?k%gJ)mu6Bzzs*{E_fYpizNcg>l1_ z!PLW08bo*JuTuh@66lmbrvy4B&?$jV33N)JQv#h5=#)UG1UeP6>2Mpi=^!66lmbrvy4B&?$jV33N)J zQv#h5=#)UG1Ue zP6>2Mpi=^!66lmbrvy4B&?$jV3H;wCfw9%|ZmnE6OdUJ6Q0<3nigMj*X4T@wHH(Jz z?K`V_an+LPxijX^?K@}AQtgq0i>v73q`vC(p+oY@hYlD#V9@kIc>^n^56E-(A2P6F z$c((9g9pqQGGJ)l(4hkdmiNnIGv?qzm{-;vWB)=Djo{8*poQH?=^&-FM`EdUy z@4Wsvr>g_L!Yg3%>gfO9!X^JDzS2nes>plse;NP3rSHED@BamQHvfYAe~bVBmGImD z7xLQszwrP6i=6g@&Lkox@}m=%MRsaNw49!qel`$2=a7PE{;lo)g4LADQf35n}|jiX}$3sRB}HNuKB(p7+V&4!mt!m+Y!*>&(7Dyw~QJgdkC z1GR{=9xq`Vd?#bgfC(G@%3yHuX?Ll63~bUvz%NNvkKvF_X)mzqCDTzH?kP>$g`-xb zG}tXtK2XFlnPTeRzX3NS2>OMv6A_Y2Plucm5#mstMueV@K(7N6l1;x8)=v}R*;^+& zD5jy5;Y>UjVd|4eTDA)p!IpOM$;;fqE+CGvd_{UoKrL|*C>wq<_1J)iI*0L0Y?xDH! z6x0qWr~;*6X)HjPT;X;i+DvrC6jS;`1tGRCvP3tO=#K4ylXG=LKS!Y0S+A1y0$IN! zYK9VFasMK#j{;cZ5*)CPB3#zEITX4ma>%&;8~A@Y1A=wye}Hv0NqxMo0a;>@xST8dwADH9?OE{wyt-17 zHr=y$COag?UWUa!I}$co50}H2xr~C#M{vQ9Gew0}9vqXn2JW1xXJ8?wx^jk20OAZ? zfTf+GBKUKLPCz!Cp*^q=LtQ<1Bwhf-xvGVQb2Ste&Q&t*F4DMSddkb*DAq#HlrmVm zB_mKDfu;6C4k`wHFMx-ryeH}V!@3x{C1&XkqGHes>}j}&tPZI~>!B9t`DaF}s*(~2|Ognnsmr}V)-;zkz4?mAwt=y<)NB^$jZ!w<42Ci|2w!25jYo?TTxDgA@c~&_9)sRgw~YgfX^@zRLsZl zxgF9H3dx7XzC)BO7X-^<6fI#n#nupMR!-Y;j${P|@ZXOBai)Gm&Dtjk1G6r|E1rVj zswJ&Z%WnNWuu`Fx#!FhEw(f*W!XtP{TE#2Pr+QJ29giFpDx(xsUZOy=atbOr1xg`8 zRxL-GroT;vb@tzZjp^aaRYmNXcmv9)ZU8w8rgQ`Bc|3?h(rVF>uWpHSiXIJmq>yIp#I;MTr--TnAlGI)xOI#VwX^k-x%yLm<~Kv(~}-SsKzv zLjEz+5ws}62ixJry7mT&dRT-*$+j>sQDLxAJvxFcBVid0i@gk%NwB0m1}tyF*fSK| z3IiA5cn&WpCKG;}Y+Hwbbp4&}*RaqnGq#Umc^{T;f55|TLbN}@()BzZ0g5y`+5Qg% z(V^O)Uu-V``o54kRb%?TW5JY#^iJQGq-$|=Z(@~M$w6NVE47ibOFs)%Y9pnglGaA1 zl3+9SB-}$#L|uMYs%sh%P3P5h9|XJ21A;uTpbJ3(_wc~40iP~4rV@Umd%@cMaR@8M z53nBSbv-Egm;(S$64tbRI{7<(`xsmH3bnm0?J!jr(c^dgB1Gtk(W)o<}~8yt=hipLT-8=NApejgCS*nI?L zi0PS*yFn&pupv`S@5A(dSSh$6(RmMuOQ(BOOw6TKSXNRJF1w|VA&AjRg)xCk_(zmT zkrNfIt!xL8cev;!T3h8I+r(q2?eajXAUJfAM7^hxfaI}wpk9o0N%UH32H#Nx6cd!y zNQ@~b_W@F-qMV7!A~b4}vK zK}de2YEg-Wi>!PJws14yHj2Im^^!sP4laIy86E)bg3hQcLN&pZc~tt8L>_+v)J0Nq zAthNEjx5nBz2L4_*fkV?KD7{v4cHZBEEJi9q_nApz??3S zGzHT)P_PW{K(P4&LfH%$We$O?j#7~x#BWoGp{C68I2?uFkfU8j=oo5oVuw)dK~$&2 zOr}65O_v9v2F`N)@|es#4vEaBgeF82>cbNIdJsY8U z<*b3b0P@PY+y`HC8R9(#V+q3jHHtp76z*u02xAk+dONgKSSiF2)MzE@9)hk4+XNv& z+%lXayFVY10Oj$NqR>E228YagMEnW{f!w?`*uIR?H(hdOyhYf#70!vgML7HxJk3k- zAWJN76<++7QwP1(D$pbgwC72XqrW3L9@Z{GS5hCAoCd&_jz>~19?ya^eINT6sJ*K1 zpORCE1K8oLAAp)EWgUujZ=lr=64O&z(k<}Nq5mt7@0Kd(r))vNvcp&QQdkLNzLCH$ z7l`RX%38#vOC#k%FXjLKr>-Qg^>|Rjrnr)b3~Ja6Lz5nYD>Vd0vz<2tMn?+O>_IpisUb+A zhTt4Q)DWalLqK_IWqbf+rX#F9xVcp}sS7>%q5ej?j}oQl@_Pr>7_ zv6t%oDiknrJEX_!eW{yH>MNk$F95y^#i#=9vLJQUtw9}!sun|3P{yQkSczHDCe4AB zm=${KBVEwrU=@mnD&FbA82n|Nx&hdYMV<%$qcQGDT)dsNb)v!$W^J9)|6BY(U-$n-p2N%XktL^OV;S#Vx4y zIkXX2#9Sf0)rGgNX2*(yk zUo52%u~4sHQz@7_9xXbHBh`Ab3+ZhxK=C5xO{_x^gKAMgz0h1*{eTltI|1%T@LPa4 zp>_mJBw88r`FF@Ez(icU7qECMj8UZSLHsemKIFM*5Q&8NZLy5~fczEp;vWdM#G%rF zq(zp&5M=c6H67S$};(k;W z@vVqZO9*qs25_155||#Ha5fNcN9YvdY?u~%1mN2#;8j}e?G*cFIDU%k5c9P__W*tg z3MG0uOb9Tnm`0e=!`v zm`qv1x>B~BUI6zyg$5)e#C(H*r03$DB*-qYsKaO`N%RxQk}1Z+Jl4y7;abnLM~KNn zFdnO(vMc5~fYgAA8dwXET1l~6! z0aCjyYT);X%Y|@)3*jArTnL))(*U^;P7q76(v~A^%5M<&<9tZ0%gnA34*$S2rKc?- zqSIVND*$p4og}IfZ4goX06Ti}2Z6w)6`1jWTS$pBG`6c(jlm}7vs0J4SK1&5(gA+?gjZOdoFO1Jhq-ya^fG00Upy#znHHZ`>9Y3YP z+W}9ZqWCWkCk(j;_Mf=@GLd|Sc24Y%M%{WGF}NK2%Opo;xCxC0^`C|DN~(}=q?$Jw z9s>2Bjnsdp0wPfV*+?0#k{|V-jg*}nqW-gy3L=$gf%?xzDu*1R{TA_?WO}9hH=_H|=v&PAo zO(G%IICLwqO`Q@B%_T;u3V8(xgn5^BC+1UkLiYxxL1A_8z?OOvkD6N$)+Kb$LdkLq zU4%4h$BAa4n}D>^sQzjoaijrEO{4mYOP~ipHI3>oE&>&xGi?;~gp=X`=t|p(xEyo> z%uG|GvP=iq2WpBABrf0s`O$7<=uV6(PylEuM*I$3x*=11a-t7d==H&s$;EgCB+Pc< z9qnjqB2=s>2Q%O-2wzH(C^gY2XmoW;02uvvPpAMG;9?;mSV(x{jVS$V;Y7m@$*~g`c(%**4T&kEP*LsW-XlbvboL?mQ`{Jm zc^OkeAe(b8JqB;O2UMiV(ce=H83^fi!~b7dK{rlG3W+ma!Sk(N0F2uS&qakMA_L0J?7tQEk8r@yu`$Lh(TWw1xp~3dF^^rxxo`{q9G0<)yB(Xn3(jOvrp`3HZ zVkbpKtUMk;68lP2!~y6PXDlv)M4XL6#1oH;@FKo?T_jpul>XVE#)9fku>LIRJs6PO z3lucJBv>o26o-L{<)|SzE7tiDaV)4{>}DQ*Wh6WmYhG+)(2a(kFXz7gE3x5%&5h7$ z4cpoXwy#mIaF_TV5tpe@^4ADMd~Xy`ls4%dKpa16;`d>1)K19#1*9PdthLPtP*Ty;E@KX z=(Hez1v`05D*(Dt?+YpPO;VBm_jh}X1HHOy(kPIclsI5{3sUv zgsC^8{Ss1zzDP(gt_M#%8&bytHa-cEXG1D^8yf)fEKI$QKs~7V0+qSIeN9&ZR7hD> z3Tzq&a0dy$7k!~NZ3noX3PR@11QG|UDgb6tU^Ax$4-3!8-j}(Vu0=8Zh!>d~C~6r% z>T(Nx2()ZRZ7lR9D}FJ<@YWVf#f*eiTPzhRlA4?;q>gBanTdy1oy07FRjW=SB|c^i zT(kuoG55f#E#S~c7I3VEi^$d+FV!yxkpaiEuE>@Dt)*X_4?Kja8IL0so1h>uLAMB8 zQ_hIC0>HX`04tGmqJ2DpCjtJj4q(b00u@j~86|dGO5oK1!{nRF15X1;6|mU72YmG? zTu-3A6MH8DGTaW&bEKUv3Tvnb_-C{oVjm8^3NYOOaG)Q)Oo2i~R>2qvmuWa2ex_vO zMUs_m${<)tvQlreR#96t4U8#*hgnlfCE`k&`bEQOAtg-NlL zj#;Z}*;Wapuz{va`EUmadb{_fe5jErMC2z-je(qmL}4Q03DOe~=L_(nGE;?XWaU%f zu4x;En1%qdF&hmr@<2$eAg~0Wct60TRRpdAxQAjVk+1Go05^^X*ku(6)JIZex-a3* zmD)+}kSGcaQ{KZPo*9qe6`40SYhn?pUEd2MOzr6hwux8LdP9U(FNTAh)`^_fMF2Ui z6FIF%0sh*7_>(xT-vZ1c)+cdV6LKQ7-bgfAG9e?~IJj{-XHXFRIFL{a6T@q`3XOIa zD~Mg=?gW=C?mjBZ%(@u}w4Am+2&i9em*I;^{SXMoM#KQrltuk}P&VyH4-ON& zf?N#=a@Lk{*6IOr)|L?_`6YmywOY>F=KwitwdAXtmK$O1HcCSOBCzKIyH0{TXKy7Z z=S{e~$h{EmoV~hB>}}xeQIH>DbXf?(Ik|(PGRtHV*NUj)-Q;NTz)fTAo_5x(G&}Kl z;s3|jdq7!HJm2Fp(>-qkZ{O~+%O;k%WRRRCNfZSM0)mL5WCT%62nMp`pokv{Dk2D| zWD!9nDu|+@2oh9O6cH3b0Rzha-s+is^LD@I{C|56!`$iWs$137Jv}|)A=h(??ncFw z5s);`)})Nau6ee`yR%Z1HWOI4C^rdWY86u6vC@jbYwt1Jr;sua;R3`zXYr{N2jRC= z18f%b$Iks)|E+KZ6^|aaoF&6FRsr0Fm{jf}91du^Louxucn_d*r=q3^j{tPtMK~1* zycaO!?m^24ybmzWJ%yfkf~rOW##^Cv*k|fGS)sSF%ha{2#+gAX^f8XmX{TK12zKeT z-6+mm&!EzTv)Ej)61{HwCy?UYwJP|lN3{N3J*eHoI4cN)hhfrkvr%?}f};R&A#n}l zRU>T9Ku$fOwN_)an+MzC<6~0kzsVka9MG|fIQ>u|EH^0BgV@bRmaEx}-sC{^rbylV z=uM@;X!NE52BJ3|Fc7^NfPv`E1PnxP7GS*PoF?~+*sN-eooDFq+H zl5?CY*!-G=orQGx*?_~(Nu_*;7XaeY?q*ivI>iJuzFMVql6px;oBm~~^zHjzq?gf> z+a4WW0>7L(+|>yBHmZ3AP(_n!1z*sk;MEyPN#X-U;ZsU%cU!?g9+CCy>RnVO{M8jB~&DOWy-5VucoVCs1LzfB70e1w@nl$VwNmxqc~j zj%yuKUj`~2l}Z;eEDNh+rc!YYSw0v08C+GTXzBYvr5~hH4TM?z{?SzG%u2Umvy*Io zXQf}Y`woCe{U(*}0tBXBBZQn<`e;RqB$7`)b3^kQ7j|@x|n^r&{PSVFEAXc zEb=JkMnj!Rm6p?zQa539o^116eN~lm-9COom!Xuq8WXmW6f6s92U7Mw%bCFAv$5IM zA3N8$NZBf`+XBriWU3i6D=GB`HjS9r4M?VLfJ}WO^$Df;yhu_!c3M%Yh3nSGkh6>J z(b7m=q?8@9tZrkmbF-_J>J-q`g;MSxa9Rj-1w;;2fxN1Z%~8_4i>mJkRJzls?o6rM zu<4AkQ;lFV3~}9|upo+g({;y zOa(0B_CzCzhtmMV?moYJP6v#*A7gZo(iwm$?zR32WF}xy_nX#U=`6r>_n-GYI2$m- za@I4=-o<7N8}DUT|D~P{aDNU`mYW~p1%QDFF9g(4c8dVrD7(dgaZz^9113b-y#QDw z%I-zLq$s;3fXQxAKS(bDrn*04pc}$WTndQQ>? z+=8-ofT3v4tp~&cB%hX5dDxssW2oJ({!eWQ75)nPU>F{kADU1{{tz@{T-vAkiG(l;juj{b;oAz-Pnl> zVU*G#H)WAGN`yk<+6YRG!6pZ}t1?5#tK?AhER{;B=vk^Lpc9>8rvU~gQt5!MyB_9* zM=`*V`zU(UF&fDL#O)~-Nsqg1IcItNPi!vJuM#1BNo9wksaG5|tZ3@x0^+<{CJfnY zuxUib)k5fcuJ7ooL6b1Q=jk8PJXPwJLz2Xve4uh_3e(4L*;}YaHL2 zg>;;19ExUe6G&On3~mYtmU~#~b8L1Z9IAZ?P1Yt9Jyo}pN+Xwgp}HOrO?HKq;xV&* zgy*VmtkgMBsjF0~UEHg5v#C^zGvTJ#w0jaew}+6zy+iJ|ehT+tCESIiR6lGoBiI=k z(m85q$SndOZ8c0(pYtmX2ehNPat|#u~6pTR)%gtDe%RQDq3Wym=3ic&Y%2}b}*uMeOE!7u8 z^UaZqh^f@T9Sxjn_)LI(Jjl+GxTFd(;~`|5sPKyHAT1f-wv9SJA_=`Rsj04M<&AaEz31Y{tEO+W_8t^{PTsQ4K| z5|AMhkkz%kfDDz=I6w)=-O+#yiw0zPY(PfD1ms=`NI7VbfIKJxc^Xgx@<22o_w|R% z=^vm(0y6R>;YmOV$o<()Y(O?IML?E#0l5k>@o|aptymi?#T$v-GVw;Dw%qe(qG{e0o;O;Wccpl<7#hTz*XWID&s8^pV7gt$bL>@<(RHzU zG)mU3J{Sv&?|FQ4A;ybZ{T>-1mpo&8VvOw-W33@8#y*T1``|+w8|E9^XN=L%Nio)h zDRtB{_9Vo3ZLE)q`)dGsZLE(8JPg>L@VLOMfV?)=UlFG9x>$cLySygW-^ebnhxG}O zUsD%rEVBT=<+W!TuZ8t@EQvaGGRCPN#Hos?CQkhnb?V3Sbm|C7BsETnQyEYte|V{y zojlGqUS<9PIbJs72pcRjx1MG3x>?8DG@q3p(?py0v+w}0v|tgN1hQL0TnI#LGRToW z8Ma@86WOCsCKQsymX*Pv)-27{$CP2PP|D}Dlh2{4$AK|;TjKB_hoeqDhXW9N2Ly41vCq5=%tZp-+2G0za9tk< z+{NJDz{7BY(p~L_l_CEE@NROx+2HZOquF7*2}T?jna@)q#{O`D#8!9w@4 zf6i~pp7xD&;caLV_j-$aRz@_8|J;Bo_xmnffgqPCa70`vhkY4Q9uORD%7=UkCY>HO zVe9~Lu`@al#!>ccau|!UON`DVg_`mh`_y!w!$JtkXf)k47vT7my_+1LM451Wx)8@sJpHZoIlcrzNw<~4DH@ko zV{N`>Z0_@It_j$D-Oj`cM)n2Y=ITN=Z;im_Hs5AaW6R=#430?K)B)y-jNWc@1P6y1 zCOvlg9KwqgL=dcojT+_jk*x}`fZ`0(% zQDgIfZ}Y2w&13fCwE35B^LQb@uX30^>)TAf){FI7)BcTt(?&z$7yD6mn>&FQ-Vgk% zV8p?jlKkd#GINsi!U^3ZMY_rFHg8ax5$}TiGAT`gCJE6+p&JZCUpgJ{2vs|CEd;q7 zg(JI=($VHmbYe$YM@HE%eZ3JUFv>dnXwz!qg+9qCIMOfWSWv>V3EpPHSV~M3H|`b3LEyX0wAA)46=m`h^1niJc8)nkd*FvRbMxCEqjfOt*kHxhrr zCtj-%@u|$2)&+^T4H9oxsM3Frpi)<#_!ggd*8&ml7E83dA^M$9v}b^552t(xL=&5O z5x%96&7thngM6FiAvkob9AT?wb_C`%p&o34_aLwgJVPXSnTvtB%%_JI28Sd z_aOc+2f%v+9Pe>%B*#>o)TINCD8#WHQ+u+{u^I%0<7C6B8?bPE(%?sdh2s>%@nvA) zIJE%B>9HJV7>*x7TsY1SaGdEpPmWp5J&v;qam>8~9GCeV>p)OAE;F2Z0}IFH22TLy zT8@6jaC{Y*i#d9Q!+|VU_^*_*TrNTCRZ=gP>yP?Xv(Lpx{hHb5%A~q;suQvN! zbJS~OKRP(B^Zho1<9g%w0ZZc?<=z}{(Wus#lLUNzW8^-?2CWjntd)=>hFx+522T9 zmil{V9EP~4??1%){-g1|8pOr-(*fU4Ij>`;lKm6PBrks|y7k_{dMEU`!t`CP7K>Qu8Y`C3%=qa$m&iWympDH&OpLXB|G zQqhq$LoJC1_qbLb_e@oAWW7S}wSDeoAt>By8}2s%3->yP>qubXUf19Sz{0&=fP4J_ z_XcKP+N7b;xf6AyO&Y1#Ho4aK%j9Ddb)O%?kNdwHW;B z1jUa&hWBD%@#A)b-vgF@c!%Nm3$XOVzAD-e?=-$hp57((Oh4>r_9ajIn|;aC z0cKzFbfDRnJRKza(Oeu7>-SLOcUl|I@8JQz?^dtV?+qvuzlRm_`|wiuJ=gd9V+e9J z7Dt$i(Hwf(w`nFR^Nh_)zRhO?HlI;LuymVUvaM%xej%F)Jz-(FZ?h2uQs@m$Lhqc=3r_H!rU884h(m>)IRS!l4PxtE9L`<^mTM5l;Lm|& z=}GD3EdK{sh6F7*m+RTuHDycNTNW3zbx2FMl3^jvlubsN3=8qv8x}m*616wn8P|$v z8Se5RF8q@ z{7{C8+6K1;mf^9E;d~FU_*&QaD#LX>voFJVec6wuYeU}`)9o4=U*ss+E$UXB$aPQ%BF9y`DtQ?VRfhNGc?*P6<*RB%m`n`M(CO2-?TPw%L zqz~e!hx&T^L5~cLLygbVfn{jCTiP({+i+j6@$DYt+Xje>Z}$a!8=+^?w^Jw+-|qE% zwOL_AjmrtI3j&*ATVz_=nY!hd?v6A7aI-U2>dy{c*Edhz`W6*Hwi}Dy=JbH zH~XBc$#wEu!a4u?aI0>B3^HlCMORA#{6fkluO@)r$r-jL?~Ym zER;VtF5C+&ln+WeWiA007r&75T%mu+$TsZc(LY=u^^W-TP3j#rAwLLJ!ttv>$dBo+ z6!O&^{x13aAA=z8wBU&FUj{6McF|?Q=4- znSTtYs}L7XR}Ci_mj2aEBH&cJv&ZQ_;bh`{MSlXm!T2^~r_OY};khF!@wN+&2)_q` z$FXZ{F>b05=UI1F%-r#|9dRyI+xQHNCPyxI-Js z+3uAM#x3YbJ!p}7LvYL4iZhQIM{kH-wVWTIwItc7OW13Lp<#I_;;NO6FGDY^=V>q0P0@aK+iW|96mDkFRVlKFdg(iTu z%k>t4O#yKelKakN=qULh{7}zguMUEwl6a3SAr#eCMDCICY%X`-!g7yH?ScHGa7r}{ z>C384K)#;HbBGsn^(pUig#Q~=R0BZAiVqA8?Q2JPFM?Yj_a zEqC4?ya`UY2++1d1F`QtVtOBTV;?b{fFs2)zN+EzDXL#WcVzaFNEz9VcVvd z2VWAlNlr5QN5m^s{tK%1oA-O8bIxKLzfUk zYXL*<0Jt`iTGs)_S)s?U|2+~NYGUx)NXbtL3&N0N(2$HaV@?H)W1XuG!{ zfGt_?D?rDJCgAUub2E?gm4xcev2!YfH(XDI-24cAC;HA|SP=E!i@yATous~Kzd!UK}$0s%V@sX)Lkh{|qTJ%hhfhcu_y{{zroa=@z}hX;^q1*GZ- zyNAnqmFkk6+sseXdVpxEzA&POVY37SqiPd}rfM3{(L7d1i&!1kQHQ$`9EDa(z>w?U z#=5jzE5La7EtK+QTGbjbA<%M{Eaxj8KaEWT^l)`^9LBK@0pmBJhUMlv&CiEfoRD7yzRE)d;6@nt3+Z--4LT11VG!(8Q|fVEMmVY!2# zzBhB~5kT~dds*pGZ03W8n#jx>7pOFWl`LmIrC!2jBC<=(iNlaTJs>qxROdI%EUJz+ z&1^ut9)FZNe#Ay|cCv`s`)okRbD|@^@fV1W{Onx_i0m!N@udnj?UB}M1$8V9=vWr3 zV|lEOSB#FksN)`{5l)zD6PtZaK*zdR9qVItY@m*45^e+xH2WKXX!hN-@F_Nrx53U% zvV13CVH>0Z1JriFKw|9xgs1TdsG5$=g(tDIpM%N=0UaNTj{Ma5h&n8%DJ!+d<}I9k z)iJtuFi`1`RLT$WVOEL`5MKfYTss07aP24{Tzi2QUd84bX7nk#_FcfjNk|1U`Uk*3 zM*j#1*S@C?er`!d0L+zK_q z{@=4uwkp|CFDKMP_Dcs^2`-k?g|+%%a}(!XRy?fz9k6yqtmOytU$K@yr2Pk|rPIAY zHCF-MK&SiLa@O+r+t~b%&P1p_E{A|60SgrX z0~RU*1}szp3|OcP2n&a3;X7<{aqd+0MC0v75(Dqx{5V8B8>K%`9$t_M^FZ1^o~ z)ru^e26Qx!)zLz9HX;BhM;cwD4Pd>7Li$P3rORC!lGW&c=}17cMUj8!?v zsJtH9R5xti;kZ6B9^?9mfXe%*5>IPWYBV;-IADyU;YR{eV<;6pK|KoSxRokKqSI9GDu!F9WRQG{7L*0(eV{xB}2zc)Db-u zF9i&_WqeOx28@>}w@h@F0VY_^78-mXoBo();8h0X`0MdemTMqoxsT(lAQsjFI_@5S zDzy#}r2nIi#AHlKa7m)xC+WB2-3z!dlS=Q34z6Gb+$K8meb`PN?(;b3$O(Q2V95R4 zpAPN>jC1Ru@uco9zyx>1R!?|0U=jCaTwuvn#Cw2vOnxSqsOPY01=rPqc+4pF2U_bB zNLkSY_!JQRV<#(pj7=vPQeUz0z6=EZs8q@yaF4N)n}79j98g;+T=Jb^mVJj|!jNI_ zt%f@f_z?~7x!}(Mi<;%SY!>0IXGm8o%ZEG;;mm>Of~7{<+0J;22JtdK*^ySFi)C>| zK$jnP4IY%a36NLdx&kAXHUp4X;JTu~eSq=^TBYd_PR(D(t3vDGVwUl$wqYTd$$gdT zU4YV0p{A^%)occ69{#LW0VdLmql3!|-!%sTGx)GbvG)*lU5_TYUf})Nea?_)56=%i zP$OJ=u=Ubz1LXC-zD3|HKwj_bo&q-l@_JwQ5wVkR=T}V;}wO zgPP*@tZ2hdd!7MVj1p5E+Khey!;4IsO=H> zFCbSI^j;xey$7Dl9R~P8-Rg)?t)7;}WdXfUN-v?b%yz&J1&##dvVi`m8HKOnVp0OO zUrP4_ve)U41ug`X_J{vV9e#fHmJM;tsmVOK+62HU$5$3{T%S|xxSEHl4`l-08rzRSP{DrGZl z+4;S`j7{&d{cc~@rh7Ry#KocVHa#jX&j{z)Ye72aL6ltr2X8R4GD)0&cAt^prknVj z=RlAv5jY~8Hvu2d-l7z&%xZ)Xkj><@Hw;!aJbIqr{HtaZ}m8|l=uWW**Ku-PNl<}JqNEf5#KZw=V&Y4hvyIZyaDdlj-d38z0j#J9P?w>iZ4y%ShQ zrlAEm42$J3+;I5W=WuU;!#(yR4&!_d{Dmq>{Bbr{YY^)x<_6dV z-)6kr0GlW_bB)r;qSQ3@BzrXt-U&StkSB$VmC58>Bnet(Gz`x74Niri+%=qEz{&sl z22JBWXPjK;>s=IZa)EuGHjki8oLpGQ$tE0(U-NDL;oE%81UmONELd}Becj;Jz`WGf zs|#>i8_Q{(;WP~5;_Jo$r}gs0Zq92c6HXfnacays?DaYAfuO`;ui{Zn19Rm`e<-*Fr}p|I(~0+?jO$K%e}PW>iO=7ht3NgT2SNN_^h5o5fd2uTAD_!P zg)-s)St0&|IV}9*^XI4Fh5s*xUlm~C|Es}WfhDHr3?2>46%zfM;rth{_;cRAK4zf# zJ(lwyhVx4h7tWUhoG;p6l5+-5{lfW@aL%a(ENA0CMK;*!Um}APN963R4iFR{Qk<6& zkE{oP#fMab7Xgb8MGf8!EIy<;$S5nTRbS7CbSKt_3@2u=%yeY1Jd5h$Lyi+2EVG<} z7~FIELY4TC?QrInGX_|EC@!)=A0FKbAL{r%%!i=(P{(k57nrk4UDx0rfn~t2XK>_B zTwpMF>Kh-X1555SaAF7iMzKCLHa^saxcJaC;KQ}fUiz>CW#U5<@gc~$AqU&;KIdH! z6wciZr!&A@nA1IkQ*^-Z>2ok+LNCK1?JkeQZ2=CqI{b-(oOUP^4!sL;m{trH?(sR? z1wr9(k8p_Eyf@b7ea7ZA-{u1Wn+}A^Zr6Mt1p7hiN4MKzRiio?~A~aPm@er zl<4Q>)8qpAG{xs+`tel5sTIVfEoKBbO>^dw(|D9g9HyImS_~}tG*e`QZ4*Bq9GCbU zH$zb3vczya0W5vuC4>3n57H-=7Kqz2pQCB3<%VNThzrM+0gkUYUy$RyC=-q=3dLAe7_T~0Q}hMZO?6HdDe zaay|)obW)eTv=t^4ng5`#Bi7lES!!Syc$?I9W(e4u(b7Y)7ISwdTsqxfwum}=WN>g zgyH-jstf1u1DwBg`jGQTs1nZKnYNw@ENy+VP+RZkS?-GO!wLwB4_6GwPl09P^{>Hy z0*ep-36Ay=yoqckgQl%*CF5J^K^{k~qT`#Rwvl6BlnFu45o5 zoU%9_v$B=~%XpS8ILe`TEQegf;X|K8sQ`x(N@2M*=c>=4WFZb+IX>0$IphrXIMgy6 zS^`Vk);4%3u*9W~!A}88+tyWbUPv9{wQW5WdtPYZb2e?;&~V;@>e9B?1~@lT4avDW zR7u<#3+J5nz|yu&L^jxuyK$`P=KIhef)dAWhT{xiiDP$zHv-Ffp@-mTTzmQ)y)oBt zJPvW;cw2zut!hC6IOY!ZIQA|S*ZbMF_xhX~Ku|c{YdG8i%!O`!pNZR(z+CCpBURU! zIl%)yClj{^4X4*3E}TXMI6b7kCa1F~lej%xh|}vl56tj6#og_3nqfHA0G2q-G?*Ww zl=I~*!|^d-IbY6JvFFRVu^gW=9G5~|I6f2L__XSf2#&v^OgPRf#Ig8naD3J0n1E|u ziQB7&Q!QZO_?p4JfTb;8H+USda9(XV{|79b*A(EqE|&9p!}%463+FcioHwXf$+`G& zkMqVtoVoU=_xqgdLr^&HH=J$<7S10VJQ-Lxe`4@zVB!3!;av3|kMn^7oIj7{e9&+{ zi0Z=maDej{DyaxKUk_Ch=R<`!f4CBy&-tADK~OlKGn}Ra3+LYq-T*9|&l`LUSU6uW zoO_P&IR9RN^Tk-smkehc;==i_0O!k!U(m>T5M{#o&qACh;8dVfwKrbQfuL|s)iPdg z1{Tgm4L$)ZoYMqH`*|@PGlpkq8N=i6^*Ckg=op@<4@ewQCY-W#!7==6UVU8SbLs~{ z;dG7RFdbMpRX2D8u*9u~mhtiku;gb=9owgB`y5TbuVXk~g}B76et=_LT`Y{a-Eg1B zv0fpL_kIG79ej=hASfI=7)~>Rg=5D8aq8rAFymoo!{H5x3x}Hn9J=Vi@j1pn;Bj0coT53t)VFEc?qy@MCd9?& zD*>C!bgLxT?1?h5x!ki!d#%fHp)GeWY;N*x-tXJoWa6+8SmN@g!MlKYt4MD)aXACr zkgVSlY_8w8m@DW9U5^%s*>T^diP=}i<{{taiGa&rMfZe z0YzOIYiB@Q_!o1dV{Mw-n*76~JpSo!LH?_-jG(Lg{3}9GVp!eq>j=!dY`TWQPXO~a zo33f_I^cEWT+8L^b=F1T5xn-UZSa@Cybq@9xSOh*1z?C z7D+?*Hi(-w!3ORd^npJSEdDhX|AKSXhdImZ>if_Yg1pIxBNFq`z_N&av%xO`%XLXN zgZBaRKA-L`aWBD}e7c8}mEavdeT&%_zj~Us8IILA@vB#Xw&@+)Hn*9!xd0Qw{f}k{ z+$qW6P$ovAF{uj&XIm78~hzqAh z0Zt3tcgX1q$|Noe4X03VbRhG1d`a)|_yj&4&o-9F;}iIJyrBNL3!1>k()AsJ=<6bJ1R^ziFz!$LjKU z@geN-C*#y7todP}=6r5^!0RtRSu|55g_*o}FVJ)ZRvp2p^1|AWXy zoEHJZ@;ZWAkKJDAUg|JDk*p2|Odq0Y%lQZb>Pu|wRoFSH@Sw&Caf_t&Kb_#YXPz4^Q=nbx-qP(bZBr|`7)Qh+VQ4RU+;d6DS^*+ds7?G0_l_6*}eTPS71;m*2noBiSUVb69uh>hOF17|=n zzfm9YZSe%4M{gDSP%D3SAO2pap6aQ%2P)Y8^i-igACTQoPm85L!&fO2o0&rYUC2s+ z=0xewx&k997f>RtIJ+SIThVv$!8OTArky0=QY!Zz)_J*LjpKB9jmI|W2~0J&^MEvT zUBH24yVgtUa-wg&N7f|uhTjVK|2>D^U7p6?)X0GB;#d-GulHgceqS7(09kSPgQ&xM zYN1-nt0)nN_ZFnTmObc8&)EAA+q#wq*j|xe0H4E&Mj!E-FJ0&#i=ltK9TpaX9bqX( zFnr!eLKsOOi)UUKkh2_wK;OGJ{j0A-+Up$YdjUSriYCv0dw|@VK!td9LGnBe*5uFb zqcfn`wvDOu{$ufo2ZL?fv{fwMma=KA7+O<|X`9wEOgutTIa7MLYrlzlSu>zV?8Vsy z4er_w^%=gwjS%EQ8;-EG(~kk4rbMQ}&N$2J!ET-<7z9lGa(q6fV;8q4GlBuXhA|1N z`DQ9Z4aa93849`dPQbU-0=~wm%^%5X_+tSTy+XrFBcv{Q;ik?0MP2y?Y zLZ0qpTD0{kJnvIzYbd-A%+X)B6O2%Mqey$7f*D3`5DHnp`gS^s9mC;9Tj5lZRd&3W znH{7hf?=4zFx>8QXbM3Yzi;=WY0eOReUrw`J8g~mOHj=g=HtD-n*LBDN6lNA$ll4{Q6u`q`KD5ciD0JX+1|7~htqNn5i%{$;j~;(e@wJ;I4x%`%d|X))A9vSAk*?3PRpf? z)AF30?OxenOv`gPEtj(3v|Lq#rlO5-iho~erFJTrf0B{fEz={O#%Wul0qWhil?jIzPcGQrB^{NQ|km(G@v$J)N-H|`^7{p2U>v{fUc#U zLiGZd_cU@bt$<+vlTHlVW9325uq6WW?_F4s7qt%+#k zfFklusm)R;=tS?dfqW-gzcC=+QRLYZ>q17|B=XT9D9f5rgBK9I#`P~C27}Fv+oBf` z*CVf@7Z4*Ck(aHNYHZ6h`@IoS)daszYsm-xM-es!#1#W>XZn+9EYZeJqC*pK#CtP# z7ItGUCmd$HeB@*m^W%COVQ3RD*N)~aXFH`n!sc)6sjfD@B;CoDdHA<1=^|3`WR&m= zHu2F*lfMDO_#ItHg|XR=5nJ6wr*02e=_^jXg!!>Jb*DJx)MKUVuu0v4oqO<^5_Pxj zK03~m8g8iZ=f%_@Y!)K{)fgKukv?d=vd<0OrXV9Gl zjt0b2`>UyP3pUN6MNPKx5$5r>y9t#<$3#(C8(sZt3QhvVN0@)czBu393SGqhI*fIy zW|CH=BU~vF{S^_+$Yx}ypc-sCr?>;TirDl=uBq4904oEbd6mX2=Q>Jt#AZZK>}+NO zykWchkZ#|y0p2uHgD7=BHiJq06B|#ee`LFJ#-aUL^07!on+b2kKF8zBuvxShJ11xdh@Up!r0&z+-@}eC=D{alnv#?OTk01sLZ}gc&LQ z8Zb$IMM&T`fDy~7gnpvxV$*a7cFxkRPZN0&yD^e3jP)a-);32{38mS=UHhLHmlGfRmef2DaSpGelC?< zN19+ar9Q=GFHT6f3PhqMneU~GR5GRHP3&F>0`a8W>4KA$cy4G@h{ zK|^tEY$h$jPALbeP~36X`ng*IQkHyzT`H9X#8};%l?Gwchas!$V63j-M0r$_O8l~o z@Td&vxGx|hL`M}s9c{9zfNr$OssV=N=P^Y18o;o8ykAsR2TYQW_Y156m@FUf7g!T8 zRX*M?uohsNe7s*^ZNOsk@qU4I05jpgOaHM+?0}t4jPQ+kg#gJY_B%q#l25HmLfr&N z|5<4eHWuzfs9Ra7N1#&A*h;-jr8%s$2%G2GQHDDBnSwi==pfrqD)CEJLa#rd`I!QN z0|3p>6bKv$Xnv+Z;2^;GsHcMg&Ce7_=@3A?f02TvE0v4QcQ0e-0S9O0`|wjAJuRyi zOYVmhMp1e#iH$L-C6C{PO|w0MT`oX03N6`RM|Lq28q-tV{{e&VIoCq`;1rPg4x7vnsBh6<;s z!;ZV#_N0yofdd%SB^QqZ+U}olQQ$E^@Hs^tzhiTeL&UER#^2KcK4(NnY0Lm55NBD* zO#?-NzX0M*p{i`T2H1Rw!Cl#yU8#Q^_XijjsjH&nOOO|cm&<9mFDnhhrZihLMd=nQ zNx9F$-*GHShEy=0T3F6gJpMd3XP`=zP`aYZRPt4SaVVQLTz*|f`dSX4?ViHCOWLeB zV4Qm^GErbIAiD1lRCyj7`ASJ8g`QY0pt3@&%8FEpm;Vw_sRA~ekZ-CPD>V*OY7$$i zsj1Y7m3m`ymL_jtrM7`e*T+_BZz@e>r8(F<#1n0IE{}Fq?g77nyRnkxtfJH=Y%Xra z&K+FoyiK`{5mt%!?MCWTN*%}MMUK=XDK$bx$B+Ar)FnzeiI~-Yik)%1EO=D8zafee zMs?K|n;fuHv+3%TfTd}oBVXloR*I^e0jQ!XX9D6a ze^3@*W&vVkSwssfusO-ZUr3hoRkVBj4^o!p4K%{mb`Z0U%hOppLWHY-c=H zFlk>>(a!U-=*aIp%S1}@YznmTW=I7R^esT! z{a;UyAdMHm z%$0A>L7<|gCU_UVCH4cR$pT673aD1}W|UmQj?Sf#pzYAJI9kKAeGkOs&RGrGjvA{K zW309qo9`Q|7d2Mr85(=nH&(ZxvHq)Itc_>v2*hNWsm%$xlD{O<&NF6KM6Rc?bVL3I z>N9S&-%3m2iJrqZl7p4ZrPR{p`P9I-4C$@LQvM5kw^6gFt4~30`(~rYV2@i}D3(AB zmPQx|$O`~HgpH6g1Mu)Tz@e3^2y^dUwqYJ~^Df(PGKX!mVl?9CeD3%BL+%@V?)Mq) zI3|B~pSZSAJ;`Ho3Szvf(34`@|H+spnIcV+Jjn~k^k|bzt%)(DXakf;`%f$A_A+Fp zUg#O?3Nc=s=!N3;Xh2?^=tbi8OMtvM(Tk;YA0RJI^z($#WO*@$*%D!P0kX0r^Kz8g zOZ)-mqRl3I%$62p_5*sW-r_Oq1uQ01zp<1lm-8O{ULk-w^G+tiW2-00Xxs00{E?u7 zuh%qZMVsxJ&$hDN2xhWml&fTCpb4@^LX$LgRa-h$z>~_Dfa|6{g#{4g^(l^U@WEoJ zmq5*9O|}q|Ii~NllrR{B*V#>IaKCS`RUw1*aq7g+NO+;U2*H62W>=eukeNHxviPkh z{IEn!D7*XiO{?~>M->QVjkc&X&{x|EYF-{FM`9XwaIBgk_L~LN?A(h=qkJ_($!44! zi4DhSUyo^lF~M*=76`|q_FW9eV(5|hKkkKtgYiTekmoQ3X8H!-gP?rOb*4#yUx4Lf zuCoj-HVrdEoJ{m=oBchrDe%<(z;gr_mk)u?HD%wTOg;qql(c1WUd3Zcmi3Cuk1WlT z{pf=CeBUW^?tE69${qkU!hL~oH_iM%dmjBNGTj@bo)e#gsXdIN(3`$r6(A^QhBsrI z^)26|Y1S?FDQORe=AeXU74k!W;H&8fHC$J~5pn-PU@k1^eSW%nvw)9$y~dUO#+Aho z7gs(Fxbm@$i3>iah_XuvjsC=Qh4#x;;5oA``{1`ehqDmm(gcnOhx8e^AlwT4T>%a! zV>$d_I5dH{aQG>};YYhaIoyjf;c&|1K>LG*!}(3H@R!eFIt01Wfg|J)ZNh(IZC)`p zH~2QM25kOoKS!I#e4GCjYQn_Dun@r~<12HJy97b`wr<1`ze~$*%p{9 z7I>eH=e^8(fF(6kMZUOvZ?~xINBO2Z-au>GshGo&EPFnxN{q4`4p%1SGMsJX`zy*M zMwt$I1ye4zW-aZ0EgPK9O@Vjy&x_e>IXQ~b;@XPn29ps zP_Gb&Gn`+w^Es@5;84b>or%e(z!IbD4gM3D%R0Kf;aD1TE$PBH82ksYbm1Evjz7gE zo*ksDxO}p^qv$CvpXlywk?GLX~>4 zTg~u;*9h8+;vbI0oC;?7Jk6Lf+sUjJYkPeMRC>Ok?H7Z#mlU%7CY#|k-*#WvYo9%s@dLE{%JLCxCoLL{3Ym)i~iF@W?;EHbp-{^5hyNAeuppMhLOsU8)Hqr5o_{Im2r)q=bP~L zNSJ)pH~Cf}lO5Q%_xUD2hv3)Dv3;=({E=_bc(q@(g^6GTKg{4A_tpFjHC$fC5f1s$ z?((&-$8?u(OoNrh`jABAyFi1TP@_ow29#Zb4*hMR2CJNm!{>dQ10cx7Y8;W%%uHY| zxakW9zX8k@H~o77P8VZ2T{4`$g1B({E5PZpdY+t0{Lka`XCY2oI539sL3LQRGaEus zo^uOpUXgJql5=-N$6OO9>xT-2a|m0dq_5@(YV| zZf`&=>)SB{VxIOcZp}zgUN6s2lydqzdUhI`#OsQ>;Fx@zmpn~;N|m3pEH268h+J!S z2IiW)Zt8n#o@r|CGcaRt3(d!-gNYOR0F^rXY9IF1b~c{9;H&N8d*)q~-0bT$iPTLz z%iaTVp?{0eH{))1T_GN>{ev<|q#l}$6XfvlDsUL=amb%w4HXmK`RndNmHID3rH6gu z84E0ncN=gd_N4fTPt*)iqlKua^iff2SdP)X$+A84hzE}qYJht_h5DJk!I2P@Z{g3> zqu@>E0$|>)(6fX<=1ySVuF$g${vDY2EA*TK1fPl}__T>+`Gp>j`GH8z(=*BOK9or$ zpD7fS#C5Rns?Tu-1cl?PhT}$H;rN=*!E4RcJ_pm9YYd0uK8N)I4r}!Za^M6~IIJtg z;fBTFu&ZEez87rG_X|~ea3Crj@QGK2_Oh5V;z-PN`E#GA83qsPV%1|asrdt_^u4d9 zE7XYp-y8o&0gL}9O`EI&9>W?x7&H0(_LOhO^xK~#9{CfQpLH9UHEDBN7fV1m4nUKn z&Dla}ll>+PSojGABt1T4gWw1y%aydL3M`atS0)*KfE%?y4abe0TWQxDvJ55H^@bAD zujAYY^C^Yg8%Sv)H2sU{=mfXmP_nNl4wvvLZH6FkLg5H0MNbx`e3K?CN{h+t6TaR& z(QCp`#(jb|r@`)3Ck4>IK|Q;*fHKU^Ks3*X`g_ zG^2P&cUgfXye(>l!{E_NpbT4$Vlo}3;>nT!+T3$@NOv@qZ2^c(~wOy*^&%pJhO@ma&^ zG_Y{|pTVh1JdV!^F3y{ZdO?9~S`^E7vEkbYRfX@10lv?>^%B8%F3N=O3x)Xp$qe4? z^L-71GR4|#_#6c0JxcwS!B>EJlTvRH9PL$a$8vhda4Pqb$7y?j)4T4o~q866=0cy9W?k8V3~q_Ven;Qaz0dmK-GxP`z7G7{D&Pwy{rxH0A3g;$)L(QvBi)0O zhJGBl-HHD$JOi`ATF*Z zg`z7(MMBk!pllk-#MN*}202k^9{SDo9{QD^s^P#U5BY#Z_@S#H08hh_g!Ok-_o|v5(&^BAN z=`mjQIiig>&P4fKK*!~cGg1B&pmurVOiG^ybY0#!6F3hLpYfIbH(;<1rZQ@wt*=wh z1`Iw2Da+-pGI_3K0U(}={Q>gba7-;B!Nmdj7et=7#lEGxFB_!yJ6Uez+d$r$1NjXM=jwp`T9MBW=Q_%}ywN2duLs0Kz7tV>bt6>Y&Fb3% z)ptmB-ZT=wcS?2MGm`4NO!Zx;p3(x<_tCre1J(CRb>18j?>=C4JpTiE{`9pv!)G~9 z1>{eQe12>HY~=H*;VOyIYt84ES~$^UX(`Y8aN$z;v;u_wn<4)~Eyz!D@Jz(mfc$uo z&yU6g%10k6n+S+#Jca6OL0K(y@Vh$C2C6?N)w!l3PxUNdb$sO>@>ys=wZviL2IOB5 z`TWPcUNrI-Am5s~zlD!bZ3@V57WsU7ZyEWrxFRZv23PM>vw66eMWvfOt?fcy_4&kIy({U43|ddROs3aCFQ|64%*g2?Cd z|J}%c2l{wC{_Zf8%wEx)3n`2UG;iUjz)8zXTXCe;F`f{!c*jq^hv_3(!2ND)4W>usa4J zX=TImKY$VUGR8H5R{)c(&`l`vUM=W_-Pl(P2C&|J*pwcFop6FKf|Qar76YLKQa?M+ z3q~BE?u7GTKz5B`e5Y(Tuo;xvNE1gAOs`;FK%!_}p#l7N@I@y&IP z3?f_}!U01S00V|90_rIDN`N7E0T?f$*2;kR`4_rXWCafVng2Uu8(F*^o7Ei6>LuV@ zRx4n%4k}tv!gT?W-KSaUA~sJWuT}E|JoMNk!JUc$L;}~0l`Mz9Nu`QoGZUGrZb(3P zY8#NcUQ{>so2ES=p4@EBN}aK(g?_1eFbG`&m2Q?w>$ZC_>BdU(rqTq~>kf!t`^b0m zO7K-lw=w!;hFu!{*N!a1e2}@)mFqu6~=WsVA z|Ea#*s!*Ha-pn^^nAdg^1MsQ3aFO47$ujmqf3!ZkYobf&<#D3n;|A7 z8w!mRfI_m7!1(1J$;JX}0t!jKk?xUfBD->5tEueDZLMaq%RZ=^GgQg!f4T*CqYkx< zaj2Cz)DtGep|(+nT7#68dIBZlP@94dt;fi&2Y3$s12J)E04YTBUcs}r<4`_O;EjOn zsd|vW`vKWg^L&Z_t`w? zp6c<f@WDvUY+3c+at9|shI(*>>t6p}LtliA7jOey&tCBk{uA878#Awc#_J)00A z_ufdKD=zSfCb`A*6n#KSnH7n7D}hW7JqDlkeClgsD=Tf{5%YElR6y|Wx`i0QCxhMka%>kDqL*IEM zItP3)5fW>0@`OK@ji1_8HSN(yRV$##c-qimRowl39u_lkNagY*%-{XsV5>^-OrLhQ zhf3l^ANmmb88cPNw&OUbh!ey)pqWT1&H>FtDqfetGkUgKiz5q%f=4;E6}#pWDe+y^DQr*)HA=1-EY?)BiqvTBVZu8M}AqpcN*>ALs#boxo?n7U7@WX9)WheqJFl z{JLe`@c{u{NURHZ%bS3?^da#!z_T3OOGxQNz(Fi6$x^Ebb?p^>Z{*=<6+28cxFRil zg*MZ*?2`zxo%k9mGEBOb==lur>m))JC;f*?wNWXeR>K_xRyX5ujLzKf&+fAkt)Oq> zbHjGvXbB2;HAiW7qL{w%Ssd*lN8`<}3dgYltsceL;Mb+0)NAm*kbLFQXoFWO0*R8n zDDXe^2t2jasE#nH{zb=h)Ku&%HHi^Wzo6;HI%)|HjdRrJJMiflN1f}5RB+U52+u@E zwFJ9Kj`{?IC*uWUoEgVzdBJgy8hMFp*{3`7O3enreCX|=8jDrmJIE)oSwF_|TPB8XTuEpzZ)C3OUPveZ4Ok*&7kc#awe z;o|BM94VzfM(VlhD{T9!p|Ek)w%kP^XWK>W&}IfLbOwb^=wA?bLNnSTXrb`*1Re8LVr09zOB z(0K@6vO{~|~;rfb&1#h z39}*3x2VUq!RU z#V4Cyl#<#OCCMLQFp0M#d<%Er929pIHT(La!cVD#akMDokUElktdN$F0#E*Dhp=y_ zzcikpANCV#VnD;Ih}gB#n!z_U*XFaCp3K~+%+owl;P0VNYwFcXSA4Ef!#aUB2DmXu7p8#6BJ7^G&CK#}9v zo<(66bK`9aoTfnP8Z0)80MmrEf1-xX6kwiYU4n)U6tJ?%f13EvADv%~u#L+(O`)S^ zoE({f|B}e1CFE|UsZn-HSf~tulu$_`mD^A@h2>-Ha9Eg(^_gI;9R=(c!*Ov+Z3C<%}R3_ z-2?V&06SfJ*a61YO5VQ$-N4S>i4?@i)L6GmNZk^S)Qwbt9EWRrZ|b+GU-B1h9>f@9 zm$yst{}emdE=}X?$#zLD!IoxAlek?9P?tW6p+hpWG+=z`1s~vrQkIqh43{P!va!no zMoQB!fq8(*R%#EZE!rEKl7q0R(HMd`b}9ZJZx^#mGmFGjCd4hNEvB*nwUrI>@@MyX zhsk@;kMMRxOx!$Zvnk6sb4|-0vgu^f8`$`N7|p_&cAwpHs4PdhFJ+{oOx{n>BPC^h zal20hXSlpd7+^|ZCy8T~|EJDk(QPnFWWch@QBiyqw*T}wVAsR2%b5TPA=g9Nul!12 zp37BFNyG|Rz_3&=X)UYb9CS0gmTekWnhuCQS*iRzhobbS;VBv`4!3XT@NO@;55VgFXyM@ij5KqK_Cs1Bc~ zF_np2@*y0k$!^m?r;fpX<&Mm$Dx_P58Bv9zR_c5dRjtO1x@IYMZL4~%8pxfv+!UPi z)E3Wi+jzbu;Q1E3Rs{5N}G>Fi{IgOwyNkD@jJ*Q zeG%Hg+g(1MyHsiA-piJs9ozS2OW!MD?UW*E%yP_=Es`s(IgVfOa^*cgS1w4dyeviT=j=ExMXKKw3fp^b=gkHv^$%V>3tBYje~Vr@VA|a zVe$zavTEFp1M~5(CZ{y=XUpLIj|@#otKgI`g6~6cczp|pYFY1L8$N&yj(mmxlGZ_8 zzT~^Q7>;klp<335*oF^t^F1~=e2)A7V3XAm^y5%DYbtiqxN{CWrLa?@zsD=q z)q=nZ99qxAMI4L<-CD3>2dP-`366%-{m#W61SE}?TZQM3)83%t4Xmflpj_57YT11* zq@q!-1zWo)to@$om!NJdv1Vf+v5>XqFY8W-MkvaW#{a|Zt!xnEn1jD;rb@M{99kxm z!);!I*-hNzT>&64uM_#X)J*fez2=Ccg(c?6o-2Q0={QD7u?;XBfBepl2uksrCw;qWcIOLR7iopWP=p1@gmv3sa~O;lvTK@Ro03xavd; z-}n|rL@gLisB>SyLdCgECj91^oU4s{^F8q?V+re-%0bf zqb$Fh*1=N_{LDhuL>!gw+8RowyN<^WyK7PloX`9}b+dLc@Z7{h(JW7esPyAq*vaq5 z`E{fp_h3KHTLFRCo?P}Mnl85*)1L!UUh-z+ab5%La3spR13UCO?@8>4<7=>U8b`7| z#1121)m@i?IDe;XujhWJWJ(msf~S!#>a5=oKcb@at+OJSv_Imrh15Ak9}ml+w2wc< zk)5Xf(q-nhlhY?NbKl7+a=NtOPBvho&PFg-cFd;H2du(G3}yb1SfzxGYIhzSCbI&{)|! z5$Cjnh;*gOaxTsp4lKkgYa#v&un?~z=j5D&z;a%$BIjkE@ML09O(qsrSq5k1HOvGi zbl%?(XA0NndDrvmOEZ0xG(IY` zu_T^a{XcaSzx45co?^oVPOxR5@&7!*hP~6PRnAvx_xYK=T<1luA8MZG^y@m)0@Hz8 z^%5*Cu)*hgorzu`CJlHp#n<`GpneR3ua1!;az+Vn^I}rnm6+59w%15G2c_~ofu(6{ zxtwj5zaLlvQPZWudUG*Gs$SSbZ{jq^|5FWI%!j&}iB3$k8o9~;4|9?CVCVk~Rdg;= z`%lg(PC$@hQ_T#)wC$KwVIHiSx;)nvxaMl-ra^&RS;e+t$Nv{r_5Xhu#a`cac9VTu z!83dUXW!5J?a>BwB}bok*&gT;l9A85yhtcN1*I}dzv%MHwgJ}|QZN%!FS`k{43#w; z5@vZJl>>PyuV_NreT>b8%D*kVUjh=>0hUH$5|3V39gm`r!Zb=Et8cvA| zPM#IF2D#=VZN5gu1uUm#`~=9DPktwurLRPD0aZj6arOVgvWOd&Mcl9~;)Z1rH!O>| zVY7%EHjB7nvxpl$*OQC5VY7%EZizIQg||82LzKjGXzcB~zK8$;OG|C)#QP?!boP254r187w2IN>`7$&L;$nj<@} z>i}QpixSNWmP;ZtKwmA zRXo16gUd0vtnZ5F$|@Ia<0?t!a?v(4A*Cgby6v!u?~0AN6BOSEyXH<%f?2msWsvx1 z_qiU8fv>jNO))mvMVD^(W>a&W!>CmB3t(A*o9nRCq+!w_gTvF#w-!m9_Ww(<8#<8Ojl zaxC@$T1%C;O?)$C)?8jBGRPVw93?X(#^vq*kF)oHlA_w$KbyrWPiA-W*PXY#J zhMXh_0s=}7B1jMcMS=>7pky�re_oL`94QQ81&RDCUd_6Dnp@OkB9W@7t%Ur-%9P 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�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
From 2043923ef4c7ac2c8039e6ea3b550753cccbcc1f Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 2 May 2024 12:04:42 -0400 Subject: [PATCH 55/72] feat: add txn subcommand --- cmd/soroban-cli/src/commands/mod.rs | 8 +- cmd/soroban-cli/src/commands/txn/mod.rs | 48 ++++ cmd/soroban-cli/src/commands/txn/send.rs | 35 +++ cmd/soroban-cli/src/commands/txn/sign.rs | 97 +++++++ cmd/soroban-cli/src/commands/txn/simulate.rs | 38 +++ cmd/soroban-cli/src/commands/txn/xdr.rs | 67 +++++ cmd/soroban-cli/src/lib.rs | 2 + cmd/soroban-cli/src/signer.rs | 277 +++++++++++++++++++ 8 files changed, 571 insertions(+), 1 deletion(-) create mode 100644 cmd/soroban-cli/src/commands/txn/mod.rs create mode 100644 cmd/soroban-cli/src/commands/txn/send.rs create mode 100644 cmd/soroban-cli/src/commands/txn/sign.rs create mode 100644 cmd/soroban-cli/src/commands/txn/simulate.rs create mode 100644 cmd/soroban-cli/src/commands/txn/xdr.rs create mode 100644 cmd/soroban-cli/src/signer.rs diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index f904c465f..df28339bd 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -12,8 +12,8 @@ pub mod global; pub mod keys; pub mod network; pub mod plugin; +pub mod txn; pub mod version; - pub mod txn_result; pub const HEADING_RPC: &str = "Options (RPC)"; @@ -101,6 +101,7 @@ impl Root { Cmd::Network(network) => network.run().await?, Cmd::Version(version) => version.run(), Cmd::Keys(id) => id.run().await?, + Cmd::Txn(tx) => tx.run().await?, Cmd::Cache(data) => data.run()?, }; Ok(()) @@ -135,6 +136,9 @@ pub enum Cmd { Network(network::Cmd), /// Print version information Version(version::Cmd), + /// Sign, Simulate, and Send transactions + #[command(subcommand)] + Txn(txn::Cmd), /// Cache for tranasctions and contract specs #[command(subcommand)] Cache(cache::Cmd), @@ -158,6 +162,8 @@ pub enum Error { #[error(transparent)] Network(#[from] network::Error), #[error(transparent)] + Txn(#[from] txn::Error), + #[error(transparent)] Cache(#[from] cache::Error), } diff --git a/cmd/soroban-cli/src/commands/txn/mod.rs b/cmd/soroban-cli/src/commands/txn/mod.rs new file mode 100644 index 000000000..9de360fcc --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/mod.rs @@ -0,0 +1,48 @@ +use clap::Parser; + +pub mod send; +pub mod sign; +pub mod simulate; +pub mod xdr; + +use stellar_xdr::cli as xdr_cli; + +#[derive(Debug, Parser)] +pub enum Cmd { + /// Add a new identity (keypair, ledger, macOS keychain) + Inspect(xdr_cli::Root), + /// Given an identity return its address (public key) + Sign(sign::Cmd), + /// Submit a transaction to the network + Send(send::Cmd), + /// Simulate a transaction + Simulate(simulate::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + /// An error during the simulation + #[error(transparent)] + Simulate(#[from] simulate::Error), + /// An error during the inspect + #[error(transparent)] + Inspect(#[from] xdr_cli::Error), + /// An error during the sign + #[error(transparent)] + Sign(#[from] sign::Error), + /// An error during the send + #[error(transparent)] + Send(#[from] send::Error), +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + match self { + Cmd::Inspect(cmd) => cmd.run()?, + Cmd::Sign(cmd) => cmd.run().await?, + Cmd::Send(cmd) => cmd.run().await?, + Cmd::Simulate(cmd) => cmd.run().await?, + }; + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/txn/send.rs b/cmd/soroban-cli/src/commands/txn/send.rs new file mode 100644 index 000000000..1cf3f8478 --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/send.rs @@ -0,0 +1,35 @@ +use soroban_rpc::GetTransactionResponse; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + XdrArgs(#[from] super::xdr::Error), + #[error(transparent)] + Config(#[from] super::super::config::Error), + #[error(transparent)] + Rpc(#[from] crate::rpc::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + #[clap(flatten)] + pub xdr_args: super::xdr::Args, + #[clap(flatten)] + pub config: super::super::config::Args, +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + let response = self.send().await?; + println!("{response:#?}"); + Ok(()) + } + + pub async fn send(&self) -> Result { + let txn_env = self.xdr_args.txn_envelope()?; + let network = self.config.get_network()?; + let client = crate::rpc::Client::new(&network.rpc_url)?; + Ok(client.send_transaction(&txn_env).await?) + } +} diff --git a/cmd/soroban-cli/src/commands/txn/sign.rs b/cmd/soroban-cli/src/commands/txn/sign.rs new file mode 100644 index 000000000..ec4bb1155 --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/sign.rs @@ -0,0 +1,97 @@ +use std::io; + +// use crossterm::{ +// event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, +// execute, +// terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, +// }; +use soroban_sdk::xdr::{self, Limits, TransactionEnvelope, WriteXdr}; + +use crate::signer::{self, InMemory, Stellar}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + XdrArgs(#[from] super::xdr::Error), + #[error(transparent)] + Signer(#[from] signer::Error), + #[error(transparent)] + Config(#[from] super::super::config::Error), + #[error(transparent)] + StellarStrkey(#[from] stellar_strkey::DecodeError), + #[error(transparent)] + Xdr(#[from] xdr::Error), + #[error(transparent)] + Io(#[from] io::Error), + #[error("User cancelled signing, perhaps need to add -y")] + UserCancelledSigning, +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + /// Confirm that a signature can be signed by the given keypair automatically. + #[arg(long, short = 'y')] + yes: bool, + #[clap(flatten)] + pub xdr_args: super::xdr::Args, + #[clap(flatten)] + pub config: super::super::config::Args, +} + +impl Cmd { + #[allow(clippy::unused_async)] + pub async fn run(&self) -> Result<(), Error> { + let envelope = self.sign()?; + println!("{}", envelope.to_xdr_base64(Limits::none())?.trim()); + Ok(()) + } + + pub fn sign(&self) -> Result { + let source = &self.config.source_account; + tracing::debug!("signing transaction with source account {}", source); + let txn = self.xdr_args.txn()?; + let key = self.config.key_pair()?; + let address = + stellar_strkey::ed25519::PublicKey::from_payload(key.verifying_key().as_bytes())?; + let in_memory = InMemory { + network_passphrase: self.config.get_network()?.network_passphrase, + keypairs: vec![key], + }; + self.prompt_user()?; + Ok(in_memory.sign_txn(txn, &stellar_strkey::Strkey::PublicKeyEd25519(address))?) + } + + pub fn prompt_user(&self) -> Result<(), Error> { + if self.yes { + return Ok(()); + } + Err(Error::UserCancelledSigning) + // TODO use crossterm to prompt user for confirmation + // // Set up the terminal + // let mut stdout = io::stdout(); + // execute!(stdout, EnterAlternateScreen)?; + // terminal::enable_raw_mode()?; + + // println!("Press 'y' or 'Y' for yes, any other key for no:"); + + // loop { + // if let Event::Key(KeyEvent { + // code, + // modifiers: KeyModifiers::NONE, + // .. + // }) = event::read()? + // { + // match code { + // KeyCode::Char('y' | 'Y') => break, + // _ => return Err(Error::UserCancelledSigning), + // } + // } + // } + + // // Clean up the terminal + // terminal::disable_raw_mode()?; + // execute!(stdout, LeaveAlternateScreen)?; + // Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/txn/simulate.rs b/cmd/soroban-cli/src/commands/txn/simulate.rs new file mode 100644 index 000000000..5fbae28c0 --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/simulate.rs @@ -0,0 +1,38 @@ +use soroban_rpc::Assembled; +use soroban_sdk::xdr::{self, WriteXdr}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + XdrArgs(#[from] super::xdr::Error), + #[error(transparent)] + Config(#[from] super::super::config::Error), + #[error(transparent)] + Rpc(#[from] crate::rpc::Error), + #[error(transparent)] + Xdr(#[from] xdr::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + #[clap(flatten)] + pub xdr_args: super::xdr::Args, + #[clap(flatten)] + pub config: super::super::config::Args, +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + let res = self.simulate().await?; + println!("{}", res.transaction().to_xdr_base64(xdr::Limits::none())?); + Ok(()) + } + + pub async fn simulate(&self) -> Result { + let tx = self.xdr_args.txn()?; + let network = self.config.get_network()?; + let client = crate::rpc::Client::new(&network.rpc_url)?; + Ok(client.create_assembled_transaction(&tx).await?) + } +} diff --git a/cmd/soroban-cli/src/commands/txn/xdr.rs b/cmd/soroban-cli/src/commands/txn/xdr.rs new file mode 100644 index 000000000..180c12e6b --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/xdr.rs @@ -0,0 +1,67 @@ +use std::{ + io::{stdin, Read}, + path::PathBuf, +}; + +use soroban_env_host::xdr::ReadXdr; +use soroban_sdk::xdr::{Limits, Transaction, TransactionEnvelope}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("failed to decode XDR from base64")] + Base64Decode, + #[error("failed to decode XDR from file: {0}")] + FileDecode(PathBuf), + #[error("failed to decode XDR from stdin")] + StdinDecode, + #[error(transparent)] + Io(#[from] std::io::Error), +} + +/// XDR input, either base64 encoded or file path and stdin if neither is provided +#[derive(Debug, clap::Args, Clone)] +#[group(skip)] +pub struct Args { + /// Base64 encoded XDR transaction + #[arg( + long = "xdr-base64", + env = "STELLAR_TXN_XDR_BASE64", + conflicts_with = "xdr_file" + )] + pub xdr_base64: Option, + //// File containing Binary encoded data + #[arg( + long = "xdr-file", + env = "STELLAR_TXN_XDR_FILE", + conflicts_with = "xdr_base64" + )] + pub xdr_file: Option, +} + +impl Args { + pub fn xdr(&self) -> Result { + match (self.xdr_base64.as_ref(), self.xdr_file.as_ref()) { + (Some(xdr_base64), None) => { + T::from_xdr_base64(xdr_base64, Limits::none()).map_err(|_| Error::Base64Decode) + } + (_, Some(xdr_file)) => T::from_xdr(std::fs::read(xdr_file)?, Limits::none()) + .map_err(|_| Error::FileDecode(xdr_file.clone())), + + _ => { + let mut buf = String::new(); + let _ = stdin() + .read_to_string(&mut buf) + .map_err(|_| Error::StdinDecode)?; + T::from_xdr_base64(buf.trim(), Limits::none()).map_err(|_| Error::StdinDecode) + } + } + } + + pub fn txn(&self) -> Result { + self.xdr::() + } + + pub fn txn_envelope(&self) -> Result { + self.xdr::() + } +} diff --git a/cmd/soroban-cli/src/lib.rs b/cmd/soroban-cli/src/lib.rs index 5cde45436..9a9900605 100644 --- a/cmd/soroban-cli/src/lib.rs +++ b/cmd/soroban-cli/src/lib.rs @@ -8,11 +8,13 @@ use std::path::Path; pub(crate) use soroban_env_host::xdr; pub(crate) use soroban_rpc as rpc; + pub mod commands; pub mod fee; pub mod key; pub mod log; pub mod toid; +pub mod signer; pub mod utils; pub mod wasm; diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs new file mode 100644 index 000000000..bd5605598 --- /dev/null +++ b/cmd/soroban-cli/src/signer.rs @@ -0,0 +1,277 @@ +use ed25519_dalek::Signer; +use sha2::{Digest, Sha256}; + +use soroban_env_host::xdr::{ + self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, + InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, + ScVal, Signature, SignatureHint, SorobanAddressCredentials, SorobanAuthorizationEntry, + SorobanAuthorizedFunction, SorobanCredentials, Transaction, TransactionEnvelope, + TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, + TransactionV1Envelope, Uint256, WriteXdr, +}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Xdr(#[from] xdr::Error), + #[error("Error signing transaction {address}")] + MissingSignerForAddress { address: String }, +} + +fn requires_auth(txn: &Transaction) -> Option { + let [op @ Operation { + body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }), + .. + }] = txn.operations.as_slice() + else { + return None; + }; + matches!( + auth.first().map(|x| &x.root_invocation.function), + Some(&SorobanAuthorizedFunction::ContractFn(_)) + ) + .then(move || op.clone()) +} + +/// A trait for signing Stellar transactions and Soroban authorization entries +pub trait Stellar { + /// The type of the options that can be passed when creating a new signer + type Init; + /// Create a new signer with the given network passphrase and options + fn new(network_passphrase: &str, options: Option) -> Self; + + /// Get the network hash + fn network_hash(&self) -> xdr::Hash; + + /// Sign a transaction hash with the given source account + /// # Errors + /// Returns an error if the source account is not found + fn sign_txn_hash( + &self, + txn: [u8; 32], + source_account: &stellar_strkey::Strkey, + ) -> Result; + + /// Sign a Soroban authorization entry with the given address + /// # Errors + /// Returns an error if the address is not found + fn sign_soroban_authorization_entry( + &self, + unsigned_entry: &SorobanAuthorizationEntry, + signature_expiration_ledger: u32, + address: &[u8; 32], + ) -> Result; + + /// Sign a Stellar transaction with the given source account + /// This is a default implementation that signs the transaction hash and returns a decorated signature + /// # Errors + /// Returns an error if the source account is not found + fn sign_txn( + &self, + txn: Transaction, + source_account: &stellar_strkey::Strkey, + ) -> Result { + let signature_payload = TransactionSignaturePayload { + network_id: self.network_hash(), + tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(txn.clone()), + }; + let hash = Sha256::digest(signature_payload.to_xdr(Limits::none())?).into(); + let decorated_signature = self.sign_txn_hash(hash, source_account)?; + Ok(TransactionEnvelope::Tx(TransactionV1Envelope { + tx: txn, + signatures: vec![decorated_signature].try_into()?, + })) + } + + /// Sign a Soroban authorization entries for a given transaction and set the expiration ledger + /// # Errors + /// Returns an error if the address is not found + fn sign_soroban_authorizations( + &self, + raw: &Transaction, + signature_expiration_ledger: u32, + ) -> Result, Error> { + let mut tx = raw.clone(); + let Some(mut op) = requires_auth(&tx) else { + return Ok(None); + }; + + let xdr::Operation { + body: OperationBody::InvokeHostFunction(ref mut body), + .. + } = op + else { + return Ok(None); + }; + + let signed_auths = body + .auth + .as_slice() + .iter() + .map(|raw_auth| { + self.maybe_sign_soroban_authorization_entry(raw_auth, signature_expiration_ledger) + }) + .collect::, Error>>()?; + + body.auth = signed_auths.try_into()?; + tx.operations = vec![op].try_into()?; + Ok(Some(tx)) + } + + /// Sign a Soroban authorization entry if the address is public key + /// # Errors + /// Returns an error if the address in entry is a contract + fn maybe_sign_soroban_authorization_entry( + &self, + unsigned_entry: &SorobanAuthorizationEntry, + signature_expiration_ledger: u32, + ) -> Result { + if let SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(SorobanAddressCredentials { ref address, .. }), + .. + } = unsigned_entry + { + // See if we have a signer for this authorizationEntry + // If not, then we Error + let needle = match address { + ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(ref a)))) => a, + ScAddress::Contract(Hash(c)) => { + // This address is for a contract. This means we're using a custom + // smart-contract account. Currently the CLI doesn't support that yet. + return Err(Error::MissingSignerForAddress { + address: stellar_strkey::Strkey::Contract(stellar_strkey::Contract(*c)) + .to_string(), + }); + } + }; + self.sign_soroban_authorization_entry( + unsigned_entry, + signature_expiration_ledger, + needle, + ) + } else { + Ok(unsigned_entry.clone()) + } + } +} + +use std::fmt::Debug; +#[derive(Debug)] +pub struct InMemory { + pub network_passphrase: String, + pub keypairs: Vec, +} + +impl InMemory { + pub fn get_key( + &self, + key: &stellar_strkey::Strkey, + ) -> Result<&ed25519_dalek::SigningKey, Error> { + match key { + stellar_strkey::Strkey::PublicKeyEd25519(stellar_strkey::ed25519::PublicKey(bytes)) => { + self.keypairs + .iter() + .find(|k| k.verifying_key().to_bytes() == *bytes) + } + _ => None, + } + .ok_or_else(|| Error::MissingSignerForAddress { + address: key.to_string(), + }) + } +} + +impl Stellar for InMemory { + type Init = Vec; + fn new(network_passphrase: &str, options: Option>) -> Self { + InMemory { + network_passphrase: network_passphrase.to_string(), + keypairs: options.unwrap_or_default(), + } + } + + fn sign_txn_hash( + &self, + txn: [u8; 32], + source_account: &stellar_strkey::Strkey, + ) -> Result { + let source_account = self.get_key(source_account)?; + let tx_signature = source_account.sign(&txn); + Ok(DecoratedSignature { + // TODO: remove this unwrap. It's safe because we know the length of the array + hint: SignatureHint( + source_account.verifying_key().to_bytes()[28..] + .try_into() + .unwrap(), + ), + signature: Signature(tx_signature.to_bytes().try_into()?), + }) + } + + fn sign_soroban_authorization_entry( + &self, + unsigned_entry: &SorobanAuthorizationEntry, + signature_expiration_ledger: u32, + signer: &[u8; 32], + ) -> Result { + let mut auth = unsigned_entry.clone(); + let SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(ref mut credentials), + .. + } = auth + else { + // Doesn't need special signing + return Ok(auth); + }; + let SorobanAddressCredentials { nonce, .. } = credentials; + + let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { + network_id: self.network_hash(), + invocation: auth.root_invocation.clone(), + nonce: *nonce, + signature_expiration_ledger, + }) + .to_xdr(Limits::none())?; + + let strkey = stellar_strkey::ed25519::PublicKey(*signer); + let payload = Sha256::digest(preimage); + let signer = self.get_key(&stellar_strkey::Strkey::PublicKeyEd25519(strkey))?; + let signature = signer.sign(&payload); + + let map = ScMap::sorted_from(vec![ + ( + ScVal::Symbol(ScSymbol("public_key".try_into()?)), + ScVal::Bytes( + signer + .verifying_key() + .to_bytes() + .to_vec() + .try_into() + .map_err(Error::Xdr)?, + ), + ), + ( + ScVal::Symbol(ScSymbol("signature".try_into()?)), + ScVal::Bytes( + signature + .to_bytes() + .to_vec() + .try_into() + .map_err(Error::Xdr)?, + ), + ), + ]) + .map_err(Error::Xdr)?; + credentials.signature = ScVal::Vec(Some( + vec![ScVal::Map(Some(map))].try_into().map_err(Error::Xdr)?, + )); + credentials.signature_expiration_ledger = signature_expiration_ledger; + auth.credentials = SorobanCredentials::Address(credentials.clone()); + + Ok(auth) + } + + fn network_hash(&self) -> xdr::Hash { + xdr::Hash(Sha256::digest(self.network_passphrase.as_bytes()).into()) + } +} From 8aa819c22412195df161b2f3fe96fd9a823895c7 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 2 May 2024 14:10:55 -0400 Subject: [PATCH 56/72] feat: first pass at using ledger to sign --- .gitignore | 1 + Cargo.lock | 1 + Cargo.toml | 4 ++ cmd/crates/stellar-ledger/src/lib.rs | 52 ++++++++++++--- cmd/soroban-cli/Cargo.toml | 2 + cmd/soroban-cli/src/commands/txn/sign.rs | 60 +++++++++++++---- cmd/soroban-cli/src/signer.rs | 83 +++++++++++++++++------- 7 files changed, 157 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index bb94c12be..77af5e3ee 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ captive-core/ !test.toml *.sqlite test_snapshots +.env \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 0fbcc1f9d..c0e470555 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4694,6 +4694,7 @@ dependencies = [ "soroban-spec-rust", "soroban-spec-tools", "soroban-spec-typescript", + "stellar-ledger", "stellar-rpc-client", "stellar-strkey 0.0.8", "stellar-xdr", diff --git a/Cargo.toml b/Cargo.toml index 213ae8b65..f9a429737 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,10 @@ version = "=21.0.1-preview.1" version = "=21.0.0-preview.1" path = "cmd/soroban-cli" +[workspace.dependencies.stellar-ledger] +version = "=21.0.0-preview.1" +path = "./cmd/crates/stellar-ledger" + [workspace.dependencies.soroban-rpc] package = "stellar-rpc-client" version = "=21.0.1" diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 89b6725c2..c3f7b202d 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -1,6 +1,9 @@ use futures::executor::block_on; use ledger_transport::{APDUCommand, Exchange}; -use ledger_transport_hid::{hidapi::HidError, LedgerHIDError}; +use ledger_transport_hid::{ + hidapi::{self, HidError}, + LedgerHIDError, TransportNativeHID, +}; use sha2::{Digest, Sha256}; use soroban_env_host::xdr::{Hash, Transaction}; @@ -11,7 +14,7 @@ use stellar_xdr::curr::{ TransactionV1Envelope, WriteXdr, }; -use crate::signer::{Error, Stellar}; +pub use crate::signer::{Error, Stellar}; mod signer; mod speculos; @@ -71,11 +74,41 @@ pub struct LedgerOptions { exchange: T, hd_path: slip10::BIP32Path, } +// let hidapi = HidApi::new().map_err(NEARLedgerError::HidApiError)?; +// TransportNativeHID::new(&hidapi).map_err(NEARLedgerError::LedgerHidError) +impl LedgerOptions { + pub fn new(hd_path: u32) -> Self { + let hd_path = bip_path_from_index(hd_path); + let hidapi = hidapi::HidApi::new().unwrap(); + LedgerOptions { + exchange: TransportNativeHID::new(&hidapi).unwrap(), + hd_path, + } + } +} pub struct LedgerSigner { network_passphrase: String, transport: T, - hd_path: slip10::BIP32Path, + pub hd_path: slip10::BIP32Path, +} + +pub struct NativeSigner(LedgerSigner); + +impl AsRef> for NativeSigner { + fn as_ref(&self) -> &LedgerSigner { + &self.0 + } +} + +impl From<(String, u32)> for NativeSigner { + fn from((network_passphrase, hd_path): (String, u32)) -> Self { + Self(LedgerSigner { + network_passphrase, + transport: TransportNativeHID::new(&hidapi::HidApi::new().unwrap()).unwrap(), + hd_path: bip_path_from_index(hd_path), + }) + } } impl LedgerSigner @@ -114,7 +147,7 @@ where pub async fn sign_transaction_hash( &self, hd_path: slip10::BIP32Path, - transaction_hash: Vec, + transaction_hash: &[u8], ) -> Result, LedgerError> { let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); @@ -123,7 +156,7 @@ where data.insert(0, HD_PATH_ELEMENTS_COUNT); data.append(&mut hd_path_to_bytes); - data.append(&mut transaction_hash.clone()); + data.extend_from_slice(transaction_hash); let command = APDUCommand { cla: CLA, @@ -284,7 +317,7 @@ impl Stellar for LedgerSigner { txn: [u8; 32], _source_account: &stellar_strkey::Strkey, ) -> Result { - let signature = block_on(self.sign_transaction_hash(self.hd_path.clone(), txn.to_vec())) //TODO: refactor sign_transaction_hash + let signature = block_on(self.sign_transaction_hash(self.hd_path.clone(), &txn)) //TODO: refactor sign_transaction_hash .unwrap(); // FIXME: handle error let sig_bytes = signature.try_into().unwrap(); // FIXME: handle error @@ -516,10 +549,9 @@ mod test { let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); - let test_hash = - "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889".as_bytes(); + let test_hash = b"3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889"; - let result = ledger.sign_transaction_hash(path, test_hash.into()).await; + let result = ledger.sign_transaction_hash(path, test_hash).await; if let Err(LedgerError::APDUExchangeError(msg)) = result { assert_eq!(msg, "Ledger APDU retcode: 0x6C66"); // this error code is SW_TX_HASH_SIGNING_MODE_NOT_ENABLED https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md @@ -563,7 +595,7 @@ mod test { } } - let result = ledger.sign_transaction_hash(path, test_hash).await; + let result = ledger.sign_transaction_hash(path, &test_hash).await; match result { Ok(response) => { diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index b60684f1f..99147b4d8 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -46,6 +46,8 @@ soroban-ledger-snapshot = { workspace = true } stellar-strkey = { workspace = true } soroban-sdk = { workspace = true } soroban-rpc = { workspace = true } +stellar-ledger ={ workspace = true } + cargo_toml = "0.20.1" clap = { workspace = true, features = [ "derive", diff --git a/cmd/soroban-cli/src/commands/txn/sign.rs b/cmd/soroban-cli/src/commands/txn/sign.rs index ec4bb1155..c5d919a12 100644 --- a/cmd/soroban-cli/src/commands/txn/sign.rs +++ b/cmd/soroban-cli/src/commands/txn/sign.rs @@ -5,7 +5,9 @@ use std::io; // execute, // terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, // }; -use soroban_sdk::xdr::{self, Limits, TransactionEnvelope, WriteXdr}; +use soroban_sdk::xdr::{self, Limits, Transaction, TransactionEnvelope, WriteXdr}; +use stellar_ledger::NativeSigner; +use stellar_strkey::Strkey; use crate::signer::{self, InMemory, Stellar}; @@ -31,35 +33,39 @@ pub enum Error { #[group(skip)] pub struct Cmd { /// Confirm that a signature can be signed by the given keypair automatically. - #[arg(long, short = 'y')] + #[arg(long, short = 'y', short = 'Y')] yes: bool, #[clap(flatten)] pub xdr_args: super::xdr::Args, #[clap(flatten)] pub config: super::super::config::Args, + + #[arg(long, value_enum, default_value = "file")] + pub signer: SignerType, +} + +#[derive(clap::ValueEnum, Clone, Debug)] +pub enum SignerType { + File, + Ledger, } impl Cmd { #[allow(clippy::unused_async)] pub async fn run(&self) -> Result<(), Error> { - let envelope = self.sign()?; + let envelope = self.sign().await?; println!("{}", envelope.to_xdr_base64(Limits::none())?.trim()); Ok(()) } - pub fn sign(&self) -> Result { + pub async fn sign(&self) -> Result { let source = &self.config.source_account; tracing::debug!("signing transaction with source account {}", source); let txn = self.xdr_args.txn()?; - let key = self.config.key_pair()?; - let address = - stellar_strkey::ed25519::PublicKey::from_payload(key.verifying_key().as_bytes())?; - let in_memory = InMemory { - network_passphrase: self.config.get_network()?.network_passphrase, - keypairs: vec![key], - }; - self.prompt_user()?; - Ok(in_memory.sign_txn(txn, &stellar_strkey::Strkey::PublicKeyEd25519(address))?) + match self.signer { + SignerType::File => self.sign_file(txn).await, + SignerType::Ledger => self.sign_ledger(txn).await, + } } pub fn prompt_user(&self) -> Result<(), Error> { @@ -94,4 +100,32 @@ impl Cmd { // execute!(stdout, LeaveAlternateScreen)?; // Ok(()) } + + pub async fn sign_file(&self, txn: Transaction) -> Result { + let key = self.config.key_pair()?; + let address = + stellar_strkey::ed25519::PublicKey::from_payload(key.verifying_key().as_bytes())?; + let in_memory = InMemory { + network_passphrase: self.config.get_network()?.network_passphrase, + keypairs: vec![key], + }; + self.prompt_user()?; + Ok(in_memory + .sign_txn(txn, &Strkey::PublicKeyEd25519(address)) + .await?) + } + + pub async fn sign_ledger(&self, txn: Transaction) -> Result { + let index: u32 = self + .config + .hd_path + .unwrap_or_default() + .try_into() + .expect("usize bigger than u32"); + let signer: NativeSigner = (self.config.get_network()?.network_passphrase, index).into(); + let account = + Strkey::PublicKeyEd25519(signer.as_ref().get_public_key(index).await.unwrap()); + let bx_signer = Box::new(signer); + Ok(bx_signer.sign_txn(txn, &account).await.unwrap()) + } } diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index bd5605598..a160af587 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -1,14 +1,15 @@ use ed25519_dalek::Signer; -use sha2::{Digest, Sha256}; +use sha2::{digest::typenum::Le, Digest, Sha256}; use soroban_env_host::xdr::{ self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, - InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, - ScVal, Signature, SignatureHint, SorobanAddressCredentials, SorobanAuthorizationEntry, - SorobanAuthorizedFunction, SorobanCredentials, Transaction, TransactionEnvelope, - TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, + InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ReadXdr, ScAddress, ScMap, + ScSymbol, ScVal, Signature, SignatureHint, SorobanAddressCredentials, + SorobanAuthorizationEntry, SorobanAuthorizedFunction, SorobanCredentials, Transaction, + TransactionEnvelope, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, TransactionV1Envelope, Uint256, WriteXdr, }; +use stellar_ledger::{LedgerSigner, NativeSigner}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -50,7 +51,7 @@ pub trait Stellar { &self, txn: [u8; 32], source_account: &stellar_strkey::Strkey, - ) -> Result; + ) -> impl std::future::Future> + Send; /// Sign a Soroban authorization entry with the given address /// # Errors @@ -60,13 +61,13 @@ pub trait Stellar { unsigned_entry: &SorobanAuthorizationEntry, signature_expiration_ledger: u32, address: &[u8; 32], - ) -> Result; + ) -> impl std::future::Future> + Send; /// Sign a Stellar transaction with the given source account /// This is a default implementation that signs the transaction hash and returns a decorated signature /// # Errors /// Returns an error if the source account is not found - fn sign_txn( + async fn sign_txn( &self, txn: Transaction, source_account: &stellar_strkey::Strkey, @@ -76,7 +77,7 @@ pub trait Stellar { tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(txn.clone()), }; let hash = Sha256::digest(signature_payload.to_xdr(Limits::none())?).into(); - let decorated_signature = self.sign_txn_hash(hash, source_account)?; + let decorated_signature = self.sign_txn_hash(hash, source_account).await?; Ok(TransactionEnvelope::Tx(TransactionV1Envelope { tx: txn, signatures: vec![decorated_signature].try_into()?, @@ -86,7 +87,7 @@ pub trait Stellar { /// Sign a Soroban authorization entries for a given transaction and set the expiration ledger /// # Errors /// Returns an error if the address is not found - fn sign_soroban_authorizations( + async fn sign_soroban_authorizations( &self, raw: &Transaction, signature_expiration_ledger: u32, @@ -104,16 +105,13 @@ pub trait Stellar { return Ok(None); }; - let signed_auths = body - .auth - .as_slice() - .iter() - .map(|raw_auth| { - self.maybe_sign_soroban_authorization_entry(raw_auth, signature_expiration_ledger) - }) - .collect::, Error>>()?; - - body.auth = signed_auths.try_into()?; + let mut auths = body.auth.to_vec(); + for auth in auths.iter_mut() { + *auth = self + .maybe_sign_soroban_authorization_entry(auth, signature_expiration_ledger) + .await?; + } + body.auth = auths.try_into()?; tx.operations = vec![op].try_into()?; Ok(Some(tx)) } @@ -121,7 +119,7 @@ pub trait Stellar { /// Sign a Soroban authorization entry if the address is public key /// # Errors /// Returns an error if the address in entry is a contract - fn maybe_sign_soroban_authorization_entry( + async fn maybe_sign_soroban_authorization_entry( &self, unsigned_entry: &SorobanAuthorizationEntry, signature_expiration_ledger: u32, @@ -149,6 +147,7 @@ pub trait Stellar { signature_expiration_ledger, needle, ) + .await } else { Ok(unsigned_entry.clone()) } @@ -190,7 +189,7 @@ impl Stellar for InMemory { } } - fn sign_txn_hash( + async fn sign_txn_hash( &self, txn: [u8; 32], source_account: &stellar_strkey::Strkey, @@ -208,7 +207,7 @@ impl Stellar for InMemory { }) } - fn sign_soroban_authorization_entry( + async fn sign_soroban_authorization_entry( &self, unsigned_entry: &SorobanAuthorizationEntry, signature_expiration_ledger: u32, @@ -275,3 +274,41 @@ impl Stellar for InMemory { xdr::Hash(Sha256::digest(self.network_passphrase.as_bytes()).into()) } } + +impl Stellar for Box { + type Init = u32; + + fn new(network_passphrase: &str, options: Option) -> Self { + Box::new((network_passphrase.to_owned(), options.unwrap_or_default()).into()) + } + + fn network_hash(&self) -> xdr::Hash { + use stellar_ledger::Stellar; + self.as_ref().as_ref().network_hash() + } + + async fn sign_txn_hash( + &self, + txn: [u8; 32], + _source_account: &stellar_strkey::Strkey, + ) -> Result { + Ok(DecoratedSignature::from_xdr( + self.as_ref() + .as_ref() + .sign_transaction_hash(self.as_ref().as_ref().hd_path.clone(), &txn) + .await + .unwrap(), + Limits::none(), + ) + .unwrap()) + } + + async fn sign_soroban_authorization_entry( + &self, + unsigned_entry: &SorobanAuthorizationEntry, + signature_expiration_ledger: u32, + address: &[u8; 32], + ) -> Result { + todo!() + } +} From b9516a42654daa751220e1e8a9fc432347608574 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 2 May 2024 15:05:39 -0400 Subject: [PATCH 57/72] fix: can't decode the return value from signing hash --- cmd/soroban-cli/src/signer.rs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index a160af587..d72ce5459 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -292,15 +292,24 @@ impl Stellar for Box { txn: [u8; 32], _source_account: &stellar_strkey::Strkey, ) -> Result { - Ok(DecoratedSignature::from_xdr( - self.as_ref() - .as_ref() - .sign_transaction_hash(self.as_ref().as_ref().hd_path.clone(), &txn) - .await - .unwrap(), - Limits::none(), - ) - .unwrap()) + let index = self.as_ref().as_ref().hd_path.clone(); + let mut res = self + .as_ref() + .as_ref() + .sign_transaction_hash(index, &txn) + .await + .unwrap(); + println!("{}", base64::encode(&res)); + println!("{}", res.len()); + println!("{:#?}", Signature::from_xdr(&res, Limits::none())); + + todo!("Need to figure out how to get Signature"); + let source_account = self.as_ref().as_ref().get_public_key(0).await.unwrap(); + // Ok(DecoratedSignature { + // // TODO: remove this unwrap. It's safe because we know the length of the array + // hint: SignatureHint(source_account.0[28..].try_into().unwrap()), + // signature, + // }) } async fn sign_soroban_authorization_entry( From a5b6f9b3c24f3cf253cd66442cff77b2a7f54dba Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 2 May 2024 11:47:59 -0400 Subject: [PATCH 58/72] Cleanup --- cmd/crates/stellar-ledger/Cargo.toml | 1 - .../stellar-ledger/src/transport_zemu_http.rs | 36 +++---------------- 2 files changed, 5 insertions(+), 32 deletions(-) diff --git a/cmd/crates/stellar-ledger/Cargo.toml b/cmd/crates/stellar-ledger/Cargo.toml index 322debb3e..3761d36e8 100644 --- a/cmd/crates/stellar-ledger/Cargo.toml +++ b/cmd/crates/stellar-ledger/Cargo.toml @@ -45,7 +45,6 @@ features = ["curr", "std", "serde"] env_logger = "0.11.3" futures = "0.3.30" hidapi = { version = "1.4.1", features = ["linux-static-hidraw"], default-features = false } -ledger-zondax-generic = "0.10.0" log = "0.4.21" once_cell = "1.19.0" pretty_assertions = "1.2.1" diff --git a/cmd/crates/stellar-ledger/src/transport_zemu_http.rs b/cmd/crates/stellar-ledger/src/transport_zemu_http.rs index 9a05e37f0..14b28a2c7 100644 --- a/cmd/crates/stellar-ledger/src/transport_zemu_http.rs +++ b/cmd/crates/stellar-ledger/src/transport_zemu_http.rs @@ -1,34 +1,7 @@ -// I was getting an error when trying to install this as a crate, so i'm just going to take the pieces that i need an put them here since i am not using the gRpc transport right now anyway -// error: failed to run custom build command for `ledger-transport-zemu v0.10.0 (/Users/elizabethengelman/Projects/Aha-Labs/ledger-rs/ledger-transport-zemu)` - -// Caused by: -// process didn't exit successfully: `/Users/elizabethengelman/Projects/Aha-Labs/ledger-rs/target/debug/build/ledger-transport-zemu-e14fd4e52eee79e2/build-script-build` (exit status: 101) -// --- stdout -// cargo:rerun-if-changed=zemu.proto - -// --- stderr -// thread 'main' panicked at /Users/elizabethengelman/.cargo/registry/src/index.crates.io-6f17d22bba15001f/protoc-2.18.2/src/lib.rs:203:17: -// protoc binary not found: cannot find binary path -// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace -// warning: build failed, waiting for other jobs to finish... - -// this if from: https://github.com/Zondax/ledger-rs/blob/master/ledger-transport-zemu/src/lib.rs -// removed the grpc stuff -/******************************************************************************* -* (c) 2022 Zondax AG -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -********************************************************************************/ +// This is based on the `ledger-transport-zemu` crate's TransportZemuHttp: https://github.com/Zondax/ledger-rs/tree/master/ledger-transport-zemu +// Instead of using TransportZemuHttp mod from the crate, we are including a custom copy here for a couple of reasons: +// - we get more control over the mod for our testing purposes +// - the ledger-transport-zemu TransportZemuHttp includes a Grpc implementation that we don't need right now, and was causing some errors with dependency mismatches when trying to use the whole TransportZemuHttp mod. use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE}; use reqwest::{Client as HttpClient, Response}; @@ -67,6 +40,7 @@ struct ZemuResponse { } impl TransportZemuHttp { + #[allow(dead_code)] //this is being used in tests only pub fn new(host: &str, port: u16) -> Self { Self { url: format!("http://{host}:{port}"), From 8829da2f24c1a6f8aeb54e645afe16bced22c174 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 2 May 2024 12:30:37 -0400 Subject: [PATCH 59/72] Rename TransportZemuHttp -> EmulatorHttpTransport since we're not actually using the zemu framework --- ...t_zemu_http.rs => emulator_http_transport.rs} | 6 +++--- cmd/crates/stellar-ledger/src/emulator_tests.rs | 4 ++-- cmd/crates/stellar-ledger/src/lib.rs | 16 ++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) rename cmd/crates/stellar-ledger/src/{transport_zemu_http.rs => emulator_http_transport.rs} (96%) diff --git a/cmd/crates/stellar-ledger/src/transport_zemu_http.rs b/cmd/crates/stellar-ledger/src/emulator_http_transport.rs similarity index 96% rename from cmd/crates/stellar-ledger/src/transport_zemu_http.rs rename to cmd/crates/stellar-ledger/src/emulator_http_transport.rs index 14b28a2c7..66454254c 100644 --- a/cmd/crates/stellar-ledger/src/transport_zemu_http.rs +++ b/cmd/crates/stellar-ledger/src/emulator_http_transport.rs @@ -23,7 +23,7 @@ pub enum LedgerZemuError { InnerError, } -pub struct TransportZemuHttp { +pub struct EmulatorHttpTransport { url: String, } @@ -39,7 +39,7 @@ struct ZemuResponse { error: Option, } -impl TransportZemuHttp { +impl EmulatorHttpTransport { #[allow(dead_code)] //this is being used in tests only pub fn new(host: &str, port: u16) -> Self { Self { @@ -49,7 +49,7 @@ impl TransportZemuHttp { } #[async_trait] -impl Exchange for TransportZemuHttp { +impl Exchange for EmulatorHttpTransport { type Error = LedgerZemuError; type AnswerType = Vec; diff --git a/cmd/crates/stellar-ledger/src/emulator_tests.rs b/cmd/crates/stellar-ledger/src/emulator_tests.rs index c7aad3c40..dc1b4df16 100644 --- a/cmd/crates/stellar-ledger/src/emulator_tests.rs +++ b/cmd/crates/stellar-ledger/src/emulator_tests.rs @@ -5,8 +5,8 @@ use soroban_env_host::xdr::Transaction; use soroban_env_host::xdr::{self, Operation, OperationBody, Uint256}; use std::vec; +use crate::emulator_http_transport::EmulatorHttpTransport; use crate::speculos::Speculos; -use crate::transport_zemu_http::TransportZemuHttp; use crate::{LedgerError, LedgerOptions, LedgerSigner}; use std::sync::Arc; @@ -344,7 +344,7 @@ struct EventsResponse { } fn get_zemu_transport(host: &str, port: u16) -> Result { - Ok(TransportZemuHttp::new(host, port)) + Ok(EmulatorHttpTransport::new(host, port)) } async fn wait_for_emulator_start_text(ui_host_port: u16) { diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 89b6725c2..047ca1d31 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -13,9 +13,9 @@ use stellar_xdr::curr::{ use crate::signer::{Error, Stellar}; +mod emulator_http_transport; mod signer; mod speculos; -mod transport_zemu_http; #[cfg(all(test, feature = "emulator-tests"))] mod emulator_tests; @@ -337,7 +337,7 @@ mod test { use httpmock::prelude::*; use serde_json::json; - use crate::transport_zemu_http::TransportZemuHttp; + use crate::emulator_http_transport::EmulatorHttpTransport; use soroban_env_host::xdr::Transaction; use std::vec; @@ -367,7 +367,7 @@ mod test { .json_body(json!({"data": "e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd9000"})); }); - let transport = TransportZemuHttp::new(&server.host(), server.port()); + let transport = EmulatorHttpTransport::new(&server.host(), server.port()); let ledger_options = Some(LedgerOptions { exchange: transport, hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), @@ -405,7 +405,7 @@ mod test { .json_body(json!({"data": "000500039000"})); }); - let transport = TransportZemuHttp::new(&server.host(), server.port()); + let transport = EmulatorHttpTransport::new(&server.host(), server.port()); let ledger_options = Some(LedgerOptions { exchange: transport, hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), @@ -450,7 +450,7 @@ mod test { .json_body(json!({"data": "5c2f8eb41e11ab922800071990a25cf9713cc6e7c43e50e0780ddc4c0c6da50c784609ef14c528a12f520d8ea9343b49083f59c51e3f28af8c62b3edeaade60e9000"})); }); - let transport = TransportZemuHttp::new(&server.host(), server.port()); + let transport = EmulatorHttpTransport::new(&server.host(), server.port()); let ledger_options = Some(LedgerOptions { exchange: transport, hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), @@ -508,7 +508,7 @@ mod test { .json_body(json!({"data": "6c66"})); }); - let transport = TransportZemuHttp::new(&server.host(), server.port()); + let transport = EmulatorHttpTransport::new(&server.host(), server.port()); let ledger_options = Some(LedgerOptions { exchange: transport, hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), @@ -544,8 +544,8 @@ mod test { .json_body(json!({"data": "6970b9c9d3a6f4de7fb93e8d3920ec704fc4fece411873c40570015bbb1a60a197622bc3bf5644bb38ae73e1b96e4d487d716d142d46c7e944f008dece92df079000"})); }); - let transport = TransportZemuHttp::new(&server.host(), server.port()); - let ledger_options = Some(LedgerOptions { + let transport = EmulatorHttpTransport::new(&server.host(), server.port()); + let ledger_options: Option> = Some(LedgerOptions { exchange: transport, hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), }); From dfb5e3eb7ce9a67ad0ec5d645c4bb2c70abbd1c9 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 2 May 2024 13:06:19 -0400 Subject: [PATCH 60/72] Update error handling --- cmd/crates/stellar-ledger/src/lib.rs | 58 +++++++++++++++++-------- cmd/crates/stellar-ledger/src/signer.rs | 6 +-- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 047ca1d31..fa5a8337b 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -60,11 +60,14 @@ pub enum LedgerError { #[error("Error occurred while initializing Ledger HID transport: {0}")] LedgerHidError(#[from] LedgerHIDError), - #[error("Error with ADPU exchange with Ledger device: {0}")] //TODO update this message + #[error("Error with ADPU exchange with Ledger device: {0}")] APDUExchangeError(String), #[error("Error occurred while exchanging with Ledger device: {0}")] LedgerConnectionError(String), + + #[error("Error occurred while parsing BIP32 path: {0}")] + Bip32PathError(String), } pub struct LedgerOptions { @@ -89,7 +92,7 @@ where &self, index: u32, ) -> Result { - let hd_path = bip_path_from_index(index); + let hd_path = bip_path_from_index(index)?; Self::get_public_key_with_display_flag(self, hd_path, false).await } @@ -111,7 +114,7 @@ where /// based on impl from [https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166](https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166) /// # Errors /// Returns an error if there is an issue with connecting with the device or signing the given tx on the device. Or, if the device has not enabled hash signing - pub async fn sign_transaction_hash( + async fn sign_transaction_hash( &self, hd_path: slip10::BIP32Path, transaction_hash: Vec, @@ -139,8 +142,8 @@ where /// Sign a Stellar transaction with the account on the Ledger device /// # Errors /// Returns an error if there is an issue with connecting with the device or signing the given tx on the device - #[allow(clippy::missing_panics_doc)] // TODO: handle panics/unwraps - pub async fn sign_transaction( + #[allow(clippy::missing_panics_doc)] + async fn sign_transaction( &self, hd_path: slip10::BIP32Path, transaction: Transaction, @@ -152,7 +155,9 @@ where network_id: network_hash, tagged_transaction, }; - let mut signature_payload_as_bytes = signature_payload.to_xdr(Limits::none()).unwrap(); + let mut signature_payload_as_bytes = signature_payload + .to_xdr(Limits::none()) + .expect("tx payload should be able to be written as xdr"); let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); @@ -230,7 +235,8 @@ where tracing::info!("APDU in: {}", hex::encode(command.serialize())); match self.send_command_to_ledger(command).await { - Ok(value) => Ok(stellar_strkey::ed25519::PublicKey::from_payload(&value).unwrap()), + Ok(value) => Ok(stellar_strkey::ed25519::PublicKey::from_payload(&value) + .expect("payload should be able to be converted into PublicKey")), Err(err) => Err(err), } } @@ -267,7 +273,7 @@ impl Stellar for LedgerSigner { type Init = LedgerOptions; fn new(network_passphrase: &str, options: Option>) -> Self { - let options_unwrapped = options.unwrap(); + let options_unwrapped = options.expect("LedgerSigner should have LedgerOptions passed in"); LedgerSigner { network_passphrase: network_passphrase.to_string(), transport: options_unwrapped.exchange, @@ -282,12 +288,17 @@ impl Stellar for LedgerSigner { fn sign_txn_hash( &self, txn: [u8; 32], - _source_account: &stellar_strkey::Strkey, + source_account: &stellar_strkey::Strkey, ) -> Result { let signature = block_on(self.sign_transaction_hash(self.hd_path.clone(), txn.to_vec())) //TODO: refactor sign_transaction_hash - .unwrap(); // FIXME: handle error + .map_err(|e| { + tracing::error!("Error signing transaction hash with Ledger device: {e}"); + Error::MissingSignerForAddress { + address: source_account.to_string(), + } + })?; - let sig_bytes = signature.try_into().unwrap(); // FIXME: handle error + let sig_bytes = signature.try_into()?; Ok(DecoratedSignature { hint: SignatureHint([0u8; 4]), //FIXME signature: Signature(sig_bytes), @@ -299,9 +310,15 @@ impl Stellar for LedgerSigner { txn: Transaction, _source_account: &stellar_strkey::Strkey, ) -> Result { - let signature = block_on(self.sign_transaction(self.hd_path.clone(), txn.clone())).unwrap(); // FIXME: handle error + let signature = block_on(self.sign_transaction(self.hd_path.clone(), txn.clone())) + .map_err(|e| { + tracing::error!("Error signing transaction with Ledger device: {e}"); + Error::MissingSignerForAddress { + address: "source_account".to_string(), + } + })?; - let sig_bytes = signature.try_into().unwrap(); // FIXME: handle error + let sig_bytes = signature.try_into()?; let decorated_signature = DecoratedSignature { hint: SignatureHint([0u8; 4]), //FIXME signature: Signature(sig_bytes), @@ -309,22 +326,25 @@ impl Stellar for LedgerSigner { Ok(TransactionEnvelope::Tx(TransactionV1Envelope { tx: txn, - signatures: vec![decorated_signature].try_into().unwrap(), //fixme: remove unwrap + signatures: vec![decorated_signature].try_into()?, })) } } -fn bip_path_from_index(index: u32) -> slip10::BIP32Path { +fn bip_path_from_index(index: u32) -> Result { let path = format!("m/44'/148'/{index}'"); - path.parse().unwrap() // this is basically the same thing as slip10::BIP32Path::from_str - - // the device handles this part: https://github.com/AhaLabs/rs-sep5/blob/9d6e3886b4b424dd7b730ec24c865f6fad5d770c/src/seed_phrase.rs#L86 + path.parse().map_err(|e| { + let error_string = format!("Error parsing BIP32 path: {e}"); + LedgerError::Bip32PathError(error_string) + }) } fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { (0..hd_path.depth()) .flat_map(|index| { - let value = *hd_path.index(index).unwrap(); + let value = *hd_path + .index(index) + .expect("should be able to get index of hd path"); value.to_be_bytes() }) .collect::>() diff --git a/cmd/crates/stellar-ledger/src/signer.rs b/cmd/crates/stellar-ledger/src/signer.rs index e403c19ec..80032c23b 100644 --- a/cmd/crates/stellar-ledger/src/signer.rs +++ b/cmd/crates/stellar-ledger/src/signer.rs @@ -5,12 +5,12 @@ use soroban_env_host::xdr::{ TransactionV1Envelope, WriteXdr, }; -use soroban_rpc::Error as RpcError; - #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] - RpcError(#[from] RpcError), + Xdr(#[from] xdr::Error), + #[error("Error signing transaction {address}")] + MissingSignerForAddress { address: String }, } /// A trait for signing Stellar transactions and Soroban authorization entries From feec1a0a2c24d431ce5378db7062efca24552925 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 2 May 2024 13:06:19 -0400 Subject: [PATCH 61/72] Update error handling --- cmd/crates/stellar-ledger/src/lib.rs | 90 +++++++++++++++++-------- cmd/crates/stellar-ledger/src/signer.rs | 6 +- 2 files changed, 64 insertions(+), 32 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 047ca1d31..7fb79c4c7 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -5,8 +5,9 @@ use sha2::{Digest, Sha256}; use soroban_env_host::xdr::{Hash, Transaction}; use std::vec; +use stellar_strkey::DecodeError; use stellar_xdr::curr::{ - DecoratedSignature, Limits, Signature, SignatureHint, TransactionEnvelope, + DecoratedSignature, Error as XdrError, Limits, Signature, SignatureHint, TransactionEnvelope, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, TransactionV1Envelope, WriteXdr, }; @@ -60,11 +61,20 @@ pub enum LedgerError { #[error("Error occurred while initializing Ledger HID transport: {0}")] LedgerHidError(#[from] LedgerHIDError), - #[error("Error with ADPU exchange with Ledger device: {0}")] //TODO update this message + #[error("Error with ADPU exchange with Ledger device: {0}")] APDUExchangeError(String), #[error("Error occurred while exchanging with Ledger device: {0}")] LedgerConnectionError(String), + + #[error("Error occurred while parsing BIP32 path: {0}")] + Bip32PathError(String), + + #[error(transparent)] + XdrError(#[from] XdrError), + + #[error(transparent)] + DecodeError(#[from] DecodeError), } pub struct LedgerOptions { @@ -89,7 +99,7 @@ where &self, index: u32, ) -> Result { - let hd_path = bip_path_from_index(index); + let hd_path = bip_path_from_index(index)?; Self::get_public_key_with_display_flag(self, hd_path, false).await } @@ -111,12 +121,12 @@ where /// based on impl from [https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166](https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166) /// # Errors /// Returns an error if there is an issue with connecting with the device or signing the given tx on the device. Or, if the device has not enabled hash signing - pub async fn sign_transaction_hash( + async fn sign_transaction_hash( &self, hd_path: slip10::BIP32Path, transaction_hash: Vec, ) -> Result, LedgerError> { - let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); + let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path)?; let capacity = 1 + hd_path_to_bytes.len() + transaction_hash.len(); let mut data: Vec = Vec::with_capacity(capacity); @@ -139,8 +149,8 @@ where /// Sign a Stellar transaction with the account on the Ledger device /// # Errors /// Returns an error if there is an issue with connecting with the device or signing the given tx on the device - #[allow(clippy::missing_panics_doc)] // TODO: handle panics/unwraps - pub async fn sign_transaction( + #[allow(clippy::missing_panics_doc)] + async fn sign_transaction( &self, hd_path: slip10::BIP32Path, transaction: Transaction, @@ -152,9 +162,9 @@ where network_id: network_hash, tagged_transaction, }; - let mut signature_payload_as_bytes = signature_payload.to_xdr(Limits::none()).unwrap(); + let mut signature_payload_as_bytes = signature_payload.to_xdr(Limits::none())?; - let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); + let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path)?; let capacity = 1 + hd_path_to_bytes.len() + signature_payload_as_bytes.len(); let mut data: Vec = Vec::with_capacity(capacity); @@ -208,7 +218,7 @@ where ) -> Result { // convert the hd_path into bytes to be sent as `data` to the Ledger // the first element of the data should be the number of elements in the path - let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); + let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path)?; let hd_path_elements_count = hd_path.depth(); hd_path_to_bytes.insert(0, hd_path_elements_count); @@ -230,7 +240,7 @@ where tracing::info!("APDU in: {}", hex::encode(command.serialize())); match self.send_command_to_ledger(command).await { - Ok(value) => Ok(stellar_strkey::ed25519::PublicKey::from_payload(&value).unwrap()), + Ok(value) => Ok(stellar_strkey::ed25519::PublicKey::from_payload(&value)?), Err(err) => Err(err), } } @@ -267,7 +277,7 @@ impl Stellar for LedgerSigner { type Init = LedgerOptions; fn new(network_passphrase: &str, options: Option>) -> Self { - let options_unwrapped = options.unwrap(); + let options_unwrapped = options.expect("LedgerSigner should have LedgerOptions passed in"); LedgerSigner { network_passphrase: network_passphrase.to_string(), transport: options_unwrapped.exchange, @@ -282,12 +292,17 @@ impl Stellar for LedgerSigner { fn sign_txn_hash( &self, txn: [u8; 32], - _source_account: &stellar_strkey::Strkey, + source_account: &stellar_strkey::Strkey, ) -> Result { let signature = block_on(self.sign_transaction_hash(self.hd_path.clone(), txn.to_vec())) //TODO: refactor sign_transaction_hash - .unwrap(); // FIXME: handle error + .map_err(|e| { + tracing::error!("Error signing transaction hash with Ledger device: {e}"); + Error::MissingSignerForAddress { + address: source_account.to_string(), + } + })?; - let sig_bytes = signature.try_into().unwrap(); // FIXME: handle error + let sig_bytes = signature.try_into()?; Ok(DecoratedSignature { hint: SignatureHint([0u8; 4]), //FIXME signature: Signature(sig_bytes), @@ -299,9 +314,15 @@ impl Stellar for LedgerSigner { txn: Transaction, _source_account: &stellar_strkey::Strkey, ) -> Result { - let signature = block_on(self.sign_transaction(self.hd_path.clone(), txn.clone())).unwrap(); // FIXME: handle error + let signature = block_on(self.sign_transaction(self.hd_path.clone(), txn.clone())) + .map_err(|e| { + tracing::error!("Error signing transaction with Ledger device: {e}"); + Error::MissingSignerForAddress { + address: "source_account".to_string(), + } + })?; - let sig_bytes = signature.try_into().unwrap(); // FIXME: handle error + let sig_bytes = signature.try_into()?; let decorated_signature = DecoratedSignature { hint: SignatureHint([0u8; 4]), //FIXME signature: Signature(sig_bytes), @@ -309,25 +330,36 @@ impl Stellar for LedgerSigner { Ok(TransactionEnvelope::Tx(TransactionV1Envelope { tx: txn, - signatures: vec![decorated_signature].try_into().unwrap(), //fixme: remove unwrap + signatures: vec![decorated_signature].try_into()?, })) } } -fn bip_path_from_index(index: u32) -> slip10::BIP32Path { +fn bip_path_from_index(index: u32) -> Result { let path = format!("m/44'/148'/{index}'"); - path.parse().unwrap() // this is basically the same thing as slip10::BIP32Path::from_str - - // the device handles this part: https://github.com/AhaLabs/rs-sep5/blob/9d6e3886b4b424dd7b730ec24c865f6fad5d770c/src/seed_phrase.rs#L86 + path.parse().map_err(|e| { + let error_string = format!("Error parsing BIP32 path: {e}"); + LedgerError::Bip32PathError(error_string) + }) } -fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { - (0..hd_path.depth()) - .flat_map(|index| { - let value = *hd_path.index(index).unwrap(); - value.to_be_bytes() - }) - .collect::>() +fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Result, LedgerError> { + let hd_path_indices = 0..hd_path.depth(); + let mut result = Vec::with_capacity(hd_path.depth() as usize); + + for index in hd_path_indices { + let value = hd_path.index(index); + if let Some(v) = value { + let value_bytes = v.to_be_bytes(); + result.push(value_bytes); + } else { + return Err(LedgerError::Bip32PathError( + "Error getting index of hd path".to_string(), + )); + } + } + + Ok(result.into_iter().flatten().collect()) } #[cfg(test)] diff --git a/cmd/crates/stellar-ledger/src/signer.rs b/cmd/crates/stellar-ledger/src/signer.rs index e403c19ec..80032c23b 100644 --- a/cmd/crates/stellar-ledger/src/signer.rs +++ b/cmd/crates/stellar-ledger/src/signer.rs @@ -5,12 +5,12 @@ use soroban_env_host::xdr::{ TransactionV1Envelope, WriteXdr, }; -use soroban_rpc::Error as RpcError; - #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] - RpcError(#[from] RpcError), + Xdr(#[from] xdr::Error), + #[error("Error signing transaction {address}")] + MissingSignerForAddress { address: String }, } /// A trait for signing Stellar transactions and Soroban authorization entries From 9f574288d6eab9d677c09107258a2d96af119dff Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 3 May 2024 11:25:02 -0400 Subject: [PATCH 62/72] fix: clippy and fmt --- cmd/crates/soroban-spec-tools/src/lib.rs | 3 +-- cmd/soroban-cli/src/commands/contract/invoke.rs | 4 +++- docs/soroban-cli-full-docs.md | 3 --- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/cmd/crates/soroban-spec-tools/src/lib.rs b/cmd/crates/soroban-spec-tools/src/lib.rs index e6d496437..c227c3478 100644 --- a/cmd/crates/soroban-spec-tools/src/lib.rs +++ b/cmd/crates/soroban-spec-tools/src/lib.rs @@ -1137,8 +1137,7 @@ impl Spec { ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { fields, .. }) if fields .first() - .map(|f| f.name.to_utf8_string_lossy() == "0") - .unwrap_or_default() => + .is_some_and(|f| f.name.to_utf8_string_lossy() == "0") => { let fields = fields .iter() diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 00d7383d7..d297f9c54 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -21,7 +21,9 @@ use soroban_env_host::{ HostError, }; -use soroban_sdk::xdr::{AccountEntry, AccountEntryExt, AccountId, ContractDataEntry, DiagnosticEvent, Thresholds}; +use soroban_sdk::xdr::{ + AccountEntry, AccountEntryExt, AccountId, ContractDataEntry, DiagnosticEvent, Thresholds, +}; use soroban_spec::read::FromWasmError; use stellar_strkey::DecodeError; diff --git a/docs/soroban-cli-full-docs.md b/docs/soroban-cli-full-docs.md index 9d10a653e..b005f1e7f 100644 --- a/docs/soroban-cli-full-docs.md +++ b/docs/soroban-cli-full-docs.md @@ -1387,9 +1387,6 @@ Read cached action * `--id ` — ID of the cache entry - Possible values: `envelope` - -
From 578db4f5f14590297e84f8e1dcbe97c7ca548bf2 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 2 May 2024 12:04:42 -0400 Subject: [PATCH 63/72] feat: add txn subcommand --- cmd/soroban-cli/src/commands/mod.rs | 8 +- cmd/soroban-cli/src/commands/txn/mod.rs | 48 ++++ cmd/soroban-cli/src/commands/txn/send.rs | 35 +++ cmd/soroban-cli/src/commands/txn/sign.rs | 97 +++++++ cmd/soroban-cli/src/commands/txn/simulate.rs | 38 +++ cmd/soroban-cli/src/commands/txn/xdr.rs | 67 +++++ cmd/soroban-cli/src/lib.rs | 2 + cmd/soroban-cli/src/signer.rs | 277 +++++++++++++++++++ 8 files changed, 571 insertions(+), 1 deletion(-) create mode 100644 cmd/soroban-cli/src/commands/txn/mod.rs create mode 100644 cmd/soroban-cli/src/commands/txn/send.rs create mode 100644 cmd/soroban-cli/src/commands/txn/sign.rs create mode 100644 cmd/soroban-cli/src/commands/txn/simulate.rs create mode 100644 cmd/soroban-cli/src/commands/txn/xdr.rs create mode 100644 cmd/soroban-cli/src/signer.rs diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index f904c465f..df28339bd 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -12,8 +12,8 @@ pub mod global; pub mod keys; pub mod network; pub mod plugin; +pub mod txn; pub mod version; - pub mod txn_result; pub const HEADING_RPC: &str = "Options (RPC)"; @@ -101,6 +101,7 @@ impl Root { Cmd::Network(network) => network.run().await?, Cmd::Version(version) => version.run(), Cmd::Keys(id) => id.run().await?, + Cmd::Txn(tx) => tx.run().await?, Cmd::Cache(data) => data.run()?, }; Ok(()) @@ -135,6 +136,9 @@ pub enum Cmd { Network(network::Cmd), /// Print version information Version(version::Cmd), + /// Sign, Simulate, and Send transactions + #[command(subcommand)] + Txn(txn::Cmd), /// Cache for tranasctions and contract specs #[command(subcommand)] Cache(cache::Cmd), @@ -158,6 +162,8 @@ pub enum Error { #[error(transparent)] Network(#[from] network::Error), #[error(transparent)] + Txn(#[from] txn::Error), + #[error(transparent)] Cache(#[from] cache::Error), } diff --git a/cmd/soroban-cli/src/commands/txn/mod.rs b/cmd/soroban-cli/src/commands/txn/mod.rs new file mode 100644 index 000000000..9de360fcc --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/mod.rs @@ -0,0 +1,48 @@ +use clap::Parser; + +pub mod send; +pub mod sign; +pub mod simulate; +pub mod xdr; + +use stellar_xdr::cli as xdr_cli; + +#[derive(Debug, Parser)] +pub enum Cmd { + /// Add a new identity (keypair, ledger, macOS keychain) + Inspect(xdr_cli::Root), + /// Given an identity return its address (public key) + Sign(sign::Cmd), + /// Submit a transaction to the network + Send(send::Cmd), + /// Simulate a transaction + Simulate(simulate::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + /// An error during the simulation + #[error(transparent)] + Simulate(#[from] simulate::Error), + /// An error during the inspect + #[error(transparent)] + Inspect(#[from] xdr_cli::Error), + /// An error during the sign + #[error(transparent)] + Sign(#[from] sign::Error), + /// An error during the send + #[error(transparent)] + Send(#[from] send::Error), +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + match self { + Cmd::Inspect(cmd) => cmd.run()?, + Cmd::Sign(cmd) => cmd.run().await?, + Cmd::Send(cmd) => cmd.run().await?, + Cmd::Simulate(cmd) => cmd.run().await?, + }; + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/txn/send.rs b/cmd/soroban-cli/src/commands/txn/send.rs new file mode 100644 index 000000000..1cf3f8478 --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/send.rs @@ -0,0 +1,35 @@ +use soroban_rpc::GetTransactionResponse; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + XdrArgs(#[from] super::xdr::Error), + #[error(transparent)] + Config(#[from] super::super::config::Error), + #[error(transparent)] + Rpc(#[from] crate::rpc::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + #[clap(flatten)] + pub xdr_args: super::xdr::Args, + #[clap(flatten)] + pub config: super::super::config::Args, +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + let response = self.send().await?; + println!("{response:#?}"); + Ok(()) + } + + pub async fn send(&self) -> Result { + let txn_env = self.xdr_args.txn_envelope()?; + let network = self.config.get_network()?; + let client = crate::rpc::Client::new(&network.rpc_url)?; + Ok(client.send_transaction(&txn_env).await?) + } +} diff --git a/cmd/soroban-cli/src/commands/txn/sign.rs b/cmd/soroban-cli/src/commands/txn/sign.rs new file mode 100644 index 000000000..ec4bb1155 --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/sign.rs @@ -0,0 +1,97 @@ +use std::io; + +// use crossterm::{ +// event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, +// execute, +// terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, +// }; +use soroban_sdk::xdr::{self, Limits, TransactionEnvelope, WriteXdr}; + +use crate::signer::{self, InMemory, Stellar}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + XdrArgs(#[from] super::xdr::Error), + #[error(transparent)] + Signer(#[from] signer::Error), + #[error(transparent)] + Config(#[from] super::super::config::Error), + #[error(transparent)] + StellarStrkey(#[from] stellar_strkey::DecodeError), + #[error(transparent)] + Xdr(#[from] xdr::Error), + #[error(transparent)] + Io(#[from] io::Error), + #[error("User cancelled signing, perhaps need to add -y")] + UserCancelledSigning, +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + /// Confirm that a signature can be signed by the given keypair automatically. + #[arg(long, short = 'y')] + yes: bool, + #[clap(flatten)] + pub xdr_args: super::xdr::Args, + #[clap(flatten)] + pub config: super::super::config::Args, +} + +impl Cmd { + #[allow(clippy::unused_async)] + pub async fn run(&self) -> Result<(), Error> { + let envelope = self.sign()?; + println!("{}", envelope.to_xdr_base64(Limits::none())?.trim()); + Ok(()) + } + + pub fn sign(&self) -> Result { + let source = &self.config.source_account; + tracing::debug!("signing transaction with source account {}", source); + let txn = self.xdr_args.txn()?; + let key = self.config.key_pair()?; + let address = + stellar_strkey::ed25519::PublicKey::from_payload(key.verifying_key().as_bytes())?; + let in_memory = InMemory { + network_passphrase: self.config.get_network()?.network_passphrase, + keypairs: vec![key], + }; + self.prompt_user()?; + Ok(in_memory.sign_txn(txn, &stellar_strkey::Strkey::PublicKeyEd25519(address))?) + } + + pub fn prompt_user(&self) -> Result<(), Error> { + if self.yes { + return Ok(()); + } + Err(Error::UserCancelledSigning) + // TODO use crossterm to prompt user for confirmation + // // Set up the terminal + // let mut stdout = io::stdout(); + // execute!(stdout, EnterAlternateScreen)?; + // terminal::enable_raw_mode()?; + + // println!("Press 'y' or 'Y' for yes, any other key for no:"); + + // loop { + // if let Event::Key(KeyEvent { + // code, + // modifiers: KeyModifiers::NONE, + // .. + // }) = event::read()? + // { + // match code { + // KeyCode::Char('y' | 'Y') => break, + // _ => return Err(Error::UserCancelledSigning), + // } + // } + // } + + // // Clean up the terminal + // terminal::disable_raw_mode()?; + // execute!(stdout, LeaveAlternateScreen)?; + // Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/txn/simulate.rs b/cmd/soroban-cli/src/commands/txn/simulate.rs new file mode 100644 index 000000000..5fbae28c0 --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/simulate.rs @@ -0,0 +1,38 @@ +use soroban_rpc::Assembled; +use soroban_sdk::xdr::{self, WriteXdr}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + XdrArgs(#[from] super::xdr::Error), + #[error(transparent)] + Config(#[from] super::super::config::Error), + #[error(transparent)] + Rpc(#[from] crate::rpc::Error), + #[error(transparent)] + Xdr(#[from] xdr::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + #[clap(flatten)] + pub xdr_args: super::xdr::Args, + #[clap(flatten)] + pub config: super::super::config::Args, +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + let res = self.simulate().await?; + println!("{}", res.transaction().to_xdr_base64(xdr::Limits::none())?); + Ok(()) + } + + pub async fn simulate(&self) -> Result { + let tx = self.xdr_args.txn()?; + let network = self.config.get_network()?; + let client = crate::rpc::Client::new(&network.rpc_url)?; + Ok(client.create_assembled_transaction(&tx).await?) + } +} diff --git a/cmd/soroban-cli/src/commands/txn/xdr.rs b/cmd/soroban-cli/src/commands/txn/xdr.rs new file mode 100644 index 000000000..180c12e6b --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/xdr.rs @@ -0,0 +1,67 @@ +use std::{ + io::{stdin, Read}, + path::PathBuf, +}; + +use soroban_env_host::xdr::ReadXdr; +use soroban_sdk::xdr::{Limits, Transaction, TransactionEnvelope}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("failed to decode XDR from base64")] + Base64Decode, + #[error("failed to decode XDR from file: {0}")] + FileDecode(PathBuf), + #[error("failed to decode XDR from stdin")] + StdinDecode, + #[error(transparent)] + Io(#[from] std::io::Error), +} + +/// XDR input, either base64 encoded or file path and stdin if neither is provided +#[derive(Debug, clap::Args, Clone)] +#[group(skip)] +pub struct Args { + /// Base64 encoded XDR transaction + #[arg( + long = "xdr-base64", + env = "STELLAR_TXN_XDR_BASE64", + conflicts_with = "xdr_file" + )] + pub xdr_base64: Option, + //// File containing Binary encoded data + #[arg( + long = "xdr-file", + env = "STELLAR_TXN_XDR_FILE", + conflicts_with = "xdr_base64" + )] + pub xdr_file: Option, +} + +impl Args { + pub fn xdr(&self) -> Result { + match (self.xdr_base64.as_ref(), self.xdr_file.as_ref()) { + (Some(xdr_base64), None) => { + T::from_xdr_base64(xdr_base64, Limits::none()).map_err(|_| Error::Base64Decode) + } + (_, Some(xdr_file)) => T::from_xdr(std::fs::read(xdr_file)?, Limits::none()) + .map_err(|_| Error::FileDecode(xdr_file.clone())), + + _ => { + let mut buf = String::new(); + let _ = stdin() + .read_to_string(&mut buf) + .map_err(|_| Error::StdinDecode)?; + T::from_xdr_base64(buf.trim(), Limits::none()).map_err(|_| Error::StdinDecode) + } + } + } + + pub fn txn(&self) -> Result { + self.xdr::() + } + + pub fn txn_envelope(&self) -> Result { + self.xdr::() + } +} diff --git a/cmd/soroban-cli/src/lib.rs b/cmd/soroban-cli/src/lib.rs index 5cde45436..9a9900605 100644 --- a/cmd/soroban-cli/src/lib.rs +++ b/cmd/soroban-cli/src/lib.rs @@ -8,11 +8,13 @@ use std::path::Path; pub(crate) use soroban_env_host::xdr; pub(crate) use soroban_rpc as rpc; + pub mod commands; pub mod fee; pub mod key; pub mod log; pub mod toid; +pub mod signer; pub mod utils; pub mod wasm; diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs new file mode 100644 index 000000000..bd5605598 --- /dev/null +++ b/cmd/soroban-cli/src/signer.rs @@ -0,0 +1,277 @@ +use ed25519_dalek::Signer; +use sha2::{Digest, Sha256}; + +use soroban_env_host::xdr::{ + self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, + InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, + ScVal, Signature, SignatureHint, SorobanAddressCredentials, SorobanAuthorizationEntry, + SorobanAuthorizedFunction, SorobanCredentials, Transaction, TransactionEnvelope, + TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, + TransactionV1Envelope, Uint256, WriteXdr, +}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Xdr(#[from] xdr::Error), + #[error("Error signing transaction {address}")] + MissingSignerForAddress { address: String }, +} + +fn requires_auth(txn: &Transaction) -> Option { + let [op @ Operation { + body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }), + .. + }] = txn.operations.as_slice() + else { + return None; + }; + matches!( + auth.first().map(|x| &x.root_invocation.function), + Some(&SorobanAuthorizedFunction::ContractFn(_)) + ) + .then(move || op.clone()) +} + +/// A trait for signing Stellar transactions and Soroban authorization entries +pub trait Stellar { + /// The type of the options that can be passed when creating a new signer + type Init; + /// Create a new signer with the given network passphrase and options + fn new(network_passphrase: &str, options: Option) -> Self; + + /// Get the network hash + fn network_hash(&self) -> xdr::Hash; + + /// Sign a transaction hash with the given source account + /// # Errors + /// Returns an error if the source account is not found + fn sign_txn_hash( + &self, + txn: [u8; 32], + source_account: &stellar_strkey::Strkey, + ) -> Result; + + /// Sign a Soroban authorization entry with the given address + /// # Errors + /// Returns an error if the address is not found + fn sign_soroban_authorization_entry( + &self, + unsigned_entry: &SorobanAuthorizationEntry, + signature_expiration_ledger: u32, + address: &[u8; 32], + ) -> Result; + + /// Sign a Stellar transaction with the given source account + /// This is a default implementation that signs the transaction hash and returns a decorated signature + /// # Errors + /// Returns an error if the source account is not found + fn sign_txn( + &self, + txn: Transaction, + source_account: &stellar_strkey::Strkey, + ) -> Result { + let signature_payload = TransactionSignaturePayload { + network_id: self.network_hash(), + tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(txn.clone()), + }; + let hash = Sha256::digest(signature_payload.to_xdr(Limits::none())?).into(); + let decorated_signature = self.sign_txn_hash(hash, source_account)?; + Ok(TransactionEnvelope::Tx(TransactionV1Envelope { + tx: txn, + signatures: vec![decorated_signature].try_into()?, + })) + } + + /// Sign a Soroban authorization entries for a given transaction and set the expiration ledger + /// # Errors + /// Returns an error if the address is not found + fn sign_soroban_authorizations( + &self, + raw: &Transaction, + signature_expiration_ledger: u32, + ) -> Result, Error> { + let mut tx = raw.clone(); + let Some(mut op) = requires_auth(&tx) else { + return Ok(None); + }; + + let xdr::Operation { + body: OperationBody::InvokeHostFunction(ref mut body), + .. + } = op + else { + return Ok(None); + }; + + let signed_auths = body + .auth + .as_slice() + .iter() + .map(|raw_auth| { + self.maybe_sign_soroban_authorization_entry(raw_auth, signature_expiration_ledger) + }) + .collect::, Error>>()?; + + body.auth = signed_auths.try_into()?; + tx.operations = vec![op].try_into()?; + Ok(Some(tx)) + } + + /// Sign a Soroban authorization entry if the address is public key + /// # Errors + /// Returns an error if the address in entry is a contract + fn maybe_sign_soroban_authorization_entry( + &self, + unsigned_entry: &SorobanAuthorizationEntry, + signature_expiration_ledger: u32, + ) -> Result { + if let SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(SorobanAddressCredentials { ref address, .. }), + .. + } = unsigned_entry + { + // See if we have a signer for this authorizationEntry + // If not, then we Error + let needle = match address { + ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(ref a)))) => a, + ScAddress::Contract(Hash(c)) => { + // This address is for a contract. This means we're using a custom + // smart-contract account. Currently the CLI doesn't support that yet. + return Err(Error::MissingSignerForAddress { + address: stellar_strkey::Strkey::Contract(stellar_strkey::Contract(*c)) + .to_string(), + }); + } + }; + self.sign_soroban_authorization_entry( + unsigned_entry, + signature_expiration_ledger, + needle, + ) + } else { + Ok(unsigned_entry.clone()) + } + } +} + +use std::fmt::Debug; +#[derive(Debug)] +pub struct InMemory { + pub network_passphrase: String, + pub keypairs: Vec, +} + +impl InMemory { + pub fn get_key( + &self, + key: &stellar_strkey::Strkey, + ) -> Result<&ed25519_dalek::SigningKey, Error> { + match key { + stellar_strkey::Strkey::PublicKeyEd25519(stellar_strkey::ed25519::PublicKey(bytes)) => { + self.keypairs + .iter() + .find(|k| k.verifying_key().to_bytes() == *bytes) + } + _ => None, + } + .ok_or_else(|| Error::MissingSignerForAddress { + address: key.to_string(), + }) + } +} + +impl Stellar for InMemory { + type Init = Vec; + fn new(network_passphrase: &str, options: Option>) -> Self { + InMemory { + network_passphrase: network_passphrase.to_string(), + keypairs: options.unwrap_or_default(), + } + } + + fn sign_txn_hash( + &self, + txn: [u8; 32], + source_account: &stellar_strkey::Strkey, + ) -> Result { + let source_account = self.get_key(source_account)?; + let tx_signature = source_account.sign(&txn); + Ok(DecoratedSignature { + // TODO: remove this unwrap. It's safe because we know the length of the array + hint: SignatureHint( + source_account.verifying_key().to_bytes()[28..] + .try_into() + .unwrap(), + ), + signature: Signature(tx_signature.to_bytes().try_into()?), + }) + } + + fn sign_soroban_authorization_entry( + &self, + unsigned_entry: &SorobanAuthorizationEntry, + signature_expiration_ledger: u32, + signer: &[u8; 32], + ) -> Result { + let mut auth = unsigned_entry.clone(); + let SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(ref mut credentials), + .. + } = auth + else { + // Doesn't need special signing + return Ok(auth); + }; + let SorobanAddressCredentials { nonce, .. } = credentials; + + let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { + network_id: self.network_hash(), + invocation: auth.root_invocation.clone(), + nonce: *nonce, + signature_expiration_ledger, + }) + .to_xdr(Limits::none())?; + + let strkey = stellar_strkey::ed25519::PublicKey(*signer); + let payload = Sha256::digest(preimage); + let signer = self.get_key(&stellar_strkey::Strkey::PublicKeyEd25519(strkey))?; + let signature = signer.sign(&payload); + + let map = ScMap::sorted_from(vec![ + ( + ScVal::Symbol(ScSymbol("public_key".try_into()?)), + ScVal::Bytes( + signer + .verifying_key() + .to_bytes() + .to_vec() + .try_into() + .map_err(Error::Xdr)?, + ), + ), + ( + ScVal::Symbol(ScSymbol("signature".try_into()?)), + ScVal::Bytes( + signature + .to_bytes() + .to_vec() + .try_into() + .map_err(Error::Xdr)?, + ), + ), + ]) + .map_err(Error::Xdr)?; + credentials.signature = ScVal::Vec(Some( + vec![ScVal::Map(Some(map))].try_into().map_err(Error::Xdr)?, + )); + credentials.signature_expiration_ledger = signature_expiration_ledger; + auth.credentials = SorobanCredentials::Address(credentials.clone()); + + Ok(auth) + } + + fn network_hash(&self) -> xdr::Hash { + xdr::Hash(Sha256::digest(self.network_passphrase.as_bytes()).into()) + } +} From 4e5405847f4f7131f446022a803a63348976bf7f Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 3 May 2024 11:37:23 -0400 Subject: [PATCH 64/72] feat: sign blob --- cmd/crates/stellar-ledger/src/lib.rs | 22 +++++++++++++++++----- cmd/soroban-cli/src/commands/txn/sign.rs | 18 ++++++++++++------ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index c281fa55a..5b1a72f74 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -152,23 +152,23 @@ where self.send_command_to_ledger(command).await } - /// Sign a Stellar transaction hash with the account on the Ledger device + /// Sign a Stellar transaction hash with the account on the Ledger device /// based on impl from [https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166](https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166) /// # Errors /// Returns an error if there is an issue with connecting with the device or signing the given tx on the device. Or, if the device has not enabled hash signing - async fn sign_transaction_hash( + pub async fn sign_blob( &self, hd_path: slip10::BIP32Path, - transaction_hash: &[u8], + blob: &[u8], ) -> Result, LedgerError> { let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); - let capacity = 1 + hd_path_to_bytes.len() + transaction_hash.len(); + let capacity = 1 + hd_path_to_bytes.len() + blob.len(); let mut data: Vec = Vec::with_capacity(capacity); data.insert(0, HD_PATH_ELEMENTS_COUNT); data.append(&mut hd_path_to_bytes); - data.extend_from_slice(transaction_hash); + data.extend_from_slice(blob); let command = APDUCommand { cla: CLA, @@ -181,6 +181,18 @@ where self.send_command_to_ledger(command).await } + /// Sign a Stellar transaction hash with the account on the Ledger device + /// based on impl from [https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166](https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166) + /// # Errors + /// Returns an error if there is an issue with connecting with the device or signing the given tx on the device. Or, if the device has not enabled hash signing + pub async fn sign_transaction_hash( + &self, + hd_path: slip10::BIP32Path, + transaction_hash: &[u8], + ) -> Result, LedgerError> { + self.sign_blob(hd_path, transaction_hash).await + } + /// Sign a Stellar transaction with the account on the Ledger device /// # Errors /// Returns an error if there is an issue with connecting with the device or signing the given tx on the device diff --git a/cmd/soroban-cli/src/commands/txn/sign.rs b/cmd/soroban-cli/src/commands/txn/sign.rs index c5d919a12..092cac5fe 100644 --- a/cmd/soroban-cli/src/commands/txn/sign.rs +++ b/cmd/soroban-cli/src/commands/txn/sign.rs @@ -5,8 +5,10 @@ use std::io; // execute, // terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, // }; -use soroban_sdk::xdr::{self, Limits, Transaction, TransactionEnvelope, WriteXdr}; -use stellar_ledger::NativeSigner; +use soroban_sdk::xdr::{ + self, Limits, MuxedAccount, Transaction, TransactionEnvelope, Uint256, WriteXdr, +}; +use stellar_ledger::{LedgerError, NativeSigner}; use stellar_strkey::Strkey; use crate::signer::{self, InMemory, Stellar}; @@ -27,6 +29,8 @@ pub enum Error { Io(#[from] io::Error), #[error("User cancelled signing, perhaps need to add -y")] UserCancelledSigning, + #[error(transparent)] + Ledger(#[from] LedgerError), } #[derive(Debug, clap::Parser, Clone)] @@ -115,16 +119,18 @@ impl Cmd { .await?) } - pub async fn sign_ledger(&self, txn: Transaction) -> Result { + pub async fn sign_ledger(&self, mut txn: Transaction) -> Result { let index: u32 = self .config .hd_path .unwrap_or_default() .try_into() .expect("usize bigger than u32"); - let signer: NativeSigner = (self.config.get_network()?.network_passphrase, index).into(); - let account = - Strkey::PublicKeyEd25519(signer.as_ref().get_public_key(index).await.unwrap()); + let signer: NativeSigner = + (self.config.get_network()?.network_passphrase, index).try_into()?; + let key = signer.as_ref().get_public_key(index).await.unwrap(); + let account = Strkey::PublicKeyEd25519(key); + txn.source_account = MuxedAccount::Ed25519(Uint256(key.0)); let bx_signer = Box::new(signer); Ok(bx_signer.sign_txn(txn, &account).await.unwrap()) } From 40d978e8f7530c2124b0d87869a2e5244d210ef1 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Fri, 3 May 2024 11:42:45 -0400 Subject: [PATCH 65/72] Fix SignatureHint --- cmd/crates/stellar-ledger/src/lib.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 7fb79c4c7..9226b6a30 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -302,9 +302,17 @@ impl Stellar for LedgerSigner { } })?; + let hint = source_account.to_string().into_bytes()[28..] + .try_into() + .map_err(|e| { + tracing::error!("Error converting source_account to string: {e}"); + Error::MissingSignerForAddress { + address: source_account.to_string(), + } + })?; let sig_bytes = signature.try_into()?; Ok(DecoratedSignature { - hint: SignatureHint([0u8; 4]), //FIXME + hint: SignatureHint(hint), signature: Signature(sig_bytes), }) } @@ -312,7 +320,7 @@ impl Stellar for LedgerSigner { fn sign_txn( &self, txn: Transaction, - _source_account: &stellar_strkey::Strkey, + source_account: &stellar_strkey::Strkey, ) -> Result { let signature = block_on(self.sign_transaction(self.hd_path.clone(), txn.clone())) .map_err(|e| { @@ -322,9 +330,17 @@ impl Stellar for LedgerSigner { } })?; + let hint = source_account.to_string().into_bytes()[28..] + .try_into() + .map_err(|e| { + tracing::error!("Error converting source_account to string: {e}"); + Error::MissingSignerForAddress { + address: source_account.to_string(), + } + })?; let sig_bytes = signature.try_into()?; let decorated_signature = DecoratedSignature { - hint: SignatureHint([0u8; 4]), //FIXME + hint: SignatureHint(hint), signature: Signature(sig_bytes), }; From 8026558b485c561c6d5e4a92c23ac20a077784ab Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Fri, 3 May 2024 13:09:20 -0400 Subject: [PATCH 66/72] Add get_transport fn back in --- cmd/crates/stellar-ledger/src/lib.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 9226b6a30..1eaede12f 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -1,6 +1,7 @@ use futures::executor::block_on; +use hidapi::HidApi; use ledger_transport::{APDUCommand, Exchange}; -use ledger_transport_hid::{hidapi::HidError, LedgerHIDError}; +use ledger_transport_hid::{hidapi::HidError, LedgerHIDError, TransportNativeHID}; use sha2::{Digest, Sha256}; use soroban_env_host::xdr::{Hash, Transaction}; @@ -378,6 +379,12 @@ fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Result, LedgerError> Ok(result.into_iter().flatten().collect()) } +pub fn get_transport() -> Result { + // instantiate the connection to Ledger, this will return an error if Ledger is not connected + let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; + TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) +} + #[cfg(test)] mod test { use std::str::FromStr; From 8d63c33075b9b15e4bddadef191b7009d6a6c5a5 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 3 May 2024 14:44:03 -0400 Subject: [PATCH 67/72] feat: initial signing working! --- cmd/crates/stellar-ledger/src/lib.rs | 11 +- .../src/commands/contract/install.rs | 25 ++- .../src/commands/contract/invoke.rs | 3 +- cmd/soroban-cli/src/commands/keys/address.rs | 33 ++- cmd/soroban-cli/src/commands/mod.rs | 2 +- cmd/soroban-cli/src/commands/txn/sign.rs | 15 +- cmd/soroban-cli/src/lib.rs | 3 +- cmd/soroban-cli/src/signer.rs | 196 ++++++++++-------- 8 files changed, 171 insertions(+), 117 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 5b1a72f74..76fbdec24 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -114,7 +114,7 @@ impl AsRef> for NativeSigner { impl TryFrom<(String, u32)> for NativeSigner { type Error = LedgerError; - fn try_from((network_passphrase, hd_path): (String, u32)) -> Result { + fn try_from((network_passphrase, hd_path): (String, u32)) -> Result { Ok(Self(LedgerSigner { network_passphrase, transport: transport_native_hid()?, @@ -138,6 +138,13 @@ where Self::get_public_key_with_display_flag(self, hd_path, false).await } + pub fn get_public_key_sync( + &self, + index: u32, + ) -> Result { + block_on(self.get_public_key(index)) + } + /// Get the device app's configuration /// # Errors /// Returns an error if there is an issue with connecting with the device or getting the config from the device @@ -152,7 +159,7 @@ where self.send_command_to_ledger(command).await } - /// Sign a Stellar transaction hash with the account on the Ledger device + /// Sign a Stellar transaction hash with the account on the Ledger device /// based on impl from [https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166](https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166) /// # Errors /// Returns an error if there is an issue with connecting with the device or signing the given tx on the device. Or, if the device has not enabled hash signing diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index 7208e1a93..f04def508 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -121,13 +121,12 @@ impl NetworkRunnable for Cmd { let key = config.key_pair()?; // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - let account_details = client.get_account(&public_strkey).await?; + let public_strkey = stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()); + let account_details = client.get_account(&public_strkey.to_string()).await?; let sequence: i64 = account_details.seq_num.into(); let (tx_without_preflight, hash) = - build_install_contract_code_tx(&contract, sequence + 1, self.fee.fee, &key)?; + build_install_contract_code_tx(&contract, sequence + 1, self.fee.fee, &public_strkey)?; if self.fee.build_only { return Ok(TxnResult::from_xdr(&tx_without_preflight)?); @@ -223,14 +222,12 @@ pub(crate) fn build_install_contract_code_tx( source_code: &[u8], sequence: i64, fee: u32, - key: &ed25519_dalek::SigningKey, + key: &stellar_strkey::ed25519::PublicKey, ) -> Result<(Transaction, Hash), XdrError> { let hash = utils::contract_hash(source_code)?; let op = Operation { - source_account: Some(MuxedAccount::Ed25519(Uint256( - key.verifying_key().to_bytes(), - ))), + source_account: None, body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { host_function: HostFunction::UploadContractWasm(source_code.try_into()?), auth: VecM::default(), @@ -238,7 +235,7 @@ pub(crate) fn build_install_contract_code_tx( }; let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), + source_account: MuxedAccount::Ed25519(Uint256(key.0)), fee, seq_num: SequenceNumber(sequence), cond: Preconditions::None, @@ -260,8 +257,14 @@ mod tests { b"foo", 300, 1, - &utils::parse_secret_key("SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP") - .unwrap(), + &stellar_strkey::ed25519::PublicKey( + *utils::parse_secret_key( + "SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP", + ) + .unwrap() + .verifying_key() + .as_bytes(), + ), ); assert!(result.is_ok()); diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index db3b4eb3f..547f1768b 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -204,9 +204,10 @@ impl Cmd { let mut s = val.next().unwrap().to_string_lossy().to_string(); if matches!(i.type_, ScSpecTypeDef::Address) { let cmd = crate::commands::keys::address::Cmd { - name: s.clone(), + name: Some(s.clone()), hd_path: Some(0), locator: config.locator.clone(), + use_ledger: false, }; if let Ok(address) = cmd.public_key() { s = address.to_string(); diff --git a/cmd/soroban-cli/src/commands/keys/address.rs b/cmd/soroban-cli/src/commands/keys/address.rs index d13381b49..44a793706 100644 --- a/cmd/soroban-cli/src/commands/keys/address.rs +++ b/cmd/soroban-cli/src/commands/keys/address.rs @@ -13,13 +13,21 @@ pub enum Error { #[error(transparent)] StrKey(#[from] stellar_strkey::DecodeError), + #[error(transparent)] + Ledger(#[from] stellar_ledger::LedgerError), + #[error("Invalid HD path index {0}")] + UsizeConversionError(usize), } #[derive(Debug, clap::Parser, Clone)] #[group(skip)] pub struct Cmd { /// Name of identity to lookup, default test identity used if not provided - pub name: String, + pub name: Option, + + /// Use Ledger + #[arg(long, short = 'l')] + pub use_ledger: bool, /// If identity is a seed phrase use this hd path, default is 0 #[arg(long)] @@ -35,15 +43,34 @@ impl Cmd { Ok(()) } + pub fn name(&self) -> &str { + self.name.as_deref().unwrap_or("default") + } + pub fn private_key(&self) -> Result { Ok(self .locator - .read_identity(&self.name)? + .read_identity(self.name())? .key_pair(self.hd_path)?) } + pub fn hd_path(&self) -> Result { + let hd_path = &self.hd_path.unwrap_or_default(); + (*hd_path) + .try_into() + .map_err(|_| Error::UsizeConversionError(*hd_path)) + } + pub fn public_key(&self) -> Result { - if let Ok(key) = stellar_strkey::ed25519::PublicKey::from_string(&self.name) { + if self.use_ledger { + let signer: stellar_ledger::NativeSigner = ( + String::new(), + self.hd_path.unwrap_or_default().try_into().unwrap(), + ) + .try_into()?; + return Ok(signer.as_ref().get_public_key_sync(self.hd_path()?)?); + } + if let Ok(key) = stellar_strkey::ed25519::PublicKey::from_string(self.name()) { Ok(key) } else { Ok(stellar_strkey::ed25519::PublicKey::from_payload( diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index df28339bd..4eeeb53e2 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -13,8 +13,8 @@ pub mod keys; pub mod network; pub mod plugin; pub mod txn; -pub mod version; pub mod txn_result; +pub mod version; pub const HEADING_RPC: &str = "Options (RPC)"; const ABOUT: &str = "Build, deploy, & interact with contracts; set identities to sign with; configure networks; generate keys; and more. diff --git a/cmd/soroban-cli/src/commands/txn/sign.rs b/cmd/soroban-cli/src/commands/txn/sign.rs index 092cac5fe..c764fc977 100644 --- a/cmd/soroban-cli/src/commands/txn/sign.rs +++ b/cmd/soroban-cli/src/commands/txn/sign.rs @@ -1,12 +1,13 @@ use std::io; +use soroban_rpc::Client; // use crossterm::{ // event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, // execute, // terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, // }; use soroban_sdk::xdr::{ - self, Limits, MuxedAccount, Transaction, TransactionEnvelope, Uint256, WriteXdr, + self, Limits, MuxedAccount, SequenceNumber, Transaction, TransactionEnvelope, Uint256, WriteXdr, }; use stellar_ledger::{LedgerError, NativeSigner}; use stellar_strkey::Strkey; @@ -31,6 +32,8 @@ pub enum Error { UserCancelledSigning, #[error(transparent)] Ledger(#[from] LedgerError), + #[error(transparent)] + Rpc(#[from] soroban_rpc::Error), } #[derive(Debug, clap::Parser, Clone)] @@ -43,7 +46,6 @@ pub struct Cmd { pub xdr_args: super::xdr::Args, #[clap(flatten)] pub config: super::super::config::Args, - #[arg(long, value_enum, default_value = "file")] pub signer: SignerType, } @@ -63,8 +65,6 @@ impl Cmd { } pub async fn sign(&self) -> Result { - let source = &self.config.source_account; - tracing::debug!("signing transaction with source account {}", source); let txn = self.xdr_args.txn()?; match self.signer { SignerType::File => self.sign_file(txn).await, @@ -105,7 +105,7 @@ impl Cmd { // Ok(()) } - pub async fn sign_file(&self, txn: Transaction) -> Result { + pub async fn sign_file(&self, mut txn: Transaction) -> Result { let key = self.config.key_pair()?; let address = stellar_strkey::ed25519::PublicKey::from_payload(key.verifying_key().as_bytes())?; @@ -113,6 +113,8 @@ impl Cmd { network_passphrase: self.config.get_network()?.network_passphrase, keypairs: vec![key], }; + let client = Client::new(&self.config.get_network()?.rpc_url)?; + txn.seq_num = SequenceNumber(client.get_account(&address.to_string()).await?.seq_num.0 + 1); self.prompt_user()?; Ok(in_memory .sign_txn(txn, &Strkey::PublicKeyEd25519(address)) @@ -130,7 +132,10 @@ impl Cmd { (self.config.get_network()?.network_passphrase, index).try_into()?; let key = signer.as_ref().get_public_key(index).await.unwrap(); let account = Strkey::PublicKeyEd25519(key); + let client = Client::new(&self.config.get_network()?.rpc_url)?; + txn.seq_num = SequenceNumber(client.get_account(&account.to_string()).await?.seq_num.0 + 1); txn.source_account = MuxedAccount::Ed25519(Uint256(key.0)); + eprintln!("Account {account}"); let bx_signer = Box::new(signer); Ok(bx_signer.sign_txn(txn, &account).await.unwrap()) } diff --git a/cmd/soroban-cli/src/lib.rs b/cmd/soroban-cli/src/lib.rs index 9a9900605..5f44d6b8b 100644 --- a/cmd/soroban-cli/src/lib.rs +++ b/cmd/soroban-cli/src/lib.rs @@ -8,13 +8,12 @@ use std::path::Path; pub(crate) use soroban_env_host::xdr; pub(crate) use soroban_rpc as rpc; - pub mod commands; pub mod fee; pub mod key; pub mod log; -pub mod toid; pub mod signer; +pub mod toid; pub mod utils; pub mod wasm; diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index d72ce5459..89b484bed 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -9,6 +9,7 @@ use soroban_env_host::xdr::{ TransactionEnvelope, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, TransactionV1Envelope, Uint256, WriteXdr, }; +use soroban_sdk::xdr::BytesM; use stellar_ledger::{LedgerSigner, NativeSigner}; #[derive(thiserror::Error, Debug)] @@ -17,6 +18,8 @@ pub enum Error { Xdr(#[from] xdr::Error), #[error("Error signing transaction {address}")] MissingSignerForAddress { address: String }, + #[error(transparent)] + Ledger(#[from] stellar_ledger::LedgerError), } fn requires_auth(txn: &Transaction) -> Option { @@ -53,15 +56,67 @@ pub trait Stellar { source_account: &stellar_strkey::Strkey, ) -> impl std::future::Future> + Send; + async fn sign_blob( + &self, + data: &[u8], + source_account: &stellar_strkey::Strkey, + ) -> Result, Error>; + /// Sign a Soroban authorization entry with the given address /// # Errors /// Returns an error if the address is not found - fn sign_soroban_authorization_entry( + async fn sign_soroban_authorization_entry( &self, unsigned_entry: &SorobanAuthorizationEntry, signature_expiration_ledger: u32, - address: &[u8; 32], - ) -> impl std::future::Future> + Send; + address: &stellar_strkey::ed25519::PublicKey, + ) -> Result { + let mut auth = unsigned_entry.clone(); + let SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(ref mut credentials), + .. + } = auth + else { + // Doesn't need special signing + return Ok(auth); + }; + let SorobanAddressCredentials { nonce, .. } = credentials; + + let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { + network_id: self.network_hash(), + invocation: auth.root_invocation.clone(), + nonce: *nonce, + signature_expiration_ledger, + }) + .to_xdr(Limits::none())?; + + let payload = Sha256::digest(preimage); + let signature = self + .sign_blob( + &payload, + &stellar_strkey::Strkey::PublicKeyEd25519(*address), + ) + .await?; + + let map = ScMap::sorted_from(vec![ + ( + ScVal::Symbol(ScSymbol("public_key".try_into()?)), + ScVal::Bytes(address.0.to_vec().try_into().map_err(Error::Xdr)?), + ), + ( + ScVal::Symbol(ScSymbol("signature".try_into()?)), + ScVal::Bytes(signature.try_into().map_err(Error::Xdr)?), + ), + ]) + .map_err(Error::Xdr)?; + credentials.signature = ScVal::Vec(Some( + vec![ScVal::Map(Some(map))].try_into().map_err(Error::Xdr)?, + )); + credentials.signature_expiration_ledger = signature_expiration_ledger; + auth.credentials = SorobanCredentials::Address(credentials.clone()); + + Ok(auth) + } /// Sign a Stellar transaction with the given source account /// This is a default implementation that signs the transaction hash and returns a decorated signature @@ -106,7 +161,7 @@ pub trait Stellar { }; let mut auths = body.auth.to_vec(); - for auth in auths.iter_mut() { + for auth in &mut auths { *auth = self .maybe_sign_soroban_authorization_entry(auth, signature_expiration_ledger) .await?; @@ -132,7 +187,10 @@ pub trait Stellar { // See if we have a signer for this authorizationEntry // If not, then we Error let needle = match address { - ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(ref a)))) => a, + ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(a)))) => { + {} + stellar_strkey::ed25519::PublicKey(*a) + } ScAddress::Contract(Hash(c)) => { // This address is for a contract. This means we're using a custom // smart-contract account. Currently the CLI doesn't support that yet. @@ -145,7 +203,7 @@ pub trait Stellar { self.sign_soroban_authorization_entry( unsigned_entry, signature_expiration_ledger, - needle, + &needle, ) .await } else { @@ -189,6 +247,16 @@ impl Stellar for InMemory { } } + async fn sign_blob( + &self, + data: &[u8], + source_account: &stellar_strkey::Strkey, + ) -> Result, Error> { + let source_account = self.get_key(source_account)?; + let sig = source_account.sign(data); + Ok(sig.to_bytes().to_vec()) + } + async fn sign_txn_hash( &self, txn: [u8; 32], @@ -207,69 +275,6 @@ impl Stellar for InMemory { }) } - async fn sign_soroban_authorization_entry( - &self, - unsigned_entry: &SorobanAuthorizationEntry, - signature_expiration_ledger: u32, - signer: &[u8; 32], - ) -> Result { - let mut auth = unsigned_entry.clone(); - let SorobanAuthorizationEntry { - credentials: SorobanCredentials::Address(ref mut credentials), - .. - } = auth - else { - // Doesn't need special signing - return Ok(auth); - }; - let SorobanAddressCredentials { nonce, .. } = credentials; - - let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { - network_id: self.network_hash(), - invocation: auth.root_invocation.clone(), - nonce: *nonce, - signature_expiration_ledger, - }) - .to_xdr(Limits::none())?; - - let strkey = stellar_strkey::ed25519::PublicKey(*signer); - let payload = Sha256::digest(preimage); - let signer = self.get_key(&stellar_strkey::Strkey::PublicKeyEd25519(strkey))?; - let signature = signer.sign(&payload); - - let map = ScMap::sorted_from(vec![ - ( - ScVal::Symbol(ScSymbol("public_key".try_into()?)), - ScVal::Bytes( - signer - .verifying_key() - .to_bytes() - .to_vec() - .try_into() - .map_err(Error::Xdr)?, - ), - ), - ( - ScVal::Symbol(ScSymbol("signature".try_into()?)), - ScVal::Bytes( - signature - .to_bytes() - .to_vec() - .try_into() - .map_err(Error::Xdr)?, - ), - ), - ]) - .map_err(Error::Xdr)?; - credentials.signature = ScVal::Vec(Some( - vec![ScVal::Map(Some(map))].try_into().map_err(Error::Xdr)?, - )); - credentials.signature_expiration_ledger = signature_expiration_ledger; - auth.credentials = SorobanCredentials::Address(credentials.clone()); - - Ok(auth) - } - fn network_hash(&self) -> xdr::Hash { xdr::Hash(Sha256::digest(self.network_passphrase.as_bytes()).into()) } @@ -279,7 +284,11 @@ impl Stellar for Box { type Init = u32; fn new(network_passphrase: &str, options: Option) -> Self { - Box::new((network_passphrase.to_owned(), options.unwrap_or_default()).into()) + Box::new( + (network_passphrase.to_owned(), options.unwrap_or_default()) + .try_into() + .unwrap(), + ) } fn network_hash(&self) -> xdr::Hash { @@ -287,37 +296,40 @@ impl Stellar for Box { self.as_ref().as_ref().network_hash() } + async fn sign_blob( + &self, + data: &[u8], + _source_account: &stellar_strkey::Strkey, + ) -> Result, Error> { + let index = self.as_ref().as_ref().hd_path.clone(); + Ok(self.as_ref().as_ref().sign_blob(index, data).await?) + } + async fn sign_txn_hash( &self, txn: [u8; 32], - _source_account: &stellar_strkey::Strkey, + source_account: &stellar_strkey::Strkey, ) -> Result { let index = self.as_ref().as_ref().hd_path.clone(); - let mut res = self + let res = self .as_ref() .as_ref() .sign_transaction_hash(index, &txn) .await .unwrap(); - println!("{}", base64::encode(&res)); - println!("{}", res.len()); - println!("{:#?}", Signature::from_xdr(&res, Limits::none())); - - todo!("Need to figure out how to get Signature"); - let source_account = self.as_ref().as_ref().get_public_key(0).await.unwrap(); - // Ok(DecoratedSignature { - // // TODO: remove this unwrap. It's safe because we know the length of the array - // hint: SignatureHint(source_account.0[28..].try_into().unwrap()), - // signature, - // }) - } - - async fn sign_soroban_authorization_entry( - &self, - unsigned_entry: &SorobanAuthorizationEntry, - signature_expiration_ledger: u32, - address: &[u8; 32], - ) -> Result { - todo!() + let sig_bytes = res.try_into().unwrap(); // FIXME: handle error + let bytes = match source_account { + stellar_strkey::Strkey::PublicKeyEd25519(d) => d.0, + stellar_strkey::Strkey::PrivateKeyEd25519(_) => todo!(), + stellar_strkey::Strkey::PreAuthTx(_) => todo!(), + stellar_strkey::Strkey::HashX(_) => todo!(), + stellar_strkey::Strkey::MuxedAccountEd25519(_) => todo!(), + stellar_strkey::Strkey::SignedPayloadEd25519(_) => todo!(), + stellar_strkey::Strkey::Contract(_) => todo!(), + }; + Ok(DecoratedSignature { + hint: SignatureHint(bytes[28..].try_into().unwrap()), + signature: Signature(sig_bytes), + }) } } From 201b0343697525291c8f543b02fb3217caee9237 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 3 May 2024 15:14:55 -0400 Subject: [PATCH 68/72] fix: fmt --- cmd/crates/stellar-ledger/src/lib.rs | 15 +++------------ cmd/soroban-cli/src/signer.rs | 14 +++++++------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 76fbdec24..0bd0ee07d 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -86,17 +86,6 @@ pub struct LedgerOptions { exchange: T, hd_path: slip10::BIP32Path, } -// let hidapi = HidApi::new().map_err(NEARLedgerError::HidApiError)?; -// TransportNativeHID::new(&hidapi).map_err(NEARLedgerError::LedgerHidError) -impl LedgerOptions { - pub fn new(hd_path: u32) -> Result { - let hd_path = bip_path_from_index(hd_path)?; - Ok(LedgerOptions { - exchange: transport_native_hid()?, - hd_path, - }) - } -} pub struct LedgerSigner { network_passphrase: String, @@ -137,7 +126,9 @@ where let hd_path = bip_path_from_index(index)?; Self::get_public_key_with_display_flag(self, hd_path, false).await } - + /// Synchronous version of get_public_key + /// # Errors + /// pub fn get_public_key_sync( &self, index: u32, diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index 89b484bed..7e293eec0 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -1,16 +1,15 @@ use ed25519_dalek::Signer; -use sha2::{digest::typenum::Le, Digest, Sha256}; +use sha2::{Digest, Sha256}; use soroban_env_host::xdr::{ self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, - InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ReadXdr, ScAddress, ScMap, - ScSymbol, ScVal, Signature, SignatureHint, SorobanAddressCredentials, - SorobanAuthorizationEntry, SorobanAuthorizedFunction, SorobanCredentials, Transaction, - TransactionEnvelope, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, + InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, + ScVal, Signature, SignatureHint, SorobanAddressCredentials, SorobanAuthorizationEntry, + SorobanAuthorizedFunction, SorobanCredentials, Transaction, TransactionEnvelope, + TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, TransactionV1Envelope, Uint256, WriteXdr, }; -use soroban_sdk::xdr::BytesM; -use stellar_ledger::{LedgerSigner, NativeSigner}; +use stellar_ledger::NativeSigner; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -38,6 +37,7 @@ fn requires_auth(txn: &Transaction) -> Option { } /// A trait for signing Stellar transactions and Soroban authorization entries +#[allow(async_fn_in_trait)] pub trait Stellar { /// The type of the options that can be passed when creating a new signer type Init; From 28fca60938b7fadd3d4faa5102065faf5d7e261b Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 3 May 2024 15:21:32 -0400 Subject: [PATCH 69/72] feat: preview hash for ledger --- cmd/soroban-cli/src/signer.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index 7e293eec0..a6657b632 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -310,6 +310,8 @@ impl Stellar for Box { txn: [u8; 32], source_account: &stellar_strkey::Strkey, ) -> Result { + let hash = hex::encode(&txn); + eprintln!("You should see the following on your ledger:\n{hash}"); let index = self.as_ref().as_ref().hd_path.clone(); let res = self .as_ref() From 02f840e754a25d74da57314474561c88e9c42456 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Fri, 3 May 2024 16:05:27 -0400 Subject: [PATCH 70/72] Update one last error --- cmd/crates/stellar-ledger/src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 1eaede12f..e7456b7c8 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -266,10 +266,9 @@ where let error_string = format!("Ledger APDU retcode: 0x{retcode:X}"); Err(LedgerError::APDUExchangeError(error_string)) } - Err(_err) => { - //FIX ME!!!! - Err(LedgerError::LedgerConnectionError("test".to_string())) - } + Err(_err) => Err(LedgerError::LedgerConnectionError( + "Error connecting to ledger device".to_string(), + )), } } } From a5eca3714d03da01315cc4f157b28b215faaf9e2 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 7 May 2024 10:19:47 -0400 Subject: [PATCH 71/72] Cleanup imports --- Cargo.lock | 743 ++++++++++++++++++++++++++- cmd/crates/stellar-ledger/Cargo.toml | 1 - cmd/crates/stellar-ledger/src/lib.rs | 6 +- 3 files changed, 720 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7c5576aa..c2d75323e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,6 +116,25 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "assert_cmd" version = "2.0.14" @@ -146,6 +165,205 @@ dependencies = [ "tempfile", ] +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" +dependencies = [ + "concurrent-queue", + "event-listener 5.3.0", + "event-listener-strategy 0.5.2", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10202063978b3351199d68f8b22c4e47e4b1b822f8d43fd862d5ea8c006b29a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.0.1", + "futures-lite 2.3.0", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.2.1", + "async-executor", + "async-io 2.3.2", + "async-lock 3.3.0", + "blocking", + "futures-lite 2.3.0", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.27", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +dependencies = [ + "async-lock 3.3.0", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.3.0", + "parking", + "polling 3.7.0", + "rustix 0.38.31", + "slab", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +dependencies = [ + "event-listener 4.0.3", + "event-listener-strategy 0.4.0", + "pin-project-lite", +] + +[[package]] +name = "async-object-pool" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb901c30ebc2fc4ab46395bbfbdba9542c16559d853645d75190c3056caf3bc" +dependencies = [ + "async-std", +] + +[[package]] +name = "async-process" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +dependencies = [ + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.31", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-signal" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afe66191c335039c7bb78f99dc7520b0cbb166b3a1cb33a03f53d8a1c6f2afda" +dependencies = [ + "async-io 2.3.2", + "async-lock 3.3.0", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.31", + "signal-hook-registry", + "slab", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-attributes", + "async-channel 1.9.0", + "async-global-executor", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 1.13.0", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.76" @@ -157,6 +375,12 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" @@ -214,6 +438,17 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-cookies" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67bd8fd42c16bdb08688243dc5f0cc117a3ca9efeeaba3a345a18a6159ad96f7" +dependencies = [ + "lalrpop", + "lalrpop-util", + "regex", +] + [[package]] name = "beef" version = "0.5.2" @@ -223,6 +458,21 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -253,6 +503,20 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495f7104e962b7356f0aeb34247aca1fe7d2e783b346582db7f2904cb5717e88" +dependencies = [ + "async-channel 2.2.1", + "async-lock 3.3.0", + "async-task", + "futures-io", + "futures-lite 2.3.0", + "piper", +] + [[package]] name = "bollard" version = "0.16.1" @@ -501,6 +765,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -597,6 +870,12 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -875,6 +1154,16 @@ dependencies = [ "dirs-sys 0.3.7", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + [[package]] name = "dirs-sys" version = "0.3.7" @@ -898,6 +1187,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -1004,6 +1304,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + [[package]] name = "encoding_rs" version = "0.8.33" @@ -1064,6 +1373,65 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.0", + "pin-project-lite", +] + [[package]] name = "faster-hex" version = "0.9.0" @@ -1073,6 +1441,15 @@ dependencies = [ "serde", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -1107,6 +1484,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.28" @@ -1210,6 +1593,34 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand 2.0.1", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.30" @@ -1619,7 +2030,7 @@ dependencies = [ "itoa", "libc", "memmap2", - "rustix", + "rustix 0.38.31", "smallvec", "thiserror", ] @@ -1782,7 +2193,7 @@ dependencies = [ "gix-command", "gix-config-value", "parking_lot", - "rustix", + "rustix 0.38.31", "thiserror", ] @@ -1983,7 +2394,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e839f3d0798b296411263da6bee780a176ef8008a5dfc31287f7eda9266ab8" dependencies = [ - "fastrand", + "fastrand 2.0.1", "unicode-normalization", ] @@ -2065,6 +2476,18 @@ dependencies = [ "walkdir", ] +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "group" version = "0.13.0" @@ -2130,9 +2553,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.6" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -2257,6 +2680,34 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "httpmock" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ec9586ee0910472dec1a1f0f8acf52f0fdde93aea74d70d4a3107b4be0fd5b" +dependencies = [ + "assert-json-diff", + "async-object-pool", + "async-std", + "async-trait", + "base64 0.21.7", + "basic-cookies", + "crossbeam-utils", + "form_urlencoded", + "futures-util", + "hyper 0.14.28", + "lazy_static", + "levenshtein", + "log", + "regex", + "serde", + "serde_json", + "serde_regex", + "similar", + "tokio", + "url", +] + [[package]] name = "humantime" version = "2.1.0" @@ -2280,7 +2731,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.5.5", "tokio", "tower-service", "tracing", @@ -2363,7 +2814,7 @@ dependencies = [ "http-body 1.0.0", "hyper 1.3.1", "pin-project-lite", - "socket2", + "socket2 0.5.5", "tokio", "tower", "tower-service", @@ -2488,6 +2939,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "io-close" version = "0.3.7" @@ -2498,6 +2958,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "ipnet" version = "2.9.0" @@ -2629,6 +3100,46 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lalrpop" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools 0.11.0", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax 0.8.2", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +dependencies = [ + "regex-automata 0.4.5", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -2673,16 +3184,10 @@ dependencies = [ ] [[package]] -name = "ledger-zondax-generic" -version = "0.10.0" +name = "levenshtein" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02036c84eab9c48e85bc568d269221ba4f5e1cfbc785c3c2c2f6bb8c131f9287" -dependencies = [ - "async-trait", - "ledger-transport", - "serde", - "thiserror", -] +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" @@ -2716,6 +3221,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -2737,6 +3248,9 @@ name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +dependencies = [ + "value-bag", +] [[package]] name = "matchers" @@ -2817,6 +3331,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "no-std-compat" version = "0.4.1" @@ -3004,6 +3524,12 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.1" @@ -3054,6 +3580,16 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap 2.2.3", +] + [[package]] name = "phf" version = "0.11.2" @@ -3061,7 +3597,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ "phf_macros", - "phf_shared", + "phf_shared 0.11.2", ] [[package]] @@ -3070,7 +3606,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ - "phf_shared", + "phf_shared 0.11.2", "rand", ] @@ -3081,12 +3617,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" dependencies = [ "phf_generator", - "phf_shared", + "phf_shared 0.11.2", "proc-macro2", "quote", "syn 2.0.39", ] +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + [[package]] name = "phf_shared" version = "0.11.2" @@ -3096,6 +3641,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.4" @@ -3128,6 +3679,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.0.1", + "futures-io", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -3150,6 +3712,37 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 0.38.31", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3162,6 +3755,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "predicates" version = "2.1.5" @@ -3501,6 +4100,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + [[package]] name = "rustix" version = "0.38.31" @@ -3510,7 +4123,7 @@ dependencies = [ "bitflags 2.4.2", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.13", "windows-sys 0.52.0", ] @@ -3739,6 +4352,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.17" @@ -3912,6 +4535,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "similar" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" + [[package]] name = "siphasher" version = "0.3.11" @@ -3966,6 +4595,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.5.5" @@ -4356,7 +4995,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stellar-ledger" -version = "20.3.4" +version = "21.0.0-preview.1" dependencies = [ "bollard", "byteorder", @@ -4364,11 +5003,10 @@ dependencies = [ "env_logger", "futures", "hex", - "hidapi", "home", + "httpmock", "ledger-transport", "ledger-transport-hid", - "ledger-zondax-generic", "log", "once_cell", "phf", @@ -4461,6 +5099,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", +] + [[package]] name = "strsim" version = "0.10.0" @@ -4578,11 +5229,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" dependencies = [ "cfg-if", - "fastrand", - "rustix", + "fastrand 2.0.1", + "rustix 0.38.31", "windows-sys 0.52.0", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -4748,6 +5410,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -4777,7 +5448,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.5", "tokio-macros", "windows-sys 0.48.0", ] @@ -5046,6 +5717,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "untrusted" version = "0.9.0" @@ -5094,6 +5771,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "value-bag" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" + [[package]] name = "vcpkg" version = "0.2.15" @@ -5115,6 +5798,12 @@ dependencies = [ "libc", ] +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + [[package]] name = "walkdir" version = "2.4.0" @@ -5337,7 +6026,7 @@ dependencies = [ "home", "once_cell", "regex", - "rustix", + "rustix 0.38.31", ] [[package]] diff --git a/cmd/crates/stellar-ledger/Cargo.toml b/cmd/crates/stellar-ledger/Cargo.toml index 3761d36e8..1cda34add 100644 --- a/cmd/crates/stellar-ledger/Cargo.toml +++ b/cmd/crates/stellar-ledger/Cargo.toml @@ -44,7 +44,6 @@ features = ["curr", "std", "serde"] [dev-dependencies] env_logger = "0.11.3" futures = "0.3.30" -hidapi = { version = "1.4.1", features = ["linux-static-hidraw"], default-features = false } log = "0.4.21" once_cell = "1.19.0" pretty_assertions = "1.2.1" diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index e7456b7c8..342d25387 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -1,7 +1,9 @@ use futures::executor::block_on; -use hidapi::HidApi; use ledger_transport::{APDUCommand, Exchange}; -use ledger_transport_hid::{hidapi::HidError, LedgerHIDError, TransportNativeHID}; +use ledger_transport_hid::{ + hidapi::{HidApi, HidError}, + LedgerHIDError, TransportNativeHID, +}; use sha2::{Digest, Sha256}; use soroban_env_host::xdr::{Hash, Transaction}; From 5eedab5a3ea60692f74fe376874fdeadabdc2170 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 7 May 2024 14:53:57 -0400 Subject: [PATCH 72/72] fix: use sign_blob and share sign_txn_hash default method --- cmd/crates/stellar-ledger/src/lib.rs | 144 +++++++++++---------------- cmd/soroban-cli/src/signer.rs | 57 ++++++----- 2 files changed, 91 insertions(+), 110 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index e5bde33dd..665cfcad4 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -412,20 +412,15 @@ fn bip_path_from_index(index: u32) -> Result { fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Result, LedgerError> { let hd_path_indices = 0..hd_path.depth(); - let mut result = Vec::with_capacity(hd_path.depth() as usize); - - for index in hd_path_indices { - let value = hd_path.index(index); - if let Some(v) = value { - let value_bytes = v.to_be_bytes(); - result.push(value_bytes); - } else { - return Err(LedgerError::Bip32PathError( - "Error getting index of hd path".to_string(), - )); - } - } - + let result = hd_path_indices + .into_iter() + .map(|index| { + Ok(hd_path + .index(index) + .ok_or_else(|| LedgerError::Bip32PathError(format!("{hd_path}")))? + .to_be_bytes()) + }) + .collect::, LedgerError>>()?; Ok(result.into_iter().flatten().collect()) } @@ -434,7 +429,6 @@ pub fn get_transport() -> Result { let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) } - #[cfg(test)] mod test { use std::str::FromStr; @@ -473,25 +467,16 @@ mod test { }); let transport = EmulatorHttpTransport::new(&server.host(), server.port()); - let ledger_options = Some(LedgerOptions { + let ledger_options = LedgerOptions { exchange: transport, hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - }); + }; - let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); - match ledger.get_public_key(0).await { - Ok(public_key) => { - let public_key_string = public_key.to_string(); - // This is determined by the seed phrase used to start up the emulator - let expected_public_key = - "GDUTHCF37UX32EMANXIL2WOOVEDZ47GHBTT3DYKU6EKM37SOIZXM2FN7"; - assert_eq!(public_key_string, expected_public_key); - } - Err(e) => { - println!("{e}"); - assert!(false); - } - } + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, Some(ledger_options)); + let public_key = ledger.get_public_key(0).await.unwrap(); + let public_key_string = public_key.to_string(); + let expected_public_key = "GDUTHCF37UX32EMANXIL2WOOVEDZ47GHBTT3DYKU6EKM37SOIZXM2FN7"; + assert_eq!(public_key_string, expected_public_key); mock_server.assert(); } @@ -511,22 +496,15 @@ mod test { }); let transport = EmulatorHttpTransport::new(&server.host(), server.port()); - let ledger_options = Some(LedgerOptions { + let ledger_options = LedgerOptions { exchange: transport, hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - }); - - let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); - match ledger.get_app_configuration().await { - Ok(config) => { - assert_eq!(config, vec![0, 5, 0, 3]); - } - Err(e) => { - println!("{e}"); - assert!(false); - } }; + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, Some(ledger_options)); + let config = ledger.get_app_configuration().await.unwrap(); + assert_eq!(config, vec![0, 5, 0, 3]); + mock_server.assert(); } @@ -556,15 +534,15 @@ mod test { }); let transport = EmulatorHttpTransport::new(&server.host(), server.port()); - let ledger_options = Some(LedgerOptions { + let ledger_options = LedgerOptions { exchange: transport, hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - }); - let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + }; + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, Some(ledger_options)); let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); - let fake_source_acct = [0 as u8; 32]; - let fake_dest_acct = [0 as u8; 32]; + let fake_source_acct = [0; 32]; + let fake_dest_acct = [0; 32]; let tx = Transaction { source_account: MuxedAccount::Ed25519(Uint256(fake_source_acct)), fee: 100, @@ -584,23 +562,18 @@ mod test { .unwrap(), }; - let result = ledger.sign_transaction(path, tx).await; - match result { - Ok(response) => { - assert_eq!( hex::encode(response), "5c2f8eb41e11ab922800071990a25cf9713cc6e7c43e50e0780ddc4c0c6da50c784609ef14c528a12f520d8ea9343b49083f59c51e3f28af8c62b3edeaade60e"); - } - Err(e) => { - println!("{e}"); - assert!(false); - } - }; + let response = ledger.sign_transaction(path, tx).await.unwrap(); + assert_eq!( + hex::encode(response), + "5c2f8eb41e11ab922800071990a25cf9713cc6e7c43e50e0780ddc4c0c6da50c784609ef14c528a12f520d8ea9343b49083f59c51e3f28af8c62b3edeaade60e" + ); + mock_request_1.assert(); mock_request_2.assert(); } #[tokio::test] async fn test_sign_tx_hash_when_hash_signing_is_not_enabled() { - //when hash signing isn't enabled on the device we expect an error let server = MockServer::start(); let mock_server = server.mock(|when, then| { when.method(POST) @@ -614,21 +587,23 @@ mod test { }); let transport = EmulatorHttpTransport::new(&server.host(), server.port()); - let ledger_options = Some(LedgerOptions { + let ledger_options = LedgerOptions { exchange: transport, hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - }); - let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + }; + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, Some(ledger_options)); let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); let test_hash = b"3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889"; - let result = ledger.sign_transaction_hash(path, test_hash).await; - if let Err(LedgerError::APDUExchangeError(msg)) = result { + let err = ledger + .sign_transaction_hash(path, test_hash) + .await + .unwrap_err(); + if let LedgerError::APDUExchangeError(msg) = err { assert_eq!(msg, "Ledger APDU retcode: 0x6C66"); - // this error code is SW_TX_HASH_SIGNING_MODE_NOT_ENABLED https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md } else { - panic!("Unexpected result: {:?}", result); + panic!("Unexpected error: {err:?}"); } mock_server.assert(); @@ -649,34 +624,29 @@ mod test { }); let transport = EmulatorHttpTransport::new(&server.host(), server.port()); - let ledger_options: Option> = Some(LedgerOptions { + let ledger_options = LedgerOptions { exchange: transport, hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - }); - let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + }; + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, Some(ledger_options)); let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); let mut test_hash = vec![0u8; 32]; - match hex::decode_to_slice( + hex::decode_to_slice( "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889", &mut test_hash as &mut [u8], - ) { - Ok(()) => {} - Err(e) => { - panic!("Unexpected result: {e}"); - } - } - - let result = ledger.sign_transaction_hash(path, &test_hash).await; - - match result { - Ok(response) => { - assert_eq!( hex::encode(response), "6970b9c9d3a6f4de7fb93e8d3920ec704fc4fece411873c40570015bbb1a60a197622bc3bf5644bb38ae73e1b96e4d487d716d142d46c7e944f008dece92df07"); - } - Err(e) => { - panic!("Unexpected result: {e}"); - } - } + ) + .unwrap(); + + let response = ledger + .sign_transaction_hash(path, &test_hash) + .await + .unwrap(); + + assert_eq!( + hex::encode(response), + "6970b9c9d3a6f4de7fb93e8d3920ec704fc4fece411873c40570015bbb1a60a197622bc3bf5644bb38ae73e1b96e4d487d716d142d46c7e944f008dece92df07" + ); mock_server.assert(); } diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index a6657b632..e0750d9a7 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -10,6 +10,25 @@ use soroban_env_host::xdr::{ TransactionV1Envelope, Uint256, WriteXdr, }; use stellar_ledger::NativeSigner; +use stellar_strkey::Strkey; + +trait ToBytes{ + fn to_bytes(&self) -> Vec; +} + +impl ToBytes for Strkey { + fn to_bytes(&self) -> Vec { + match self { + Strkey::PublicKeyEd25519(i) => i.0, + Strkey::PrivateKeyEd25519(i) => i.0, + Strkey::PreAuthTx(i) => i.0, + Strkey::HashX(i) => i.0, + Strkey::MuxedAccountEd25519(_) => todo!("Muxed account"), + Strkey::SignedPayloadEd25519(_) => todo!("Signed Payload"), + Strkey::Contract(i) => i.0, + }.to_vec() + } +} #[derive(thiserror::Error, Debug)] pub enum Error { @@ -50,11 +69,22 @@ pub trait Stellar { /// Sign a transaction hash with the given source account /// # Errors /// Returns an error if the source account is not found - fn sign_txn_hash( + async fn sign_txn_hash( &self, txn: [u8; 32], source_account: &stellar_strkey::Strkey, - ) -> impl std::future::Future> + Send; + ) -> Result{ + let tx_signature = self.sign_blob(&txn, source_account).await?; + Ok(DecoratedSignature { + // TODO: remove this unwrap. It's safe because we know the length of the array + hint: SignatureHint( + source_account.to_bytes()[28..] + .try_into() + .unwrap(), + ), + signature: Signature(tx_signature.try_into()?), + }) + } async fn sign_blob( &self, @@ -257,24 +287,6 @@ impl Stellar for InMemory { Ok(sig.to_bytes().to_vec()) } - async fn sign_txn_hash( - &self, - txn: [u8; 32], - source_account: &stellar_strkey::Strkey, - ) -> Result { - let source_account = self.get_key(source_account)?; - let tx_signature = source_account.sign(&txn); - Ok(DecoratedSignature { - // TODO: remove this unwrap. It's safe because we know the length of the array - hint: SignatureHint( - source_account.verifying_key().to_bytes()[28..] - .try_into() - .unwrap(), - ), - signature: Signature(tx_signature.to_bytes().try_into()?), - }) - } - fn network_hash(&self) -> xdr::Hash { xdr::Hash(Sha256::digest(self.network_passphrase.as_bytes()).into()) } @@ -299,9 +311,10 @@ impl Stellar for Box { async fn sign_blob( &self, data: &[u8], - _source_account: &stellar_strkey::Strkey, + _: &stellar_strkey::Strkey, ) -> Result, Error> { let index = self.as_ref().as_ref().hd_path.clone(); + eprintln!("You should see the following on your ledger:\n{}", hex::encode(data)); Ok(self.as_ref().as_ref().sign_blob(index, data).await?) } @@ -310,8 +323,6 @@ impl Stellar for Box { txn: [u8; 32], source_account: &stellar_strkey::Strkey, ) -> Result { - let hash = hex::encode(&txn); - eprintln!("You should see the following on your ledger:\n{hash}"); let index = self.as_ref().as_ref().hd_path.clone(); let res = self .as_ref()

=hf!Jy;Cq=rX zu@psE^mteBgypeR+HvKGV@eQ{d;v3!E%Z88%&6=NF3u9cJXsw!@6^J6UbM1d^L{B* zgB8^<`I#QzEKy6-;JvLd3q)-Q+QXKKUz5o2Yem@>7hPYoYXL0A{X8^#Iq4D)Bs$m2Y!9pwHMsuNSTC zj-|(}hUSeGC=37vx|PNHY-`fat-T!6`H2iqU39Fn#aUu5#AHgMqA|Lpu@_LVbag8h zumG2sRUs;@n0a$V5fwa6TZj^dkRlU)QDh1&r2#U@)V%bGGBK9)*hs>AB>Xt2Ir$B8C zZxpXKFBjaBsRfoI28zrTsYp9W@nVs7if(YVUM)pwRx71cg4SUvR}-vDphkKTO89p3#eU0MZ1C2uCQ2(cJ?+k{0+*tB|-{2 z=BnPN0%2FU#lUW=GPADkFb8@G9|YplsM33wc8SztfPJGfd8f@C9g)8L4xscIPtnSz z_`@nxTA)QM`-sP_3S)s47bKtBg`gwLhiUi@S?#oHY8_z3!oqA@#)G~{wbEgBiejaU zwM5rZRWc*OM`?0kwNqt|h-%iFv}wnAUl&F>_J(QZz}}_yh-DZGvnK4?&CVV;Ps!}8 zA6aa)6?9*<)%c#U0&uHB_dcDCb(RGj7ggp&*b#y5S%9M=)!*@mkTUQBOLS+kh=rUL z=}d|c*EmJ&Py(Jya|*`<&}Uh_3j~Xe9R?U~O;sS9X;E8g0*4!d(sP+pO<`w(l$N3; zR7g+rAk7#Ltnk1Mk8EN$iPRfiAk$j6VjBg7gj!%JTvKGBNO|3$7a2&3bW?P%NL?RM zmhK#>oBd$ESv6P)6e>+#te7RYTGmihQR#2d!cm*>SAeEz54nu&T`C z@aA?lkHfAoVeiUnon-zR05w;pVyp`f$=+|SzMLH+mrYshN|PzPxs}aQw;GbIC58zr zhH18_)TLz26J;F_EEh4D0!!fmp-m(YK=eWbJ*jGEcQj#FyF0i)d)pdzgLr#n3~Uf-j5;0{5+1z3QiP7(Eh6Q0f|OCE6nVl~ zy;_PA2+90q2Gq`9I+yBdnm~VDTc0IudTqJJtnBlGKCy}t150T<$w2zZuLS6+(t>74 zri-#kK36QaA2l&bn|=IhZ?l;{@&()`QXq4|y{_ITP&O8Qf`!HouNJIv?E&@J>mnJv zDVV9}u3~2iHaokswW;X^k+kOoZ#b(r5KVJL(gN#6)C@l0>W2hJja~VqV8peALrVK- zhGgv#z%G&e^n%`k0b1~wNEX1$A}qj5BCRE0|Dzo1LW@CqdTSaC^_S-k*P-kP)4$#TTc4w1UJ z58P!XDt<9zb?HvwoH2_eWN&t1H;CGr^Z-I{e&KO7&U;~&^>UH!Gl6x+Hh|s64(ioX z^u%3~JgbrAj7@WT|CC6+fc;u~itvmv_t`=m*siJQvsn5g>T4E{8I_$qg~AFA@5u^j zo46>}7xM5_4P}oFXD{Frd6+?-W7^@nTlE-Tq;P=4#tt*y%+Z<~>r<+6lB!hi&j+Vk z^^I+UgU&|ySd;^9mkGO%rUMqL~ z0B#qlTc0<^>#xir?C{29180etC3+77yH3=`gL%pDoAT^(h%=ORqey)U+-&R$z^krI zqRnGjm4+};ytfCAh&?K*Su@P7M=YyxOmMTNg=R-XFzD=%VA$EM0tT+;DuWxZtP%%V z#G-Aj++0c=@hspL^F3U)dO%t4npNSQwKPqS>gAGU4YsRrr#V;!xUWioVN8~WYG%0} z^4>9Ll|WCZ2-gHnNp?IIM9l^^F)Nnab+)i7U3G=5>=Kh)C(2D=!TN=`7Z_5UYH2q( zY%ws$2lJD6XyEd|;!zD3;k}54)7N-_0Hp z9CLPqtT%~jX5c-tc)+b56`T-#eN*QMyDdrM>M4Y?GB__{CV;PsZ2yG0=oz!Y*F>rh zcq3DTixo!gev7jv&pc<$BnjDe0DFzdo@;A6fbYz+%P~^Vdm`I_VBQyH*B{tv=E^Tm z@bg72T&~KFrb{zhqzgSj>~#}hru5gF*#OY*W*s0!YF02c_E}bApe}7Q?NTNfAog&Rqz~2U}1ZV;` zfen~lBK0M}9f9eEmRJ)}g}+m=vrzMtsarUJn-z6lD@gIuom~`WOVJzyqLRUzNO=znUbx_uBoe2YHnX^verv|TQ%$K$45u-PM@e0&|~aM z-u4WMDlnXtyH-2jo=^ELP((4fI+EqYqHi7aqY75ir%YcL7vEwJV}7%%!))U{TpzN$zze zUbl<1dEF)0>1^Jt!jhxxk}<1Ztf;2+{uHa#WSOsCfNe$#?3{J^D-yWHT6J$LEPdA2 zG;DJ8RmjS+6oF&Uf{J{2-ul{Xl0SGBTg)!_c_Q6Ebm_LJQ=h*}nlGA6jVKTniNa-8 zqdh60B~(eiCfQ=au(M^W3sU$f6yDk3;2vf9Sj~?yX}0I%<|A76u%t&srEQ|gtG1!vhFDChlD&`YTl>Yl2<`X z5yKK?+-&6r)2xSX7#%^LB2suj;r^i0^}t4BR|0!QwjJ&hbHMd_wM2#CwM!b^T?0|X zo@7#l@`YiGLf2R!d!|ewwK=b)(6to6H4$kg#tX*#T`|UCq*%TAJ-yz#Nec4@|G? zfg3|Fv_$iwe7iL2AK-{cJ6MOnQq{~pYQtU4qZf)KV|YZOoOPong)>Gji5X`yYiC6r z6qSaGDCrwIp>$J2)>_e4%PP$7b)WFr&4pDCfd`EpdR*|Tv)n=~pM(L0tKMc3LDo@IvURn%z=*phsl(Y>x|F53seEUga#M z@%i`;0ukRJ@e!0RPlR-rvp|G2NJzeO4FVC;AR)1#t0)j54HD8K*8>q!rMnp<;%ILA zgkCL0v}9$yndR7Iprct|Uh0!|S>u%2A={lIbziyE(^6zTUe3zz4GX0%eA4F%3m{tu zH;JX+iOdo$DcadW2)ly6-Wx|pSjod8`8%Udd(Qf=@x0&#XWgwY3JTmWh0p%NTN$z% zDsj7&nqMx(hghCxTXSXN&->&Zz?P3J_-_PK-Q(mv`sHI_vS3R%yMW%Xw{Hi(pZ zTUk6_Z*D3KB3Z3cd)D`rOYtF-XMKM;Ygk#gn8U)##NlG8Z?>#?D3Y=P|PY! zs@++sYh|n$t`qp3as#+kgI$@g6v6IpE7A+>H}(eLL1X6u4;#A_IBM)?fMdok0gfB{ zL%<1R7X!~2`)R;TppJ0D;#QDfx55?#==$ltqj9nn>x z6`U$6nRUrbTHQuk7O>9P!jSB)T3zo}K)sA6D$W>=W>*v(Q# zui1S9=r{J0#J11ahF&evI10>_)F&!khtx((?UrSCJIt#}Y+*;HCIB$=a(364ojqO* zyFwenA7h+7@Cv&@sVoutV_Dx#l1B9c9_kR#VcJ^9NuczVH3(+56=?tqLoc*Mg~h;Z z4o3NbynLm~HaYMrdjTvJZeD;vvXs`5?OFiW6;0xv{0cd=PO4i)yE{*ygi1LGJp+PT_-=ZQ9i*yintssIcToqKI9r#^tcJ{D{ zc+|FO;TvALmLeVo-#cvx$cnTlMHp{OQKU_h5223#otHo(bpIV?YHr0TGQ*=;7-otF zFP6eJqeVj*5pcCg*T}VkM~qzo9Ct0?Rj1~O^a2ZvT?s69E#Q}+!d@t??JeA&YL^3UGPVI^ z{nGJ(_`{|~V=029Rog@wf$f65&H~vsm7=i6It2LMTN_ThvNg{Etj67d+;05i zr;HgX>4vXP0Lg41=5c33)`>va)%K{{*$L6G8>FG6bZR8^b}CC#8eoVuEA(n9Es)J6 z;Ib1T3BZfk!S3v$o%yv$)_!Vq1lg^|&Z=4ubV+}$NU;Dcyh_h(Q+eF>h%_bU3YIy` z1!AMKdbLDLE@c&ZEOw{q1qwSwSotKh-#T-v;W}R)ynWQ*R)tL?e9co)UD*JvXi8xh zUwV^wMKUNg9aglmCFxd!X=JM> zTw7!MpG9n>6t0YGEX}D_+&=5MOeLLWWglz0Rbep0mk*Q8M7Jso6IN^lG*D0RUlw?Z z`JZHRH!yDIxyZ8#epX$CA{o^g5=(Cn!is)QcDB+g#EMbyPO+P0E}o?;(bUD28gG_W zrHiqnJ~C@u(5IeN=_ci*3zm>7-lNlfZ?*qw%$5nINzt$HKDX>({}PF%aKhI#%d&r; z!HPO-x4*0+V3rjb2BHW9Y$*c3txwZlz>CRW{Sj?gqeJ&!eZ8nYPd%3Sc$YvQ`4~t5$tKTEObws+1jcExOgPn;FN zHW9lXxJRVv0NkCa!3SJ@Nbs<;M+Ik`r3tS(n@6>Rh=we{!sH?<+RM{pZ85ZlLZ#gX zs|RFp!mJvn1Sn(8fk{sv*mX%Vb7C!Nr(Gg@uTSEf$ z8(X_Gx49L-mQyGS8t~Iu>t%Vz(LiP!o zmn{r>Sb1f3W4TlQG`u#R2d#<*!+FeEYI{k91&{Z6Bc=VFH&Jt(Ri-6+6Zh{USyo|S zkAHS_gq-Uv*B(UPDpr2m!Hup4wu!6*VICA=13o3v6Sc_R>3txum)nblBg*3BIxHjWUO;|F?O*lI2le__NW|)$@gu6wW3Bb#l z`VHYFQ?CGc&W&}K@cNAJ6D|;`B*61-OnBzK&egzenOeb(rK#dHaKJ2M?cdYqxfWO; zlCK`{7E`YTvZnNazM%{OSyOt5|Fnpb;2DvoF7UFkjn@S4xfbAPFUBA(lQGbnslj=! z2C@eBfU8YC4A5SjtQTgD?NO{_$tRipHq4t+>T3MH;31opL%=dcA_raI8Vha+SZC}? zV7+UBO|Av@xE46E1$vEb06yJT0uPyX708YD>VQIGjRsnocqUEzu;9-p|9~R`T z7Qh0+df(VpfM0-ysCAz?U62>2F_tL4#>(^pc+wcOT65>HQh?ZnV~{*d*}rSdrH|cAFOUX5D#Op5W;nxLJuKAl5Uu?OJ^#UK9L7K41w1WU;ROjp{MD}LO9srJrl-&uQ zbanpmY;P30f2^egw3Pi@J1%3-zaxkZ!xxl$R+Oyx^AdfRDBNE(ZXO+>RB{(GMWPA< z9mY2FYKbdw6lj-5H3C?1tsJYqYB&B9rnOlBZRXD&_jMXO-h-AX;9-vc+%N3hu|4VW zBI7-1QMX%4BB6DayMUsd-PeX)p$Cd~<3DQJTpfa0sy7)s`ZKJOW@A<9_pnQv9d?7( zJiUZJ2F`Ei3U_*kszbs`)>R>OfE9&VM#*ZxH(97>TUIZy(AXhhv52`YuoNXQ>lbMm znqDuF6~&ZA=?!MJYLj5IvwF1@1(rx!DpCPpqexW}uB9+rCyBgL{L_BK7sWM}Wlz8; zTA2x9=ZnYQ!FzWO$=VVX&98b}qfEuq1KuLi=4R%4sg|&h@UAth6#&gv;hEqeS3fRz z!r7+;ez987sTnw1#D_(KJDi=SjbNtj4J)`rKjrMZf=RZCtk@>l>Fhm%`WT~M9vg*2%>bjHax|8aHe7PlY_;O3)@MU$Gr|1w?_coE1C%{xtzxCu(VkKd7^NAYto064`{?nNnWD2PF zfHohX9?)-=hl%cNrUuW6G_lSLvLWn=n5l=5U-jYa0d>-s2$u+!i7)|In|k;u0slL# z>faW;FT%o5^FY^`HbK8ni5_sNsaFE*tXP9PT&?;nMIW*AtrKZG0DRN16L(LTwz6JV zOMSZtzXD6)Y@H9yW@ai*5skd|T*w(c2-c}vd#`YRf z$YuL$l{d?(tn8uk|3A>AQ=-T&w=LO+G0nEhIx8I0yr@8XZa7CxC0=?67!{Rn z>zGJ0<%DgY5^3o(GCyxcDt)$E=DfwKotQh7vETi5zASt<`S?l$n;4nU<89RxZO$&Hm>u+ToE-8!dZE4bb$!LZNWhY^&eYX*Y@MXIl8t|PW8(Em$BAfd9#!&b@ zBIdC~SdOvQ&VpmgI3cRpS)Y^o#rmvn?ZrK|ACEm&h^xbmQZKfw2KNEWoE;JjJ6moF zSEEgqH4J38n3WO6Y@8bjvhENuW5ENW;i5vS?>tv=?@j z__TuT16y?}HkRng@Nu0qiXC9ADzVQW9vzWBJ$KwA@d|e1f5%|apZ&*Q_$#y#yKYrP zW@!r;%0fy)=T5^yf|+kc8bDv@VV1cbxHqP1TV1vl~z-$po58M%YnBA@i_J$tj ze%AvJgdXOo>w#mThk4ZXz+<6@IpKQXiO|D5>3V=0V2cxGM5H)@QzFyDoEAwBJR5qL zGp+}o4?WBat_NNWJnVBDEK23q4G`>w%8Y!(8KffDgy4-|$IdiJVzAIs)G*l3(CfW9_}* zJ5@fMg{L>hPoolrFh0HK;a41wxV#PJp)rME1HSl^EvBHU(Ctvn<+ zY;1#}ATYC+sil~8gvGR%=K~+X*#l-N!OZj$mP^Dh6DiDc7GZA{sfJ%~A&3V=CX_9S zZjXgEaJS$-XUkTbGk?0pz}sfC8Z2Ak%N-OhMN9~W`Yw^)z5;iPG??A@2=5bdo1=Gb zuuS{5CcTT297~`DnOH+q4#=iJD+Ub&W_`01qr& zkl7*&_ma;5{G2d9!_hl6i}8>wXubUARztVT z&a#VCMs6l$IdF<{^sGph_3 zW|K4<7$o+~`!c><6LwFT3$9w0Vk{FEU5Sgz&^)T*6Nm`2iA03m3-u8tF0L5SOXi|7 z9%rR~+p;R-u}EEXlgQqa!7LHQ`#1QdqU`+}>@rd1{Ts_|vof}cO7Gvctr;DmgIP4% z(|}pVu3Riw;;ddRF$l#1c5;|SRJ!07tp;(s&a74ew~Cl5pcgK@qbe3&LE(8KEem2= z>MWbaptHc!BBs?dLR`WNEJfHTPK$KWJ8S=Dh@lB)rkAihCZ4+lJt8k>T`z@4Q`okM znaj^kQR&)1)_$q)v#eg=sIi5~jnxBEAGWND?hzr z=*B*Ko@pLeEfO4e_JrW9vwF1@A@oS{0)@ul_I71$6~!|>v6iA}qFmvvOl8g54waKFg{&cv|Yy^;w$n+4nm3 zmK|QpDA|Bj95$pLw5&=gHjJ(HrH)DMpFCC^UR2hbR;n<&>FHKI*lHKm^z?M8d2Cse zHDBrl^;tZE-CLi<1H6-#RSAQ~Z+rw+Qx_kF`A4Ibb}duZkf?NRrd=DPuKBV>vTjq> zyH;w|dxH7)kZjcv!EtAKbavL+@~Azg0w+Ye9h|1SKiv`{JuaHem!zUVc(P!Ebka;H zBr}Ak3noaX#!U1~93lJQAghP~UlZk3mxKFCLwTwYO4;j6%6(Z>Gc*^arVDjz&j(@N zlfA$KQR#m(==wIPJ1na*mQl*?qHFd#zA$=;$lf`@tT91fZVSVgan}UM=Nx=GQIo|d zGu!I39+sLXtK)54kzeDLX~Q6uKFFl)m!#%{!J1OVeXi zRg6^axT;p{&S!Q#%9KA~rpWAIW{FB6P=q?CTGn8fV1}`U+eoYwer9D~3+7gX0Io|9 zbhTSry56!HK%b}*aoA09JAnHN-A8n>h1`tQxT%M^YP{yGUM;b+ zhyNaF6e7{i77>Si7tB)E-yyim*de`IN=tim_Mcc;bR9%@K*Sx-LE%x6DgZp9RZoe1 z+O>MMRP|}+G2T$7{B&nlc0WYmRNEucTmzOEyAtrQd!sLO$=@d}ambcRPdTesOHpy| z@rE8{%ok}sEEKr&M(+HEwbE{KgRKH?&Sd+9;G(m7wG_^KB;6z`Z3{63%vd=9c1ouP zGjA+~2Lv~>JKP?Ly2Ax{BdHS8ZWcB~>T2FIZOqiNuvB$qH^Cc~sXls5@Vc{Rt(R&6 zyYclIUATy;CW~#0Ik*9M$k@gs0w41R-Ts1W&kLwfO$OdI_6DHU;%~GGvLlc~Vz;_> zyI_a2GPM*jvNoxfUuFeyt3K%r_vphvdiu=Yimm|YFI`9T3M$PTr-GhwD(Intg(6kf z19}as@)`>7FJxdJqihmtiiFde*)k*xw;BLwYgXVbV+&goZdZ?4^)dp_nN@FIL+|_2 zc(?b`?bnEs86;NumZGl@OFAYh-Aq&2EWHex1LCw4r4W4n+BEN!XgeMZ#_E1xy|Jr+ z$6c#eOQ|CYDKfyP<0@dTNd4VmT0)3hKHsNkp>(x>?B32LZqjnyVI@NYHaJ>_2PzjU5CQixg4! zu#mApN?0SKI5z_$lZM^|_FtvqmR<_>QUle#sBj}JS+x~luT?MedsRX=TB#W(ryE3)F zQq_@NAhHQyzf_tPVHn0KVwVEUO7AA3$2~&Z{}-55Wn8eBA!06q-un!qrHB$ULQ`Ly zYmVla;CB9+bO?Uv1&}$e5#{Q;&RM;VB`;)+Un8XobzpfqaqzIyW?_81>>EzbK zQsg%`-5A+oBbU8^(sD@dt*b_!ynBYkS;FNW zkrv`!fx8>KSUe)tG0%Ne;5{>z%WRnTy0Jyp}*p{2u8nLY23o@gVui&ou_Vh`4cbsJhhvvYb96K z(La`co@l@Ly!yxA-ify38V#!US|G-^u>bu-jqgOq<7%(p4_dt+_TS&{`tjj>`TO_l0T*IUj*{%~LZ>hH@}dmdkHe_yTj_|>ZSYWLsQy1$9`v)f(yGma19KKel! z<3@Il?n>8ALReIuG!?f86VQkJ$|XVOM*d=i;uj zjX>DdUgzCzMQT@jou4H~`Yd9hz*2gh&+=;T0kXW>dw?vjx9k)7YR}!Py`Oxw?x(MI zzkIdYG12k3+U@#)00JI+neU)WvA-=zuq3%e`%i!%jRYuws5jE*pWuH-N5t`uL`eXXxcZ^xC- z+Ol|b9S3?uhwb`u@%iT3-I=415JmEsG#s})~g)_)UiM`Fj^776Bm_I=BdcDr@*Dmk#RH2Z#mO~%Ik}Q)YzAG*HkIh zT>zKEN_T}FV{^Gx;`RQ7TWC z(oDsmu%_`J?DqX>;yplrbyNFwpv<%qA#+M z;?9}(wo(oE9{%0#QXnv??91ErGXTrQ51Fj5#?hC+WRU6N${-)Fj?j!cyK|XchgD_oV+g9^uk&Aw zj!3W7V+{dn{mrl1t&wfTU)Zt6v(G2PZfs%D6=BCR*JcZZU7=r!b}Lf5Lf2~9SW4P4 zSBgq}Vdt~b=3Cem;tRV;a-snSSPd0_2eW=*Mam9fbjP)0yHvjb%!JZvxG+I)VRw0N zy&HXOlQu9cmDs|LrKH$^uq*V*Q06b`W9EOwU)Uwlg;c%fFX6bfDLztdHvTGMhFvnX!mbcz*m{<2{@`%*pn-x+2)=@&a@l+W@@6-l(uNBiC1hWpKuAI(w2_0DZ;|>D3a$bz9~w z*__;}@LeWh)uwV<{y>edhbH7JtS;~CeC8{O{uZme5PeuN_KFL@ioPh!;su*VN2E`` zN$0r4(mV_+AA9?!@<(N*i#}0UqRYa}yUc!cGc!XoX7*ZEr?=DY$<8aYRA*Qv3-Tpd zsZRH#SLzJ2Zu2nMBbX_&4PmL6^&(v(flZ-@+3I@W+0Y9u;rH~v^o~h_q!BimVERR+ zh$%uCo>iD8ZMV6%WYBP2nVKD$-6Zoq{*|+Hf_7b*UDAq$=DILhVlTK-t^ZJV^rsdg zP`h9oSt}B|F4zEK2GAdRn8hOb0hWl&56m*x1It4X!~QCJfQyUS!wkC~SQC1oC8l$+ zJ+}MO49X9{czXuY_6+3h8A#hRkhfkpXsOkbGgQG{%5iMdr_rD8?adb8>UHj3Elz>}^9p3c;# zg)fV=%Ulq6&As9*(fRmf!cjsOHQ;4q2gWJz)K86$$RK?y$)qUlH(}LdSp&cfk&P+L z^&&MCh`CkhL(0$2GE4W;t+#ssZRWzZ=-Z7g^k~t_)|FcodUQ+Xi;y%_z)X=170g`M z1B7aNp(T1R%KIv}Z%m!8Olu~L`?W754ZrqSZKN(3eFki_EPGk)5jg@}IRR_$peNFSn}r zdPz}U5S8y%p4;rb*`qx5IlyYDjNsdu-NbsT&%8B&n~h~RvqV2df6kFc`vuStQD?<& zk}}-RzC_TM$vnX8xB=)FX<7k3c~=1Vlvb~nqGm?1Q=}}$+NX4(SBWpT>cyBPR{9x% zXvGxEvNu=mcI>)RQ@L!jC#b!Y>-Mq$*B@<*dbLEfQ+{T21R<*dU}?}#_EzUU38gJ7 ziX=VQomrXuJi3N1gJwcJ;n?Y#4>aABOns@<||_G49`EKrk*ro)u37cLbI^#jHs%-lW1#ai&_jb{3tVpxx`4 z-E38-#RSk17J|?cQHPyJZFdgs%CuI))U3cLGELdKIGQWH;H~@Tybro9%ifM*RjD=X ze5nrrT~=fW@Y1!8JItz(6<4_{)fsk4oe^7MvkJQ`3LxwX_0G@Q>br=&wP6ZZ+8TCA z4~Jc$-mr6j_Q*8s3iaNU)$1K*cL>B-sdr6gH`Th#K04-3qGGpMcKGtkdm*GVg{(pp zLw9(o9`in=S4&KShm?hoEQAdbwWhWRb~~$AOHqIbW{5ObJ;NsyzG%SSYF7Wx`!(Bd zd7d$Rsm#dP#dWEOr{2( zceP$E(dy1$@JMi`62LA|WqiV}!|d$+@Xf}q1a@Ra3gZ?#|xKLQ6CVJAzy%8pj1YirNZXE2^~hP}V0*XxRZwOS6N?#v14eJxn%p zfLWmzS|YUYo2>mg%EWJ>avj@8N9bCnn4(OxdX&X{rFEsO#md4o z>Bf0xRhW4-o!wux+MW4I;+!d~N`GR94Vk5Ij#tBRtFdrYCJnaVd`vJm<^w*i6eLX^ zC&-%KC#G;km z%FH*bRY0Gyh3Qr-Fqpe#DO;j>MXME9^^3G+87>uHdIQk=uPSr3%dC>?b8&Sl*An$n znkDW<3IPvgj(uku|DqXoJ|cF5K-iT9yRjOmFW4cVQ>1ARuqZ3bgVF?=MI+7_*zxfN91y0QRDaV@hAa@s)a@7{K^FP_(nTn*>_7VJbFD(M%%g5tZJk@2C#dVOjAO zTwn@5kKY?A?Mh&g#_?%`cXvt?x)13QG|c(Py<&A^nnER50x@ zl|2{qE?&h56Mk}bO;ToPce>q{yAoKZ!PDl$^dC#^(thz{gOpmDj|-l3_MG4)XZ1Rk zyo~+$N9K~oSz|a8KvkMbV=`^@&LzvXlhuh)gn0oGAF%aEiqybiqsC^sIe=7M_daKezGnoleKKF zSW67^p3jya1(8cRtv^Q0(k+|NuXnDCVZGgH25ulU%Isix`G60HswNjPp*8+1}pP3Pi^R@ZcH zO*Q?pEVVkUl4|~8+cU*tusgjTv9%p1eLQ}+PHd0Jb}pf%ae8rCd4w{0tmap!;hFf?K03T2N>VVAQbKVQ@_lvaCs@(*#`sR zuQtAGy;b$|oCc-cU)N}x+ z<2Idk$POTcie3Az(Gls>ca`JZo|dfGk~L|7>y2#yH5(kwVr_d?VN05uRi4bn=gn7P zC18bx$JKj4?0sfMKUktSv7#F9=*BLw`$W3nV0^!`dbJbRc{@;E8dv>6>mkm;`4Ob zTcQ)XCg^WkjhzOzQZIH_ZpHjBR9f-2LaKPfZmTkUARGC}7kU`0S7r6ONCT-?OEhoq zuZ)f?k+fV?lSQOE>$9k`rvGtxOj*b4OT8zxDyplCe_okZ&637aGCgVsv}iZ}fcAdT zyLPGtV($aH#nQEfEide@F*|!?6Ly6y?_}n$)$HtEX}+;TdbLD9PrKV|b#KKF&r&;D zK})+trSVuU7wnnWSF;W%ixNGaMa?Y9k^`(*{5AW`JIW%o&T6S?zdCN#Qe~+?JeFY# z<+i|PDU9z&_PsKzLoc*Mr$l+bO4-$iF4pOYyz~H07K$nZ6a((1lhZqe?Wt&GbutbZfjfq9j@-z0&WO{3Wk0l}v3O<%O%euSS zS-n~cKlt!a$KFFkR?k=#!)z(C5Y~!R4RDLd#sg-(>-B0Wig1bL!k{8Fmi-|sJx&1` z*a;w6T@d@4NdL?BUs_;hs|%zDt`|xFH~}qIkL&-c4dw~g15dgB zObg7Y>wycd|34?gw5k=V7ibfy-g`a*Gt2eBT-X2hWSAwc2bR13ffkqzt_L={{%_l0 zw!0qK>H57bF#BB(+~fM4Eiezc9(cs{zuyA$gzJH)T>r2P6R3(Pel z83A3c|FagDYh4dq=lVZygPG=fpvU!p(FQZa^}sCGpKh5gW{&HDUf2I&3(P#%1AVT4 zZZgaQ*8}~o|Gy@~+~j&-sq6n!3(PXt0}a<7Z-E(fJ+RvK|7kMJ8rK8sT>t1~nDwp) zHoE?B3(O|h1DjocvK3~F>w!C5|2J(g+guOqaQ%C2Fn75g*y;MeYlhk7dSI{XpKgV@ z+x5T!*Z=P=F!#6~xZm|>CLa{@fa`%HuK&#zm z&{7({m$!>cl()9rC`&yK0~U#_SulP%we!<)DI0*;1 z0_woMSiM?G9s8WLN72MO?)Z8C+cQz@YI+CPnKz8H+e@^hw9Yok3I}kjK`zegOgpGo zOKAaLE%q@Z1=Q#EA9sD?E#Q7J42}u8uSyTK$|k9NgT&IgAeL7im;E;~{uFKQwpR*E z5h5{p$M*tEIo&sziC)T9M4y{grZL5cQ&j@)w_=s-ESIq&%LlF(nCou!YAFI^{<@WY z06ATv9!UU9$#F(Ho8Le-I^ryaTZD;OWzC(Z0ym0m5iqtaVH1RwQr9!3Q6EtVTd`)M zm(c8H2_t7}JiFFiYqYsIix4FY>a zilOtc&|MDZE(i122WbNF%O&6y4T`fAq37|gd{`M-$D9yb?HDha4-aLxh!lTbc8IcB z1J8-g8Ys?E)H@{UHj$dr4Q>``2HYXoVeBekr?HLw0%}nI<{eep8`jBUt4J>GN9%Wr z*D40QaD&!8W?H-Jf7MyNT8hx{;RS~C0>gR5jDn>oK+s-bRbF6KUZC>khth2#H5g!S z+l+;|#`OUGJ2sQ2x*lLh8Fy99GCR8yn48&EdP@4(w_!=q7S?;KNTC5-48d&CxUst3Ku=;<@kdqjMe?UtOA#wV66+j^HMe0{5s4-bK>rX>r6TFG%j_U5 zDJmj{D_4Y7RuP3s;ef)vMfCzcMwMQrL{g2ndI&8ke8G2%YIed>oZ~nkM$hU6`pnl5 zu-Mp2&oE5MHD#$;)sBX@(YE1zW>x7*X)|BNVP~FrBm+Rd$VLffwMeZ6)`(0G!@83` z_Kez5WfwVWc7wo^#ts7~T?>pDI}AK+Y$a?O<}K<4+^W(AWF0gIH|W(;I%;jwWJ}%` zPo)}0Ke5_q1u9k)W)ILc9=O@q%C3}K+0PodRqbp_YP{Bb4XzWccUG^K(uTN|y~S9d zOzZfY#4HJ6oft1)nM|c7(ucZ(1ALfQ0GnK^S4&YhfqLD&z&ero6Y$DLvCentla7{Pvh*Fop0Hfv9f1&+f<`2O?vedRZ_ZJr0X=$YwQp}Yjgp- zU9j1;z!qaGwc%!lh$=uz)ie;ZS#H)2_L*7ZCczSCfm=j!)DF6%hB!+RHnGkW$*dLh z9mN*!LWvreMS`Wq+CTHI6^k+GJ}YwrpPp4aPnkOM3$z`}^#_R>V`)O56@v{lJ?bt-F|EBH&6)c{@==^@|#7MRyXatvHDb^w^J8)s>OnIc*L z^<7 zhi6-0T1C_=z3ta z>;FRw%o^7N>s;Jq3W{c~AZLWW-1!lYJ0Upb0F#fJ> zr))9KbHMdL_DJ?ituO~&4;&WB|8KXz9C1DHkn6uV`C&0f zT@O6!`hzVn$6XIR?)tx-4D*ERwYn^Q`Fls3%BjPEE@LYfPqNs2xITewg-j_yw#i>F zc7X^B(61*Aah4(|9AT=9Ilv`Q8yesRDm7oDtUk8_`i&g`yZ{#qOHsphl5P^!JfG^3 z+AFW!j*0EVZpdyOCVHUA?6{R4=gCZVBLXZltIB{;xZkq45L$|{oGz(Hq#Jo)u88>s z-YC*w>D5w{K$s#@7VxUEl`5r;2f0}7F{|3A5bn#)A|H~KYPS;+FI$O9yj!H+YFRft zAUJ01%F}{poz<(Q2$z*Ybh--xHW)jIi|i(Bx7fQ~t5-`=mc=(qG^y#Mw(_`dRI@3( z-C4a_iV}}VdQ?=?Z_h~mTz!_b=^b8|M7zU-xmF}^09%6Tg_fdLgk%;_OiLc{U8Hi$ z)uAk(QMJR%j-Wb$g~qM|ZV_p8?2df1saFDbWonSVRKdLhx)f`Hr8HLeZ;#?}s(Fng zpCc=y@Hn&G%i4!t48E%9H2-?3R$>4BlbO+GOJO!m5+hu5yN8MKxJVrM}s+D$52nJy!GPYDnsJmetVfSehbEQPNe5oE^&&(`Kpx z2HA7RCd?$M=s0Z;l=ay#m>c#=X^C7qR6RCFFwa@NTKd!y!RLSC^Xq@IshPr`o_Brk z_cwjy$A5au)}Pq+y+75v`L>_?*`~IR$v@oQ^mWbGG=Jly&gPFcPi^g*^vzA*($d}h zt%|(I1uW4bf@oo=x;8q&r`uZ<6jt*)0;ikl4Qu-==y+ zt)g~OmniBd#-%MzZwaCIR0f(YJLpJaf24hMI*$>K5?y6lYB#mGukD8(EM+6+AWN7f#HumZ>@-}e_JSpVOPd5`EvD( zr;m=Nzg_(znl9=Qh5jwsydxriW}+|G+bx@iGMaZ8yK=etvfY-^1!3gF z_x9v6{K-TPyDt~<(f+aA@0VNtYTFY_IWKxm^tveYYi4M@h#nK25QV-R zcj&2mUq*9fA9nC>B{F>DJM#RnD#DFww6~ z^cN^AxlOcQv{7_G6y+|8pWdr~zYxt7g?_d8^`hHETN3?I@yA5dZqWM?QP}Sgzgx6N zv^UWo$n2*|cYmUPPWtnrQPJBX@*YU?&z16{o>YHHHlcq>WnU3p6upO^d1|w$S2SM~ zcIEug$Gl*U)8Eip^$pagdZ35o7wSv=@!SF*QU33X_+BguKk$u24*ge(_*(mA_``N% zMq7o@PSICd|J5!>Ec7)>P zN;D!mD+)XKsf(S~2}2)<3z-OI~3!#0=hfXDSjhoe18Qm_7IKt0c4c!+E=(+St-8+dw|8AmRo9LH`9~2FV zh7B3yiY|&?OY{q5+b?Q}j)~q<-qfVN zCh_f}DWZ-ei1sqk{2;X}?84u?)mpEj zg`$4ZoFCOQT~Xw375|)Q^&0IhqR?+2M?X996XkAA%B@!ZTG2YuPSIKUxjD&SApR!N zV$qUBzhoTyGtv_?K8P{mdvP57=C%5qMRZ!!^grkJp?^a9wnYED__Lz(qEXQW(d(k9 z=e-}(y#D;?$UM>QqR>CQPS+L@`h|)9(Q)+q5`BMSzg>Fro)SGP3je#t(Z3))@hlVF zlK4sE+?wcPKFrtL7|Q6(s?iZ>oB9*l`8lWk!ULj{Di4ihbSCi!e@=8>G--VvZ`2bQ z?Ff0I809X?ck7QkO%p=%MHBV&q21Mcul8YI{*UWR=6EBS=kS{o`D%>?KKFeYez84z z8GG_ZGCC)OE{P&u`1TFC4BwN;VHZeV-(~W{4jX=uJ>)WycUwmMfI4(URJKFM51m8& zkU4ZoRJQv{Wqk4L(@`FtUv!4}wdK&mZ%yRb578aUh+pP~j%D~!Skc95I^b)@uRKKJP|*T%8!snJb~!=nNWxy_=NajPKe*Vg!m;&D1HZ$`r!G^ zMu?wI41e(aIw8a_28KQm9l!nu@ymS>Kc)xq19uR=E(h^bau7ci2k}#I5I^+>@ndJ< zcRVCk61My@7{rfMLHsHd#E&~c{QMHc4_(>9M;9Ivi@$(`Ozx@I6>mJaVjGh-l{GJEIk8MExKkLNL zWkBmP+AM^2W^_OZ9m(jp5E{vdpJRadVFidEPJrUq3y2qqC6= zeZ&JlU;dy)BEH|AFS3XDK6;2RaEJJ+^C%aHj;}U{`0{dyZz_l4YsrZTp05m#auFvw zzAqe#FOw!O>;l6+842T5KT?=#K4zK zLwr*-#CJJE+eJ}t8Tsnrnba@h`-LIC0Jdy*C7CjOGb%JciR+XCe^JC&gU-iEmTndD zpAPYrp`quiGWkwSD8BWQJb1pIGU9-b?=>Vp^nvI|AqTp4;(F@XAsVk-_zeuZ!CiV^ zD_SF3Ct5GsFG}+tmHdS0WTFp0)XDemg&c^E@5u`}5FOtS7czW&-5Yh@{>ys5B|0j4 zL=x4s81C7kzdiCDftD_i=uO)QBmX* z3tt2W@kMVCU(g2erD; c1MXb1fB8Fi`-h%Y{ieE9A!I`KVXp@$#JXsr;sPZaSS zx?9hsL>EMFh(bU8SM`2h)GJyb3O!}{{xSOwGVDcDGU^gSeDfI8n^B(-x+x>RRxIL! z=Uc);hUcrnAif6->d$D2Fzf>PLa!$>x+H|A?aSj0yTE8i$m8L7c3-YO{C}n8qWlL9 z%l*Ze_GPp{82u4;Wh6gRYtx_q-Pea8MCL&Ej;5F=%}w7T`S_$<=6Eh=TLntZO&`-M z=UkDHIl>$%{Pniv`S`n-$e)ya_9J;dC+&OMa`^`(A4+8E&(G6({hV8sTB+Iohmf;&;N}_9pQymdsh($ftie%NjEM!@1Uwk2qtmF`n<0+SB#X+@AK#Ph`e(aUv6cem>UY z<7}+dNHgDOz+{^Pu< ziIPu?c`xzzN0K?O8u`z|d`m9VUz}e>jlVzDD`!}dkY94fUWxa=m*l@J$*jMw>3Mnj zi*vAU*4#Apo0^)wAo)HiCQWK?dfzkb75|fy1&-WVlE1Yi(_aUZ`e;9AQ_Z2`r~RB$ z4ViCU-*|9zu+(%AM^j^Z_n)ql|Q#9msd-EB9U*A-1@ORe}m-ZiOl@pn)svL zoPQN4H8*XNJ!fA{l)PCoXJ13ULo#PrL*5~oGo&Fiew?WdnSY&)ME@}!dlP%wxBNSD z`(Ky+!9-?#&z_OzGyg9oGVOagvHv%c`)B6%j5lXY1xh|YIZJAyr9Wj!5C;$i)~oXB62%vnr=XGF$((hJ`Y%f6++)aZNapNg$n+0q zGedq$`H^UUEB~yLr5OMFlJboIj_=CLGu|UfKKtLgB%kr*ET2Hh$CERACQAO87|!{H z%>K+-zmPji@qC-)uDN->?GK5+FR38s^`boMg)?^{e}~HF=K=kC!8yFh|8C`T7B6Jh z7w7Uq{4{A`TMWR@0Eh<`D)2ur+nr$@+QeYtNQqVB;;R^{EL!XGns!${{zK) zJd+PezEkbJK9l(u^zW6-I8pw2$-kv|uT#5`|8L2Wm_L_F@?V!^_TN(<&*NvlAG#rz z+5htMU_M_s^Ch*CU$64>cD^1t17_LEx9ewbF4JF}35)zm`lsaIo#!&~e~aY&yqL?2 zvwZ3!pEFre9+@*&sm%GSROSp;Dsu)Zl{x#A%AD;<<%dc#XJ#V*I@SMVNj_PUA1TSl zO7h{7e554bQF3M%@4^AaA`|C5G%=6h_ zXWy91tQXD=36z?fSTFhcp=SGcLeAWf8~ZP3Y)q8>17bLD6Egdsow+eZvwaDIoxjQC zhT`Q6PS|t5!FimJKPHB=Jt4DCaLy-W)+1+qLS{WSEy(3z`Tv6A;kwOw{29s3@=w1a z^WC(cm;c?06M2v1wawdK;$Jp-f5I0T9G5(j$^TLE7gT;}R{l>Uf3xJxnS4=lq`2O_ zoV17j&(9MzH_`tO_2=<&{mIWE`ToTDA*q%8ex5T#CQ5!+^=+QC{U!EW;++DMCF;jt zQ&VpL(VG1HJd*E!oE5TS@_22@o_>2i$!9+H-;~=k|CWC`mzjS@6ZyO4@A*Xjxa4OR z=l0y+Tu9_kDu4bD<@rA#dEaMp`BReLN@V)8@u@tY`Mfoena>vznf~Fdl0eDtCped6 zqGaat(xiRNXU-`_KJ%9|N+C0UIj{$!Wr-;^vc`F=6~PBPP;{Qp2kmY9DhN;30j>t}QStcQmZnep4YEYD~BjwiCs z=cRf6$CS@`DuI%(56)DXD4G3%vsEEm;4U=9`doN(o{#_h99DDF8O47@?dz1D_MMe{ zJds~6wU6?g-4ZA@H+@O3oZ~`5{_iFGOOhKa^8B|Yw-4v?V=8qpDbM{)*N^1+CnfhL z@_Q=Zm-wT8&PEB8nwz*^D;&Fn*j1ihSDJx;~eg zFZnqlpD&yt3j1#^`TLHNO!= z$$Jxdwd98q`Nt%mOypZ7pH1YSlKgriZ{5K_Y1|{UbmdtsS zkek{xKNESfWX_~S{xy<0lM?bbOXdtp$kQZq)+FTbkUW&gb0l-lB=T>N%vqC=`y_M5 zB;+5I%vqC=mq_N!NytAenKL6H4@u_CNXVa)%-NBU*GuN?NXV>j&di1Ulgf|8{`GSu zne}mBQl9&r`)}7@NqPD&Kd0mU$C(?cmHhsUvo|J6=J^U|a6)Fk=PXXhJa6GlPRQJ^ zaW*Gp#)C6JA=Cey1qzw|=S)z@^gm~VLVmW?Kg6G(`|Oi#$Pm$N+~bG_q?PssNEDv?hsgEK~vPy0A)6f)O)&KQNveB!K8$XxF@ za}@H6HSOiBP~?9}3}=QyroEi~37PhC1}J3O%UPh1X)kAjLS}q8dlWM5;S5s9w1=}u zAv0c_NeY?q;%rjLw3jnVA=6&YDusNZrhS}2ihSC~S)`DOk26Uj|9_?Ote5waeD04f zZprPrKbrrux%@XOzdVt7Ub!}rn$RJ2FVu_nd{%u#6Ro(zC>odmuOY9+tl z4i{u%~zb4B+C;9V9{$EM%P4ch# zx~8U|N%Ch){-s3zl;pEXJU=XXQPQ3tlN^cr3+8uz{?D%moc(hfe?P|AzmOS!&H#qY ze#TkAkZ&vbr=Oom@_9ej`}4W|e&s)r$Xt)-kDUk9`)9464xP~p|2)4rn8mg?nL+1L&*~E~!-g8DV|@A0kKwFh$iJp|IkOlt&to{d81io@pEHahGhUpb z4EeKSI7=Ba*H6w;hWu|;{`VE{4k=hqCnW!S}f32}U zn%LX@Rw7fLvz1qpd|nElFa;_tuvDDvbl-* zI&DWTGhh1>nfW@D$h2>HQl9yC@XoyaEvj$kUAfHs+mP7PANl!GpI@BWlv>H>7iTw3 zl+66%3}?v9C(eY1%>3Z2X2=^WX1;KCG~~~U;S6cWn`FOIa+eg$k9#Hmj>_-O zS$X#Erj4Z``I{1rueY8J6Pukq{@f!Q4 zow+^zy*QC6A3xGgMDzz|O9e{JO^a&$ol5c{6?eUkNOZDe_@w4gpySe1=mXiEa zCHc;hOnuvu`WTP=oNIH_ua)foZAt#ElFa;^y(^EOet$2K>93;gZjO&pFn}=l+hftReG$gR`_DGhaAU8#3#c zv$Y{JKAf`+`S(iWbE+gWUMG_Jct5c3?z}wr|CbV(@#+56JfHDdn8;j@k0dhl?^5EQ z{qaJQ&;Ho8FZajuE?)2*@} zkgRSu`>iDz`=P}C@oROxlH8ry?~;8gV}Dy>PyGiInfybE{F~p_)bvx5mt_9PKau29 z{#+uHe?F0)S3LilWWGX|_I8YYy7Ei+=l%7c zW&c^p4`%&~|JQ#l&;JeO|G$z|jrk|P^+29)<`QSQ<87I zOnbK__J1h*Uyytzi+6gB|Hb#@@qKSiej~|WSd+gx$zNWRzb?uDTq%D;Nv6LxC-y&A zlfNy=&wrH9$9qr7{@1P`|7I!w!IDfoyOa8k*W}N=H}AhEYw|B9`F~K#|D%%pLP>t9 zBoohp`||o;smVW; zF6W%cpMh`d)SnL)eii&nk(c0a!?wIh_%_}LSbKjGK6Q-m z&Kmt4@L}*g!#Bd;LZ4$&_2-}9yB&TMZosD-`5t&TEa#}S=jZTF*w**&a3}tS9luxM zjj)x^IEMXZY1ZF2!ILw$J$okQ$@sk$p2>WY@2Lr&1>f7p_!{nkhZ4S%Wa@t(eCIKI z-_h_&_>b@b!|UPwvvoX4+UvuIVSj7CBHW4oq|u**Z#kUL_t0R`zYKoqX#IH~;V-}y z$Npb~uW;;tKfKYA?}l@OS^2+&kG(vz$9{M><1h19%0HUG*98N7H_`BXcsFe2XTy)c zcK*tLEZR$d?D&ttd!6=t6yA^A_V=^!ET?^6fp<9hZ-y^mJZ*XR!MmOO55xPN{Qm;K z+3C+Su=g$fy_q@m=S8@m^()EJzXLoR56?4v6d!y)i}lO!s_{M5KZAz#!gfD70e*z?tbG^4o$ME@jQ)Gzg{Sb{RKu&`9n^QT z;T*j49hv^e;PYTx-gUz0FE#qN!v8~g7aD#PegXZ94gU<@xgaa=zu_TxlaU{JEc@#_ zv-U55uQ)ZEPs`x>uwBo2c&{VB6n=s7YSto+}=$L9GitdZ}BKeaI9SzK8AyY}ru z?43gXQ{eL)?uRdOcoQ5tybW$R{8jiehwp-SIQ$5__pEHZehT+5V!oRF;5qm__NRHY zNBVOBUWh;RF2f!h?T}O6Ecgb8kB4t__$>H7hnK)5XT7d~SHV)B><1g*WiZE+;xYJU zM}I4PIefg4e;(fM=zk6Vg`@uv{IH|{Bz(W4|02BTo^1bk4L%dL`|S}_v;em4c{{w` z(LW7d?dYEk4?6nm;MtCT9)8Vf&vy7_r#;sR<1gs?XL|R*k7K_Z>935(_u$UO`}n^( zG(QEu(3`FQ-@tp`mz{@y5IHRVhUB01cI?V|e7z~}MA$t~-VI+)d*wWn@mLHe4ljqF zVSZpHRo(~Sq9fk~vrDJ@%|-B?)MwAD06rdn$>y(#-0}am!gtaCWvc#}-qrA}u(tn9 z?^^iu)3!Y;-~E^M{}1r*F5%xyFzxwg_-y!I!#{$5v{}Eulm738=alvM3O^4Y3)}ta zRrm-P^Q!*MIDzvyN94fwMI9tE-;T_x; z`i#629;SS&zY@N{;Uc`B`ZgK;7{1dP@9ppdE3^4_J^T{&uSZ|@ukXY2i`n@87Vd}n zrP}wZJRF+M@56|Hyy49E`S1aU&w*cc_(J$KhbQ0}t9YMe=J!?bef0lVroKDj-MMUk zeh{Ae5bN3Ke;>Z)!>kXpo_-8Jg1)RjvFFq9Wyq~R@oTtu-M(EO{*B0g56^{c zko2CP+5c#G%i8SxIsrbC{?Ata&`j?Pcqj5pP5yp(Bl#yy`!>LPKFEE^+^>9C?xXTk z=37zt0PRsc!z&9WD^Yr@rHvGbPHXnWp-#3=sr=EjvfR8if{}V_y(uGRq!*E-)HpChvy(a#c%N!ABH*BmHqw$9)N9s{{YXTy!RRTA@e-%DcWPpKMH=Lp4sai@JjS;`77Xx z)1LF;0&Mrw92~MAeaO@G*Wt%&$j$uuIQ&ug{YJh6J`a7{zMJ9GkZ&;Zd*R)@|G(Vu z_uyB!kEZ??{5tK$O;r7T4vwq(`))FSUWR`_`EMBcpW$1OuQGh}e9wEdqT`*Se-eBc z@~V+9gkOTKz1|BywIy4RYv8@`BBNiH{87fAgKt7^$NNV3P4E|u{@3B(-Lr33KP>0l z1Mq@xWd6tZ;9knJ_W2pS2DbKj3EoA1!tg5p>+oJj|8JOhuQ>WA!FyntZ_?jR_!ijO za|QgABi{h;#e_zn2Y$nE&geFyz_+PfHj_&)95i+xwaL+CFv z`2+a)%d++Uarkc7w)gY!tWRe3?}YCozqQvl;42*d0qn-xo`!orPCRUew$Cf@dwD;6 zqN#r-9s3yXkMA%%5AK9*e+PJYj`FR&0{B$&+xf5^Ui!_ff7il?U7C%@t?PNlur$IkbY;pLyr{F7dI#-}*nOn=XT zd#_-=8D0xN_36z1MR?9fad3(ReX%Ed- z^YMr9AFs~%=kP^WW%Kzz;r-~_@%$4!4A)KmW7zn=4cqyDDtzJT+n$yGMNs^`4)`wO zWi&_eIq)^qXWM@syapaLs$CdE)@R5dZfuD5b55hloA9r<>+$B};# zE<5r&;G!d!hFs|IbMR_M|IhFY$A0t9V7wm8>~|J?z|mg=zYJUZ)!;?w+wr{$?xegU zP5*)|8kV|nPMJm7pq;Am14Gb5X839>f;I z>R8P_=koa|n6Ok1tATwUi*l6|;v}-^dLUPoLa zpMKK6y<8y)Yt^`;;N^|)k3N*^eiYQpQ@)fKU;{~s!||$@O!{FpOu}3_+!hp7dLtIAs;`wWv;~>^SSjb}AE_ zp;7kjVhorWs*&I6M+{Z$t8hb2`Yv9REpJFt)2pt zlVrpMK6>4Rj7{ztJ z!NB%{AAb)K6?vx3gcSUo2FCG#qs7~$~N1Un03UacxEBiq?5JS4{LrLAe@|H z*6FUwm=`;`^dwUgu18Jo9<>F?tYt>%S*s_UntVNKqWAQ96jzdB$Ac;}q3p*=F5$mm zw#k)3!lbQ<`ikFMgXH!$j;o2NTvSh(8%!e)wV!2K||)QnunAASh=><531uFHB3yIZ)UBld8KzywVarHjV5*F zII(92^;Dpa#q`8Qy_}n>H4;BB8>54A?zuRPn-i+7n4gx?s?nlQ&(T5gCSWF@?8dMeOtKD*Lc^-XM_ZqIJ;&qLeYk7RN zdRXs?CyB(SfPo=SZ5F?xoW%++x$gi3ay6jJV-JZQgBEe)>IvO+{ruN9LJmug$R6|0B* zdTlEenDMUbhs^o)<9cWtAtqPbyv`!_R>i7oDmU5mzG~{ncTPNQUG4YcuFI5CW}c9M zfMai;dlRS~;@J4dpw?2!T&E^d9VA5q#!t6e=)p zQ%OrnqAbQ?VlboQ8q=j#&DU~KQO}l`8Cv9o!sjuLKbopfT##aMsTC)Q+>~U-fVwy) zIqc-J8E|)4<9?$qQ&Nv?NQA;yFBXpVHYQX`$@?zFr6$ml5mG62^FXs@?lcprZ4)eVdhV(f=*Ie>(&pg^+%Vl z9rcGcZ5Ua;V%_RNzq51wCSo8`NG4uAymHkBzjJXL(a6xsjjNZB=zJ0%VV7Zmv%D=u zw^MP7Zzpc5Lb`m=!kdLovCV>}0`oZu4&kH~mda6I2E`KsXmfgcN_ia!;^)moZY?)pBLec5~JX)4Y3 zbB(0LHWC(87zFv2MrZ;iwqj9Zt#fDQ$S;S$i*T{v~3Ovqv>{JGJE3aj*Vvm#~jq8SGuaqgoAYJ5vfI*bzG*u^hizw+1xoV zCIWT=Iv^sgty(m$gDX0irt;X34Tp0zN1R7>TecN3q!Z3jk(xI7WCK&Dav&4m%}vNa zmY{YqvU>GVl}6$0!I&k;(l)1HmZ(gVp)O>M4YuUr5qQ3YzS8MZpJM-1TZo%Z*i5!N zt`LiRQrJvfNl~~4%3Spz|m;4tH7Mv(RIDbbK#6| ziYf=WiNMLHxBo0Om@Y#CFy#y?jSL!PoG@9S)GiUY!J(0)HZpp?^@~i$);IFFrXL_c zMqDmm&TS65@@fqdRP>WlgoA(}C=}zI@|#+c$Lji2m$;@x<)(|(v4|HpAu6&qo6DsD zt)bd6O`*hS>cfwCxwJ$jq(#X1Rk*vv@#>)!oy2yu`_Bbdg$=W_xX8s7i%Ou>&xdtF z8pivTG7C{bLY6u%nU-ysIHj*%BKTvCstT4)_7EW&@IwqMGu3&dXgMa_6oQvE)__r$ z3rSqbMVqH3v}8>cnFMYDMeLz`3R^&$&{9B_-dsRA7)xwkd2Y$3lA3cw;dm*{qM#*@ zPHWC&{bZl`m+8Bc)KY*eF~w+CG@hy!O5)?&E3&tGXmBMl4)Iyl@^_eHJdiNElcfAZ z!^Ao^tQ%Ojx+!0p7){!IX;LzoE-blNZo0b>v2ekM#5E)&3x_(}t^~Nsdd2ifx&+bq&Ha zZsLfF7F!jF5P6zoblthxbC8VI1GXKjI~pX7dUIi;jac1K#4p5WB}8HFRQ4e0a1Fd@p-=} zZvf;nnaON7wax2(m3N>@)7}CoB?`s~l5pb`CEhnhSV{IKpSd9=>AOcT$$^zlJn0}a zx?068XihZw&4Jq_!)M|olLOw|Kbxen#B}8?m^m$*gh?&I@i5sOquO=rBw@wO)3oQ9 z^ew#^VX$1=zXr}rC0L}lR6f#CP+#gf99|zvkF9Hn3m-p=jK`cvRQGVYWYhn z+0Du_1rJ40Ee$KnMNT#DeD%7{msjBUQ4^}A1eqjm;cMhZ#S}A6$LRS3{s7@1Pu<&O zPU&!Q!QSbM_;TXd*zLlu+X0*fg1clLTAwcF!C+Xt6RXnqm8AcsVy~AHsPF1%F9g)8#qVf3e94ba}~oDbeCrd*=kUl1LbG{FD@QX5qq6` zASr`fjT^y;ia!kcPSPkYx$Kasu4y`Lkr!;b)TAtGZjx)H%tGtsjjP%WNAxQSoqt3v zu|PNN+*&j&11EudEhfXXMpKtH#b{DwN2freofL=GyEL4P39?JD>zFW;%#AVA_;Pe^ zKwlzL8+})k&Pa9Fnk+!w7+3XtB~5co3AVlhA<4B1eQ6qH>6=pk*wh$#8{?W%m=afo zblJX;X@2z~;_Us6e$CRNk&TBGN0kk6t7I3=b42PY?W-R}>VRx1L@dGy*_xWZGtzG@ z%;Bny&P88+D1~5BmO4EptP+iE(osWF z;!wXtLUr<>g4ZVWAqS&JAU90Z`D*v6EaluVC>M1sRJTk8LsbzO>m5M{#cjaUNpr&E zS*og8E#I&jhukB}^1+?7eL|e`#g-II$r->v>&hj6Gx?or#kEZopQc67)lOR{9#lJZ zjoOx#O8;`n>a~YmE-K1w`;vmwmDV)dwk$pCNNZZU(5DHEIohSG=1Kn!Dond~P>pH5 z^LayP-Zy8NblUU|^|aB|Y14Ik+vw`F>AH*C=<2kC=}LMVdt{lE@GlnIG}>l4*ztCd z5jT{C34ucFe{iX7uB#zo=3?5C_L6iyG>g^BI!M+`cF@fFV+5^5D$i6OyKO{f{<)HD zhD=Xw*WqmTq$AWmWAhmO1sywUU6pJuPET#us%)ONZB>i7tPQxunY>?XZX-WG>l`k|;E_zymf-{NFSyhy2mu4-I)(Ng^g@ zEy}fUbi?w24`|`gTAW%zk9Tc2Za$%M`K2y~Ixuc7jRrV##G`lP>Q+r1On#s)nyOCu z?Dqfnk9_o8{w`J-&0_yYShwypx^rP_Jp(>g08kn?2hNZlJO`o;C)IHzmS<#bHuWp1& zvh_+iwyh#~i2uNF!+Q=HA}Hi z=eOw1JLYij){7*E{$u-F040y@?_=e|y%XijQ09-wW+p;;& Ledger transport -//! -//! Provides a set of commands that can be executed to communicate with NEAR App installed on Ledger device: -//! - Read PublicKey from Ledger device by HD Path -//! - Sign a Transaction -use ledger_transport::APDUCommand; -use ledger_transport_hid::{ - hidapi::{HidApi, HidError}, - LedgerHIDError, TransportNativeHID, -}; - -// https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md -const GET_PUBLIC_KEY: u8 = 0x02; // Instruction code to get public key -const SIGN_TX: u8 = 0x04; // Instruction code to sign a transaction on the Ledger -const GET_APP_CONFIGURATION: u8 = 0x06; // Instruction code to get app configuration from the Ledger -const SIGN_TX_HASH: u8 = 0x08; // Instruction code to sign a transaction hash on the Ledger -const CLA: u8 = 0xE0; // Instruction class -const P2_GET_PUB_NO_DISPLAY: u8 = 0x00; -const P2_GET_PUB_DISPLAY: u8 = 0x01; - -/// -// const INS_GET_WALLET_ID: u8 = 0x05; // Get Wallet ID -// const INS_GET_VERSION: u8 = 6; // Instruction code to get app version from the Ledger -// const INS_SIGN_NEP413_MESSAGE: u8 = 7; // Instruction code to sign a nep-413 message with Ledger -// const INS_SIGN_NEP366_DELEGATE_ACTION: u8 = 8; // Instruction code to sign a nep-413 message with Ledger -// const NETWORK_ID: u8 = 'W' as u8; // Instruction parameter 2 -const RETURN_CODE_OK: u16 = 36864; // APDUAnswer.retcode which means success from Ledger -const CHUNK_SIZE: usize = 250; // Chunk size to be sent to Ledger - -/// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with -pub type BorshSerializedUnsignedTransaction = Vec; - -const P1_GET_PUB_DISPLAY: u8 = 0; -const P1_GET_PUB_SILENT: u8 = 1; - -const P1_SIGN_NORMAL: u8 = 0; -const P1_SIGN_NORMAL_LAST_CHUNK: u8 = 0x80; - -/// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with -// pub type NEARLedgerAppVersion = Vec; -/// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with -pub type SignatureBytes = Vec; - -#[derive(Debug)] -pub enum NEARLedgerError { - /// Error occuring on init of hidapid and getting current devices list - HidApiError(HidError), - /// Error occuring on creating a new hid transport, connecting to first ledger device found - LedgerHidError(LedgerHIDError), - /// Error occurred while exchanging with Ledger device - APDUExchangeError(String), - /// Error with transport - LedgerHIDError(LedgerHIDError), -} - -/// Converts BIP32Path into bytes (`Vec`) -fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { - (0..hd_path.depth()) - .map(|index| { - let value = *hd_path.index(index).unwrap(); - value.to_be_bytes() - }) - .flatten() - .collect::>() -} - -#[inline(always)] -fn log_command(index: usize, is_last_chunk: bool, command: &APDUCommand>) { - tracing::info!( - "APDU in{}: {}", - if is_last_chunk { - " (last)".to_string() - } else { - format!(" ({})", index) - }, - hex::encode(&command.serialize()) - ); -} - -/// Get the version of NEAR App installed on Ledger -/// -/// # Returns -/// -/// * A `Result` whose `Ok` value is an `NEARLedgerAppVersion` (just a `Vec` for now, where first value is a major version, second is a minor and the last is the path) -/// and whose `Err` value is a `NEARLedgerError` containing an error which occurred. -// pub fn get_version() -> Result { -// //! Something -// // instantiate the connection to Ledger -// // will return an error if Ledger is not connected -// let transport = get_transport()?; -// let command = APDUCommand { -// cla: CLA, -// ins: INS_GET_VERSION, -// p1: 0, // Instruction parameter 1 (offset) -// p2: 0, -// data: vec![], -// }; - -// tracing::info!("APDU in: {}", hex::encode(&command.serialize())); - -// match transport.exchange(&command) { -// Ok(response) => { -// log::info!( -// "APDU out: {}\nAPDU ret code: {:x}", -// hex::encode(response.apdu_data()), -// response.retcode(), -// ); -// // Ok means we successfully exchanged with the Ledger -// // but doesn't mean our request succeeded -// // we need to check it based on `response.retcode` -// if response.retcode() == RETURN_CODE_OK { -// return Ok(response.data().to_vec()); -// } else { -// let retcode = response.retcode(); - -// let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); -// return Err(NEARLedgerError::APDUExchangeError(error_string)); -// } -// } -// Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), -// }; -// } - -// /// Gets PublicKey from the Ledger on the given `hd_path` -// /// -/// # Inputs -/// * `hd_path` - seed phrase hd path `slip10::BIP32Path` for which PublicKey to look -/// -/// # Returns -/// -/// * A `Result` whose `Ok` value is an `ed25519_dalek::PublicKey` and whose `Err` value is a -/// `NEARLedgerError` containing an error which -/// occurred. -/// -/// # Examples -/// -/// ```no_run -/// use near_ledger::get_public_key; -/// use slip10::BIP32Path; -/// use std::str::FromStr; -/// -/// # fn main() { -/// let hd_path = BIP32Path::from_str("44'/397'/0'/0'/1'").unwrap(); -/// let public_key = get_public_key(hd_path).unwrap(); -/// println!("{:#?}", public_key); -/// # } -/// ``` -/// -/// # Trick -/// -/// To convert the answer into `near_crypto::PublicKey` do: -/// -/// ``` -/// # let public_key_bytes = [10u8; 32]; -/// # let public_key = ed25519_dalek::PublicKey::from_bytes(&public_key_bytes).unwrap(); -/// let public_key = near_crypto::PublicKey::ED25519( -/// near_crypto::ED25519PublicKey::from( -/// public_key.to_bytes(), -/// ) -/// ); -/// ``` -pub fn get_public_key(index: u32) -> Result { - let hd_path = bip_from_index(index); - get_public_key_with_display_flag(hd_path, true) -} - -fn bip_from_index(index: u32) -> slip10::BIP32Path { - let path = format!("m/44'/148'/{index}'"); - println!("path: {:?}", path); - path.parse().unwrap() // this is basically the same thing as slip10::BIP32Path::from_str - - // the device handles this part: https://github.com/AhaLabs/rs-sep5/blob/9d6e3886b4b424dd7b730ec24c865f6fad5d770c/src/seed_phrase.rs#L86 -} - -pub fn get_public_key_with_display_flag( - hd_path: slip10::BIP32Path, - display_and_confirm: bool, -) -> Result { - // instantiate the connection to Ledger - // will return an error if Ledger is not connected - let transport = get_transport()?; - - // hd_path must be converted into bytes to be sent as `data` to the Ledger - let hd_path_bytes = hd_path_to_bytes(&hd_path); - println!("hd_path_bytes: {:?}", hd_path_bytes); - - let command = APDUCommand { - cla: CLA, - ins: GET_PUBLIC_KEY, - p1: 0x00, // Instruction parameter 1 (offset) - p2: P2_GET_PUB_DISPLAY, - data: hd_path_bytes, - }; - tracing::info!("APDU in: {}", hex::encode(&command.serialize())); - - match transport.exchange(&command) { - Ok(response) => { - tracing::info!( - "APDU out: {}\nAPDU ret code: {:x}", - hex::encode(response.apdu_data()), - response.retcode(), - ); - // Ok means we successfully exchanged with the Ledger - // but doesn't mean our request succeeded - // we need to check it based on `response.retcode` - if response.retcode() == RETURN_CODE_OK { - return Ok( - stellar_strkey::ed25519::PublicKey::from_payload(&response.data()).unwrap(), - ); - } else { - let retcode = response.retcode(); - - let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); - return Err(NEARLedgerError::APDUExchangeError(error_string)); - } - } - Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), - }; -} - -// pub fn get_wallet_id( -// hd_path: slip10::BIP32Path, -// ) -> Result { -// // instantiate the connection to Ledger -// // will return an error if Ledger is not connected -// let transport = get_transport()?; - -// // hd_path must be converted into bytes to be sent as `data` to the Ledger -// let hd_path_bytes = hd_path_to_bytes(&hd_path); - -// let command = APDUCommand { -// cla: CLA, -// ins: INS_GET_WALLET_ID, -// p1: 0, // Instruction parameter 1 (offset) -// p2: NETWORK_ID, -// data: hd_path_bytes, -// }; -// log::info!("APDU in: {}", hex::encode(&command.serialize())); - -// match transport.exchange(&command) { -// Ok(response) => { -// log::info!( -// "APDU out: {}\nAPDU ret code: {:x}", -// hex::encode(response.apdu_data()), -// response.retcode(), -// ); -// // Ok means we successfully exchanged with the Ledger -// // but doesn't mean our request succeeded -// // we need to check it based on `response.retcode` -// if response.retcode() == RETURN_CODE_OK { -// return Ok(ed25519_dalek::PublicKey::from_bytes(&response.data()).unwrap()); -// } else { -// let retcode = response.retcode(); - -// let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); -// return Err(NEARLedgerError::APDUExchangeError(error_string)); -// } -// } -// Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), -// }; -// } - -fn get_transport() -> Result { - // instantiate the connection to Ledger - // will return an error if Ledger is not connected - let hidapi = HidApi::new().map_err(NEARLedgerError::HidApiError)?; - TransportNativeHID::new(&hidapi).map_err(NEARLedgerError::LedgerHidError) -} - -// /// Sign the transaction. Transaction should be [borsh serialized](https://github.com/near/borsh-rs) `Vec` -// /// -// /// # Inputs -// /// * `unsigned_transaction_borsh_serializer` - unsigned transaction `near_primitives::transaction::Transaction` -// /// which is serialized with `BorshSerializer` and basically is just `Vec` -// /// * `seed_phrase_hd_path` - seed phrase hd path `slip10::BIP32Path` with which to sign -// /// -// /// # Returns -// /// -// /// * A `Result` whose `Ok` value is an `Signature` (bytes) and whose `Err` value is a -// /// `NEARLedgerError` containing an error which occurred. -// /// -// /// # Examples -// /// -// /// ```no_run -// /// use near_ledger::sign_transaction; -// /// use near_primitives::{borsh, borsh::BorshSerialize}; -// /// use slip10::BIP32Path; -// /// use std::str::FromStr; -// /// -// /// # fn main() { -// /// # let near_unsigned_transaction = [10; 250]; -// /// let hd_path = BIP32Path::from_str("44'/397'/0'/0'/1'").unwrap(); -// /// let borsh_transaction = borsh::to_vec(&near_unsigned_transaction).unwrap(); -// /// let signature = sign_transaction(borsh_transaction, hd_path).unwrap(); -// /// println!("{:#?}", signature); -// /// # } -// /// ``` -// /// -// /// # Trick -// /// -// /// To convert the answer into `near_crypto::Signature` do: -// /// -// /// ``` -// /// # let signature = [10; 64].to_vec(); -// /// let signature = near_crypto::Signature::from_parts(near_crypto::KeyType::ED25519, &signature) -// /// .expect("Signature is not expected to fail on deserialization"); -// /// ``` -// pub fn sign_transaction( -// unsigned_tx: BorshSerializedUnsignedTransaction, -// seed_phrase_hd_path: slip10::BIP32Path, -// ) -> Result { -// let transport = get_transport()?; -// // seed_phrase_hd_path must be converted into bytes to be sent as `data` to the Ledger -// let hd_path_bytes = hd_path_to_bytes(&seed_phrase_hd_path); - -// let mut data: Vec = vec![]; -// data.extend(hd_path_bytes); -// data.extend(&unsigned_tx); - -// let chunks = data.chunks(CHUNK_SIZE); -// let chunks_count = chunks.len(); - -// for (i, chunk) in chunks.enumerate() { -// let is_last_chunk = chunks_count == i + 1; -// let command = APDUCommand { -// cla: CLA, -// ins: INS_SIGN_TRANSACTION, -// p1: if is_last_chunk { -// P1_SIGN_NORMAL_LAST_CHUNK -// } else { -// P1_SIGN_NORMAL -// }, // Instruction parameter 1 (offset) -// p2: NETWORK_ID, -// data: chunk.to_vec(), -// }; -// log_command(i, is_last_chunk, &command); -// match transport.exchange(&command) { -// Ok(response) => { -// log::info!( -// "APDU out: {}\nAPDU ret code: {:x}", -// hex::encode(response.apdu_data()), -// response.retcode(), -// ); -// // Ok means we successfully exchanged with the Ledger -// // but doesn't mean our request succeeded -// // we need to check it based on `response.retcode` -// if response.retcode() == RETURN_CODE_OK { -// if is_last_chunk { -// return Ok(response.data().to_vec()); -// } -// } else { -// let retcode = response.retcode(); - -// let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); -// return Err(NEARLedgerError::APDUExchangeError(error_string)); -// } -// } -// Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), -// }; -// } -// Err(NEARLedgerError::APDUExchangeError( -// "Unable to process request".to_owned(), -// )) -// } diff --git a/cmd/crates/stellar-ledger/src/types.rs b/cmd/crates/stellar-ledger/src/types.rs deleted file mode 100644 index 8e3b2177e..000000000 --- a/cmd/crates/stellar-ledger/src/types.rs +++ /dev/null @@ -1,245 +0,0 @@ -use serde::Serialize; -use stellar_xdr::curr::{ - ScSpecEntry, ScSpecFunctionInputV0, ScSpecTypeDef, ScSpecUdtEnumCaseV0, - ScSpecUdtErrorEnumCaseV0, ScSpecUdtStructFieldV0, ScSpecUdtUnionCaseV0, -}; - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct StructField { - pub doc: String, - pub name: String, - pub value: Type, -} - -impl From<&ScSpecUdtStructFieldV0> for StructField { - fn from(f: &ScSpecUdtStructFieldV0) -> Self { - StructField { - doc: f.doc.to_utf8_string_lossy(), - name: f.name.to_utf8_string_lossy(), - value: (&f.type_).into(), - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct FunctionInput { - pub doc: String, - pub name: String, - pub value: Type, -} - -impl From<&ScSpecFunctionInputV0> for FunctionInput { - fn from(f: &ScSpecFunctionInputV0) -> Self { - FunctionInput { - doc: f.doc.to_utf8_string_lossy(), - name: f.name.to_utf8_string_lossy(), - value: (&f.type_).into(), - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct UnionCase { - pub doc: String, - pub name: String, - pub values: Vec, -} - -impl From<&ScSpecUdtUnionCaseV0> for UnionCase { - fn from(c: &ScSpecUdtUnionCaseV0) -> Self { - let (doc, name, values) = match c { - ScSpecUdtUnionCaseV0::VoidV0(v) => ( - v.doc.to_utf8_string_lossy(), - v.name.to_utf8_string_lossy(), - vec![], - ), - ScSpecUdtUnionCaseV0::TupleV0(t) => ( - t.doc.to_utf8_string_lossy(), - t.name.to_utf8_string_lossy(), - t.type_.iter().map(Type::from).collect(), - ), - }; - UnionCase { doc, name, values } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct EnumCase { - pub doc: String, - pub name: String, - pub value: u32, -} - -impl From<&ScSpecUdtEnumCaseV0> for EnumCase { - fn from(c: &ScSpecUdtEnumCaseV0) -> Self { - EnumCase { - doc: c.doc.to_utf8_string_lossy(), - name: c.name.to_utf8_string_lossy(), - value: c.value, - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ErrorEnumCase { - pub doc: String, - pub name: String, - pub value: u32, -} - -impl From<&ScSpecUdtErrorEnumCaseV0> for EnumCase { - fn from(c: &ScSpecUdtErrorEnumCaseV0) -> Self { - EnumCase { - doc: c.doc.to_utf8_string_lossy(), - name: c.name.to_utf8_string_lossy(), - value: c.value, - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(tag = "type")] -#[serde(rename_all = "camelCase")] -pub enum Type { - Void, - Val, - U64, - I64, - U32, - I32, - U128, - I128, - U256, - I256, - Bool, - Symbol, - Error, - Bytes, - String, - Address, - Timepoint, - Duration, - Map { key: Box, value: Box }, - Option { value: Box }, - Result { value: Box, error: Box }, - Vec { element: Box }, - BytesN { n: u32 }, - Tuple { elements: Vec }, - Custom { name: String }, -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(tag = "type")] -#[serde(rename_all = "camelCase")] -pub enum Entry { - Function { - doc: String, - name: String, - inputs: Vec, - outputs: Vec, - }, - Struct { - doc: String, - name: String, - fields: Vec, - }, - Union { - doc: String, - name: String, - cases: Vec, - }, - Enum { - doc: String, - name: String, - cases: Vec, - }, - ErrorEnum { - doc: String, - name: String, - cases: Vec, - }, -} - -impl From<&ScSpecTypeDef> for Type { - fn from(spec: &ScSpecTypeDef) -> Self { - match spec { - ScSpecTypeDef::Map(map) => Type::Map { - key: Box::new(Type::from(map.key_type.as_ref())), - value: Box::new(Type::from(map.value_type.as_ref())), - }, - ScSpecTypeDef::Option(opt) => Type::Option { - value: Box::new(Type::from(opt.value_type.as_ref())), - }, - ScSpecTypeDef::Result(res) => Type::Result { - value: Box::new(Type::from(res.ok_type.as_ref())), - error: Box::new(Type::from(res.error_type.as_ref())), - }, - ScSpecTypeDef::Tuple(tuple) => Type::Tuple { - elements: tuple.value_types.iter().map(Type::from).collect(), - }, - ScSpecTypeDef::Vec(vec) => Type::Vec { - element: Box::new(Type::from(vec.element_type.as_ref())), - }, - ScSpecTypeDef::Udt(udt) => Type::Custom { - name: udt.name.to_utf8_string_lossy(), - }, - ScSpecTypeDef::BytesN(b) => Type::BytesN { n: b.n }, - ScSpecTypeDef::Val => Type::Val, - ScSpecTypeDef::U64 => Type::U64, - ScSpecTypeDef::I64 => Type::I64, - ScSpecTypeDef::U32 => Type::U32, - ScSpecTypeDef::I32 => Type::I32, - ScSpecTypeDef::U128 => Type::U128, - ScSpecTypeDef::I128 => Type::I128, - ScSpecTypeDef::U256 => Type::U256, - ScSpecTypeDef::I256 => Type::I256, - ScSpecTypeDef::Bool => Type::Bool, - ScSpecTypeDef::Symbol => Type::Symbol, - ScSpecTypeDef::Error => Type::Error, - ScSpecTypeDef::Bytes => Type::Bytes, - ScSpecTypeDef::String => Type::String, - ScSpecTypeDef::Address => Type::Address, - ScSpecTypeDef::Void => Type::Void, - ScSpecTypeDef::Timepoint => Type::Timepoint, - ScSpecTypeDef::Duration => Type::Duration, - } - } -} - -impl From<&ScSpecEntry> for Entry { - fn from(spec: &ScSpecEntry) -> Self { - match spec { - ScSpecEntry::FunctionV0(f) => Entry::Function { - doc: f.doc.to_utf8_string_lossy(), - name: f.name.to_utf8_string_lossy(), - inputs: f.inputs.iter().map(FunctionInput::from).collect(), - outputs: f.outputs.iter().map(Type::from).collect(), - }, - ScSpecEntry::UdtStructV0(s) => Entry::Struct { - doc: s.doc.to_utf8_string_lossy(), - name: s.name.to_utf8_string_lossy(), - fields: s.fields.iter().map(StructField::from).collect(), - }, - ScSpecEntry::UdtUnionV0(u) => Entry::Union { - doc: u.doc.to_utf8_string_lossy(), - name: u.name.to_utf8_string_lossy(), - cases: u.cases.iter().map(UnionCase::from).collect(), - }, - ScSpecEntry::UdtEnumV0(e) => Entry::Enum { - doc: e.doc.to_utf8_string_lossy(), - name: e.name.to_utf8_string_lossy(), - cases: e.cases.iter().map(EnumCase::from).collect(), - }, - ScSpecEntry::UdtErrorEnumV0(e) => Entry::Enum { - doc: e.doc.to_utf8_string_lossy(), - name: e.name.to_utf8_string_lossy(), - cases: e.cases.iter().map(EnumCase::from).collect(), - }, - } - } -} From 1ea114f299e7adfd0852e271fda78b4eb45fcae0 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:08:07 -0400 Subject: [PATCH 28/72] Update the expected sign_transaction response hex --- cmd/crates/stellar-ledger/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index bdc3f36cd..b7e1c7478 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -193,8 +193,7 @@ mod test { match ledger.sign_transaction(path, tx).await { Ok(response) => { - stop_emulator(&mut emulator).await; - assert_eq!( hex::encode(response), "ab5de404a9a28ef6cee7387610d7a4c876f5a4051647eeaf077b909eb77ab309ca6dad4ec127da3537d2663204e4a8f4d0e2163d63af9e9d33471069e1d5c90b"); + assert_eq!( hex::encode(response), "77b9f6e6924e5db2e35c5ecd7dd95248eadd51ea35d61e467cf6ba0df28ca7f38674e3fea8c8a3e2a0fa45f49d4381f9cf24bcc0ff8b708c9337beb854e98e0d"); } Err(e) => { stop_emulator(&mut emulator).await; From e2b6e0f107e24e294c1137fe175650d15071dae5 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 11 Apr 2024 14:21:18 -0400 Subject: [PATCH 29/72] Fix tests --- cmd/crates/stellar-ledger/src/emulator.rs | 6 ----- cmd/crates/stellar-ledger/src/lib.rs | 32 +++-------------------- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/emulator.rs b/cmd/crates/stellar-ledger/src/emulator.rs index 482860712..f864720e2 100644 --- a/cmd/crates/stellar-ledger/src/emulator.rs +++ b/cmd/crates/stellar-ledger/src/emulator.rs @@ -50,9 +50,3 @@ impl Emulator { Ok(()) } } - -// ------------------------------------------------------------- - -// next steps: -// have this docker connection start the speculos emulator -// see if i can use that emulator in tests like they do with zemu diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index b7e1c7478..b78b0c758 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -38,16 +38,11 @@ mod test { HostFunction, InvokeContractArgs, Memo, MuxedAccount, PaymentOp, Preconditions, SequenceNumber, StringM, TransactionExt, TransactionV0, TransactionV0Ext, VecM, }; - // should MuxedAccount be stellar_strkey::ed25519::MuxedAccount; instead? + use tokio::time::sleep; use crate::app::LedgerError::APDUExchangeError; - // TODO: - // - create setup and cleanup functions to start and then stop the emulator at the beginning and end of the test run - // - test each of the device models - // - handle the sleep differently - #[ignore] #[tokio::test] #[serial] @@ -139,7 +134,7 @@ mod test { let destination_account_str = "GCKUD4BHIYSAYHU7HBB5FDSW6CSYH3GSOUBPWD2KE7KNBERP4BSKEJDV"; let destination_account_bytes = - match stellar_strkey::Strkey::from_string(source_account_str) { + match stellar_strkey::Strkey::from_string(destination_account_str) { Ok(key) => match key { stellar_strkey::Strkey::PublicKeyEd25519(p) => p.0, _ => { @@ -153,25 +148,6 @@ mod test { } }; - // let tx_v0 = TransactionV0 { - // source_account_ed25519: Uint256(source_account_bytes), - // fee: 100, - // seq_num: SequenceNumber(1), - // time_bounds: None, - // memo: Memo::Text("Stellar".as_bytes().try_into().unwrap()), - // operations: vec![Operation { - // source_account: Some(MuxedAccount::Ed25519(Uint256(source_account_bytes))), - // body: OperationBody::Payment(PaymentOp { - // destination: MuxedAccount::Ed25519(Uint256(destination_account_bytes)), - // asset: xdr::Asset::Native, - // amount: 100, - // }), - // }] - // .try_into() - // .unwrap(), - // ext: TransactionV0Ext::V0, - // }; - let tx = Transaction { source_account: MuxedAccount::Ed25519(Uint256(source_account_bytes)), // was struggling to create a real account in this way with the G... address fee: 100, @@ -221,7 +197,7 @@ mod test { let result = ledger.sign_transaction_hash(path, test_hash.into()).await; if let Err(APDUExchangeError(msg)) = result { assert_eq!(msg, "Ledger APDU retcode: 0x6C66"); - // this error code is SW_TX_HASH_SIGNING_MODE_NOT_ENABLED from https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md + // this error code is SW_TX_HASH_SIGNING_MODE_NOT_ENABLED https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md } else { stop_emulator(&mut emulator).await; panic!("Unexpected result: {:?}", result); @@ -276,7 +252,7 @@ mod test { assert!(start_result.is_ok()); //TODO: handle this in a different way - // perhaps i can check the endpoint to see if its up before trying to get the public key + // perhaps i can check the endpoint to see if its up before trying to send anything sleep(Duration::from_secs(2)).await; } From 692e659081627dce5d065c6047a8e77dd1df6952 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 11 Apr 2024 14:22:51 -0400 Subject: [PATCH 30/72] Remove unused imports --- cmd/crates/stellar-ledger/src/app.rs | 17 ++++------------- cmd/crates/stellar-ledger/src/lib.rs | 26 ++++++-------------------- 2 files changed, 10 insertions(+), 33 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs index 2d8a0beb9..8065ea0c8 100644 --- a/cmd/crates/stellar-ledger/src/app.rs +++ b/cmd/crates/stellar-ledger/src/app.rs @@ -1,17 +1,8 @@ -use byteorder::{BigEndian, WriteBytesExt}; -use reqwest::Response; use sha2::{Digest, Sha256}; -use std::{ - io::{Cursor, Write}, - str::FromStr, - thread::sleep, - time::Duration, - vec, -}; +use std::vec; use stellar_xdr::curr::{ - self, Hash, Limited, Limits, ReadXdr, TransactionEnvelope, TransactionSignaturePayload, - TransactionSignaturePayloadTaggedTransaction, TransactionV0, TransactionV0Envelope, - TransactionV1Envelope, WriteXdr, + Hash, Limits, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, + WriteXdr, }; use ledger_transport::{APDUCommand, Exchange}; @@ -22,7 +13,7 @@ use ledger_transport_hid::{ use soroban_env_host::xdr::Transaction; -use crate::transport_zemu_http::{LedgerZemuError, TransportZemuHttp}; +use crate::transport_zemu_http::TransportZemuHttp; const APDU_MAX_SIZE: u8 = 150; // from https://github.com/LedgerHQ/ledger-live/blob/36cfbf3fa3300fd99bcee2ab72e1fd8f280e6280/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L181 diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index b78b0c758..6cededbb1 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -1,17 +1,5 @@ // https://github.com/zondax/ledger-rs -use ed25519_dalek::Signer; -use sha2::{Digest, Sha256}; - -use soroban_env_host::xdr::{ - self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, - InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, - ScVal, Signature, SignatureHint, SorobanAddressCredentials, SorobanAuthorizationEntry, - SorobanAuthorizedFunction, SorobanCredentials, Transaction, TransactionEnvelope, - TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, - TransactionV1Envelope, Uint256, WriteXdr, -}; - pub mod app; mod emulator; @@ -20,23 +8,21 @@ mod docker; mod transport_zemu_http; -use crate::app::get_zemu_transport; -use crate::{app::new_get_transport, emulator::Emulator}; -enum Error {} - #[cfg(test)] mod test { + use soroban_env_host::xdr::{self, Operation, OperationBody, Transaction, Uint256}; + + use crate::app::get_zemu_transport; + use crate::{app::new_get_transport, emulator::Emulator}; - use std::{collections::HashMap, path::PathBuf, str::FromStr, thread, time::Duration}; + use std::{collections::HashMap, str::FromStr, time::Duration}; use super::*; - use once_cell::sync::Lazy; use serial_test::serial; use stellar_xdr::curr::{ - HostFunction, InvokeContractArgs, Memo, MuxedAccount, PaymentOp, Preconditions, - SequenceNumber, StringM, TransactionExt, TransactionV0, TransactionV0Ext, VecM, + Memo, MuxedAccount, PaymentOp, Preconditions, SequenceNumber, TransactionExt, }; use tokio::time::sleep; From 84d2b07e40672f0444130d858f70b9f5f29f143a Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 11 Apr 2024 14:39:45 -0400 Subject: [PATCH 31/72] Move app code into lib.rs --- cmd/crates/stellar-ledger/src/app.rs | 294 ------------------------- cmd/crates/stellar-ledger/src/lib.rs | 318 +++++++++++++++++++++++++-- 2 files changed, 301 insertions(+), 311 deletions(-) delete mode 100644 cmd/crates/stellar-ledger/src/app.rs diff --git a/cmd/crates/stellar-ledger/src/app.rs b/cmd/crates/stellar-ledger/src/app.rs deleted file mode 100644 index 8065ea0c8..000000000 --- a/cmd/crates/stellar-ledger/src/app.rs +++ /dev/null @@ -1,294 +0,0 @@ -use sha2::{Digest, Sha256}; -use std::vec; -use stellar_xdr::curr::{ - Hash, Limits, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, - WriteXdr, -}; - -use ledger_transport::{APDUCommand, Exchange}; -use ledger_transport_hid::{ - hidapi::{HidApi, HidError}, - LedgerHIDError, TransportNativeHID, -}; - -use soroban_env_host::xdr::Transaction; - -use crate::transport_zemu_http::TransportZemuHttp; - -const APDU_MAX_SIZE: u8 = 150; // from https://github.com/LedgerHQ/ledger-live/blob/36cfbf3fa3300fd99bcee2ab72e1fd8f280e6280/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L181 - -// these came from https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md -const CLA: u8 = 0xE0; // Instruction class - -const GET_PUBLIC_KEY: u8 = 0x02; // Instruction code to get public key -const P1_GET_PUBLIC_KEY: u8 = 0x00; -const P2_GET_PUBLIC_KEY_NO_DISPLAY: u8 = 0x00; -const P2_GET_PUBLIC_KEY_DISPLAY: u8 = 0x01; - -const SIGN_TX: u8 = 0x04; -const P1_SIGN_TX_FIRST: u8 = 0x00; // 0 -const P1_SIGN_TX_NOT_FIRST: u8 = 0x80; // 128 -const P2_SIGN_TX_LAST: u8 = 0x00; // 0 -const P2_SIGN_TX_MORE: u8 = 0x80; // 128 - -const GET_APP_CONFIGURATION: u8 = 0x06; -const P1_GET_APP_CONFIGURATION: u8 = 0x00; -const P2_GET_APP_CONFIGURATION: u8 = 0x00; - -const SIGN_TX_HASH: u8 = 0x08; -const P1_SIGN_TX_HASH: u8 = 0x00; -const P2_SIGN_TX_HASH: u8 = 0x00; - -const RETURN_CODE_OK: u16 = 36864; // APDUAnswer.retcode which means success from Ledger - -#[derive(thiserror::Error, Debug)] -pub enum LedgerError { - #[error("Error occurred while initializing HIDAPI: {0}")] - HidApiError(#[from] HidError), - - #[error("Error occurred while initializing Ledger HID transport: {0}")] - LedgerHidError(#[from] LedgerHIDError), - - #[error("Error with ADPU exchange with Ledger device: {0}")] //TODO update this message - APDUExchangeError(String), - - #[error("Error occurred while exchanging with Ledger device: {0}")] - LedgerConnectionError(String), -} - -pub struct Ledger { - transport: T, -} - -impl Ledger -where - T: Exchange, -{ - pub fn new(transport: T) -> Ledger { - Ledger { - transport: transport, - } - } - - pub async fn get_public_key( - &self, - index: u32, - ) -> Result { - let hd_path = bip_path_from_index(index); - Self::get_public_key_with_display_flag(self, hd_path, false).await - } - - pub async fn get_app_configuration(&self) -> Result, LedgerError> { - let command = APDUCommand { - cla: CLA, - ins: GET_APP_CONFIGURATION, - p1: P1_GET_APP_CONFIGURATION, - p2: P2_GET_APP_CONFIGURATION, - data: vec![], - }; - self.send_command_to_ledger(command).await - } - - // based on impl from https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166 - pub async fn sign_transaction_hash( - &self, - hd_path: slip10::BIP32Path, - transaction_hash: Vec, - ) -> Result, LedgerError> { - // convert the hd_path into bytes to be sent as `data` to the Ledger - // the first element of the data should be the number of elements in the path - - let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); - let hd_path_elements_count = hd_path.depth(); - hd_path_to_bytes.insert(0, hd_path_elements_count); - - let mut data = hd_path_to_bytes; - data.append(&mut transaction_hash.clone()); - - let command = APDUCommand { - cla: CLA, - ins: SIGN_TX_HASH, - p1: P1_SIGN_TX_HASH, - p2: P2_SIGN_TX_HASH, - data: data, - }; - - self.send_command_to_ledger(command).await - } - - pub async fn sign_transaction( - &self, - hd_path: slip10::BIP32Path, - transaction: Transaction, - ) -> Result, LedgerError> { - // TODO: in js-stellar-base signatureBase fn, they update the tx if the envelope type is V0 - do i need to take care of that here? - // i think not because in generated.rs the TransactionSignaturePayload has a note that says "Backwards Compatibility: Use ENVELOPE_TYPE_TX to sign ENVELOPE_TYPE_TX_V0" - - let tagged_transaction = - TransactionSignaturePayloadTaggedTransaction::Tx(transaction.clone()); - - // TODO: do not hardcode this passphrase - let testnet_passphrase = "Test SDF Network ; September 2015"; - let network_hash = Hash(Sha256::digest(testnet_passphrase.as_bytes()).into()); - - let signature_payload = TransactionSignaturePayload { - network_id: network_hash, - tagged_transaction: tagged_transaction, - }; - - let mut signature_payload_as_bytes = signature_payload.to_xdr(Limits::none()).unwrap(); - - let mut data: Vec = Vec::new(); - - let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); - let hd_path_elements_count = hd_path.depth(); - - data.insert(0, hd_path_elements_count); - data.append(&mut hd_path_to_bytes); - data.append(&mut signature_payload_as_bytes); - - let buffer_size = 1 + hd_path_elements_count * 4; - let chunk_size = APDU_MAX_SIZE - buffer_size; - - let chunks = data.chunks(chunk_size as usize); - let chunks_count = chunks.len(); - - let mut result = Vec::new(); - println!("chunks_count: {:?}", chunks_count); - - // notes: - // the first chunk has the hd_path_elements_count and the hd_path at the beginning, before the tx [3, 128...122...47] - // the second chunk has just the end of the tx [224, 100... 0, 0, 0, 0] - - for (i, chunk) in chunks.enumerate() { - let is_first_chunk = i == 0; - let is_last_chunk = chunks_count == i + 1; - - let command = APDUCommand { - cla: CLA, - ins: SIGN_TX, - p1: if is_first_chunk { - P1_SIGN_TX_FIRST - } else { - P1_SIGN_TX_NOT_FIRST - }, - p2: if is_last_chunk { - P2_SIGN_TX_LAST - } else { - P2_SIGN_TX_MORE - }, - data: chunk.to_vec(), - }; - - match self.send_command_to_ledger(command).await { - Ok(mut r) => { - result.append(&mut r); - } - Err(e) => { - return Err(e); - } - } - } - - Ok(result) - } - - /// The display_and_confirm bool determines if the Ledger will display the public key on its screen and requires user approval to share - async fn get_public_key_with_display_flag( - &self, - hd_path: slip10::BIP32Path, - display_and_confirm: bool, - ) -> Result { - // convert the hd_path into bytes to be sent as `data` to the Ledger - // the first element of the data should be the number of elements in the path - let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); - let hd_path_elements_count = hd_path.depth(); - hd_path_to_bytes.insert(0, hd_path_elements_count); - - println!("data: {:?}", hd_path_to_bytes); - - let p2 = if display_and_confirm { - P2_GET_PUBLIC_KEY_DISPLAY - } else { - P2_GET_PUBLIC_KEY_NO_DISPLAY - }; - - // more information about how to build this command can be found at https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md - let command = APDUCommand { - cla: CLA, - ins: GET_PUBLIC_KEY, - p1: P1_GET_PUBLIC_KEY, - p2: p2, - data: hd_path_to_bytes, - }; - - tracing::info!("APDU in: {}", hex::encode(&command.serialize())); - - match self.send_command_to_ledger(command).await { - Ok(value) => { - return Ok(stellar_strkey::ed25519::PublicKey::from_payload(&value).unwrap()); - } - Err(err) => { - return Err(err); - } - } - } - - async fn send_command_to_ledger( - &self, - command: APDUCommand>, - ) -> Result, LedgerError> { - match self.transport.exchange(&command).await { - Ok(response) => { - tracing::info!( - "APDU out: {}\nAPDU ret code: {:x}", - hex::encode(response.apdu_data()), - response.retcode(), - ); - // Ok means we successfully connected with the Ledger but it doesn't mean our request succeeded. We still need to check the response.retcode - println!("RETCODE: {:?}", response.retcode()); - println!("response: {:?}", response.data()); - if response.retcode() == RETURN_CODE_OK { - return Ok(response.data().to_vec()); - } else { - let retcode = response.retcode(); - - let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); - return Err(LedgerError::APDUExchangeError(error_string)); - } - } - Err(err) => { - //FIX ME!!!! - return Err(LedgerError::LedgerConnectionError("test".to_string())); - } - }; - } -} - -fn bip_path_from_index(index: u32) -> slip10::BIP32Path { - let path = format!("m/44'/148'/{index}'"); - path.parse().unwrap() // this is basically the same thing as slip10::BIP32Path::from_str - - // the device handles this part: https://github.com/AhaLabs/rs-sep5/blob/9d6e3886b4b424dd7b730ec24c865f6fad5d770c/src/seed_phrase.rs#L86 -} - -fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { - println!("hd_path.depth: {:?}", hd_path.depth()); - (0..hd_path.depth()) - .map(|index| { - let value = *hd_path.index(index).unwrap(); - value.to_be_bytes() - }) - .flatten() - .collect::>() -} - -pub fn new_get_transport() -> Result { - // instantiate the connection to Ledger, this will return an error if Ledger is not connected - let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; - TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) -} - -pub fn get_zemu_transport(host: &str, port: u16) -> Result { - Ok(TransportZemuHttp::new(host, port)) -} diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 6cededbb1..53487f714 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -1,19 +1,309 @@ // https://github.com/zondax/ledger-rs -pub mod app; - mod emulator; mod docker; mod transport_zemu_http; +use sha2::{Digest, Sha256}; +use std::vec; +use stellar_xdr::curr::{ + Hash, Limits, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, + WriteXdr, +}; + +use ledger_transport::{APDUCommand, Exchange}; +use ledger_transport_hid::{ + hidapi::{HidApi, HidError}, + LedgerHIDError, TransportNativeHID, +}; + +use soroban_env_host::xdr::Transaction; + +use crate::transport_zemu_http::TransportZemuHttp; + +// this is from https://github.com/LedgerHQ/ledger-live/blob/36cfbf3fa3300fd99bcee2ab72e1fd8f280e6280/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L181 +const APDU_MAX_SIZE: u8 = 150; + +// these constant values are from https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md +const CLA: u8 = 0xE0; + +const GET_PUBLIC_KEY: u8 = 0x02; +const P1_GET_PUBLIC_KEY: u8 = 0x00; +const P2_GET_PUBLIC_KEY_NO_DISPLAY: u8 = 0x00; +const P2_GET_PUBLIC_KEY_DISPLAY: u8 = 0x01; + +const SIGN_TX: u8 = 0x04; +const P1_SIGN_TX_FIRST: u8 = 0x00; +const P1_SIGN_TX_NOT_FIRST: u8 = 0x80; +const P2_SIGN_TX_LAST: u8 = 0x00; +const P2_SIGN_TX_MORE: u8 = 0x80; + +const GET_APP_CONFIGURATION: u8 = 0x06; +const P1_GET_APP_CONFIGURATION: u8 = 0x00; +const P2_GET_APP_CONFIGURATION: u8 = 0x00; + +const SIGN_TX_HASH: u8 = 0x08; +const P1_SIGN_TX_HASH: u8 = 0x00; +const P2_SIGN_TX_HASH: u8 = 0x00; + +const RETURN_CODE_OK: u16 = 36864; // APDUAnswer.retcode which means success from Ledger + +#[derive(thiserror::Error, Debug)] +pub enum LedgerError { + #[error("Error occurred while initializing HIDAPI: {0}")] + HidApiError(#[from] HidError), + + #[error("Error occurred while initializing Ledger HID transport: {0}")] + LedgerHidError(#[from] LedgerHIDError), + + #[error("Error with ADPU exchange with Ledger device: {0}")] //TODO update this message + APDUExchangeError(String), + + #[error("Error occurred while exchanging with Ledger device: {0}")] + LedgerConnectionError(String), +} + +pub struct Ledger { + transport: T, +} + +impl Ledger +where + T: Exchange, +{ + pub fn new(transport: T) -> Ledger { + Ledger { + transport: transport, + } + } + + pub async fn get_public_key( + &self, + index: u32, + ) -> Result { + let hd_path = bip_path_from_index(index); + Self::get_public_key_with_display_flag(self, hd_path, false).await + } + + pub async fn get_app_configuration(&self) -> Result, LedgerError> { + let command = APDUCommand { + cla: CLA, + ins: GET_APP_CONFIGURATION, + p1: P1_GET_APP_CONFIGURATION, + p2: P2_GET_APP_CONFIGURATION, + data: vec![], + }; + self.send_command_to_ledger(command).await + } + + // based on impl from https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166 + pub async fn sign_transaction_hash( + &self, + hd_path: slip10::BIP32Path, + transaction_hash: Vec, + ) -> Result, LedgerError> { + // convert the hd_path into bytes to be sent as `data` to the Ledger + // the first element of the data should be the number of elements in the path + + let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); + let hd_path_elements_count = hd_path.depth(); + hd_path_to_bytes.insert(0, hd_path_elements_count); + + let mut data = hd_path_to_bytes; + data.append(&mut transaction_hash.clone()); + + let command = APDUCommand { + cla: CLA, + ins: SIGN_TX_HASH, + p1: P1_SIGN_TX_HASH, + p2: P2_SIGN_TX_HASH, + data: data, + }; + + self.send_command_to_ledger(command).await + } + + pub async fn sign_transaction( + &self, + hd_path: slip10::BIP32Path, + transaction: Transaction, + ) -> Result, LedgerError> { + let tagged_transaction = + TransactionSignaturePayloadTaggedTransaction::Tx(transaction.clone()); + + // TODO: do not hardcode this passphrase + let testnet_passphrase = "Test SDF Network ; September 2015"; + let network_hash = Hash(Sha256::digest(testnet_passphrase.as_bytes()).into()); + + let signature_payload = TransactionSignaturePayload { + network_id: network_hash, + tagged_transaction: tagged_transaction, + }; + + let mut signature_payload_as_bytes = signature_payload.to_xdr(Limits::none()).unwrap(); + + let mut data: Vec = Vec::new(); + + let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); + let hd_path_elements_count = hd_path.depth(); + + data.insert(0, hd_path_elements_count); + data.append(&mut hd_path_to_bytes); + data.append(&mut signature_payload_as_bytes); + + let buffer_size = 1 + hd_path_elements_count * 4; + let chunk_size = APDU_MAX_SIZE - buffer_size; + + let chunks = data.chunks(chunk_size as usize); + let chunks_count = chunks.len(); + + let mut result = Vec::new(); + println!("chunks_count: {:?}", chunks_count); + + // notes: + // the first chunk has the hd_path_elements_count and the hd_path at the beginning, before the tx [3, 128...122...47] + // the second chunk has just the end of the tx [224, 100... 0, 0, 0, 0] + + for (i, chunk) in chunks.enumerate() { + let is_first_chunk = i == 0; + let is_last_chunk = chunks_count == i + 1; + + let command = APDUCommand { + cla: CLA, + ins: SIGN_TX, + p1: if is_first_chunk { + P1_SIGN_TX_FIRST + } else { + P1_SIGN_TX_NOT_FIRST + }, + p2: if is_last_chunk { + P2_SIGN_TX_LAST + } else { + P2_SIGN_TX_MORE + }, + data: chunk.to_vec(), + }; + + match self.send_command_to_ledger(command).await { + Ok(mut r) => { + result.append(&mut r); + } + Err(e) => { + return Err(e); + } + } + } + + Ok(result) + } + + /// The display_and_confirm bool determines if the Ledger will display the public key on its screen and requires user approval to share + async fn get_public_key_with_display_flag( + &self, + hd_path: slip10::BIP32Path, + display_and_confirm: bool, + ) -> Result { + // convert the hd_path into bytes to be sent as `data` to the Ledger + // the first element of the data should be the number of elements in the path + let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); + let hd_path_elements_count = hd_path.depth(); + hd_path_to_bytes.insert(0, hd_path_elements_count); + + println!("data: {:?}", hd_path_to_bytes); + + let p2 = if display_and_confirm { + P2_GET_PUBLIC_KEY_DISPLAY + } else { + P2_GET_PUBLIC_KEY_NO_DISPLAY + }; + + // more information about how to build this command can be found at https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md + let command = APDUCommand { + cla: CLA, + ins: GET_PUBLIC_KEY, + p1: P1_GET_PUBLIC_KEY, + p2: p2, + data: hd_path_to_bytes, + }; + + tracing::info!("APDU in: {}", hex::encode(&command.serialize())); + + match self.send_command_to_ledger(command).await { + Ok(value) => { + return Ok(stellar_strkey::ed25519::PublicKey::from_payload(&value).unwrap()); + } + Err(err) => { + return Err(err); + } + } + } + + async fn send_command_to_ledger( + &self, + command: APDUCommand>, + ) -> Result, LedgerError> { + match self.transport.exchange(&command).await { + Ok(response) => { + tracing::info!( + "APDU out: {}\nAPDU ret code: {:x}", + hex::encode(response.apdu_data()), + response.retcode(), + ); + // Ok means we successfully connected with the Ledger but it doesn't mean our request succeeded. We still need to check the response.retcode + println!("RETCODE: {:?}", response.retcode()); + println!("response: {:?}", response.data()); + if response.retcode() == RETURN_CODE_OK { + return Ok(response.data().to_vec()); + } else { + let retcode = response.retcode(); + + let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); + return Err(LedgerError::APDUExchangeError(error_string)); + } + } + Err(err) => { + //FIX ME!!!! + return Err(LedgerError::LedgerConnectionError("test".to_string())); + } + }; + } +} + +fn bip_path_from_index(index: u32) -> slip10::BIP32Path { + let path = format!("m/44'/148'/{index}'"); + path.parse().unwrap() // this is basically the same thing as slip10::BIP32Path::from_str + + // the device handles this part: https://github.com/AhaLabs/rs-sep5/blob/9d6e3886b4b424dd7b730ec24c865f6fad5d770c/src/seed_phrase.rs#L86 +} + +fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { + println!("hd_path.depth: {:?}", hd_path.depth()); + (0..hd_path.depth()) + .map(|index| { + let value = *hd_path.index(index).unwrap(); + value.to_be_bytes() + }) + .flatten() + .collect::>() +} + +pub fn new_get_transport() -> Result { + // instantiate the connection to Ledger, this will return an error if Ledger is not connected + let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; + TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) +} + +pub fn get_zemu_transport(host: &str, port: u16) -> Result { + Ok(TransportZemuHttp::new(host, port)) +} + #[cfg(test)] mod test { use soroban_env_host::xdr::{self, Operation, OperationBody, Transaction, Uint256}; - use crate::app::get_zemu_transport; - use crate::{app::new_get_transport, emulator::Emulator}; + use crate::emulator::Emulator; use std::{collections::HashMap, str::FromStr, time::Duration}; @@ -27,14 +317,12 @@ mod test { use tokio::time::sleep; - use crate::app::LedgerError::APDUExchangeError; - #[ignore] #[tokio::test] #[serial] async fn test_get_public_key_with_ledger_device() { let transport = new_get_transport().unwrap(); - let ledger = app::Ledger::new(transport); + let ledger = Ledger::new(transport); let public_key = ledger.get_public_key(0).await; println!("{public_key:?}"); assert!(public_key.is_ok()); @@ -46,7 +334,7 @@ mod test { start_emulator(&mut emulator).await; let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); - let ledger = app::Ledger::new(transport); + let ledger = Ledger::new(transport); match ledger.get_public_key(0).await { Ok(public_key) => { @@ -73,7 +361,7 @@ mod test { start_emulator(&mut emulator).await; let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); - let ledger = app::Ledger::new(transport); + let ledger = Ledger::new(transport); match ledger.get_app_configuration().await { Ok(config) => { @@ -95,14 +383,10 @@ mod test { start_emulator(&mut emulator).await; let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); - let ledger = app::Ledger::new(transport); + let ledger = Ledger::new(transport); let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); - // this transaction came from https://github.com/stellar/rs-stellar-xdr/blob/main/tests/tx_small.rs - // and i am getting a retcode of 27684 which is unknown op - // built this tx with https://laboratory.stellar.org/#xdr-viewer?input=AAAAAgAAAAAg2pmLdeQrH3%2BF0HXBJ%2FWyRt8SrZbwELz3929ysW5XEwAAAGQAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAACVQfAnRiQMHp84Q9KOVvClg%2BzSdQL7D0on1NCSL%2BBkogAAAAAAAAAAAJiWgAAAAAAAAAAA&type=TransactionEnvelope - let source_account_str = "GAQNVGMLOXSCWH37QXIHLQJH6WZENXYSVWLPAEF4673W64VRNZLRHMFM"; let source_account_bytes = match stellar_strkey::Strkey::from_string(source_account_str) { Ok(key) => match key { @@ -174,14 +458,14 @@ mod test { start_emulator(&mut emulator).await; let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); - let ledger = app::Ledger::new(transport); + let ledger = Ledger::new(transport); let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); let test_hash = "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889".as_bytes(); let result = ledger.sign_transaction_hash(path, test_hash.into()).await; - if let Err(APDUExchangeError(msg)) = result { + if let Err(LedgerError::APDUExchangeError(msg)) = result { assert_eq!(msg, "Ledger APDU retcode: 0x6C66"); // this error code is SW_TX_HASH_SIGNING_MODE_NOT_ENABLED https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md } else { @@ -200,7 +484,7 @@ mod test { enable_hash_signing().await; let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); - let ledger = app::Ledger::new(transport); + let ledger = Ledger::new(transport); let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); let mut test_hash = vec![0u8; 32]; From 0d5d159f1adb0fcd756316bc3ffe58164ea496c4 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Fri, 12 Apr 2024 12:00:06 -0400 Subject: [PATCH 32/72] Make LedgerSigner adhere to Stellar signer trait --- cmd/crates/stellar-ledger/Cargo.toml | 2 + cmd/crates/stellar-ledger/src/lib.rs | 156 ++++++--- cmd/crates/stellar-ledger/src/signer.rs | 405 ++++++++---------------- 3 files changed, 255 insertions(+), 308 deletions(-) diff --git a/cmd/crates/stellar-ledger/Cargo.toml b/cmd/crates/stellar-ledger/Cargo.toml index c19dd559e..6c9f60ac3 100644 --- a/cmd/crates/stellar-ledger/Cargo.toml +++ b/cmd/crates/stellar-ledger/Cargo.toml @@ -32,6 +32,8 @@ home = "0.5.9" futures-util = "0.3.30" tokio = { version = "1", features = ["full"] } reqwest = { version = "0.11", features = ["json"]} +soroban-rpc.workspace = true +async-trait.workspace = true [dependencies.stellar-xdr] workspace = true diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 53487f714..5e5e9e4b7 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -1,28 +1,28 @@ -// https://github.com/zondax/ledger-rs - -mod emulator; - -mod docker; - mod transport_zemu_http; - -use sha2::{Digest, Sha256}; -use std::vec; -use stellar_xdr::curr::{ - Hash, Limits, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, - WriteXdr, -}; - +use async_trait::async_trait; +use futures::executor::block_on; use ledger_transport::{APDUCommand, Exchange}; use ledger_transport_hid::{ hidapi::{HidApi, HidError}, LedgerHIDError, TransportNativeHID, }; +use sha2::{Digest, Sha256}; -use soroban_env_host::xdr::Transaction; +use soroban_env_host::xdr::{Hash, Transaction}; +use std::vec; +use stellar_xdr::curr::{ + self, DecoratedSignature, Limits, Signature, SignatureHint, TransactionEnvelope, + TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, + TransactionV1Envelope, WriteXdr, +}; +use crate::signer::{Error, Stellar}; use crate::transport_zemu_http::TransportZemuHttp; +mod docker; +mod emulator; +mod signer; + // this is from https://github.com/LedgerHQ/ledger-live/blob/36cfbf3fa3300fd99bcee2ab72e1fd8f280e6280/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L181 const APDU_MAX_SIZE: u8 = 150; @@ -65,20 +65,21 @@ pub enum LedgerError { LedgerConnectionError(String), } -pub struct Ledger { +pub struct LedgerOptions { + exchange: T, + hd_path: slip10::BIP32Path, +} + +pub struct LedgerSigner { + network_passphrase: String, transport: T, + hd_path: slip10::BIP32Path, } -impl Ledger +impl LedgerSigner where T: Exchange, { - pub fn new(transport: T) -> Ledger { - Ledger { - transport: transport, - } - } - pub async fn get_public_key( &self, index: u32, @@ -99,7 +100,7 @@ where } // based on impl from https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166 - pub async fn sign_transaction_hash( + async fn sign_transaction_hash( &self, hd_path: slip10::BIP32Path, transaction_hash: Vec, @@ -133,9 +134,8 @@ where let tagged_transaction = TransactionSignaturePayloadTaggedTransaction::Tx(transaction.clone()); - // TODO: do not hardcode this passphrase - let testnet_passphrase = "Test SDF Network ; September 2015"; - let network_hash = Hash(Sha256::digest(testnet_passphrase.as_bytes()).into()); + let passphrase = self.network_passphrase.clone(); + let network_hash = Hash(Sha256::digest(passphrase.as_bytes()).into()); let signature_payload = TransactionSignaturePayload { network_id: network_hash, @@ -271,6 +271,58 @@ where } } +#[async_trait] +impl Stellar for LedgerSigner { + type Init = LedgerOptions; + + fn new(network_passphrase: &str, options: Option>) -> Self { + let options_unwrapped = options.unwrap(); + LedgerSigner { + network_passphrase: network_passphrase.to_string(), + transport: options_unwrapped.exchange, + hd_path: options_unwrapped.hd_path, + } + } + + fn network_hash(&self) -> stellar_xdr::curr::Hash { + Hash(Sha256::digest(self.network_passphrase.as_bytes()).into()) + } + + fn sign_txn_hash( + &self, + txn: [u8; 32], + source_account: &stellar_strkey::Strkey, + ) -> Result { + let signature = block_on(self.sign_transaction_hash(self.hd_path.clone(), txn.to_vec())) //TODO: refactor sign_transaction_hash + .unwrap(); // FIXME: handle error + + let sig_bytes = signature.try_into().unwrap(); // FIXME: handle error + Ok(DecoratedSignature { + hint: SignatureHint([0u8; 4]), //FIXME + signature: Signature(sig_bytes), + }) + } + + fn sign_txn( + &self, + txn: Transaction, + source_account: &stellar_strkey::Strkey, + ) -> Result { + let signature = block_on(self.sign_transaction(self.hd_path.clone(), txn.clone())).unwrap(); // FIXME: handle error + + let sig_bytes = signature.try_into().unwrap(); // FIXME: handle error + let decorated_signature = DecoratedSignature { + hint: SignatureHint([0u8; 4]), //FIXME + signature: Signature(sig_bytes), + }; + + Ok(TransactionEnvelope::Tx(TransactionV1Envelope { + tx: txn, + signatures: vec![decorated_signature].try_into().unwrap(), //fixme: remove unwrap + })) + } +} + fn bip_path_from_index(index: u32) -> slip10::BIP32Path { let path = format!("m/44'/148'/{index}'"); path.parse().unwrap() // this is basically the same thing as slip10::BIP32Path::from_str @@ -289,7 +341,7 @@ fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { .collect::>() } -pub fn new_get_transport() -> Result { +pub fn get_transport() -> Result { // instantiate the connection to Ledger, this will return an error if Ledger is not connected let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) @@ -317,12 +369,18 @@ mod test { use tokio::time::sleep; + const TEST_NETWORK_PASSPHRASE: &str = "Test SDF Network ; September 2015"; + #[ignore] #[tokio::test] #[serial] async fn test_get_public_key_with_ledger_device() { - let transport = new_get_transport().unwrap(); - let ledger = Ledger::new(transport); + let transport = get_transport().unwrap(); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); let public_key = ledger.get_public_key(0).await; println!("{public_key:?}"); assert!(public_key.is_ok()); @@ -334,7 +392,11 @@ mod test { start_emulator(&mut emulator).await; let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); - let ledger = Ledger::new(transport); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); match ledger.get_public_key(0).await { Ok(public_key) => { @@ -355,13 +417,18 @@ mod test { stop_emulator(&mut emulator).await; } + #[ignore] #[tokio::test] async fn test_get_app_configuration() { let mut emulator = Emulator::new().await; start_emulator(&mut emulator).await; let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); - let ledger = Ledger::new(transport); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); match ledger.get_app_configuration().await { Ok(config) => { @@ -377,13 +444,18 @@ mod test { stop_emulator(&mut emulator).await; } + #[ignore] #[tokio::test] async fn test_sign_tx() { let mut emulator = Emulator::new().await; start_emulator(&mut emulator).await; let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); - let ledger = Ledger::new(transport); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); @@ -439,7 +511,7 @@ mod test { match ledger.sign_transaction(path, tx).await { Ok(response) => { - assert_eq!( hex::encode(response), "77b9f6e6924e5db2e35c5ecd7dd95248eadd51ea35d61e467cf6ba0df28ca7f38674e3fea8c8a3e2a0fa45f49d4381f9cf24bcc0ff8b708c9337beb854e98e0d"); + assert_eq!( hex::encode(response), "5c2f8eb41e11ab922800071990a25cf9713cc6e7c43e50e0780ddc4c0c6da50c784609ef14c528a12f520d8ea9343b49083f59c51e3f28af8c62b3edeaade60e"); } Err(e) => { stop_emulator(&mut emulator).await; @@ -451,14 +523,19 @@ mod test { stop_emulator(&mut emulator).await; } + #[ignore] #[tokio::test] async fn test_sign_tx_hash_when_hash_signing_is_not_enabled() { - //when hash signing isnt enabled on the device we expect an error + //when hash signing isn't enabled on the device we expect an error let mut emulator = Emulator::new().await; start_emulator(&mut emulator).await; let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); - let ledger = Ledger::new(transport); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); let test_hash = @@ -476,6 +553,7 @@ mod test { stop_emulator(&mut emulator).await; } + #[ignore] #[tokio::test] async fn test_sign_tx_hash_when_hash_signing_is_enabled() { //when hash signing isnt enabled on the device we expect an error @@ -484,7 +562,11 @@ mod test { enable_hash_signing().await; let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); - let ledger = Ledger::new(transport); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); let mut test_hash = vec![0u8; 32]; diff --git a/cmd/crates/stellar-ledger/src/signer.rs b/cmd/crates/stellar-ledger/src/signer.rs index b91e1c2f6..e49d4ae5a 100644 --- a/cmd/crates/stellar-ledger/src/signer.rs +++ b/cmd/crates/stellar-ledger/src/signer.rs @@ -1,271 +1,134 @@ -// use ed25519_dalek::Signer; -// use sha2::{Digest, Sha256}; - -// use soroban_env_host::xdr::{ -// self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, -// Limits, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, ScVal, Signature, SignatureHint, -// SorobanAddressCredentials, SorobanAuthorizationEntry, SorobanCredentials, Transaction, -// TransactionEnvelope, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, -// TransactionV1Envelope, Uint256, WriteXdr, Operation, InvokeHostFunctionOp, SorobanAuthorizedFunction, -// }; - -// enum Error {} - -// fn requires_auth(txn: &Transaction) -> Option { -// let [op @ Operation { -// body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }), -// .. -// }] = txn.operations.as_slice() -// else { -// return None; -// }; -// matches!( -// auth.first().map(|x| &x.root_invocation.function), -// Some(&SorobanAuthorizedFunction::ContractFn(_)) -// ) -// .then(move || op.clone()) -// } - -// /// A trait for signing Stellar transactions and Soroban authorization entries -// pub trait Stellar { -// /// The type of the options that can be passed when creating a new signer -// type Init; -// /// Create a new signer with the given network passphrase and options -// fn new(network_passphrase: &str, options: Option) -> Self; - -// /// Get the network hash -// fn network_hash(&self) -> xdr::Hash; - -// /// Sign a transaction hash with the given source account -// /// # Errors -// /// Returns an error if the source account is not found -// fn sign_txn_hash( -// &self, -// txn: [u8; 32], -// source_account: &stellar_strkey::Strkey, -// ) -> Result; - -// /// Sign a Soroban authorization entry with the given address -// /// # Errors -// /// Returns an error if the address is not found -// fn sign_soroban_authorization_entry( -// &self, -// unsigned_entry: &SorobanAuthorizationEntry, -// signature_expiration_ledger: u32, -// address: &[u8; 32], -// ) -> Result; - -// /// Sign a Stellar transaction with the given source account -// /// This is a default implementation that signs the transaction hash and returns a decorated signature -// /// # Errors -// /// Returns an error if the source account is not found -// fn sign_txn( -// &self, -// txn: Transaction, -// source_account: &stellar_strkey::Strkey, -// ) -> Result { -// let signature_payload = TransactionSignaturePayload { -// network_id: self.network_hash(), -// tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(txn.clone()), -// }; -// let hash = Sha256::digest(&signature_payload.to_xdr(Limits::none())?).into(); -// let decorated_signature = self.sign_txn_hash(hash, source_account)?; -// Ok(TransactionEnvelope::Tx(TransactionV1Envelope { -// tx: txn, -// signatures: vec![decorated_signature].try_into()?, -// })) -// } - -// /// Sign a Soroban authorization entries for a given transaction and set the expiration ledger -// /// # Errors -// /// Returns an error if the address is not found -// fn sign_soroban_authorizations( -// &self, -// raw: &Transaction, -// signature_expiration_ledger: u32, -// ) -> Result, Error> { -// let mut tx = raw.clone(); -// let Some(mut op) = requires_auth(&tx) else { -// return Ok(None); -// }; - -// let xdr::Operation { -// body: OperationBody::InvokeHostFunction(ref mut body), -// .. -// } = op -// else { -// return Ok(None); -// }; - -// let signed_auths = body -// .auth -// .as_slice() -// .iter() -// .map(|raw_auth| { -// self.maybe_sign_soroban_authorization_entry(raw_auth, signature_expiration_ledger) -// }) -// .collect::, Error>>()?; - -// body.auth = signed_auths.try_into()?; -// tx.operations = vec![op].try_into()?; -// Ok(Some(tx)) -// } - -// /// Sign a Soroban authorization entry if the address is public key -// /// # Errors -// /// Returns an error if the address in entry is a contract -// fn maybe_sign_soroban_authorization_entry( -// &self, -// unsigned_entry: &SorobanAuthorizationEntry, -// signature_expiration_ledger: u32, -// ) -> Result { -// if let SorobanAuthorizationEntry { -// credentials: SorobanCredentials::Address(SorobanAddressCredentials { ref address, .. }), -// .. -// } = unsigned_entry -// { -// // See if we have a signer for this authorizationEntry -// // If not, then we Error -// let needle = match address { -// ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(ref a)))) => a, -// ScAddress::Contract(Hash(c)) => { -// // This address is for a contract. This means we're using a custom -// // smart-contract account. Currently the CLI doesn't support that yet. -// return Err(Error::MissingSignerForAddress { -// address: stellar_strkey::Strkey::Contract(stellar_strkey::Contract(*c)) -// .to_string(), -// }); -// } -// }; -// self.sign_soroban_authorization_entry( -// unsigned_entry, -// signature_expiration_ledger, -// needle, -// ) -// } else { -// Ok(unsigned_entry.clone()) -// } -// } -// } - - -// use std::fmt::Debug; -// #[derive(Debug)] -// pub struct DefaultSigner { -// network_passphrase: String, -// keypairs: Vec, -// } - -// impl DefaultSigner { -// pub fn get_key( -// &self, -// key: &stellar_strkey::Strkey, -// ) -> Result<&ed25519_dalek::SigningKey, Error> { -// match key { -// stellar_strkey::Strkey::PublicKeyEd25519(stellar_strkey::ed25519::PublicKey(bytes)) => { -// self.keypairs -// .iter() -// .find(|k| k.verifying_key().to_bytes() == *bytes) -// } -// _ => None, -// } -// .ok_or_else(|| Error::MissingSignerForAddress { -// address: key.to_string(), -// }) -// } -// } - -// impl Stellar for DefaultSigner { -// type Init = Vec; -// fn new(network_passphrase: &str, options: Option>) -> Self { -// DefaultSigner { -// network_passphrase: network_passphrase.to_string(), -// keypairs: options.unwrap_or_default(), -// } -// } - -// fn sign_txn_hash( -// &self, -// txn: [u8; 32], -// source_account: &stellar_strkey::Strkey, -// ) -> Result { -// let source_account = self.get_key(source_account)?; -// let tx_signature = source_account.sign(&txn); -// Ok(DecoratedSignature { -// // TODO: remove this unwrap. It's safe because we know the length of the array -// hint: SignatureHint( -// source_account.verifying_key().to_bytes()[28..] -// .try_into() -// .unwrap(), -// ), -// signature: Signature(tx_signature.to_bytes().try_into()?), -// }) -// } - -// fn sign_soroban_authorization_entry( -// &self, -// unsigned_entry: &SorobanAuthorizationEntry, -// signature_expiration_ledger: u32, -// signer: &[u8; 32], -// ) -> Result { -// let mut auth = unsigned_entry.clone(); -// let SorobanAuthorizationEntry { -// credentials: SorobanCredentials::Address(ref mut credentials), -// .. -// } = auth -// else { -// // Doesn't need special signing -// return Ok(auth); -// }; -// let SorobanAddressCredentials { nonce, .. } = credentials; - -// let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { -// network_id: self.network_hash(), -// invocation: auth.root_invocation.clone(), -// nonce: *nonce, -// signature_expiration_ledger, -// }) -// .to_xdr(Limits::none())?; - -// let strkey = stellar_strkey::ed25519::PublicKey(*signer); -// let payload = Sha256::digest(&preimage); -// let signer = self.get_key(&stellar_strkey::Strkey::PublicKeyEd25519(strkey))?; -// let signature = signer.sign(&payload); - -// let map = ScMap::sorted_from(vec![ -// ( -// ScVal::Symbol(ScSymbol("public_key".try_into()?)), -// ScVal::Bytes( -// signer -// .verifying_key() -// .to_bytes() -// .to_vec() -// .try_into() -// .map_err(Error::Xdr)?, -// ), -// ), -// ( -// ScVal::Symbol(ScSymbol("signature".try_into()?)), -// ScVal::Bytes( -// signature -// .to_bytes() -// .to_vec() -// .try_into() -// .map_err(Error::Xdr)?, -// ), -// ), -// ]) -// .map_err(Error::Xdr)?; -// credentials.signature = ScVal::Vec(Some( -// vec![ScVal::Map(Some(map))].try_into().map_err(Error::Xdr)?, -// )); -// credentials.signature_expiration_ledger = signature_expiration_ledger; -// auth.credentials = SorobanCredentials::Address(credentials.clone()); - -// Ok(auth) -// } - -// fn network_hash(&self) -> xdr::Hash { -// xdr::Hash(Sha256::digest(self.network_passphrase.as_bytes()).into()) -// } -// } +use ed25519_dalek::Signer; +use ledger_transport::async_trait; +use sha2::{Digest, Sha256}; +use soroban_env_host::xdr::{ + self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, + InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, + ScVal, Signature, SignatureHint, SorobanAddressCredentials, SorobanAuthorizationEntry, + SorobanAuthorizedFunction, SorobanCredentials, Transaction, TransactionEnvelope, + TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, + TransactionV1Envelope, Uint256, WriteXdr, +}; + +use soroban_rpc::Error as RpcError; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + RpcError(#[from] RpcError), +} + +fn requires_auth(txn: &Transaction) -> Option { + let [op @ Operation { + body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }), + .. + }] = txn.operations.as_slice() + else { + return None; + }; + matches!( + auth.first().map(|x| &x.root_invocation.function), + Some(&SorobanAuthorizedFunction::ContractFn(_)) + ) + .then(move || op.clone()) +} + +/// A trait for signing Stellar transactions and Soroban authorization entries +#[async_trait] +pub trait Stellar { + /// The type of the options that can be passed when creating a new signer + type Init; + /// Create a new signer with the given network passphrase and options + fn new(network_passphrase: &str, options: Option) -> Self; + + /// Get the network hash + fn network_hash(&self) -> xdr::Hash; + + /// Sign a transaction hash with the given source account + /// # Errors + /// Returns an error if the source account is not found + fn sign_txn_hash( + &self, + txn: [u8; 32], + source_account: &stellar_strkey::Strkey, + ) -> Result; + + /// Sign a Stellar transaction with the given source account + /// This is a default implementation that signs the transaction hash and returns a decorated signature + /// # Errors + /// Returns an error if the source account is not found + fn sign_txn( + &self, + txn: Transaction, + source_account: &stellar_strkey::Strkey, + ) -> Result { + let signature_payload = TransactionSignaturePayload { + network_id: self.network_hash(), + tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(txn.clone()), + }; + let hash = Sha256::digest(&signature_payload.to_xdr(Limits::none()).unwrap()).into(); //#fixme: remove unwrap + let decorated_signature = self.sign_txn_hash(hash, source_account)?; + Ok(TransactionEnvelope::Tx(TransactionV1Envelope { + tx: txn, + signatures: vec![decorated_signature].try_into().unwrap(), //fixme: remove unwrap + })) + } +} +struct DefaultSigner { + network_passphrase: String, + keypairs: Vec, +} + +impl DefaultSigner { + pub fn get_key( + &self, + key: &stellar_strkey::Strkey, + ) -> Result<&ed25519_dalek::SigningKey, Error> { + match key { + stellar_strkey::Strkey::PublicKeyEd25519(stellar_strkey::ed25519::PublicKey(bytes)) => { + self.keypairs + .iter() + .find(|k| k.verifying_key().to_bytes() == *bytes) + } + _ => None, + } + .ok_or_else(|| { + Error::RpcError(RpcError::MissingSignerForAddress { + address: key.to_string(), + }) + }) + } +} + +#[async_trait] +impl Stellar for DefaultSigner { + type Init = Vec; + fn new(network_passphrase: &str, options: Option>) -> Self { + DefaultSigner { + network_passphrase: network_passphrase.to_string(), + keypairs: options.unwrap_or_default(), + } + } + + fn sign_txn_hash( + &self, + txn: [u8; 32], + source_account: &stellar_strkey::Strkey, + ) -> Result { + let source_account = self.get_key(source_account)?; + let tx_signature = source_account.sign(&txn); + Ok(DecoratedSignature { + // TODO: remove this unwrap. It's safe because we know the length of the array + hint: SignatureHint( + source_account.verifying_key().to_bytes()[28..] + .try_into() + .unwrap(), + ), + signature: Signature(tx_signature.to_bytes().try_into().unwrap()), //FIXME: remove unwrap + }) + } + + fn network_hash(&self) -> xdr::Hash { + xdr::Hash(Sha256::digest(self.network_passphrase.as_bytes()).into()) + } +} From dcc34aba61ae2d616832aa40acbb5936c5890fec Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 16 Apr 2024 14:43:49 -0400 Subject: [PATCH 33/72] Check in Cargo.lock --- Cargo.lock | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 05c63fd19..1014b1903 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3170,10 +3170,12 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -3185,6 +3187,7 @@ dependencies = [ "sync_wrapper", "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls", "tower-service", "url", @@ -4189,6 +4192,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" name = "stellar-ledger" version = "20.3.1" dependencies = [ + "async-trait", "bollard", "byteorder", "ed25519-dalek 2.0.0", @@ -4204,6 +4208,7 @@ dependencies = [ "log", "once_cell", "pretty_assertions", + "reqwest", "sep5", "serde", "serde_derive", @@ -4212,6 +4217,7 @@ dependencies = [ "sha2 0.9.9", "slip10", "soroban-env-host", + "soroban-rpc", "soroban-spec", "stellar-strkey 0.0.7", "stellar-xdr", From fb9f65367eb8b4f5f5b5d549ec565537ddf40a8c Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 16 Apr 2024 14:46:44 -0400 Subject: [PATCH 34/72] Clippy --- cmd/crates/stellar-ledger/src/lib.rs | 19 +++++++++---------- cmd/crates/stellar-ledger/src/signer.rs | 8 +++----- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 5e5e9e4b7..d177cc73d 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -11,7 +11,7 @@ use sha2::{Digest, Sha256}; use soroban_env_host::xdr::{Hash, Transaction}; use std::vec; use stellar_xdr::curr::{ - self, DecoratedSignature, Limits, Signature, SignatureHint, TransactionEnvelope, + DecoratedSignature, Limits, Signature, SignatureHint, TransactionEnvelope, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, TransactionV1Envelope, WriteXdr, }; @@ -120,7 +120,7 @@ where ins: SIGN_TX_HASH, p1: P1_SIGN_TX_HASH, p2: P2_SIGN_TX_HASH, - data: data, + data, }; self.send_command_to_ledger(command).await @@ -139,7 +139,7 @@ where let signature_payload = TransactionSignaturePayload { network_id: network_hash, - tagged_transaction: tagged_transaction, + tagged_transaction, }; let mut signature_payload_as_bytes = signature_payload.to_xdr(Limits::none()).unwrap(); @@ -224,7 +224,7 @@ where cla: CLA, ins: GET_PUBLIC_KEY, p1: P1_GET_PUBLIC_KEY, - p2: p2, + p2, data: hd_path_to_bytes, }; @@ -256,14 +256,13 @@ where println!("response: {:?}", response.data()); if response.retcode() == RETURN_CODE_OK { return Ok(response.data().to_vec()); - } else { - let retcode = response.retcode(); - - let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); - return Err(LedgerError::APDUExchangeError(error_string)); } + + let retcode = response.retcode(); + let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); + return Err(LedgerError::APDUExchangeError(error_string)); } - Err(err) => { + Err(_err) => { //FIX ME!!!! return Err(LedgerError::LedgerConnectionError("test".to_string())); } diff --git a/cmd/crates/stellar-ledger/src/signer.rs b/cmd/crates/stellar-ledger/src/signer.rs index e49d4ae5a..12d037803 100644 --- a/cmd/crates/stellar-ledger/src/signer.rs +++ b/cmd/crates/stellar-ledger/src/signer.rs @@ -2,12 +2,10 @@ use ed25519_dalek::Signer; use ledger_transport::async_trait; use sha2::{Digest, Sha256}; use soroban_env_host::xdr::{ - self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, - InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, - ScVal, Signature, SignatureHint, SorobanAddressCredentials, SorobanAuthorizationEntry, - SorobanAuthorizedFunction, SorobanCredentials, Transaction, TransactionEnvelope, + self, DecoratedSignature, InvokeHostFunctionOp, Limits, Operation, OperationBody, Signature, + SignatureHint, SorobanAuthorizedFunction, Transaction, TransactionEnvelope, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, - TransactionV1Envelope, Uint256, WriteXdr, + TransactionV1Envelope, WriteXdr, }; use soroban_rpc::Error as RpcError; From d8a5dc013e9f52f7aaf91759f903ca0ff4122a8a Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 17 Apr 2024 13:52:35 -0400 Subject: [PATCH 35/72] WIP: start to use testcontainers, but it's flakey and upgrade bollard --- Cargo.toml | 1 + cmd/crates/stellar-ledger/Cargo.toml | 5 +- cmd/crates/stellar-ledger/src/lib.rs | 117 ++++++++++++++++-- .../stellar-ledger/src/transport_zemu_http.rs | 4 +- cmd/soroban-cli/Cargo.toml | 2 +- 5 files changed, 114 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aad28aceb..70426dceb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,7 @@ ethnum = "1.3.2" hex = "0.4.3" itertools = "0.10.0" async-trait = "0.1.76" +bollard = "0.16.0" serde-aux = "4.1.2" serde_json = "1.0.82" diff --git a/cmd/crates/stellar-ledger/Cargo.toml b/cmd/crates/stellar-ledger/Cargo.toml index 6c9f60ac3..5972a0173 100644 --- a/cmd/crates/stellar-ledger/Cargo.toml +++ b/cmd/crates/stellar-ledger/Cargo.toml @@ -27,13 +27,16 @@ slip10 = "0.4.3" tracing = {workspace=true} hex.workspace = true byteorder = "1.5.0" -bollard = "0.15.0" +bollard = { workspace=true } home = "0.5.9" futures-util = "0.3.30" tokio = { version = "1", features = ["full"] } reqwest = { version = "0.11", features = ["json"]} soroban-rpc.workspace = true async-trait.workspace = true +testcontainers = { git = "https://github.com/testcontainers/testcontainers-rs.git", rev = "4b3e4f08a2c0bdf521636b03f959f004e6d216aa" } +phf = { version = "0.11.2", features = ["macros"] } + [dependencies.stellar-xdr] workspace = true diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index d177cc73d..a9de4b3cf 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -9,7 +9,7 @@ use ledger_transport_hid::{ use sha2::{Digest, Sha256}; use soroban_env_host::xdr::{Hash, Transaction}; -use std::vec; +use std::{path::PathBuf, vec}; use stellar_xdr::curr::{ DecoratedSignature, Limits, Signature, SignatureHint, TransactionEnvelope, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, @@ -350,6 +350,92 @@ pub fn get_zemu_transport(host: &str, port: u16) -> Result "/project/deps/nanos-secure-sdk", + "BOLOS_ENV" => "/opt/bolos", + "DISPLAY" => "host.docker.internal:0", +}); +struct Map(phf::Map<&'static str, &'static str>); + +impl From<&Map> for HashMap { + fn from(Map(map): &Map) -> Self { + map.into_iter() + .map(|(a, b)| ((*a).to_string(), (*b).to_string())) + .collect() + } +} + +#[derive(Debug, Default)] +pub struct Speculos( + HashMap, + HashMap, + Vec, +); +const DEFAULT_APP_PATH: &str = "/project/app/bin"; +impl Speculos { + pub fn new() -> Self { + #[allow(unused_mut)] + let apps_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("apps"); + let mut volumes = HashMap::new(); + volumes.insert( + apps_dir.to_str().unwrap().to_string(), + DEFAULT_APP_PATH.to_string(), + ); + let container_elf_path = format!("{DEFAULT_APP_PATH}/stellarNanosApp.elf"); + let command_string = format!("/home/zondax/speculos/speculos.py --log-level speculos:DEBUG --color JADE_GREEN --display headless -s \"other base behind follow wet put glad muscle unlock sell income october\" -m nanos {container_elf_path}"); + Speculos(ENV.into(), volumes, vec![command_string]) + } +} + +#[derive(Debug, Default, Clone)] +pub struct SpeculosArgs; + +impl ImageArgs for SpeculosArgs { + fn into_iterator(self) -> Box> { + let container_elf_path = format!("{DEFAULT_APP_PATH}/stellarNanosApp.elf"); + let command_string = format!("/home/zondax/speculos/speculos.py --log-level speculos:DEBUG --color JADE_GREEN --display headless -s \"other base behind follow wet put glad muscle unlock sell income october\" -m nanos {container_elf_path}"); + Box::new(vec![command_string].into_iter()) + } +} + +impl Image for Speculos { + type Args = SpeculosArgs; + + fn name(&self) -> String { + NAME.to_owned() + } + + fn tag(&self) -> String { + TAG.to_owned() + } + + fn expose_ports(&self) -> Vec { + vec![5000, 9998, 41000] + } + + fn ready_conditions(&self) -> Vec { + // vec![WaitFor::seconds(30)] + vec![WaitFor::message_on_stdout("HTTP proxy started...")] + } + + fn env_vars(&self) -> Box + '_> { + Box::new(self.0.iter()) + } + + fn volumes(&self) -> Box + '_> { + Box::new(self.1.iter()) + } +} + #[cfg(test)] mod test { use soroban_env_host::xdr::{self, Operation, OperationBody, Transaction, Uint256}; @@ -387,10 +473,11 @@ mod test { #[tokio::test] async fn test_get_public_key() { - let mut emulator = Emulator::new().await; - start_emulator(&mut emulator).await; + let docker = clients::Cli::default(); + let node = docker.run(Speculos::new()); + let host_port = node.get_host_port_ipv4(9998); - let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); + let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); let ledger_options = Some(LedgerOptions { exchange: transport, hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), @@ -407,22 +494,25 @@ mod test { assert_eq!(public_key_string, expected_public_key); } Err(e) => { + node.stop(); println!("{e}"); assert!(false); - stop_emulator(&mut emulator).await; } } - stop_emulator(&mut emulator).await; + node.stop(); } - #[ignore] #[tokio::test] async fn test_get_app_configuration() { - let mut emulator = Emulator::new().await; - start_emulator(&mut emulator).await; + let docker = clients::Cli::default(); + let node = docker.run(Speculos::new()); + let host_port = node.get_host_port_ipv4(9998); - let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); + println!("hostport for 9998: {host_port}"); + println!("hostport for 5000: {}", node.get_host_port_ipv4(5000)); + + let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); let ledger_options = Some(LedgerOptions { exchange: transport, hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), @@ -434,13 +524,16 @@ mod test { assert_eq!(config, vec![0, 5, 0, 3]); } Err(e) => { + node.stop(); println!("{e}"); assert!(false); - stop_emulator(&mut emulator).await; } }; - stop_emulator(&mut emulator).await; + // sleep 10 seconds here + sleep(Duration::from_secs(10)).await; + + node.stop(); } #[ignore] diff --git a/cmd/crates/stellar-ledger/src/transport_zemu_http.rs b/cmd/crates/stellar-ledger/src/transport_zemu_http.rs index 68ab59cd4..5cb7a5181 100644 --- a/cmd/crates/stellar-ledger/src/transport_zemu_http.rs +++ b/cmd/crates/stellar-ledger/src/transport_zemu_http.rs @@ -98,10 +98,12 @@ impl Exchange for TransportZemuHttp { headers.insert(ACCEPT, HeaderValue::from_static("application/json")); headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + println!("url: {}", self.url); + let resp: Response = HttpClient::new() .post(&self.url) .headers(headers) - .timeout(Duration::from_secs(10)) + .timeout(Duration::from_secs(20)) .json(&request) .send() .await diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index 666b8c456..3306ccd10 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -106,7 +106,7 @@ ureq = { version = "2.9.1", features = ["json"] } tempfile = "3.8.1" toml_edit = "0.21.0" rust-embed = { version = "8.2.0", features = ["debug-embed"] } -bollard = "0.15.0" +bollard = { workspace=true } futures-util = "0.3.30" home = "0.5.9" # For hyper-tls From db06ddcfc63108aedc3c69651634e594f94c766c Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 17 Apr 2024 17:24:55 -0400 Subject: [PATCH 36/72] Getting closer, this isn't as flakey but has some sleeps in there --- cmd/crates/stellar-ledger/src/lib.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index a9de4b3cf..7598dc1a8 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -418,9 +418,9 @@ impl Image for Speculos { TAG.to_owned() } - fn expose_ports(&self) -> Vec { - vec![5000, 9998, 41000] - } + // fn expose_ports(&self) -> Vec { + // vec![5000, 9998, 41000] + // } fn ready_conditions(&self) -> Vec { // vec![WaitFor::seconds(30)] @@ -484,6 +484,8 @@ mod test { }); let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + sleep(Duration::from_secs(10)).await; + match ledger.get_public_key(0).await { Ok(public_key) => { let public_key_string = public_key.to_string(); @@ -494,12 +496,14 @@ mod test { assert_eq!(public_key_string, expected_public_key); } Err(e) => { + sleep(Duration::from_secs(10)).await; node.stop(); println!("{e}"); assert!(false); } } + sleep(Duration::from_secs(10)).await; node.stop(); } @@ -524,6 +528,7 @@ mod test { assert_eq!(config, vec![0, 5, 0, 3]); } Err(e) => { + sleep(Duration::from_secs(10)).await; node.stop(); println!("{e}"); assert!(false); @@ -532,7 +537,6 @@ mod test { // sleep 10 seconds here sleep(Duration::from_secs(10)).await; - node.stop(); } @@ -615,14 +619,14 @@ mod test { stop_emulator(&mut emulator).await; } - #[ignore] #[tokio::test] async fn test_sign_tx_hash_when_hash_signing_is_not_enabled() { //when hash signing isn't enabled on the device we expect an error - let mut emulator = Emulator::new().await; - start_emulator(&mut emulator).await; + let docker = clients::Cli::default(); + let node = docker.run(Speculos::new()); + let host_port = node.get_host_port_ipv4(9998); - let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); + let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); let ledger_options = Some(LedgerOptions { exchange: transport, hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), @@ -638,11 +642,13 @@ mod test { assert_eq!(msg, "Ledger APDU retcode: 0x6C66"); // this error code is SW_TX_HASH_SIGNING_MODE_NOT_ENABLED https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md } else { - stop_emulator(&mut emulator).await; + sleep(Duration::from_secs(10)).await; + node.stop(); panic!("Unexpected result: {:?}", result); } - stop_emulator(&mut emulator).await; + sleep(Duration::from_secs(10)).await; + node.stop(); } #[ignore] From e6cbf777b186e39cbece22a39663f43e0b7fb55f Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 18 Apr 2024 12:19:38 -0400 Subject: [PATCH 37/72] Use testcontainers for all ledger tests --- cmd/crates/stellar-ledger/src/lib.rs | 199 ++++++++---------- .../stellar-ledger/src/transport_zemu_http.rs | 2 - 2 files changed, 86 insertions(+), 115 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 7598dc1a8..7230b2ede 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -330,7 +330,6 @@ fn bip_path_from_index(index: u32) -> slip10::BIP32Path { } fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { - println!("hd_path.depth: {:?}", hd_path.depth()); (0..hd_path.depth()) .map(|index| { let value = *hd_path.index(index).unwrap(); @@ -442,6 +441,7 @@ mod test { use crate::emulator::Emulator; + use std::sync::Arc; use std::{collections::HashMap, str::FromStr, time::Duration}; use super::*; @@ -484,8 +484,6 @@ mod test { }); let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); - sleep(Duration::from_secs(10)).await; - match ledger.get_public_key(0).await { Ok(public_key) => { let public_key_string = public_key.to_string(); @@ -503,7 +501,6 @@ mod test { } } - sleep(Duration::from_secs(10)).await; node.stop(); } @@ -513,9 +510,6 @@ mod test { let node = docker.run(Speculos::new()); let host_port = node.get_host_port_ipv4(9998); - println!("hostport for 9998: {host_port}"); - println!("hostport for 5000: {}", node.get_host_port_ipv4(5000)); - let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); let ledger_options = Some(LedgerOptions { exchange: transport, @@ -528,30 +522,31 @@ mod test { assert_eq!(config, vec![0, 5, 0, 3]); } Err(e) => { - sleep(Duration::from_secs(10)).await; node.stop(); println!("{e}"); assert!(false); } }; - // sleep 10 seconds here - sleep(Duration::from_secs(10)).await; node.stop(); } - #[ignore] #[tokio::test] async fn test_sign_tx() { - let mut emulator = Emulator::new().await; - start_emulator(&mut emulator).await; + let docker = clients::Cli::default(); + let node = docker.run(Speculos::new()); + let host_port = node.get_host_port_ipv4(9998); + let ui_host_port = node.get_host_port_ipv4(5000); + + // println!("sleeping for 10 to give me time to get the port"); + // sleep(Duration::from_secs(15)).await; - let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); + let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); let ledger_options = Some(LedgerOptions { exchange: transport, hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), }); - let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + let ledger = Arc::new(LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options)); let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); @@ -605,18 +600,29 @@ mod test { .unwrap(), }; - match ledger.sign_transaction(path, tx).await { + let sign = tokio::task::spawn({ + let ledger = Arc::clone(&ledger); + async move { ledger.sign_transaction(path, tx).await } + }); + let approve = tokio::task::spawn(approve_tx_signature(ui_host_port)); + + // sleep(Duration::from_secs(20)).await; + + let result = sign.await.unwrap(); + let _ = approve.await.unwrap(); + + match result { Ok(response) => { assert_eq!( hex::encode(response), "5c2f8eb41e11ab922800071990a25cf9713cc6e7c43e50e0780ddc4c0c6da50c784609ef14c528a12f520d8ea9343b49083f59c51e3f28af8c62b3edeaade60e"); } Err(e) => { - stop_emulator(&mut emulator).await; + node.stop(); println!("{e}"); assert!(false); } }; - stop_emulator(&mut emulator).await; + node.stop(); } #[tokio::test] @@ -642,29 +648,29 @@ mod test { assert_eq!(msg, "Ledger APDU retcode: 0x6C66"); // this error code is SW_TX_HASH_SIGNING_MODE_NOT_ENABLED https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md } else { - sleep(Duration::from_secs(10)).await; node.stop(); panic!("Unexpected result: {:?}", result); } - sleep(Duration::from_secs(10)).await; node.stop(); } - #[ignore] #[tokio::test] async fn test_sign_tx_hash_when_hash_signing_is_enabled() { //when hash signing isnt enabled on the device we expect an error - let mut emulator = Emulator::new().await; - start_emulator(&mut emulator).await; - enable_hash_signing().await; + let docker = clients::Cli::default(); + let node = docker.run(Speculos::new()); + let host_port = node.get_host_port_ipv4(9998); + let ui_host_port = node.get_host_port_ipv4(5000); + + enable_hash_signing(ui_host_port).await; - let transport = get_zemu_transport("127.0.0.1", 9998).unwrap(); + let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); let ledger_options = Some(LedgerOptions { exchange: transport, hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), }); - let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + let ledger = Arc::new(LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options)); let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); let mut test_hash = vec![0u8; 32]; @@ -675,26 +681,32 @@ mod test { ) { Ok(()) => {} Err(e) => { - stop_emulator(&mut emulator).await; + sleep(Duration::from_secs(10)).await; + node.stop(); panic!("Unexpected result: {e}"); } } - let result = ledger.sign_transaction_hash(path, test_hash); + let sign = tokio::task::spawn({ + let ledger = Arc::clone(&ledger); + async move { ledger.sign_transaction_hash(path, test_hash).await } + }); + let approve = tokio::task::spawn(approve_tx_hash_signature(ui_host_port)); - approve_tx_hash_signature().await; + let result = sign.await.unwrap(); + let _ = approve.await.unwrap(); - match result.await { + match result { Ok(result) => { println!("this is the response from signing the hash: {result:?}"); } Err(e) => { - stop_emulator(&mut emulator).await; + node.stop(); panic!("Unexpected result: {e}"); } } - stop_emulator(&mut emulator).await; + node.stop(); } async fn start_emulator(e: &mut Emulator) { @@ -712,16 +724,16 @@ mod test { } // FIXME lol/sob - async fn enable_hash_signing() { - // let client = reqwest::Client::new(); - // client.post("http://localhost:5001/button/right") + async fn enable_hash_signing(ui_host_port: u16) { + println!("enabliing hash signing on the device"); + let mut map = HashMap::new(); map.insert("action", "press-and-release"); let client = reqwest::Client::new(); // right button press client - .post("http://localhost:5001/button/right") + .post(format!("http://localhost:{ui_host_port}/button/right")) .json(&map) .send() .await @@ -729,7 +741,7 @@ mod test { // both button press client - .post("http://localhost:5001/button/both") + .post(format!("http://localhost:{ui_host_port}/button/both")) .json(&map) .send() .await @@ -737,7 +749,7 @@ mod test { // both button press client - .post("http://localhost:5001/button/both") + .post(format!("http://localhost:{ui_host_port}/button/both")) .json(&map) .send() .await @@ -745,7 +757,7 @@ mod test { // right button press client - .post("http://localhost:5001/button/right") + .post(format!("http://localhost:{ui_host_port}/button/right")) .json(&map) .send() .await @@ -753,7 +765,7 @@ mod test { // right button press client - .post("http://localhost:5001/button/right") + .post(format!("http://localhost:{ui_host_port}/button/right")) .json(&map) .send() .await @@ -761,96 +773,57 @@ mod test { // both button press client - .post("http://localhost:5001/button/both") + .post(format!("http://localhost:{ui_host_port}/button/both")) .json(&map) .send() .await .unwrap(); } - async fn approve_tx_hash_signature() { - println!("approving tx hash sig"); - - // let client = reqwest::Client::new(); - // client.post("http://localhost:5001/button/right") + async fn approve_tx_hash_signature(ui_host_port: u16) { + println!("approving tx hash sig on the device"); let mut map = HashMap::new(); map.insert("action", "press-and-release"); let client = reqwest::Client::new(); - // right button press - client - .post("http://localhost:5001/button/right") - .json(&map) - .send() - .await - .unwrap(); + // press the right button 10 times + for _ in 0..10 { + client + .post(format!("http://localhost:{ui_host_port}/button/right")) + .json(&map) + .send() + .await + .unwrap(); + } - // right button press - client - .post("http://localhost:5001/button/right") - .json(&map) - .send() - .await - .unwrap(); - // right button press + // press both buttons client - .post("http://localhost:5001/button/right") + .post(format!("http://localhost:{ui_host_port}/button/both")) .json(&map) .send() .await .unwrap(); - // right button press - client - .post("http://localhost:5001/button/right") - .json(&map) - .send() - .await - .unwrap(); - // right button press - client - .post("http://localhost:5001/button/right") - .json(&map) - .send() - .await - .unwrap(); - // right button press - client - .post("http://localhost:5001/button/right") - .json(&map) - .send() - .await - .unwrap(); - // right button press - client - .post("http://localhost:5001/button/right") - .json(&map) - .send() - .await - .unwrap(); - // right button press - client - .post("http://localhost:5001/button/right") - .json(&map) - .send() - .await - .unwrap(); - // right button press - client - .post("http://localhost:5001/button/right") - .json(&map) - .send() - .await - .unwrap(); - // right button press - client - .post("http://localhost:5001/button/right") - .json(&map) - .send() - .await - .unwrap(); - // both button press + } + + async fn approve_tx_signature(ui_host_port: u16) { + println!("approving tx on the device"); + let mut map = HashMap::new(); + map.insert("action", "press-and-release"); + + // press right button 17 times + let client = reqwest::Client::new(); + for _ in 0..17 { + client + .post(format!("http://localhost:{ui_host_port}/button/right")) + .json(&map) + .send() + .await + .unwrap(); + } + + // press both buttons client - .post("http://localhost:5001/button/both") + .post(format!("http://localhost:{ui_host_port}/button/both")) .json(&map) .send() .await diff --git a/cmd/crates/stellar-ledger/src/transport_zemu_http.rs b/cmd/crates/stellar-ledger/src/transport_zemu_http.rs index 5cb7a5181..259b3d706 100644 --- a/cmd/crates/stellar-ledger/src/transport_zemu_http.rs +++ b/cmd/crates/stellar-ledger/src/transport_zemu_http.rs @@ -98,8 +98,6 @@ impl Exchange for TransportZemuHttp { headers.insert(ACCEPT, HeaderValue::from_static("application/json")); headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); - println!("url: {}", self.url); - let resp: Response = HttpClient::new() .post(&self.url) .headers(headers) From a367a582c44de70daa076c40f9f87a3293d9f4b8 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 18 Apr 2024 13:27:05 -0400 Subject: [PATCH 38/72] Move speculos testcontainer setup to its own file --- cmd/crates/stellar-ledger/src/lib.rs | 94 +---------------------- cmd/crates/stellar-ledger/src/speculos.rs | 82 ++++++++++++++++++++ 2 files changed, 86 insertions(+), 90 deletions(-) create mode 100644 cmd/crates/stellar-ledger/src/speculos.rs diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 7230b2ede..bc6d6784f 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -9,7 +9,7 @@ use ledger_transport_hid::{ use sha2::{Digest, Sha256}; use soroban_env_host::xdr::{Hash, Transaction}; -use std::{path::PathBuf, vec}; +use std::vec; use stellar_xdr::curr::{ DecoratedSignature, Limits, Signature, SignatureHint, TransactionEnvelope, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, @@ -22,6 +22,7 @@ use crate::transport_zemu_http::TransportZemuHttp; mod docker; mod emulator; mod signer; +mod speculos; // this is from https://github.com/LedgerHQ/ledger-live/blob/36cfbf3fa3300fd99bcee2ab72e1fd8f280e6280/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L181 const APDU_MAX_SIZE: u8 = 150; @@ -349,97 +350,12 @@ pub fn get_zemu_transport(host: &str, port: u16) -> Result "/project/deps/nanos-secure-sdk", - "BOLOS_ENV" => "/opt/bolos", - "DISPLAY" => "host.docker.internal:0", -}); -struct Map(phf::Map<&'static str, &'static str>); - -impl From<&Map> for HashMap { - fn from(Map(map): &Map) -> Self { - map.into_iter() - .map(|(a, b)| ((*a).to_string(), (*b).to_string())) - .collect() - } -} - -#[derive(Debug, Default)] -pub struct Speculos( - HashMap, - HashMap, - Vec, -); -const DEFAULT_APP_PATH: &str = "/project/app/bin"; -impl Speculos { - pub fn new() -> Self { - #[allow(unused_mut)] - let apps_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("apps"); - let mut volumes = HashMap::new(); - volumes.insert( - apps_dir.to_str().unwrap().to_string(), - DEFAULT_APP_PATH.to_string(), - ); - let container_elf_path = format!("{DEFAULT_APP_PATH}/stellarNanosApp.elf"); - let command_string = format!("/home/zondax/speculos/speculos.py --log-level speculos:DEBUG --color JADE_GREEN --display headless -s \"other base behind follow wet put glad muscle unlock sell income october\" -m nanos {container_elf_path}"); - Speculos(ENV.into(), volumes, vec![command_string]) - } -} - -#[derive(Debug, Default, Clone)] -pub struct SpeculosArgs; - -impl ImageArgs for SpeculosArgs { - fn into_iterator(self) -> Box> { - let container_elf_path = format!("{DEFAULT_APP_PATH}/stellarNanosApp.elf"); - let command_string = format!("/home/zondax/speculos/speculos.py --log-level speculos:DEBUG --color JADE_GREEN --display headless -s \"other base behind follow wet put glad muscle unlock sell income october\" -m nanos {container_elf_path}"); - Box::new(vec![command_string].into_iter()) - } -} - -impl Image for Speculos { - type Args = SpeculosArgs; - - fn name(&self) -> String { - NAME.to_owned() - } - - fn tag(&self) -> String { - TAG.to_owned() - } - - // fn expose_ports(&self) -> Vec { - // vec![5000, 9998, 41000] - // } - - fn ready_conditions(&self) -> Vec { - // vec![WaitFor::seconds(30)] - vec![WaitFor::message_on_stdout("HTTP proxy started...")] - } - - fn env_vars(&self) -> Box + '_> { - Box::new(self.0.iter()) - } - - fn volumes(&self) -> Box + '_> { - Box::new(self.1.iter()) - } -} - #[cfg(test)] mod test { use soroban_env_host::xdr::{self, Operation, OperationBody, Transaction, Uint256}; use crate::emulator::Emulator; + use crate::speculos::Speculos; use std::sync::Arc; use std::{collections::HashMap, str::FromStr, time::Duration}; @@ -452,6 +368,7 @@ mod test { Memo, MuxedAccount, PaymentOp, Preconditions, SequenceNumber, TransactionExt, }; + use testcontainers::{clients, Container}; use tokio::time::sleep; const TEST_NETWORK_PASSPHRASE: &str = "Test SDF Network ; September 2015"; @@ -538,9 +455,6 @@ mod test { let host_port = node.get_host_port_ipv4(9998); let ui_host_port = node.get_host_port_ipv4(5000); - // println!("sleeping for 10 to give me time to get the port"); - // sleep(Duration::from_secs(15)).await; - let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); let ledger_options = Some(LedgerOptions { exchange: transport, diff --git a/cmd/crates/stellar-ledger/src/speculos.rs b/cmd/crates/stellar-ledger/src/speculos.rs new file mode 100644 index 000000000..24faaf97a --- /dev/null +++ b/cmd/crates/stellar-ledger/src/speculos.rs @@ -0,0 +1,82 @@ +use std::{collections::HashMap, path::PathBuf, str::FromStr, time::Duration}; +use testcontainers::{core::WaitFor, Image, ImageArgs}; + +const NAME: &str = "docker.io/zondax/builder-zemu"; +const TAG: &str = "speculos-3a3439f6b45eca7f56395673caaf434c202e7005"; + +static ENV: &Map = &Map(phf::phf_map! { + "BOLOS_SDK"=> "/project/deps/nanos-secure-sdk", + "BOLOS_ENV" => "/opt/bolos", + "DISPLAY" => "host.docker.internal:0", +}); +struct Map(phf::Map<&'static str, &'static str>); + +impl From<&Map> for HashMap { + fn from(Map(map): &Map) -> Self { + map.into_iter() + .map(|(a, b)| ((*a).to_string(), (*b).to_string())) + .collect() + } +} + +#[derive(Debug, Default)] +pub struct Speculos( + HashMap, + HashMap, + Vec, +); +const DEFAULT_APP_PATH: &str = "/project/app/bin"; +impl Speculos { + pub fn new() -> Self { + #[allow(unused_mut)] + let apps_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("apps"); + let mut volumes = HashMap::new(); + volumes.insert( + apps_dir.to_str().unwrap().to_string(), + DEFAULT_APP_PATH.to_string(), + ); + let container_elf_path = format!("{DEFAULT_APP_PATH}/stellarNanosApp.elf"); + let command_string = format!("/home/zondax/speculos/speculos.py --log-level speculos:DEBUG --color JADE_GREEN --display headless -s \"other base behind follow wet put glad muscle unlock sell income october\" -m nanos {container_elf_path}"); + Speculos(ENV.into(), volumes, vec![command_string]) + } +} + +#[derive(Debug, Default, Clone)] +pub struct SpeculosArgs; + +impl ImageArgs for SpeculosArgs { + fn into_iterator(self) -> Box> { + let container_elf_path = format!("{DEFAULT_APP_PATH}/stellarNanosApp.elf"); + let command_string = format!("/home/zondax/speculos/speculos.py --log-level speculos:DEBUG --color JADE_GREEN --display headless -s \"other base behind follow wet put glad muscle unlock sell income october\" -m nanos {container_elf_path}"); + Box::new(vec![command_string].into_iter()) + } +} + +impl Image for Speculos { + type Args = SpeculosArgs; + + fn name(&self) -> String { + NAME.to_owned() + } + + fn tag(&self) -> String { + TAG.to_owned() + } + + // fn expose_ports(&self) -> Vec { + // vec![5000, 9998, 41000] + // } + + fn ready_conditions(&self) -> Vec { + // vec![WaitFor::seconds(30)] + vec![WaitFor::message_on_stdout("HTTP proxy started...")] + } + + fn env_vars(&self) -> Box + '_> { + Box::new(self.0.iter()) + } + + fn volumes(&self) -> Box + '_> { + Box::new(self.1.iter()) + } +} From bd518448a9eac984ea5de911c8432fe95b9fca42 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 18 Apr 2024 15:13:05 -0400 Subject: [PATCH 39/72] Remove docker.rs and emulator.rs to use testcontainers instead --- cmd/crates/stellar-ledger/src/docker.rs | 259 ---------------------- cmd/crates/stellar-ledger/src/emulator.rs | 52 ----- cmd/crates/stellar-ledger/src/lib.rs | 17 -- 3 files changed, 328 deletions(-) delete mode 100644 cmd/crates/stellar-ledger/src/docker.rs delete mode 100644 cmd/crates/stellar-ledger/src/emulator.rs diff --git a/cmd/crates/stellar-ledger/src/docker.rs b/cmd/crates/stellar-ledger/src/docker.rs deleted file mode 100644 index 5bcb2f890..000000000 --- a/cmd/crates/stellar-ledger/src/docker.rs +++ /dev/null @@ -1,259 +0,0 @@ -use std::{collections::HashMap, path::PathBuf}; - -use bollard::{ - container::{Config, CreateContainerOptions, LogsOptions, StartContainerOptions}, - image::CreateImageOptions, - service::{HostConfig, PortBinding}, - ClientVersion, Docker, -}; -use futures_util::TryStreamExt; - -#[allow(unused_imports)] -// Need to add this for windows, since we are only using this crate for the unix fn try_docker_desktop_socket -use home::home_dir; - -pub const DOCKER_HOST_HELP: &str = "Optional argument to override the default docker host. This is useful when you are using a non-standard docker host path for your Docker-compatible container runtime, e.g. Docker Desktop defaults to $HOME/.docker/run/docker.sock instead of /var/run/docker.sock"; - -// DEFAULT_DOCKER_HOST is from the bollard crate on the main branch, which has not been released yet: https://github.com/fussybeaver/bollard/blob/0972b1aac0ad5c08798e100319ddd0d2ee010365/src/docker.rs#L64 -#[cfg(unix)] -pub const DEFAULT_DOCKER_HOST: &str = "unix:///var/run/docker.sock"; - -#[cfg(windows)] -pub const DEFAULT_DOCKER_HOST: &str = "npipe:////./pipe/docker_engine"; - -// DEFAULT_TIMEOUT and API_DEFAULT_VERSION are from the bollard crate -const DEFAULT_TIMEOUT: u64 = 120; -const API_DEFAULT_VERSION: &ClientVersion = &ClientVersion { - major_version: 1, - minor_version: 40, -}; - -const BOLOS_SDK: &str = "/project/deps/nanos-secure-sdk"; -const DEFAULT_APP_PATH: &str = "/project/app/bin"; -const BOLOS_ENV: &str = "/opt/bolos"; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("⛔ ️Failed to start container: {0}")] - BollardErr(#[from] bollard::errors::Error), - - #[error("URI scheme is not supported: {uri}")] - UnsupportedURISchemeError { uri: String }, -} - -pub struct DockerConnection { - docker: Docker, -} - -impl DockerConnection { - pub async fn new() -> Self { - DockerConnection { - docker: connect_to_docker(&Some(DEFAULT_DOCKER_HOST.to_owned())) - .await - .unwrap(), - } - } - - pub async fn get_image_with_defaults(&self, image_name: &str) -> Result<(), Error> { - self.docker - .create_image( - Some(CreateImageOptions { - from_image: image_name.to_string(), - ..Default::default() - }), - None, - None, - ) - .try_collect::>() - .await?; - - Ok(()) - } - - pub async fn get_container_with_defaults(&self, image_name: &str) -> Result { - let default_port_mappings = vec!["5001:5000", "9998:9998", "41000:41000"]; - // The port mapping in the bollard crate is formatted differently than the docker CLI. In the docker CLI, we usually specify exposed ports as `-p HOST_PORT:CONTAINER_PORT`. But with the bollard crate, it is expecting the port mapping to be a map of the container port (with the protocol) to the host port. - let mut port_mapping_hash = HashMap::new(); - for port_mapping in default_port_mappings { - let ports_vec: Vec<&str> = port_mapping.split(':').collect(); - let from_port = ports_vec[0]; - let to_port = ports_vec[1]; - - port_mapping_hash.insert( - format!("{to_port}/tcp"), - Some(vec![PortBinding { - host_ip: None, - host_port: Some(from_port.to_string()), - }]), - ); - } - - let container_elf_path = format!("{DEFAULT_APP_PATH}/stellarNanosApp.elf"); - let command_string = format!("/home/zondax/speculos/speculos.py --log-level speculos:DEBUG --color JADE_GREEN --display headless -s \"other base behind follow wet put glad muscle unlock sell income october\" -m nanos {container_elf_path}"); - let command_args = vec![command_string.as_str()]; - - let apps_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("apps"); - let volume_bind_string = format!("{}:/project/app/bin", apps_dir.display()); - - let bolos_sdk = format!("BOLOS_SDK={BOLOS_SDK}"); - let bolos_env = format!("BOLOS_ENV={BOLOS_ENV}"); - let display = format!("DISPLAY=host.docker.internal:0"); // TODO: this should be condiditional depending on os i think - let env_vars: Vec<&str> = vec![&bolos_sdk, &bolos_env, &display]; - - let config = Config { - image: Some(image_name), - tty: Some(true), - attach_stdout: Some(true), - attach_stderr: Some(true), - env: Some(env_vars), - host_config: Some(HostConfig { - auto_remove: Some(true), - port_bindings: Some(port_mapping_hash), - binds: Some(vec![volume_bind_string]), - ..Default::default() - }), - cmd: Some(command_args), - ..Default::default() - }; - - let create_container_response = self - .docker - .create_container( - Some(CreateContainerOptions { - name: "FIX_ME", - ..Default::default() - }), - config, - ) - .await?; - - Ok(create_container_response.id) - } - - pub async fn start_container_with_defaults( - &self, - container_response_id: &str, - ) -> Result<(), bollard::errors::Error> { - // deal with this error - self.docker - .start_container(container_response_id, None::>) - .await - } - - pub async fn stream_logs(&self, container_response_id: &str) { - let log_options = Some(LogsOptions:: { - follow: true, - stdout: true, - stderr: true, - ..Default::default() - }); - - let logs = self.docker.logs(container_response_id, log_options); - let logs = logs.try_collect::>().await; - println!("{logs:?}"); - } - - pub async fn stop_container(&self, container_response_id: &str) { - self.docker - .stop_container(container_response_id, None) - .await - .unwrap(); - } -} - -pub async fn connect_to_docker(docker_host: &Option) -> Result { - // if no docker_host is provided, use the default docker host: - // "unix:///var/run/docker.sock" on unix machines - // "npipe:////./pipe/docker_engine" on windows machines - - let host = docker_host - .clone() - .unwrap_or(DEFAULT_DOCKER_HOST.to_string()); - - // this is based on the `connect_with_defaults` method which has not yet been released in the bollard crate - // https://github.com/fussybeaver/bollard/blob/0972b1aac0ad5c08798e100319ddd0d2ee010365/src/docker.rs#L660 - let connection = match host.clone() { - // if tcp or http, use connect_with_http_defaults - // if unix and host starts with "unix://" use connect_with_unix - // if windows and host starts with "npipe://", use connect_with_named_pipe - // else default to connect_with_unix - h if h.starts_with("tcp://") || h.starts_with("http://") => { - Docker::connect_with_http_defaults() - } - #[cfg(unix)] - h if h.starts_with("unix://") => { - Docker::connect_with_unix(&h, DEFAULT_TIMEOUT, API_DEFAULT_VERSION) - } - #[cfg(windows)] - h if h.starts_with("npipe://") => { - Docker::connect_with_named_pipe(&h, DEFAULT_TIMEOUT, API_DEFAULT_VERSION) - } - _ => { - return Err(Error::UnsupportedURISchemeError { - uri: host.to_string(), - }); - } - }?; - - match check_docker_connection(&connection).await { - Ok(()) => Ok(connection), - // If we aren't able to connect with the defaults, or with the provided docker_host - // try to connect with the default docker desktop socket since that is a common use case for devs - #[allow(unused_variables)] - Err(e) => { - // if on unix, try to connect to the default docker desktop socket - #[cfg(unix)] - { - let docker_desktop_connection = try_docker_desktop_socket(&host)?; - match check_docker_connection(&docker_desktop_connection).await { - Ok(()) => Ok(docker_desktop_connection), - Err(err) => Err(err)?, - } - } - - #[cfg(windows)] - { - Err(e)? - } - } - } -} - -#[cfg(unix)] -fn try_docker_desktop_socket(host: &str) -> Result { - let default_docker_desktop_host = - format!("{}/.docker/run/docker.sock", home_dir().unwrap().display()); - println!("Failed to connect to DOCKER_HOST: {host}.\nTrying to connect to the default Docker Desktop socket at {default_docker_desktop_host}."); - - Docker::connect_with_unix( - &default_docker_desktop_host, - DEFAULT_TIMEOUT, - API_DEFAULT_VERSION, - ) -} - -// When bollard is not able to connect to the docker daemon, it returns a generic ConnectionRefused error -// This method attempts to connect to the docker daemon and returns a more specific error message -async fn check_docker_connection(docker: &Docker) -> Result<(), bollard::errors::Error> { - // This is a bit hacky, but the `client_addr` field is not directly accessible from the `Docker` struct, but we can access it from the debug string representation of the `Docker` struct - let docker_debug_string = format!("{docker:#?}"); - let start_of_client_addr = docker_debug_string.find("client_addr: ").unwrap(); - let end_of_client_addr = docker_debug_string[start_of_client_addr..] - .find(',') - .unwrap(); - // Extract the substring containing the value of client_addr - let client_addr = &docker_debug_string - [start_of_client_addr + "client_addr: ".len()..start_of_client_addr + end_of_client_addr] - .trim() - .trim_matches('"'); - - match docker.version().await { - Ok(_version) => Ok(()), - Err(err) => { - println!( - "⛔️ Failed to connect to the Docker daemon at {client_addr:?}. Is the docker daemon running?\nℹ️ Running a local Stellar network requires a Docker-compatible container runtime.\nℹ️ Please note that if you are using Docker Desktop, you may need to utilize the `--docker-host` flag to pass in the location of the docker socket on your machine.\n" - ); - Err(err) - } - } -} diff --git a/cmd/crates/stellar-ledger/src/emulator.rs b/cmd/crates/stellar-ledger/src/emulator.rs deleted file mode 100644 index f864720e2..000000000 --- a/cmd/crates/stellar-ledger/src/emulator.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::docker::DockerConnection; - -pub enum Error {} - -pub struct Emulator { - docker: DockerConnection, - container_id: Option, -} - -impl Emulator { - pub async fn new() -> Self { - let d = DockerConnection::new().await; - - Self { - docker: d, - container_id: None, - } - } - - pub async fn run(&mut self) -> Result<(), Error> { - let zondax_speculos_image = - "docker.io/zondax/builder-zemu:speculos-3a3439f6b45eca7f56395673caaf434c202e7005"; - self.docker - .get_image_with_defaults(zondax_speculos_image) - .await - .unwrap(); - - let container_id = self - .docker - .get_container_with_defaults(zondax_speculos_image) - .await - .unwrap(); - - self.container_id = Some(container_id.clone()); - - // This is starting up, but i think it fails pretty quickly, and i think we have it configured to delete itself once it starts. yep, when auto_remove is set to false, it sticks around but it exits right away - self.docker - .start_container_with_defaults(&container_id) - .await - .unwrap(); - - // self.docker.stream_logs(&container_id).await; - Ok(()) - } - - pub async fn stop(&self) -> Result<(), Error> { - if let Some(container_id) = &self.container_id { - self.docker.stop_container(container_id).await; - } - Ok(()) - } -} diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index bc6d6784f..667ce48c2 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -19,8 +19,6 @@ use stellar_xdr::curr::{ use crate::signer::{Error, Stellar}; use crate::transport_zemu_http::TransportZemuHttp; -mod docker; -mod emulator; mod signer; mod speculos; @@ -354,7 +352,6 @@ pub fn get_zemu_transport(host: &str, port: u16) -> Result Date: Mon, 22 Apr 2024 14:22:34 -0400 Subject: [PATCH 40/72] wip --- cmd/crates/stellar-ledger/src/lib.rs | 21 +++++++++++++++++++-- cmd/crates/stellar-ledger/src/speculos.rs | 6 +----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 667ce48c2..cc7e149fe 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -389,6 +389,7 @@ mod test { async fn test_get_public_key() { let docker = clients::Cli::default(); let node = docker.run(Speculos::new()); + sleep(Duration::from_secs(2)).await; let host_port = node.get_host_port_ipv4(9998); let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); @@ -408,7 +409,6 @@ mod test { assert_eq!(public_key_string, expected_public_key); } Err(e) => { - sleep(Duration::from_secs(10)).await; node.stop(); println!("{e}"); assert!(false); @@ -422,6 +422,8 @@ mod test { async fn test_get_app_configuration() { let docker = clients::Cli::default(); let node = docker.run(Speculos::new()); + + sleep(Duration::from_secs(1)).await; let host_port = node.get_host_port_ipv4(9998); let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); @@ -449,6 +451,8 @@ mod test { async fn test_sign_tx() { let docker = clients::Cli::default(); let node = docker.run(Speculos::new()); + + sleep(Duration::from_secs(1)).await; let host_port = node.get_host_port_ipv4(9998); let ui_host_port = node.get_host_port_ipv4(5000); @@ -541,6 +545,8 @@ mod test { //when hash signing isn't enabled on the device we expect an error let docker = clients::Cli::default(); let node = docker.run(Speculos::new()); + + sleep(Duration::from_secs(1)).await; let host_port = node.get_host_port_ipv4(9998); let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); @@ -571,11 +577,16 @@ mod test { //when hash signing isnt enabled on the device we expect an error let docker = clients::Cli::default(); let node = docker.run(Speculos::new()); + + sleep(Duration::from_secs(1)).await; + let host_port = node.get_host_port_ipv4(9998); let ui_host_port = node.get_host_port_ipv4(5000); enable_hash_signing(ui_host_port).await; + sleep(Duration::from_secs(2)).await; + let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); let ledger_options = Some(LedgerOptions { exchange: transport, @@ -622,7 +633,7 @@ mod test { // FIXME lol/sob async fn enable_hash_signing(ui_host_port: u16) { - println!("enabliing hash signing on the device"); + println!("enabling hash signing on the device"); let mut map = HashMap::new(); map.insert("action", "press-and-release"); @@ -634,6 +645,7 @@ mod test { .json(&map) .send() .await + .map_err(|e| println!("error in enable_hash_signing: {e}")) .unwrap(); // both button press @@ -642,6 +654,7 @@ mod test { .json(&map) .send() .await + .map_err(|e| println!("error in enable_hash_signing: {e}")) .unwrap(); // both button press @@ -650,6 +663,7 @@ mod test { .json(&map) .send() .await + .map_err(|e| println!("error in enable_hash_signing: {e}")) .unwrap(); // right button press @@ -658,6 +672,7 @@ mod test { .json(&map) .send() .await + .map_err(|e| println!("error in enable_hash_signing: {e}")) .unwrap(); // right button press @@ -666,6 +681,7 @@ mod test { .json(&map) .send() .await + .map_err(|e| println!("error in enable_hash_signing: {e}")) .unwrap(); // both button press @@ -674,6 +690,7 @@ mod test { .json(&map) .send() .await + .map_err(|e| println!("error in enable_hash_signing: {e}")) .unwrap(); } diff --git a/cmd/crates/stellar-ledger/src/speculos.rs b/cmd/crates/stellar-ledger/src/speculos.rs index 24faaf97a..9a82f4203 100644 --- a/cmd/crates/stellar-ledger/src/speculos.rs +++ b/cmd/crates/stellar-ledger/src/speculos.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, path::PathBuf, str::FromStr, time::Duration}; +use std::{collections::HashMap, path::PathBuf}; use testcontainers::{core::WaitFor, Image, ImageArgs}; const NAME: &str = "docker.io/zondax/builder-zemu"; @@ -63,10 +63,6 @@ impl Image for Speculos { TAG.to_owned() } - // fn expose_ports(&self) -> Vec { - // vec![5000, 9998, 41000] - // } - fn ready_conditions(&self) -> Vec { // vec![WaitFor::seconds(30)] vec![WaitFor::message_on_stdout("HTTP proxy started...")] From fd402fc78471af8b3c9957719a01d214aac12aec Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:50:12 -0400 Subject: [PATCH 41/72] Cleanup --- cmd/crates/stellar-ledger/src/lib.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index cc7e149fe..721dd6942 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -389,7 +389,7 @@ mod test { async fn test_get_public_key() { let docker = clients::Cli::default(); let node = docker.run(Speculos::new()); - sleep(Duration::from_secs(2)).await; + sleep(Duration::from_secs(1)).await; let host_port = node.get_host_port_ipv4(9998); let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); @@ -521,8 +521,6 @@ mod test { }); let approve = tokio::task::spawn(approve_tx_signature(ui_host_port)); - // sleep(Duration::from_secs(20)).await; - let result = sign.await.unwrap(); let _ = approve.await.unwrap(); @@ -585,8 +583,6 @@ mod test { enable_hash_signing(ui_host_port).await; - sleep(Duration::from_secs(2)).await; - let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); let ledger_options = Some(LedgerOptions { exchange: transport, @@ -603,7 +599,6 @@ mod test { ) { Ok(()) => {} Err(e) => { - sleep(Duration::from_secs(10)).await; node.stop(); panic!("Unexpected result: {e}"); } From 396bc1f68b8a067c6931ecbbd43fea5e326027b7 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:51:21 -0400 Subject: [PATCH 42/72] Checkin Cargo.lock --- Cargo.lock | 198 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 177 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1014b1903..a9b45f06b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,6 +202,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + [[package]] name = "base64ct" version = "1.6.0" @@ -249,19 +255,22 @@ dependencies = [ [[package]] name = "bollard" -version = "0.15.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03db470b3c0213c47e978da93200259a1eb4dae2e5512cba9955e2b540a6fc6" +checksum = "0aed08d3adb6ebe0eff737115056652670ae290f177759aac19c30456135f94c" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "bollard-stubs", "bytes", "futures-core", "futures-util", "hex", - "http 0.2.11", - "hyper", - "hyperlocal", + "http 1.0.0", + "http-body-util", + "hyper 1.3.1", + "hyper-named-pipe", + "hyper-util", + "hyperlocal-next", "log", "pin-project-lite", "serde", @@ -272,15 +281,16 @@ dependencies = [ "thiserror", "tokio", "tokio-util", + "tower-service", "url", "winapi", ] [[package]] name = "bollard-stubs" -version = "1.43.0-rc.2" +version = "1.44.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58071e8fd9ec1e930efd28e3a90c1251015872a2ce49f81f36421b86466932e" +checksum = "709d9aa1c37abb89d40f19f5d0ad6f0d88cb1581264e571c9350fc5bb89cf1c5" dependencies = [ "serde", "serde_repr", @@ -2175,6 +2185,29 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.0.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.0.0", + "http-body 1.0.0", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.8.0" @@ -2205,7 +2238,7 @@ dependencies = [ "futures-util", "h2", "http 0.2.11", - "http-body", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -2217,6 +2250,40 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper 1.3.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -2225,7 +2292,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.11", - "hyper", + "hyper 0.14.28", "log", "rustls 0.21.10", "rustls-native-certs", @@ -2240,23 +2307,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.28", "native-tls", "tokio", "tokio-native-tls", ] [[package]] -name = "hyperlocal" -version = "0.8.0" +name = "hyper-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ + "bytes", + "futures-channel", "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "hyper 1.3.1", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "hyperlocal-next" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf569d43fa9848e510358c07b80f4adf34084ddc28c6a4a651ee8474c070dcc" +dependencies = [ "hex", - "hyper", - "pin-project", + "http-body-util", + "hyper 1.3.1", + "hyper-util", + "pin-project-lite", "tokio", + "tower-service", ] [[package]] @@ -2421,7 +2510,7 @@ dependencies = [ "async-trait", "beef", "futures-util", - "hyper", + "hyper 0.14.28", "jsonrpsee-types", "serde", "serde_json", @@ -2437,7 +2526,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f80c17f62c7653ce767e3d7288b793dfec920f97067ceb189ebdd3570f2bc20" dependencies = [ "async-trait", - "hyper", + "hyper 0.14.28", "hyper-rustls", "jsonrpsee-core", "jsonrpsee-types", @@ -2903,6 +2992,48 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.4" @@ -3167,8 +3298,8 @@ dependencies = [ "futures-util", "h2", "http 0.2.11", - "http-body", - "hyper", + "http-body 0.4.6", + "hyper 0.14.28", "hyper-rustls", "hyper-tls", "ipnet", @@ -3711,6 +3842,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -3807,7 +3944,7 @@ dependencies = [ "hex", "home", "http 0.2.11", - "hyper", + "hyper 0.14.28", "hyper-tls", "itertools 0.10.5", "jsonrpsee-core", @@ -4207,6 +4344,7 @@ dependencies = [ "ledger-zondax-generic", "log", "once_cell", + "phf", "pretty_assertions", "reqwest", "sep5", @@ -4221,6 +4359,7 @@ dependencies = [ "soroban-spec", "stellar-strkey 0.0.7", "stellar-xdr", + "testcontainers", "thiserror", "tokio", "tracing", @@ -4455,6 +4594,22 @@ dependencies = [ "soroban-sdk", ] +[[package]] +name = "testcontainers" +version = "0.15.0" +source = "git+https://github.com/testcontainers/testcontainers-rs.git?rev=4b3e4f08a2c0bdf521636b03f959f004e6d216aa#4b3e4f08a2c0bdf521636b03f959f004e6d216aa" +dependencies = [ + "bollard-stubs", + "futures", + "hex", + "hmac 0.12.1", + "log", + "rand", + "serde", + "serde_json", + "sha2 0.10.8", +] + [[package]] name = "thiserror" version = "1.0.55" @@ -4680,6 +4835,7 @@ dependencies = [ "futures-util", "pin-project", "pin-project-lite", + "tokio", "tower-layer", "tower-service", "tracing", From 8b53aadcaf85bf35d417480a8e2c702f31388114 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:17:31 -0400 Subject: [PATCH 43/72] Clippy --- Cargo.lock | 1 - cmd/crates/stellar-ledger/Cargo.toml | 2 +- cmd/crates/stellar-ledger/src/lib.rs | 59 +++++++++++-------- cmd/crates/stellar-ledger/src/signer.rs | 18 +----- cmd/crates/stellar-ledger/src/speculos.rs | 19 +++--- .../stellar-ledger/src/transport_zemu_http.rs | 5 +- 6 files changed, 44 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 94fab2bc8..765392da3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4289,7 +4289,6 @@ dependencies = [ "ed25519-dalek 2.0.0", "env_logger", "futures", - "futures-util", "hex", "hidapi", "home", diff --git a/cmd/crates/stellar-ledger/Cargo.toml b/cmd/crates/stellar-ledger/Cargo.toml index 5972a0173..69764c7b0 100644 --- a/cmd/crates/stellar-ledger/Cargo.toml +++ b/cmd/crates/stellar-ledger/Cargo.toml @@ -29,13 +29,13 @@ hex.workspace = true byteorder = "1.5.0" bollard = { workspace=true } home = "0.5.9" -futures-util = "0.3.30" tokio = { version = "1", features = ["full"] } reqwest = { version = "0.11", features = ["json"]} soroban-rpc.workspace = true async-trait.workspace = true testcontainers = { git = "https://github.com/testcontainers/testcontainers-rs.git", rev = "4b3e4f08a2c0bdf521636b03f959f004e6d216aa" } phf = { version = "0.11.2", features = ["macros"] } +futures = "0.3.30" [dependencies.stellar-xdr] diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 721dd6942..bd25b0338 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -79,6 +79,9 @@ impl LedgerSigner where T: Exchange, { + /// Get the public key from the device + /// # Errors + /// Returns an error if there is an issue with connecting with the device or getting the public key from the device pub async fn get_public_key( &self, index: u32, @@ -87,6 +90,9 @@ where Self::get_public_key_with_display_flag(self, hd_path, false).await } + /// Get the device app's configuration + /// # Errors + /// Returns an error if there is an issue with connecting with the device or getting the config from the device pub async fn get_app_configuration(&self) -> Result, LedgerError> { let command = APDUCommand { cla: CLA, @@ -98,8 +104,11 @@ where self.send_command_to_ledger(command).await } - // based on impl from https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166 - async fn sign_transaction_hash( + /// Sign a Stellar transaction hash with the account on the Ledger device + /// based on impl from [https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166](https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L166) + /// # Errors + /// Returns an error if there is an issue with connecting with the device or signing the given tx on the device. Or, if the device has not enabled hash signing + pub async fn sign_transaction_hash( &self, hd_path: slip10::BIP32Path, transaction_hash: Vec, @@ -125,6 +134,10 @@ where self.send_command_to_ledger(command).await } + /// Sign a Stellar transaction with the account on the Ledger device + /// # Errors + /// Returns an error if there is an issue with connecting with the device or signing the given tx on the device + #[allow(clippy::missing_panics_doc)] // TODO: handle panics/unwraps pub async fn sign_transaction( &self, hd_path: slip10::BIP32Path, @@ -159,8 +172,6 @@ where let chunks_count = chunks.len(); let mut result = Vec::new(); - println!("chunks_count: {:?}", chunks_count); - // notes: // the first chunk has the hd_path_elements_count and the hd_path at the beginning, before the tx [3, 128...122...47] // the second chunk has just the end of the tx [224, 100... 0, 0, 0, 0] @@ -198,7 +209,7 @@ where Ok(result) } - /// The display_and_confirm bool determines if the Ledger will display the public key on its screen and requires user approval to share + /// The `display_and_confirm` bool determines if the Ledger will display the public key on its screen and requires user approval to share async fn get_public_key_with_display_flag( &self, hd_path: slip10::BIP32Path, @@ -210,8 +221,6 @@ where let hd_path_elements_count = hd_path.depth(); hd_path_to_bytes.insert(0, hd_path_elements_count); - println!("data: {:?}", hd_path_to_bytes); - let p2 = if display_and_confirm { P2_GET_PUBLIC_KEY_DISPLAY } else { @@ -227,15 +236,11 @@ where data: hd_path_to_bytes, }; - tracing::info!("APDU in: {}", hex::encode(&command.serialize())); + tracing::info!("APDU in: {}", hex::encode(command.serialize())); match self.send_command_to_ledger(command).await { - Ok(value) => { - return Ok(stellar_strkey::ed25519::PublicKey::from_payload(&value).unwrap()); - } - Err(err) => { - return Err(err); - } + Ok(value) => Ok(stellar_strkey::ed25519::PublicKey::from_payload(&value).unwrap()), + Err(err) => Err(err), } } @@ -251,21 +256,19 @@ where response.retcode(), ); // Ok means we successfully connected with the Ledger but it doesn't mean our request succeeded. We still need to check the response.retcode - println!("RETCODE: {:?}", response.retcode()); - println!("response: {:?}", response.data()); if response.retcode() == RETURN_CODE_OK { return Ok(response.data().to_vec()); } let retcode = response.retcode(); - let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); - return Err(LedgerError::APDUExchangeError(error_string)); + let error_string = format!("Ledger APDU retcode: 0x{retcode:X}"); + Err(LedgerError::APDUExchangeError(error_string)) } Err(_err) => { //FIX ME!!!! - return Err(LedgerError::LedgerConnectionError("test".to_string())); + Err(LedgerError::LedgerConnectionError("test".to_string())) } - }; + } } } @@ -289,7 +292,7 @@ impl Stellar for LedgerSigner { fn sign_txn_hash( &self, txn: [u8; 32], - source_account: &stellar_strkey::Strkey, + _source_account: &stellar_strkey::Strkey, ) -> Result { let signature = block_on(self.sign_transaction_hash(self.hd_path.clone(), txn.to_vec())) //TODO: refactor sign_transaction_hash .unwrap(); // FIXME: handle error @@ -304,7 +307,7 @@ impl Stellar for LedgerSigner { fn sign_txn( &self, txn: Transaction, - source_account: &stellar_strkey::Strkey, + _source_account: &stellar_strkey::Strkey, ) -> Result { let signature = block_on(self.sign_transaction(self.hd_path.clone(), txn.clone())).unwrap(); // FIXME: handle error @@ -330,20 +333,25 @@ fn bip_path_from_index(index: u32) -> slip10::BIP32Path { fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { (0..hd_path.depth()) - .map(|index| { + .flat_map(|index| { let value = *hd_path.index(index).unwrap(); value.to_be_bytes() }) - .flatten() .collect::>() } +/// Gets a transport connection for a ledger device +/// # Errors +/// Returns an error if there is an issue with connecting with the device pub fn get_transport() -> Result { // instantiate the connection to Ledger, this will return an error if Ledger is not connected let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) } +/// Gets a transport connection for a the Zemu emulator +/// # Errors +/// Returns an error if there is an issue with connecting with the device pub fn get_zemu_transport(host: &str, port: u16) -> Result { Ok(TransportZemuHttp::new(host, port)) } @@ -365,7 +373,7 @@ mod test { Memo, MuxedAccount, PaymentOp, Preconditions, SequenceNumber, TransactionExt, }; - use testcontainers::{clients, Container}; + use testcontainers::clients; use tokio::time::sleep; const TEST_NETWORK_PASSPHRASE: &str = "Test SDF Network ; September 2015"; @@ -381,7 +389,6 @@ mod test { }); let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); let public_key = ledger.get_public_key(0).await; - println!("{public_key:?}"); assert!(public_key.is_ok()); } diff --git a/cmd/crates/stellar-ledger/src/signer.rs b/cmd/crates/stellar-ledger/src/signer.rs index 12d037803..0da8a42f1 100644 --- a/cmd/crates/stellar-ledger/src/signer.rs +++ b/cmd/crates/stellar-ledger/src/signer.rs @@ -2,8 +2,7 @@ use ed25519_dalek::Signer; use ledger_transport::async_trait; use sha2::{Digest, Sha256}; use soroban_env_host::xdr::{ - self, DecoratedSignature, InvokeHostFunctionOp, Limits, Operation, OperationBody, Signature, - SignatureHint, SorobanAuthorizedFunction, Transaction, TransactionEnvelope, + self, DecoratedSignature, Limits, Signature, SignatureHint, Transaction, TransactionEnvelope, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, TransactionV1Envelope, WriteXdr, }; @@ -16,21 +15,6 @@ pub enum Error { RpcError(#[from] RpcError), } -fn requires_auth(txn: &Transaction) -> Option { - let [op @ Operation { - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }), - .. - }] = txn.operations.as_slice() - else { - return None; - }; - matches!( - auth.first().map(|x| &x.root_invocation.function), - Some(&SorobanAuthorizedFunction::ContractFn(_)) - ) - .then(move || op.clone()) -} - /// A trait for signing Stellar transactions and Soroban authorization entries #[async_trait] pub trait Stellar { diff --git a/cmd/crates/stellar-ledger/src/speculos.rs b/cmd/crates/stellar-ledger/src/speculos.rs index 9a82f4203..b2193175a 100644 --- a/cmd/crates/stellar-ledger/src/speculos.rs +++ b/cmd/crates/stellar-ledger/src/speculos.rs @@ -4,6 +4,7 @@ use testcontainers::{core::WaitFor, Image, ImageArgs}; const NAME: &str = "docker.io/zondax/builder-zemu"; const TAG: &str = "speculos-3a3439f6b45eca7f56395673caaf434c202e7005"; +#[allow(dead_code)] static ENV: &Map = &Map(phf::phf_map! { "BOLOS_SDK"=> "/project/deps/nanos-secure-sdk", "BOLOS_ENV" => "/opt/bolos", @@ -11,6 +12,7 @@ static ENV: &Map = &Map(phf::phf_map! { }); struct Map(phf::Map<&'static str, &'static str>); +#[allow(clippy::implicit_hasher)] impl From<&Map> for HashMap { fn from(Map(map): &Map) -> Self { map.into_iter() @@ -20,13 +22,10 @@ impl From<&Map> for HashMap { } #[derive(Debug, Default)] -pub struct Speculos( - HashMap, - HashMap, - Vec, -); +pub struct Speculos(HashMap, HashMap); const DEFAULT_APP_PATH: &str = "/project/app/bin"; impl Speculos { + #[allow(dead_code)] pub fn new() -> Self { #[allow(unused_mut)] let apps_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("apps"); @@ -35,16 +34,14 @@ impl Speculos { apps_dir.to_str().unwrap().to_string(), DEFAULT_APP_PATH.to_string(), ); - let container_elf_path = format!("{DEFAULT_APP_PATH}/stellarNanosApp.elf"); - let command_string = format!("/home/zondax/speculos/speculos.py --log-level speculos:DEBUG --color JADE_GREEN --display headless -s \"other base behind follow wet put glad muscle unlock sell income october\" -m nanos {container_elf_path}"); - Speculos(ENV.into(), volumes, vec![command_string]) + Speculos(ENV.into(), volumes) } } #[derive(Debug, Default, Clone)] -pub struct SpeculosArgs; +pub struct Args; -impl ImageArgs for SpeculosArgs { +impl ImageArgs for Args { fn into_iterator(self) -> Box> { let container_elf_path = format!("{DEFAULT_APP_PATH}/stellarNanosApp.elf"); let command_string = format!("/home/zondax/speculos/speculos.py --log-level speculos:DEBUG --color JADE_GREEN --display headless -s \"other base behind follow wet put glad muscle unlock sell income october\" -m nanos {container_elf_path}"); @@ -53,7 +50,7 @@ impl ImageArgs for SpeculosArgs { } impl Image for Speculos { - type Args = SpeculosArgs; + type Args = Args; fn name(&self) -> String { NAME.to_owned() diff --git a/cmd/crates/stellar-ledger/src/transport_zemu_http.rs b/cmd/crates/stellar-ledger/src/transport_zemu_http.rs index 259b3d706..9a05e37f0 100644 --- a/cmd/crates/stellar-ledger/src/transport_zemu_http.rs +++ b/cmd/crates/stellar-ledger/src/transport_zemu_http.rs @@ -42,9 +42,6 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum LedgerZemuError { - /// Device not found error - #[error("Ledger connect error")] - ConnectError, /// zemu reponse error #[error("Zemu response error")] ResponseError, @@ -72,7 +69,7 @@ struct ZemuResponse { impl TransportZemuHttp { pub fn new(host: &str, port: u16) -> Self { Self { - url: format!("http://{}:{}", host, port), + url: format!("http://{host}:{port}"), } } } From 868b9d6750f62223f04f7bce89200f3a47ed7ae3 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 24 Apr 2024 16:42:59 -0400 Subject: [PATCH 44/72] Make sure that the ledger's ready text is displayed before continuing with tests --- cmd/crates/stellar-ledger/src/lib.rs | 77 +++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index bd25b0338..13d2485d1 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -358,6 +358,7 @@ pub fn get_zemu_transport(host: &str, port: u16) -> Result, + } + + async fn wait_for_emulator_start_text(ui_host_port: u16) { + sleep(Duration::from_secs(1)).await; + + let mut ready = false; + while !ready { + let events = get_emulator_events(ui_host_port).await; + + if events.iter().any(|event| event.text == "is ready") { + ready = true; + } + } + } + + async fn get_emulator_events(ui_host_port: u16) -> Vec { + let client = reqwest::Client::new(); + let resp = client + .get(format!("http://localhost:{ui_host_port}/events")) + .send() + .await + .unwrap() + .json::() + .await + .unwrap(); // not worrying about unwraps for test helpers for now + resp.events + } + async fn approve_tx_hash_signature(ui_host_port: u16) { println!("approving tx hash sig on the device"); let mut map = HashMap::new(); From bb6c4ab30fcf37199f9ce1d60ac99e2762b3b804 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 24 Apr 2024 17:45:08 -0400 Subject: [PATCH 45/72] Try waiting until the screen has changed before sending another button req --- cmd/crates/stellar-ledger/src/lib.rs | 104 +++++++++------------------ 1 file changed, 35 insertions(+), 69 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 13d2485d1..e09545b80 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -466,9 +466,6 @@ mod test { let ui_host_port: u16 = node.get_host_port_ipv4(5000); wait_for_emulator_start_text(ui_host_port).await; - // wait for emulator to load - check the events - // sleep to account for key delay - // for some things, waiting for the screen to change... but prob dont need that for this let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); let ledger_options = Some(LedgerOptions { @@ -597,10 +594,6 @@ mod test { let ui_host_port: u16 = node.get_host_port_ipv4(5000); wait_for_emulator_start_text(ui_host_port).await; - // wait for emulator to load - check the events - // sleep to account for key delay - // for some things, waiting for the screen to change... but prob dont need that for this - enable_hash_signing(ui_host_port).await; let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); @@ -646,70 +639,57 @@ mod test { node.stop(); } - // FIXME lol/sob - async fn enable_hash_signing(ui_host_port: u16) { - println!("enabling hash signing on the device"); - - let mut map = HashMap::new(); - map.insert("action", "press-and-release"); + // Based on the zemu click fn + async fn click(ui_host_port: u16, url: &str) { + let previous_events = get_emulator_events(ui_host_port).await; let client = reqwest::Client::new(); - // right button press + let mut payload = HashMap::new(); + payload.insert("action", "press-and-release"); + + let mut screen_has_changed = false; + client - .post(format!("http://localhost:{ui_host_port}/button/right")) - .json(&map) + .post(format!("http://localhost:{ui_host_port}/{url}")) + .json(&payload) .send() .await - .map_err(|e| println!("error in enable_hash_signing: {e}")) .unwrap(); + while !screen_has_changed { + let current_events = get_emulator_events(ui_host_port).await; + + if !(previous_events == current_events) { + screen_has_changed = true + } + } + + sleep(Duration::from_secs(1)).await; + } + + async fn enable_hash_signing(ui_host_port: u16) { + println!("enabling hash signing on the device"); + + // right button press + click(ui_host_port, "button/right").await; + // both button press - client - .post(format!("http://localhost:{ui_host_port}/button/both")) - .json(&map) - .send() - .await - .map_err(|e| println!("error in enable_hash_signing: {e}")) - .unwrap(); + click(ui_host_port, "button/both").await; // both button press - client - .post(format!("http://localhost:{ui_host_port}/button/both")) - .json(&map) - .send() - .await - .map_err(|e| println!("error in enable_hash_signing: {e}")) - .unwrap(); + click(ui_host_port, "button/both").await; // right button press - client - .post(format!("http://localhost:{ui_host_port}/button/right")) - .json(&map) - .send() - .await - .map_err(|e| println!("error in enable_hash_signing: {e}")) - .unwrap(); + click(ui_host_port, "button/right").await; // right button press - client - .post(format!("http://localhost:{ui_host_port}/button/right")) - .json(&map) - .send() - .await - .map_err(|e| println!("error in enable_hash_signing: {e}")) - .unwrap(); + click(ui_host_port, "button/right").await; // both button press - client - .post(format!("http://localhost:{ui_host_port}/button/both")) - .json(&map) - .send() - .await - .map_err(|e| println!("error in enable_hash_signing: {e}")) - .unwrap(); + click(ui_host_port, "button/both").await; } - #[derive(Debug, Deserialize)] + #[derive(Debug, Deserialize, PartialEq)] struct EmulatorEvent { text: String, x: u16, @@ -751,27 +731,13 @@ mod test { async fn approve_tx_hash_signature(ui_host_port: u16) { println!("approving tx hash sig on the device"); - let mut map = HashMap::new(); - map.insert("action", "press-and-release"); - - let client = reqwest::Client::new(); // press the right button 10 times for _ in 0..10 { - client - .post(format!("http://localhost:{ui_host_port}/button/right")) - .json(&map) - .send() - .await - .unwrap(); + click(ui_host_port, "button/right").await; } // press both buttons - client - .post(format!("http://localhost:{ui_host_port}/button/both")) - .json(&map) - .send() - .await - .unwrap(); + click(ui_host_port, "button/both").await; } async fn approve_tx_signature(ui_host_port: u16) { From fb773e74a4ed61c3037be1b2c871462e0650ce3c Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Wed, 24 Apr 2024 02:45:43 +1000 Subject: [PATCH 46/72] Fix issue with cc 1.0.86 and macos builds and build binaries on all (#1287) * Fix issue with cc 1.0.86 and macos builds * fix * fix * update to 1.0.88 * try 1.0.95 --- .github/workflows/binaries.yml | 73 ++++++++++++++++++++++++++++++++++ .github/workflows/publish.yml | 58 --------------------------- 2 files changed, 73 insertions(+), 58 deletions(-) create mode 100644 .github/workflows/binaries.yml diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml new file mode 100644 index 000000000..9afb1a8d6 --- /dev/null +++ b/.github/workflows/binaries.yml @@ -0,0 +1,73 @@ +name: Binaries + +on: + release: + types: [published] + pull_request: + +defaults: + run: + shell: bash + +jobs: + + build: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-20.04 # Use 20.04 to get an older version of glibc for increased compat + target: x86_64-unknown-linux-gnu + - os: ubuntu-20.04 # Use 20.04 to get an older version of glibc for increased compat + target: aarch64-unknown-linux-gnu + - os: macos-latest + target: x86_64-apple-darwin + - os: macos-latest + target: aarch64-apple-darwin + - os: windows-latest + target: x86_64-pc-windows-msvc + ext: .exe + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - run: rustup update + - run: rustup target add ${{ matrix.target }} + - if: matrix.target == 'aarch64-unknown-linux-gnu' + run: sudo apt-get update && sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + - name: Setup Version and Name + run: | + version="$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[] | select(.name == "soroban-cli") | .version')" + echo "VERSION=${version}" >> $GITHUB_ENV + echo "NAME=soroban-cli-${version}-${{ matrix.target }}" >> $GITHUB_ENV + - name: Package + run: cargo package --no-verify + - name: Build + env: + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc + run: | + cd target/package + tar xvfz soroban-cli-$VERSION.crate + cd soroban-cli-$VERSION + cargo build --target-dir=../.. --features opt --release --target ${{ matrix.target }} + - name: Compress + run: | + cd target/${{ matrix.target }}/release + tar czvf $NAME.tar.gz soroban${{ matrix.ext }} + - name: Upload to Artifacts + uses: actions/upload-artifact@v3 + with: + name: ${{ env.NAME }} + path: 'target/${{ matrix.target }}/release/${{ env.NAME }}.tar.gz' + - name: Upload to Release + if: github.event_name == 'release' + uses: actions/github-script@v6 + with: + script: | + const fs = require('fs'); + await github.rest.repos.uploadReleaseAsset({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: ${{ github.event.release.id }}, + name: '${{ env.NAME }}.tar.gz', + data: fs.readFileSync('target/${{ matrix.target }}/release/${{ env.NAME }}.tar.gz'), + }); diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b51a48523..744dc76b2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,61 +14,3 @@ jobs: uses: stellar/actions/.github/workflows/rust-publish.yml@main secrets: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - - upload: - needs: publish - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-20.04 # Use 20.04 to get an older version of glibc for increased compat - target: x86_64-unknown-linux-gnu - - os: ubuntu-20.04 # Use 20.04 to get an older version of glibc for increased compat - target: aarch64-unknown-linux-gnu - - os: macos-latest - target: x86_64-apple-darwin - - os: macos-latest - target: aarch64-apple-darwin - - os: windows-latest - target: x86_64-pc-windows-msvc - ext: .exe - runs-on: ${{ matrix.os }} - env: - VERSION: '${{ github.event.release.name }}' - NAME: 'soroban-cli-${{ github.event.release.name }}-${{ matrix.target }}' - steps: - - uses: actions/checkout@v3 - - run: rustup update - - run: rustup target add ${{ matrix.target }} - - if: matrix.target == 'aarch64-unknown-linux-gnu' - run: sudo apt-get update && sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu - - name: Package - run: cargo package --no-verify - - name: Build - env: - CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc - run: | - cd target/package - tar xvfz soroban-cli-$VERSION.crate - cd soroban-cli-$VERSION - cargo build --target-dir=../.. --features opt --release --target ${{ matrix.target }} - - name: Compress - run: | - cd target/${{ matrix.target }}/release - tar czvf $NAME.tar.gz soroban${{ matrix.ext }} - - uses: actions/upload-artifact@v3 - with: - name: ${{ env.NAME }} - path: 'target/${{ matrix.target }}/release/${{ env.NAME }}.tar.gz' - - name: Upload - uses: actions/github-script@v6 - with: - script: | - const fs = require('fs'); - await github.rest.repos.uploadReleaseAsset({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: ${{ github.event.release.id }}, - name: '${{ env.NAME }}.tar.gz', - data: fs.readFileSync('target/${{ matrix.target }}/release/${{ env.NAME }}.tar.gz'), - }); From 11961ae1f2e2246e75d2612e93c439844332ca2e Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 25 Apr 2024 09:01:23 -0400 Subject: [PATCH 47/72] Cleanup --- cmd/crates/stellar-ledger/Cargo.toml | 1 - cmd/crates/stellar-ledger/src/lib.rs | 2 - cmd/crates/stellar-ledger/src/signer.rs | 63 +---------------------- cmd/crates/stellar-ledger/src/speculos.rs | 1 - 4 files changed, 1 insertion(+), 66 deletions(-) diff --git a/cmd/crates/stellar-ledger/Cargo.toml b/cmd/crates/stellar-ledger/Cargo.toml index 69764c7b0..b277dce01 100644 --- a/cmd/crates/stellar-ledger/Cargo.toml +++ b/cmd/crates/stellar-ledger/Cargo.toml @@ -32,7 +32,6 @@ home = "0.5.9" tokio = { version = "1", features = ["full"] } reqwest = { version = "0.11", features = ["json"]} soroban-rpc.workspace = true -async-trait.workspace = true testcontainers = { git = "https://github.com/testcontainers/testcontainers-rs.git", rev = "4b3e4f08a2c0bdf521636b03f959f004e6d216aa" } phf = { version = "0.11.2", features = ["macros"] } futures = "0.3.30" diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index e09545b80..0af551646 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -1,5 +1,4 @@ mod transport_zemu_http; -use async_trait::async_trait; use futures::executor::block_on; use ledger_transport::{APDUCommand, Exchange}; use ledger_transport_hid::{ @@ -272,7 +271,6 @@ where } } -#[async_trait] impl Stellar for LedgerSigner { type Init = LedgerOptions; diff --git a/cmd/crates/stellar-ledger/src/signer.rs b/cmd/crates/stellar-ledger/src/signer.rs index 0da8a42f1..e403c19ec 100644 --- a/cmd/crates/stellar-ledger/src/signer.rs +++ b/cmd/crates/stellar-ledger/src/signer.rs @@ -1,8 +1,6 @@ -use ed25519_dalek::Signer; -use ledger_transport::async_trait; use sha2::{Digest, Sha256}; use soroban_env_host::xdr::{ - self, DecoratedSignature, Limits, Signature, SignatureHint, Transaction, TransactionEnvelope, + self, DecoratedSignature, Limits, Transaction, TransactionEnvelope, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, TransactionV1Envelope, WriteXdr, }; @@ -16,7 +14,6 @@ pub enum Error { } /// A trait for signing Stellar transactions and Soroban authorization entries -#[async_trait] pub trait Stellar { /// The type of the options that can be passed when creating a new signer type Init; @@ -56,61 +53,3 @@ pub trait Stellar { })) } } -struct DefaultSigner { - network_passphrase: String, - keypairs: Vec, -} - -impl DefaultSigner { - pub fn get_key( - &self, - key: &stellar_strkey::Strkey, - ) -> Result<&ed25519_dalek::SigningKey, Error> { - match key { - stellar_strkey::Strkey::PublicKeyEd25519(stellar_strkey::ed25519::PublicKey(bytes)) => { - self.keypairs - .iter() - .find(|k| k.verifying_key().to_bytes() == *bytes) - } - _ => None, - } - .ok_or_else(|| { - Error::RpcError(RpcError::MissingSignerForAddress { - address: key.to_string(), - }) - }) - } -} - -#[async_trait] -impl Stellar for DefaultSigner { - type Init = Vec; - fn new(network_passphrase: &str, options: Option>) -> Self { - DefaultSigner { - network_passphrase: network_passphrase.to_string(), - keypairs: options.unwrap_or_default(), - } - } - - fn sign_txn_hash( - &self, - txn: [u8; 32], - source_account: &stellar_strkey::Strkey, - ) -> Result { - let source_account = self.get_key(source_account)?; - let tx_signature = source_account.sign(&txn); - Ok(DecoratedSignature { - // TODO: remove this unwrap. It's safe because we know the length of the array - hint: SignatureHint( - source_account.verifying_key().to_bytes()[28..] - .try_into() - .unwrap(), - ), - signature: Signature(tx_signature.to_bytes().try_into().unwrap()), //FIXME: remove unwrap - }) - } - - fn network_hash(&self) -> xdr::Hash { - xdr::Hash(Sha256::digest(self.network_passphrase.as_bytes()).into()) - } -} diff --git a/cmd/crates/stellar-ledger/src/speculos.rs b/cmd/crates/stellar-ledger/src/speculos.rs index b2193175a..13d154211 100644 --- a/cmd/crates/stellar-ledger/src/speculos.rs +++ b/cmd/crates/stellar-ledger/src/speculos.rs @@ -61,7 +61,6 @@ impl Image for Speculos { } fn ready_conditions(&self) -> Vec { - // vec![WaitFor::seconds(30)] vec![WaitFor::message_on_stdout("HTTP proxy started...")] } From 25be1e608501925278b268756d2bfaaeab3249c4 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Fri, 26 Apr 2024 10:46:12 -0400 Subject: [PATCH 48/72] Apply suggestions from code review Co-authored-by: Willem Wyndham --- cmd/crates/stellar-ledger/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 0af551646..f7a985851 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -145,8 +145,7 @@ where let tagged_transaction = TransactionSignaturePayloadTaggedTransaction::Tx(transaction.clone()); - let passphrase = self.network_passphrase.clone(); - let network_hash = Hash(Sha256::digest(passphrase.as_bytes()).into()); + let network_hash = self.network_hash(); let signature_payload = TransactionSignaturePayload { network_id: network_hash, From 3fb067f34230e4d73515b99e3ba21800db916339 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 29 Apr 2024 09:57:31 -0400 Subject: [PATCH 49/72] Address PR feedback --- Cargo.lock | 1 - cmd/crates/stellar-ledger/src/lib.rs | 40 ++++++++++++---------------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 765392da3..19f4cc656 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4283,7 +4283,6 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" name = "stellar-ledger" version = "20.3.4" dependencies = [ - "async-trait", "bollard", "byteorder", "ed25519-dalek 2.0.0", diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index f7a985851..e07031d57 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -23,8 +23,13 @@ mod speculos; // this is from https://github.com/LedgerHQ/ledger-live/blob/36cfbf3fa3300fd99bcee2ab72e1fd8f280e6280/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L181 const APDU_MAX_SIZE: u8 = 150; +const HD_PATH_ELEMENTS_COUNT: u8 = 3; +const BUFFER_SIZE: u8 = 1 + HD_PATH_ELEMENTS_COUNT * 4; +const CHUNK_SIZE: u8 = APDU_MAX_SIZE - BUFFER_SIZE; + +// These constant values are from https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md +const SIGN_TX_RESPONSE_SIZE: usize = 64; -// these constant values are from https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md const CLA: u8 = 0xE0; const GET_PUBLIC_KEY: u8 = 0x02; @@ -112,14 +117,13 @@ where hd_path: slip10::BIP32Path, transaction_hash: Vec, ) -> Result, LedgerError> { - // convert the hd_path into bytes to be sent as `data` to the Ledger - // the first element of the data should be the number of elements in the path - let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); - let hd_path_elements_count = hd_path.depth(); - hd_path_to_bytes.insert(0, hd_path_elements_count); - let mut data = hd_path_to_bytes; + let capacity = 1 + hd_path_to_bytes.len() + transaction_hash.len(); + let mut data: Vec = Vec::with_capacity(capacity); + + data.insert(0, HD_PATH_ELEMENTS_COUNT); + data.append(&mut hd_path_to_bytes); data.append(&mut transaction_hash.clone()); let command = APDUCommand { @@ -144,36 +148,26 @@ where ) -> Result, LedgerError> { let tagged_transaction = TransactionSignaturePayloadTaggedTransaction::Tx(transaction.clone()); - let network_hash = self.network_hash(); - let signature_payload = TransactionSignaturePayload { network_id: network_hash, tagged_transaction, }; - let mut signature_payload_as_bytes = signature_payload.to_xdr(Limits::none()).unwrap(); - let mut data: Vec = Vec::new(); - let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path); - let hd_path_elements_count = hd_path.depth(); - data.insert(0, hd_path_elements_count); + let capacity = 1 + hd_path_to_bytes.len() + signature_payload_as_bytes.len(); + let mut data: Vec = Vec::with_capacity(capacity); + + data.insert(0, HD_PATH_ELEMENTS_COUNT); data.append(&mut hd_path_to_bytes); data.append(&mut signature_payload_as_bytes); - let buffer_size = 1 + hd_path_elements_count * 4; - let chunk_size = APDU_MAX_SIZE - buffer_size; - - let chunks = data.chunks(chunk_size as usize); + let chunks = data.chunks(CHUNK_SIZE as usize); let chunks_count = chunks.len(); - let mut result = Vec::new(); - // notes: - // the first chunk has the hd_path_elements_count and the hd_path at the beginning, before the tx [3, 128...122...47] - // the second chunk has just the end of the tx [224, 100... 0, 0, 0, 0] - + let mut result = Vec::with_capacity(SIGN_TX_RESPONSE_SIZE); for (i, chunk) in chunks.enumerate() { let is_first_chunk = i == 0; let is_last_chunk = chunks_count == i + 1; From bf6a8704e5c5236d245e9a39f03ad091f4e701ba Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 29 Apr 2024 11:50:08 -0400 Subject: [PATCH 50/72] Separate emulator-dependent tests --- cmd/crates/stellar-ledger/Cargo.toml | 3 + .../stellar-ledger/src/emulator_tests.rs | 421 ++++++++++++++++++ cmd/crates/stellar-ledger/src/lib.rs | 415 +---------------- 3 files changed, 428 insertions(+), 411 deletions(-) create mode 100644 cmd/crates/stellar-ledger/src/emulator_tests.rs diff --git a/cmd/crates/stellar-ledger/Cargo.toml b/cmd/crates/stellar-ledger/Cargo.toml index b277dce01..91326fc61 100644 --- a/cmd/crates/stellar-ledger/Cargo.toml +++ b/cmd/crates/stellar-ledger/Cargo.toml @@ -50,3 +50,6 @@ log = "0.4.21" once_cell = "1.19.0" pretty_assertions = "1.2.1" serial_test = "3.0.0" + +[features] +emulator-tests = [] \ No newline at end of file diff --git a/cmd/crates/stellar-ledger/src/emulator_tests.rs b/cmd/crates/stellar-ledger/src/emulator_tests.rs new file mode 100644 index 000000000..3ebe7825a --- /dev/null +++ b/cmd/crates/stellar-ledger/src/emulator_tests.rs @@ -0,0 +1,421 @@ +use ledger_transport::{APDUCommand, Exchange}; +use ledger_transport_hid::{ + hidapi::{HidApi, HidError}, + LedgerHIDError, TransportNativeHID, +}; +use sha2::{Digest, Sha256}; + +use soroban_env_host::xdr::{Hash, Transaction}; +use std::vec; + +use crate::signer::{Error, Stellar}; +use crate::transport_zemu_http::TransportZemuHttp; + +#[cfg(feature = "emulator-tests")] +mod emulator_tests { + use serde::Deserialize; + use soroban_env_host::xdr::{self, Operation, OperationBody, Transaction, Uint256}; + + use crate::speculos::Speculos; + use crate::{get_zemu_transport, LedgerError, LedgerOptions, LedgerSigner}; + + use std::sync::Arc; + use std::{collections::HashMap, str::FromStr, time::Duration}; + + use super::*; + + use stellar_xdr::curr::{ + Memo, MuxedAccount, PaymentOp, Preconditions, SequenceNumber, TransactionExt, + }; + + use testcontainers::clients; + use tokio::time::sleep; + + const TEST_NETWORK_PASSPHRASE: &str = "Test SDF Network ; September 2015"; + + // #[ignore] + // #[tokio::test] + // #[serial] + // async fn test_get_public_key_with_ledger_device() { + // let transport = get_transport().unwrap(); + // let ledger_options = Some(LedgerOptions { + // exchange: transport, + // hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + // }); + // let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + // let public_key = ledger.get_public_key(0).await; + // assert!(public_key.is_ok()); + // } + + #[tokio::test] + async fn test_get_public_key() { + let docker = clients::Cli::default(); + let node = docker.run(Speculos::new()); + let host_port = node.get_host_port_ipv4(9998); + let ui_host_port: u16 = node.get_host_port_ipv4(5000); + + wait_for_emulator_start_text(ui_host_port).await; + + let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + + match ledger.get_public_key(0).await { + Ok(public_key) => { + let public_key_string = public_key.to_string(); + // This is determined by the seed phrase used to start up the emulator + // TODO: make the seed phrase configurable + let expected_public_key = + "GDUTHCF37UX32EMANXIL2WOOVEDZ47GHBTT3DYKU6EKM37SOIZXM2FN7"; + assert_eq!(public_key_string, expected_public_key); + } + Err(e) => { + node.stop(); + println!("{e}"); + assert!(false); + } + } + + node.stop(); + } + + #[tokio::test] + async fn test_get_app_configuration() { + let docker = clients::Cli::default(); + let node = docker.run(Speculos::new()); + let host_port = node.get_host_port_ipv4(9998); + let ui_host_port: u16 = node.get_host_port_ipv4(5000); + + wait_for_emulator_start_text(ui_host_port).await; + + let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + + match ledger.get_app_configuration().await { + Ok(config) => { + assert_eq!(config, vec![0, 5, 0, 3]); + } + Err(e) => { + node.stop(); + println!("{e}"); + assert!(false); + } + }; + + node.stop(); + } + + #[tokio::test] + async fn test_sign_tx() { + let docker = clients::Cli::default(); + let node = docker.run(Speculos::new()); + let host_port = node.get_host_port_ipv4(9998); + let ui_host_port: u16 = node.get_host_port_ipv4(5000); + + wait_for_emulator_start_text(ui_host_port).await; + + let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = Arc::new(LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options)); + + let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); + + let source_account_str = "GAQNVGMLOXSCWH37QXIHLQJH6WZENXYSVWLPAEF4673W64VRNZLRHMFM"; + let source_account_bytes = match stellar_strkey::Strkey::from_string(source_account_str) { + Ok(key) => match key { + stellar_strkey::Strkey::PublicKeyEd25519(p) => p.0, + _ => { + eprintln!("Error decoding public key: {:?}", key); + return; + } + }, + Err(err) => { + eprintln!("Error decoding public key: {}", err); + return; + } + }; + + let destination_account_str = "GCKUD4BHIYSAYHU7HBB5FDSW6CSYH3GSOUBPWD2KE7KNBERP4BSKEJDV"; + let destination_account_bytes = + match stellar_strkey::Strkey::from_string(destination_account_str) { + Ok(key) => match key { + stellar_strkey::Strkey::PublicKeyEd25519(p) => p.0, + _ => { + eprintln!("Error decoding public key: {:?}", key); + return; + } + }, + Err(err) => { + eprintln!("Error decoding public key: {}", err); + return; + } + }; + + let tx = Transaction { + source_account: MuxedAccount::Ed25519(Uint256(source_account_bytes)), // was struggling to create a real account in this way with the G... address + fee: 100, + seq_num: SequenceNumber(1), + cond: Preconditions::None, + memo: Memo::Text("Stellar".as_bytes().try_into().unwrap()), + ext: TransactionExt::V0, + operations: [Operation { + source_account: Some(MuxedAccount::Ed25519(Uint256(source_account_bytes))), + body: OperationBody::Payment(PaymentOp { + destination: MuxedAccount::Ed25519(Uint256(destination_account_bytes)), + asset: xdr::Asset::Native, + amount: 100, + }), + }] + .try_into() + .unwrap(), + }; + + let sign = tokio::task::spawn({ + let ledger = Arc::clone(&ledger); + async move { ledger.sign_transaction(path, tx).await } + }); + let approve = tokio::task::spawn(approve_tx_signature(ui_host_port)); + + let result = sign.await.unwrap(); + let _ = approve.await.unwrap(); + + match result { + Ok(response) => { + assert_eq!( hex::encode(response), "5c2f8eb41e11ab922800071990a25cf9713cc6e7c43e50e0780ddc4c0c6da50c784609ef14c528a12f520d8ea9343b49083f59c51e3f28af8c62b3edeaade60e"); + } + Err(e) => { + node.stop(); + println!("{e}"); + assert!(false); + } + }; + + node.stop(); + } + + #[tokio::test] + async fn test_sign_tx_hash_when_hash_signing_is_not_enabled() { + //when hash signing isn't enabled on the device we expect an error + let docker = clients::Cli::default(); + let node = docker.run(Speculos::new()); + let host_port = node.get_host_port_ipv4(9998); + let ui_host_port: u16 = node.get_host_port_ipv4(5000); + + wait_for_emulator_start_text(ui_host_port).await; + // wait for emulator to load - check the events + // sleep to account for key delay + // for some things, waiting for the screen to change... but prob dont need that for this + + let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + + let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); + let test_hash = + "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889".as_bytes(); + + let result = ledger.sign_transaction_hash(path, test_hash.into()).await; + if let Err(LedgerError::APDUExchangeError(msg)) = result { + assert_eq!(msg, "Ledger APDU retcode: 0x6C66"); + // this error code is SW_TX_HASH_SIGNING_MODE_NOT_ENABLED https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md + } else { + node.stop(); + panic!("Unexpected result: {:?}", result); + } + + node.stop(); + } + + #[tokio::test] + async fn test_sign_tx_hash_when_hash_signing_is_enabled() { + //when hash signing isnt enabled on the device we expect an error + let docker = clients::Cli::default(); + let node = docker.run(Speculos::new()); + let host_port = node.get_host_port_ipv4(9998); + let ui_host_port: u16 = node.get_host_port_ipv4(5000); + + wait_for_emulator_start_text(ui_host_port).await; + enable_hash_signing(ui_host_port).await; + + let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = Arc::new(LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options)); + + let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); + let mut test_hash = vec![0u8; 32]; + + match hex::decode_to_slice( + "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889", + &mut test_hash as &mut [u8], + ) { + Ok(()) => {} + Err(e) => { + node.stop(); + panic!("Unexpected result: {e}"); + } + } + + let sign = tokio::task::spawn({ + let ledger = Arc::clone(&ledger); + async move { ledger.sign_transaction_hash(path, test_hash).await } + }); + let approve = tokio::task::spawn(approve_tx_hash_signature(ui_host_port)); + + let result = sign.await.unwrap(); + let _ = approve.await.unwrap(); + + match result { + Ok(result) => { + println!("this is the response from signing the hash: {result:?}"); + } + Err(e) => { + node.stop(); + panic!("Unexpected result: {e}"); + } + } + + node.stop(); + } + + // Based on the zemu click fn + async fn click(ui_host_port: u16, url: &str) { + let previous_events = get_emulator_events(ui_host_port).await; + + let client = reqwest::Client::new(); + let mut payload = HashMap::new(); + payload.insert("action", "press-and-release"); + + let mut screen_has_changed = false; + + client + .post(format!("http://localhost:{ui_host_port}/{url}")) + .json(&payload) + .send() + .await + .unwrap(); + + while !screen_has_changed { + let current_events = get_emulator_events(ui_host_port).await; + + if !(previous_events == current_events) { + screen_has_changed = true + } + } + + sleep(Duration::from_secs(1)).await; + } + + async fn enable_hash_signing(ui_host_port: u16) { + println!("enabling hash signing on the device"); + + // right button press + click(ui_host_port, "button/right").await; + + // both button press + click(ui_host_port, "button/both").await; + + // both button press + click(ui_host_port, "button/both").await; + + // right button press + click(ui_host_port, "button/right").await; + + // right button press + click(ui_host_port, "button/right").await; + + // both button press + click(ui_host_port, "button/both").await; + } + + #[derive(Debug, Deserialize, PartialEq)] + struct EmulatorEvent { + text: String, + x: u16, + y: u16, + w: u16, + h: u16, + } + + #[derive(Debug, Deserialize)] + struct EventsResponse { + events: Vec, + } + + async fn wait_for_emulator_start_text(ui_host_port: u16) { + sleep(Duration::from_secs(1)).await; + + let mut ready = false; + while !ready { + let events = get_emulator_events(ui_host_port).await; + + if events.iter().any(|event| event.text == "is ready") { + ready = true; + } + } + } + + async fn get_emulator_events(ui_host_port: u16) -> Vec { + let client = reqwest::Client::new(); + let resp = client + .get(format!("http://localhost:{ui_host_port}/events")) + .send() + .await + .unwrap() + .json::() + .await + .unwrap(); // not worrying about unwraps for test helpers for now + resp.events + } + + async fn approve_tx_hash_signature(ui_host_port: u16) { + println!("approving tx hash sig on the device"); + // press the right button 10 times + for _ in 0..10 { + click(ui_host_port, "button/right").await; + } + + // press both buttons + click(ui_host_port, "button/both").await; + } + + async fn approve_tx_signature(ui_host_port: u16) { + println!("approving tx on the device"); + let mut map = HashMap::new(); + map.insert("action", "press-and-release"); + + // press right button 17 times + let client = reqwest::Client::new(); + for _ in 0..17 { + client + .post(format!("http://localhost:{ui_host_port}/button/right")) + .json(&map) + .send() + .await + .unwrap(); + } + + // press both buttons + client + .post(format!("http://localhost:{ui_host_port}/button/both")) + .json(&map) + .send() + .await + .unwrap(); + } +} diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index e07031d57..513f03da5 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -1,4 +1,3 @@ -mod transport_zemu_http; use futures::executor::block_on; use ledger_transport::{APDUCommand, Exchange}; use ledger_transport_hid::{ @@ -20,6 +19,10 @@ use crate::transport_zemu_http::TransportZemuHttp; mod signer; mod speculos; +mod transport_zemu_http; + +#[cfg(test)] +mod emulator_tests; // this is from https://github.com/LedgerHQ/ledger-live/blob/36cfbf3fa3300fd99bcee2ab72e1fd8f280e6280/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L181 const APDU_MAX_SIZE: u8 = 150; @@ -346,413 +349,3 @@ pub fn get_transport() -> Result { pub fn get_zemu_transport(host: &str, port: u16) -> Result { Ok(TransportZemuHttp::new(host, port)) } - -#[cfg(test)] -mod test { - use serde::Deserialize; - use soroban_env_host::xdr::{self, Operation, OperationBody, Transaction, Uint256}; - - use crate::speculos::Speculos; - - use std::sync::Arc; - use std::{collections::HashMap, str::FromStr, time::Duration}; - - use super::*; - - use serial_test::serial; - - use stellar_xdr::curr::{ - Memo, MuxedAccount, PaymentOp, Preconditions, SequenceNumber, TransactionExt, - }; - - use testcontainers::clients; - use tokio::time::sleep; - - const TEST_NETWORK_PASSPHRASE: &str = "Test SDF Network ; September 2015"; - - #[ignore] - #[tokio::test] - #[serial] - async fn test_get_public_key_with_ledger_device() { - let transport = get_transport().unwrap(); - let ledger_options = Some(LedgerOptions { - exchange: transport, - hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - }); - let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); - let public_key = ledger.get_public_key(0).await; - assert!(public_key.is_ok()); - } - - #[tokio::test] - async fn test_get_public_key() { - let docker = clients::Cli::default(); - let node = docker.run(Speculos::new()); - let host_port = node.get_host_port_ipv4(9998); - let ui_host_port: u16 = node.get_host_port_ipv4(5000); - - wait_for_emulator_start_text(ui_host_port).await; - - let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); - let ledger_options = Some(LedgerOptions { - exchange: transport, - hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - }); - let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); - - match ledger.get_public_key(0).await { - Ok(public_key) => { - let public_key_string = public_key.to_string(); - // This is determined by the seed phrase used to start up the emulator - // TODO: make the seed phrase configurable - let expected_public_key = - "GDUTHCF37UX32EMANXIL2WOOVEDZ47GHBTT3DYKU6EKM37SOIZXM2FN7"; - assert_eq!(public_key_string, expected_public_key); - } - Err(e) => { - node.stop(); - println!("{e}"); - assert!(false); - } - } - - node.stop(); - } - - #[tokio::test] - async fn test_get_app_configuration() { - let docker = clients::Cli::default(); - let node = docker.run(Speculos::new()); - let host_port = node.get_host_port_ipv4(9998); - let ui_host_port: u16 = node.get_host_port_ipv4(5000); - - wait_for_emulator_start_text(ui_host_port).await; - - let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); - let ledger_options = Some(LedgerOptions { - exchange: transport, - hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - }); - let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); - - match ledger.get_app_configuration().await { - Ok(config) => { - assert_eq!(config, vec![0, 5, 0, 3]); - } - Err(e) => { - node.stop(); - println!("{e}"); - assert!(false); - } - }; - - node.stop(); - } - - #[tokio::test] - async fn test_sign_tx() { - let docker = clients::Cli::default(); - let node = docker.run(Speculos::new()); - let host_port = node.get_host_port_ipv4(9998); - let ui_host_port: u16 = node.get_host_port_ipv4(5000); - - wait_for_emulator_start_text(ui_host_port).await; - - let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); - let ledger_options = Some(LedgerOptions { - exchange: transport, - hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - }); - let ledger = Arc::new(LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options)); - - let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); - - let source_account_str = "GAQNVGMLOXSCWH37QXIHLQJH6WZENXYSVWLPAEF4673W64VRNZLRHMFM"; - let source_account_bytes = match stellar_strkey::Strkey::from_string(source_account_str) { - Ok(key) => match key { - stellar_strkey::Strkey::PublicKeyEd25519(p) => p.0, - _ => { - eprintln!("Error decoding public key: {:?}", key); - return; - } - }, - Err(err) => { - eprintln!("Error decoding public key: {}", err); - return; - } - }; - - let destination_account_str = "GCKUD4BHIYSAYHU7HBB5FDSW6CSYH3GSOUBPWD2KE7KNBERP4BSKEJDV"; - let destination_account_bytes = - match stellar_strkey::Strkey::from_string(destination_account_str) { - Ok(key) => match key { - stellar_strkey::Strkey::PublicKeyEd25519(p) => p.0, - _ => { - eprintln!("Error decoding public key: {:?}", key); - return; - } - }, - Err(err) => { - eprintln!("Error decoding public key: {}", err); - return; - } - }; - - let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(source_account_bytes)), // was struggling to create a real account in this way with the G... address - fee: 100, - seq_num: SequenceNumber(1), - cond: Preconditions::None, - memo: Memo::Text("Stellar".as_bytes().try_into().unwrap()), - ext: TransactionExt::V0, - operations: [Operation { - source_account: Some(MuxedAccount::Ed25519(Uint256(source_account_bytes))), - body: OperationBody::Payment(PaymentOp { - destination: MuxedAccount::Ed25519(Uint256(destination_account_bytes)), - asset: xdr::Asset::Native, - amount: 100, - }), - }] - .try_into() - .unwrap(), - }; - - let sign = tokio::task::spawn({ - let ledger = Arc::clone(&ledger); - async move { ledger.sign_transaction(path, tx).await } - }); - let approve = tokio::task::spawn(approve_tx_signature(ui_host_port)); - - let result = sign.await.unwrap(); - let _ = approve.await.unwrap(); - - match result { - Ok(response) => { - assert_eq!( hex::encode(response), "5c2f8eb41e11ab922800071990a25cf9713cc6e7c43e50e0780ddc4c0c6da50c784609ef14c528a12f520d8ea9343b49083f59c51e3f28af8c62b3edeaade60e"); - } - Err(e) => { - node.stop(); - println!("{e}"); - assert!(false); - } - }; - - node.stop(); - } - - #[tokio::test] - async fn test_sign_tx_hash_when_hash_signing_is_not_enabled() { - //when hash signing isn't enabled on the device we expect an error - let docker = clients::Cli::default(); - let node = docker.run(Speculos::new()); - let host_port = node.get_host_port_ipv4(9998); - let ui_host_port: u16 = node.get_host_port_ipv4(5000); - - wait_for_emulator_start_text(ui_host_port).await; - // wait for emulator to load - check the events - // sleep to account for key delay - // for some things, waiting for the screen to change... but prob dont need that for this - - let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); - let ledger_options = Some(LedgerOptions { - exchange: transport, - hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - }); - let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); - - let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); - let test_hash = - "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889".as_bytes(); - - let result = ledger.sign_transaction_hash(path, test_hash.into()).await; - if let Err(LedgerError::APDUExchangeError(msg)) = result { - assert_eq!(msg, "Ledger APDU retcode: 0x6C66"); - // this error code is SW_TX_HASH_SIGNING_MODE_NOT_ENABLED https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md - } else { - node.stop(); - panic!("Unexpected result: {:?}", result); - } - - node.stop(); - } - - #[tokio::test] - async fn test_sign_tx_hash_when_hash_signing_is_enabled() { - //when hash signing isnt enabled on the device we expect an error - let docker = clients::Cli::default(); - let node = docker.run(Speculos::new()); - let host_port = node.get_host_port_ipv4(9998); - let ui_host_port: u16 = node.get_host_port_ipv4(5000); - - wait_for_emulator_start_text(ui_host_port).await; - enable_hash_signing(ui_host_port).await; - - let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); - let ledger_options = Some(LedgerOptions { - exchange: transport, - hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - }); - let ledger = Arc::new(LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options)); - - let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); - let mut test_hash = vec![0u8; 32]; - - match hex::decode_to_slice( - "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889", - &mut test_hash as &mut [u8], - ) { - Ok(()) => {} - Err(e) => { - node.stop(); - panic!("Unexpected result: {e}"); - } - } - - let sign = tokio::task::spawn({ - let ledger = Arc::clone(&ledger); - async move { ledger.sign_transaction_hash(path, test_hash).await } - }); - let approve = tokio::task::spawn(approve_tx_hash_signature(ui_host_port)); - - let result = sign.await.unwrap(); - let _ = approve.await.unwrap(); - - match result { - Ok(result) => { - println!("this is the response from signing the hash: {result:?}"); - } - Err(e) => { - node.stop(); - panic!("Unexpected result: {e}"); - } - } - - node.stop(); - } - - // Based on the zemu click fn - async fn click(ui_host_port: u16, url: &str) { - let previous_events = get_emulator_events(ui_host_port).await; - - let client = reqwest::Client::new(); - let mut payload = HashMap::new(); - payload.insert("action", "press-and-release"); - - let mut screen_has_changed = false; - - client - .post(format!("http://localhost:{ui_host_port}/{url}")) - .json(&payload) - .send() - .await - .unwrap(); - - while !screen_has_changed { - let current_events = get_emulator_events(ui_host_port).await; - - if !(previous_events == current_events) { - screen_has_changed = true - } - } - - sleep(Duration::from_secs(1)).await; - } - - async fn enable_hash_signing(ui_host_port: u16) { - println!("enabling hash signing on the device"); - - // right button press - click(ui_host_port, "button/right").await; - - // both button press - click(ui_host_port, "button/both").await; - - // both button press - click(ui_host_port, "button/both").await; - - // right button press - click(ui_host_port, "button/right").await; - - // right button press - click(ui_host_port, "button/right").await; - - // both button press - click(ui_host_port, "button/both").await; - } - - #[derive(Debug, Deserialize, PartialEq)] - struct EmulatorEvent { - text: String, - x: u16, - y: u16, - w: u16, - h: u16, - } - - #[derive(Debug, Deserialize)] - struct EventsResponse { - events: Vec, - } - - async fn wait_for_emulator_start_text(ui_host_port: u16) { - sleep(Duration::from_secs(1)).await; - - let mut ready = false; - while !ready { - let events = get_emulator_events(ui_host_port).await; - - if events.iter().any(|event| event.text == "is ready") { - ready = true; - } - } - } - - async fn get_emulator_events(ui_host_port: u16) -> Vec { - let client = reqwest::Client::new(); - let resp = client - .get(format!("http://localhost:{ui_host_port}/events")) - .send() - .await - .unwrap() - .json::() - .await - .unwrap(); // not worrying about unwraps for test helpers for now - resp.events - } - - async fn approve_tx_hash_signature(ui_host_port: u16) { - println!("approving tx hash sig on the device"); - // press the right button 10 times - for _ in 0..10 { - click(ui_host_port, "button/right").await; - } - - // press both buttons - click(ui_host_port, "button/both").await; - } - - async fn approve_tx_signature(ui_host_port: u16) { - println!("approving tx on the device"); - let mut map = HashMap::new(); - map.insert("action", "press-and-release"); - - // press right button 17 times - let client = reqwest::Client::new(); - for _ in 0..17 { - client - .post(format!("http://localhost:{ui_host_port}/button/right")) - .json(&map) - .send() - .await - .unwrap(); - } - - // press both buttons - client - .post(format!("http://localhost:{ui_host_port}/button/both")) - .json(&map) - .send() - .await - .unwrap(); - } -} From 1ac9400a8725c794493045df2d4ad1bcc2c94f38 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 29 Apr 2024 12:06:11 -0400 Subject: [PATCH 51/72] Run emulator-dependent tests in a separate workflow --- .github/workflows/ledger-emulator.yml | 24 +++++++++++++++++++ .../stellar-ledger/src/emulator_tests.rs | 21 ++++------------ 2 files changed, 29 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/ledger-emulator.yml diff --git a/.github/workflows/ledger-emulator.yml b/.github/workflows/ledger-emulator.yml new file mode 100644 index 000000000..4a092c1ba --- /dev/null +++ b/.github/workflows/ledger-emulator.yml @@ -0,0 +1,24 @@ +name: Ledger Emulator Tests + +on: + push: + branches: [main, release/**] + pull_request: + +defaults: + run: + shell: bash + +jobs: + emulator-tests: + runs-on: ubuntu-latest + env: + CI_TESTS: true + steps: + - uses: actions/checkout@v3 + - uses: stellar/actions/rust-cache@main + - name: install libudev-dev + run: | + sudo apt install -y libudev-dev + - run: | + cargo test --manifest-path cmd/crates/stellar-ledger/Cargo.toml --features "emulator-tests" \ No newline at end of file diff --git a/cmd/crates/stellar-ledger/src/emulator_tests.rs b/cmd/crates/stellar-ledger/src/emulator_tests.rs index 3ebe7825a..7fbb5ee77 100644 --- a/cmd/crates/stellar-ledger/src/emulator_tests.rs +++ b/cmd/crates/stellar-ledger/src/emulator_tests.rs @@ -1,20 +1,11 @@ -use ledger_transport::{APDUCommand, Exchange}; -use ledger_transport_hid::{ - hidapi::{HidApi, HidError}, - LedgerHIDError, TransportNativeHID, -}; -use sha2::{Digest, Sha256}; - -use soroban_env_host::xdr::{Hash, Transaction}; -use std::vec; - -use crate::signer::{Error, Stellar}; -use crate::transport_zemu_http::TransportZemuHttp; - #[cfg(feature = "emulator-tests")] mod emulator_tests { + use soroban_env_host::xdr::Transaction; + use std::vec; + + use crate::signer::Stellar; use serde::Deserialize; - use soroban_env_host::xdr::{self, Operation, OperationBody, Transaction, Uint256}; + use soroban_env_host::xdr::{self, Operation, OperationBody, Uint256}; use crate::speculos::Speculos; use crate::{get_zemu_transport, LedgerError, LedgerOptions, LedgerSigner}; @@ -22,8 +13,6 @@ mod emulator_tests { use std::sync::Arc; use std::{collections::HashMap, str::FromStr, time::Duration}; - use super::*; - use stellar_xdr::curr::{ Memo, MuxedAccount, PaymentOp, Preconditions, SequenceNumber, TransactionExt, }; From 8a097af054a6009f74eb50aaba97d6ce068ca4b2 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 29 Apr 2024 19:43:39 -0400 Subject: [PATCH 52/72] chore: move feature to mod declaration --- .../stellar-ledger/src/emulator_tests.rs | 677 +++++++++--------- cmd/crates/stellar-ledger/src/lib.rs | 2 +- .../src/commands/contract/invoke.rs | 2 + 3 files changed, 339 insertions(+), 342 deletions(-) diff --git a/cmd/crates/stellar-ledger/src/emulator_tests.rs b/cmd/crates/stellar-ledger/src/emulator_tests.rs index 7fbb5ee77..19c909e11 100644 --- a/cmd/crates/stellar-ledger/src/emulator_tests.rs +++ b/cmd/crates/stellar-ledger/src/emulator_tests.rs @@ -1,126 +1,139 @@ -#[cfg(feature = "emulator-tests")] -mod emulator_tests { - use soroban_env_host::xdr::Transaction; - use std::vec; - - use crate::signer::Stellar; - use serde::Deserialize; - use soroban_env_host::xdr::{self, Operation, OperationBody, Uint256}; - - use crate::speculos::Speculos; - use crate::{get_zemu_transport, LedgerError, LedgerOptions, LedgerSigner}; - - use std::sync::Arc; - use std::{collections::HashMap, str::FromStr, time::Duration}; - - use stellar_xdr::curr::{ - Memo, MuxedAccount, PaymentOp, Preconditions, SequenceNumber, TransactionExt, - }; - - use testcontainers::clients; - use tokio::time::sleep; - - const TEST_NETWORK_PASSPHRASE: &str = "Test SDF Network ; September 2015"; - - // #[ignore] - // #[tokio::test] - // #[serial] - // async fn test_get_public_key_with_ledger_device() { - // let transport = get_transport().unwrap(); - // let ledger_options = Some(LedgerOptions { - // exchange: transport, - // hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - // }); - // let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); - // let public_key = ledger.get_public_key(0).await; - // assert!(public_key.is_ok()); - // } - - #[tokio::test] - async fn test_get_public_key() { - let docker = clients::Cli::default(); - let node = docker.run(Speculos::new()); - let host_port = node.get_host_port_ipv4(9998); - let ui_host_port: u16 = node.get_host_port_ipv4(5000); - - wait_for_emulator_start_text(ui_host_port).await; - - let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); - let ledger_options = Some(LedgerOptions { - exchange: transport, - hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - }); - let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); - - match ledger.get_public_key(0).await { - Ok(public_key) => { - let public_key_string = public_key.to_string(); - // This is determined by the seed phrase used to start up the emulator - // TODO: make the seed phrase configurable - let expected_public_key = - "GDUTHCF37UX32EMANXIL2WOOVEDZ47GHBTT3DYKU6EKM37SOIZXM2FN7"; - assert_eq!(public_key_string, expected_public_key); - } - Err(e) => { - node.stop(); - println!("{e}"); - assert!(false); - } +use soroban_env_host::xdr::Transaction; +use std::vec; + +use crate::signer::Stellar; +use serde::Deserialize; +use soroban_env_host::xdr::{self, Operation, OperationBody, Uint256}; + +use crate::speculos::Speculos; +use crate::{get_zemu_transport, LedgerError, LedgerOptions, LedgerSigner}; + +use std::sync::Arc; +use std::{collections::HashMap, str::FromStr, time::Duration}; + +use stellar_xdr::curr::{ + Memo, MuxedAccount, PaymentOp, Preconditions, SequenceNumber, TransactionExt, +}; + +use testcontainers::clients; +use tokio::time::sleep; + +const TEST_NETWORK_PASSPHRASE: &str = "Test SDF Network ; September 2015"; + +// #[ignore] +// #[tokio::test] +// #[serial] +// async fn test_get_public_key_with_ledger_device() { +// let transport = get_transport().unwrap(); +// let ledger_options = Some(LedgerOptions { +// exchange: transport, +// hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), +// }); +// let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); +// let public_key = ledger.get_public_key(0).await; +// assert!(public_key.is_ok()); +// } + +#[tokio::test] +async fn test_get_public_key() { + let docker = clients::Cli::default(); + let node = docker.run(Speculos::new()); + let host_port = node.get_host_port_ipv4(9998); + let ui_host_port: u16 = node.get_host_port_ipv4(5000); + + wait_for_emulator_start_text(ui_host_port).await; + + let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + + match ledger.get_public_key(0).await { + Ok(public_key) => { + let public_key_string = public_key.to_string(); + // This is determined by the seed phrase used to start up the emulator + // TODO: make the seed phrase configurable + let expected_public_key = "GDUTHCF37UX32EMANXIL2WOOVEDZ47GHBTT3DYKU6EKM37SOIZXM2FN7"; + assert_eq!(public_key_string, expected_public_key); + } + Err(e) => { + node.stop(); + println!("{e}"); + assert!(false); } - - node.stop(); - } - - #[tokio::test] - async fn test_get_app_configuration() { - let docker = clients::Cli::default(); - let node = docker.run(Speculos::new()); - let host_port = node.get_host_port_ipv4(9998); - let ui_host_port: u16 = node.get_host_port_ipv4(5000); - - wait_for_emulator_start_text(ui_host_port).await; - - let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); - let ledger_options = Some(LedgerOptions { - exchange: transport, - hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - }); - let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); - - match ledger.get_app_configuration().await { - Ok(config) => { - assert_eq!(config, vec![0, 5, 0, 3]); - } - Err(e) => { - node.stop(); - println!("{e}"); - assert!(false); - } - }; - - node.stop(); } - #[tokio::test] - async fn test_sign_tx() { - let docker = clients::Cli::default(); - let node = docker.run(Speculos::new()); - let host_port = node.get_host_port_ipv4(9998); - let ui_host_port: u16 = node.get_host_port_ipv4(5000); + node.stop(); +} - wait_for_emulator_start_text(ui_host_port).await; +#[tokio::test] +async fn test_get_app_configuration() { + let docker = clients::Cli::default(); + let node = docker.run(Speculos::new()); + let host_port = node.get_host_port_ipv4(9998); + let ui_host_port: u16 = node.get_host_port_ipv4(5000); + + wait_for_emulator_start_text(ui_host_port).await; + + let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + + match ledger.get_app_configuration().await { + Ok(config) => { + assert_eq!(config, vec![0, 5, 0, 3]); + } + Err(e) => { + node.stop(); + println!("{e}"); + assert!(false); + } + }; - let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); - let ledger_options = Some(LedgerOptions { - exchange: transport, - hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - }); - let ledger = Arc::new(LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options)); + node.stop(); +} - let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); +#[tokio::test] +async fn test_sign_tx() { + let docker = clients::Cli::default(); + let node = docker.run(Speculos::new()); + let host_port = node.get_host_port_ipv4(9998); + let ui_host_port: u16 = node.get_host_port_ipv4(5000); + + wait_for_emulator_start_text(ui_host_port).await; + + let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = Arc::new(LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options)); + + let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); + + let source_account_str = "GAQNVGMLOXSCWH37QXIHLQJH6WZENXYSVWLPAEF4673W64VRNZLRHMFM"; + let source_account_bytes = match stellar_strkey::Strkey::from_string(source_account_str) { + Ok(key) => match key { + stellar_strkey::Strkey::PublicKeyEd25519(p) => p.0, + _ => { + eprintln!("Error decoding public key: {:?}", key); + return; + } + }, + Err(err) => { + eprintln!("Error decoding public key: {}", err); + return; + } + }; - let source_account_str = "GAQNVGMLOXSCWH37QXIHLQJH6WZENXYSVWLPAEF4673W64VRNZLRHMFM"; - let source_account_bytes = match stellar_strkey::Strkey::from_string(source_account_str) { + let destination_account_str = "GCKUD4BHIYSAYHU7HBB5FDSW6CSYH3GSOUBPWD2KE7KNBERP4BSKEJDV"; + let destination_account_bytes = + match stellar_strkey::Strkey::from_string(destination_account_str) { Ok(key) => match key { stellar_strkey::Strkey::PublicKeyEd25519(p) => p.0, _ => { @@ -134,277 +147,259 @@ mod emulator_tests { } }; - let destination_account_str = "GCKUD4BHIYSAYHU7HBB5FDSW6CSYH3GSOUBPWD2KE7KNBERP4BSKEJDV"; - let destination_account_bytes = - match stellar_strkey::Strkey::from_string(destination_account_str) { - Ok(key) => match key { - stellar_strkey::Strkey::PublicKeyEd25519(p) => p.0, - _ => { - eprintln!("Error decoding public key: {:?}", key); - return; - } - }, - Err(err) => { - eprintln!("Error decoding public key: {}", err); - return; - } - }; - - let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(source_account_bytes)), // was struggling to create a real account in this way with the G... address - fee: 100, - seq_num: SequenceNumber(1), - cond: Preconditions::None, - memo: Memo::Text("Stellar".as_bytes().try_into().unwrap()), - ext: TransactionExt::V0, - operations: [Operation { - source_account: Some(MuxedAccount::Ed25519(Uint256(source_account_bytes))), - body: OperationBody::Payment(PaymentOp { - destination: MuxedAccount::Ed25519(Uint256(destination_account_bytes)), - asset: xdr::Asset::Native, - amount: 100, - }), - }] - .try_into() - .unwrap(), - }; - - let sign = tokio::task::spawn({ - let ledger = Arc::clone(&ledger); - async move { ledger.sign_transaction(path, tx).await } - }); - let approve = tokio::task::spawn(approve_tx_signature(ui_host_port)); + let tx = Transaction { + source_account: MuxedAccount::Ed25519(Uint256(source_account_bytes)), // was struggling to create a real account in this way with the G... address + fee: 100, + seq_num: SequenceNumber(1), + cond: Preconditions::None, + memo: Memo::Text("Stellar".as_bytes().try_into().unwrap()), + ext: TransactionExt::V0, + operations: [Operation { + source_account: Some(MuxedAccount::Ed25519(Uint256(source_account_bytes))), + body: OperationBody::Payment(PaymentOp { + destination: MuxedAccount::Ed25519(Uint256(destination_account_bytes)), + asset: xdr::Asset::Native, + amount: 100, + }), + }] + .try_into() + .unwrap(), + }; - let result = sign.await.unwrap(); - let _ = approve.await.unwrap(); + let sign = tokio::task::spawn({ + let ledger = Arc::clone(&ledger); + async move { ledger.sign_transaction(path, tx).await } + }); + let approve = tokio::task::spawn(approve_tx_signature(ui_host_port)); - match result { - Ok(response) => { - assert_eq!( hex::encode(response), "5c2f8eb41e11ab922800071990a25cf9713cc6e7c43e50e0780ddc4c0c6da50c784609ef14c528a12f520d8ea9343b49083f59c51e3f28af8c62b3edeaade60e"); - } - Err(e) => { - node.stop(); - println!("{e}"); - assert!(false); - } - }; + let result = sign.await.unwrap(); + let _ = approve.await.unwrap(); - node.stop(); - } - - #[tokio::test] - async fn test_sign_tx_hash_when_hash_signing_is_not_enabled() { - //when hash signing isn't enabled on the device we expect an error - let docker = clients::Cli::default(); - let node = docker.run(Speculos::new()); - let host_port = node.get_host_port_ipv4(9998); - let ui_host_port: u16 = node.get_host_port_ipv4(5000); - - wait_for_emulator_start_text(ui_host_port).await; - // wait for emulator to load - check the events - // sleep to account for key delay - // for some things, waiting for the screen to change... but prob dont need that for this - - let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); - let ledger_options = Some(LedgerOptions { - exchange: transport, - hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - }); - let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); - - let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); - let test_hash = - "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889".as_bytes(); - - let result = ledger.sign_transaction_hash(path, test_hash.into()).await; - if let Err(LedgerError::APDUExchangeError(msg)) = result { - assert_eq!(msg, "Ledger APDU retcode: 0x6C66"); - // this error code is SW_TX_HASH_SIGNING_MODE_NOT_ENABLED https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md - } else { + match result { + Ok(response) => { + assert_eq!( hex::encode(response), "5c2f8eb41e11ab922800071990a25cf9713cc6e7c43e50e0780ddc4c0c6da50c784609ef14c528a12f520d8ea9343b49083f59c51e3f28af8c62b3edeaade60e"); + } + Err(e) => { node.stop(); - panic!("Unexpected result: {:?}", result); + println!("{e}"); + assert!(false); } + }; + + node.stop(); +} +#[tokio::test] +async fn test_sign_tx_hash_when_hash_signing_is_not_enabled() { + //when hash signing isn't enabled on the device we expect an error + let docker = clients::Cli::default(); + let node = docker.run(Speculos::new()); + let host_port = node.get_host_port_ipv4(9998); + let ui_host_port: u16 = node.get_host_port_ipv4(5000); + + wait_for_emulator_start_text(ui_host_port).await; + // wait for emulator to load - check the events + // sleep to account for key delay + // for some things, waiting for the screen to change... but prob dont need that for this + + let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + + let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); + let test_hash = "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889".as_bytes(); + + let result = ledger.sign_transaction_hash(path, test_hash.into()).await; + if let Err(LedgerError::APDUExchangeError(msg)) = result { + assert_eq!(msg, "Ledger APDU retcode: 0x6C66"); + // this error code is SW_TX_HASH_SIGNING_MODE_NOT_ENABLED https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md + } else { node.stop(); + panic!("Unexpected result: {:?}", result); } - #[tokio::test] - async fn test_sign_tx_hash_when_hash_signing_is_enabled() { - //when hash signing isnt enabled on the device we expect an error - let docker = clients::Cli::default(); - let node = docker.run(Speculos::new()); - let host_port = node.get_host_port_ipv4(9998); - let ui_host_port: u16 = node.get_host_port_ipv4(5000); - - wait_for_emulator_start_text(ui_host_port).await; - enable_hash_signing(ui_host_port).await; - - let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); - let ledger_options = Some(LedgerOptions { - exchange: transport, - hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), - }); - let ledger = Arc::new(LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options)); - - let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); - let mut test_hash = vec![0u8; 32]; - - match hex::decode_to_slice( - "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889", - &mut test_hash as &mut [u8], - ) { - Ok(()) => {} - Err(e) => { - node.stop(); - panic!("Unexpected result: {e}"); - } + node.stop(); +} + +#[tokio::test] +async fn test_sign_tx_hash_when_hash_signing_is_enabled() { + //when hash signing isnt enabled on the device we expect an error + let docker = clients::Cli::default(); + let node = docker.run(Speculos::new()); + let host_port = node.get_host_port_ipv4(9998); + let ui_host_port: u16 = node.get_host_port_ipv4(5000); + + wait_for_emulator_start_text(ui_host_port).await; + enable_hash_signing(ui_host_port).await; + + let transport = get_zemu_transport("127.0.0.1", host_port).unwrap(); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = Arc::new(LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options)); + + let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); + let mut test_hash = vec![0u8; 32]; + + match hex::decode_to_slice( + "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889", + &mut test_hash as &mut [u8], + ) { + Ok(()) => {} + Err(e) => { + node.stop(); + panic!("Unexpected result: {e}"); } + } - let sign = tokio::task::spawn({ - let ledger = Arc::clone(&ledger); - async move { ledger.sign_transaction_hash(path, test_hash).await } - }); - let approve = tokio::task::spawn(approve_tx_hash_signature(ui_host_port)); + let sign = tokio::task::spawn({ + let ledger = Arc::clone(&ledger); + async move { ledger.sign_transaction_hash(path, test_hash).await } + }); + let approve = tokio::task::spawn(approve_tx_hash_signature(ui_host_port)); - let result = sign.await.unwrap(); - let _ = approve.await.unwrap(); + let result = sign.await.unwrap(); + let _ = approve.await.unwrap(); - match result { - Ok(result) => { - println!("this is the response from signing the hash: {result:?}"); - } - Err(e) => { - node.stop(); - panic!("Unexpected result: {e}"); - } + match result { + Ok(result) => { + println!("this is the response from signing the hash: {result:?}"); + } + Err(e) => { + node.stop(); + panic!("Unexpected result: {e}"); } - - node.stop(); } - // Based on the zemu click fn - async fn click(ui_host_port: u16, url: &str) { - let previous_events = get_emulator_events(ui_host_port).await; + node.stop(); +} - let client = reqwest::Client::new(); - let mut payload = HashMap::new(); - payload.insert("action", "press-and-release"); +// Based on the zemu click fn +async fn click(ui_host_port: u16, url: &str) { + let previous_events = get_emulator_events(ui_host_port).await; - let mut screen_has_changed = false; + let client = reqwest::Client::new(); + let mut payload = HashMap::new(); + payload.insert("action", "press-and-release"); - client - .post(format!("http://localhost:{ui_host_port}/{url}")) - .json(&payload) - .send() - .await - .unwrap(); + let mut screen_has_changed = false; - while !screen_has_changed { - let current_events = get_emulator_events(ui_host_port).await; + client + .post(format!("http://localhost:{ui_host_port}/{url}")) + .json(&payload) + .send() + .await + .unwrap(); - if !(previous_events == current_events) { - screen_has_changed = true - } - } + while !screen_has_changed { + let current_events = get_emulator_events(ui_host_port).await; - sleep(Duration::from_secs(1)).await; + if !(previous_events == current_events) { + screen_has_changed = true + } } - async fn enable_hash_signing(ui_host_port: u16) { - println!("enabling hash signing on the device"); + sleep(Duration::from_secs(1)).await; +} - // right button press - click(ui_host_port, "button/right").await; +async fn enable_hash_signing(ui_host_port: u16) { + println!("enabling hash signing on the device"); - // both button press - click(ui_host_port, "button/both").await; + // right button press + click(ui_host_port, "button/right").await; - // both button press - click(ui_host_port, "button/both").await; + // both button press + click(ui_host_port, "button/both").await; - // right button press - click(ui_host_port, "button/right").await; + // both button press + click(ui_host_port, "button/both").await; - // right button press - click(ui_host_port, "button/right").await; + // right button press + click(ui_host_port, "button/right").await; - // both button press - click(ui_host_port, "button/both").await; - } + // right button press + click(ui_host_port, "button/right").await; - #[derive(Debug, Deserialize, PartialEq)] - struct EmulatorEvent { - text: String, - x: u16, - y: u16, - w: u16, - h: u16, - } + // both button press + click(ui_host_port, "button/both").await; +} - #[derive(Debug, Deserialize)] - struct EventsResponse { - events: Vec, - } +#[derive(Debug, Deserialize, PartialEq)] +struct EmulatorEvent { + text: String, + x: u16, + y: u16, + w: u16, + h: u16, +} - async fn wait_for_emulator_start_text(ui_host_port: u16) { - sleep(Duration::from_secs(1)).await; +#[derive(Debug, Deserialize)] +struct EventsResponse { + events: Vec, +} - let mut ready = false; - while !ready { - let events = get_emulator_events(ui_host_port).await; +async fn wait_for_emulator_start_text(ui_host_port: u16) { + sleep(Duration::from_secs(1)).await; - if events.iter().any(|event| event.text == "is ready") { - ready = true; - } - } - } + let mut ready = false; + while !ready { + let events = get_emulator_events(ui_host_port).await; - async fn get_emulator_events(ui_host_port: u16) -> Vec { - let client = reqwest::Client::new(); - let resp = client - .get(format!("http://localhost:{ui_host_port}/events")) - .send() - .await - .unwrap() - .json::() - .await - .unwrap(); // not worrying about unwraps for test helpers for now - resp.events + if events.iter().any(|event| event.text == "is ready") { + ready = true; + } } +} - async fn approve_tx_hash_signature(ui_host_port: u16) { - println!("approving tx hash sig on the device"); - // press the right button 10 times - for _ in 0..10 { - click(ui_host_port, "button/right").await; - } +async fn get_emulator_events(ui_host_port: u16) -> Vec { + let client = reqwest::Client::new(); + let resp = client + .get(format!("http://localhost:{ui_host_port}/events")) + .send() + .await + .unwrap() + .json::() + .await + .unwrap(); // not worrying about unwraps for test helpers for now + resp.events +} - // press both buttons - click(ui_host_port, "button/both").await; +async fn approve_tx_hash_signature(ui_host_port: u16) { + println!("approving tx hash sig on the device"); + // press the right button 10 times + for _ in 0..10 { + click(ui_host_port, "button/right").await; } - async fn approve_tx_signature(ui_host_port: u16) { - println!("approving tx on the device"); - let mut map = HashMap::new(); - map.insert("action", "press-and-release"); - - // press right button 17 times - let client = reqwest::Client::new(); - for _ in 0..17 { - client - .post(format!("http://localhost:{ui_host_port}/button/right")) - .json(&map) - .send() - .await - .unwrap(); - } + // press both buttons + click(ui_host_port, "button/both").await; +} - // press both buttons +async fn approve_tx_signature(ui_host_port: u16) { + println!("approving tx on the device"); + let mut map = HashMap::new(); + map.insert("action", "press-and-release"); + + // press right button 17 times + let client = reqwest::Client::new(); + for _ in 0..17 { client - .post(format!("http://localhost:{ui_host_port}/button/both")) + .post(format!("http://localhost:{ui_host_port}/button/right")) .json(&map) .send() .await .unwrap(); } + + // press both buttons + client + .post(format!("http://localhost:{ui_host_port}/button/both")) + .json(&map) + .send() + .await + .unwrap(); } diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 513f03da5..5dfb8b8e8 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -21,7 +21,7 @@ mod signer; mod speculos; mod transport_zemu_http; -#[cfg(test)] +#[cfg(all(test, feature = "emulator-tests"))] mod emulator_tests; // this is from https://github.com/LedgerHQ/ledger-live/blob/36cfbf3fa3300fd99bcee2ab72e1fd8f280e6280/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L181 diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index cd42d818f..7e22e9cf8 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -296,6 +296,8 @@ impl NetworkRunnable for Cmd { type Error = Error; type Result = String; + //TODO: remove + #[allow(clippy::map_clone)] async fn run_against_rpc_server( &self, global_args: Option<&global::Args>, From 78292714f7c2a274f4234f4fcbeb8be5194870ba Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:14:56 -0400 Subject: [PATCH 53/72] Add unit tests to lib that mock out http requests --- cmd/crates/stellar-ledger/Cargo.toml | 2 + .../stellar-ledger/src/emulator_tests.rs | 13 +- cmd/crates/stellar-ledger/src/lib.rs | 264 ++++++++++++++++-- 3 files changed, 257 insertions(+), 22 deletions(-) diff --git a/cmd/crates/stellar-ledger/Cargo.toml b/cmd/crates/stellar-ledger/Cargo.toml index 91326fc61..322debb3e 100644 --- a/cmd/crates/stellar-ledger/Cargo.toml +++ b/cmd/crates/stellar-ledger/Cargo.toml @@ -50,6 +50,8 @@ log = "0.4.21" once_cell = "1.19.0" pretty_assertions = "1.2.1" serial_test = "3.0.0" +httpmock = "0.7.0-rc.1" + [features] emulator-tests = [] \ No newline at end of file diff --git a/cmd/crates/stellar-ledger/src/emulator_tests.rs b/cmd/crates/stellar-ledger/src/emulator_tests.rs index 19c909e11..c7aad3c40 100644 --- a/cmd/crates/stellar-ledger/src/emulator_tests.rs +++ b/cmd/crates/stellar-ledger/src/emulator_tests.rs @@ -1,12 +1,13 @@ -use soroban_env_host::xdr::Transaction; -use std::vec; - use crate::signer::Stellar; +use ledger_transport::Exchange; use serde::Deserialize; +use soroban_env_host::xdr::Transaction; use soroban_env_host::xdr::{self, Operation, OperationBody, Uint256}; +use std::vec; use crate::speculos::Speculos; -use crate::{get_zemu_transport, LedgerError, LedgerOptions, LedgerSigner}; +use crate::transport_zemu_http::TransportZemuHttp; +use crate::{LedgerError, LedgerOptions, LedgerSigner}; use std::sync::Arc; use std::{collections::HashMap, str::FromStr, time::Duration}; @@ -342,6 +343,10 @@ struct EventsResponse { events: Vec, } +fn get_zemu_transport(host: &str, port: u16) -> Result { + Ok(TransportZemuHttp::new(host, port)) +} + async fn wait_for_emulator_start_text(ui_host_port: u16) { sleep(Duration::from_secs(1)).await; diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 5dfb8b8e8..89b6725c2 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -1,9 +1,6 @@ use futures::executor::block_on; use ledger_transport::{APDUCommand, Exchange}; -use ledger_transport_hid::{ - hidapi::{HidApi, HidError}, - LedgerHIDError, TransportNativeHID, -}; +use ledger_transport_hid::{hidapi::HidError, LedgerHIDError}; use sha2::{Digest, Sha256}; use soroban_env_host::xdr::{Hash, Transaction}; @@ -15,7 +12,6 @@ use stellar_xdr::curr::{ }; use crate::signer::{Error, Stellar}; -use crate::transport_zemu_http::TransportZemuHttp; mod signer; mod speculos; @@ -334,18 +330,250 @@ fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { .collect::>() } -/// Gets a transport connection for a ledger device -/// # Errors -/// Returns an error if there is an issue with connecting with the device -pub fn get_transport() -> Result { - // instantiate the connection to Ledger, this will return an error if Ledger is not connected - let hidapi = HidApi::new().map_err(LedgerError::HidApiError)?; - TransportNativeHID::new(&hidapi).map_err(LedgerError::LedgerHidError) -} +#[cfg(test)] +mod test { + use std::str::FromStr; + + use httpmock::prelude::*; + use serde_json::json; + + use crate::transport_zemu_http::TransportZemuHttp; + + use soroban_env_host::xdr::Transaction; + use std::vec; + + use crate::signer::Stellar; + use soroban_env_host::xdr::{self, Operation, OperationBody, Uint256}; + + use crate::{LedgerError, LedgerOptions, LedgerSigner}; + + use stellar_xdr::curr::{ + Memo, MuxedAccount, PaymentOp, Preconditions, SequenceNumber, TransactionExt, + }; + + const TEST_NETWORK_PASSPHRASE: &str = "Test SDF Network ; September 2015"; + + #[tokio::test] + async fn test_get_public_key() { + let server = MockServer::start(); + let mock_server = server.mock(|when, then| { + when.method(POST) + .path("/") + .header("accept", "application/json") + .header("content-type", "application/json") + .json_body(json!({ "apduHex": "e00200000d038000002c8000009480000000" })); + then.status(200) + .header("content-type", "application/json") + .json_body(json!({"data": "e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd9000"})); + }); + + let transport = TransportZemuHttp::new(&server.host(), server.port()); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + match ledger.get_public_key(0).await { + Ok(public_key) => { + let public_key_string = public_key.to_string(); + // This is determined by the seed phrase used to start up the emulator + let expected_public_key = + "GDUTHCF37UX32EMANXIL2WOOVEDZ47GHBTT3DYKU6EKM37SOIZXM2FN7"; + assert_eq!(public_key_string, expected_public_key); + } + Err(e) => { + println!("{e}"); + assert!(false); + } + } + + mock_server.assert(); + } + + #[tokio::test] + async fn test_get_app_configuration() { + let server = MockServer::start(); + let mock_server = server.mock(|when, then| { + when.method(POST) + .path("/") + .header("accept", "application/json") + .header("content-type", "application/json") + .json_body(json!({ "apduHex": "e006000000" })); + then.status(200) + .header("content-type", "application/json") + .json_body(json!({"data": "000500039000"})); + }); + + let transport = TransportZemuHttp::new(&server.host(), server.port()); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + match ledger.get_app_configuration().await { + Ok(config) => { + assert_eq!(config, vec![0, 5, 0, 3]); + } + Err(e) => { + println!("{e}"); + assert!(false); + } + }; + + mock_server.assert(); + } + + #[tokio::test] + async fn test_sign_tx() { + let server = MockServer::start(); + let mock_request_1 = server.mock(|when, then| { + when.method(POST) + .path("/") + .header("accept", "application/json") + .header("content-type", "application/json") + .json_body(json!({ "apduHex": "e004008089038000002c8000009480000000cee0302d59844d32bdca915c8203dd44b33fbb7edc19051ea37abedf28ecd472000000020000000000000000000000000000000000000000000000000000000000000000000000000000006400000000000000010000000000000001000000075374656c6c6172000000000100000001000000000000000000000000" })); + then.status(200) + .header("content-type", "application/json") + .json_body(json!({"data": "9000"})); + }); + + let mock_request_2 = server.mock(|when, then| { + when.method(POST) + .path("/") + .header("accept", "application/json") + .header("content-type", "application/json") + .json_body(json!({ "apduHex": "e0048000500000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006400000000" })); + then.status(200) + .header("content-type", "application/json") + .json_body(json!({"data": "5c2f8eb41e11ab922800071990a25cf9713cc6e7c43e50e0780ddc4c0c6da50c784609ef14c528a12f520d8ea9343b49083f59c51e3f28af8c62b3edeaade60e9000"})); + }); + + let transport = TransportZemuHttp::new(&server.host(), server.port()); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); + + let fake_source_acct = [0 as u8; 32]; + let fake_dest_acct = [0 as u8; 32]; + let tx = Transaction { + source_account: MuxedAccount::Ed25519(Uint256(fake_source_acct)), + fee: 100, + seq_num: SequenceNumber(1), + cond: Preconditions::None, + memo: Memo::Text("Stellar".as_bytes().try_into().unwrap()), + ext: TransactionExt::V0, + operations: [Operation { + source_account: Some(MuxedAccount::Ed25519(Uint256(fake_source_acct))), + body: OperationBody::Payment(PaymentOp { + destination: MuxedAccount::Ed25519(Uint256(fake_dest_acct)), + asset: xdr::Asset::Native, + amount: 100, + }), + }] + .try_into() + .unwrap(), + }; + + let result = ledger.sign_transaction(path, tx).await; + match result { + Ok(response) => { + assert_eq!( hex::encode(response), "5c2f8eb41e11ab922800071990a25cf9713cc6e7c43e50e0780ddc4c0c6da50c784609ef14c528a12f520d8ea9343b49083f59c51e3f28af8c62b3edeaade60e"); + } + Err(e) => { + println!("{e}"); + assert!(false); + } + }; + mock_request_1.assert(); + mock_request_2.assert(); + } + + #[tokio::test] + async fn test_sign_tx_hash_when_hash_signing_is_not_enabled() { + //when hash signing isn't enabled on the device we expect an error + let server = MockServer::start(); + let mock_server = server.mock(|when, then| { + when.method(POST) + .path("/") + .header("accept", "application/json") + .header("content-type", "application/json") + .json_body(json!({ "apduHex": "e00800004d038000002c800000948000000033333839653966306631613635663139373336636163663534346332653832353331336538343437663536393233336262386462333961613630376338383839" })); + then.status(200) + .header("content-type", "application/json") + .json_body(json!({"data": "6c66"})); + }); + + let transport = TransportZemuHttp::new(&server.host(), server.port()); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + + let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); + let test_hash = + "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889".as_bytes(); + + let result = ledger.sign_transaction_hash(path, test_hash.into()).await; + if let Err(LedgerError::APDUExchangeError(msg)) = result { + assert_eq!(msg, "Ledger APDU retcode: 0x6C66"); + // this error code is SW_TX_HASH_SIGNING_MODE_NOT_ENABLED https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md + } else { + panic!("Unexpected result: {:?}", result); + } + + mock_server.assert(); + } + + #[tokio::test] + async fn test_sign_tx_hash_when_hash_signing_is_enabled() { + let server = MockServer::start(); + let mock_server = server.mock(|when, then| { + when.method(POST) + .path("/") + .header("accept", "application/json") + .header("content-type", "application/json") + .json_body(json!({ "apduHex": "e00800002d038000002c80000094800000003389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889" })); + then.status(200) + .header("content-type", "application/json") + .json_body(json!({"data": "6970b9c9d3a6f4de7fb93e8d3920ec704fc4fece411873c40570015bbb1a60a197622bc3bf5644bb38ae73e1b96e4d487d716d142d46c7e944f008dece92df079000"})); + }); + + let transport = TransportZemuHttp::new(&server.host(), server.port()); + let ledger_options = Some(LedgerOptions { + exchange: transport, + hd_path: slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(), + }); + let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options); + let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap(); + let mut test_hash = vec![0u8; 32]; + + match hex::decode_to_slice( + "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889", + &mut test_hash as &mut [u8], + ) { + Ok(()) => {} + Err(e) => { + panic!("Unexpected result: {e}"); + } + } + + let result = ledger.sign_transaction_hash(path, test_hash).await; -/// Gets a transport connection for a the Zemu emulator -/// # Errors -/// Returns an error if there is an issue with connecting with the device -pub fn get_zemu_transport(host: &str, port: u16) -> Result { - Ok(TransportZemuHttp::new(host, port)) + match result { + Ok(response) => { + assert_eq!( hex::encode(response), "6970b9c9d3a6f4de7fb93e8d3920ec704fc4fece411873c40570015bbb1a60a197622bc3bf5644bb38ae73e1b96e4d487d716d142d46c7e944f008dece92df07"); + } + Err(e) => { + panic!("Unexpected result: {e}"); + } + } + + mock_server.assert(); + } } From 4d20c27db4f166d07ed6a2e2a8f7c18e140c1417 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 2 May 2024 11:49:42 -0400 Subject: [PATCH 54/72] feat: --no-build command allows returning built transaction --sim-only for assembling transactions --- cmd/crates/soroban-test/src/lib.rs | 35 ++++--- .../tests/it/integration/hello_world.rs | 24 ++++- .../src/commands/contract/deploy/asset.rs | 15 ++- .../src/commands/contract/deploy/wasm.rs | 24 +++-- .../src/commands/contract/extend.rs | 20 ++-- .../src/commands/contract/fetch.rs | 14 ++- .../src/commands/contract/install.rs | 18 ++-- .../src/commands/contract/invoke.rs | 99 ++++++++++++------- cmd/soroban-cli/src/commands/contract/read.rs | 14 ++- .../src/commands/contract/restore.rs | 26 +++-- cmd/soroban-cli/src/commands/events.rs | 33 ++++--- cmd/soroban-cli/src/commands/mod.rs | 4 +- cmd/soroban-cli/src/commands/txn_result.rs | 56 +++++++++++ cmd/soroban-cli/src/fee.rs | 25 ++++- cmd/soroban-cli/src/lib.rs | 3 +- docs/soroban-cli-full-docs.md | 51 ++++++++++ 16 files changed, 354 insertions(+), 107 deletions(-) create mode 100644 cmd/soroban-cli/src/commands/txn_result.rs diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index e4d7410ce..835d2066b 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -236,22 +236,25 @@ impl TestEnv { }, hd_path: None, }; - cmd.run_against_rpc_server( - Some(&global::Args { - locator: config::locator::Args { - global: false, - config_dir, - }, - filter_logs: Vec::default(), - quiet: false, - verbose: false, - very_verbose: false, - list: false, - no_cache: false, - }), - Some(&config), - ) - .await + Ok(cmd + .run_against_rpc_server( + Some(&global::Args { + locator: config::locator::Args { + global: false, + config_dir, + }, + filter_logs: Vec::default(), + quiet: false, + verbose: false, + very_verbose: false, + list: false, + no_cache: false, + }), + Some(&config), + ) + .await? + .res() + .unwrap()) } /// Reference to current directory of the `TestEnv`. diff --git a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs index be03afa8d..758c6fce0 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -11,6 +11,18 @@ use crate::integration::util::extend_contract; use super::util::{deploy_hello, extend, HELLO_WORLD}; #[allow(clippy::too_many_lines)] +#[tokio::test] +async fn invoke_view_with_non_existent_source_account() { + let sandbox = &TestEnv::new(); + let id = deploy_hello(sandbox).await; + let world = "world"; + let mut cmd = hello_world_cmd(&id, world); + cmd.config.source_account = String::new(); + cmd.is_view = true; + let res = sandbox.run_cmd_with(cmd, "test").await.unwrap(); + assert_eq!(res, format!(r#"["Hello",{world:?}]"#)); +} + #[tokio::test] async fn invoke() { let sandbox = &TestEnv::new(); @@ -140,12 +152,16 @@ fn invoke_hello_world(sandbox: &TestEnv, id: &str) { .success(); } -async fn invoke_hello_world_with_lib(e: &TestEnv, id: &str) { - let cmd = contract::invoke::Cmd { +fn hello_world_cmd(id: &str, arg: &str) -> contract::invoke::Cmd { + contract::invoke::Cmd { contract_id: id.to_string(), - slop: vec!["hello".into(), "--world=world".into()], + slop: vec!["hello".into(), format!("--world={arg}").into()], ..Default::default() - }; + } +} + +async fn invoke_hello_world_with_lib(e: &TestEnv, id: &str) { + let cmd = hello_world_cmd(id, "world"); let res = e.run_cmd_with(cmd, "test").await.unwrap(); assert_eq!(res, r#"["Hello","world"]"#); } diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index 41e7367dc..65557eda9 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -14,7 +14,9 @@ use std::{array::TryFromSliceError, fmt::Debug, num::ParseIntError}; use crate::{ commands::{ config::{self, data}, - global, network, NetworkRunnable, + global, network, + txn_result::TxnResult, + NetworkRunnable, }, rpc::{Client, Error as SorobanRpcError}, utils::{contract_id_hash_from_asset, parsing::parse_asset}, @@ -80,7 +82,7 @@ impl NetworkRunnable for Cmd { &self, args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result { + ) -> Result, Error> { let config = config.unwrap_or(&self.config); // Parse asset let asset = parse_asset(&self.asset)?; @@ -108,8 +110,11 @@ impl NetworkRunnable for Cmd { network_passphrase, &key, )?; + if self.fee.build_only { + return Ok(TxnResult::from_xdr(&tx)?); + } let txn = client.create_assembled_transaction(&tx).await?; - let txn = self.fee.apply_to_assembled_txn(txn); + let txn = self.fee.apply_to_assembled_txn(txn)?; let get_txn_resp = client .send_assembled_transaction(txn, &key, &[], network_passphrase, None, None) .await? @@ -118,7 +123,9 @@ impl NetworkRunnable for Cmd { data::write(get_txn_resp, &network.rpc_uri()?)?; } - Ok(stellar_strkey::Contract(contract_id.0).to_string()) + Ok(TxnResult::Xdr( + stellar_strkey::Contract(contract_id.0).to_string(), + )) } } diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 40369b230..7a2b929d4 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -17,7 +17,9 @@ use soroban_env_host::{ use crate::commands::{ config::data, contract::{self, id::wasm::get_contract_id}, - global, network, NetworkRunnable, + global, network, + txn_result::{self, TxnResult}, + NetworkRunnable, }; use crate::{ commands::{config, contract::install, HEADING_RPC}, @@ -93,6 +95,8 @@ pub enum Error { #[error(transparent)] WasmId(#[from] contract::id::wasm::Error), #[error(transparent)] + TxnResult(#[from] txn_result::Error), + #[error(transparent)] Data(#[from] data::Error), #[error(transparent)] Network(#[from] network::Error), @@ -115,18 +119,20 @@ impl NetworkRunnable for Cmd { &self, global_args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result { + ) -> Result, Error> { let config = config.unwrap_or(&self.config); let wasm_hash = if let Some(wasm) = &self.wasm { + let mut fee = self.fee.clone(); + fee.build_only = false; let hash = install::Cmd { wasm: wasm::Args { wasm: wasm.clone() }, config: config.clone(), - fee: self.fee.clone(), + fee, ignore_checks: self.ignore_checks, } .run_against_rpc_server(global_args, Some(config)) .await?; - hex::encode(hash) + hex::encode(hash.try_res()?) } else { self.wasm_hash .as_ref() @@ -169,8 +175,12 @@ impl NetworkRunnable for Cmd { salt, &key, )?; + if self.fee.build_only { + return Ok(TxnResult::from_xdr(&txn)?); + } + let txn = client.create_assembled_transaction(&txn).await?; - let txn = self.fee.apply_to_assembled_txn(txn); + let txn = self.fee.apply_to_assembled_txn(txn)?; let get_txn_resp = client .send_assembled_transaction(txn, &key, &[], &network.network_passphrase, None, None) .await? @@ -178,7 +188,9 @@ impl NetworkRunnable for Cmd { if global_args.map_or(true, |a| !a.no_cache) { data::write(get_txn_resp, &network.rpc_uri()?)?; } - Ok(stellar_strkey::Contract(contract_id.0).to_string()) + Ok(TxnResult::Res( + stellar_strkey::Contract(contract_id.0).to_string(), + )) } } diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index ab7834959..cebddd0c0 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -11,7 +11,9 @@ use soroban_env_host::xdr::{ use crate::{ commands::{ config::{self, data}, - global, network, NetworkRunnable, + global, network, + txn_result::TxnResult, + NetworkRunnable, }, key, rpc::{self, Client}, @@ -87,7 +89,11 @@ pub enum Error { impl Cmd { #[allow(clippy::too_many_lines)] pub async fn run(&self) -> Result<(), Error> { - let ttl_ledger = self.run_against_rpc_server(None, None).await?; + let res = self.run_against_rpc_server(None, None).await?; + let TxnResult::Res(ttl_ledger) = &res else { + println!("{}", res.xdr().unwrap()); + return Ok(()); + }; if self.ttl_ledger_only { println!("{ttl_ledger}"); } else { @@ -117,7 +123,7 @@ impl NetworkRunnable for Cmd { &self, args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result { + ) -> Result, Self::Error> { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); @@ -161,7 +167,9 @@ impl NetworkRunnable for Cmd { resource_fee: 0, }), }; - + if self.fee.build_only { + return Ok(TxnResult::from_xdr(&tx)?); + } let res = client .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) .await?; @@ -194,7 +202,7 @@ impl NetworkRunnable for Cmd { let entry = client.get_full_ledger_entries(&keys).await?; let extension = entry.entries[0].live_until_ledger_seq; if entry.latest_ledger + i64::from(extend_to) < i64::from(extension) { - return Ok(extension); + return Ok(TxnResult::Res(extension)); } } @@ -209,7 +217,7 @@ impl NetworkRunnable for Cmd { }), .. }), - ) => Ok(*live_until_ledger_seq), + ) => Ok(TxnResult::Res(*live_until_ledger_seq)), _ => Err(Error::LedgerEntryNotFound), } } diff --git a/cmd/soroban-cli/src/commands/contract/fetch.rs b/cmd/soroban-cli/src/commands/contract/fetch.rs index eefb1b4b8..5b223e71a 100644 --- a/cmd/soroban-cli/src/commands/contract/fetch.rs +++ b/cmd/soroban-cli/src/commands/contract/fetch.rs @@ -21,6 +21,7 @@ use stellar_strkey::DecodeError; use super::super::config::{self, locator}; use crate::commands::network::{self, Network}; +use crate::commands::txn_result::TxnResult; use crate::commands::{global, NetworkRunnable}; use crate::{ rpc::{self, Client}, @@ -116,7 +117,14 @@ impl Cmd { } pub async fn get_bytes(&self) -> Result, Error> { - self.run_against_rpc_server(None, None).await + // This is safe because fetch doesn't create a transaction + unsafe { + Ok(self + .run_against_rpc_server(None, None) + .await? + .res() + .unwrap_unchecked()) + } } pub fn network(&self) -> Result { @@ -137,7 +145,7 @@ impl NetworkRunnable for Cmd { &self, _args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result, Error> { + ) -> Result>, Error> { let network = config.map_or_else(|| self.network(), |c| Ok(c.get_network()?))?; tracing::trace!(?network); let contract_id = self.contract_id()?; @@ -146,7 +154,7 @@ impl NetworkRunnable for Cmd { .verify_network_passphrase(Some(&network.network_passphrase)) .await?; // async closures are not yet stable - Ok(client.get_remote_wasm(&contract_id).await?) + Ok(TxnResult::Res(client.get_remote_wasm(&contract_id).await?)) } } pub fn get_contract_wasm_from_storage( diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index 8763fd7e6..7208e1a93 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -12,6 +12,7 @@ use soroban_env_host::xdr::{ use super::restore; use crate::commands::network; +use crate::commands::txn_result::TxnResult; use crate::commands::{config::data, global, NetworkRunnable}; use crate::key; use crate::rpc::{self, Client}; @@ -72,7 +73,10 @@ pub enum Error { impl Cmd { pub async fn run(&self) -> Result<(), Error> { - let res_str = hex::encode(self.run_against_rpc_server(None, None).await?); + let res_str = match self.run_against_rpc_server(None, None).await? { + TxnResult::Xdr(xdr) => xdr, + TxnResult::Res(hash) => hex::encode(hash), + }; println!("{res_str}"); Ok(()) } @@ -86,7 +90,7 @@ impl NetworkRunnable for Cmd { &self, args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result { + ) -> Result, Error> { let config = config.unwrap_or(&self.config); let contract = self.wasm.read()?; let network = config.get_network()?; @@ -125,6 +129,9 @@ impl NetworkRunnable for Cmd { let (tx_without_preflight, hash) = build_install_contract_code_tx(&contract, sequence + 1, self.fee.fee, &key)?; + if self.fee.build_only { + return Ok(TxnResult::from_xdr(&tx_without_preflight)?); + } let code_key = xdr::LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() }); let contract_data = client.get_ledger_entries(&[code_key]).await?; @@ -142,7 +149,7 @@ impl NetworkRunnable for Cmd { // Skip reupload if this isn't V0 because V1 extension already // exists. if code.ext.ne(&ContractCodeEntryExt::V0) { - return Ok(hash); + return Ok(TxnResult::Res(hash)); } } _ => { @@ -151,11 +158,10 @@ impl NetworkRunnable for Cmd { } } } - let txn = client .create_assembled_transaction(&tx_without_preflight) .await?; - let txn = self.fee.apply_to_assembled_txn(txn); + let txn = self.fee.apply_to_assembled_txn(txn)?; let txn_resp = client .send_assembled_transaction(txn, &key, &[], &network.network_passphrase, None, None) .await?; @@ -189,7 +195,7 @@ impl NetworkRunnable for Cmd { if args.map_or(true, |a| !a.no_cache) { data::write_spec(&hash.to_string(), &wasm_spec.spec)?; } - Ok(hash) + Ok(TxnResult::Res(hash)) } } diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index d40f9c46f..00d7383d7 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -12,15 +12,16 @@ use heck::ToKebabCase; use soroban_env_host::{ xdr::{ - self, ContractDataEntry, Error as XdrError, Hash, HostFunction, InvokeContractArgs, - InvokeHostFunctionOp, LedgerEntryData, LedgerFootprint, Memo, MuxedAccount, Operation, - OperationBody, Preconditions, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, - ScVal, ScVec, SequenceNumber, SorobanAuthorizationEntry, SorobanResources, Transaction, + self, Hash, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, LedgerEntryData, + LedgerFootprint, Memo, MuxedAccount, Operation, OperationBody, Preconditions, PublicKey, + ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, ScVec, SequenceNumber, + SorobanAuthorizationEntry, SorobanResources, String32, StringM, Transaction, TransactionExt, Uint256, VecM, }, HostError, }; +use soroban_sdk::xdr::{AccountEntry, AccountEntryExt, AccountId, ContractDataEntry, DiagnosticEvent, Thresholds}; use soroban_spec::read::FromWasmError; use stellar_strkey::DecodeError; @@ -28,6 +29,7 @@ use super::super::{ config::{self, locator}, events, }; +use crate::commands::txn_result::TxnResult; use crate::commands::NetworkRunnable; use crate::{ commands::{config::data, global, network}, @@ -80,7 +82,7 @@ pub enum Error { error: soroban_spec_tools::Error, }, #[error("cannot add contract to ledger entries: {0}")] - CannotAddContractToLedgerEntries(XdrError), + CannotAddContractToLedgerEntries(xdr::Error), #[error(transparent)] // TODO: the Display impl of host errors is pretty user-unfriendly // (it just calls Debug). I think we can do better than that @@ -109,7 +111,7 @@ pub enum Error { error: soroban_spec_tools::Error, }, #[error(transparent)] - Xdr(#[from] XdrError), + Xdr(#[from] xdr::Error), #[error("error parsing int: {0}")] ParseIntError(#[from] ParseIntError), #[error(transparent)] @@ -169,6 +171,7 @@ impl Cmd { &self, contract_id: [u8; 32], spec_entries: &[ScSpecEntry], + config: &config::Args, ) -> Result<(String, Spec, InvokeContractArgs, Vec), Error> { let spec = Spec(Some(spec_entries.to_vec())); let mut cmd = clap::Command::new(self.contract_id.clone()) @@ -201,7 +204,7 @@ impl Cmd { let cmd = crate::commands::keys::address::Cmd { name: s.clone(), hd_path: Some(0), - locator: self.config.locator.clone(), + locator: config.locator.clone(), }; if let Ok(address) = cmd.public_key() { s = address.to_string(); @@ -272,7 +275,7 @@ impl Cmd { Ok(()) } - pub async fn invoke(&self, global_args: &global::Args) -> Result { + pub async fn invoke(&self, global_args: &global::Args) -> Result, Error> { self.run_against_rpc_server(Some(global_args), None).await } @@ -309,7 +312,7 @@ impl NetworkRunnable for Cmd { &self, global_args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result { + ) -> Result, Error> { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); @@ -317,19 +320,24 @@ impl NetworkRunnable for Cmd { let spec_entries = self.spec_entries()?; if let Some(spec_entries) = &spec_entries { // For testing wasm arg parsing - let _ = self.build_host_function_parameters(contract_id, spec_entries)?; + let _ = self.build_host_function_parameters(contract_id, spec_entries, config)?; } let client = rpc::Client::new(&network.rpc_url)?; - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; - let key = config.key_pair()?; - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - let account_details = client.get_account(&public_strkey).await?; + let account_details = if self.is_view { + default_account_entry() + } else { + client + .verify_network_passphrase(Some(&network.network_passphrase)) + .await?; + let key = config.key_pair()?; + + // Get the account sequence number + let public_strkey = + stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); + client.get_account(&public_strkey).await? + }; let sequence: i64 = account_details.seq_num.into(); + let AccountId(PublicKey::PublicKeyTypeEd25519(account_id)) = account_details.account_id; let r = client.get_contract_data(&contract_id).await?; tracing::trace!("{r:?}"); @@ -361,15 +369,18 @@ impl NetworkRunnable for Cmd { // Get the ledger footprint let (function, spec, host_function_params, signers) = - self.build_host_function_parameters(contract_id, &spec_entries)?; + self.build_host_function_parameters(contract_id, &spec_entries, config)?; let tx = build_invoke_contract_tx( host_function_params.clone(), sequence + 1, self.fee.fee, - &key, + account_id, )?; + if self.fee.build_only { + return Ok(TxnResult::from_xdr(&tx)?); + } let txn = client.create_assembled_transaction(&tx).await?; - let txn = self.fee.apply_to_assembled_txn(txn); + let txn = self.fee.apply_to_assembled_txn(txn)?; let sim_res = txn.sim_response(); if global_args.map_or(true, |a| !a.no_cache) { data::write(sim_res.clone().into(), &network.rpc_uri()?)?; @@ -386,7 +397,7 @@ impl NetworkRunnable for Cmd { let res = client .send_assembled_transaction( txn, - &key, + &config.key_pair()?, &signers, &network.network_passphrase, Some(log_events), @@ -404,10 +415,27 @@ impl NetworkRunnable for Cmd { } } +const DEFAULT_ACCOUNT_ID: AccountId = AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))); + +fn default_account_entry() -> AccountEntry { + AccountEntry { + account_id: DEFAULT_ACCOUNT_ID, + balance: 0, + seq_num: SequenceNumber(0), + num_sub_entries: 0, + inflation_dest: None, + flags: 0, + home_domain: String32::from(unsafe { StringM::<32>::from_str("TEST").unwrap_unchecked() }), + thresholds: Thresholds([0; 4]), + signers: unsafe { [].try_into().unwrap_unchecked() }, + ext: AccountEntryExt::V0, + } +} + fn log_events( footprint: &LedgerFootprint, auth: &[VecM], - events: &[xdr::DiagnosticEvent], + events: &[DiagnosticEvent], ) { crate::log::auth(auth); crate::log::diagnostic_events(events, tracing::Level::TRACE); @@ -418,7 +446,11 @@ fn log_resources(resources: &SorobanResources) { crate::log::cost(resources); } -pub fn output_to_string(spec: &Spec, res: &ScVal, function: &str) -> Result { +pub fn output_to_string( + spec: &Spec, + res: &ScVal, + function: &str, +) -> Result, Error> { let mut res_str = String::new(); if let Some(output) = spec.find_function(function)?.outputs.first() { res_str = spec @@ -429,14 +461,14 @@ pub fn output_to_string(spec: &Spec, res: &ScVal, function: &str) -> Result Result { let op = Operation { source_account: None, @@ -446,7 +478,7 @@ fn build_invoke_contract_tx( }), }; Ok(Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), + source_account: MuxedAccount::Ed25519(source_account_id), fee, seq_num: SequenceNumber(sequence), cond: Preconditions::None, @@ -506,16 +538,15 @@ fn build_custom_cmd(name: &str, spec: &Spec) -> Result { // Set up special-case arg rules arg = match type_ { - xdr::ScSpecTypeDef::Bool => arg + ScSpecTypeDef::Bool => arg .num_args(0..1) .default_missing_value("true") .default_value("false") .num_args(0..=1), - xdr::ScSpecTypeDef::Option(_val) => arg.required(false), - xdr::ScSpecTypeDef::I256 - | xdr::ScSpecTypeDef::I128 - | xdr::ScSpecTypeDef::I64 - | xdr::ScSpecTypeDef::I32 => arg.allow_hyphen_values(true), + ScSpecTypeDef::Option(_val) => arg.required(false), + ScSpecTypeDef::I256 | ScSpecTypeDef::I128 | ScSpecTypeDef::I64 | ScSpecTypeDef::I32 => { + arg.allow_hyphen_values(true) + } _ => arg, }; diff --git a/cmd/soroban-cli/src/commands/contract/read.rs b/cmd/soroban-cli/src/commands/contract/read.rs index a7b1d07a8..1d23e5c6a 100644 --- a/cmd/soroban-cli/src/commands/contract/read.rs +++ b/cmd/soroban-cli/src/commands/contract/read.rs @@ -14,7 +14,7 @@ use soroban_env_host::{ use soroban_sdk::xdr::Limits; use crate::{ - commands::{config, global, NetworkRunnable}, + commands::{config, global, txn_result::TxnResult, NetworkRunnable}, key, rpc::{self, Client, FullLedgerEntries, FullLedgerEntry}, }; @@ -91,7 +91,13 @@ pub enum Error { impl Cmd { pub async fn run(&self) -> Result<(), Error> { - let entries = self.run_against_rpc_server(None, None).await?; + let entries = match self.run_against_rpc_server(None, None).await? { + TxnResult::Res(res) => res, + TxnResult::Xdr(xdr) => { + println!("{xdr}"); + return Ok(()); + } + }; self.output_entries(&entries) } @@ -178,12 +184,12 @@ impl NetworkRunnable for Cmd { &self, _: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result { + ) -> Result, Error> { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); let client = Client::new(&network.rpc_url)?; let keys = self.key.parse_keys()?; - Ok(client.get_full_ledger_entries(&keys).await?) + Ok(TxnResult::Res(client.get_full_ledger_entries(&keys).await?)) } } diff --git a/cmd/soroban-cli/src/commands/contract/restore.rs b/cmd/soroban-cli/src/commands/contract/restore.rs index 8b5921a1a..6fc8eb17f 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -13,7 +13,9 @@ use crate::{ commands::{ config::{self, data, locator}, contract::extend, - global, network, NetworkRunnable, + global, network, + txn_result::{self, TxnResult}, + NetworkRunnable, }, key, rpc::{self, Client}, @@ -87,13 +89,21 @@ pub enum Error { Data(#[from] data::Error), #[error(transparent)] Network(#[from] network::Error), + + #[error(transparent)] + TxnResult(#[from] txn_result::Error), } impl Cmd { #[allow(clippy::too_many_lines)] pub async fn run(&self) -> Result<(), Error> { - let expiration_ledger_seq = self.run_against_rpc_server(None, None).await?; - + let expiration_ledger_seq = match self.run_against_rpc_server(None, None).await? { + TxnResult::Res(res) => res, + TxnResult::Xdr(xdr) => { + println!("{xdr}"); + return Ok(()); + } + }; if let Some(ledgers_to_extend) = self.ledgers_to_extend { extend::Cmd { key: self.key.clone(), @@ -121,7 +131,7 @@ impl NetworkRunnable for Cmd { &self, args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result { + ) -> Result, Error> { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); @@ -162,7 +172,9 @@ impl NetworkRunnable for Cmd { resource_fee: 0, }), }; - + if self.fee.build_only { + return Ok(TxnResult::from_xdr(&tx)?); + } let res = client .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) .await?; @@ -198,7 +210,9 @@ impl NetworkRunnable for Cmd { operations[0].changes.len() ); } - parse_operations(operations).ok_or(Error::MissingOperationResult) + Ok(TxnResult::Res( + parse_operations(operations).ok_or(Error::MissingOperationResult)?, + )) } } diff --git a/cmd/soroban-cli/src/commands/events.rs b/cmd/soroban-cli/src/commands/events.rs index 42145f5bf..f1707f349 100644 --- a/cmd/soroban-cli/src/commands/events.rs +++ b/cmd/soroban-cli/src/commands/events.rs @@ -5,7 +5,9 @@ use soroban_env_host::xdr::{self, Limits, ReadXdr}; use super::{ config::{self, locator}, - global, network, NetworkRunnable, + global, network, + txn_result::TxnResult, + NetworkRunnable, }; use crate::{rpc, utils}; @@ -124,6 +126,8 @@ pub enum Error { Locator(#[from] locator::Error), #[error(transparent)] Config(#[from] config::Error), + #[error(transparent)] + TxnResult(#[from] super::txn_result::Error), } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)] @@ -167,7 +171,8 @@ impl Cmd { })?; } - let response = self.run_against_rpc_server(None, None).await?; + let txn_res = self.run_against_rpc_server(None, None).await?; + let response = txn_res.try_res()?; for event in &response.events { match self.output { @@ -214,7 +219,7 @@ impl NetworkRunnable for Cmd { &self, _args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result { + ) -> Result, Error> { let start = self.start()?; let network = if let Some(config) = config { Ok(config.get_network()?) @@ -226,15 +231,17 @@ impl NetworkRunnable for Cmd { client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; - client - .get_events( - start, - Some(self.event_type), - &self.contract_ids, - &self.topic_filters, - Some(self.count), - ) - .await - .map_err(Error::Rpc) + Ok(TxnResult::Res( + client + .get_events( + start, + Some(self.event_type), + &self.contract_ids, + &self.topic_filters, + Some(self.count), + ) + .await + .map_err(Error::Rpc)?, + )) } } diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index 4bb5dcb53..f904c465f 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -14,6 +14,8 @@ pub mod network; pub mod plugin; pub mod version; +pub mod txn_result; + pub const HEADING_RPC: &str = "Options (RPC)"; const ABOUT: &str = "Build, deploy, & interact with contracts; set identities to sign with; configure networks; generate keys; and more. @@ -168,5 +170,5 @@ pub trait NetworkRunnable { &self, global_args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result; + ) -> Result, Self::Error>; } diff --git a/cmd/soroban-cli/src/commands/txn_result.rs b/cmd/soroban-cli/src/commands/txn_result.rs new file mode 100644 index 000000000..f69f549be --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn_result.rs @@ -0,0 +1,56 @@ +use std::fmt::{Display, Formatter}; + +use soroban_sdk::xdr::{self, Limits, WriteXdr}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Expect xdr string")] + XdrStringExpected, + #[error("Expect result")] + ResultExpected, +} + +pub enum TxnResult { + Xdr(String), + Res(T), +} + +impl TxnResult { + pub fn from_xdr(res: &impl WriteXdr) -> Result { + Ok(TxnResult::Xdr(res.to_xdr_base64(Limits::none())?)) + } + + pub fn xdr(&self) -> Option<&str> { + match self { + TxnResult::Xdr(xdr) => Some(xdr), + TxnResult::Res(_) => None, + } + } + + pub fn res(self) -> Option { + match self { + TxnResult::Res(res) => Some(res), + TxnResult::Xdr(_) => None, + } + } + + pub fn try_xdr(&self) -> Result<&str, Error> { + self.xdr().ok_or(Error::XdrStringExpected) + } + + pub fn try_res(self) -> Result { + self.res().ok_or(Error::ResultExpected) + } +} + +impl Display for TxnResult +where + T: Display, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + TxnResult::Xdr(xdr) => write!(f, "{xdr}"), + TxnResult::Res(res) => write!(f, "{res}"), + } + } +} diff --git a/cmd/soroban-cli/src/fee.rs b/cmd/soroban-cli/src/fee.rs index 353fe6e5d..f6ac76d58 100644 --- a/cmd/soroban-cli/src/fee.rs +++ b/cmd/soroban-cli/src/fee.rs @@ -1,5 +1,6 @@ use clap::arg; -use soroban_env_host::xdr; + +use soroban_env_host::xdr::{self, WriteXdr}; use soroban_rpc::Assembled; use crate::commands::HEADING_RPC; @@ -16,15 +17,31 @@ pub struct Args { /// Number of instructions to simulate #[arg(long, help_heading = HEADING_RPC)] pub instructions: Option, + /// Build the transaction only write the base64 xdr to stdout + #[arg(long, help_heading = HEADING_RPC)] + pub build_only: bool, + /// Simulation the transaction only write the base64 xdr to stdout + #[arg(long, help_heading = HEADING_RPC, conflicts_with = "build_only")] + pub sim_only: bool, } impl Args { - pub fn apply_to_assembled_txn(&self, txn: Assembled) -> Assembled { - if let Some(instructions) = self.instructions { + pub fn apply_to_assembled_txn(&self, txn: Assembled) -> Result { + let simulated_txn = if let Some(instructions) = self.instructions { txn.set_max_instructions(instructions) } else { add_padding_to_instructions(txn) + }; + if self.sim_only { + println!( + "{}", + simulated_txn + .transaction() + .to_xdr_base64(xdr::Limits::none())? + ); + std::process::exit(0); } + Ok(simulated_txn) } } @@ -47,6 +64,8 @@ impl Default for Args { fee: 100, cost: false, instructions: None, + build_only: false, + sim_only: false, } } } diff --git a/cmd/soroban-cli/src/lib.rs b/cmd/soroban-cli/src/lib.rs index d4118a6be..5cde45436 100644 --- a/cmd/soroban-cli/src/lib.rs +++ b/cmd/soroban-cli/src/lib.rs @@ -3,9 +3,10 @@ clippy::must_use_candidate, clippy::missing_panics_doc )] +use std::path::Path; + pub(crate) use soroban_env_host::xdr; pub(crate) use soroban_rpc as rpc; -use std::path::Path; pub mod commands; pub mod fee; diff --git a/docs/soroban-cli-full-docs.md b/docs/soroban-cli-full-docs.md index 766a7798e..9d10a653e 100644 --- a/docs/soroban-cli-full-docs.md +++ b/docs/soroban-cli-full-docs.md @@ -240,6 +240,14 @@ Deploy builtin Soroban Asset Contract Possible values: `true`, `false` * `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + @@ -391,6 +399,14 @@ If no keys are specified the contract itself is extended. Possible values: `true`, `false` * `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + @@ -423,6 +439,14 @@ Deploy a wasm contract Possible values: `true`, `false` * `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + * `-i`, `--ignore-checks` — Whether to ignore safety checks when deploying contracts Default value: `false` @@ -587,6 +611,14 @@ Install a WASM file to the ledger without creating a contract instance Possible values: `true`, `false` * `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + * `--wasm ` — Path to wasm binary * `-i`, `--ignore-checks` — Whether to ignore safety checks when deploying contracts @@ -636,6 +668,14 @@ soroban contract invoke ... -- --help Possible values: `true`, `false` * `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + @@ -748,6 +788,14 @@ If no keys are specificed the contract itself is restored. Possible values: `true`, `false` * `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + @@ -1339,6 +1387,9 @@ Read cached action * `--id ` — ID of the cache entry + Possible values: `envelope` + +