diff --git a/CHANGELOG.md b/CHANGELOG.md index 8745f522..6561f042 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,4 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 failing on non-english language systems preventing users from starting the swap-cli and asb. +### Security + +- Fixed an issue where Alice would not verify if Bob's Bitcoin lock transaction is semantically correct, i.e. pays the agreed upon amount to an output owned by both of them. + Fixing this required a **breaking change** on the network layer and hence old versions are not compatible with this version. + [Unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/v0.3...HEAD diff --git a/Cargo.lock b/Cargo.lock index f4bbf4df..3ae505f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,6 +221,16 @@ dependencies = [ "keccak-hash 0.1.2", ] +[[package]] +name = "base64" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" +dependencies = [ + "byteorder", + "safemem", +] + [[package]] name = "base64" version = "0.10.1" @@ -251,9 +261,9 @@ dependencies = [ "async-trait", "bdk-macros", "bitcoin", - "electrum-client", + "electrum-client 0.7.0", "js-sys", - "log", + "log 0.4.14", "miniscript", "rand 0.7.3", "serde", @@ -273,6 +283,22 @@ dependencies = [ "syn", ] +[[package]] +name = "bdk-testutils" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d9382c8dfda457f2be9b700ffd580f12babec5d34ee39343768f65724ddd64" +dependencies = [ + "bitcoin", + "bitcoincore-rpc", + "electrum-client 0.6.0", + "log 0.4.14", + "miniscript", + "serde", + "serde_json", + "serial_test", +] + [[package]] name = "bech32" version = "0.7.3" @@ -326,7 +352,7 @@ dependencies = [ "thiserror", "tokio", "tracing", - "url", + "url 2.2.1", ] [[package]] @@ -338,6 +364,19 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoincore-rpc" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d708433972bf78bd5f909d1d288f9ac1cceeab1460edb954e962f83e1f440a3" +dependencies = [ + "bitcoincore-rpc-json", + "jsonrpc", + "log 0.4.14", + "serde", + "serde_json", +] + [[package]] name = "bitcoincore-rpc-json" version = "0.13.0" @@ -886,6 +925,22 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "electrum-client" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21453800c95bb1aaa57490458c42d60c6277cb8a3e386030ec2381d5c2d4fa77" +dependencies = [ + "bitcoin", + "log 0.4.14", + "rustls 0.16.0", + "serde", + "serde_json", + "socks", + "webpki", + "webpki-roots 0.19.0", +] + [[package]] name = "electrum-client" version = "0.7.0" @@ -893,7 +948,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cab4d90cc575a7daab4cfed9e315912a88071bc47462e6be57516a2f01ccc89" dependencies = [ "bitcoin", - "log", + "log 0.4.14", "rustls 0.16.0", "serde", "serde_json", @@ -1050,7 +1105,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" dependencies = [ "matches", - "percent-encoding", + "percent-encoding 2.1.0", ] [[package]] @@ -1194,7 +1249,7 @@ checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "serde", "typenum", - "version_check", + "version_check 0.9.3", ] [[package]] @@ -1395,6 +1450,25 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" +[[package]] +name = "hyper" +version = "0.10.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" +dependencies = [ + "base64 0.9.3", + "httparse", + "language-tags", + "log 0.3.9", + "mime 0.2.6", + "num_cpus", + "time 0.1.43", + "traitobject", + "typeable", + "unicase", + "url 1.7.2", +] + [[package]] name = "hyper" version = "0.14.5" @@ -1426,14 +1500,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" dependencies = [ "futures-util", - "hyper", - "log", + "hyper 0.14.4", + "log 0.4.14", "rustls 0.19.0", "tokio", "tokio-rustls", "webpki", ] +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.2.2" @@ -1545,6 +1630,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonrpc" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436f3455a8a4e9c7b14de9f1206198ee5d0bdc2db1b560339d2141093d7dd389" +dependencies = [ + "hyper 0.10.16", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "jsonrpc_client" version = "0.5.1" @@ -1556,7 +1653,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "url", + "url 2.2.1", ] [[package]] @@ -1589,6 +1686,12 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" + [[package]] name = "lazy_static" version = "1.4.0" @@ -1634,7 +1737,7 @@ dependencies = [ "libp2p-tcp", "libp2p-yamux", "parity-multiaddr", - "parking_lot", + "parking_lot 0.11.1", "pin-project 1.0.5", "smallvec", "wasm-timer", @@ -1646,7 +1749,7 @@ version = "0.1.0" source = "git+https://github.com/comit-network/rust-libp2p-async-await#7a9006ceddd132ef5d40a597936cf15381a5cfe1" dependencies = [ "libp2p", - "log", + "log 0.4.14", ] [[package]] @@ -1664,11 +1767,11 @@ dependencies = [ "futures-timer", "lazy_static", "libsecp256k1", - "log", + "log 0.4.14", "multihash", "multistream-select", "parity-multiaddr", - "parking_lot", + "parking_lot 0.11.1", "pin-project 1.0.5", "prost", "prost-build", @@ -1691,7 +1794,7 @@ checksum = "9712eb3e9f7dcc77cc5ca7d943b6a85ce4b1faaf91a67e003442412a26d6d6f8" dependencies = [ "futures", "libp2p-core", - "log", + "log 0.4.14", "smallvec", "trust-dns-resolver", ] @@ -1706,9 +1809,9 @@ dependencies = [ "bytes", "futures", "libp2p-core", - "log", + "log 0.4.14", "nohash-hasher", - "parking_lot", + "parking_lot 0.11.1", "rand 0.7.3", "smallvec", "unsigned-varint 0.7.0", @@ -1725,7 +1828,7 @@ dependencies = [ "futures", "lazy_static", "libp2p-core", - "log", + "log 0.4.14", "prost", "prost-build", "rand 0.7.3", @@ -1747,7 +1850,7 @@ dependencies = [ "futures", "libp2p-core", "libp2p-swarm", - "log", + "log 0.4.14", "lru", "minicbor", "rand 0.7.3", @@ -1765,7 +1868,7 @@ dependencies = [ "either", "futures", "libp2p-core", - "log", + "log 0.4.14", "rand 0.7.3", "smallvec", "void", @@ -1794,7 +1897,7 @@ dependencies = [ "ipnet", "libc", "libp2p-core", - "log", + "log 0.4.14", "socket2 0.4.0", "tokio", ] @@ -1807,7 +1910,7 @@ checksum = "96d6144cc94143fb0a8dd1e7c2fbcc32a2808168bcd1d69920635424d5993b7b" dependencies = [ "futures", "libp2p-core", - "parking_lot", + "parking_lot 0.11.1", "thiserror", "yamux", ] @@ -1834,6 +1937,15 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +dependencies = [ + "scopeguard", +] + [[package]] name = "lock_api" version = "0.4.2" @@ -1843,6 +1955,15 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.14", +] + [[package]] name = "log" version = "0.4.14" @@ -1906,6 +2027,15 @@ dependencies = [ "autocfg 1.0.1", ] +[[package]] +name = "mime" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" +dependencies = [ + "log 0.3.9", +] + [[package]] name = "mime" version = "0.3.16" @@ -1958,7 +2088,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2182a122f3b7f3f5329cb1972cee089ba2459a0a80a56935e6e674f096f8d839" dependencies = [ "libc", - "log", + "log 0.4.14", "miow", "ntapi", "winapi 0.3.9", @@ -2059,7 +2189,7 @@ checksum = "7d91ec0a2440aaff5f78ec35631a7027d50386c6163aa975f7caa0d5da4b6ff8" dependencies = [ "bytes", "futures", - "log", + "log 0.4.14", "pin-project 1.0.5", "smallvec", "unsigned-varint 0.7.0", @@ -2079,7 +2209,7 @@ checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" dependencies = [ "lexical-core", "memchr", - "version_check", + "version_check 0.9.3", ] [[package]] @@ -2208,11 +2338,11 @@ dependencies = [ "byteorder", "data-encoding", "multihash", - "percent-encoding", + "percent-encoding 2.1.0", "serde", "static_assertions 1.1.0", "unsigned-varint 0.7.0", - "url", + "url 2.2.1", ] [[package]] @@ -2227,6 +2357,16 @@ dependencies = [ "serde", ] +[[package]] +name = "parking_lot" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +dependencies = [ + "lock_api 0.3.4", + "parking_lot_core 0.7.2", +] + [[package]] name = "parking_lot" version = "0.11.1" @@ -2234,8 +2374,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" dependencies = [ "instant", - "lock_api", - "parking_lot_core", + "lock_api 0.4.2", + "parking_lot_core 0.8.3", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi", + "libc", + "redox_syscall 0.1.57", + "smallvec", + "winapi 0.3.9", ] [[package]] @@ -2263,6 +2417,12 @@ dependencies = [ "regex", ] +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -2414,7 +2574,7 @@ dependencies = [ "proc-macro2", "quote", "syn", - "version_check", + "version_check 0.9.3", ] [[package]] @@ -2425,7 +2585,7 @@ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", - "version_check", + "version_check 0.9.3", ] [[package]] @@ -2468,7 +2628,7 @@ dependencies = [ "bytes", "heck", "itertools", - "log", + "log 0.4.14", "multimap", "petgraph", "prost", @@ -2826,14 +2986,14 @@ dependencies = [ "futures-util", "http", "http-body", - "hyper", + "hyper 0.14.4", "hyper-rustls", "ipnet", "js-sys", "lazy_static", - "log", - "mime", - "percent-encoding", + "log 0.4.14", + "mime 0.3.16", + "percent-encoding 2.1.0", "pin-project-lite", "rustls 0.19.0", "serde", @@ -2841,7 +3001,7 @@ dependencies = [ "serde_urlencoded", "tokio", "tokio-rustls", - "url", + "url 2.2.1", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -2925,7 +3085,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b25a18b1bf7387f0145e7f8324e700805aade3842dd3db2e74e4cdeb4677c09e" dependencies = [ "base64 0.10.1", - "log", + "log 0.4.14", "ring", "sct", "webpki", @@ -2938,7 +3098,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b" dependencies = [ "base64 0.13.0", - "log", + "log 0.4.14", "ring", "sct", "webpki", @@ -2961,6 +3121,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + [[package]] name = "scopeguard" version = "1.1.0" @@ -3097,6 +3263,28 @@ dependencies = [ "serde", ] +[[package]] +name = "serial_test" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef5f7c7434b2f2c598adc6f9494648a1e41274a75c0ba4056f680ae0c117fd6" +dependencies = [ + "lazy_static", + "parking_lot 0.10.2", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08338d8024b227c62bd68a12c7c9883f5c66780abaef15c550dc56f46ee6515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha-1" version = "0.9.4" @@ -3196,8 +3384,8 @@ dependencies = [ "fs2", "fxhash", "libc", - "log", - "parking_lot", + "log 0.4.14", + "parking_lot 0.11.1", ] [[package]] @@ -3278,7 +3466,7 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2beb4d1860a61f571530b3f855a1b538d0200f7871c63331ecd6f17b1f014f8" dependencies = [ - "version_check", + "version_check 0.9.3", ] [[package]] @@ -3426,6 +3614,7 @@ dependencies = [ "backoff", "base64 0.13.0", "bdk", + "bdk-testutils", "big-bytes", "bitcoin", "bitcoin-harness", @@ -3437,7 +3626,7 @@ dependencies = [ "ecdsa_fun", "futures", "get-port", - "hyper", + "hyper 0.14.4", "libp2p", "libp2p-async-await", "miniscript", @@ -3472,7 +3661,7 @@ dependencies = [ "tracing", "tracing-futures", "tracing-subscriber", - "url", + "url 2.2.1", "uuid", "void", "zip", @@ -3545,7 +3734,7 @@ dependencies = [ "derivative", "hex 0.4.3", "hmac 0.8.1", - "log", + "log 0.4.14", "rand 0.7.3", "serde", "serde_json", @@ -3560,7 +3749,7 @@ checksum = "d5e3ed6e3598dbf32cba8cb356b881c085e0adea57597f387723430dd94b4084" dependencies = [ "hex 0.4.3", "hmac 0.10.1", - "log", + "log 0.4.14", "rand 0.8.3", "serde", "serde_json", @@ -3626,7 +3815,7 @@ dependencies = [ "standback", "stdweb", "time-macros", - "version_check", + "version_check 0.9.3", "winapi 0.3.9", ] @@ -3690,7 +3879,7 @@ dependencies = [ "mio", "num_cpus", "once_cell", - "parking_lot", + "parking_lot 0.11.1", "pin-project-lite", "signal-hook-registry", "tokio-macros", @@ -3751,7 +3940,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e96bb520beab540ab664bd5a9cfeaa1fcd846fa68c830b42e2c8963071251d2" dependencies = [ "futures-util", - "log", + "log 0.4.14", "pin-project 1.0.5", "rustls 0.19.0", "tokio", @@ -3770,7 +3959,7 @@ dependencies = [ "bytes", "futures-core", "futures-sink", - "log", + "log 0.4.14", "pin-project-lite", "tokio", ] @@ -3841,7 +4030,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" dependencies = [ "lazy_static", - "log", + "log 0.4.14", "tracing-core", ] @@ -3863,6 +4052,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "traitobject" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" + [[package]] name = "trust-dns-proto" version = "0.20.1" @@ -3876,16 +4071,16 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna", + "idna 0.2.2", "ipnet", "lazy_static", - "log", + "log 0.4.14", "rand 0.8.3", "smallvec", "thiserror", "tinyvec", "tokio", - "url", + "url 2.2.1", ] [[package]] @@ -3898,9 +4093,9 @@ dependencies = [ "futures-util", "ipconfig", "lazy_static", - "log", + "log 0.4.14", "lru-cache", - "parking_lot", + "parking_lot 0.11.1", "resolv-conf", "smallvec", "thiserror", @@ -3926,17 +4121,23 @@ dependencies = [ "http", "httparse", "input_buffer", - "log", + "log 0.4.14", "rand 0.8.3", "rustls 0.19.0", "sha-1", "thiserror", - "url", + "url 2.2.1", "utf-8", "webpki", "webpki-roots 0.21.0", ] +[[package]] +name = "typeable" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" + [[package]] name = "typenum" version = "1.13.0" @@ -3967,6 +4168,15 @@ dependencies = [ "static_assertions 1.1.0", ] +[[package]] +name = "unicase" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" +dependencies = [ + "version_check 0.1.5", +] + [[package]] name = "unicode-bidi" version = "0.3.4" @@ -4037,6 +4247,17 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +dependencies = [ + "idna 0.1.5", + "matches", + "percent-encoding 1.0.1", +] + [[package]] name = "url" version = "2.2.1" @@ -4044,9 +4265,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" dependencies = [ "form_urlencoded", - "idna", + "idna 0.2.2", "matches", - "percent-encoding", + "percent-encoding 2.1.0", "serde", ] @@ -4072,6 +4293,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + [[package]] name = "version_check" version = "0.9.3" @@ -4090,7 +4317,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log", + "log 0.4.14", "try-lock", ] @@ -4126,7 +4353,7 @@ checksum = "5b7d8b6942b8bb3a9b0e73fc79b98095a27de6fa247615e59d096754a3bc2aa8" dependencies = [ "bumpalo", "lazy_static", - "log", + "log 0.4.14", "proc-macro2", "quote", "syn", @@ -4182,7 +4409,7 @@ checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" dependencies = [ "futures", "js-sys", - "parking_lot", + "parking_lot 0.11.1", "pin-utils", "wasm-bindgen", "wasm-bindgen-futures", @@ -4332,9 +4559,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cc7bd8c983209ed5d527f44b01c41b7dc146fd960c61cf9e1d25399841dc271" dependencies = [ "futures", - "log", + "log 0.4.14", "nohash-hasher", - "parking_lot", + "parking_lot 0.11.1", "rand 0.7.3", "static_assertions 1.1.0", ] diff --git a/swap/Cargo.toml b/swap/Cargo.toml index b66a4f3e..1ddaad4f 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -64,6 +64,7 @@ tokio-tar = { path = "../tokio-tar" } zip = "0.5" [dev-dependencies] +bdk-testutils = { version = "0.3" } bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs" } get-port = "3" hyper = "0.14" diff --git a/swap/src/bitcoin.rs b/swap/src/bitcoin.rs index 7577633d..84434ffe 100644 --- a/swap/src/bitcoin.rs +++ b/swap/src/bitcoin.rs @@ -14,6 +14,7 @@ pub use crate::bitcoin::redeem::TxRedeem; pub use crate::bitcoin::refund::TxRefund; pub use crate::bitcoin::timelocks::{BlockHeight, ExpiredTimelocks}; pub use ::bitcoin::util::amount::Amount; +pub use ::bitcoin::util::psbt::PartiallySignedTransaction; pub use ::bitcoin::{Address, Network, Transaction, Txid}; pub use ecdsa_fun::adaptor::EncryptedSignature; pub use ecdsa_fun::fun::Scalar; @@ -105,6 +106,13 @@ impl SecretKey { #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)] pub struct PublicKey(Point); +impl PublicKey { + #[cfg(test)] + pub fn random() -> Self { + Self(Point::random(&mut rand::thread_rng())) + } +} + impl From for Point { fn from(from: PublicKey) -> Self { from.0 diff --git a/swap/src/bitcoin/lock.rs b/swap/src/bitcoin/lock.rs index d2d5e7ca..63f2fb53 100644 --- a/swap/src/bitcoin/lock.rs +++ b/swap/src/bitcoin/lock.rs @@ -4,7 +4,8 @@ use crate::bitcoin::{ }; use ::bitcoin::util::psbt::PartiallySignedTransaction; use ::bitcoin::{OutPoint, TxIn, TxOut, Txid}; -use anyhow::Result; +use anyhow::{bail, Result}; +use bdk::database::BatchDatabase; use bitcoin::Script; use ecdsa_fun::fun::Point; use miniscript::{Descriptor, DescriptorTrait}; @@ -18,10 +19,18 @@ pub struct TxLock { } impl TxLock { - pub async fn new(wallet: &Wallet, amount: Amount, A: PublicKey, B: PublicKey) -> Result { + pub async fn new( + wallet: &Wallet, + amount: Amount, + A: PublicKey, + B: PublicKey, + ) -> Result + where + D: BatchDatabase, + { let lock_output_descriptor = build_shared_output_descriptor(A.0, B.0); let address = lock_output_descriptor - .address(wallet.get_network().await) + .address(wallet.get_network()) .expect("can derive address from descriptor"); let psbt = wallet.send_to_address(address, amount).await?; @@ -32,6 +41,56 @@ impl TxLock { }) } + /// Creates an instance of `TxLock` from a PSBT, the public keys of the + /// parties and the specified amount. + /// + /// This function validates that the given PSBT does indeed pay that + /// specified amount to a shared output. + pub fn from_psbt( + psbt: PartiallySignedTransaction, + A: PublicKey, + B: PublicKey, + btc: Amount, + ) -> Result { + let shared_output_candidate = match psbt.global.unsigned_tx.output.as_slice() { + [shared_output_candidate, _] if shared_output_candidate.value == btc.as_sat() => { + shared_output_candidate + } + [_, shared_output_candidate] if shared_output_candidate.value == btc.as_sat() => { + shared_output_candidate + } + // A single output is possible if Bob funds without any change necessary + [shared_output_candidate] if shared_output_candidate.value == btc.as_sat() => { + shared_output_candidate + } + [_, _] => { + bail!("Neither of the two provided outputs pays the right amount!"); + } + [_] => { + bail!("The provided output does not pay the right amount!"); + } + other => { + let num_outputs = other.len(); + bail!( + "PSBT has {} outputs, expected one or two. Something is fishy!", + num_outputs + ); + } + }; + + let descriptor = build_shared_output_descriptor(A.0, B.0); + let legit_shared_output_script = descriptor.script_pubkey(); + + if shared_output_candidate.script_pubkey != legit_shared_output_script { + bail!("Output script is not a shared output") + } + + Ok(TxLock { + inner: psbt, + output_descriptor: descriptor, + }) + } + pub fn lock_amount(&self) -> Amount { Amount::from_sat(self.inner.clone().extract_tx().output[self.lock_output_vout()].value) } @@ -116,3 +175,84 @@ impl Watchable for TxLock { self.output_descriptor.script_pubkey() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn given_bob_sends_good_psbt_when_reconstructing_then_succeeeds() { + let (A, B) = alice_and_bob(); + let wallet = Wallet::new_funded(50000); + let agreed_amount = Amount::from_sat(10000); + + let psbt = bob_make_psbt(A, B, &wallet, agreed_amount).await; + let result = TxLock::from_psbt(psbt, A, B, agreed_amount); + + result.expect("PSBT to be valid"); + } + + #[tokio::test] + async fn bob_can_fund_without_a_change_output() { + let (A, B) = alice_and_bob(); + let fees = 610; + let agreed_amount = Amount::from_sat(10000); + let wallet = Wallet::new_funded(agreed_amount.as_sat() + fees); + + let psbt = bob_make_psbt(A, B, &wallet, agreed_amount).await; + assert_eq!( + psbt.global.unsigned_tx.output.len(), + 1, + "psbt should only have a single output" + ); + let result = TxLock::from_psbt(psbt, A, B, agreed_amount); + + result.expect("PSBT to be valid"); + } + + #[tokio::test] + async fn given_bob_is_sending_less_than_agreed_when_reconstructing_txlock_then_fails() { + let (A, B) = alice_and_bob(); + let wallet = Wallet::new_funded(50000); + let agreed_amount = Amount::from_sat(10000); + + let bad_amount = Amount::from_sat(5000); + let psbt = bob_make_psbt(A, B, &wallet, bad_amount).await; + let result = TxLock::from_psbt(psbt, A, B, agreed_amount); + + result.expect_err("PSBT to be invalid"); + } + + #[tokio::test] + async fn given_bob_is_sending_to_a_bad_output_reconstructing_txlock_then_fails() { + let (A, B) = alice_and_bob(); + let wallet = Wallet::new_funded(50000); + let agreed_amount = Amount::from_sat(10000); + + let E = eve(); + let psbt = bob_make_psbt(E, B, &wallet, agreed_amount).await; + let result = TxLock::from_psbt(psbt, A, B, agreed_amount); + + result.expect_err("PSBT to be invalid"); + } + + /// Helper function that represents Bob's action of constructing the PSBT. + /// + /// Extracting this allows us to keep the tests concise. + async fn bob_make_psbt( + A: PublicKey, + B: PublicKey, + wallet: &Wallet<(), bdk::database::MemoryDatabase, ()>, + amount: Amount, + ) -> PartiallySignedTransaction { + TxLock::new(&wallet, amount, A, B).await.unwrap().into() + } + + fn alice_and_bob() -> (PublicKey, PublicKey) { + (PublicKey::random(), PublicKey::random()) + } + + fn eve() -> PublicKey { + PublicKey::random() + } +} diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index e4ba57d4..c87c0f3e 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -5,11 +5,12 @@ use ::bitcoin::util::psbt::PartiallySignedTransaction; use ::bitcoin::Txid; use anyhow::{bail, Context, Result}; use bdk::blockchain::{noop_progress, Blockchain, ElectrumBlockchain}; +use bdk::database::BatchDatabase; use bdk::descriptor::Segwitv0; use bdk::electrum_client::{ElectrumApi, GetHistoryRes}; use bdk::keys::DerivableKey; use bdk::{FeeRate, KeychainKind}; -use bitcoin::Script; +use bitcoin::{Network, Script}; use reqwest::Url; use std::collections::{BTreeMap, HashMap}; use std::convert::TryFrom; @@ -21,10 +22,11 @@ use tokio::sync::{watch, Mutex}; const SLED_TREE_NAME: &str = "default_tree"; -pub struct Wallet { - client: Arc>, - wallet: Arc>>, +pub struct Wallet { + client: Arc>, + wallet: Arc>>, finality_confirmations: u32, + network: Network, } impl Wallet { @@ -39,7 +41,7 @@ impl Wallet { let db = bdk::sled::open(wallet_dir)?.open_tree(SLED_TREE_NAME)?; - let bdk_wallet = bdk::Wallet::new( + let wallet = bdk::Wallet::new( bdk::template::BIP84(key.clone(), KeychainKind::External), Some(bdk::template::BIP84(key, KeychainKind::Internal)), env_config.bitcoin_network, @@ -50,108 +52,19 @@ impl Wallet { let electrum = bdk::electrum_client::Client::new(electrum_rpc_url.as_str()) .context("Failed to initialize Electrum RPC client")?; + let network = wallet.network(); + Ok(Self { - wallet: Arc::new(Mutex::new(bdk_wallet)), client: Arc::new(Mutex::new(Client::new( electrum, env_config.bitcoin_sync_interval(), )?)), + wallet: Arc::new(Mutex::new(wallet)), finality_confirmations: env_config.bitcoin_finality_confirmations, + network, }) } - pub async fn balance(&self) -> Result { - let balance = self - .wallet - .lock() - .await - .get_balance() - .context("Failed to calculate Bitcoin balance")?; - - Ok(Amount::from_sat(balance)) - } - - pub async fn new_address(&self) -> Result
{ - let address = self - .wallet - .lock() - .await - .get_new_address() - .context("Failed to get new Bitcoin address")?; - - Ok(address) - } - - pub async fn get_tx(&self, txid: Txid) -> Result> { - let tx = self.wallet.lock().await.client().get_tx(&txid)?; - - Ok(tx) - } - - pub async fn transaction_fee(&self, txid: Txid) -> Result { - let fees = self - .wallet - .lock() - .await - .list_transactions(true)? - .iter() - .find(|tx| tx.txid == txid) - .context("Could not find tx in bdk wallet when trying to determine fees")? - .fees; - - Ok(Amount::from_sat(fees)) - } - - pub async fn sync(&self) -> Result<()> { - self.wallet - .lock() - .await - .sync(noop_progress(), None) - .context("Failed to sync balance of Bitcoin wallet")?; - - Ok(()) - } - - pub async fn send_to_address( - &self, - address: Address, - amount: Amount, - ) -> Result { - let wallet = self.wallet.lock().await; - - let mut tx_builder = wallet.build_tx(); - tx_builder.add_recipient(address.script_pubkey(), amount.as_sat()); - tx_builder.fee_rate(self.select_feerate()); - let (psbt, _details) = tx_builder.finish()?; - - Ok(psbt) - } - - /// Calculates the maximum "giveable" amount of this wallet. - /// - /// We define this as the maximum amount we can pay to a single output, - /// already accounting for the fees we need to spend to get the - /// transaction confirmed. - pub async fn max_giveable(&self, locking_script_size: usize) -> Result { - let wallet = self.wallet.lock().await; - - let mut tx_builder = wallet.build_tx(); - - let dummy_script = Script::from(vec![0u8; locking_script_size]); - tx_builder.set_single_recipient(dummy_script); - tx_builder.drain_wallet(); - tx_builder.fee_rate(self.select_feerate()); - let (_, details) = tx_builder.finish().context("Failed to build transaction")?; - - let max_giveable = details.sent - details.fees; - - Ok(Amount::from_sat(max_giveable)) - } - - pub async fn get_network(&self) -> bitcoin::Network { - self.wallet.lock().await.network() - } - /// Broadcast the given transaction to the network and emit a log statement /// if done so successfully. /// @@ -260,13 +173,6 @@ impl Wallet { sub } - - /// Selects an appropriate [`FeeRate`] to be used for getting transactions - /// confirmed within a reasonable amount of time. - fn select_feerate(&self) -> FeeRate { - // TODO: This should obviously not be a const :) - FeeRate::from_sat_per_vb(5.0) - } } /// Represents a subscription to the status of a given transaction. @@ -329,6 +235,152 @@ impl Subscription { } } +impl Wallet +where + D: BatchDatabase, +{ + pub async fn balance(&self) -> Result { + let balance = self + .wallet + .lock() + .await + .get_balance() + .context("Failed to calculate Bitcoin balance")?; + + Ok(Amount::from_sat(balance)) + } + + pub async fn new_address(&self) -> Result
{ + let address = self + .wallet + .lock() + .await + .get_new_address() + .context("Failed to get new Bitcoin address")?; + + Ok(address) + } + + pub async fn transaction_fee(&self, txid: Txid) -> Result { + let fees = self + .wallet + .lock() + .await + .list_transactions(true)? + .iter() + .find(|tx| tx.txid == txid) + .context("Could not find tx in bdk wallet when trying to determine fees")? + .fees; + + Ok(Amount::from_sat(fees)) + } + + pub async fn send_to_address( + &self, + address: Address, + amount: Amount, + ) -> Result { + let wallet = self.wallet.lock().await; + + let mut tx_builder = wallet.build_tx(); + tx_builder.add_recipient(address.script_pubkey(), amount.as_sat()); + tx_builder.fee_rate(self.select_feerate()); + let (psbt, _details) = tx_builder.finish()?; + + Ok(psbt) + } + + /// Calculates the maximum "giveable" amount of this wallet. + /// + /// We define this as the maximum amount we can pay to a single output, + /// already accounting for the fees we need to spend to get the + /// transaction confirmed. + pub async fn max_giveable(&self, locking_script_size: usize) -> Result { + let wallet = self.wallet.lock().await; + + let mut tx_builder = wallet.build_tx(); + + let dummy_script = Script::from(vec![0u8; locking_script_size]); + tx_builder.set_single_recipient(dummy_script); + tx_builder.drain_wallet(); + tx_builder.fee_rate(self.select_feerate()); + let (_, details) = tx_builder.finish().context("Failed to build transaction")?; + + let max_giveable = details.sent - details.fees; + + Ok(Amount::from_sat(max_giveable)) + } +} + +impl Wallet +where + B: Blockchain, + D: BatchDatabase, +{ + pub async fn get_tx(&self, txid: Txid) -> Result> { + let tx = self.wallet.lock().await.client().get_tx(&txid)?; + + Ok(tx) + } + + pub async fn sync(&self) -> Result<()> { + self.wallet + .lock() + .await + .sync(noop_progress(), None) + .context("Failed to sync balance of Bitcoin wallet")?; + + Ok(()) + } +} + +impl Wallet { + // TODO: Get rid of this by changing bounds on bdk::Wallet + pub fn get_network(&self) -> bitcoin::Network { + self.network + } + + /// Selects an appropriate [`FeeRate`] to be used for getting transactions + /// confirmed within a reasonable amount of time. + fn select_feerate(&self) -> FeeRate { + // TODO: This should obviously not be a const :) + FeeRate::from_sat_per_vb(5.0) + } +} + +#[cfg(test)] +impl Wallet<(), bdk::database::MemoryDatabase, ()> { + /// Creates a new, funded wallet to be used within tests. + pub fn new_funded(amount: u64) -> Self { + use bdk::database::MemoryDatabase; + use bdk::{LocalUtxo, TransactionDetails}; + use bitcoin::OutPoint; + use std::str::FromStr; + use testutils::testutils; + + let descriptors = testutils!(@descriptors ("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)")); + + let mut database = MemoryDatabase::new(); + bdk::populate_test_db!( + &mut database, + testutils! { + @tx ( (@external descriptors, 0) => amount ) (@confirmations 1) + }, + Some(100) + ); + + let wallet = + bdk::Wallet::new_offline(&descriptors.0, None, Network::Regtest, database).unwrap(); + + Self { + client: Arc::new(Mutex::new(())), + wallet: Arc::new(Mutex::new(wallet)), + finality_confirmations: 1, + network: Network::Regtest, + } + } +} + /// Defines a watchable transaction. /// /// For a transaction to be watchable, we need to know two things: Its @@ -350,7 +402,7 @@ impl Watchable for (Txid, Script) { } } -struct Client { +pub struct Client { electrum: bdk::electrum_client::Client, latest_block: BlockHeight, last_ping: Instant, diff --git a/swap/src/protocol.rs b/swap/src/protocol.rs index faab1610..fb3a35be 100644 --- a/swap/src/protocol.rs +++ b/swap/src/protocol.rs @@ -1,7 +1,9 @@ +use crate::{bitcoin, monero}; use conquer_once::Lazy; use ecdsa_fun::fun::marker::Mark; +use serde::{Deserialize, Serialize}; use sha2::Sha256; -use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQ; +use sigma_fun::ext::dl_secp256k1_ed25519_eq::{CrossCurveDLEQ, CrossCurveDLEQProof}; use sigma_fun::HashTranscript; pub mod alice; @@ -18,6 +20,44 @@ pub static CROSS_CURVE_PROOF_SYSTEM: Lazy< #[derive(Debug, Copy, Clone)] pub struct StartingBalances { - pub xmr: crate::monero::Amount, + pub xmr: monero::Amount, pub btc: bitcoin::Amount, } + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Message0 { + B: bitcoin::PublicKey, + S_b_monero: monero::PublicKey, + S_b_bitcoin: bitcoin::PublicKey, + dleq_proof_s_b: CrossCurveDLEQProof, + v_b: monero::PrivateViewKey, + refund_address: bitcoin::Address, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Message1 { + A: bitcoin::PublicKey, + S_a_monero: monero::PublicKey, + S_a_bitcoin: bitcoin::PublicKey, + dleq_proof_s_a: CrossCurveDLEQProof, + v_a: monero::PrivateViewKey, + redeem_address: bitcoin::Address, + punish_address: bitcoin::Address, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Message2 { + psbt: bitcoin::PartiallySignedTransaction, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Message3 { + tx_cancel_sig: bitcoin::Signature, + tx_refund_encsig: bitcoin::EncryptedSignature, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Message4 { + tx_punish_sig: bitcoin::Signature, + tx_cancel_sig: bitcoin::Signature, +} diff --git a/swap/src/protocol/alice.rs b/swap/src/protocol/alice.rs index 9bc6423d..5909a701 100644 --- a/swap/src/protocol/alice.rs +++ b/swap/src/protocol/alice.rs @@ -8,10 +8,8 @@ use uuid::Uuid; pub use self::behaviour::{Behaviour, OutEvent}; pub use self::event_loop::{EventLoop, EventLoopHandle}; -pub use self::execution_setup::Message1; pub use self::state::*; pub use self::swap::{run, run_until}; -pub use execution_setup::Message3; mod behaviour; pub mod event_loop; diff --git a/swap/src/protocol/alice/execution_setup.rs b/swap/src/protocol/alice/execution_setup.rs index 965b6bd6..a4b04aa0 100644 --- a/swap/src/protocol/alice/execution_setup.rs +++ b/swap/src/protocol/alice/execution_setup.rs @@ -1,30 +1,9 @@ -use crate::bitcoin::{EncryptedSignature, Signature}; use crate::network::cbor_request_response::BUF_SIZE; use crate::protocol::alice::{State0, State3}; -use crate::protocol::bob::{Message0, Message2, Message4}; -use crate::{bitcoin, monero}; +use crate::protocol::{Message0, Message2, Message4}; use anyhow::{Context, Error}; use libp2p::PeerId; use libp2p_async_await::BehaviourOutEvent; -use serde::{Deserialize, Serialize}; -use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQProof; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Message1 { - pub(crate) A: bitcoin::PublicKey, - pub(crate) S_a_monero: monero::PublicKey, - pub(crate) S_a_bitcoin: bitcoin::PublicKey, - pub(crate) dleq_proof_s_a: CrossCurveDLEQProof, - pub(crate) v_a: monero::PrivateViewKey, - pub(crate) redeem_address: bitcoin::Address, - pub(crate) punish_address: bitcoin::Address, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Message3 { - pub(crate) tx_cancel_sig: Signature, - pub(crate) tx_refund_encsig: EncryptedSignature, -} #[derive(Debug)] pub enum OutEvent { @@ -78,7 +57,9 @@ impl Behaviour { let message2 = serde_cbor::from_slice::(&substream.read_message(BUF_SIZE).await?) .context("Failed to deserialize message2")?; - let state2 = state1.receive(message2); + let state2 = state1 + .receive(message2) + .context("Failed to receive Message2")?; substream .write_message( diff --git a/swap/src/protocol/alice/state.rs b/swap/src/protocol/alice/state.rs index ae9e58dd..fe0f5679 100644 --- a/swap/src/protocol/alice/state.rs +++ b/swap/src/protocol/alice/state.rs @@ -4,9 +4,8 @@ use crate::bitcoin::{ use crate::env::Config; use crate::monero::wallet::{TransferRequest, WatchRequest}; use crate::monero::TransferProof; -use crate::protocol::alice::{Message1, Message3}; -use crate::protocol::bob::{Message0, Message2, Message4}; -use crate::protocol::CROSS_CURVE_PROOF_SYSTEM; +use crate::monero_ext::ScalarExt; +use crate::protocol::{Message0, Message1, Message2, Message3, Message4, CROSS_CURVE_PROOF_SYSTEM}; use crate::{bitcoin, monero}; use anyhow::{anyhow, bail, Context, Result}; use monero_rpc::wallet::BlockHeight; @@ -74,21 +73,20 @@ impl fmt::Display for AliceState { } } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct State0 { - pub a: bitcoin::SecretKey, - pub s_a: monero::Scalar, - pub v_a: monero::PrivateViewKey, - pub(crate) S_a_monero: monero::PublicKey, - pub(crate) S_a_bitcoin: bitcoin::PublicKey, - pub dleq_proof_s_a: CrossCurveDLEQProof, - #[serde(with = "::bitcoin::util::amount::serde::as_sat")] - pub btc: bitcoin::Amount, - pub xmr: monero::Amount, - pub cancel_timelock: CancelTimelock, - pub punish_timelock: PunishTimelock, - pub redeem_address: bitcoin::Address, - pub punish_address: bitcoin::Address, + a: bitcoin::SecretKey, + s_a: monero::Scalar, + v_a: monero::PrivateViewKey, + S_a_monero: monero::PublicKey, + S_a_bitcoin: bitcoin::PublicKey, + dleq_proof_s_a: CrossCurveDLEQProof, + btc: bitcoin::Amount, + xmr: monero::Amount, + cancel_timelock: CancelTimelock, + punish_timelock: PunishTimelock, + redeem_address: bitcoin::Address, + punish_address: bitcoin::Address, } impl State0 { @@ -168,7 +166,7 @@ impl State0 { } } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug)] pub struct State1 { a: bitcoin::SecretKey, B: bitcoin::PublicKey, @@ -180,7 +178,6 @@ pub struct State1 { v: monero::PrivateViewKey, v_a: monero::PrivateViewKey, dleq_proof_s_a: CrossCurveDLEQProof, - #[serde(with = "::bitcoin::util::amount::serde::as_sat")] btc: bitcoin::Amount, xmr: monero::Amount, cancel_timelock: CancelTimelock, @@ -203,8 +200,11 @@ impl State1 { } } - pub fn receive(self, msg: Message2) -> State2 { - State2 { + pub fn receive(self, msg: Message2) -> Result { + let tx_lock = bitcoin::TxLock::from_psbt(msg.psbt, self.a.public(), self.B, self.btc) + .context("Failed to re-construct TxLock from received PSBT")?; + + Ok(State2 { a: self.a, B: self.B, s_a: self.s_a, @@ -218,12 +218,12 @@ impl State1 { refund_address: self.refund_address, redeem_address: self.redeem_address, punish_address: self.punish_address, - tx_lock: msg.tx_lock, - } + tx_lock, + }) } } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug)] pub struct State2 { a: bitcoin::SecretKey, B: bitcoin::PublicKey, @@ -231,7 +231,6 @@ pub struct State2 { S_b_monero: monero::PublicKey, S_b_bitcoin: bitcoin::PublicKey, v: monero::PrivateViewKey, - #[serde(with = "::bitcoin::util::amount::serde::as_sat")] btc: bitcoin::Amount, xmr: monero::Amount, cancel_timelock: CancelTimelock, @@ -295,23 +294,23 @@ impl State2 { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub struct State3 { - pub a: bitcoin::SecretKey, - pub B: bitcoin::PublicKey, - pub s_a: monero::Scalar, - pub S_b_monero: monero::PublicKey, - pub S_b_bitcoin: bitcoin::PublicKey, + a: bitcoin::SecretKey, + B: bitcoin::PublicKey, + s_a: monero::Scalar, + S_b_monero: monero::PublicKey, + S_b_bitcoin: bitcoin::PublicKey, pub v: monero::PrivateViewKey, #[serde(with = "::bitcoin::util::amount::serde::as_sat")] - pub btc: bitcoin::Amount, - pub xmr: monero::Amount, + btc: bitcoin::Amount, + xmr: monero::Amount, pub cancel_timelock: CancelTimelock, pub punish_timelock: PunishTimelock, - pub refund_address: bitcoin::Address, - pub redeem_address: bitcoin::Address, - pub punish_address: bitcoin::Address, + refund_address: bitcoin::Address, + redeem_address: bitcoin::Address, + punish_address: bitcoin::Address, pub tx_lock: bitcoin::TxLock, - pub tx_punish_sig_bob: bitcoin::Signature, - pub tx_cancel_sig_bob: bitcoin::Signature, + tx_punish_sig_bob: bitcoin::Signature, + tx_cancel_sig_bob: bitcoin::Signature, } impl State3 { @@ -367,15 +366,48 @@ impl State3 { TxCancel::new(&self.tx_lock, self.cancel_timelock, self.a.public(), self.B) } - pub fn tx_punish(&self) -> TxPunish { + pub fn tx_refund(&self) -> TxRefund { + bitcoin::TxRefund::new(&self.tx_cancel(), &self.refund_address) + } + + pub fn extract_monero_private_key( + &self, + published_refund_tx: bitcoin::Transaction, + ) -> Result { + self.tx_refund().extract_monero_private_key( + published_refund_tx, + self.s_a, + self.a.clone(), + self.S_b_bitcoin, + ) + } + + pub fn signed_redeem_transaction( + &self, + sig: bitcoin::EncryptedSignature, + ) -> Result { + bitcoin::TxRedeem::new(&self.tx_lock, &self.redeem_address) + .complete(sig, self.a.clone(), self.s_a.to_secpfun_scalar(), self.B) + .context("Failed to complete Bitcoin redeem transaction") + } + + pub fn signed_cancel_transaction(&self) -> Result { + self.tx_cancel() + .complete_as_alice(self.a.clone(), self.B, self.tx_cancel_sig_bob.clone()) + .context("Failed to complete Bitcoin cancel transaction") + } + + pub fn signed_punish_transaction(&self) -> Result { + self.tx_punish() + .complete(self.tx_punish_sig_bob.clone(), self.a.clone(), self.B) + .context("Failed to complete Bitcoin punish transaction") + } + + fn tx_punish(&self) -> TxPunish { bitcoin::TxPunish::new( &self.tx_cancel(), &self.punish_address, self.punish_timelock, ) } - - pub fn tx_refund(&self) -> TxRefund { - bitcoin::TxRefund::new(&self.tx_cancel(), &self.refund_address) - } } diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 6806f668..dc732bf7 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -1,8 +1,7 @@ //! Run an XMR/BTC swap in the role of Alice. //! Alice holds XMR and wishes receive BTC. -use crate::bitcoin::{ExpiredTimelocks, TxRedeem}; +use crate::bitcoin::ExpiredTimelocks; use crate::env::Config; -use crate::monero_ext::ScalarExt; use crate::protocol::alice; use crate::protocol::alice::event_loop::EventLoopHandle; use crate::protocol::alice::AliceState; @@ -158,12 +157,7 @@ async fn next_state( } => match state3.expired_timelocks(bitcoin_wallet).await? { ExpiredTimelocks::None => { let tx_lock_status = bitcoin_wallet.subscribe_to(state3.tx_lock.clone()).await; - match TxRedeem::new(&state3.tx_lock, &state3.redeem_address).complete( - *encrypted_signature, - state3.a.clone(), - state3.s_a.to_secpfun_scalar(), - state3.B, - ) { + match state3.signed_redeem_transaction(*encrypted_signature) { Ok(tx) => match bitcoin_wallet.broadcast(tx, "redeem").await { Ok((_, subscription)) => match subscription.wait_until_final().await { Ok(_) => AliceState::BtcRedeemed, @@ -205,18 +199,14 @@ async fn next_state( state3, monero_wallet_restore_blockheight, } => { - let tx_cancel = state3.tx_cancel(); + let transaction = state3.signed_cancel_transaction()?; // If Bob hasn't yet broadcasted the tx cancel, we do it if bitcoin_wallet - .get_raw_transaction(tx_cancel.txid()) + .get_raw_transaction(transaction.txid()) .await .is_err() { - let transaction = tx_cancel - .complete_as_alice(state3.a.clone(), state3.B, state3.tx_cancel_sig_bob.clone()) - .context("Failed to complete Bitcoin cancel transaction")?; - if let Err(e) = bitcoin_wallet.broadcast(transaction, "cancel").await { tracing::debug!( "Assuming transaction is already broadcasted because: {:#}", @@ -243,14 +233,9 @@ async fn next_state( select! { seen_refund = tx_refund_status.wait_until_seen() => { seen_refund.context("Failed to monitor refund transaction")?; - let published_refund_tx = bitcoin_wallet.get_raw_transaction(state3.tx_refund().txid()).await?; - let spend_key = state3.tx_refund().extract_monero_private_key( - published_refund_tx, - state3.s_a, - state3.a.clone(), - state3.S_b_bitcoin, - )?; + let published_refund_tx = bitcoin_wallet.get_raw_transaction(state3.tx_refund().txid()).await?; + let spend_key = state3.extract_monero_private_key(published_refund_tx)?; AliceState::BtcRefunded { spend_key, @@ -283,11 +268,7 @@ async fn next_state( state3, monero_wallet_restore_blockheight, } => { - let signed_tx_punish = state3.tx_punish().complete( - state3.tx_punish_sig_bob.clone(), - state3.a.clone(), - state3.B, - )?; + let signed_tx_punish = state3.signed_punish_transaction()?; let punish = async { let (txid, subscription) = @@ -313,16 +294,11 @@ async fn next_state( // because a punish tx failure is not recoverable (besides re-trying) if the // refund tx was not included. - let tx_refund = state3.tx_refund(); - let published_refund_tx = - bitcoin_wallet.get_raw_transaction(tx_refund.txid()).await?; + let published_refund_tx = bitcoin_wallet + .get_raw_transaction(state3.tx_refund().txid()) + .await?; - let spend_key = tx_refund.extract_monero_private_key( - published_refund_tx, - state3.s_a, - state3.a.clone(), - state3.S_b_bitcoin, - )?; + let spend_key = state3.extract_monero_private_key(published_refund_tx)?; AliceState::BtcRefunded { spend_key, diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index 604d879a..2f3b3dd3 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -4,7 +4,6 @@ use crate::network::{encrypted_signature, spot_price}; use crate::protocol::bob; use crate::{bitcoin, monero}; use anyhow::{anyhow, Error, Result}; -pub use execution_setup::{Message0, Message2, Message4}; use libp2p::core::Multiaddr; use libp2p::request_response::{RequestResponseEvent, RequestResponseMessage, ResponseChannel}; use libp2p::{NetworkBehaviour, PeerId}; diff --git a/swap/src/protocol/bob/execution_setup.rs b/swap/src/protocol/bob/execution_setup.rs index 6fa7491e..1c53bf22 100644 --- a/swap/src/protocol/bob/execution_setup.rs +++ b/swap/src/protocol/bob/execution_setup.rs @@ -1,35 +1,11 @@ -use crate::bitcoin::Signature; use crate::network::cbor_request_response::BUF_SIZE; -use crate::protocol::alice::{Message1, Message3}; use crate::protocol::bob::{State0, State2}; +use crate::protocol::{Message1, Message3}; use anyhow::{Context, Error, Result}; use libp2p::PeerId; use libp2p_async_await::BehaviourOutEvent; -use serde::{Deserialize, Serialize}; -use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQProof; use std::sync::Arc; -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Message0 { - pub(crate) B: crate::bitcoin::PublicKey, - pub(crate) S_b_monero: monero::PublicKey, - pub(crate) S_b_bitcoin: crate::bitcoin::PublicKey, - pub(crate) dleq_proof_s_b: CrossCurveDLEQProof, - pub(crate) v_b: crate::monero::PrivateViewKey, - pub(crate) refund_address: bitcoin::Address, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Message2 { - pub(crate) tx_lock: crate::bitcoin::TxLock, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Message4 { - pub(crate) tx_punish_sig: Signature, - pub(crate) tx_cancel_sig: Signature, -} - #[derive(Debug)] pub enum OutEvent { Done(Result), diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index dd3a3bea..512b6a25 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -6,9 +6,7 @@ use crate::monero; use crate::monero::wallet::WatchRequest; use crate::monero::{monero_private_key, TransferProof}; use crate::monero_ext::ScalarExt; -use crate::protocol::alice::{Message1, Message3}; -use crate::protocol::bob::{Message0, Message2, Message4}; -use crate::protocol::CROSS_CURVE_PROOF_SYSTEM; +use crate::protocol::{Message0, Message1, Message2, Message3, Message4, CROSS_CURVE_PROOF_SYSTEM}; use anyhow::{anyhow, bail, Context, Result}; use ecdsa_fun::adaptor::{Adaptor, HashTranscript}; use ecdsa_fun::nonce::Deterministic; @@ -69,7 +67,7 @@ impl fmt::Display for BobState { } } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct State0 { b: bitcoin::SecretKey, s_b: monero::Scalar, @@ -77,7 +75,6 @@ pub struct State0 { S_b_bitcoin: bitcoin::PublicKey, v_b: monero::PrivateViewKey, dleq_proof_s_b: CrossCurveDLEQProof, - #[serde(with = "::bitcoin::util::amount::serde::as_sat")] btc: bitcoin::Amount, xmr: monero::Amount, cancel_timelock: CancelTimelock, @@ -170,7 +167,7 @@ impl State0 { } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug)] pub struct State1 { A: bitcoin::PublicKey, b: bitcoin::SecretKey, @@ -191,7 +188,7 @@ pub struct State1 { impl State1 { pub fn next_message(&self) -> Message2 { Message2 { - tx_lock: self.tx_lock.clone(), + psbt: self.tx_lock.clone().into(), } } diff --git a/swap/tests/bob_refunds_using_cancel_and_refund_command.rs b/swap/tests/bob_refunds_using_cancel_and_refund_command.rs index e04eab58..f9a7a41e 100644 --- a/swap/tests/bob_refunds_using_cancel_and_refund_command.rs +++ b/swap/tests/bob_refunds_using_cancel_and_refund_command.rs @@ -1,13 +1,13 @@ -pub mod testutils; +pub mod harness; +use harness::bob_run_until::is_btc_locked; +use harness::FastCancelConfig; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; -use testutils::bob_run_until::is_btc_locked; -use testutils::FastCancelConfig; #[tokio::test] async fn given_bob_manually_refunds_after_btc_locked_bob_refunds() { - testutils::setup_test(FastCancelConfig, |mut ctx| async move { + harness::setup_test(FastCancelConfig, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); diff --git a/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired.rs b/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired.rs index 8edf705d..13793009 100644 --- a/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired.rs +++ b/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired.rs @@ -1,14 +1,14 @@ -pub mod testutils; +pub mod harness; use bob::cancel::Error; +use harness::bob_run_until::is_btc_locked; +use harness::SlowCancelConfig; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; -use testutils::bob_run_until::is_btc_locked; -use testutils::SlowCancelConfig; #[tokio::test] async fn given_bob_manually_cancels_when_timelock_not_expired_errors() { - testutils::setup_test(SlowCancelConfig, |mut ctx| async move { + harness::setup_test(SlowCancelConfig, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); diff --git a/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force.rs b/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force.rs index ff46d683..8a9d7f66 100644 --- a/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force.rs +++ b/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force.rs @@ -1,13 +1,13 @@ -pub mod testutils; +pub mod harness; +use harness::bob_run_until::is_btc_locked; +use harness::SlowCancelConfig; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; -use testutils::bob_run_until::is_btc_locked; -use testutils::SlowCancelConfig; #[tokio::test] async fn given_bob_manually_forces_cancel_when_timelock_not_expired_errors() { - testutils::setup_test(SlowCancelConfig, |mut ctx| async move { + harness::setup_test(SlowCancelConfig, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); diff --git a/swap/tests/happy_path.rs b/swap/tests/happy_path.rs index fb665e73..5f1ac5b3 100644 --- a/swap/tests/happy_path.rs +++ b/swap/tests/happy_path.rs @@ -1,14 +1,14 @@ -pub mod testutils; +pub mod harness; +use harness::SlowCancelConfig; use swap::protocol::{alice, bob}; -use testutils::SlowCancelConfig; use tokio::join; /// Run the following tests with RUST_MIN_STACK=10000000 #[tokio::test] async fn happy_path() { - testutils::setup_test(SlowCancelConfig, |mut ctx| async move { + harness::setup_test(SlowCancelConfig, |mut ctx| async move { let (bob_swap, _) = ctx.bob_swap().await; let bob_swap = tokio::spawn(bob::run(bob_swap)); diff --git a/swap/tests/happy_path_restart_bob_after_xmr_locked.rs b/swap/tests/happy_path_restart_bob_after_xmr_locked.rs index 6abba569..9967c117 100644 --- a/swap/tests/happy_path_restart_bob_after_xmr_locked.rs +++ b/swap/tests/happy_path_restart_bob_after_xmr_locked.rs @@ -1,13 +1,13 @@ -pub mod testutils; +pub mod harness; +use harness::bob_run_until::is_xmr_locked; +use harness::SlowCancelConfig; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; -use testutils::bob_run_until::is_xmr_locked; -use testutils::SlowCancelConfig; #[tokio::test] async fn given_bob_restarts_after_xmr_is_locked_resume_swap() { - testutils::setup_test(SlowCancelConfig, |mut ctx| async move { + harness::setup_test(SlowCancelConfig, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_xmr_locked)); diff --git a/swap/tests/happy_path_restart_bob_before_xmr_locked.rs b/swap/tests/happy_path_restart_bob_before_xmr_locked.rs index 6abba569..9967c117 100644 --- a/swap/tests/happy_path_restart_bob_before_xmr_locked.rs +++ b/swap/tests/happy_path_restart_bob_before_xmr_locked.rs @@ -1,13 +1,13 @@ -pub mod testutils; +pub mod harness; +use harness::bob_run_until::is_xmr_locked; +use harness::SlowCancelConfig; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; -use testutils::bob_run_until::is_xmr_locked; -use testutils::SlowCancelConfig; #[tokio::test] async fn given_bob_restarts_after_xmr_is_locked_resume_swap() { - testutils::setup_test(SlowCancelConfig, |mut ctx| async move { + harness::setup_test(SlowCancelConfig, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_xmr_locked)); diff --git a/swap/tests/testutils/bitcoind.rs b/swap/tests/harness/bitcoind.rs similarity index 100% rename from swap/tests/testutils/bitcoind.rs rename to swap/tests/harness/bitcoind.rs diff --git a/swap/tests/testutils/electrs.rs b/swap/tests/harness/electrs.rs similarity index 99% rename from swap/tests/testutils/electrs.rs rename to swap/tests/harness/electrs.rs index c4c09720..46eff15a 100644 --- a/swap/tests/testutils/electrs.rs +++ b/swap/tests/harness/electrs.rs @@ -1,4 +1,4 @@ -use crate::testutils::bitcoind; +use crate::harness::bitcoind; use bitcoin::Network; use std::collections::HashMap; use testcontainers::core::{Container, Docker, WaitForMessage}; diff --git a/swap/tests/testutils/mod.rs b/swap/tests/harness/mod.rs similarity index 99% rename from swap/tests/testutils/mod.rs rename to swap/tests/harness/mod.rs index 7df968ed..8fafe985 100644 --- a/swap/tests/testutils/mod.rs +++ b/swap/tests/harness/mod.rs @@ -1,7 +1,7 @@ mod bitcoind; mod electrs; -use crate::testutils; +use crate::harness; use anyhow::{bail, Context, Result}; use async_trait::async_trait; use bitcoin_harness::{BitcoindRpcApi, Client}; @@ -452,7 +452,7 @@ where let env_config = C::get_config(); - let (monero, containers) = testutils::init_containers(&cli).await; + let (monero, containers) = harness::init_containers(&cli).await; let btc_amount = bitcoin::Amount::from_sat(1_000_000); let xmr_amount = monero::Amount::from_monero(btc_amount.as_btc() / FixedRate::RATE).unwrap(); @@ -470,7 +470,7 @@ where let electrs_rpc_port = containers .electrs - .get_host_port(testutils::electrs::RPC_PORT) + .get_host_port(harness::electrs::RPC_PORT) .expect("Could not map electrs rpc port"); let alice_seed = Seed::random().unwrap(); @@ -600,7 +600,7 @@ async fn init_bitcoind_container( let docker = cli.run_with_args(image, run_args); let a = docker - .get_host_port(testutils::bitcoind::RPC_PORT) + .get_host_port(harness::bitcoind::RPC_PORT) .context("Could not map bitcoind rpc port")?; let bitcoind_url = { @@ -627,7 +627,7 @@ pub async fn init_electrs_container( let bitcoind_rpc_addr = format!( "{}:{}", bitcoind_container_name, - testutils::bitcoind::RPC_PORT + harness::bitcoind::RPC_PORT ); let image = electrs::Electrs::default() .with_volume(volume) diff --git a/swap/tests/punish.rs b/swap/tests/punish.rs index 758f8e98..f058dc79 100644 --- a/swap/tests/punish.rs +++ b/swap/tests/punish.rs @@ -1,15 +1,15 @@ -pub mod testutils; +pub mod harness; +use harness::bob_run_until::is_btc_locked; +use harness::FastPunishConfig; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; -use testutils::bob_run_until::is_btc_locked; -use testutils::FastPunishConfig; /// Bob locks Btc and Alice locks Xmr. Bob does not act; he fails to send Alice /// the encsig and fail to refund or redeem. Alice punishes. #[tokio::test] async fn alice_punishes_if_bob_never_acts_after_fund() { - testutils::setup_test(FastPunishConfig, |mut ctx| async move { + harness::setup_test(FastPunishConfig, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked));