From 2ca4379d30c19ffdc16f0319d49c0dae1cc715a8 Mon Sep 17 00:00:00 2001 From: DanGould Date: Sun, 22 Oct 2023 16:12:43 -0400 Subject: [PATCH 1/6] Receive payjoin --- Cargo.lock | 747 ++++++++++++++++++++++----------- mutiny-core/Cargo.toml | 5 +- mutiny-core/src/error.rs | 9 + mutiny-core/src/lib.rs | 22 + mutiny-core/src/nodemanager.rs | 104 +++++ mutiny-core/src/onchain.rs | 95 ++++- mutiny-core/src/payjoin.rs | 63 +++ mutiny-wasm/Cargo.toml | 2 +- mutiny-wasm/src/error.rs | 4 + mutiny-wasm/src/lib.rs | 2 + mutiny-wasm/src/models.rs | 14 + 11 files changed, 828 insertions(+), 239 deletions(-) create mode 100644 mutiny-core/src/payjoin.rs diff --git a/Cargo.lock b/Cargo.lock index e9e87408a..9833e0313 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[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" @@ -29,13 +39,39 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.3" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures 0.2.12", + "opaque-debug", +] + +[[package]] +name = "aes" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", - "cpufeatures", + "cipher 0.4.4", + "cpufeatures 0.2.12", +] + +[[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 0.7.5", + "cipher 0.3.0", + "ctr 0.7.0", + "ghash 0.4.4", + "subtle", ] [[package]] @@ -44,11 +80,11 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", + "aead 0.5.2", + "aes 0.8.4", + "cipher 0.4.4", + "ctr 0.9.2", + "ghash 0.5.1", "subtle", ] @@ -60,9 +96,9 @@ checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" [[package]] name = "ahash" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", @@ -72,9 +108,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -117,7 +153,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -128,7 +164,7 @@ checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", - "cpufeatures", + "cpufeatures 0.2.12", "password-hash 0.5.0", ] @@ -151,13 +187,13 @@ dependencies = [ [[package]] name = "async-recursion" -version = "1.0.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -179,18 +215,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -251,9 +287,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -362,6 +398,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bhttp" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef06386f8f092c3419e153a657396e53cafbb901de445a5c54d96ab2ff8c7b2" +dependencies = [ + "thiserror", +] + [[package]] name = "bincode" version = "1.3.3" @@ -389,7 +434,7 @@ checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" dependencies = [ "bitcoin_hashes 0.11.0", "rand", - "rand_core", + "rand_core 0.6.4", "serde", "unicode-normalization", ] @@ -522,9 +567,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bitmaps" @@ -589,16 +634,16 @@ dependencies = [ "ff", "group", "pairing", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "byte-slice-cast" @@ -614,9 +659,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cbc" @@ -624,17 +669,14 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] name = "cc" -version = "1.0.83" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg-if" @@ -642,6 +684,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[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" @@ -649,8 +703,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher", - "cpufeatures", + "cipher 0.4.4", + "cpufeatures 0.2.12", +] + +[[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]] @@ -659,10 +726,10 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ - "aead", - "chacha20", - "cipher", - "poly1305", + "aead 0.5.2", + "chacha20 0.9.1", + "cipher 0.4.4", + "poly1305 0.8.0", "zeroize", ] @@ -678,7 +745,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -708,6 +775,15 @@ dependencies = [ "half", ] +[[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" @@ -763,6 +839,15 @@ dependencies = [ "memchr", ] +[[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.12" @@ -791,24 +876,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core", + "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 = "ctr" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher", + "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.20.6" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c376d08ea6aa96aafe61237c7200d1241cb177b7d3a542d791f2d118e9cbb955" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ "darling_core", "darling_macro", @@ -816,27 +933,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.6" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33043dcd19068b8192064c704b3f83eb464f91f1ff527b44a4e2b08d9cdb8855" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] name = "darling_macro" -version = "0.20.6" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a91391accf613803c2a9bf9abccdbaa07c54b4244a5b64883f9c3c137c86be" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -889,9 +1006,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "email_address" @@ -984,9 +1101,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "fedimint-aead" @@ -1139,7 +1256,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -1405,7 +1522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ "bitvec", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1516,7 +1633,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -1533,9 +1650,9 @@ checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ "gloo-timers 0.2.6", "send_wrapper 0.4.0", @@ -1584,12 +1701,22 @@ dependencies = [ [[package]] name = "ghash" -version = "0.5.0" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" dependencies = [ "opaque-debug", - "polyval", + "polyval 0.5.3", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval 0.6.2", ] [[package]] @@ -1608,7 +1735,7 @@ dependencies = [ "futures-core", "futures-sink", "gloo-utils", - "http 0.2.11", + "http 0.2.12", "js-sys", "pin-project", "serde", @@ -1629,7 +1756,7 @@ dependencies = [ "futures-core", "futures-sink", "gloo-utils", - "http 0.2.11", + "http 0.2.12", "js-sys", "pin-project", "serde", @@ -1684,23 +1811,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] [[package]] name = "h2" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http 0.2.11", - "indexmap 2.2.2", + "http 0.2.12", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -1709,9 +1836,9 @@ dependencies = [ [[package]] name = "half" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" +checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" dependencies = [ "cfg-if", "crunchy", @@ -1739,7 +1866,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.8", + "ahash 0.8.11", "allocator-api2", ] @@ -1751,9 +1878,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.4" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -1782,6 +1909,35 @@ 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" @@ -1791,11 +1947,32 @@ 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.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -1804,9 +1981,9 @@ dependencies = [ [[package]] name = "http" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -1820,7 +1997,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http 0.2.11", + "http 0.2.12", "pin-project-lite", ] @@ -1847,7 +2024,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http 0.2.11", + "http 0.2.12", "http-body", "httparse", "httpdate", @@ -1867,7 +2044,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http 0.2.11", + "http 0.2.12", "hyper", "rustls 0.21.10", "tokio", @@ -1934,7 +2111,7 @@ checksum = "978d142c8028edf52095703af2fad11d6f611af1246685725d6b850634647085" dependencies = [ "bitmaps", "imbl-sized-chunks", - "rand_core", + "rand_core 0.6.4", "rand_xoshiro", "version_check", ] @@ -1957,7 +2134,7 @@ dependencies = [ "autocfg", "impl-tools-lib", "proc-macro-error", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -1969,7 +2146,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -2015,9 +2192,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.2" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -2115,7 +2292,7 @@ dependencies = [ "futures-channel", "futures-util", "gloo-net 0.5.0", - "http 0.2.11", + "http 0.2.12", "jsonrpsee-core", "pin-project", "rustls-pki-types", @@ -2183,7 +2360,7 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8a07ab8da9a283b906f6735ddd17d3680158bb72259e853441d1dd0167079ec" dependencies = [ - "http 0.2.11", + "http 0.2.12", "jsonrpsee-client-transport", "jsonrpsee-core", "jsonrpsee-types", @@ -2200,13 +2377,13 @@ dependencies = [ "base64ct", "chrono", "ciborium", - "hmac", + "hmac 0.12.1", "lazy_static", - "rand_core", + "rand_core 0.6.4", "secp256k1 0.28.2", "serde", "serde_json", - "sha2", + "sha2 0.10.8", "smallvec", "subtle", "zeroize", @@ -2218,7 +2395,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.12", ] [[package]] @@ -2368,7 +2545,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29742339d2d88bd3ea1f4305e11b22d3efada9f86010ccbd7b6646837cc57e85" dependencies = [ - "aes", + "aes 0.8.4", "anyhow", "base64 0.13.1", "bech32", @@ -2484,9 +2661,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", @@ -2555,8 +2732,8 @@ dependencies = [ name = "mutiny-core" version = "0.6.6" dependencies = [ - "aes", - "aes-gcm", + "aes 0.8.4", + "aes-gcm 0.10.3", "anyhow", "argon2", "async-lock", @@ -2604,6 +2781,7 @@ dependencies = [ "moksha-core", "nostr", "nostr-sdk", + "once_cell", "payjoin", "pbkdf2 0.11.0", "reqwest", @@ -2695,13 +2873,13 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "255485c2f41cf8f39d4e4a1901199549f54e32def81a71a8afe05f75809f441d" dependencies = [ - "aes", + "aes 0.8.4", "base64 0.21.7", "bip39", "bitcoin 0.30.2", "cbc", - "chacha20", - "chacha20poly1305", + "chacha20 0.9.1", + "chacha20poly1305 0.10.1", "getrandom", "instant", "js-sys", @@ -2809,9 +2987,9 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -2849,6 +3027,29 @@ 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 = "once_cell" version = "1.19.0" @@ -2857,17 +3058,17 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.63" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if", "foreign-types", "libc", @@ -2884,7 +3085,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -2895,9 +3096,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.99" +version = "0.9.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" dependencies = [ "cc", "libc", @@ -2982,7 +3183,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -2993,7 +3194,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -3005,13 +3206,18 @@ checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "payjoin" -version = "0.13.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7b659f9e4ff06192df5d4504ea7ae866a1680eb2c87e4dca521fb14753eb0e7" +checksum = "ab3f2e1c292dd03cf1a63bcbf1a032ff89e1bd6ef303b16fa08fb8bd983c5185" dependencies = [ + "bhttp", "bip21", "bitcoin 0.30.2", + "chacha20poly1305 0.10.1", "log", + "ohttp", + "rand", + "serde", "serde_json", "url", ] @@ -3023,9 +3229,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ "digest 0.10.7", - "hmac", + "hmac 0.12.1", "password-hash 0.4.2", - "sha2", + "sha2 0.10.8", ] [[package]] @@ -3035,7 +3241,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac", + "hmac 0.12.1", ] [[package]] @@ -3062,22 +3268,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -3094,9 +3300,20 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures 0.2.12", + "opaque-debug", + "universal-hash 0.4.0", +] [[package]] name = "poly1305" @@ -3104,21 +3321,33 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.12", "opaque-debug", - "universal-hash", + "universal-hash 0.5.1", ] [[package]] name = "polyval" -version = "0.6.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.12", "opaque-debug", - "universal-hash", + "universal-hash 0.4.0", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.12", + "opaque-debug", + "universal-hash 0.5.1", ] [[package]] @@ -3199,9 +3428,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -3229,7 +3458,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -3239,9 +3468,15 @@ 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_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" @@ -3257,7 +3492,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -3271,13 +3506,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.5", + "regex-automata 0.4.6", "regex-syntax 0.8.2", ] @@ -3292,9 +3527,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -3325,7 +3560,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http 0.2.11", + "http 0.2.12", "http-body", "hyper", "hyper-rustls", @@ -3427,11 +3662,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -3464,9 +3699,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" dependencies = [ "log", "ring 0.17.8", @@ -3487,9 +3722,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048a63e5b3ac996d78d402940b5fa47973d2d080c6c6fffa1d0f19c4445310b7" +checksum = "868e20fada228fefaf6b652e00cc73623d54f8171e7352c18bb281571f2d92da" [[package]] name = "rustls-webpki" @@ -3520,9 +3755,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "salsa20" @@ -3530,7 +3765,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] @@ -3563,7 +3798,7 @@ dependencies = [ "password-hash 0.5.0", "pbkdf2 0.12.2", "salsa20", - "sha2", + "sha2 0.10.8", ] [[package]] @@ -3685,9 +3920,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "send_wrapper" @@ -3727,7 +3962,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -3736,7 +3971,7 @@ version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ - "indexmap 2.2.2", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -3756,15 +3991,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270" +checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" dependencies = [ "base64 0.21.7", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.2", + "indexmap 2.2.6", "serde", "serde_derive", "serde_json", @@ -3774,14 +4009,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d" +checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -3792,7 +4027,7 @@ checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", "cfg-if", - "cpufeatures", + "cpufeatures 0.2.12", "digest 0.9.0", "opaque-debug", ] @@ -3804,10 +4039,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.12", "digest 0.10.7", ] +[[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.12", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.8" @@ -3815,7 +4063,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.12", "digest 0.10.7", ] @@ -3858,18 +4106,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3921,7 +4169,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -3943,9 +4191,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" dependencies = [ "proc-macro2", "quote", @@ -3987,13 +4235,12 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", "windows-sys 0.52.0", ] @@ -4021,14 +4268,14 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -4117,7 +4364,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -4157,7 +4404,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pki-types", "tokio", ] @@ -4208,7 +4455,7 @@ checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" dependencies = [ "futures-util", "log", - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pki-types", "tokio", "tokio-rustls 0.25.0", @@ -4231,6 +4478,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "toml_datetime" version = "0.6.3" @@ -4243,7 +4499,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.2.2", + "indexmap 2.2.6", "toml_datetime", "winnow", ] @@ -4273,7 +4529,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -4330,7 +4586,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 0.2.11", + "http 0.2.12", "httparse", "log", "native-tls", @@ -4350,11 +4606,11 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.0.0", + "http 1.1.0", "httparse", "log", "rand", - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pki-types", "sha1", "thiserror", @@ -4398,6 +4654,16 @@ 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" @@ -4450,7 +4716,7 @@ version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "272ebdfbc99111033031d2f10e018836056e4d2c8e2acda76450ec7974269fa7" dependencies = [ - "indexmap 2.2.2", + "indexmap 2.2.6", "serde", "serde_json", "utoipa-gen", @@ -4465,14 +4731,14 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] name = "uuid" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", "serde", @@ -4505,7 +4771,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -4562,7 +4828,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", "wasm-bindgen-shared", ] @@ -4596,7 +4862,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4609,9 +4875,9 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-bindgen-test" -version = "0.3.40" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139bd73305d50e1c1c4333210c0db43d989395b64a237bd35c10ef3832a7f70c" +checksum = "143ddeb4f833e2ed0d252e618986e18bfc7b0e52f2d28d77d05b2f045dd8eb61" dependencies = [ "console_error_panic_hook", "js-sys", @@ -4623,13 +4889,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.40" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70072aebfe5da66d2716002c729a14e4aec4da0e23cc2ea66323dac541c93928" +checksum = "a5211b7550606857312bba1d978a8ec75692eae187becc5e680444fffc5e6f89" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -4662,9 +4928,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" dependencies = [ "js-sys", "wasm-bindgen", @@ -4733,7 +4999,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -4751,7 +5017,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -4771,17 +5037,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -4792,9 +5058,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -4804,9 +5070,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -4816,9 +5082,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -4828,9 +5094,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -4840,9 +5106,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -4852,9 +5118,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -4864,15 +5130,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winnow" -version = "0.5.37" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cad8365489051ae9f054164e459304af2e7e9bb407c958076c8bf4aef52da5" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] @@ -4896,6 +5162,17 @@ dependencies = [ "tap", ] +[[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 = "zerocopy" version = "0.7.32" @@ -4913,7 +5190,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -4933,5 +5210,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] diff --git a/mutiny-core/Cargo.toml b/mutiny-core/Cargo.toml index 8729f0771..e522e4754 100644 --- a/mutiny-core/Cargo.toml +++ b/mutiny-core/Cargo.toml @@ -33,7 +33,7 @@ lightning-rapid-gossip-sync = { version = "0.0.121" } lightning-background-processor = { version = "0.0.121", features = ["futures"] } lightning-transaction-sync = { version = "0.0.121", default-features = false, features = ["esplora-async-https"] } lightning-liquidity = "0.1.0-alpha.2" -chrono = "0.4.22" +chrono = "0.4.33" futures-util = { version = "0.3", default-features = false } reqwest = { version = "0.11", default-features = false, features = ["multipart", "json"] } async-trait = "0.1.68" @@ -44,7 +44,8 @@ cbc = { version = "0.1", features = ["alloc"] } aes = { version = "0.8" } jwt-compact = { version = "0.8.0-beta.1", features = ["es256k"] } argon2 = { version = "0.5.0", features = ["password-hash", "alloc"] } -payjoin = { version = "0.13.0", features = ["send", "base64"] } +once_cell = "1.18.0" +payjoin = { version = "0.15.0", features = ["v2", "send", "receive", "base64"] } bincode = "1.3.3" hex-conservative = "0.1.1" async-lock = "3.2.0" diff --git a/mutiny-core/src/error.rs b/mutiny-core/src/error.rs index 2ccee84d8..b3688a9fa 100644 --- a/mutiny-core/src/error.rs +++ b/mutiny-core/src/error.rs @@ -154,6 +154,9 @@ pub enum MutinyError { /// Cannot change password to the same password #[error("Cannot change password to the same password.")] SamePassword, + /// Error with payjoin + #[error("Payjoin error: {0}")] + Payjoin(crate::payjoin::Error), /// Payjoin request creation failed. #[error("Failed to create payjoin request.")] PayjoinCreateRequest, @@ -574,6 +577,12 @@ impl From for MutinyError { } } +impl From for MutinyError { + fn from(e: crate::payjoin::Error) -> Self { + Self::Payjoin(e) + } +} + impl From for MutinyError { fn from(_e: payjoin::send::CreateRequestError) -> Self { Self::PayjoinCreateRequest diff --git a/mutiny-core/src/lib.rs b/mutiny-core/src/lib.rs index 0ce0bc0f4..6f1e0349d 100644 --- a/mutiny-core/src/lib.rs +++ b/mutiny-core/src/lib.rs @@ -8,6 +8,7 @@ type_alias_bounds )] extern crate core; +extern crate payjoin as pj; pub mod auth; pub mod blindauth; @@ -33,6 +34,7 @@ mod node; pub mod nodemanager; pub mod nostr; mod onchain; +mod payjoin; mod peermanager; pub mod scorer; pub mod storage; @@ -1573,11 +1575,31 @@ impl MutinyWallet { return Err(MutinyError::WalletOperationFailed); }; + let (pj, ohttp) = match self.node_manager.start_payjoin_session().await { + Ok((enrolled, ohttp_keys)) => { + let pj_uri = enrolled.fallback_target(); + self.node_manager.spawn_payjoin_receiver(enrolled); + let ohttp = base64::encode_config( + ohttp_keys + .encode() + .map_err(|_| MutinyError::PayjoinConfigError)?, + base64::URL_SAFE_NO_PAD, + ); + (Some(pj_uri), Some(ohttp)) + } + Err(e) => { + log_error!(self.logger, "Error enrolling payjoin: {e}"); + (None, None) + } + }; + Ok(MutinyBip21RawMaterials { address, invoice, btc_amount: amount.map(|amount| bitcoin::Amount::from_sat(amount).to_btc().to_string()), labels, + pj, + ohttp, }) } diff --git a/mutiny-core/src/nodemanager.rs b/mutiny-core/src/nodemanager.rs index d723c1889..d67d07081 100644 --- a/mutiny-core/src/nodemanager.rs +++ b/mutiny-core/src/nodemanager.rs @@ -2,6 +2,7 @@ use crate::auth::MutinyAuthClient; use crate::labels::LabelStorage; use crate::ldkstorage::CHANNEL_CLOSURE_PREFIX; use crate::logging::LOGGING_KEY; +use crate::payjoin::Error as PayjoinError; use crate::utils::{sleep, spawn}; use crate::MutinyInvoice; use crate::MutinyWalletConfig; @@ -50,6 +51,7 @@ use lightning::{log_debug, log_error, log_info, log_trace, log_warn}; use lightning_invoice::Bolt11Invoice; use lightning_transaction_sync::EsploraSyncClient; use payjoin::Uri; +use pj::receive::v2::Enrolled; use reqwest::Client; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -99,6 +101,8 @@ pub struct MutinyBip21RawMaterials { pub invoice: Option, pub btc_amount: Option, pub labels: Vec, + pub pj: Option, + pub ohttp: Option, } #[derive(Serialize, Deserialize, Clone, Eq, PartialEq)] @@ -666,6 +670,34 @@ impl NodeManager { Err(MutinyError::WalletOperationFailed) } + pub async fn start_payjoin_session( + &self, + ) -> Result<(Enrolled, payjoin::OhttpKeys), PayjoinError> { + use crate::payjoin::{fetch_ohttp_keys, random_ohttp_relay, PAYJOIN_DIR}; + + let ohttp_keys = fetch_ohttp_keys(PAYJOIN_DIR.to_owned()).await?; + let http_client = reqwest::Client::builder().build()?; + + let mut enroller = payjoin::receive::v2::Enroller::from_directory_config( + PAYJOIN_DIR.to_owned(), + ohttp_keys.clone(), + random_ohttp_relay().to_owned(), + ); + let (req, context) = enroller.extract_req()?; + let ohttp_response = http_client + .post(req.url) + .header("Content-Type", "message/ohttp-req") + .body(req.body) + .send() + .await?; + let ohttp_response = ohttp_response.bytes().await?; + Ok(( + enroller.process_res(ohttp_response.as_ref(), context)?, + ohttp_keys, + )) + } + + // Send v1 payjoin request pub async fn send_payjoin( &self, uri: Uri<'_, NetworkUnchecked>, @@ -740,6 +772,78 @@ impl NodeManager { Ok(txid) } + pub fn spawn_payjoin_receiver(&self, enrolled: Enrolled) { + let logger = self.logger.clone(); + let wallet = self.wallet.clone(); + utils::spawn(async move { + match Self::receive_payjoin(wallet, enrolled).await { + Ok(txid) => log_info!(logger, "Received payjoin txid: {txid}"), + Err(e) => log_error!(logger, "Error receiving payjoin: {e}"), + }; + }); + } + + /// Poll the payjoin relay to maintain a payjoin session and create a payjoin proposal. + async fn receive_payjoin( + wallet: Arc>, + mut enrolled: payjoin::receive::v2::Enrolled, + ) -> Result { + let http_client = reqwest::Client::builder() + .build() + .map_err(PayjoinError::Reqwest)?; + let proposal: payjoin::receive::v2::UncheckedProposal = + Self::poll_for_fallback_psbt(&http_client, &mut enrolled).await?; + let original_tx = proposal.extract_tx_to_schedule_broadcast(); + let mut payjoin_proposal = match wallet + .process_payjoin_proposal(proposal) + .map_err(|e| PayjoinError::ReceiverStateMachine(e.to_string())) + { + Ok(p) => p, + Err(e) => { + wallet.broadcast_transaction(original_tx).await?; + return Err(e.into()); + } + }; + + let (req, ohttp_ctx) = payjoin_proposal + .extract_v2_req() + .map_err(|e| PayjoinError::ReceiverStateMachine(e.to_string()))?; + let res = http_client + .post(req.url) + .header("Content-Type", "message/ohttp-req") + .body(req.body) + .send() + .await + .map_err(PayjoinError::Reqwest)?; + let res = res.bytes().await.map_err(PayjoinError::Reqwest)?; + // enroll must succeed + let _res = payjoin_proposal + .deserialize_res(res.to_vec(), ohttp_ctx) + .map_err(|e| PayjoinError::ReceiverStateMachine(e.to_string()))?; + Ok(payjoin_proposal.psbt().clone().extract_tx().txid()) + } + + async fn poll_for_fallback_psbt( + client: &reqwest::Client, + enroller: &mut payjoin::receive::v2::Enrolled, + ) -> Result { + loop { + let (req, context) = enroller.extract_req()?; + let ohttp_response = client + .post(req.url) + .header("Content-Type", "message/ohttp-req") + .body(req.body) + .send() + .await?; + let ohttp_response = ohttp_response.bytes().await?; + let proposal = enroller.process_res(ohttp_response.as_ref(), context)?; + match proposal { + Some(proposal) => return Ok(proposal), + None => utils::sleep(5000).await, + } + } + } + /// Sends an on-chain transaction to the given address. /// The amount is in satoshis and the fee rate is in sat/vbyte. /// diff --git a/mutiny-core/src/onchain.rs b/mutiny-core/src/onchain.rs index 7cad3d3bc..c34f3be44 100644 --- a/mutiny-core/src/onchain.rs +++ b/mutiny-core/src/onchain.rs @@ -14,7 +14,7 @@ use bdk_esplora::EsploraAsyncExt; use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey}; use bitcoin::consensus::serialize; use bitcoin::psbt::{Input, PartiallySignedTransaction}; -use bitcoin::{Address, Network, OutPoint, ScriptBuf, Transaction, Txid}; +use bitcoin::{Address, Network, OutPoint, Script, ScriptBuf, Transaction, Txid}; use esplora_client::AsyncClient; use hex_conservative::DisplayHex; use lightning::events::bump_transaction::{Utxo, WalletSource}; @@ -355,10 +355,103 @@ impl OnChainWallet { Ok(()) } + fn is_mine(&self, script: &Script) -> Result { + Ok(self.wallet.try_read()?.is_mine(script)) + } + pub fn list_utxos(&self) -> Result, MutinyError> { Ok(self.wallet.try_read()?.list_unspent().collect()) } + pub fn process_payjoin_proposal( + &self, + proposal: payjoin::receive::v2::UncheckedProposal, + ) -> Result { + use payjoin::Error; + + // Receive Check 1 bypass: We're not an automated payment processor. + let proposal = proposal.assume_interactive_receiver(); + log::trace!("check1"); + + // Receive Check 2: receiver can't sign for proposal inputs + let proposal = proposal.check_inputs_not_owned(|input| { + self.is_mine(input).map_err(|e| Error::Server(e.into())) + })?; + log::trace!("check2"); + + // Receive Check 3: receiver can't sign for proposal inputs + let proposal = proposal.check_no_mixed_input_scripts()?; + log::trace!("check3"); + + // Receive Check 4: have we seen this input before? + let payjoin = proposal.check_no_inputs_seen_before(|_input| { + // This check ensures an automated sender does not get phished. It is not necessary for interactive payjoin **where the sender cannot generate bip21s from us** + // assume false since Mutiny is not an automatic payment processor + Ok(false) + })?; + log::trace!("check4"); + + let mut provisional_payjoin = payjoin.identify_receiver_outputs(|output| { + self.is_mine(output).map_err(|e| Error::Server(e.into())) + })?; + self.try_contributing_inputs(&mut provisional_payjoin) + .map_err(|e| Error::Server(e.into()))?; + + // Outputs may be substituted for e.g. batching at this stage + // We're not doing this yet. + + // Don't provide input to transactions with a fee rate below the low fee rate + // They might lock our coins up + let min_pj_fee_rate = + bitcoin::FeeRate::from_sat_per_kwu(self.fees.get_low_fee_rate() as u64); + let payjoin_proposal = provisional_payjoin.finalize_proposal( + |psbt| { + let mut psbt = psbt.clone(); + let wallet = self + .wallet + .try_read() + .map_err(|_| Error::Server(MutinyError::WalletSigningFailed.into()))?; + wallet + .sign(&mut psbt, SignOptions::default()) + .map_err(|_| Error::Server(MutinyError::WalletSigningFailed.into()))?; + Ok(psbt) + }, + Some(min_pj_fee_rate), + )?; + let payjoin_proposal_psbt = payjoin_proposal.psbt(); + log::debug!( + "Receiver's Payjoin proposal PSBT Rsponse: {:#?}", + payjoin_proposal_psbt + ); + Ok(payjoin_proposal) + } + + fn try_contributing_inputs( + &self, + payjoin: &mut payjoin::receive::v2::ProvisionalProposal, + ) -> Result<(), MutinyError> { + use payjoin::bitcoin::Amount; + + let available_inputs = self.list_utxos()?; + let candidate_inputs: std::collections::HashMap = available_inputs + .iter() + .filter(|u| u.confirmation_time.is_confirmed()) + .map(|i| (Amount::from_sat(i.txout.value), i.outpoint)) + .collect(); + + let selected_outpoint = payjoin + .try_preserving_privacy(candidate_inputs) + .map_err(|_| anyhow!("no privacy-preserving selection available"))?; + let selected_utxo = available_inputs + .iter() + .find(|i| i.outpoint == selected_outpoint) + .ok_or(anyhow!("This shouldn't happen. Failed to retrieve the privacy preserving utxo from those we provided to the seclector."))?; + log::debug!("selected utxo: {:#?}", selected_utxo); + + payjoin.contribute_witness_input(selected_utxo.txout.clone(), selected_outpoint); + Ok(()) + } + pub fn list_transactions( &self, include_raw: bool, diff --git a/mutiny-core/src/payjoin.rs b/mutiny-core/src/payjoin.rs new file mode 100644 index 000000000..533df8323 --- /dev/null +++ b/mutiny-core/src/payjoin.rs @@ -0,0 +1,63 @@ +use once_cell::sync::Lazy; +use payjoin::OhttpKeys; +use url::Url; + +pub(crate) static OHTTP_RELAYS: [Lazy; 2] = [ + Lazy::new(|| Url::parse("https://pj.bobspacebkk.com").expect("Invalid URL")), + Lazy::new(|| Url::parse("https://ohttp-relay.obscuravpn.io").expect("Invalid URL")), +]; + +pub fn random_ohttp_relay() -> &'static Url { + let mut buf = [0u8; 1]; + getrandom::getrandom(&mut buf).expect("Failed to get random byte"); + let idx = (buf[0] as usize) % OHTTP_RELAYS.len(); + &OHTTP_RELAYS[idx] +} + +pub(crate) static PAYJOIN_DIR: Lazy = + Lazy::new(|| Url::parse("https://payjo.in").expect("Invalid URL")); + +pub async fn fetch_ohttp_keys(directory: Url) -> Result { + let http_client = reqwest::Client::builder().build()?; + + let ohttp_keys_res = http_client + .get(format!("{}/ohttp-keys", directory.as_ref())) + .send() + .await? + .bytes() + .await?; + OhttpKeys::decode(ohttp_keys_res.as_ref()).map_err(|_| Error::OhttpDecodeFailed) +} + +#[derive(Debug)] +pub enum Error { + Reqwest(reqwest::Error), + ReceiverStateMachine(String), + Txid(bitcoin::hashes::hex::Error), + OhttpDecodeFailed, +} + +impl std::error::Error for Error {} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match &self { + Error::Reqwest(e) => write!(f, "Reqwest error: {}", e), + Error::ReceiverStateMachine(e) => write!(f, "Payjoin state machine error: {}", e), + Error::Txid(e) => write!(f, "Payjoin txid error: {}", e), + Error::OhttpDecodeFailed => write!(f, "Failed to decode ohttp keys"), + } + } +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Error::Reqwest(e) + } +} + +impl From for Error { + fn from(e: payjoin::receive::Error) -> Self { + Error::ReceiverStateMachine(e.to_string()) + } +} diff --git a/mutiny-wasm/Cargo.toml b/mutiny-wasm/Cargo.toml index 3e8f87b7b..dc1cdd612 100644 --- a/mutiny-wasm/Cargo.toml +++ b/mutiny-wasm/Cargo.toml @@ -43,7 +43,7 @@ futures = "0.3.25" urlencoding = "2.1.2" once_cell = "1.18.0" hex-conservative = "0.1.1" -payjoin = { version = "0.13.0", features = ["send", "base64"] } +payjoin = { version = "0.15.0", features = ["send", "base64"] } fedimint-core = "0.3.0" moksha-core = "0.2.1" diff --git a/mutiny-wasm/src/error.rs b/mutiny-wasm/src/error.rs index d058c7826..f52255931 100644 --- a/mutiny-wasm/src/error.rs +++ b/mutiny-wasm/src/error.rs @@ -150,6 +150,9 @@ pub enum MutinyJsError { /// Cannot change password to the same password #[error("Cannot change password to the same password.")] SamePassword, + /// Payjoin failed + #[error("Payjoin failed: {0}")] + Payjoin(String), /// Payjoin request creation failed. #[error("Failed to create payjoin request.")] PayjoinCreateRequest, @@ -243,6 +246,7 @@ impl From for MutinyJsError { MutinyError::InvalidArgumentsError => MutinyJsError::InvalidArgumentsError, MutinyError::LspAmountTooHighError => MutinyJsError::LspAmountTooHighError, MutinyError::NetworkMismatch => MutinyJsError::NetworkMismatch, + MutinyError::Payjoin(e) => MutinyJsError::Payjoin(e.to_string()), MutinyError::PayjoinConfigError => MutinyJsError::PayjoinConfigError, MutinyError::PayjoinCreateRequest => MutinyJsError::PayjoinCreateRequest, MutinyError::PayjoinResponse(e) => MutinyJsError::PayjoinResponse(e.to_string()), diff --git a/mutiny-wasm/src/lib.rs b/mutiny-wasm/src/lib.rs index 635726442..c2f0432b0 100644 --- a/mutiny-wasm/src/lib.rs +++ b/mutiny-wasm/src/lib.rs @@ -494,6 +494,8 @@ impl MutinyWallet { invoice: None, btc_amount: None, labels, + pj: None, + ohttp: None, }) } diff --git a/mutiny-wasm/src/models.rs b/mutiny-wasm/src/models.rs index 279632cf2..9051ccb6b 100644 --- a/mutiny-wasm/src/models.rs +++ b/mutiny-wasm/src/models.rs @@ -633,6 +633,8 @@ pub struct MutinyBip21RawMaterials { pub(crate) invoice: Option, pub(crate) btc_amount: Option, pub(crate) labels: Vec, + pub(crate) pj: Option, + pub(crate) ohttp: Option, } #[wasm_bindgen] @@ -661,6 +663,16 @@ impl MutinyBip21RawMaterials { pub fn labels(&self) -> Vec { self.labels.clone() } + + #[wasm_bindgen(getter)] + pub fn pj(&self) -> Option { + self.pj.clone() + } + + #[wasm_bindgen(getter)] + pub fn ohttp(&self) -> Option { + self.ohttp.clone() + } } impl From for MutinyBip21RawMaterials { @@ -670,6 +682,8 @@ impl From for MutinyBip21RawMaterials { invoice: m.invoice.map(|i| i.to_string()), btc_amount: m.btc_amount, labels: m.labels, + pj: m.pj, + ohttp: m.ohttp, } } } From 9e736db5ca51b05092b78e97ed011fb132eed6d0 Mon Sep 17 00:00:00 2001 From: DanGould Date: Mon, 27 Nov 2023 11:29:44 -0500 Subject: [PATCH 2/6] Stop payjoin session with nodemanager --- mutiny-core/src/nodemanager.rs | 10 ++++++++-- mutiny-core/src/payjoin.rs | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/mutiny-core/src/nodemanager.rs b/mutiny-core/src/nodemanager.rs index d67d07081..d9e579a75 100644 --- a/mutiny-core/src/nodemanager.rs +++ b/mutiny-core/src/nodemanager.rs @@ -774,9 +774,10 @@ impl NodeManager { pub fn spawn_payjoin_receiver(&self, enrolled: Enrolled) { let logger = self.logger.clone(); + let stop = self.stop.clone(); let wallet = self.wallet.clone(); utils::spawn(async move { - match Self::receive_payjoin(wallet, enrolled).await { + match Self::receive_payjoin(wallet, stop, enrolled).await { Ok(txid) => log_info!(logger, "Received payjoin txid: {txid}"), Err(e) => log_error!(logger, "Error receiving payjoin: {e}"), }; @@ -786,13 +787,14 @@ impl NodeManager { /// Poll the payjoin relay to maintain a payjoin session and create a payjoin proposal. async fn receive_payjoin( wallet: Arc>, + stop: Arc, mut enrolled: payjoin::receive::v2::Enrolled, ) -> Result { let http_client = reqwest::Client::builder() .build() .map_err(PayjoinError::Reqwest)?; let proposal: payjoin::receive::v2::UncheckedProposal = - Self::poll_for_fallback_psbt(&http_client, &mut enrolled).await?; + Self::poll_for_fallback_psbt(stop, &http_client, &mut enrolled).await?; let original_tx = proposal.extract_tx_to_schedule_broadcast(); let mut payjoin_proposal = match wallet .process_payjoin_proposal(proposal) @@ -824,10 +826,14 @@ impl NodeManager { } async fn poll_for_fallback_psbt( + stop: Arc, client: &reqwest::Client, enroller: &mut payjoin::receive::v2::Enrolled, ) -> Result { loop { + if stop.load(Ordering::Relaxed) { + return Err(crate::payjoin::Error::Shutdown); + } let (req, context) = enroller.extract_req()?; let ohttp_response = client .post(req.url) diff --git a/mutiny-core/src/payjoin.rs b/mutiny-core/src/payjoin.rs index 533df8323..d3e166ada 100644 --- a/mutiny-core/src/payjoin.rs +++ b/mutiny-core/src/payjoin.rs @@ -35,6 +35,7 @@ pub enum Error { ReceiverStateMachine(String), Txid(bitcoin::hashes::hex::Error), OhttpDecodeFailed, + Shutdown, } impl std::error::Error for Error {} @@ -46,6 +47,7 @@ impl std::fmt::Display for Error { Error::ReceiverStateMachine(e) => write!(f, "Payjoin state machine error: {}", e), Error::Txid(e) => write!(f, "Payjoin txid error: {}", e), Error::OhttpDecodeFailed => write!(f, "Failed to decode ohttp keys"), + Error::Shutdown => write!(f, "Payjoin stopped by application shutdown"), } } } From 1a2f135db8a8ff83704ad591208d855582122e0e Mon Sep 17 00:00:00 2001 From: DanGould Date: Thu, 30 Nov 2023 11:13:57 -0500 Subject: [PATCH 3/6] Persist payjoin receive sessions temporarily Payjoin sessions poll while the wallet is active until they expire. --- mutiny-core/src/lib.rs | 10 +++++-- mutiny-core/src/nodemanager.rs | 34 ++++++++++++++++----- mutiny-core/src/payjoin.rs | 54 ++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 10 deletions(-) diff --git a/mutiny-core/src/lib.rs b/mutiny-core/src/lib.rs index 6f1e0349d..3ea6d829d 100644 --- a/mutiny-core/src/lib.rs +++ b/mutiny-core/src/lib.rs @@ -49,6 +49,7 @@ pub use crate::gossip::{GOSSIP_SYNC_TIME_KEY, NETWORK_GRAPH_KEY, PROB_SCORER_KEY pub use crate::keymanager::generate_seed; pub use crate::ldkstorage::{CHANNEL_CLOSURE_PREFIX, CHANNEL_MANAGER_KEY, MONITORS_PREFIX_KEY}; use crate::nostr::primal::{PrimalApi, PrimalClient}; +use crate::payjoin::PayjoinStorage; use crate::storage::{ get_payment_hash_from_key, list_payment_info, persist_payment_info, update_nostr_contact_list, IndexItem, MutinyStorage, DEVICE_ID_KEY, EXPECTED_NETWORK_KEY, NEED_FULL_SYNC_KEY, @@ -1188,6 +1189,7 @@ impl MutinyWallet { // when we restart, gen a new session id self.node_manager = Arc::new(nm_builder.build().await?); NodeManager::start_sync(self.node_manager.clone()); + NodeManager::resume_payjoins(self.node_manager.clone()); Ok(()) } @@ -1577,8 +1579,12 @@ impl MutinyWallet { let (pj, ohttp) = match self.node_manager.start_payjoin_session().await { Ok((enrolled, ohttp_keys)) => { - let pj_uri = enrolled.fallback_target(); - self.node_manager.spawn_payjoin_receiver(enrolled); + let session = self + .node_manager + .storage + .store_new_recv_session(enrolled.clone())?; + let pj_uri = session.enrolled.fallback_target(); + self.node_manager.spawn_payjoin_receiver(session); let ohttp = base64::encode_config( ohttp_keys .encode() diff --git a/mutiny-core/src/nodemanager.rs b/mutiny-core/src/nodemanager.rs index d9e579a75..dd6fc4031 100644 --- a/mutiny-core/src/nodemanager.rs +++ b/mutiny-core/src/nodemanager.rs @@ -2,7 +2,7 @@ use crate::auth::MutinyAuthClient; use crate::labels::LabelStorage; use crate::ldkstorage::CHANNEL_CLOSURE_PREFIX; use crate::logging::LOGGING_KEY; -use crate::payjoin::Error as PayjoinError; +use crate::payjoin::{Error as PayjoinError, PayjoinStorage, RecvSession}; use crate::utils::{sleep, spawn}; use crate::MutinyInvoice; use crate::MutinyWalletConfig; @@ -581,6 +581,14 @@ impl NodeManager { Ok(()) } + /// Starts a background task to poll payjoin sessions to attempt receiving. + pub(crate) fn resume_payjoins(nm: Arc>) { + let all = nm.storage.list_recv_sessions().unwrap_or_default(); + for payjoin in all { + nm.clone().spawn_payjoin_receiver(payjoin); + } + } + /// Creates a background process that will sync the wallet with the blockchain. /// This will also update the fee estimates every 10 minutes. pub fn start_sync(nm: Arc>) { @@ -772,12 +780,13 @@ impl NodeManager { Ok(txid) } - pub fn spawn_payjoin_receiver(&self, enrolled: Enrolled) { + pub fn spawn_payjoin_receiver(&self, session: RecvSession) { let logger = self.logger.clone(); let stop = self.stop.clone(); + let storage = Arc::new(self.storage.clone()); let wallet = self.wallet.clone(); utils::spawn(async move { - match Self::receive_payjoin(wallet, stop, enrolled).await { + match Self::receive_payjoin(wallet, stop, storage, session).await { Ok(txid) => log_info!(logger, "Received payjoin txid: {txid}"), Err(e) => log_error!(logger, "Error receiving payjoin: {e}"), }; @@ -788,13 +797,14 @@ impl NodeManager { async fn receive_payjoin( wallet: Arc>, stop: Arc, - mut enrolled: payjoin::receive::v2::Enrolled, + storage: Arc, + mut session: crate::payjoin::RecvSession, ) -> Result { let http_client = reqwest::Client::builder() .build() .map_err(PayjoinError::Reqwest)?; let proposal: payjoin::receive::v2::UncheckedProposal = - Self::poll_for_fallback_psbt(stop, &http_client, &mut enrolled).await?; + Self::poll_for_fallback_psbt(stop, storage, &http_client, &mut session).await?; let original_tx = proposal.extract_tx_to_schedule_broadcast(); let mut payjoin_proposal = match wallet .process_payjoin_proposal(proposal) @@ -827,14 +837,20 @@ impl NodeManager { async fn poll_for_fallback_psbt( stop: Arc, + storage: Arc, client: &reqwest::Client, - enroller: &mut payjoin::receive::v2::Enrolled, + session: &mut crate::payjoin::RecvSession, ) -> Result { loop { if stop.load(Ordering::Relaxed) { return Err(crate::payjoin::Error::Shutdown); } - let (req, context) = enroller.extract_req()?; + + if session.expiry < utils::now() { + let _ = storage.delete_recv_session(&session.enrolled.pubkey()); + return Err(crate::payjoin::Error::SessionExpired); + } + let (req, context) = session.enrolled.extract_req()?; let ohttp_response = client .post(req.url) .header("Content-Type", "message/ohttp-req") @@ -842,7 +858,9 @@ impl NodeManager { .send() .await?; let ohttp_response = ohttp_response.bytes().await?; - let proposal = enroller.process_res(ohttp_response.as_ref(), context)?; + let proposal = session + .enrolled + .process_res(ohttp_response.as_ref(), context)?; match proposal { Some(proposal) => return Ok(proposal), None => utils::sleep(5000).await, diff --git a/mutiny-core/src/payjoin.rs b/mutiny-core/src/payjoin.rs index d3e166ada..94b89a5f8 100644 --- a/mutiny-core/src/payjoin.rs +++ b/mutiny-core/src/payjoin.rs @@ -1,5 +1,13 @@ +use std::collections::HashMap; + +use crate::error::MutinyError; +use crate::storage::MutinyStorage; +use core::time::Duration; +use hex_conservative::DisplayHex; use once_cell::sync::Lazy; +use payjoin::receive::v2::Enrolled; use payjoin::OhttpKeys; +use serde::{Deserialize, Serialize}; use url::Url; pub(crate) static OHTTP_RELAYS: [Lazy; 2] = [ @@ -17,6 +25,50 @@ pub fn random_ohttp_relay() -> &'static Url { pub(crate) static PAYJOIN_DIR: Lazy = Lazy::new(|| Url::parse("https://payjo.in").expect("Invalid URL")); +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct RecvSession { + pub enrolled: Enrolled, + pub expiry: Duration, +} + +impl RecvSession { + pub fn pubkey(&self) -> [u8; 33] { + self.enrolled.pubkey() + } +} +pub trait PayjoinStorage { + fn list_recv_sessions(&self) -> Result, MutinyError>; + fn store_new_recv_session(&self, session: Enrolled) -> Result; + fn delete_recv_session(&self, id: &[u8; 33]) -> Result<(), MutinyError>; +} + +const PAYJOIN_KEY_PREFIX: &str = "recvpj/"; + +fn get_payjoin_key(id: &[u8; 33]) -> String { + format!("{PAYJOIN_KEY_PREFIX}{}", id.as_hex()) +} + +impl PayjoinStorage for S { + fn list_recv_sessions(&self) -> Result, MutinyError> { + let map: HashMap = self.scan(PAYJOIN_KEY_PREFIX, None)?; + Ok(map.values().map(|v| v.to_owned()).collect()) + } + + fn store_new_recv_session(&self, enrolled: Enrolled) -> Result { + let in_24_hours = crate::utils::now() + Duration::from_secs(60 * 60 * 24); + let session = RecvSession { + enrolled, + expiry: in_24_hours, + }; + self.set_data(get_payjoin_key(&session.pubkey()), session.clone(), None) + .map(|_| session) + } + + fn delete_recv_session(&self, id: &[u8; 33]) -> Result<(), MutinyError> { + self.delete(&[get_payjoin_key(id)]) + } +} + pub async fn fetch_ohttp_keys(directory: Url) -> Result { let http_client = reqwest::Client::builder().build()?; @@ -36,6 +88,7 @@ pub enum Error { Txid(bitcoin::hashes::hex::Error), OhttpDecodeFailed, Shutdown, + SessionExpired, } impl std::error::Error for Error {} @@ -48,6 +101,7 @@ impl std::fmt::Display for Error { Error::Txid(e) => write!(f, "Payjoin txid error: {}", e), Error::OhttpDecodeFailed => write!(f, "Failed to decode ohttp keys"), Error::Shutdown => write!(f, "Payjoin stopped by application shutdown"), + Error::SessionExpired => write!(f, "Payjoin session expired. Create a new payment request and have the sender try again."), } } } From 78554893a5b9cb8a6efb31317bca1982f97f3a83 Mon Sep 17 00:00:00 2001 From: DanGould Date: Sat, 6 Jan 2024 14:05:47 -0500 Subject: [PATCH 4/6] Track pending payjoin transactions in wallet A receiver payjoin proposal PSBT are tracked as pending since it awaits a sender signature. This lets the pending TX display in the UI as an ActivityItem. --- mutiny-core/src/nodemanager.rs | 31 +++++++++++++++++++++++++++---- mutiny-core/src/onchain.rs | 27 +++++++++++++++++++-------- mutiny-core/src/payjoin.rs | 16 ++++++++++++++++ 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/mutiny-core/src/nodemanager.rs b/mutiny-core/src/nodemanager.rs index dd6fc4031..04eeab243 100644 --- a/mutiny-core/src/nodemanager.rs +++ b/mutiny-core/src/nodemanager.rs @@ -718,7 +718,6 @@ impl NodeManager { .map_err(|_| MutinyError::IncorrectNetwork)?; let address = uri.address.clone(); let original_psbt = self.wallet.create_signed_psbt(address, amount, fee_rate)?; - let fee_rate = if let Some(rate) = fee_rate { FeeRate::from_sat_per_vb(rate) } else { @@ -803,11 +802,18 @@ impl NodeManager { let http_client = reqwest::Client::builder() .build() .map_err(PayjoinError::Reqwest)?; - let proposal: payjoin::receive::v2::UncheckedProposal = - Self::poll_for_fallback_psbt(stop, storage, &http_client, &mut session).await?; + let proposal: payjoin::receive::v2::UncheckedProposal = Self::poll_for_fallback_psbt( + stop, + wallet.clone(), + storage.clone(), + &http_client, + &mut session, + ) + .await?; let original_tx = proposal.extract_tx_to_schedule_broadcast(); let mut payjoin_proposal = match wallet .process_payjoin_proposal(proposal) + .await .map_err(|e| PayjoinError::ReceiverStateMachine(e.to_string())) { Ok(p) => p, @@ -832,11 +838,23 @@ impl NodeManager { let _res = payjoin_proposal .deserialize_res(res.to_vec(), ohttp_ctx) .map_err(|e| PayjoinError::ReceiverStateMachine(e.to_string()))?; - Ok(payjoin_proposal.psbt().clone().extract_tx().txid()) + let payjoin_tx = payjoin_proposal.psbt().clone().extract_tx(); + let payjoin_txid = payjoin_tx.txid(); + wallet + .insert_tx( + payjoin_tx.clone(), + ConfirmationTime::unconfirmed(utils::now().as_secs()), + None, + ) + .await?; + session.payjoin_tx = Some(payjoin_tx); + storage.update_recv_session(session)?; + Ok(payjoin_txid) } async fn poll_for_fallback_psbt( stop: Arc, + wallet: Arc>, storage: Arc, client: &reqwest::Client, session: &mut crate::payjoin::RecvSession, @@ -847,6 +865,11 @@ impl NodeManager { } if session.expiry < utils::now() { + if let Some(payjoin_tx) = &session.payjoin_tx { + wallet + .cancel_tx(payjoin_tx) + .map_err(|_| crate::payjoin::Error::CancelPayjoinTx)?; + } let _ = storage.delete_recv_session(&session.enrolled.pubkey()); return Err(crate::payjoin::Error::SessionExpired); } diff --git a/mutiny-core/src/onchain.rs b/mutiny-core/src/onchain.rs index c34f3be44..62aac876a 100644 --- a/mutiny-core/src/onchain.rs +++ b/mutiny-core/src/onchain.rs @@ -355,6 +355,12 @@ impl OnChainWallet { Ok(()) } + pub(crate) fn cancel_tx(&self, tx: &Transaction) -> Result<(), MutinyError> { + let mut wallet = self.wallet.try_write()?; + wallet.cancel_tx(tx); + Ok(()) + } + fn is_mine(&self, script: &Script) -> Result { Ok(self.wallet.try_read()?.is_mine(script)) } @@ -363,7 +369,7 @@ impl OnChainWallet { Ok(self.wallet.try_read()?.list_unspent().collect()) } - pub fn process_payjoin_proposal( + pub async fn process_payjoin_proposal( &self, proposal: payjoin::receive::v2::UncheckedProposal, ) -> Result { @@ -407,21 +413,26 @@ impl OnChainWallet { let payjoin_proposal = provisional_payjoin.finalize_proposal( |psbt| { let mut psbt = psbt.clone(); - let wallet = self - .wallet - .try_read() - .map_err(|_| Error::Server(MutinyError::WalletSigningFailed.into()))?; - wallet + self.wallet + .try_write() + .map_err(|_| Error::Server(MutinyError::WalletSigningFailed.into()))? .sign(&mut psbt, SignOptions::default()) .map_err(|_| Error::Server(MutinyError::WalletSigningFailed.into()))?; Ok(psbt) }, Some(min_pj_fee_rate), )?; - let payjoin_proposal_psbt = payjoin_proposal.psbt(); + let payjoin_psbt_tx = payjoin_proposal.psbt().clone().extract_tx(); + self.insert_tx( + payjoin_psbt_tx, + ConfirmationTime::unconfirmed(crate::utils::now().as_secs()), + None, + ) + .await + .map_err(|_| Error::Server(MutinyError::WalletOperationFailed.into()))?; log::debug!( "Receiver's Payjoin proposal PSBT Rsponse: {:#?}", - payjoin_proposal_psbt + payjoin_proposal.psbt() ); Ok(payjoin_proposal) } diff --git a/mutiny-core/src/payjoin.rs b/mutiny-core/src/payjoin.rs index 94b89a5f8..d1f9909f6 100644 --- a/mutiny-core/src/payjoin.rs +++ b/mutiny-core/src/payjoin.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use crate::error::MutinyError; use crate::storage::MutinyStorage; +use bitcoin::Transaction; use core::time::Duration; use hex_conservative::DisplayHex; use once_cell::sync::Lazy; @@ -29,6 +30,7 @@ pub(crate) static PAYJOIN_DIR: Lazy = pub struct RecvSession { pub enrolled: Enrolled, pub expiry: Duration, + pub payjoin_tx: Option, } impl RecvSession { @@ -39,6 +41,7 @@ impl RecvSession { pub trait PayjoinStorage { fn list_recv_sessions(&self) -> Result, MutinyError>; fn store_new_recv_session(&self, session: Enrolled) -> Result; + fn update_recv_session(&self, session: RecvSession) -> Result<(), MutinyError>; fn delete_recv_session(&self, id: &[u8; 33]) -> Result<(), MutinyError>; } @@ -59,11 +62,16 @@ impl PayjoinStorage for S { let session = RecvSession { enrolled, expiry: in_24_hours, + payjoin_tx: None, }; self.set_data(get_payjoin_key(&session.pubkey()), session.clone(), None) .map(|_| session) } + fn update_recv_session(&self, session: RecvSession) -> Result<(), MutinyError> { + self.set_data(get_payjoin_key(&session.pubkey()), session, None) + } + fn delete_recv_session(&self, id: &[u8; 33]) -> Result<(), MutinyError> { self.delete(&[get_payjoin_key(id)]) } @@ -89,6 +97,10 @@ pub enum Error { OhttpDecodeFailed, Shutdown, SessionExpired, + BadDirectoryHost, + BadOhttpWsHost, + RequestFailed(String), + CancelPayjoinTx, } impl std::error::Error for Error {} @@ -102,6 +114,10 @@ impl std::fmt::Display for Error { Error::OhttpDecodeFailed => write!(f, "Failed to decode ohttp keys"), Error::Shutdown => write!(f, "Payjoin stopped by application shutdown"), Error::SessionExpired => write!(f, "Payjoin session expired. Create a new payment request and have the sender try again."), + Error::BadDirectoryHost => write!(f, "Bad directory host"), + Error::BadOhttpWsHost => write!(f, "Bad ohttp ws host"), + Error::RequestFailed(e) => write!(f, "Request failed: {}", e), + Error::CancelPayjoinTx => write!(f, "Failed to cancel payjoin tx in wallet"), } } } From 144c81e70eacb2ccc4efc13ebe8824f376332bc6 Mon Sep 17 00:00:00 2001 From: DanGould Date: Mon, 25 Mar 2024 16:15:52 -0400 Subject: [PATCH 5/6] WebSocket proxy OHTTP KeyConfig fetch Bootstrap Oblivious HTTP without revealing a client IP to the directory. --- Cargo.lock | 19 ++++++++ mutiny-core/Cargo.toml | 6 +++ mutiny-core/src/nodemanager.rs | 4 +- mutiny-core/src/payjoin.rs | 79 ++++++++++++++++++++++++++++++---- 4 files changed, 98 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9833e0313..a854f0d04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1636,6 +1636,17 @@ dependencies = [ "syn 2.0.55", ] +[[package]] +name = "futures-rustls" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d8a2499f0fecc0492eb3e47eab4e92da7875e1028ad2528f214ac3346ca04e" +dependencies = [ + "futures-io", + "rustls 0.22.3", + "rustls-pki-types", +] + [[package]] name = "futures-sink" version = "0.3.30" @@ -1754,6 +1765,7 @@ checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173" dependencies = [ "futures-channel", "futures-core", + "futures-io", "futures-sink", "gloo-utils", "http 0.2.12", @@ -2760,9 +2772,11 @@ dependencies = [ "fedimint-tbs", "fedimint-wallet-client", "futures", + "futures-rustls", "futures-util", "getrandom", "gloo-net 0.4.0", + "gloo-net 0.5.0", "gloo-timers 0.3.0", "hex-conservative", "itertools 0.11.0", @@ -2785,6 +2799,7 @@ dependencies = [ "payjoin", "pbkdf2 0.11.0", "reqwest", + "rustls-pki-types", "serde", "serde_json", "thiserror", @@ -2796,6 +2811,7 @@ dependencies = [ "wasm-bindgen-test", "web-sys", "web-time", + "webpki-roots 0.26.1", ] [[package]] @@ -3725,6 +3741,9 @@ name = "rustls-pki-types" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "868e20fada228fefaf6b652e00cc73623d54f8171e7352c18bb281571f2d92da" +dependencies = [ + "web-time", +] [[package]] name = "rustls-webpki" diff --git a/mutiny-core/Cargo.toml b/mutiny-core/Cargo.toml index e522e4754..2409d8118 100644 --- a/mutiny-core/Cargo.toml +++ b/mutiny-core/Cargo.toml @@ -45,7 +45,11 @@ aes = { version = "0.8" } jwt-compact = { version = "0.8.0-beta.1", features = ["es256k"] } argon2 = { version = "0.5.0", features = ["password-hash", "alloc"] } once_cell = "1.18.0" +gloo-net = { version = "0.5.0", features = ["io-util"] } payjoin = { version = "0.15.0", features = ["v2", "send", "receive", "base64"] } +futures-rustls = { version = "0.25.1" } +rustls-pki-types = { version = "1.4.0", features = ["web"] } +webpki-roots = "0.26.1" bincode = "1.3.3" hex-conservative = "0.1.1" async-lock = "3.2.0" @@ -85,6 +89,8 @@ wasm-bindgen-futures = { version = "0.4.38" } gloo-net = { version = "0.4.0" } web-time = "1.1" gloo-timers = { version = "0.3.0", features = ["futures"] } +web-sys = { version = "0.3.65", features = ["console"] } +js-sys = "0.3.65" getrandom = { version = "0.2", features = ["js"] } # add nip07 feature for wasm32 nostr = { version = "0.29.0", default-features = false, features = ["nip04", "nip05", "nip07", "nip47", "nip57"] } diff --git a/mutiny-core/src/nodemanager.rs b/mutiny-core/src/nodemanager.rs index 04eeab243..a2060f421 100644 --- a/mutiny-core/src/nodemanager.rs +++ b/mutiny-core/src/nodemanager.rs @@ -683,7 +683,9 @@ impl NodeManager { ) -> Result<(Enrolled, payjoin::OhttpKeys), PayjoinError> { use crate::payjoin::{fetch_ohttp_keys, random_ohttp_relay, PAYJOIN_DIR}; - let ohttp_keys = fetch_ohttp_keys(PAYJOIN_DIR.to_owned()).await?; + log_info!(self.logger, "Starting payjoin session"); + + let ohttp_keys = fetch_ohttp_keys().await?; let http_client = reqwest::Client::builder().build()?; let mut enroller = payjoin::receive::v2::Enroller::from_directory_config( diff --git a/mutiny-core/src/payjoin.rs b/mutiny-core/src/payjoin.rs index d1f9909f6..8caa7d218 100644 --- a/mutiny-core/src/payjoin.rs +++ b/mutiny-core/src/payjoin.rs @@ -1,9 +1,11 @@ use std::collections::HashMap; +use std::sync::Arc; use crate::error::MutinyError; use crate::storage::MutinyStorage; use bitcoin::Transaction; use core::time::Duration; +use gloo_net::websocket::futures::WebSocket; use hex_conservative::DisplayHex; use once_cell::sync::Lazy; use payjoin::receive::v2::Enrolled; @@ -77,16 +79,75 @@ impl PayjoinStorage for S { } } -pub async fn fetch_ohttp_keys(directory: Url) -> Result { - let http_client = reqwest::Client::builder().build()?; +pub async fn fetch_ohttp_keys() -> Result { + use futures_util::{AsyncReadExt, AsyncWriteExt}; - let ohttp_keys_res = http_client - .get(format!("{}/ohttp-keys", directory.as_ref())) - .send() - .await? - .bytes() - .await?; - OhttpKeys::decode(ohttp_keys_res.as_ref()).map_err(|_| Error::OhttpDecodeFailed) + let tls_connector = { + let root_store = futures_rustls::rustls::RootCertStore { + roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(), + }; + let config = futures_rustls::rustls::ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth(); + futures_rustls::TlsConnector::from(Arc::new(config)) + }; + let directory_host = PAYJOIN_DIR.host_str().ok_or(Error::BadDirectoryHost)?; + let domain = futures_rustls::rustls::pki_types::ServerName::try_from(directory_host) + .map_err(|_| Error::BadDirectoryHost)? + .to_owned(); + + let ws = WebSocket::open(&format!( + "wss://{}:443", + random_ohttp_relay() + .host_str() + .ok_or(Error::BadOhttpWsHost)? + )) + .map_err(|_| Error::BadOhttpWsHost)?; + + let mut tls_stream = tls_connector + .connect(domain, ws) + .await + .map_err(|e| Error::RequestFailed(e.to_string()))?; + let ohttp_keys_req = format!( + "GET /ohttp-keys HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n", + directory_host + ); + tls_stream + .write_all(ohttp_keys_req.as_bytes()) + .await + .map_err(|e| Error::RequestFailed(e.to_string()))?; + tls_stream + .flush() + .await + .map_err(|e| Error::RequestFailed(e.to_string()))?; + let mut response_bytes = Vec::new(); + tls_stream + .read_to_end(&mut response_bytes) + .await + .map_err(|e| Error::RequestFailed(e.to_string()))?; + let (_headers, res_body) = separate_headers_and_body(&response_bytes)?; + payjoin::OhttpKeys::decode(res_body).map_err(|_| Error::OhttpDecodeFailed) +} + +fn separate_headers_and_body(response_bytes: &[u8]) -> Result<(&[u8], &[u8]), Error> { + let separator = b"\r\n\r\n"; + + // Search for the separator + if let Some(position) = response_bytes + .windows(separator.len()) + .position(|window| window == separator) + { + // The body starts immediately after the separator + let body_start_index = position + separator.len(); + let headers = &response_bytes[..position]; + let body = &response_bytes[body_start_index..]; + + Ok((headers, body)) + } else { + Err(Error::RequestFailed( + "No header-body separator found in the response".to_string(), + )) + } } #[derive(Debug)] From 3780015bf625550820ad5c221465d1c522f81390 Mon Sep 17 00:00:00 2001 From: DanGould Date: Thu, 21 Dec 2023 17:32:07 -0500 Subject: [PATCH 6/6] Send v2 payjoin Persist sessions to poll and pending transactions to wallet as in receiving v2 payjoin. --- mutiny-core/src/nodemanager.rs | 182 ++++++++++++++++++++++++--------- mutiny-core/src/payjoin.rs | 74 ++++++++++++-- 2 files changed, 202 insertions(+), 54 deletions(-) diff --git a/mutiny-core/src/nodemanager.rs b/mutiny-core/src/nodemanager.rs index a2060f421..a90fc31fc 100644 --- a/mutiny-core/src/nodemanager.rs +++ b/mutiny-core/src/nodemanager.rs @@ -2,7 +2,7 @@ use crate::auth::MutinyAuthClient; use crate::labels::LabelStorage; use crate::ldkstorage::CHANNEL_CLOSURE_PREFIX; use crate::logging::LOGGING_KEY; -use crate::payjoin::{Error as PayjoinError, PayjoinStorage, RecvSession}; +use crate::payjoin::{random_ohttp_relay, Error as PayjoinError, PayjoinStorage, RecvSession}; use crate::utils::{sleep, spawn}; use crate::MutinyInvoice; use crate::MutinyWalletConfig; @@ -56,7 +56,6 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::cmp::max; -use std::io::Cursor; use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; #[cfg(not(target_arch = "wasm32"))] @@ -583,10 +582,14 @@ impl NodeManager { /// Starts a background task to poll payjoin sessions to attempt receiving. pub(crate) fn resume_payjoins(nm: Arc>) { - let all = nm.storage.list_recv_sessions().unwrap_or_default(); - for payjoin in all { + let receives = nm.storage.list_recv_sessions().unwrap_or_default(); + for payjoin in receives { nm.clone().spawn_payjoin_receiver(payjoin); } + let sends = nm.storage.list_send_sessions().unwrap_or_default(); + for payjoin in sends { + nm.clone().spawn_payjoin_sender(payjoin); + } } /// Creates a background process that will sync the wallet with the blockchain. @@ -681,7 +684,7 @@ impl NodeManager { pub async fn start_payjoin_session( &self, ) -> Result<(Enrolled, payjoin::OhttpKeys), PayjoinError> { - use crate::payjoin::{fetch_ohttp_keys, random_ohttp_relay, PAYJOIN_DIR}; + use crate::payjoin::{fetch_ohttp_keys, PAYJOIN_DIR}; log_info!(self.logger, "Starting payjoin session"); @@ -707,7 +710,7 @@ impl NodeManager { )) } - // Send v1 payjoin request + // Send v2 payjoin request pub async fn send_payjoin( &self, uri: Uri<'_, NetworkUnchecked>, @@ -720,6 +723,16 @@ impl NodeManager { .map_err(|_| MutinyError::IncorrectNetwork)?; let address = uri.address.clone(); let original_psbt = self.wallet.create_signed_psbt(address, amount, fee_rate)?; + // Track this transaction in the wallet so it shows as an ActivityItem in UI. + // We'll cancel it if and when this original_psbt fallback is replaced with a received payjoin. + self.wallet + .insert_tx( + original_psbt.clone().extract_tx(), + ConfirmationTime::unconfirmed(crate::utils::now().as_secs()), + None, + ) + .await?; + let fee_rate = if let Some(rate) = fee_rate { FeeRate::from_sat_per_vb(rate) } else { @@ -727,57 +740,134 @@ impl NodeManager { FeeRate::from_sat_per_kwu(sat_per_kwu as f32) }; let fee_rate = payjoin::bitcoin::FeeRate::from_sat_per_kwu(fee_rate.sat_per_kwu() as u64); - let original_psbt = payjoin::bitcoin::psbt::PartiallySignedTransaction::from_str( - &original_psbt.to_string(), - ) - .map_err(|_| MutinyError::WalletOperationFailed)?; log_debug!(self.logger, "Creating payjoin request"); - let (req, ctx) = - payjoin::send::RequestBuilder::from_psbt_and_uri(original_psbt.clone(), uri) - .unwrap() - .build_recommended(fee_rate) - .map_err(|_| MutinyError::PayjoinCreateRequest)? - .extract_v1()?; - - let client = Client::builder() - .build() - .map_err(|e| MutinyError::Other(e.into()))?; + let req_ctx = payjoin::send::RequestBuilder::from_psbt_and_uri(original_psbt.clone(), uri) + .map_err(|_| MutinyError::PayjoinCreateRequest)? + .build_recommended(fee_rate) + .map_err(|_| MutinyError::PayjoinConfigError)?; + let session = self.storage.store_new_send_session( + labels.clone(), + original_psbt.clone(), + req_ctx.clone(), + )?; + self.spawn_payjoin_sender(session); + Ok(original_psbt.extract_tx().txid()) + } - log_debug!(self.logger, "Sending payjoin request"); - let res = client - .post(req.url) - .body(req.body) - .header("Content-Type", "text/plain") - .send() + fn spawn_payjoin_sender(&self, session: crate::payjoin::SendSession) { + let wallet = self.wallet.clone(); + let logger = self.logger.clone(); + let stop = self.stop.clone(); + let storage = Arc::new(self.storage.clone()); + utils::spawn(async move { + let proposal_psbt = match Self::poll_payjoin_sender( + stop, + wallet.clone(), + storage.clone(), + session.clone(), + ) .await - .map_err(|_| MutinyError::PayjoinCreateRequest)? - .bytes() + { + Ok(psbt) => psbt, + Err(e) => { + // self.wallet cancel_tx + log_error!(logger, "Error polling payjoin sender: {e}"); + return; + } + }; + + let session_clone = session.clone(); + match Self::handle_proposal_psbt( + logger.clone(), + wallet, + session_clone.original_psbt, + proposal_psbt, + session_clone.labels, + ) .await - .map_err(|_| MutinyError::PayjoinCreateRequest)?; + { + // Ensure ResponseError is logged with debug formatting + Err(e) => log_error!(logger, "Error handling payjoin proposal: {:?}", e), + Ok(txid) => log_info!(logger, "Payjoin proposal handled: {}", txid), + } + let o_txid = session.clone().original_psbt.clone().extract_tx().txid(); + match storage.delete_send_session(session) { + Ok(_) => log_info!(logger, "Deleted payjoin send session: {}", o_txid), + Err(e) => log_error!(logger, "Error deleting payjoin send session: {e}"), + } + }); + } - let mut cursor = Cursor::new(res.to_vec()); + async fn poll_payjoin_sender( + stop: Arc, + wallet: Arc>, + storage: Arc, + mut session: crate::payjoin::SendSession, + ) -> Result { + let http = Client::builder() + .build() + .map_err(|_| MutinyError::Other(anyhow!("failed to build http client")))?; + loop { + if stop.load(Ordering::Relaxed) { + return Err(MutinyError::NotRunning); + } - log_debug!(self.logger, "Processing payjoin response"); - let proposal_psbt = ctx.process_response(&mut cursor).map_err(|e| { - // unrecognized error contents may only appear in debug logs and will not Display - log_debug!(self.logger, "Payjoin response error: {:?}", e); - e - })?; + if session.expiry < utils::now() { + wallet + .cancel_tx(&session.clone().original_psbt.extract_tx()) + .map_err(|_| crate::payjoin::Error::CancelPayjoinTx)?; + storage.delete_send_session(session)?; + return Err(MutinyError::Payjoin(crate::payjoin::Error::SessionExpired)); + } - // convert to pdk types - let original_psbt = PartiallySignedTransaction::from_str(&original_psbt.to_string()) - .map_err(|_| MutinyError::PayjoinConfigError)?; - let proposal_psbt = PartiallySignedTransaction::from_str(&proposal_psbt.to_string()) - .map_err(|_| MutinyError::PayjoinConfigError)?; + let (req, ctx) = session + .req_ctx + .extract_v2(random_ohttp_relay().to_owned()) + .map_err(|_| MutinyError::PayjoinConfigError)?; + // extract_v2 mutates the session, so we need to update it in storage to not reuse keys + storage.update_send_session(session.clone())?; + let response = http + .post(req.url) + .header("Content-Type", "message/ohttp-req") + .body(req.body) + .send() + .await + .map_err(|_| MutinyError::Other(anyhow!("failed to parse payjoin response")))?; + let mut reader = + std::io::Cursor::new(response.bytes().await.map_err(|_| { + MutinyError::Other(anyhow!("failed to parse payjoin response")) + })?); + + let psbt = ctx + .process_response(&mut reader) + .map_err(MutinyError::PayjoinResponse)?; + if let Some(psbt) = psbt { + let psbt = bitcoin::psbt::Psbt::from_str(&psbt.to_string()) + .map_err(|_| MutinyError::Other(anyhow!("psbt conversion failed")))?; + return Ok(psbt); + } else { + log::info!("No response yet for POST payjoin request, retrying some seconds"); + std::thread::sleep(std::time::Duration::from_secs(5)); + } + } + } - log_debug!(self.logger, "Sending payjoin.."); - let tx = self - .wallet + async fn handle_proposal_psbt( + logger: Arc, + wallet: Arc>, + original_psbt: PartiallySignedTransaction, + proposal_psbt: PartiallySignedTransaction, + labels: Vec, + ) -> Result { + log_debug!(logger, "Sending payjoin.."); + let original_tx = original_psbt.clone().extract_tx(); + let tx = wallet .send_payjoin(original_psbt, proposal_psbt, labels) .await?; let txid = tx.txid(); - self.broadcast_transaction(tx).await?; - log_debug!(self.logger, "Payjoin broadcast! TXID: {txid}"); + wallet.broadcast_transaction(tx).await?; + wallet.cancel_tx(&original_tx)?; + log_info!(logger, "Payjoin broadcast! TXID: {txid}"); Ok(txid) } diff --git a/mutiny-core/src/payjoin.rs b/mutiny-core/src/payjoin.rs index 8caa7d218..0842ad036 100644 --- a/mutiny-core/src/payjoin.rs +++ b/mutiny-core/src/payjoin.rs @@ -3,13 +3,14 @@ use std::sync::Arc; use crate::error::MutinyError; use crate::storage::MutinyStorage; -use bitcoin::Transaction; +use bitcoin::{psbt::Psbt, Transaction, Txid}; use core::time::Duration; use gloo_net::websocket::futures::WebSocket; use hex_conservative::DisplayHex; use once_cell::sync::Lazy; use payjoin::receive::v2::Enrolled; use payjoin::OhttpKeys; +use pj::send::RequestContext; use serde::{Deserialize, Serialize}; use url::Url; @@ -40,22 +41,46 @@ impl RecvSession { self.enrolled.pubkey() } } + +#[derive(Clone, PartialEq, Serialize, Deserialize)] +pub struct SendSession { + pub original_psbt: Psbt, + pub req_ctx: RequestContext, + pub labels: Vec, + pub expiry: Duration, +} + pub trait PayjoinStorage { fn list_recv_sessions(&self) -> Result, MutinyError>; fn store_new_recv_session(&self, session: Enrolled) -> Result; fn update_recv_session(&self, session: RecvSession) -> Result<(), MutinyError>; fn delete_recv_session(&self, id: &[u8; 33]) -> Result<(), MutinyError>; + + fn list_send_sessions(&self) -> Result, MutinyError>; + fn store_new_send_session( + &self, + labels: Vec, + original_psbt: Psbt, + req_ctx: RequestContext, + ) -> Result; + fn update_send_session(&self, session: SendSession) -> Result<(), MutinyError>; + fn delete_send_session(&self, session: SendSession) -> Result<(), MutinyError>; } -const PAYJOIN_KEY_PREFIX: &str = "recvpj/"; +const RECV_PAYJOIN_KEY_PREFIX: &str = "recvpj/"; +const SEND_PAYJOIN_KEY_PREFIX: &str = "sendpj/"; -fn get_payjoin_key(id: &[u8; 33]) -> String { - format!("{PAYJOIN_KEY_PREFIX}{}", id.as_hex()) +fn get_recv_key(id: &[u8; 33]) -> String { + format!("{RECV_PAYJOIN_KEY_PREFIX}{}", id.as_hex()) +} + +fn get_send_key(original_txid: Txid) -> String { + format!("{RECV_PAYJOIN_KEY_PREFIX}{}", original_txid) } impl PayjoinStorage for S { fn list_recv_sessions(&self) -> Result, MutinyError> { - let map: HashMap = self.scan(PAYJOIN_KEY_PREFIX, None)?; + let map: HashMap = self.scan(RECV_PAYJOIN_KEY_PREFIX, None)?; Ok(map.values().map(|v| v.to_owned()).collect()) } @@ -66,16 +91,49 @@ impl PayjoinStorage for S { expiry: in_24_hours, payjoin_tx: None, }; - self.set_data(get_payjoin_key(&session.pubkey()), session.clone(), None) + self.set_data(get_recv_key(&session.pubkey()), session.clone(), None) .map(|_| session) } fn update_recv_session(&self, session: RecvSession) -> Result<(), MutinyError> { - self.set_data(get_payjoin_key(&session.pubkey()), session, None) + self.set_data(get_recv_key(&session.pubkey()), session, None) } fn delete_recv_session(&self, id: &[u8; 33]) -> Result<(), MutinyError> { - self.delete(&[get_payjoin_key(id)]) + self.delete(&[get_recv_key(id)]) + } + + fn store_new_send_session( + &self, + labels: Vec, + original_psbt: Psbt, + req_ctx: RequestContext, + ) -> Result { + let in_24_hours = crate::utils::now() + Duration::from_secs(60 * 60 * 24); + let o_txid = original_psbt.clone().extract_tx().txid(); + let session = SendSession { + labels, + original_psbt, + expiry: in_24_hours, + req_ctx, + }; + self.set_data(o_txid.to_string(), session.clone(), None) + .map(|_| session) + } + + fn list_send_sessions(&self) -> Result, MutinyError> { + let map: HashMap = self.scan(SEND_PAYJOIN_KEY_PREFIX, None)?; + Ok(map.values().map(|v| v.to_owned()).collect()) + } + + fn update_send_session(&self, session: SendSession) -> Result<(), MutinyError> { + let o_txid = session.clone().original_psbt.extract_tx().txid(); + self.set_data(get_send_key(o_txid), session, None) + } + + fn delete_send_session(&self, session: SendSession) -> Result<(), MutinyError> { + let o_txid = session.original_psbt.extract_tx().txid(); + self.delete(&[get_send_key(o_txid)]) } }