373: Alice verifies Bob's PSBT r=thomaseizinger a=thomaseizinger

TODO:

- [x] Update the changelog

Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
This commit is contained in:
bors[bot] 2021-03-30 03:59:03 +00:00 committed by GitHub
commit 7e5e00909a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 775 additions and 343 deletions

View File

@ -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

355
Cargo.lock generated
View File

@ -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",
]

View File

@ -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"

View File

@ -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<PublicKey> for Point {
fn from(from: PublicKey) -> Self {
from.0

View File

@ -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<Self> {
pub async fn new<B, D, C>(
wallet: &Wallet<B, D, C>,
amount: Amount,
A: PublicKey,
B: PublicKey,
) -> Result<Self>
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<Self> {
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()
}
}

View File

@ -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<Mutex<Client>>,
wallet: Arc<Mutex<bdk::Wallet<ElectrumBlockchain, bdk::sled::Tree>>>,
pub struct Wallet<B = ElectrumBlockchain, D = bdk::sled::Tree, C = Client> {
client: Arc<Mutex<C>>,
wallet: Arc<Mutex<bdk::Wallet<B, D>>>,
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<Amount> {
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<Address> {
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<Option<Transaction>> {
let tx = self.wallet.lock().await.client().get_tx(&txid)?;
Ok(tx)
}
pub async fn transaction_fee(&self, txid: Txid) -> Result<Amount> {
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<PartiallySignedTransaction> {
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<Amount> {
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<B, D, C> Wallet<B, D, C>
where
D: BatchDatabase,
{
pub async fn balance(&self) -> Result<Amount> {
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<Address> {
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<Amount> {
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<PartiallySignedTransaction> {
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<Amount> {
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<B, D, C> Wallet<B, D, C>
where
B: Blockchain,
D: BatchDatabase,
{
pub async fn get_tx(&self, txid: Txid) -> Result<Option<Transaction>> {
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<B, D, C> Wallet<B, D, C> {
// 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,

View File

@ -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,
}

View File

@ -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;

View File

@ -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::<Message2>(&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(

View File

@ -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<State2> {
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<monero::PrivateKey> {
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::Transaction> {
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<bitcoin::Transaction> {
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<bitcoin::Transaction> {
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)
}
}

View File

@ -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,

View File

@ -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};

View File

@ -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<State2>),

View File

@ -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(),
}
}

View File

@ -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));

View File

@ -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));

View File

@ -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));

View File

@ -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));

View File

@ -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));

View File

@ -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));

View File

@ -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};

View File

@ -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)

View File

@ -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));