From 52b9a78de2076ec8517b4290f7291678e609d251 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 24 Mar 2021 18:30:55 +1100 Subject: [PATCH] Alice to validate Bob's PSBT for correctness In order for the re-construction of TxLock to be meaningful, we limit `Message2` to the PSBT instead of the full struct. This is a breaking change in the network layer. The PSBT is valid if: - It has at most two outputs (we allow a change output) - One of the outputs pays the agreed upon amount to a shared output script Resolves #260. --- CHANGELOG.md | 5 + Cargo.lock | 355 +++++++++++++++++---- swap/Cargo.toml | 1 + swap/src/bitcoin.rs | 1 + swap/src/bitcoin/lock.rs | 144 ++++++++- swap/src/bitcoin/wallet.rs | 33 ++ swap/src/protocol.rs | 2 +- swap/src/protocol/alice/execution_setup.rs | 4 +- swap/src/protocol/alice/state.rs | 11 +- swap/src/protocol/bob/state.rs | 2 +- 10 files changed, 485 insertions(+), 73 deletions(-) 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 fef2e252..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; diff --git a/swap/src/bitcoin/lock.rs b/swap/src/bitcoin/lock.rs index edbcbd85..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,7 +19,15 @@ 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()) @@ -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 0bda71cb..c87c0f3e 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -348,6 +348,39 @@ impl Wallet { } } +#[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 diff --git a/swap/src/protocol.rs b/swap/src/protocol.rs index e446759c..fb3a35be 100644 --- a/swap/src/protocol.rs +++ b/swap/src/protocol.rs @@ -47,7 +47,7 @@ pub struct Message1 { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Message2 { - tx_lock: bitcoin::TxLock, + psbt: bitcoin::PartiallySignedTransaction, } #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/swap/src/protocol/alice/execution_setup.rs b/swap/src/protocol/alice/execution_setup.rs index d3a37519..a4b04aa0 100644 --- a/swap/src/protocol/alice/execution_setup.rs +++ b/swap/src/protocol/alice/execution_setup.rs @@ -57,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 ecaee48f..fe0f5679 100644 --- a/swap/src/protocol/alice/state.rs +++ b/swap/src/protocol/alice/state.rs @@ -200,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, @@ -215,8 +218,8 @@ impl State1 { refund_address: self.refund_address, redeem_address: self.redeem_address, punish_address: self.punish_address, - tx_lock: msg.tx_lock, - } + tx_lock, + }) } } diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index c70b458e..512b6a25 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -188,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(), } }