diff --git a/Cargo.lock b/Cargo.lock index 3226e48..35370bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,7 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ "generic-array", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -49,17 +49,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "aes" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures 0.2.9", -] - [[package]] name = "aes-gcm" version = "0.9.2" @@ -67,24 +56,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f" dependencies = [ "aead 0.4.3", - "aes 0.7.5", + "aes", "cipher 0.3.0", - "ctr 0.7.0", - "ghash 0.4.4", - "subtle", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead 0.5.2", - "aes 0.8.3", - "cipher 0.4.4", - "ctr 0.9.2", - "ghash 0.5.0", + "ctr", + "ghash", "subtle", ] @@ -378,6 +353,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", + "secp256k1 0.29.0", + "sha2 0.10.8", + "subtle", + "zeroize", +] + [[package]] name = "bitcoin-internals" version = "0.1.0" @@ -399,6 +393,29 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "340e09e8399c7bd8912f495af6aa58bea0c9214773417ffaa8f6460f93aaee56" +[[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", + "serde", + "serde_derive", + "sha2 0.9.9", + "thiserror", + "toml", +] + [[package]] name = "bitcoin-private" version = "0.1.0" @@ -767,7 +784,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core 0.6.4", + "rand_core", "typenum", ] @@ -790,28 +807,6 @@ dependencies = [ "cipher 0.3.0", ] -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher 0.4.4", -] - -[[package]] -name = "curve25519-dalek" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "subtle", - "zeroize", -] - [[package]] name = "darling" version = "0.13.4" @@ -901,12 +896,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - [[package]] name = "esplora-client" version = "0.6.0" @@ -1087,17 +1076,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" dependencies = [ "opaque-debug", - "polyval 0.5.3", -] - -[[package]] -name = "ghash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" -dependencies = [ - "opaque-debug", - "polyval 0.6.1", + "polyval", ] [[package]] @@ -1123,31 +1102,6 @@ dependencies = [ "scroll", ] -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - [[package]] name = "heck" version = "0.4.1" @@ -1225,38 +1179,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "hpke" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf39e5461bfdc6ad0fbc97067519fcaf96a7a2e67f24cc0eb8a1e7c0c45af792" -dependencies = [ - "aead 0.5.2", - "aes-gcm 0.10.3", - "byteorder", - "chacha20poly1305 0.10.1", - "digest 0.10.7", - "generic-array", - "hkdf 0.12.4", - "hmac 0.12.1", - "rand_core 0.6.4", - "sha2 0.10.8", - "subtle", - "x25519-dalek", - "zeroize", -] - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.1.0" @@ -1268,17 +1190,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -1286,7 +1197,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.1.0", + "http", ] [[package]] @@ -1297,8 +1208,8 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.1.0", - "http-body 1.0.1", + "http", + "http-body", "pin-project-lite", ] @@ -1314,30 +1225,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "hyper" -version = "0.14.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.7", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.4.1" @@ -1347,8 +1234,8 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", - "http-body 1.0.1", + "http", + "http-body", "httparse", "httpdate", "itoa", @@ -1358,22 +1245,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.30", - "log", - "rustls 0.21.7", - "rustls-native-certs 0.6.3", - "tokio", - "tokio-rustls 0.24.1", -] - [[package]] name = "hyper-rustls" version = "0.26.0" @@ -1381,15 +1252,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", - "http 1.1.0", - "hyper 1.4.1", + "http", + "hyper", "hyper-util", "log", "rustls 0.22.4", - "rustls-native-certs 0.7.1", + "rustls-native-certs", "rustls-pki-types", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls", "tower-service", "webpki-roots 0.26.3", ] @@ -1401,7 +1272,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a343d17fe7885302ed7252767dc7bb83609a874b6ff581142241ec4b73957ad" dependencies = [ "http-body-util", - "hyper 1.4.1", + "hyper", "hyper-util", "pin-project-lite", "tokio", @@ -1418,9 +1289,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", - "http-body 1.0.1", - "hyper 1.4.1", + "http", + "http-body", + "hyper", "pin-project-lite", "socket2 0.5.7", "tokio", @@ -1445,16 +1316,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "indexmap" -version = "2.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" -dependencies = [ - "equivalent", - "hashbrown", -] - [[package]] name = "inout" version = "0.1.3" @@ -1658,29 +1519,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "ohttp" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578cb11a3fb5c85697ed8bb850d5ad1cbf819d3eea05c2b253cf1d240fbb10c5" -dependencies = [ - "aead 0.4.3", - "aes-gcm 0.9.2", - "byteorder", - "chacha20poly1305 0.8.0", - "hex", - "hkdf 0.11.0", - "hpke", - "lazy_static", - "log", - "rand", - "serde", - "serde_derive", - "sha2 0.9.9", - "thiserror", - "toml", -] - [[package]] name = "ohttp-relay" version = "0.0.8" @@ -1688,10 +1526,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7850c40a0aebcba289d3252c0a45f93cba6ad4b0c46b88a5fc51dba6ddce8632" dependencies = [ "futures", - "http 1.1.0", + "http", "http-body-util", - "hyper 1.4.1", - "hyper-rustls 0.26.0", + "hyper", + "hyper-rustls", "hyper-tungstenite", "hyper-util", "once_cell", @@ -1784,16 +1622,14 @@ checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "payjoin" version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf485245549b366884e295426755ce649d924762f676c1cc00e12e21501884a3" dependencies = [ "bhttp", "bip21", "bitcoin 0.32.2", - "chacha20poly1305 0.10.1", - "http 1.1.0", + "bitcoin-hpke", + "bitcoin-ohttp", + "http", "log", - "ohttp", "reqwest", "rustls 0.22.4", "serde", @@ -1804,18 +1640,20 @@ dependencies = [ [[package]] name = "payjoin-directory" version = "0.0.1" -source = "git+https://github.com/payjoin/rust-payjoin#12e08ce3476562e5d22512e17c13281e156bc4b2" dependencies = [ "anyhow", "bhttp", - "bitcoin 0.30.1", + "bitcoin 0.32.2", + "bitcoin-ohttp", "futures", - "hyper 0.14.30", - "hyper-rustls 0.24.2", - "ohttp", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", "redis", - "rustls 0.21.7", + "rustls 0.22.4", "tokio", + "tokio-rustls", "tracing", "tracing-subscriber", ] @@ -1826,10 +1664,10 @@ version = "0.20.0" dependencies = [ "base64 0.22.1", "bdk", + "bitcoin-ohttp", "bitcoincore-rpc", "hex", - "http 1.1.0", - "ohttp", + "http", "ohttp-relay", "payjoin", "payjoin-directory", @@ -1938,18 +1776,6 @@ dependencies = [ "universal-hash 0.4.0", ] -[[package]] -name = "polyval" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.9", - "opaque-debug", - "universal-hash 0.5.1", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -1988,7 +1814,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -1998,15 +1824,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" - [[package]] name = "rand_core" version = "0.6.4" @@ -2121,11 +1941,11 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.1.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", - "hyper 1.4.1", - "hyper-rustls 0.26.0", + "hyper", + "hyper-rustls", "hyper-util", "ipnet", "js-sys", @@ -2135,14 +1955,14 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls 0.22.4", - "rustls-pemfile 2.1.2", + "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", @@ -2213,18 +2033,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-native-certs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" -dependencies = [ - "openssl-probe", - "rustls-pemfile 1.0.4", - "schannel", - "security-framework", -] - [[package]] name = "rustls-native-certs" version = "0.7.1" @@ -2232,21 +2040,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.2", + "rustls-pemfile", "rustls-pki-types", "schannel", "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - [[package]] name = "rustls-pemfile" version = "2.1.2" @@ -2633,9 +2432,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -2794,16 +2593,6 @@ dependencies = [ "syn 2.0.48", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.7", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.25.0" @@ -2952,7 +2741,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.1.0", + "http", "httparse", "log", "rand", @@ -3521,17 +3310,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "x25519-dalek" -version = "2.0.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5da623d8af10a62342bcbbb230e33e58a63255a58012f8653c578e54bab48df" -dependencies = [ - "curve25519-dalek", - "rand_core 0.6.4", - "zeroize", -] - [[package]] name = "yasna" version = "0.5.2" diff --git a/Cargo.toml b/Cargo.toml index 15c6fcf..4981c28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ uniffi = { version = "0.28.0", features = ["bindgen-tests"] } bdk = { version = "0.29.0", features = ["all-keys", "use-esplora-ureq", "keys-bip39"] } bitcoincore-rpc = "0.19.0" http = "1" -payjoin-directory = { git = "https://github.com/payjoin/rust-payjoin", features = ["danger-local-https"] } +payjoin-directory = { path = "../payjoin/payjoin-directory", features = ["danger-local-https"] } ohttp-relay = "0.0.8" rcgen = { version = "0.11" } reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } @@ -27,10 +27,10 @@ testcontainers-modules = { version = "0.1.3", features = ["redis"] } tokio = { version = "1.12.0", features = ["full"] } [dependencies] -payjoin = {version = "=0.20.0", features = ["send", "receive", "base64", "v2", "io"] } +payjoin = {path = "../payjoin/payjoin", features = ["send", "receive", "base64", "v2", "io"] } uniffi = { version = "0.28.0" } thiserror = "1.0.47" -ohttp = { version = "0.5.1" } +ohttp = { package = "bitcoin-ohttp", version = "0.6.0" } url = "2.5.0" base64 = "0.22.1" hex = "0.4.3" diff --git a/src/lib.rs b/src/lib.rs index 84b6331..0d48550 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,12 +2,15 @@ pub mod error; pub mod io; +pub mod ohttp; pub mod receive; pub mod send; pub mod types; pub mod uri; use crate::error::PayjoinError; +#[allow(unused_imports)] +use crate::ohttp::ClientResponse; #[cfg(feature = "uniffi")] use crate::receive::v1::{ CanBroadcast, GenerateScript, IsOutputKnown, IsScriptOwned, ProcessPartiallySignedTransaction, @@ -19,16 +22,11 @@ use crate::receive::v1::{ }; #[allow(unused_imports)] use crate::receive::v2::{ - ActiveSession, ClientResponse, RequestResponse, SessionInitializer, V2MaybeInputsOwned, - V2MaybeInputsSeen, V2MaybeMixedInputScripts, V2OutputsUnknown, V2PayjoinProposal, - V2ProvisionalProposal, V2UncheckedProposal, -}; -#[allow(unused_imports)] -use crate::send::v1::{ - ContextV1, RequestBuilder, RequestContext, RequestContextV1, RequestContextV2, + Receiver, RequestResponse, V2MaybeInputsOwned, V2MaybeInputsSeen, V2MaybeMixedInputScripts, + V2OutputsUnknown, V2PayjoinProposal, V2ProvisionalProposal, V2UncheckedProposal, }; #[allow(unused_imports)] -use crate::send::v2::ContextV2; +use crate::send::*; #[allow(unused_imports)] use crate::types::{Network, OhttpKeys, OutPoint, Request, TxOut}; #[allow(unused_imports)] diff --git a/src/ohttp.rs b/src/ohttp.rs new file mode 100644 index 0000000..46b13ac --- /dev/null +++ b/src/ohttp.rs @@ -0,0 +1,15 @@ +use std::sync::Mutex; + +pub struct ClientResponse(Mutex>); + +impl From<&ClientResponse> for ohttp::ClientResponse { + fn from(value: &ClientResponse) -> Self { + let mut data_guard = value.0.lock().unwrap(); + Option::take(&mut *data_guard).expect("ClientResponse moved out of memory") + } +} +impl From for ClientResponse { + fn from(value: ohttp::ClientResponse) -> Self { + Self(Mutex::new(Some(value))) + } +} diff --git a/src/receive/v1.rs b/src/receive/v1.rs index 7602ad3..790d89e 100644 --- a/src/receive/v1.rs +++ b/src/receive/v1.rs @@ -7,7 +7,7 @@ use payjoin::bitcoin::FeeRate; use payjoin::receive as pdk; use crate::error::PayjoinError; -use crate::types::{OutPoint, TxOut}; +use crate::types::{OutPoint, Script, TxOut}; pub trait CanBroadcast { fn callback(&self, tx: Vec) -> Result; @@ -280,7 +280,7 @@ impl OutputsUnknown { pub fn identify_receiver_outputs( &self, is_receiver_output: impl Fn(&Vec) -> Result, - ) -> Result { + ) -> Result { self.0 .clone() .identify_receiver_outputs(|input| { @@ -292,6 +292,50 @@ impl OutputsUnknown { } } +pub struct WantsOutputs(payjoin::receive::WantsOutputs); + +impl From for WantsOutputs { + fn from(value: payjoin::receive::WantsOutputs) -> Self { + Self(value) + } +} +impl WantsOutputs { + pub fn replace_receiver_outputs( + &self, + replacement_outputs: Vec, + drain_script: &Script, + ) -> Result { + self.0 + .clone() + .replace_receiver_outputs(replacement_outputs.into(), drain_script.clone().into()) + } + + pub fn commit_outputs(&self) -> Result { + self.0.clone().commit_outputs().map_err(|e| e.into()) + } +} + +pub struct WantsInputs(payjoin::receive::WantsInputs); + +impl From for WantsInputs { + fn from(value: payjoin::receive::WantsInputs) -> Self { + Self(value) + } +} + +impl WantsInputs { + pub fn contribute_witness_inputs( + &self, + replacement_inputs: Vec<(OutPoint, TxOut)>, + ) -> Result { + self.0.clone().replace_receiver_inputs(replacement_inputs.into()) + } + + pub fn commit_inputs(&self) -> Result { + self.0.clone().commit_inputs().map_err(|e| e.into()) + } +} + ///A mutable checked proposal that the receiver may contribute inputs to make a payjoin. pub struct ProvisionalProposal(Mutex); @@ -370,6 +414,7 @@ impl ProvisionalProposal { &self, process_psbt: Box, min_feerate_sat_per_vb: Option, + max_feerate_sat_per_vb: u64, ) -> Result, PayjoinError> { self.mutex_guard() .clone() @@ -381,6 +426,7 @@ impl ProvisionalProposal { .map_err(|e| pdk::Error::Server(Box::new(e))) }, min_feerate_sat_per_vb.and_then(|x| FeeRate::from_sat_per_vb(x)), + FeeRate::from_sat_per_vb(max_feerate_sat_per_vb), ) .map(|e| Arc::new(e.into())) .map_err(|e| e.into()) @@ -390,6 +436,7 @@ impl ProvisionalProposal { &self, process_psbt: impl Fn(String) -> Result, min_feerate_sat_per_vb: Option, + max_feerate_sat_per_vb: u64, ) -> Result, PayjoinError> { self.mutex_guard() .clone() @@ -400,6 +447,7 @@ impl ProvisionalProposal { .map_err(|e| pdk::Error::Server(Box::new(e))) }, min_feerate_sat_per_vb.and_then(|x| FeeRate::from_sat_per_vb(x)), + FeeRate::from_sat_per_vb(max_feerate_sat_per_vb), ) .map(|e| Arc::new(e.into())) .map_err(|e| e.into()) diff --git a/src/receive/v2.rs b/src/receive/v2.rs index de8ee22..29c64a5 100644 --- a/src/receive/v2.rs +++ b/src/receive/v2.rs @@ -8,52 +8,40 @@ use payjoin::bitcoin::psbt::Psbt; use payjoin::bitcoin::FeeRate; use payjoin::receive as pdk; +use crate::ohttp::ClientResponse; #[cfg(feature = "uniffi")] use crate::receive::v1::{ CanBroadcast, GenerateScript, IsOutputKnown, IsScriptOwned, ProcessPartiallySignedTransaction, }; -use crate::types::Network; +use crate::types::{Network, Script}; use crate::uri::PjUriBuilder; use crate::{OhttpKeys, OutPoint, PayjoinError, Request, TxOut, Url}; -pub struct ClientResponse(Mutex>); - -impl From<&ClientResponse> for ohttp::ClientResponse { - fn from(value: &ClientResponse) -> Self { - let mut data_guard = value.0.lock().unwrap(); - Option::take(&mut *data_guard).expect("ClientResponse moved out of memory") - } -} -impl From for ClientResponse { - fn from(value: ohttp::ClientResponse) -> Self { - Self(Mutex::new(Some(value))) - } -} - pub struct RequestResponse { pub request: Request, pub client_response: Arc, } #[derive(Clone, Debug)] -pub struct SessionInitializer(pub payjoin::receive::v2::SessionInitializer); -impl From for payjoin::receive::v2::SessionInitializer { - fn from(value: SessionInitializer) -> Self { +pub struct Receiver(pub payjoin::receive::v2::Receiver); +impl From for payjoin::receive::v2::Receiver { + fn from(value: Receiver) -> Self { value.0 } } -impl From for SessionInitializer { - fn from(value: payjoin::receive::v2::SessionInitializer) -> Self { +impl From for Receiver { + fn from(value: payjoin::receive::v2::Receiver) -> Self { Self(value) } } -impl SessionInitializer { +impl Receiver { /// Creates a new `SessionInitializer` with the provided parameters. /// /// # Parameters /// - `address`: The Bitcoin address for the payjoin session. + /// - `network`: The network to use for address verification. /// - `directory`: The URL of the store-and-forward payjoin directory. /// - `ohttp_keys`: The OHTTP keys used for encrypting and decrypting HTTP requests and responses. /// - `ohttp_relay`: The URL of the OHTTP relay, used to keep client IP address confidential. @@ -67,15 +55,15 @@ impl SessionInitializer { #[cfg(feature = "uniffi")] pub fn new( address: String, - expire_after: Option, network: Network, directory: Arc, ohttp_keys: Arc, ohttp_relay: Arc, + expire_after: Option, ) -> Result { let address = payjoin::bitcoin::Address::from_str(address.as_str())? .require_network(network.into())?; - Ok(payjoin::receive::v2::SessionInitializer::new( + Ok(payjoin::receive::v2::Receiver::new( address, (*directory).clone().into(), (*ohttp_keys).clone().into(), @@ -87,15 +75,15 @@ impl SessionInitializer { #[cfg(not(feature = "uniffi"))] pub fn new( address: String, - expire_after: Option, network: Network, directory: Url, ohttp_keys: OhttpKeys, ohttp_relay: Url, + expire_after: Option, ) -> Result { let address = payjoin::bitcoin::Address::from_str(address.as_str())? .require_network(network.into())?; - Ok(payjoin::receive::v2::SessionInitializer::new( + Ok(payjoin::receive::v2::Receiver::new( address, directory.into(), ohttp_keys.into(), @@ -121,61 +109,7 @@ impl SessionInitializer { Err(e) => Err(PayjoinError::V2Error { message: e.to_string() }), } } - #[cfg(not(feature = "uniffi"))] - pub fn process_res( - &self, - body: Vec, - ctx: ohttp::ClientResponse, - ) -> Result { - >::into(self.clone()) - .process_res(Cursor::new(body), ctx) - .map(|e| e.into()) - .map_err(|e| e.into()) - } - #[cfg(feature = "uniffi")] - pub fn process_res( - &self, - body: Vec, - ctx: Arc, - ) -> Result, PayjoinError> { - >::into(self.clone()) - .process_res(Cursor::new(body), ctx.as_ref().into()) - .map(|e| Arc::new(e.into())) - .map_err(|e| e.into()) - } -} -#[derive(Clone, Debug)] -pub struct ActiveSession(payjoin::receive::v2::ActiveSession); - -impl From for payjoin::receive::v2::ActiveSession { - fn from(value: ActiveSession) -> Self { - value.0 - } -} - -impl From for ActiveSession { - fn from(value: payjoin::receive::v2::ActiveSession) -> Self { - Self(value) - } -} -impl ActiveSession { - #[cfg(feature = "uniffi")] - pub fn extract_req(&self) -> Result { - match self.0.clone().extract_req() { - Ok(e) => { - Ok(RequestResponse { request: e.0.into(), client_response: Arc::new(e.1.into()) }) - } - Err(e) => Err(PayjoinError::V2Error { message: e.to_string() }), - } - } - #[cfg(not(feature = "uniffi"))] - pub fn extract_req(&self) -> Result<(Request, ohttp::ClientResponse), PayjoinError> { - match self.0.clone().extract_req() { - Ok(e) => Ok((e.0.into(), e.1)), - Err(e) => Err(PayjoinError::V2Error { message: e.to_string() }), - } - } ///The response can either be an UncheckedProposal or an ACCEPTED message indicating no UncheckedProposal is available yet. #[cfg(feature = "uniffi")] pub fn process_res( @@ -183,7 +117,7 @@ impl ActiveSession { body: Vec, context: Arc, ) -> Result>, PayjoinError> { - >::into(self.clone()) + >::into(self.clone()) .process_res(Cursor::new(body), context.as_ref().into()) .map(|e| e.map(|x| Arc::new(x.into()))) .map_err(|e| e.into()) @@ -195,21 +129,20 @@ impl ActiveSession { body: Vec, ctx: ohttp::ClientResponse, ) -> Result, PayjoinError> { - >::into(self.clone()) + >::into(self.clone()) .process_res(Cursor::new(body), ctx) .map(|e| e.map(|o| o.into())) .map_err(|e| e.into()) } + #[cfg(not(feature = "uniffi"))] pub fn pj_uri_builder(&self) -> PjUriBuilder { - >::into(self.clone()) - .pj_uri_builder() - .into() + >::into(self.clone()).pj_uri_builder().into() } #[cfg(feature = "uniffi")] pub fn pj_uri_builder(&self) -> Arc { Arc::new( - >::into(self.clone()) + >::into(self.clone()) .pj_uri_builder() .into(), ) @@ -218,23 +151,15 @@ impl ActiveSession { /// This identifies a session at the payjoin directory server. #[cfg(feature = "uniffi")] pub fn pj_url(&self) -> Arc { - Arc::new( - >::into(self.clone()) - .pj_url() - .into(), - ) + Arc::new(>::into(self.clone()).pj_url().into()) } #[cfg(not(feature = "uniffi"))] pub fn pj_url(&self) -> Url { - >::into(self.clone()) - .pj_url() - .into() + >::into(self.clone()).pj_url().into() } ///The per-session public key to use as an identifier - pub fn public_key(&self) -> String { - >::into(self.clone()) - .public_key() - .to_string() + pub fn id(&self) -> String { + >::into(self.clone()).id().to_string() } } @@ -470,6 +395,50 @@ impl V2OutputsUnknown { } } +pub struct V2WantsOutputs(payjoin::receive::v2::WantsOutputs); + +impl From for V2WantsOutputs { + fn from(value: payjoin::receive::v2::WantsOutputs) -> Self { + Self(value) + } +} +impl V2WantsOutputs { + pub fn replace_receiver_outputs( + &self, + replacement_outputs: Vec, + drain_script: &Script, + ) -> Result { + self.0 + .clone() + .replace_receiver_outputs(replacement_outputs.into(), drain_script.clone().into()) + } + + pub fn commit_outputs(&self) -> Result { + self.0.clone().commit_outputs().map_err(|e| e.into()) + } +} + +pub struct V2WantsInputs(payjoin::receive::v2::WantsInputs); + +impl From for V2WantsInputs { + fn from(value: payjoin::receive::v2::WantsInputs) -> Self { + Self(value) + } +} + +impl V2WantsInputs { + pub fn contribute_witness_inputs( + &self, + replacement_inputs: Vec<(OutPoint, TxOut)>, + ) -> Result { + self.0.clone().replace_receiver_inputs(replacement_inputs.into()) + } + + pub fn commit_inputs(&self) -> Result { + self.0.clone().commit_inputs().map_err(|e| e.into()) + } +} + pub struct V2ProvisionalProposal(pub Mutex); impl From for V2ProvisionalProposal { @@ -556,6 +525,7 @@ impl V2ProvisionalProposal { &self, process_psbt: Box, min_feerate_sat_per_vb: Option, + max_fee_rate_sat_per_vb: u64, ) -> Result, PayjoinError> { self.mutex_guard() .clone() @@ -571,6 +541,7 @@ impl V2ProvisionalProposal { } }, min_feerate_sat_per_vb.and_then(|x| FeeRate::from_sat_per_vb(x)), + FeeRate::from_sat_per_vb(max_fee_rate_sat_per_vb), ) .map(|e| Arc::new(e.into())) .map_err(|e| e.into()) @@ -580,6 +551,7 @@ impl V2ProvisionalProposal { &self, process_psbt: impl Fn(String) -> Result, min_feerate_sat_per_vb: Option, + max_feerate_sat_per_vb: u64, ) -> Result, PayjoinError> { self.mutex_guard() .clone() @@ -594,6 +566,7 @@ impl V2ProvisionalProposal { } }, min_feerate_sat_per_vb.and_then(|x| FeeRate::from_sat_per_vb(x)), + FeeRate::from_sat_per_vb(max_feerate_sat_per_vb), ) .map(|e| Arc::new(e.into())) .map_err(|e| e.into()) diff --git a/src/send/mod.rs b/src/send/mod.rs index ae6adc7..6433f25 100644 --- a/src/send/mod.rs +++ b/src/send/mod.rs @@ -1,2 +1,11 @@ +use std::sync::Arc; + pub mod v1; pub mod v2; + +struct Context(Arc); +impl From for Context { + fn from(value: payjoin::send::Context) -> Self { + Self(Arc::new(value)) + } +} diff --git a/src/send/v1.rs b/src/send/v1.rs index cc5b392..f8bc135 100644 --- a/src/send/v1.rs +++ b/src/send/v1.rs @@ -5,7 +5,7 @@ use std::sync::Arc; pub use payjoin::send as pdk; use crate::error::PayjoinError; -use crate::send::v2::ContextV2; +use crate::send::Context; use crate::types::Request; use crate::uri::{PjUri, Url}; @@ -13,20 +13,20 @@ use crate::uri::{PjUri, Url}; /// ///These parameters define how client wants to handle Payjoin. #[derive(Clone)] -pub struct RequestBuilder(pdk::RequestBuilder<'static>); +pub struct SenderBuilder(pdk::SenderBuilder<'static>); -impl From> for RequestBuilder { - fn from(value: pdk::RequestBuilder<'static>) -> Self { +impl From> for SenderBuilder { + fn from(value: pdk::SenderBuilder<'static>) -> Self { Self(value) } } -impl RequestBuilder { +impl SenderBuilder { //TODO: Replicate all functions like this & remove duplicate code /// Prepare an HTTP request and request context to process the response /// /// An HTTP client will own the Request data while Context sticks around so - /// a `(Request, Context)` tuple is returned from `RequestBuilder::build()` + /// a `(Request, Context)` tuple is returned from `SenderBuilder::build()` /// to keep them separated. pub fn from_psbt_and_uri( psbt: String, @@ -36,7 +36,7 @@ impl RequestBuilder { let psbt = payjoin::bitcoin::psbt::Psbt::from_str(psbt.as_str())?; #[cfg(feature = "uniffi")] let uri: PjUri = (*uri).clone(); - pdk::RequestBuilder::from_psbt_and_uri(psbt, uri.into()) + pdk::SenderBuilder::from_psbt_and_uri(psbt, uri.into()) .map(|e| e.into()) .map_err(|e| e.into()) } @@ -56,10 +56,7 @@ impl RequestBuilder { // The minfeerate parameter is set if the contribution is available in change. // // This method fails if no recommendation can be made or if the PSBT is malformed. - pub fn build_recommended( - &self, - min_fee_rate: u64, - ) -> Result, PayjoinError> { + pub fn build_recommended(&self, min_fee_rate: u64) -> Result, PayjoinError> { self.0 .clone() .build_recommended(payjoin::bitcoin::FeeRate::from_sat_per_kwu(min_fee_rate)) @@ -85,7 +82,7 @@ impl RequestBuilder { change_index: Option, min_fee_rate: u64, clamp_fee_contribution: bool, - ) -> Result, PayjoinError> { + ) -> Result, PayjoinError> { self.0 .clone() .build_with_additional_fee( @@ -101,10 +98,7 @@ impl RequestBuilder { /// /// While it's generally better to offer some contribution some users may wish not to. /// This function disables contribution. - pub fn build_non_incentivizing( - &self, - min_fee_rate: u64, - ) -> Result, PayjoinError> { + pub fn build_non_incentivizing(&self, min_fee_rate: u64) -> Result, PayjoinError> { match self .0 .clone() @@ -116,58 +110,48 @@ impl RequestBuilder { } } #[derive(Clone)] -pub struct RequestContext(payjoin::send::RequestContext); +pub struct Sender(payjoin::send::Sender); -impl From for RequestContext { - fn from(value: payjoin::send::RequestContext) -> Self { - RequestContext(value) +impl From for Sender { + fn from(value: payjoin::send::Sender) -> Self { + Self(value) } } #[derive(Clone)] -pub struct RequestContextV1 { - pub request: Request, - pub context_v1: Arc, -} - -#[derive(Clone)] -pub struct RequestContextV2 { +pub struct RequestContext { pub request: Request, - pub context_v2: Arc, + pub context: Arc, } -impl RequestContext { - /// Extract serialized V1 Request and Context from a Payjoin Proposal - pub fn extract_v1(&self) -> Result { - match self.0.clone().extract_v1() { - Ok(e) => Ok(RequestContextV1 { request: e.0.into(), context_v1: Arc::new(e.1.into()) }), - Err(e) => Err(e.into()), - } - } +impl Sender { /// Extract serialized Request and Context from a Payjoin Proposal. /// /// In order to support polling, this may need to be called many times to be encrypted with /// new unique nonces to make independent OHTTP requests. /// /// The `ohttp_proxy` merely passes the encrypted payload to the ohttp gateway of the receiver - pub fn extract_v2(&self, ohttp_proxy_url: Arc) -> Result { - match self.0.clone().extract_v2((*ohttp_proxy_url).clone().into()) { - Ok(e) => Ok(RequestContextV2 { request: e.0.into(), context_v2: Arc::new(e.1.into()) }), + pub fn extract_highest_version( + &self, + ohttp_proxy_url: Arc, + ) -> Result { + match self.0.clone().extract_highest_version((*ohttp_proxy_url).clone().into()) { + Ok(e) => Ok(RequestContext { request: e.0.into(), context: Arc::new(e.1.into()) }), Err(e) => Err(e.into()), } } } ///Data required for validation of response. -/// This type is used to process the response. Get it from RequestBuilder's build methods. Then you only need to call .process_response() on it to continue BIP78 flow. +/// This type is used to process the response. Get it from SenderBuilder's build methods. Then you only need to call .process_response() on it to continue BIP78 flow. #[derive(Clone)] -pub struct ContextV1(payjoin::send::ContextV1); -impl From for ContextV1 { - fn from(value: payjoin::send::ContextV1) -> Self { - Self(value) +pub struct V1Context(Arc); +impl From for V1Context { + fn from(value: payjoin::send::V1Context) -> Self { + Self(Arc::new(value)) } } -impl ContextV1 { +impl V1Context { ///Decodes and validates the response. /// Call this method with response from receiver to continue BIP78 flow. If the response is valid you will get appropriate PSBT that you should sign and broadcast. pub fn process_response(&self, response: Vec) -> Result { diff --git a/src/send/v2.rs b/src/send/v2.rs index 2641055..0f2d3cc 100644 --- a/src/send/v2.rs +++ b/src/send/v2.rs @@ -2,28 +2,94 @@ use std::io::Cursor; use std::sync::Mutex; use crate::error::PayjoinError; +use crate::ohttp::ClientResponse; -pub struct ContextV2(Mutex>); -impl From<&ContextV2> for payjoin::send::ContextV2 { - fn from(value: &ContextV2) -> Self { +pub struct Context(Mutex>); + +impl From<&Context> for payjoin::send::Context { + fn from(value: &Context) -> Self { let mut data_guard = value.0.lock().unwrap(); - Option::take(&mut *data_guard).expect("ContextV2 moved out of memory") + Option::take(&mut *data_guard).expect("Context moved out of memory") } } -impl From for ContextV2 { - fn from(value: payjoin::send::ContextV2) -> Self { - Self(Mutex::new(Some(value))) + +impl Context { + pub fn is_v1(&self) -> bool { + matches!( + <&Context as Into>::into(self), + payjoin::send::Context::V1(_) + ) + } + + pub fn is_v2(&self) -> bool { + matches!( + <&Context as Into>::into(self), + payjoin::send::Context::V2(_) + ) + } + + pub fn as_v1(&self) -> Option { + match <&Context as Into>::into(self) { + payjoin::send::Context::V1(ctx) => Some(ctx.into()), + _ => None, + } + } + + pub fn as_v2(&self) -> Option { + match <&Context as Into>::into(self) { + payjoin::send::Context::V2(ctx) => Some(ctx.into()), + _ => None, + } } } -impl ContextV2 { + +pub struct V2PostContext(payjoin::send::V2PostContext); + +impl V2PostContext { ///Decodes and validates the response. /// Call this method with response from receiver to continue BIP-??? flow. A successful response can either be None if the relay has not response yet or Some(Psbt). /// If the response is some valid PSBT you should sign and broadcast. - pub fn process_response(&self, response: Vec) -> Result, PayjoinError> { + pub fn process_response(&self, response: Vec) -> Result { let mut decoder = Cursor::new(response); - <&ContextV2 as Into>::into(self) - .process_response(&mut decoder) - .map(|e| e.map(|o| o.to_string())) + match self.0.process_response(&mut decoder) { + Ok(ctx) => Ok(V2GetContext(ctx)), + Err(e) => Err(e.into()), + } + } +} + +impl From for V2PostContext { + fn from(value: payjoin::send::V2PostContext) -> Self { + Self(value) + } +} + +pub struct V2GetContext(payjoin::send::V2GetContext); + +impl V2GetContext { + pub fn extract_req( + &self, + ohttp_relay: payjoin::Url, + ) -> Result<(crate::types::Request, crate::ClientResponse), PayjoinError> { + self.0 + .extract_req(ohttp_relay) + .map(|(req, resp)| (req.into(), resp.into())) .map_err(|e| e.into()) } + + /// Decodes and validates the response. + /// Call this method with response from receiver to continue BIP-??? flow. A successful response can either be None if the relay has not response yet or Some(Psbt). + /// If the response is some valid PSBT you should sign and broadcast. + pub fn process_response( + &self, + response: Vec, + ohttp_ctx: &ClientResponse, + ) -> Result, PayjoinError> { + let mut decoder = Cursor::new(response); + match self.0.process_response(&mut decoder, ohttp_ctx.into()) { + Ok(Some(psbt)) => Ok(Some(psbt.to_string())), + Ok(None) => Ok(None), + Err(e) => Err(e.into()), + } + } } diff --git a/src/types.rs b/src/types.rs index 54425a2..05adfe0 100644 --- a/src/types.rs +++ b/src/types.rs @@ -64,6 +64,15 @@ impl From for payjoin::bitcoin::TxOut { } } +#[derive(Clone, Debug)] +pub struct Script(pub payjoin::bitcoin::ScriptBuf); + +impl From for Script { + fn from(value: payjoin::bitcoin::ScriptBuf) -> Self { + Self(value) + } +} + impl From for TxOut { fn from(tx_out: payjoin::bitcoin::TxOut) -> Self { TxOut { value: tx_out.value.to_sat(), script_pubkey: tx_out.script_pubkey.to_bytes() } diff --git a/tests/bdk_integration_test.rs b/tests/bdk_integration_test.rs index d56edf4..420d948 100644 --- a/tests/bdk_integration_test.rs +++ b/tests/bdk_integration_test.rs @@ -382,7 +382,7 @@ mod v2 { use http::StatusCode; use payjoin_ffi::error::PayjoinError; use payjoin_ffi::receive::v2::{ - ActiveSession, SessionInitializer, V2PayjoinProposal, V2UncheckedProposal, + ActiveSession, Receiver, V2PayjoinProposal, V2UncheckedProposal, }; use payjoin_ffi::send::v1::RequestBuilder; use payjoin_ffi::types::{Network, OhttpKeys, OutPoint, TxOut}; @@ -494,7 +494,7 @@ mod v2 { custom_expire_after: Option, ) -> Result { let mock_ohttp_relay = directory.clone(); // pass through to - let initializer = SessionInitializer::new( + let initializer = Receiver::new( address.to_string(), custom_expire_after, Network::Regtest,