From 2a2a07fbca7b2e48900f0a1da53d42eeb55c187b Mon Sep 17 00:00:00 2001 From: Richard Ulrich Date: Mon, 12 Oct 2026 18:02:35 +0200 Subject: [PATCH 1/2] Adding esplora to validate the UTXOs This allows to validate proofs that no longer hold, but were valid in the past --- .github/workflows/cont_integration.yml | 2 +- Cargo.toml | 12 +++- src/reserves.rs | 80 ++++++++++++++++++++++++++ tests/data/active.psbt | 1 + tests/data/spent.psbt | 1 + tests/esplora.rs | 63 ++++++++++++++++++++ 6 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 tests/data/active.psbt create mode 100644 tests/data/spent.psbt create mode 100644 tests/esplora.rs diff --git a/.github/workflows/cont_integration.yml b/.github/workflows/cont_integration.yml index 3477e57..2067ef5 100644 --- a/.github/workflows/cont_integration.yml +++ b/.github/workflows/cont_integration.yml @@ -36,7 +36,7 @@ jobs: - name: Update Cargo.lock run: cargo update - name: Build - run: cargo build + run: cargo build --all-features - name: Clippy run: cargo clippy --all-targets -- -D warnings - name: Test diff --git a/Cargo.toml b/Cargo.toml index 4db37e3..311ee1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bdk-reserves" -version = "3.2.0" +version = "3.2.1" authors = ["Richard Ulrich "] edition = "2024" description = "Proof of reserves for bitcoin dev kit" @@ -9,16 +9,24 @@ readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/bitcoindevkit/bdk-reserves" +[features] +esplora = ["esplora-client", "rustls"] + [dependencies] bdk_wallet = { version = "3.2", default-features = false, features = ["std"] } bitcoinconsensus = "0.106" log = "^0.4" +esplora-client = { version = "0.13", optional = true, default-features = false, features = [ "async", "async-https", "tokio"] } # same version as in rust-bitcoin - I couldn't find a re-export units = { package = "bitcoin-units", version = "0.1", default-features = false, features = ["alloc"] } +# has to be manually synchronized with bdk_esplora until they add a re-export +rustls = { version = "0.23", optional = true } + [dev-dependencies] rstest = "^0.11" bdk_wallet = { version = "3.2", default-features = true, features = ["test-utils"] } bdk_electrum = "0.24" -electrsd = { version = "0.41", features = ["download", "bitcoind_download", "bitcoind_29_0", "electrs_0_10_6"] } \ No newline at end of file +electrsd = { version = "0.41", features = ["download", "bitcoind_download", "bitcoind_29_0", "electrs_0_10_6"] } +tokio = { version = "1", features = ["full"] } \ No newline at end of file diff --git a/src/reserves.rs b/src/reserves.rs index 512fc28..01d57cc 100644 --- a/src/reserves.rs +++ b/src/reserves.rs @@ -31,6 +31,8 @@ use bdk_wallet::bitcoin::{Amount, PubkeyHash, Sequence}; use bdk_wallet::chain::ChainPosition; use bdk_wallet::{AddForeignUtxoError, TxOrdering, Wallet}; use bdk_wallet::{error::CreateTxError, signer::SignerError}; +#[cfg(feature = "esplora")] +use esplora_client; use units::weight::Weight; /// The API for proof of reserves @@ -93,6 +95,10 @@ pub enum ProofError { Sign(SignerError), /// Most likely trying to inflate the amount DuplicateUtxo, + /// Error with the Esplora connection + EsploraError, + /// For the moment only supporting witness + WitnessMissing, } impl From for ProofError { @@ -356,6 +362,61 @@ fn challenge_txin(message: &str) -> TxIn { } } +pub struct OutpointsHeight { + pub outpoints: Vec<(OutPoint, TxOut)>, + pub unspent_height: u32, +} + +/// collect the information necessary to validate a proof +/// currently only works with witness transactions +/// In addition to the UTXOs, also returns the last block before the first UTXO was spent +/// This allows to validate proofs that no longer hold, but were valid in the past +#[cfg(feature = "esplora")] +pub async fn verify_prevouts( + psbt: &Psbt, + url: &str, + _height: Option, +) -> Result { + let client = esplora_client::Builder::new(url) + .header("user-agent", "bdk-reserves/3") + .build_async() + .map_err(|_| ProofError::EsploraError)?; + let current_height = client + .get_height() + .await + .map_err(|_| ProofError::EsploraError)?; + + let mut utxos = Vec::<(OutPoint, TxOut)>::new(); + let mut unspent_height = current_height; + + for (txin, inp) in psbt.unsigned_tx.input.iter().zip(psbt.inputs.iter()) { + let ostat = client + .get_output_status(&txin.previous_output.txid, txin.previous_output.vout.into()) + .await + .map_err(|_| ProofError::EsploraError)? + .ok_or(ProofError::EsploraError)?; + if ostat.spent { + let spent_state = ostat.status.ok_or(ProofError::MissingConfirmationInfo)?; + if spent_state.confirmed { + unspent_height = std::cmp::min( + unspent_height, + spent_state.block_height.ok_or(ProofError::EsploraError)? - 1, + ); + } + } + + let op = txin.previous_output; + if let Some(wutxo) = &inp.witness_utxo { + utxos.push((op, wutxo.clone())); + } + } + + Ok(OutpointsHeight { + outpoints: utxos, + unspent_height, + }) +} + #[cfg(test)] mod test { use super::*; @@ -395,6 +456,25 @@ mod test { assert_eq!(psbt_b64, expected); } + #[cfg(feature = "esplora")] + #[tokio::test] + async fn test_fetch_empty() { + let _ = rustls::crypto::CryptoProvider::install_default( + rustls::crypto::aws_lc_rs::default_provider(), + ); + + let descriptor = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"; + let (mut wallet, _) = get_funded_wallet_single(descriptor); + let message = "This is a test"; + let psbt = wallet.create_proof(message).unwrap(); + + let outpoints = verify_prevouts(&psbt, "https://ax102.blockeng.ch/", None) + .await + .unwrap(); + + assert_eq!(outpoints.outpoints.len(), 2); + } + #[test] #[should_panic(expected = "\"Key too short (<66 char), doesn't match any format\"")] fn invalid_descriptor() { diff --git a/tests/data/active.psbt b/tests/data/active.psbt new file mode 100644 index 0000000..5d6a054 --- /dev/null +++ b/tests/data/active.psbt @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/tests/data/spent.psbt b/tests/data/spent.psbt new file mode 100644 index 0000000..dec198c --- /dev/null +++ b/tests/data/spent.psbt @@ -0,0 +1 @@ +cHNidP8BAP03AwEAAAATfUqjtTyZAxfGOsqFi93k3ToGtPZ0E/BZWFlBgAFlt1kAAAAAAP////8VZFle1kNhN87Ee3jTlpqzhPY3376Bee8gryZ4EP0QxQAAAAAA/////xdqWOlIfYFpbDM+ZuBHu05GiQz+EKK/ebafYy50BPwqAAAAAAD/////K6q1ppFH2Ai6FYgXhqAP/i25RVrCNl7/LKkDKAfBedkAAAAAAP////8rqrWmkUfYCLoViBeGoA/+LblFWsI2Xv8sqQMoB8F52QEAAAAA/////yvmR/yPrZNvLPEWPdteixrpIrSe+mjGV0PRHwQvJ3skAAAAAAD/////cuwKmKQFtYW/+/3y8/ePnheAut3yDHv0R7HV22UhJX0AAAAAAP////9y7AqYpAW1hb/7/fLz94+eF4C63fIMe/RHsdXbZSElfQEAAAAA/////4ygvq0AS059XinGKxwy8SqKjRANTF6dU+CDPXemeDqVAAAAAAD/////jKC+rQBLTn1eKcYrHDLxKoqNEA1MXp1T4IM9d6Z4OpUBAAAAAP////+3xGKCPa4t1MGlkJ9jznWYBGdP9XZNMKbW+t7UvnNxzAAAAAAA/////7fEYoI9ri3UwaWQn2POdZgEZ0/1dk0wptb63tS+c3HMAQAAAAD/////wyKNGqQJpgaNszr5mLLEYQV6+lAMfXNndS/mn8PkXJ0AAAAAAP/////DIo0apAmmBo2zOvmYssRhBXr6UAx9c2d1L+afw+RcnQEAAAAA/////9Fninwz/x77J2ghJX0wcVNLRI3f3wMIlh5kePz8l2ZuAAAAAAD/////1AUOKakFoN1BqrDomHASI0VFsLtskXVQpPljoDU8zWsBAAAAAP/////swob+WCNq5562PWB+Z5JOFFogd/20GAr4Vyra6oOIAAAAAAAA/////+zChv5YI2rnnrY9YH5nkk4UWiB3/bQYCvhXKtrqg4gAAQAAAAD/////8pkjhcQSFD62iDk1sC4WLBUPcpKNoeup0O98xe4MF+kAAAAAAP////8BbOw1AwAAAAAZdqkUn3/QltN+0sDj9/DPySS+70/862iIrAAAAAAAAQEKAAAAAAAAAAABUQEHAAABASAoOPwCAAAAABepFBCNSAfpaNUWLsnOLKCLqO4EAl4UhyICAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FSDBFAiEA6crnwxlLYnlcWc2LovFA7qbw017cI//bmND/tKSNuMkCIDMCDYT7WXeJ5BRJGZuA+MRNs6sWdxo2Yo47bkUPQCS5ASICA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRySDBFAiEAreZ3cbl2oT7kEw7IDoU7ZF23rij0KFtuV4RqvkuXDuoCICueWRN9+sizOalX9N6tIr9hKe+W2Ib14K1QrjoGKhYVASICA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTSDBFAiEAnBtH9h2MP0ket2WG17u+yY3i+dS+Udqejcbi50Y+7zICIAn38DAz3z/bPIr9gJnxBip8d5GwRxWe/zSsYrzDcM5YAQEEIgAgdBDiqcx7V0LtGgDr8Co4bneNqt4doLQVuq7q8EAvTw0BBfFTIQIvUztmfi6js24hlhyf6dyjQPvgr1IQFzqDrgM3qyCldiECa7U6mOgQvQ7mGg7RFkumwCR4bXZVTnk+IC3Gzpx4xOohAtW4p9ZqQf/bb0xT1hmUAi6Ia09FAB+xWLlckWTUX4yjIQMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhSEDLTT4kyIAgzSHvSlKohncvgALn5s9gkeZVBQwAJ8PpVEhA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRyIQP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk1euAQcjIgAgdBDiqcx7V0LtGgDr8Co4bneNqt4doLQVuq7q8EAvTw0BCP3PAQUASDBFAiEA6crnwxlLYnlcWc2LovFA7qbw017cI//bmND/tKSNuMkCIDMCDYT7WXeJ5BRJGZuA+MRNs6sWdxo2Yo47bkUPQCS5AUgwRQIhAK3md3G5dqE+5BMOyA6FO2Rdt64o9ChbbleEar5Llw7qAiArnlkTffrIszmpV/TerSK/YSnvltiG9eCtUK46BioWFQFIMEUCIQCcG0f2HYw/SR63ZYbXu77JjeL51L5R2p6NxuLnRj7vMgIgCffwMDPfP9s8iv2AmfEGKnx3kbBHFZ7/NKxivMNwzlgB8VMhAi9TO2Z+LqOzbiGWHJ/p3KNA++CvUhAXOoOuAzerIKV2IQJrtTqY6BC9DuYaDtEWS6bAJHhtdlVOeT4gLcbOnHjE6iEC1bin1mpB/9tvTFPWGZQCLohrT0UAH7FYuVyRZNRfjKMhAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FIQMtNPiTIgCDNIe9KUqiGdy+AAufmz2CR5lUFDAAnw+lUSEDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HIhA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTV64AAQEgkNADAAAAAAAXqRQQjUgH6WjVFi7Jziygi6juBAJeFIciAgMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhUcwRAIgIPAaAfgPulkyQ5L6f2KTr7bIEWfBTBowsEyi9Aosr0ECIAsNTyysm/4CHhW4fN4dGC0JCUUedI0Z+0jldWcmiopoASICA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRyRzBEAiBO/Hb8owJsvAwLlLhITvCDyb0F4AcJ49xlIdiQcM0ETQIgWHvNFlXDhYjeCl3H9u0Jc/tEAhbTxTgFDR07DdaIcK0BIgID9y09lmY7DqmbCusNfyc8qxGo3jeIXx3dyNkRKtuHFpNHMEQCIBslyLRBXqm+kwjMszeUNWFBX8iZpeihmlo1s47BbMT/AiAYafOyRO+LmBm4x+EWMZ3VDzauxhung7FJAm/598b6mAEBBCIAIHQQ4qnMe1dC7RoA6/AqOG53jareHaC0Fbqu6vBAL08NAQXxUyECL1M7Zn4uo7NuIZYcn+nco0D74K9SEBc6g64DN6sgpXYhAmu1OpjoEL0O5hoO0RZLpsAkeG12VU55PiAtxs6ceMTqIQLVuKfWakH/229MU9YZlAIuiGtPRQAfsVi5XJFk1F+MoyEDJLde6tLB+cYOit615wCf7Hopr82zDYKdgtCVYv6LroUhAy00+JMiAIM0h70pSqIZ3L4AC5+bPYJHmVQUMACfD6VRIQN0aPjqmbbGR4g5i1rSVIDK0I9LDWW+VM46Vf0ga1rkciED9y09lmY7DqmbCusNfyc8qxGo3jeIXx3dyNkRKtuHFpNXrgEHIyIAIHQQ4qnMe1dC7RoA6/AqOG53jareHaC0Fbqu6vBAL08NAQj9zAEFAEcwRAIgIPAaAfgPulkyQ5L6f2KTr7bIEWfBTBowsEyi9Aosr0ECIAsNTyysm/4CHhW4fN4dGC0JCUUedI0Z+0jldWcmiopoAUcwRAIgTvx2/KMCbLwMC5S4SE7wg8m9BeAHCePcZSHYkHDNBE0CIFh7zRZVw4WI3gpdx/btCXP7RAIW08U4BQ0dOw3WiHCtAUcwRAIgGyXItEFeqb6TCMyzN5Q1YUFfyJml6KGaWjWzjsFsxP8CIBhp87JE74uYGbjH4RYxndUPNq7GG6eDsUkCb/n3xvqYAfFTIQIvUztmfi6js24hlhyf6dyjQPvgr1IQFzqDrgM3qyCldiECa7U6mOgQvQ7mGg7RFkumwCR4bXZVTnk+IC3Gzpx4xOohAtW4p9ZqQf/bb0xT1hmUAi6Ia09FAB+xWLlckWTUX4yjIQMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhSEDLTT4kyIAgzSHvSlKohncvgALn5s9gkeZVBQwAJ8PpVEhA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRyIQP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk1euAAEBIMToAgAAAAAAF6kUEI1IB+lo1RYuyc4soIuo7gQCXhSHIgIDJLde6tLB+cYOit615wCf7Hopr82zDYKdgtCVYv6LroVIMEUCIQC0teI6jSpNvTYMnaPvHBLHz8xeV78YSKHP0wDLTeIFggIgKJwbaMl8W0lphJppl+GpIda/WuptemyTsvvRxfDZh8IBIgIDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HJHMEQCIAuqur8TVlHrIYOWS8H1DM0ujqJOOPRrTzHHNY/PxsYEAiAq8VxXwyEEb+6DtbhYVffNGPsLI8KursWz162rnUw7XAEiAgP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk0YwQwIfOF7R8ET9GpC3LilmUZ/oTO3kCtEO33jfcRxTZMaf0gIgQ3PLFN/ia0aSa3ZjSGoXT6at1OmFDaw0JVcdUh5KQskBAQQiACB0EOKpzHtXQu0aAOvwKjhud42q3h2gtBW6rurwQC9PDQEF8VMhAi9TO2Z+LqOzbiGWHJ/p3KNA++CvUhAXOoOuAzerIKV2IQJrtTqY6BC9DuYaDtEWS6bAJHhtdlVOeT4gLcbOnHjE6iEC1bin1mpB/9tvTFPWGZQCLohrT0UAH7FYuVyRZNRfjKMhAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FIQMtNPiTIgCDNIe9KUqiGdy+AAufmz2CR5lUFDAAnw+lUSEDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HIhA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTV64BByMiACB0EOKpzHtXQu0aAOvwKjhud42q3h2gtBW6rurwQC9PDQEI/cwBBQBIMEUCIQC0teI6jSpNvTYMnaPvHBLHz8xeV78YSKHP0wDLTeIFggIgKJwbaMl8W0lphJppl+GpIda/WuptemyTsvvRxfDZh8IBRzBEAiALqrq/E1ZR6yGDlkvB9QzNLo6iTjj0a08xxzWPz8bGBAIgKvFcV8MhBG/ug7W4WFX3zRj7CyPCrq7Fs9etq51MO1wBRjBDAh84XtHwRP0akLcuKWZRn+hM7eQK0Q7feN9xHFNkxp/SAiBDc8sU3+JrRpJrdmNIahdPpq3U6YUNrDQlVx1SHkpCyQHxUyECL1M7Zn4uo7NuIZYcn+nco0D74K9SEBc6g64DN6sgpXYhAmu1OpjoEL0O5hoO0RZLpsAkeG12VU55PiAtxs6ceMTqIQLVuKfWakH/229MU9YZlAIuiGtPRQAfsVi5XJFk1F+MoyEDJLde6tLB+cYOit615wCf7Hopr82zDYKdgtCVYv6LroUhAy00+JMiAIM0h70pSqIZ3L4AC5+bPYJHmVQUMACfD6VRIQN0aPjqmbbGR4g5i1rSVIDK0I9LDWW+VM46Vf0ga1rkciED9y09lmY7DqmbCusNfyc8qxGo3jeIXx3dyNkRKtuHFpNXrgABASBAvAMAAAAAABepFBCNSAfpaNUWLsnOLKCLqO4EAl4UhyICAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FSDBFAiEA1/g2rzRk8SH4joG6KgolR3Duzs6MRsoqDHsYQFxpOeUCIFJNPgKVhztuek3nslD5goODjy9uH7zyxeCH1IpnVng+ASICA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRyRzBEAiB62Ajtue1nb0g5UPhCD/0XTeeMXOhkXIVzV97pSYwJQgIgY1jbyOjos8QBtSmSUsMinsYwUDusy5ipu20YLh4iPJQBIgID9y09lmY7DqmbCusNfyc8qxGo3jeIXx3dyNkRKtuHFpNIMEUCIQDL2gnT2r42FEhLgAiZnR8jsPTIeoJXFwhCFRmtZNR6qgIgOQqJSY75A5yNYU7iL46rrAA2OXN9VKORVqywBKEAQCABAQQiACB0EOKpzHtXQu0aAOvwKjhud42q3h2gtBW6rurwQC9PDQEF8VMhAi9TO2Z+LqOzbiGWHJ/p3KNA++CvUhAXOoOuAzerIKV2IQJrtTqY6BC9DuYaDtEWS6bAJHhtdlVOeT4gLcbOnHjE6iEC1bin1mpB/9tvTFPWGZQCLohrT0UAH7FYuVyRZNRfjKMhAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FIQMtNPiTIgCDNIe9KUqiGdy+AAufmz2CR5lUFDAAnw+lUSEDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HIhA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTV64BByMiACB0EOKpzHtXQu0aAOvwKjhud42q3h2gtBW6rurwQC9PDQEI/c4BBQBIMEUCIQDX+DavNGTxIfiOgboqCiVHcO7OzoxGyioMexhAXGk55QIgUk0+ApWHO256TeeyUPmCg4OPL24fvPLF4IfUimdWeD4BRzBEAiB62Ajtue1nb0g5UPhCD/0XTeeMXOhkXIVzV97pSYwJQgIgY1jbyOjos8QBtSmSUsMinsYwUDusy5ipu20YLh4iPJQBSDBFAiEAy9oJ09q+NhRIS4AImZ0fI7D0yHqCVxcIQhUZrWTUeqoCIDkKiUmO+QOcjWFO4i+Oq6wANjlzfVSjkVassAShAEAgAfFTIQIvUztmfi6js24hlhyf6dyjQPvgr1IQFzqDrgM3qyCldiECa7U6mOgQvQ7mGg7RFkumwCR4bXZVTnk+IC3Gzpx4xOohAtW4p9ZqQf/bb0xT1hmUAi6Ia09FAB+xWLlckWTUX4yjIQMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhSEDLTT4kyIAgzSHvSlKohncvgALn5s9gkeZVBQwAJ8PpVEhA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRyIQP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk1euAAEBIJDQAwAAAAAAF6kUEI1IB+lo1RYuyc4soIuo7gQCXhSHIgIDJLde6tLB+cYOit615wCf7Hopr82zDYKdgtCVYv6LroVHMEQCIG8RbiU4pfej6nYCvbRERTrOV7THtJ/xiFL83iKmn0STAiAgZE3tv89cnDkXzkUF/NWLu7jgx2aIOIw+oux59Ad89gEiAgN0aPjqmbbGR4g5i1rSVIDK0I9LDWW+VM46Vf0ga1rkckgwRQIhAN3hBE1+lZG9CspFe2Vi99jCfdxp7uT9wahGSKetI7DyAiACfY4axH2e8AC9HxxlUdEv3tF966p1AkRyXFVnFvKOiQEiAgP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk0gwRQIhAJxmYXVPXR8U+T7JAoacKaZ3LxKmGDOp6ZejLp1kBC2DAiB+9szpX3hTOhHXKyiJqCh/sOqI4JLN/lBZ/0+7Ib0keAEBBCIAIHQQ4qnMe1dC7RoA6/AqOG53jareHaC0Fbqu6vBAL08NAQXxUyECL1M7Zn4uo7NuIZYcn+nco0D74K9SEBc6g64DN6sgpXYhAmu1OpjoEL0O5hoO0RZLpsAkeG12VU55PiAtxs6ceMTqIQLVuKfWakH/229MU9YZlAIuiGtPRQAfsVi5XJFk1F+MoyEDJLde6tLB+cYOit615wCf7Hopr82zDYKdgtCVYv6LroUhAy00+JMiAIM0h70pSqIZ3L4AC5+bPYJHmVQUMACfD6VRIQN0aPjqmbbGR4g5i1rSVIDK0I9LDWW+VM46Vf0ga1rkciED9y09lmY7DqmbCusNfyc8qxGo3jeIXx3dyNkRKtuHFpNXrgEHIyIAIHQQ4qnMe1dC7RoA6/AqOG53jareHaC0Fbqu6vBAL08NAQj9zgEFAEcwRAIgbxFuJTil96PqdgK9tERFOs5XtMe0n/GIUvzeIqafRJMCICBkTe2/z1ycORfORQX81Yu7uODHZog4jD6i7Hn0B3z2AUgwRQIhAN3hBE1+lZG9CspFe2Vi99jCfdxp7uT9wahGSKetI7DyAiACfY4axH2e8AC9HxxlUdEv3tF966p1AkRyXFVnFvKOiQFIMEUCIQCcZmF1T10fFPk+yQKGnCmmdy8SphgzqemXoy6dZAQtgwIgfvbM6V94UzoR1ysoiagof7DqiOCSzf5QWf9PuyG9JHgB8VMhAi9TO2Z+LqOzbiGWHJ/p3KNA++CvUhAXOoOuAzerIKV2IQJrtTqY6BC9DuYaDtEWS6bAJHhtdlVOeT4gLcbOnHjE6iEC1bin1mpB/9tvTFPWGZQCLohrT0UAH7FYuVyRZNRfjKMhAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FIQMtNPiTIgCDNIe9KUqiGdy+AAufmz2CR5lUFDAAnw+lUSEDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HIhA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTV64AAQEgkNADAAAAAAAXqRQQjUgH6WjVFi7Jziygi6juBAJeFIciAgMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhUgwRQIhAJe1Byz1N0Z9WujF/vKFS9aHSpDQmC7lx2nvWACr5RCHAiAvVK+MUJuIIAh5+W5tZI/DMoN2V72My/8Mb/Qf29jsUgEiAgN0aPjqmbbGR4g5i1rSVIDK0I9LDWW+VM46Vf0ga1rkckcwRAIgQvtUBnniirlsWgZ28sS44likUKFj+BjKIGxU7x2UFnACIDj3WbTWwLNVjZmCjKlQLF9IxuUcRHFkn+psFxjgmmhLASICA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTRzBEAiAdNVtbZok1kacUYjwCD4G0iAIZoVIHYwcVhy+bcsKlEQIgVfmeZ9ATULCT21SF7AGuRsvPFFQNvZxOHj8nYCrFr3IBAQQiACB0EOKpzHtXQu0aAOvwKjhud42q3h2gtBW6rurwQC9PDQEF8VMhAi9TO2Z+LqOzbiGWHJ/p3KNA++CvUhAXOoOuAzerIKV2IQJrtTqY6BC9DuYaDtEWS6bAJHhtdlVOeT4gLcbOnHjE6iEC1bin1mpB/9tvTFPWGZQCLohrT0UAH7FYuVyRZNRfjKMhAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FIQMtNPiTIgCDNIe9KUqiGdy+AAufmz2CR5lUFDAAnw+lUSEDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HIhA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTV64BByMiACB0EOKpzHtXQu0aAOvwKjhud42q3h2gtBW6rurwQC9PDQEI/c0BBQBIMEUCIQCXtQcs9TdGfVroxf7yhUvWh0qQ0Jgu5cdp71gAq+UQhwIgL1SvjFCbiCAIeflubWSPwzKDdle9jMv/DG/0H9vY7FIBRzBEAiBC+1QGeeKKuWxaBnbyxLjiWKRQoWP4GMogbFTvHZQWcAIgOPdZtNbAs1WNmYKMqVAsX0jG5RxEcWSf6mwXGOCaaEsBRzBEAiAdNVtbZok1kacUYjwCD4G0iAIZoVIHYwcVhy+bcsKlEQIgVfmeZ9ATULCT21SF7AGuRsvPFFQNvZxOHj8nYCrFr3IB8VMhAi9TO2Z+LqOzbiGWHJ/p3KNA++CvUhAXOoOuAzerIKV2IQJrtTqY6BC9DuYaDtEWS6bAJHhtdlVOeT4gLcbOnHjE6iEC1bin1mpB/9tvTFPWGZQCLohrT0UAH7FYuVyRZNRfjKMhAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FIQMtNPiTIgCDNIe9KUqiGdy+AAufmz2CR5lUFDAAnw+lUSEDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HIhA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTV64AAQEgoMsCAAAAAAAXqRQQjUgH6WjVFi7Jziygi6juBAJeFIciAgMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhUgwRQIhAIaWIg/RLaQ2Kv2PJZBwrVsK6QkGO5oc6Gax5pMUJu1HAiAGLpU1ShiqbbGpnC1t6K0zYWMPfm5XuHKNfI/Z5XwJrwEiAgN0aPjqmbbGR4g5i1rSVIDK0I9LDWW+VM46Vf0ga1rkckgwRQIhALkJ3WI0WfmEDEWB8yN8J1jqyY92BoFGyJOmB8nAbZNeAiAgzrzyb2wLaVyl4LXFHE40GTa6HkmopRDN+35zJZb2yQEiAgP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk0gwRQIhANKRAxXD6w6U66SVPj+6JtU2u10UttnkCIoQZKBadntDAiAvcgibqGLyogPYkZgtI72qs2coYX3zrOfLOdPDOtaYhgEBBCIAIHQQ4qnMe1dC7RoA6/AqOG53jareHaC0Fbqu6vBAL08NAQXxUyECL1M7Zn4uo7NuIZYcn+nco0D74K9SEBc6g64DN6sgpXYhAmu1OpjoEL0O5hoO0RZLpsAkeG12VU55PiAtxs6ceMTqIQLVuKfWakH/229MU9YZlAIuiGtPRQAfsVi5XJFk1F+MoyEDJLde6tLB+cYOit615wCf7Hopr82zDYKdgtCVYv6LroUhAy00+JMiAIM0h70pSqIZ3L4AC5+bPYJHmVQUMACfD6VRIQN0aPjqmbbGR4g5i1rSVIDK0I9LDWW+VM46Vf0ga1rkciED9y09lmY7DqmbCusNfyc8qxGo3jeIXx3dyNkRKtuHFpNXrgEHIyIAIHQQ4qnMe1dC7RoA6/AqOG53jareHaC0Fbqu6vBAL08NAQj9zwEFAEgwRQIhAIaWIg/RLaQ2Kv2PJZBwrVsK6QkGO5oc6Gax5pMUJu1HAiAGLpU1ShiqbbGpnC1t6K0zYWMPfm5XuHKNfI/Z5XwJrwFIMEUCIQC5Cd1iNFn5hAxFgfMjfCdY6smPdgaBRsiTpgfJwG2TXgIgIM688m9sC2lcpeC1xRxONBk2uh5JqKUQzft+cyWW9skBSDBFAiEA0pEDFcPrDpTrpJU+P7om1Ta7XRS22eQIihBkoFp2e0MCIC9yCJuoYvKiA9iRmC0jvaqzZyhhffOs58s508M61piGAfFTIQIvUztmfi6js24hlhyf6dyjQPvgr1IQFzqDrgM3qyCldiECa7U6mOgQvQ7mGg7RFkumwCR4bXZVTnk+IC3Gzpx4xOohAtW4p9ZqQf/bb0xT1hmUAi6Ia09FAB+xWLlckWTUX4yjIQMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhSEDLTT4kyIAgzSHvSlKohncvgALn5s9gkeZVBQwAJ8PpVEhA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRyIQP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk1euAAEBIEC8AwAAAAAAF6kUEI1IB+lo1RYuyc4soIuo7gQCXhSHIgIDJLde6tLB+cYOit615wCf7Hopr82zDYKdgtCVYv6LroVHMEQCIDcrSuHCIy8dYDwYX2fk04o7gNLgAKGPIL9TJMfa1HwTAiAFTr+kHxCeNPAad8ueul5ZqEU0aasIHitJQMmMgepoDwEiAgN0aPjqmbbGR4g5i1rSVIDK0I9LDWW+VM46Vf0ga1rkckgwRQIhANyomNej5S0KfovKPU29hzDyylO/E1QGJXlrvV6QLj/NAiAprLPC3aNM5jQ6gxF7Uv7kgf+x9Tb4/OEIMvDdEal/wgEiAgP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk0cwRAIgDBhvIR9ZZzL5bRf6PMMbpi1v7/13gK/CJScbtapq3egCIFW2hwPcFDNGRfI25E8qxgSKaeIJmF+3nKEN5aX+ct/CAQEEIgAgdBDiqcx7V0LtGgDr8Co4bneNqt4doLQVuq7q8EAvTw0BBfFTIQIvUztmfi6js24hlhyf6dyjQPvgr1IQFzqDrgM3qyCldiECa7U6mOgQvQ7mGg7RFkumwCR4bXZVTnk+IC3Gzpx4xOohAtW4p9ZqQf/bb0xT1hmUAi6Ia09FAB+xWLlckWTUX4yjIQMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhSEDLTT4kyIAgzSHvSlKohncvgALn5s9gkeZVBQwAJ8PpVEhA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRyIQP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk1euAQcjIgAgdBDiqcx7V0LtGgDr8Co4bneNqt4doLQVuq7q8EAvTw0BCP3NAQUARzBEAiA3K0rhwiMvHWA8GF9n5NOKO4DS4AChjyC/UyTH2tR8EwIgBU6/pB8QnjTwGnfLnrpeWahFNGmrCB4rSUDJjIHqaA8BSDBFAiEA3KiY16PlLQp+i8o9Tb2HMPLKU78TVAYleWu9XpAuP80CICmss8Ldo0zmNDqDEXtS/uSB/7H1Nvj84Qgy8N0RqX/CAUcwRAIgDBhvIR9ZZzL5bRf6PMMbpi1v7/13gK/CJScbtapq3egCIFW2hwPcFDNGRfI25E8qxgSKaeIJmF+3nKEN5aX+ct/CAfFTIQIvUztmfi6js24hlhyf6dyjQPvgr1IQFzqDrgM3qyCldiECa7U6mOgQvQ7mGg7RFkumwCR4bXZVTnk+IC3Gzpx4xOohAtW4p9ZqQf/bb0xT1hmUAi6Ia09FAB+xWLlckWTUX4yjIQMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhSEDLTT4kyIAgzSHvSlKohncvgALn5s9gkeZVBQwAJ8PpVEhA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRyIQP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk1euAAEBIGB0AwAAAAAAF6kUEI1IB+lo1RYuyc4soIuo7gQCXhSHIgIDJLde6tLB+cYOit615wCf7Hopr82zDYKdgtCVYv6LroVIMEUCIQDiOpeNLn9TUH52eaL716+dvJG3izzKgeNikj6rG0UWZgIgJJPWxBKq6wWUoOoLfNsdvXqehOqMzAPdvWcRVAWE3mgBIgIDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HJIMEUCIQCbQIiX6cMvg8tBX+NGPcUlrlNOD2NcOIYem7f0JTn9eAIgBeTpWQU5o3+Gj0pNdcDMZCOfIDRVxqUj4N8wdNsxXAcBIgID9y09lmY7DqmbCusNfyc8qxGo3jeIXx3dyNkRKtuHFpNIMEUCIQCJSwnjDM8L3cLDOvuPEZBu/ZNvy8nccMZgquCCBsZ/RQIgdyRS2fD9JzLsfj0cY5ISJlLs63R4uEd4ZHv25a/2ysgBAQQiACB0EOKpzHtXQu0aAOvwKjhud42q3h2gtBW6rurwQC9PDQEF8VMhAi9TO2Z+LqOzbiGWHJ/p3KNA++CvUhAXOoOuAzerIKV2IQJrtTqY6BC9DuYaDtEWS6bAJHhtdlVOeT4gLcbOnHjE6iEC1bin1mpB/9tvTFPWGZQCLohrT0UAH7FYuVyRZNRfjKMhAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FIQMtNPiTIgCDNIe9KUqiGdy+AAufmz2CR5lUFDAAnw+lUSEDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HIhA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTV64BByMiACB0EOKpzHtXQu0aAOvwKjhud42q3h2gtBW6rurwQC9PDQEI/c8BBQBIMEUCIQDiOpeNLn9TUH52eaL716+dvJG3izzKgeNikj6rG0UWZgIgJJPWxBKq6wWUoOoLfNsdvXqehOqMzAPdvWcRVAWE3mgBSDBFAiEAm0CIl+nDL4PLQV/jRj3FJa5TTg9jXDiGHpu39CU5/XgCIAXk6VkFOaN/ho9KTXXAzGQjnyA0VcalI+DfMHTbMVwHAUgwRQIhAIlLCeMMzwvdwsM6+48RkG79k2/LydxwxmCq4IIGxn9FAiB3JFLZ8P0nMux+PRxjkhImUuzrdHi4R3hke/blr/bKyAHxUyECL1M7Zn4uo7NuIZYcn+nco0D74K9SEBc6g64DN6sgpXYhAmu1OpjoEL0O5hoO0RZLpsAkeG12VU55PiAtxs6ceMTqIQLVuKfWakH/229MU9YZlAIuiGtPRQAfsVi5XJFk1F+MoyEDJLde6tLB+cYOit615wCf7Hopr82zDYKdgtCVYv6LroUhAy00+JMiAIM0h70pSqIZ3L4AC5+bPYJHmVQUMACfD6VRIQN0aPjqmbbGR4g5i1rSVIDK0I9LDWW+VM46Vf0ga1rkciED9y09lmY7DqmbCusNfyc8qxGo3jeIXx3dyNkRKtuHFpNXrgABASAUkwMAAAAAABepFBCNSAfpaNUWLsnOLKCLqO4EAl4UhyICAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FSDBFAiEAgOQshynIa1z5uOeZe1LzWwJJnpfNw0ioRQU8LNFuHzsCIC0fRCyCT/Lbv7aOFAPaV2MPE3fcSRbHoatLebaur3dHASICA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRyRzBEAiAkRkDvUp/7DYDkjA2PqbL6hYsXaQfhjN34JQxofNQ8jQIgZlndSNbEE6ftp1M/+DOmi8G/eBO+iux5skc2FDFR/qkBIgID9y09lmY7DqmbCusNfyc8qxGo3jeIXx3dyNkRKtuHFpNIMEUCIQDvcrYYdDvLUpxX9E8CXV64vL+71+Ae1bXGZsUrERWeJAIgTUwCgbqNXQBv8rfs1plIbW0WgRuXRfZykTAfivyNZDABAQQiACB0EOKpzHtXQu0aAOvwKjhud42q3h2gtBW6rurwQC9PDQEF8VMhAi9TO2Z+LqOzbiGWHJ/p3KNA++CvUhAXOoOuAzerIKV2IQJrtTqY6BC9DuYaDtEWS6bAJHhtdlVOeT4gLcbOnHjE6iEC1bin1mpB/9tvTFPWGZQCLohrT0UAH7FYuVyRZNRfjKMhAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FIQMtNPiTIgCDNIe9KUqiGdy+AAufmz2CR5lUFDAAnw+lUSEDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HIhA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTV64BByMiACB0EOKpzHtXQu0aAOvwKjhud42q3h2gtBW6rurwQC9PDQEI/c4BBQBIMEUCIQCA5CyHKchrXPm455l7UvNbAkmel83DSKhFBTws0W4fOwIgLR9ELIJP8tu/to4UA9pXYw8Td9xJFsehq0t5tq6vd0cBRzBEAiAkRkDvUp/7DYDkjA2PqbL6hYsXaQfhjN34JQxofNQ8jQIgZlndSNbEE6ftp1M/+DOmi8G/eBO+iux5skc2FDFR/qkBSDBFAiEA73K2GHQ7y1KcV/RPAl1euLy/u9fgHtW1xmbFKxEVniQCIE1MAoG6jV0Ab/K37NaZSG1tFoEbl0X2cpEwH4r8jWQwAfFTIQIvUztmfi6js24hlhyf6dyjQPvgr1IQFzqDrgM3qyCldiECa7U6mOgQvQ7mGg7RFkumwCR4bXZVTnk+IC3Gzpx4xOohAtW4p9ZqQf/bb0xT1hmUAi6Ia09FAB+xWLlckWTUX4yjIQMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhSEDLTT4kyIAgzSHvSlKohncvgALn5s9gkeZVBQwAJ8PpVEhA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRyIQP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk1euAAEBIJDQAwAAAAAAF6kUEI1IB+lo1RYuyc4soIuo7gQCXhSHIgIDJLde6tLB+cYOit615wCf7Hopr82zDYKdgtCVYv6LroVIMEUCIQC2uoCNKThiMkY4hS0N/RjgjTL9xHyXfpUu8YRhG8IpsAIgbsPsv6IVfIOfkOjeLCOZ0M3HaY4y2VGjtlimyYKxajwBIgIDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HJHMEQCID4kiDHLNloY2scrbYxkbLYl0tztci2c8z6OCcd4tANmAiB063HT9xQXn3hxyCbkSQbspPuggC6/o/rCWj3pyZgtqQEiAgP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk0gwRQIhANabxBh+izQPr11tLskTDYo0TPBwW/FCnUPd4gYzdkZXAiAKfVxwkrXQEPWKfUadqfkuYTO9Ts85LKB4g+3ul+EcQQEBBCIAIHQQ4qnMe1dC7RoA6/AqOG53jareHaC0Fbqu6vBAL08NAQXxUyECL1M7Zn4uo7NuIZYcn+nco0D74K9SEBc6g64DN6sgpXYhAmu1OpjoEL0O5hoO0RZLpsAkeG12VU55PiAtxs6ceMTqIQLVuKfWakH/229MU9YZlAIuiGtPRQAfsVi5XJFk1F+MoyEDJLde6tLB+cYOit615wCf7Hopr82zDYKdgtCVYv6LroUhAy00+JMiAIM0h70pSqIZ3L4AC5+bPYJHmVQUMACfD6VRIQN0aPjqmbbGR4g5i1rSVIDK0I9LDWW+VM46Vf0ga1rkciED9y09lmY7DqmbCusNfyc8qxGo3jeIXx3dyNkRKtuHFpNXrgEHIyIAIHQQ4qnMe1dC7RoA6/AqOG53jareHaC0Fbqu6vBAL08NAQj9zgEFAEgwRQIhALa6gI0pOGIyRjiFLQ39GOCNMv3EfJd+lS7xhGEbwimwAiBuw+y/ohV8g5+Q6N4sI5nQzcdpjjLZUaO2WKbJgrFqPAFHMEQCID4kiDHLNloY2scrbYxkbLYl0tztci2c8z6OCcd4tANmAiB063HT9xQXn3hxyCbkSQbspPuggC6/o/rCWj3pyZgtqQFIMEUCIQDWm8QYfos0D69dbS7JEw2KNEzwcFvxQp1D3eIGM3ZGVwIgCn1ccJK10BD1in1Gnan5LmEzvU7POSygeIPt7pfhHEEB8VMhAi9TO2Z+LqOzbiGWHJ/p3KNA++CvUhAXOoOuAzerIKV2IQJrtTqY6BC9DuYaDtEWS6bAJHhtdlVOeT4gLcbOnHjE6iEC1bin1mpB/9tvTFPWGZQCLohrT0UAH7FYuVyRZNRfjKMhAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FIQMtNPiTIgCDNIe9KUqiGdy+AAufmz2CR5lUFDAAnw+lUSEDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HIhA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTV64AAQEgkNADAAAAAAAXqRQQjUgH6WjVFi7Jziygi6juBAJeFIciAgMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhUgwRQIhAJi8clrjwM1svGNRYYAjbDVdW1Dy/qcwbzRdkK22ZxmJAiAWYiFtqswmynT8tMxXCkCUXiTwO5S47DzB+c95bEcQRwEiAgN0aPjqmbbGR4g5i1rSVIDK0I9LDWW+VM46Vf0ga1rkckgwRQIhAOYEdTY4CF6uEbZUq+0jHn2wWrRS+hSE9Pw/owayR76qAiBToNj2JBrMhiZmEDC4pom+5uq0lLkA1i3sU0Q/sGeBZgEiAgP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk0gwRQIhALTX4VG5eJlIS4uNNWcBHWDuEDmHuJwdeLQNY3O0KaaYAiBpVSpEgvu8pcWo0Hedv9D9qZLnFGCx9ITt0462qLbNhAEBBCIAIHQQ4qnMe1dC7RoA6/AqOG53jareHaC0Fbqu6vBAL08NAQXxUyECL1M7Zn4uo7NuIZYcn+nco0D74K9SEBc6g64DN6sgpXYhAmu1OpjoEL0O5hoO0RZLpsAkeG12VU55PiAtxs6ceMTqIQLVuKfWakH/229MU9YZlAIuiGtPRQAfsVi5XJFk1F+MoyEDJLde6tLB+cYOit615wCf7Hopr82zDYKdgtCVYv6LroUhAy00+JMiAIM0h70pSqIZ3L4AC5+bPYJHmVQUMACfD6VRIQN0aPjqmbbGR4g5i1rSVIDK0I9LDWW+VM46Vf0ga1rkciED9y09lmY7DqmbCusNfyc8qxGo3jeIXx3dyNkRKtuHFpNXrgEHIyIAIHQQ4qnMe1dC7RoA6/AqOG53jareHaC0Fbqu6vBAL08NAQj9zwEFAEgwRQIhAJi8clrjwM1svGNRYYAjbDVdW1Dy/qcwbzRdkK22ZxmJAiAWYiFtqswmynT8tMxXCkCUXiTwO5S47DzB+c95bEcQRwFIMEUCIQDmBHU2OAherhG2VKvtIx59sFq0UvoUhPT8P6MGske+qgIgU6DY9iQazIYmZhAwuKaJvubqtJS5ANYt7FNEP7BngWYBSDBFAiEAtNfhUbl4mUhLi401ZwEdYO4QOYe4nB14tA1jc7QpppgCIGlVKkSC+7ylxajQd52/0P2pkucUYLH0hO3Tjraots2EAfFTIQIvUztmfi6js24hlhyf6dyjQPvgr1IQFzqDrgM3qyCldiECa7U6mOgQvQ7mGg7RFkumwCR4bXZVTnk+IC3Gzpx4xOohAtW4p9ZqQf/bb0xT1hmUAi6Ia09FAB+xWLlckWTUX4yjIQMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhSEDLTT4kyIAgzSHvSlKohncvgALn5s9gkeZVBQwAJ8PpVEhA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRyIQP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk1euAAEBIFCxAwAAAAAAF6kUEI1IB+lo1RYuyc4soIuo7gQCXhSHIgIDJLde6tLB+cYOit615wCf7Hopr82zDYKdgtCVYv6LroVIMEUCIQCIM5+AE0LNL0dGLIWwwWL/sGLD9w4uqZBPD5wytiXE5QIgC7bB/hWaroji6p9U7dKeSwoXSlTpLJ6eTLl/ju1N/zYBIgIDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HJHMEQCIEiYTF/w3BSS0raWSeD5sZ3+xAVMq2wi3lhthIPrNYvEAiAfIvJGodRLNO//Rtdo8DFkvtx7Ea/lzWADz8ylHwDrywEiAgP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk0gwRQIhANpOLX/6I5W/tzbXnGFOC1aIjTtsfT35FxzL6jxD0pKvAiA9vXRG9jderWI4cLIi0Q0rCmknKxY+Fm+bULY00JEZAgEBBCIAIHQQ4qnMe1dC7RoA6/AqOG53jareHaC0Fbqu6vBAL08NAQXxUyECL1M7Zn4uo7NuIZYcn+nco0D74K9SEBc6g64DN6sgpXYhAmu1OpjoEL0O5hoO0RZLpsAkeG12VU55PiAtxs6ceMTqIQLVuKfWakH/229MU9YZlAIuiGtPRQAfsVi5XJFk1F+MoyEDJLde6tLB+cYOit615wCf7Hopr82zDYKdgtCVYv6LroUhAy00+JMiAIM0h70pSqIZ3L4AC5+bPYJHmVQUMACfD6VRIQN0aPjqmbbGR4g5i1rSVIDK0I9LDWW+VM46Vf0ga1rkciED9y09lmY7DqmbCusNfyc8qxGo3jeIXx3dyNkRKtuHFpNXrgEHIyIAIHQQ4qnMe1dC7RoA6/AqOG53jareHaC0Fbqu6vBAL08NAQj9zgEFAEgwRQIhAIgzn4ATQs0vR0YshbDBYv+wYsP3Di6pkE8PnDK2JcTlAiALtsH+FZquiOLqn1Tt0p5LChdKVOksnp5MuX+O7U3/NgFHMEQCIEiYTF/w3BSS0raWSeD5sZ3+xAVMq2wi3lhthIPrNYvEAiAfIvJGodRLNO//Rtdo8DFkvtx7Ea/lzWADz8ylHwDrywFIMEUCIQDaTi1/+iOVv7c215xhTgtWiI07bH09+Rccy+o8Q9KSrwIgPb10RvY3Xq1iOHCyItENKwppJysWPhZvm1C2NNCRGQIB8VMhAi9TO2Z+LqOzbiGWHJ/p3KNA++CvUhAXOoOuAzerIKV2IQJrtTqY6BC9DuYaDtEWS6bAJHhtdlVOeT4gLcbOnHjE6iEC1bin1mpB/9tvTFPWGZQCLohrT0UAH7FYuVyRZNRfjKMhAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FIQMtNPiTIgCDNIe9KUqiGdy+AAufmz2CR5lUFDAAnw+lUSEDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HIhA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTV64AAQEgBOIAAAAAAAAXqRQQjUgH6WjVFi7Jziygi6juBAJeFIciAgMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhUcwRAIgZhRZTYdYBLBTgCCXf6UFkn31RHY7ed51EEfODPTP3FgCIBTu3pHyCvvQg2Z8ooA9qs4HQyFDy2wVWER6sRW9qEsTASICA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRySDBFAiEAp20ai85KnRTfxfhULKMBZBO65gJ6lCyoUw01O3BbO3gCIHs5mPC4WIxiHmbHCDrIClZ6hfA5E741zGRJNsTl4i2aASICA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTRzBEAiB0HiwaMfMG+/AtVIjNS6AJR2TcDtLEpLNlC7idavov+QIgRd3RJsUWYv9v+RSf3D6SzetUH5s6ua9RiKNVi4BQ6+cBAQQiACB0EOKpzHtXQu0aAOvwKjhud42q3h2gtBW6rurwQC9PDQEF8VMhAi9TO2Z+LqOzbiGWHJ/p3KNA++CvUhAXOoOuAzerIKV2IQJrtTqY6BC9DuYaDtEWS6bAJHhtdlVOeT4gLcbOnHjE6iEC1bin1mpB/9tvTFPWGZQCLohrT0UAH7FYuVyRZNRfjKMhAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FIQMtNPiTIgCDNIe9KUqiGdy+AAufmz2CR5lUFDAAnw+lUSEDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HIhA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTV64BByMiACB0EOKpzHtXQu0aAOvwKjhud42q3h2gtBW6rurwQC9PDQEI/c0BBQBHMEQCIGYUWU2HWASwU4Agl3+lBZJ99UR2O3nedRBHzgz0z9xYAiAU7t6R8gr70INmfKKAParOB0MhQ8tsFVhEerEVvahLEwFIMEUCIQCnbRqLzkqdFN/F+FQsowFkE7rmAnqULKhTDTU7cFs7eAIgezmY8LhYjGIeZscIOsgKVnqF8DkTvjXMZEk2xOXiLZoBRzBEAiB0HiwaMfMG+/AtVIjNS6AJR2TcDtLEpLNlC7idavov+QIgRd3RJsUWYv9v+RSf3D6SzetUH5s6ua9RiKNVi4BQ6+cB8VMhAi9TO2Z+LqOzbiGWHJ/p3KNA++CvUhAXOoOuAzerIKV2IQJrtTqY6BC9DuYaDtEWS6bAJHhtdlVOeT4gLcbOnHjE6iEC1bin1mpB/9tvTFPWGZQCLohrT0UAH7FYuVyRZNRfjKMhAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FIQMtNPiTIgCDNIe9KUqiGdy+AAufmz2CR5lUFDAAnw+lUSEDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HIhA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTV64AAQEgQA0DAAAAAAAXqRQQjUgH6WjVFi7Jziygi6juBAJeFIciAgMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhUgwRQIhALcITbBJx25ndqVAny1w6130pNTZTe/v6eWt87SOY3isAiAOp4eItSRav49fOE3+HsF8eJlyImn1MLEJiBxdsyhkhwEiAgN0aPjqmbbGR4g5i1rSVIDK0I9LDWW+VM46Vf0ga1rkckgwRQIhAJ4PzjzZbK3tAb1V7af8jEPd3PLA+7BaeyyaBfofoNlPAiBFkygCOx0q49gnpuwe61MupyY/Fcp4ZsAZWzp42qSoNQEiAgP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk0cwRAIgH8CbB/1fbi7RGIVFpJP91vbTIkdOqJ01WwP/tAHRX1MCIFj14hUjOIFgXLqD1Ztvftgxz4Oa8iv+4YmRtrXHjC6JAQEEIgAgdBDiqcx7V0LtGgDr8Co4bneNqt4doLQVuq7q8EAvTw0BBfFTIQIvUztmfi6js24hlhyf6dyjQPvgr1IQFzqDrgM3qyCldiECa7U6mOgQvQ7mGg7RFkumwCR4bXZVTnk+IC3Gzpx4xOohAtW4p9ZqQf/bb0xT1hmUAi6Ia09FAB+xWLlckWTUX4yjIQMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhSEDLTT4kyIAgzSHvSlKohncvgALn5s9gkeZVBQwAJ8PpVEhA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRyIQP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk1euAQcjIgAgdBDiqcx7V0LtGgDr8Co4bneNqt4doLQVuq7q8EAvTw0BCP3OAQUASDBFAiEAtwhNsEnHbmd2pUCfLXDrXfSk1NlN7+/p5a3ztI5jeKwCIA6nh4i1JFq/j184Tf4ewXx4mXIiafUwsQmIHF2zKGSHAUgwRQIhAJ4PzjzZbK3tAb1V7af8jEPd3PLA+7BaeyyaBfofoNlPAiBFkygCOx0q49gnpuwe61MupyY/Fcp4ZsAZWzp42qSoNQFHMEQCIB/Amwf9X24u0RiFRaST/db20yJHTqidNVsD/7QB0V9TAiBY9eIVIziBYFy6g9Wbb37YMc+DmvIr/uGJkba1x4wuiQHxUyECL1M7Zn4uo7NuIZYcn+nco0D74K9SEBc6g64DN6sgpXYhAmu1OpjoEL0O5hoO0RZLpsAkeG12VU55PiAtxs6ceMTqIQLVuKfWakH/229MU9YZlAIuiGtPRQAfsVi5XJFk1F+MoyEDJLde6tLB+cYOit615wCf7Hopr82zDYKdgtCVYv6LroUhAy00+JMiAIM0h70pSqIZ3L4AC5+bPYJHmVQUMACfD6VRIQN0aPjqmbbGR4g5i1rSVIDK0I9LDWW+VM46Vf0ga1rkciED9y09lmY7DqmbCusNfyc8qxGo3jeIXx3dyNkRKtuHFpNXrgABASCIkgMAAAAAABepFBCNSAfpaNUWLsnOLKCLqO4EAl4UhyICAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FRzBEAiBDugO7p7KtJGKocdosR80FvkGdE7LFEKLR3nAjqgoR5gIgVw9P3kUPCFF9d6eBvCjn5Y/YJdgVNNW6uO6CBgdH+QsBIgIDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HJHMEQCIFbi8jDcDE1sj58pcguIooGJXDhvrojvbG9CbozQPCjjAiAMHojZPBJP9LzKez7pgY+rANoKRgmxXaMjlu8kl9imHwEiAgP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk0cwRAIgD3cndVkTdhnYwEhwYaBVWJP2E9jq1+ZQjOxNx+luXPMCICrD7DdQc314Ap9EAN+SO4jCeT9mHrVA+skEUoPCAxIdAQEEIgAgdBDiqcx7V0LtGgDr8Co4bneNqt4doLQVuq7q8EAvTw0BBfFTIQIvUztmfi6js24hlhyf6dyjQPvgr1IQFzqDrgM3qyCldiECa7U6mOgQvQ7mGg7RFkumwCR4bXZVTnk+IC3Gzpx4xOohAtW4p9ZqQf/bb0xT1hmUAi6Ia09FAB+xWLlckWTUX4yjIQMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhSEDLTT4kyIAgzSHvSlKohncvgALn5s9gkeZVBQwAJ8PpVEhA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRyIQP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk1euAQcjIgAgdBDiqcx7V0LtGgDr8Co4bneNqt4doLQVuq7q8EAvTw0BCP3MAQUARzBEAiBDugO7p7KtJGKocdosR80FvkGdE7LFEKLR3nAjqgoR5gIgVw9P3kUPCFF9d6eBvCjn5Y/YJdgVNNW6uO6CBgdH+QsBRzBEAiBW4vIw3AxNbI+fKXILiKKBiVw4b66I72xvQm6M0Dwo4wIgDB6I2TwST/S8yns+6YGPqwDaCkYJsV2jI5bvJJfYph8BRzBEAiAPdyd1WRN2GdjASHBhoFVYk/YT2OrX5lCM7E3H6W5c8wIgKsPsN1BzfXgCn0QA35I7iMJ5P2YetUD6yQRSg8IDEh0B8VMhAi9TO2Z+LqOzbiGWHJ/p3KNA++CvUhAXOoOuAzerIKV2IQJrtTqY6BC9DuYaDtEWS6bAJHhtdlVOeT4gLcbOnHjE6iEC1bin1mpB/9tvTFPWGZQCLohrT0UAH7FYuVyRZNRfjKMhAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FIQMtNPiTIgCDNIe9KUqiGdy+AAufmz2CR5lUFDAAnw+lUSEDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HIhA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTV64AAQEgkNADAAAAAAAXqRQQjUgH6WjVFi7Jziygi6juBAJeFIciAgMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhUcwRAIgRYxL9S7/J5BX3SVNPySRxiXBrWTAihp3T4XxdNYz6D8CIG/e6bLrZqVUdQOtAbA41/es6Vy1hPIN6VAzFs9M5BVDASICA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRySDBFAiEAqBvDHFkWEXJfdqanzQJ25fUKXvjWUo/wa0otJAkBD1YCIGZhS3xBgLX/pHbmYg12ENLqGQIzIJrPPID3JFdTjq0VASICA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTSDBFAiEAslYryhKy5Njn1GNJt02Zugj62aOym3AsaRZiKj8aoD8CIA5KzBNNzfhCq6YMj+odwksJC3ctNT1eF5c4ijcxK5LGAQEEIgAgdBDiqcx7V0LtGgDr8Co4bneNqt4doLQVuq7q8EAvTw0BBfFTIQIvUztmfi6js24hlhyf6dyjQPvgr1IQFzqDrgM3qyCldiECa7U6mOgQvQ7mGg7RFkumwCR4bXZVTnk+IC3Gzpx4xOohAtW4p9ZqQf/bb0xT1hmUAi6Ia09FAB+xWLlckWTUX4yjIQMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhSEDLTT4kyIAgzSHvSlKohncvgALn5s9gkeZVBQwAJ8PpVEhA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRyIQP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk1euAQcjIgAgdBDiqcx7V0LtGgDr8Co4bneNqt4doLQVuq7q8EAvTw0BCP3OAQUARzBEAiBFjEv1Lv8nkFfdJU0/JJHGJcGtZMCKGndPhfF01jPoPwIgb97psutmpVR1A60BsDjX96zpXLWE8g3pUDMWz0zkFUMBSDBFAiEAqBvDHFkWEXJfdqanzQJ25fUKXvjWUo/wa0otJAkBD1YCIGZhS3xBgLX/pHbmYg12ENLqGQIzIJrPPID3JFdTjq0VAUgwRQIhALJWK8oSsuTY59RjSbdNmboI+tmjsptwLGkWYio/GqA/AiAOSswTTc34QqumDI/qHcJLCQt3LTU9XheXOIo3MSuSxgHxUyECL1M7Zn4uo7NuIZYcn+nco0D74K9SEBc6g64DN6sgpXYhAmu1OpjoEL0O5hoO0RZLpsAkeG12VU55PiAtxs6ceMTqIQLVuKfWakH/229MU9YZlAIuiGtPRQAfsVi5XJFk1F+MoyEDJLde6tLB+cYOit615wCf7Hopr82zDYKdgtCVYv6LroUhAy00+JMiAIM0h70pSqIZ3L4AC5+bPYJHmVQUMACfD6VRIQN0aPjqmbbGR4g5i1rSVIDK0I9LDWW+VM46Vf0ga1rkciED9y09lmY7DqmbCusNfyc8qxGo3jeIXx3dyNkRKtuHFpNXrgABASBwaQMAAAAAABepFBCNSAfpaNUWLsnOLKCLqO4EAl4UhyICAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FRzBEAiAEujxRerGoet/VhgYMeSFCkeuE8Z42OIXGx/ofrJ50/gIgLsbE5A0dlCIXXpckf35MBn9jiLVKD6tnLy1ZIj8FVe8BIgIDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HJHMEQCIEjLH1c9Rkq0wad3KqAxlpQasFjuN2gAf+mpWiazgxsnAiBl+7+NXJt8JFc5a+JNWz1f98gIwAGNOVPFo9vQJzZGhQEiAgP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk0cwRAIgSKyvqLBOmZLbK72dTb9LdoUw01eQCBrj7Vxjgw1KHVECIEZVa67aNKIA4evyOq2e9C7+J91KkTA8GJst2eRUVskSAQEEIgAgdBDiqcx7V0LtGgDr8Co4bneNqt4doLQVuq7q8EAvTw0BBfFTIQIvUztmfi6js24hlhyf6dyjQPvgr1IQFzqDrgM3qyCldiECa7U6mOgQvQ7mGg7RFkumwCR4bXZVTnk+IC3Gzpx4xOohAtW4p9ZqQf/bb0xT1hmUAi6Ia09FAB+xWLlckWTUX4yjIQMkt17q0sH5xg6K3rXnAJ/seimvzbMNgp2C0JVi/ouuhSEDLTT4kyIAgzSHvSlKohncvgALn5s9gkeZVBQwAJ8PpVEhA3Ro+OqZtsZHiDmLWtJUgMrQj0sNZb5UzjpV/SBrWuRyIQP3LT2WZjsOqZsK6w1/JzyrEajeN4hfHd3I2REq24cWk1euAQcjIgAgdBDiqcx7V0LtGgDr8Co4bneNqt4doLQVuq7q8EAvTw0BCP3MAQUARzBEAiAEujxRerGoet/VhgYMeSFCkeuE8Z42OIXGx/ofrJ50/gIgLsbE5A0dlCIXXpckf35MBn9jiLVKD6tnLy1ZIj8FVe8BRzBEAiBIyx9XPUZKtMGndyqgMZaUGrBY7jdoAH/pqVoms4MbJwIgZfu/jVybfCRXOWviTVs9X/fICMABjTlTxaPb0Cc2RoUBRzBEAiBIrK+osE6ZktsrvZ1Nv0t2hTDTV5AIGuPtXGODDUodUQIgRlVrrto0ogDh6/I6rZ70Lv4n3UqRMDwYmy3Z5FRWyRIB8VMhAi9TO2Z+LqOzbiGWHJ/p3KNA++CvUhAXOoOuAzerIKV2IQJrtTqY6BC9DuYaDtEWS6bAJHhtdlVOeT4gLcbOnHjE6iEC1bin1mpB/9tvTFPWGZQCLohrT0UAH7FYuVyRZNRfjKMhAyS3XurSwfnGDoretecAn+x6Ka/Nsw2CnYLQlWL+i66FIQMtNPiTIgCDNIe9KUqiGdy+AAufmz2CR5lUFDAAnw+lUSEDdGj46pm2xkeIOYta0lSAytCPSw1lvlTOOlX9IGta5HIhA/ctPZZmOw6pmwrrDX8nPKsRqN43iF8d3cjZESrbhxaTV64AAA== diff --git a/tests/esplora.rs b/tests/esplora.rs new file mode 100644 index 0000000..f0078a5 --- /dev/null +++ b/tests/esplora.rs @@ -0,0 +1,63 @@ +#[cfg(feature = "esplora")] +use bdk_reserves::reserves::*; +#[cfg(feature = "esplora")] +use bdk_wallet::bitcoin::{ + Amount, + base64::{Engine as _, engine::general_purpose::STANDARD}, + psbt::Psbt, +}; + +#[cfg(feature = "esplora")] +#[tokio::test] +async fn test_esplora_active() { + let _ = rustls::crypto::CryptoProvider::install_default( + rustls::crypto::aws_lc_rs::default_provider(), + ); + + let psbt = include_str!("data/active.psbt").trim_end().to_string(); + let psbt = decode_tx(&psbt); + assert_eq!(psbt.unsigned_tx.input.len(), 20); + + let outheight = verify_prevouts(&psbt, "https://blockstream.info/testnet/api/", None) + .await + .unwrap(); + assert_eq!(outheight.outpoints.len(), 20); + assert!( + outheight.unspent_height > 971104, + "unspent_height ({}) > 971104", + outheight.unspent_height + ); + + let message = "Stored in AMINA Bank AG cold storage"; + let amount = verify_proof(&psbt, message, outheight.outpoints).unwrap(); + assert_eq!(amount, Amount::from_sat(922_739)); +} + +#[cfg(feature = "esplora")] +#[tokio::test] +async fn test_esplora_spent() { + let _ = rustls::crypto::CryptoProvider::install_default( + rustls::crypto::aws_lc_rs::default_provider(), + ); + + let psbt = include_str!("data/spent.psbt").trim_end().to_string(); + let psbt = decode_tx(&psbt); + assert_eq!(psbt.unsigned_tx.input.len(), 19); + + let outheight = verify_prevouts(&psbt, "https://blockstream.info/testnet/api/", None) + .await + .unwrap(); + assert_eq!(outheight.outpoints.len(), 19); + assert_eq!(outheight.unspent_height, 2133038); + + let message = "Stored in SEBA Bank AG cold storage"; + let amount = verify_proof(&psbt, message, outheight.outpoints).unwrap(); + assert_eq!(amount, Amount::from_sat(53_865_580)); +} + +#[cfg(feature = "esplora")] +/// Decodes a partially signed transaction from a base64 string. +pub fn decode_tx(tx: &str) -> Psbt { + let tx = STANDARD.decode(tx).unwrap(); + Psbt::deserialize(tx.as_slice()).unwrap() +} From b4b554a38b4b2fa0ab54ec71635985720cf15fdb Mon Sep 17 00:00:00 2001 From: Richard Ulrich Date: Tue, 13 Oct 2026 11:26:45 +0200 Subject: [PATCH 2/2] usability improvement --- Cargo.toml | 3 +- src/reserves.rs | 76 +++++++++++++++++++++++++++++------------------- tests/esplora.rs | 26 +++++++---------- 3 files changed, 58 insertions(+), 47 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 311ee1a..d92e360 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,13 +10,14 @@ license = "MIT OR Apache-2.0" repository = "https://github.com/bitcoindevkit/bdk-reserves" [features] -esplora = ["esplora-client", "rustls"] +esplora = ["esplora-client", "rustls", "chrono"] [dependencies] bdk_wallet = { version = "3.2", default-features = false, features = ["std"] } bitcoinconsensus = "0.106" log = "^0.4" esplora-client = { version = "0.13", optional = true, default-features = false, features = [ "async", "async-https", "tokio"] } +chrono = { version = "0.4", optional = true } # same version as in rust-bitcoin - I couldn't find a re-export units = { package = "bitcoin-units", version = "0.1", default-features = false, features = ["alloc"] } diff --git a/src/reserves.rs b/src/reserves.rs index 01d57cc..8ccda36 100644 --- a/src/reserves.rs +++ b/src/reserves.rs @@ -32,6 +32,8 @@ use bdk_wallet::chain::ChainPosition; use bdk_wallet::{AddForeignUtxoError, TxOrdering, Wallet}; use bdk_wallet::{error::CreateTxError, signer::SignerError}; #[cfg(feature = "esplora")] +use chrono::{DateTime, Utc}; +#[cfg(feature = "esplora")] use esplora_client; use units::weight::Weight; @@ -211,9 +213,6 @@ impl ProofOfReserves for Wallet { /// Make sure this is a proof, and not a spendable transaction. /// Make sure the proof is valid. -/// Currently proofs can only be validated against the tip of the chain. -/// If some of the UTXOs in the proof were spent in the meantime, the proof will fail. -/// We can currently not validate whether it was valid at a certain block height. /// Since the caller provides the outpoints, he is also responsible to make sure they have enough confirmations. /// Returns the spendable amount of the proof. pub fn verify_proof( @@ -362,9 +361,33 @@ fn challenge_txin(message: &str) -> TxIn { } } -pub struct OutpointsHeight { +#[cfg(feature = "esplora")] +pub struct ProofResult { + pub value: Amount, + pub timestamp: DateTime, +} + +/// Make sure this is a proof, and not a spendable transaction. +/// Make sure the proof is valid. +/// Verifies the outpoints online with an esplora server. +#[cfg(feature = "esplora")] +pub async fn verify_proof_online( + psbt: &Psbt, + message: &str, + url: &str, +) -> Result { + let outheight = verify_prevouts(psbt, url).await?; + let amount = verify_proof(psbt, message, outheight.outpoints)?; + Ok(ProofResult { + value: amount, + timestamp: outheight.timestamp, + }) +} + +#[cfg(feature = "esplora")] +struct OutpointsHeight { pub outpoints: Vec<(OutPoint, TxOut)>, - pub unspent_height: u32, + pub timestamp: DateTime, } /// collect the information necessary to validate a proof @@ -372,11 +395,7 @@ pub struct OutpointsHeight { /// In addition to the UTXOs, also returns the last block before the first UTXO was spent /// This allows to validate proofs that no longer hold, but were valid in the past #[cfg(feature = "esplora")] -pub async fn verify_prevouts( - psbt: &Psbt, - url: &str, - _height: Option, -) -> Result { +async fn verify_prevouts(psbt: &Psbt, url: &str) -> Result { let client = esplora_client::Builder::new(url) .header("user-agent", "bdk-reserves/3") .build_async() @@ -411,9 +430,25 @@ pub async fn verify_prevouts( } } + let blockinfo = &client + .get_block_infos(Some(unspent_height)) + .await + .map_err(|_| ProofError::EsploraError)?[0]; + if blockinfo.height != unspent_height { + return Err(ProofError::EsploraError); + } + let timestamp = DateTime::from_timestamp( + blockinfo + .timestamp + .try_into() + .map_err(|_| ProofError::EsploraError)?, + 0, + ) + .ok_or(ProofError::EsploraError)?; + Ok(OutpointsHeight { outpoints: utxos, - unspent_height, + timestamp, }) } @@ -456,25 +491,6 @@ mod test { assert_eq!(psbt_b64, expected); } - #[cfg(feature = "esplora")] - #[tokio::test] - async fn test_fetch_empty() { - let _ = rustls::crypto::CryptoProvider::install_default( - rustls::crypto::aws_lc_rs::default_provider(), - ); - - let descriptor = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"; - let (mut wallet, _) = get_funded_wallet_single(descriptor); - let message = "This is a test"; - let psbt = wallet.create_proof(message).unwrap(); - - let outpoints = verify_prevouts(&psbt, "https://ax102.blockeng.ch/", None) - .await - .unwrap(); - - assert_eq!(outpoints.outpoints.len(), 2); - } - #[test] #[should_panic(expected = "\"Key too short (<66 char), doesn't match any format\"")] fn invalid_descriptor() { diff --git a/tests/esplora.rs b/tests/esplora.rs index f0078a5..ec5095a 100644 --- a/tests/esplora.rs +++ b/tests/esplora.rs @@ -6,6 +6,8 @@ use bdk_wallet::bitcoin::{ base64::{Engine as _, engine::general_purpose::STANDARD}, psbt::Psbt, }; +#[cfg(feature = "esplora")] +use chrono::{Datelike, Local}; #[cfg(feature = "esplora")] #[tokio::test] @@ -18,19 +20,13 @@ async fn test_esplora_active() { let psbt = decode_tx(&psbt); assert_eq!(psbt.unsigned_tx.input.len(), 20); - let outheight = verify_prevouts(&psbt, "https://blockstream.info/testnet/api/", None) + let message = "Stored in AMINA Bank AG cold storage"; + let pres = verify_proof_online(&psbt, message, "https://blockstream.info/testnet/api/") .await .unwrap(); - assert_eq!(outheight.outpoints.len(), 20); - assert!( - outheight.unspent_height > 971104, - "unspent_height ({}) > 971104", - outheight.unspent_height - ); - let message = "Stored in AMINA Bank AG cold storage"; - let amount = verify_proof(&psbt, message, outheight.outpoints).unwrap(); - assert_eq!(amount, Amount::from_sat(922_739)); + assert_eq!(pres.value, Amount::from_sat(922_739)); + assert_eq!(pres.timestamp.year(), Local::now().year()); } #[cfg(feature = "esplora")] @@ -44,15 +40,13 @@ async fn test_esplora_spent() { let psbt = decode_tx(&psbt); assert_eq!(psbt.unsigned_tx.input.len(), 19); - let outheight = verify_prevouts(&psbt, "https://blockstream.info/testnet/api/", None) + let message = "Stored in SEBA Bank AG cold storage"; + let pres = verify_proof_online(&psbt, message, "https://blockstream.info/testnet/api/") .await .unwrap(); - assert_eq!(outheight.outpoints.len(), 19); - assert_eq!(outheight.unspent_height, 2133038); - let message = "Stored in SEBA Bank AG cold storage"; - let amount = verify_proof(&psbt, message, outheight.outpoints).unwrap(); - assert_eq!(amount, Amount::from_sat(53_865_580)); + assert_eq!(pres.value, Amount::from_sat(53_865_580)); + assert_eq!(format!("{:?}", pres.timestamp), "2021-12-23T12:48:12Z"); } #[cfg(feature = "esplora")]