From 609795e61ffb2d67cd679d28e37786a93fcbf6fd Mon Sep 17 00:00:00 2001 From: Mehmet Efe Umit Date: Wed, 11 Jun 2025 10:25:50 -0700 Subject: [PATCH 1/2] refactor: move broadcast logic to a helper function Before introducing the payjoin sending logic (which will also make use of broadcasting), moving the broadcast logic out of the current Broadcast command since the same logic will be shared between both Broadcast and the SendPayjoin which is implemented later. --- src/handlers.rs | 168 +++++++++++++++++++++++++----------------------- 1 file changed, 89 insertions(+), 79 deletions(-) diff --git a/src/handlers.rs b/src/handlers.rs index 370681a3..ee40cdd6 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -523,85 +523,7 @@ pub(crate) async fn handle_online_wallet_subcommand( (Some(_), Some(_)) => panic!("Both `psbt` and `tx` options not allowed"), (None, None) => panic!("Missing `psbt` and `tx` option"), }; - let txid = match client { - #[cfg(feature = "electrum")] - Electrum { - client, - batch_size: _, - } => client - .transaction_broadcast(&tx) - .map_err(|e| Error::Generic(e.to_string()))?, - #[cfg(feature = "esplora")] - Esplora { - client, - parallel_requests: _, - } => client - .broadcast(&tx) - .await - .map(|()| tx.compute_txid()) - .map_err(|e| Error::Generic(e.to_string()))?, - #[cfg(feature = "rpc")] - RpcClient { client } => client - .send_raw_transaction(&tx) - .map_err(|e| Error::Generic(e.to_string()))?, - - #[cfg(feature = "cbf")] - KyotoClient { client } => { - let LightClient { - requester, - mut log_subscriber, - mut info_subscriber, - mut warning_subscriber, - update_subscriber: _, - node, - } = *client; - - let subscriber = tracing_subscriber::FmtSubscriber::new(); - tracing::subscriber::set_global_default(subscriber) - .map_err(|e| Error::Generic(format!("SetGlobalDefault error: {}", e)))?; - - tokio::task::spawn(async move { node.run().await }); - tokio::task::spawn(async move { - select! { - log = log_subscriber.recv() => { - if let Some(log) = log { - tracing::info!("{log}"); - } - }, - warn = warning_subscriber.recv() => { - if let Some(warn) = warn { - tracing::warn!("{warn}"); - } - } - } - }); - let txid = tx.compute_txid(); - requester - .broadcast_random(tx.clone()) - .map_err(|e| Error::Generic(format!("{}", e)))?; - tokio::time::timeout(tokio::time::Duration::from_secs(30), async move { - while let Some(info) = info_subscriber.recv().await { - match info { - Info::TxGossiped(wtxid) => { - tracing::info!("Successfully broadcast WTXID: {wtxid}"); - break; - } - Info::ConnectionsMet => { - tracing::info!("Rebroadcasting to new connections"); - requester.broadcast_random(tx.clone()).unwrap(); - } - _ => tracing::info!("{info}"), - } - } - }) - .await - .map_err(|_| { - tracing::warn!("Broadcast was unsuccessful"); - Error::Generic("Transaction broadcast timed out after 30 seconds".into()) - })?; - txid - } - }; + let txid = broadcast_transaction(client, tx).await?; Ok(json!({ "txid": txid })) } } @@ -958,6 +880,94 @@ async fn respond( } } +#[cfg(any( + feature = "electrum", + feature = "esplora", + feature = "cbf", + feature = "rpc" +))] +/// Broadcasts a given transaction using the blockchain client. +async fn broadcast_transaction(client: BlockchainClient, tx: Transaction) -> Result { + match client { + #[cfg(feature = "electrum")] + Electrum { + client, + batch_size: _, + } => client + .transaction_broadcast(&tx) + .map_err(|e| Error::Generic(e.to_string())), + #[cfg(feature = "esplora")] + Esplora { + client, + parallel_requests: _, + } => client + .broadcast(&tx) + .await + .map(|()| tx.compute_txid()) + .map_err(|e| Error::Generic(e.to_string())), + #[cfg(feature = "rpc")] + RpcClient { client } => client + .send_raw_transaction(&tx) + .map_err(|e| Error::Generic(e.to_string())), + #[cfg(feature = "cbf")] + KyotoClient { client } => { + let LightClient { + requester, + mut log_subscriber, + mut info_subscriber, + mut warning_subscriber, + update_subscriber: _, + node, + } = *client; + + let subscriber = tracing_subscriber::FmtSubscriber::new(); + tracing::subscriber::set_global_default(subscriber) + .map_err(|e| Error::Generic(format!("SetGlobalDefault error: {}", e)))?; + + tokio::task::spawn(async move { node.run().await }); + tokio::task::spawn(async move { + select! { + log = log_subscriber.recv() => { + if let Some(log) = log { + tracing::info!("{log}"); + } + }, + warn = warning_subscriber.recv() => { + if let Some(warn) = warn { + tracing::warn!("{warn}"); + } + } + } + }); + let txid = tx.compute_txid(); + requester + .broadcast_random(tx.clone()) + .map_err(|e| Error::Generic(format!("{}", e)))?; + tokio::time::timeout(tokio::time::Duration::from_secs(30), async move { + while let Some(info) = info_subscriber.recv().await { + match info { + Info::TxGossiped(wtxid) => { + tracing::info!("Successfully broadcast WTXID: {wtxid}"); + break; + } + Info::ConnectionsMet => { + tracing::info!("Rebroadcasting to new connections"); + requester.broadcast_random(tx.clone()).unwrap(); + } + _ => tracing::info!("{info}"), + } + } + }) + .await + .map_err(|_| { + tracing::warn!("Broadcast was unsuccessful"); + Error::Generic("Transaction broadcast timed out after 30 seconds".into()) + })?; + Ok(txid) + } + } +} + #[cfg(feature = "repl")] fn readline() -> Result { write!(std::io::stdout(), "> ").map_err(|e| Error::Generic(e.to_string()))?; From dd6a582e452ef70def1d6b9ffd1ed8d1272ad22d Mon Sep 17 00:00:00 2001 From: Mehmet Efe Umit Date: Tue, 24 Jun 2025 07:19:54 -0700 Subject: [PATCH 2/2] feat: add Payjoin send for both v1 and v2 --- Cargo.lock | 767 +++++++++++++++++++++++++++++++++++++++++++----- Cargo.toml | 17 +- src/commands.rs | 13 + src/error.rs | 9 + src/handlers.rs | 196 +++++++++++++ src/utils.rs | 18 ++ 6 files changed, 948 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 715d54ff..be2302ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "addr2line" @@ -13,9 +13,55 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", + "rand_core 0.6.4", +] + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures 0.2.17", + "opaque-debug", +] + +[[package]] +name = "aes-gcm" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f" +dependencies = [ + "aead 0.4.3", + "aes", + "cipher 0.3.0", + "ctr", + "ghash", + "subtle", +] [[package]] name = "ahash" @@ -40,9 +86,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -55,33 +101,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.8" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", "once_cell_polyfill", @@ -190,6 +236,8 @@ dependencies = [ "dirs", "env_logger", "log", + "payjoin", + "reqwest", "serde_json", "shlex", "thiserror 2.0.12", @@ -275,7 +323,7 @@ dependencies = [ "bip39", "bitcoin", "miniscript", - "rand_core", + "rand_core 0.6.4", "serde", "serde_json", ] @@ -286,6 +334,15 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" +[[package]] +name = "bhttp" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef06386f8f092c3419e153a657396e53cafbb901de445a5c54d96ab2ff8c7b2" +dependencies = [ + "thiserror 1.0.69", +] + [[package]] name = "bindgen" version = "0.69.5" @@ -303,7 +360,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn", "which", @@ -318,7 +375,7 @@ dependencies = [ "bitcoin", "bitcoin_hashes 0.15.0", "chacha20-poly1305", - "rand", + "rand 0.8.5", "tokio", ] @@ -352,6 +409,25 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin-hpke" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37a54c486727c1d1ae9cc28dcf78b6e6ba20dcb88e8c892f1437d9ce215dc8c" +dependencies = [ + "aead 0.5.2", + "chacha20poly1305 0.10.1", + "digest 0.10.7", + "generic-array", + "hkdf 0.12.4", + "hmac 0.12.1", + "rand_core 0.6.4", + "secp256k1", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "bitcoin-internals" version = "0.2.0" @@ -388,6 +464,29 @@ dependencies = [ "bitcoin-internals 0.4.0", ] +[[package]] +name = "bitcoin-ohttp" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87a803a4b54e44635206b53329c78c0029d0c70926288ac2f07f4bb1267546cb" +dependencies = [ + "aead 0.4.3", + "aes-gcm", + "bitcoin-hpke", + "byteorder", + "chacha20poly1305 0.8.0", + "hex", + "hkdf 0.11.0", + "lazy_static", + "log", + "rand 0.8.5", + "serde", + "serde_derive", + "sha2 0.9.9", + "thiserror 1.0.69", + "toml", +] + [[package]] name = "bitcoin-units" version = "0.1.2" @@ -429,6 +528,16 @@ dependencies = [ "hex-conservative 0.3.0", ] +[[package]] +name = "bitcoin_uri" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0a228e083d1702f83389b0ac71eb70078dc8d7fcbb6cde864d1cbca145f5cc" +dependencies = [ + "bitcoin", + "percent-encoding-rfc3986", +] + [[package]] name = "bitcoincore-rpc" version = "0.19.0" @@ -459,11 +568,29 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" [[package]] name = "byteorder" @@ -479,9 +606,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.24" +version = "1.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" +checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" dependencies = [ "jobserver", "libc", @@ -499,9 +626,38 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures 0.1.5", + "zeroize", +] + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures 0.2.17", +] [[package]] name = "chacha20-poly1305" @@ -509,6 +665,52 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b4b0fc281743d80256607bd65e8beedc42cb0787ea119c85b81b4c0eab85e5f" +[[package]] +name = "chacha20poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5" +dependencies = [ + "aead 0.4.3", + "chacha20 0.7.1", + "cipher 0.3.0", + "poly1305 0.7.2", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead 0.5.2", + "chacha20 0.9.1", + "cipher 0.4.4", + "poly1305 0.8.0", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -522,9 +724,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.39" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", "clap_derive", @@ -532,9 +734,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.39" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstream", "anstyle", @@ -544,9 +746,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ "heck", "proc-macro2", @@ -556,9 +758,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cmake" @@ -571,9 +773,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "core-foundation" @@ -591,6 +793,74 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "ctr" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" +dependencies = [ + "cipher 0.3.0", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + [[package]] name = "dirs" version = "6.0.0" @@ -648,7 +918,7 @@ dependencies = [ "rustls 0.23.27", "serde", "serde_json", - "webpki-roots", + "webpki-roots 0.25.4", "winapi", ] @@ -843,6 +1113,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -850,8 +1130,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -861,9 +1143,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug", + "polyval", ] [[package]] @@ -903,6 +1197,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hex-conservative" version = "0.1.2" @@ -933,6 +1233,44 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +[[package]] +name = "hkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" +dependencies = [ + "digest 0.9.0", + "hmac 0.11.0", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac 0.12.1", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "home" version = "0.5.11" @@ -1001,6 +1339,23 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls 0.23.27", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.0", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -1019,9 +1374,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ "base64 0.22.1", "bytes", @@ -1148,6 +1503,15 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -1322,9 +1686,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -1336,6 +1700,12 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "memchr" version = "2.7.4" @@ -1367,9 +1737,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -1386,7 +1756,7 @@ dependencies = [ "rustls-webpki 0.101.7", "serde", "serde_json", - "webpki-roots", + "webpki-roots 0.25.4", ] [[package]] @@ -1458,11 +1828,17 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" -version = "0.10.72" +version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ "bitflags", "cfg-if", @@ -1492,9 +1868,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.108" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -1516,9 +1892,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -1526,9 +1902,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -1537,12 +1913,37 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "payjoin" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1897797565f894c0902cb9621ce50e92d3a25f530e82a944cd5b84674fe0dcfc" +dependencies = [ + "bhttp", + "bitcoin", + "bitcoin-hpke", + "bitcoin-ohttp", + "bitcoin_uri", + "http", + "log", + "reqwest", + "serde", + "serde_json", + "url", +] + [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "percent-encoding-rfc3986" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3637c05577168127568a64e9dc5a6887da720efef07b3d9472d45f63ab191166" + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1561,11 +1962,45 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash 0.4.0", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash 0.5.1", +] + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash 0.4.0", +] + [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" @@ -1596,9 +2031,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d" dependencies = [ "proc-macro2", "syn", @@ -1613,6 +2048,61 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.1", + "rustls 0.23.27", + "socket2", + "thiserror 2.0.12", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +dependencies = [ + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.1", + "ring", + "rustc-hash 2.1.1", + "rustls 0.23.27", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.40" @@ -1635,8 +2125,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -1646,7 +2146,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -1658,6 +2168,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "redox_syscall" version = "0.5.12" @@ -1709,9 +2228,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.18" +version = "0.12.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98ff6b0dbbe4d5a37318f433d4fc82babd21631f194d370409ceb2e40b2f0b5" +checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119" dependencies = [ "base64 0.22.1", "bytes", @@ -1720,6 +2239,7 @@ dependencies = [ "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", @@ -1730,6 +2250,8 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "quinn", + "rustls 0.23.27", "rustls-pki-types", "serde", "serde_json", @@ -1737,6 +2259,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", + "tokio-rustls", "tokio-socks", "tower", "tower-http", @@ -1745,6 +2268,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots 1.0.0", ] [[package]] @@ -1787,6 +2311,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" version = "0.38.44" @@ -1834,6 +2364,7 @@ dependencies = [ "aws-lc-rs", "log", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki 0.103.3", "subtle", @@ -1846,6 +2377,7 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ + "web-time", "zeroize", ] @@ -1915,7 +2447,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "bitcoin_hashes 0.14.0", - "rand", + "rand 0.8.5", "secp256k1-sys", "serde", ] @@ -1996,6 +2528,30 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2031,9 +2587,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" @@ -2221,6 +2777,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls 0.23.27", + "tokio", +] + [[package]] name = "tokio-socks" version = "0.5.2" @@ -2233,6 +2799,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "tower" version = "0.5.2" @@ -2250,9 +2825,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.4" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "bitflags", "bytes", @@ -2291,9 +2866,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" dependencies = [ "proc-macro2", "quote", @@ -2302,9 +2877,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -2341,6 +2916,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -2356,6 +2937,26 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "universal-hash" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -2371,6 +2972,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -2508,12 +3110,31 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "4.4.2" @@ -2779,6 +3400,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zerotrie" diff --git a/Cargo.toml b/Cargo.toml index c620f20d..7dcd4abe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,8 @@ log = "0.4" serde_json = "1.0" thiserror = "2.0.11" tokio = { version = "1", features = ["full"] } +tracing = "0.1.41" +tracing-subscriber = "0.3.19" # Optional dependencies bdk_bitcoind_rpc = { version = "0.20.0", optional = true } @@ -28,8 +30,8 @@ bdk_electrum = { version = "0.23.0", optional = true } bdk_esplora = { version = "0.22.0", features = ["async-https", "tokio"], optional = true } bdk_kyoto = { version = "0.11.0", optional = true } shlex = { version = "1.3.0", optional = true } -tracing = "0.1.41" -tracing-subscriber = "0.3.19" +payjoin = { version = "0.23.0", features = ["v2", "io"], optional = true} +reqwest = { version = "0.12", default-features = false, optional = true } [features] default = ["repl", "sqlite"] @@ -41,10 +43,13 @@ repl = ["shlex"] sqlite = ["bdk_wallet/rusqlite"] # Available blockchain client options -cbf = ["bdk_kyoto"] -electrum = ["bdk_electrum"] -esplora = ["bdk_esplora"] -rpc = ["bdk_bitcoind_rpc"] +cbf = ["bdk_kyoto", "_payjoin-dependencies"] +electrum = ["bdk_electrum", "_payjoin-dependencies"] +esplora = ["bdk_esplora", "_payjoin-dependencies"] +rpc = ["bdk_bitcoind_rpc", "_payjoin-dependencies"] + +# Internal features +_payjoin-dependencies = ["payjoin", "reqwest"] # Use this to consensus verify transactions at sync time verify = [] diff --git a/src/commands.rs b/src/commands.rs index 52790bce..f8263e4f 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -414,6 +414,19 @@ pub enum OnlineWalletSubCommand { )] tx: Option, }, + /// Sends an original PSBT to a BIP 21 URI and broadcasts the returned PayJoin PSBT. + SendPayjoin { + /// BIP 21 URI for the Payjoin (either v1 or v2). + #[arg(env = "PAYJOIN_URI", long = "uri", required = true)] + uri: String, + /// Fee rate to use in sat/vbyte. + #[arg(env = "SATS_VBYTE", short = 'f', long = "fee_rate", required = true)] + fee_rate: u64, + /// URL of the Payjoin OHTTP relay. Can be repeated multiple times to attempt the + /// operation with multiple relays for redundancy. + #[arg(env = "PAYJOIN_OHTTP_RELAY", long = "ohttp_relay")] + ohttp_relay: Option>, + }, } /// Subcommands for Key operations. diff --git a/src/error.rs b/src/error.rs index cb757156..132e5e5b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -94,6 +94,15 @@ pub enum BDKCliError { #[cfg(feature = "cbf")] #[error("BDK-Kyoto update error: {0}")] KyotoUpdateError(#[from] bdk_kyoto::UpdateError), + + #[cfg(any( + feature = "electrum", + feature = "esplora", + feature = "rpc", + feature = "cbf", + ))] + #[error("Reqwest error: {0}")] + ReqwestError(#[from] reqwest::Error), } impl From for BDKCliError { diff --git a/src/handlers.rs b/src/handlers.rs index ee40cdd6..f8724550 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -62,6 +62,10 @@ use tokio::select; use { crate::commands::OnlineWalletSubCommand::*, bdk_wallet::bitcoin::{consensus::Decodable, hex::FromHex, Transaction}, + payjoin::{ + send::v2::{Sender, SenderBuilder}, + UriExt, Url, + }, }; #[cfg(feature = "esplora")] use {crate::utils::BlockchainClient::Esplora, bdk_esplora::EsploraAsyncExt}; @@ -526,6 +530,198 @@ pub(crate) async fn handle_online_wallet_subcommand( let txid = broadcast_transaction(client, tx).await?; Ok(json!({ "txid": txid })) } + SendPayjoin { + uri, + fee_rate, + ohttp_relay, + } => { + let uri = payjoin::Uri::try_from(uri) + .map_err(|e| Error::Generic(format!("Failed parsing to Payjoin URI: {}", e)))?; + let uri = uri.require_network(wallet.network()).map_err(|e| { + Error::Generic(format!( + "Failed setting the right network for the URI: {}", + e + )) + })?; + let uri = uri + .check_pj_supported() + .map_err(|e| Error::Generic(format!("URI does not support Payjoin: {}", e)))?; + + let sats = uri + .amount + .ok_or_else(|| Error::Generic("Amount is not specified in the URI.".to_string()))?; + + let fee_rate = + FeeRate::from_sat_per_vb(fee_rate).expect("Provided fee rate is not valid."); + + // Validating all OHTTP relays before we go ahead and potentially use them. + let ohttp_relays = match ohttp_relay { + None => Ok(vec![]), + Some(relays) => relays.into_iter().map(|s| Url::parse(&s)).collect(), + } + .map_err(|e| { + Error::Generic(format!("Failed to parse one or more OHTTP URls: {}", e)) + })?; + + // Build and sign the original PSBT which pays to the receiver. + let mut original_psbt = { + let mut tx_builder = wallet.build_tx(); + tx_builder + .add_recipient(uri.address.script_pubkey(), sats) + .fee_rate(fee_rate); + + tx_builder.finish().map_err(|e| { + Error::Generic(format!( + "Error occurred when building original Payjoin transaction: {}", + e + )) + })? + }; + if !wallet.sign(&mut original_psbt, SignOptions::default())? { + return Err(Error::Generic( + "Failed to sign and finalize the original PSBT.".to_string(), + )); + } + + // TODO: Implement proper persisting after the design is finalized on the PDK end (see https://github.com/payjoin/rust-payjoin/pull/675). + let mut persister = payjoin::persist::NoopPersister; + + // Using an original PSBT clone since we are going to use it later to re-introduce `..._utxo` fields to the Payjoin proposal. + let new_sender = SenderBuilder::new(original_psbt.clone(), uri.clone()) + .build_recommended(fee_rate) + .map_err(|e| { + Error::Generic(format!( + "Failed initializing Payjoin sender using the transaction fee rate: {}", + e + )) + })?; + let storage_token = new_sender.persist(&mut persister).map_err(|e| { + Error::Generic(format!( + "Failed to generate the storage token with the receiver and persister: {}", + e + )) + })?; + let req_ctx = Sender::load(storage_token, &persister) + .map_err(|e| Error::Generic(format!("Failed to load the request context using the storage token and the persister {}", e)))?; + + // We will go with Payjoin v2 if there is at least one functional OHTTP relay passed as + // an argument and the request-context extraction is successful. Otherwise, if there is + // an error in finding a healthy relay or extracting the v2 context, we use v1. + let v2_setup = if ohttp_relays.is_empty() { + Err("No OHTTP relays provided.".to_string()) + } else { + async { + for relay in ohttp_relays { + if let Ok(_) = + payjoin::io::fetch_ohttp_keys(relay.clone(), req_ctx.endpoint()).await + { + return Ok(relay); + } + } + Err("Unable to find a responsive OHTTP relay in the provided list.".to_string()) + } + .await + .and_then(|ohttp_relay| { + req_ctx + .extract_v2(ohttp_relay.clone()) + .map(|(req, ctx)| (req, ctx, ohttp_relay)) + .map_err(|_| "Failed to initialize a Payjoin v2 session.".to_string()) + }) + }; + + let mut psbt = match v2_setup { + Ok((req, ctx, ohttp_relay)) => { + println!( + "Sending using Payjoin v2 (Asynchronous) through the OHTTP relay {}", + ohttp_relay.to_string() + ); + let response = send_payjoin_post_request(req) + .await + .map_err(|e| Error::Generic(format!("Failed to send request: {}", e)))?; + + let v2_ctx = ctx + .process_response(&response.bytes().await?) + .map_err(|e| { + Error::Generic(format!("Failed to initialize v2 context: {}", e)) + })?; + + // Loop until the receiver sends back a Payjoin proposal + loop { + let (req, ohttp_ctx) = + v2_ctx.extract_req(ohttp_relay.clone()).map_err(|e| { + Error::Generic(format!( + "Failed to extract request for polling: {}", + e + )) + })?; + + let response = send_payjoin_post_request(req).await.map_err(|e| { + Error::Generic(format!("Failed to send polling request: {}", e)) + })?; + + match v2_ctx.process_response(&response.bytes().await?, ohttp_ctx) { + Ok(Some(receiver_response_psbt)) => break receiver_response_psbt, + Ok(None) => { + println!("No response yet. Keep polling...") + } + Err(e) => { + Err(Error::Generic(format!( + "Failed to process v2 response: {}", + e + )))?; + } + } + } + } + Err(e) => { + println!("Sending using Payjoin v1 (Synchronous). Reason: {e}"); + let (req, ctx) = req_ctx.extract_v1(); + let response = send_payjoin_post_request(req) + .await + .map_err(|e| Error::Generic(format!("Failed to send request: {}", e)))?; + + // TODO: Remove `as_ref()` when the type change is merged in the next release + // through the commit https://github.com/payjoin/rust-payjoin/commit/e88750b0ae59e3583b2e3233c5bef191624af0a4. + ctx.process_response(&mut response.bytes().await?.as_ref()) + .map_err(|e| { + Error::Generic(format!("Failed to send a Payjoin v1: {}", e)) + })? + } + }; + + /// Collects the inputs of a given transaction as (TxIn, Input) pairs and returns the iterator. + fn input_pairs( + psbt: &mut Psbt, + ) -> impl Iterator + '_ + { + psbt.unsigned_tx.input.iter().zip(psbt.inputs.iter_mut()) + } + + // We need to re-introduce the UTXO information of the original PSBT's inputs + // back into the Payjoin proposal we received from the receiver. The receiver strips + // the transaction of the information, so we need to add the `..._utxo` information back + // before we can re-sign our input(s) into the transaction. + let mut original_inputs_iter = input_pairs(&mut original_psbt).peekable(); + for (proposal_txin, proposal_psbt_input) in input_pairs(&mut psbt) { + if let Some((orig_txin, orig_psbt_input)) = original_inputs_iter.peek() { + if proposal_txin.previous_output == orig_txin.previous_output { + proposal_psbt_input.witness_utxo = orig_psbt_input.witness_utxo.clone(); + proposal_psbt_input.non_witness_utxo = + orig_psbt_input.non_witness_utxo.clone(); + original_inputs_iter.next(); + } + } + } + + if !wallet.sign(&mut psbt, SignOptions::default())? { + return Err(Error::Generic( + "Failed to sign and finalize the Payjoin proposal PSBT.".to_string(), + )); + } + + let txid = broadcast_transaction(client, psbt.extract_tx_fee_rate_limit()?).await?; + Ok(json!({ "txid": txid })) + } } } diff --git a/src/utils.rs b/src/utils.rs index c72fd80e..5a570147 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -371,3 +371,21 @@ pub async fn sync_kyoto_client(wallet: &mut Wallet, client: Box) -> Ok(()) } + +#[cfg(any( + feature = "electrum", + feature = "esplora", + feature = "rpc", + feature = "cbf", +))] +pub async fn send_payjoin_post_request( + req: payjoin::Request, +) -> reqwest::Result { + let client = reqwest::Client::new(); + client + .post(req.url) + .header("Content-Type", req.content_type) + .body(req.body) + .send() + .await +}