diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 958d2785a6..37bb06dbd7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ['1.20', '1.21'] + go: ['1.21', '1.22'] steps: - uses: awalsh128/cache-apt-pkgs-action@1850ee53f6e706525805321a3f2f863dcf73c962 #v1.3.0 with: diff --git a/.gitignore b/.gitignore index 04c9850784..87563dd17c 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ client/cmd/translationsreport/translationsreport client/cmd/translationsreport/worksheets server/cmd/dexadm/dexadm internal/libsecp256k1/secp256k1 +internal/cmd/xmrswap/xmrswap diff --git a/client/cmd/bisonw-desktop/go.mod b/client/cmd/bisonw-desktop/go.mod index c92c71a87f..7248e52c93 100644 --- a/client/cmd/bisonw-desktop/go.mod +++ b/client/cmd/bisonw-desktop/go.mod @@ -1,6 +1,8 @@ module decred.org/dcrdex/client/cmd/bisonw-desktop -go 1.19 +go 1.21.10 + +toolchain go1.22.4 replace decred.org/dcrdex => ../../.. diff --git a/client/cmd/bisonw-desktop/go.sum b/client/cmd/bisonw-desktop/go.sum index c0ebc6da76..33cfd37ea6 100644 --- a/client/cmd/bisonw-desktop/go.sum +++ b/client/cmd/bisonw-desktop/go.sum @@ -173,6 +173,7 @@ github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -261,6 +262,7 @@ github.com/decred/dcrd/blockchain/stake/v5 v5.0.1/go.mod h1:y1tMD1TssTlPmKDYbSrF github.com/decred/dcrd/blockchain/standalone/v2 v2.2.1 h1:zeI9CHkLM9be4QOBmIAtoPfs6NCgJM1lpmRUYE61I8o= github.com/decred/dcrd/blockchain/standalone/v2 v2.2.1/go.mod h1:yXZz/EgWdGw5nqMEvyKj/iXZ9I2VSyO95xKj6mRUMIM= github.com/decred/dcrd/blockchain/v5 v5.0.1 h1:IGr8rJsgBVKDBI8STzeuGF6Mej0xbIX4gVVBA9yEMRU= +github.com/decred/dcrd/blockchain/v5 v5.0.1/go.mod h1:LtSV1+u8aBQzlExAQcl4HIJ6Bfi5f6Rvws/9euH4mDA= github.com/decred/dcrd/certgen v1.1.3 h1:MYENpBWVSP6FkkLBSSnaBGEOWobPcgYBLDDo88szi9c= github.com/decred/dcrd/certgen v1.1.3/go.mod h1:Od5y39J+r2ZlvrizyWu2cylcYu0+emTTVm3eix4W8bw= github.com/decred/dcrd/chaincfg/chainhash v1.0.4 h1:zRCv6tdncLfLTKYqu7hrXvs7hW+8FO/NvwoFvGsrluU= @@ -324,6 +326,7 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczC github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/docker v1.6.2 h1:HlFGsy+9/xrgMmhmN+NGhCc5SHGJ7I+kHosRR1xc/aI= +github.com/docker/docker v1.6.2/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -383,8 +386,11 @@ github.com/gcash/bchutil v0.0.0-20200506001747-c2894cd54b33/go.mod h1:wB++2ZcHUv github.com/gcash/bchutil v0.0.0-20210113190856-6ea28dff4000 h1:vVi7Ym3I9T4ZKhQy0/XLKzS3xAqX4K+/cSAmnvMR+HM= github.com/gcash/bchutil v0.0.0-20210113190856-6ea28dff4000/go.mod h1:H2USFGwtiu6CNMxiVQPqZkDzsoVSt9BLNqTfBBqGXRo= github.com/gcash/bchwallet v0.8.3-0.20210524112536-14ca25bc6549 h1:hC4Qxxvq0SN/1Jhqzh6ETZoBOXBG5PMBTYkTQqBcPKQ= +github.com/gcash/bchwallet v0.8.3-0.20210524112536-14ca25bc6549/go.mod h1:4hGEkghrAx5yIOdvakkRP5ZT8K/ZMtJhrXXr1Mc6lz4= github.com/gcash/bchwallet/walletdb v0.0.0-20210524044131-61bcca2ae6f9 h1:lryOGJ7VBv4zFpLtFEsH8ik68OnTwN3A71cisqXgN6w= +github.com/gcash/bchwallet/walletdb v0.0.0-20210524044131-61bcca2ae6f9/go.mod h1:KNPI56t8uMlItrGyu3tS4NCd3jGfA6KjdUg4bsk+QJg= github.com/gcash/neutrino v0.0.0-20210524114821-3b1878290cf9 h1:V5UNzi/5pZxE5s6kfCe59VjJRmfkyI+npZizMcAvEdI= +github.com/gcash/neutrino v0.0.0-20210524114821-3b1878290cf9/go.mod h1:MshBO/Xf8SCndZFetZ8yg79db/JghnOiMmPiY1Eatlw= github.com/gen2brain/beeep v0.0.0-20240112042604-c7bb2cd88fea h1:oWUHxzaBvwkRWiINbBOY39XIF+n9b4RJEPHdQ8waJUo= github.com/gen2brain/beeep v0.0.0-20240112042604-c7bb2cd88fea/go.mod h1:0W7dI87PvXJ1Sjs0QPvWXKcQmNERY77e8l7GFhZB/s4= github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= @@ -399,6 +405,7 @@ github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITL github.com/go-critic/go-critic v0.5.6/go.mod h1:cVjj0DfqewQVIlIAGexPCaGaZDAqGE29PYDDADIVNEo= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -537,6 +544,7 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -1706,6 +1714,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= diff --git a/dex/testing/loadbot/go.mod b/dex/testing/loadbot/go.mod index db23d90e1e..034a162252 100644 --- a/dex/testing/loadbot/go.mod +++ b/dex/testing/loadbot/go.mod @@ -1,6 +1,8 @@ module decred.org/dcrdex/dex/testing/loadbot -go 1.19 +go 1.21.10 + +toolchain go1.22.4 replace decred.org/dcrdex => ../../../ diff --git a/dex/testing/loadbot/go.sum b/dex/testing/loadbot/go.sum index 4858764e51..b2ec6b3427 100644 --- a/dex/testing/loadbot/go.sum +++ b/dex/testing/loadbot/go.sum @@ -173,6 +173,7 @@ github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -257,6 +258,7 @@ github.com/decred/dcrd/blockchain/stake/v5 v5.0.1/go.mod h1:y1tMD1TssTlPmKDYbSrF github.com/decred/dcrd/blockchain/standalone/v2 v2.2.1 h1:zeI9CHkLM9be4QOBmIAtoPfs6NCgJM1lpmRUYE61I8o= github.com/decred/dcrd/blockchain/standalone/v2 v2.2.1/go.mod h1:yXZz/EgWdGw5nqMEvyKj/iXZ9I2VSyO95xKj6mRUMIM= github.com/decred/dcrd/blockchain/v5 v5.0.1 h1:IGr8rJsgBVKDBI8STzeuGF6Mej0xbIX4gVVBA9yEMRU= +github.com/decred/dcrd/blockchain/v5 v5.0.1/go.mod h1:LtSV1+u8aBQzlExAQcl4HIJ6Bfi5f6Rvws/9euH4mDA= github.com/decred/dcrd/certgen v1.1.3 h1:MYENpBWVSP6FkkLBSSnaBGEOWobPcgYBLDDo88szi9c= github.com/decred/dcrd/certgen v1.1.3/go.mod h1:Od5y39J+r2ZlvrizyWu2cylcYu0+emTTVm3eix4W8bw= github.com/decred/dcrd/chaincfg/chainhash v1.0.4 h1:zRCv6tdncLfLTKYqu7hrXvs7hW+8FO/NvwoFvGsrluU= @@ -320,6 +322,7 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczC github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/docker v1.6.2 h1:HlFGsy+9/xrgMmhmN+NGhCc5SHGJ7I+kHosRR1xc/aI= +github.com/docker/docker v1.6.2/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -379,8 +382,11 @@ github.com/gcash/bchutil v0.0.0-20200506001747-c2894cd54b33/go.mod h1:wB++2ZcHUv github.com/gcash/bchutil v0.0.0-20210113190856-6ea28dff4000 h1:vVi7Ym3I9T4ZKhQy0/XLKzS3xAqX4K+/cSAmnvMR+HM= github.com/gcash/bchutil v0.0.0-20210113190856-6ea28dff4000/go.mod h1:H2USFGwtiu6CNMxiVQPqZkDzsoVSt9BLNqTfBBqGXRo= github.com/gcash/bchwallet v0.8.3-0.20210524112536-14ca25bc6549 h1:hC4Qxxvq0SN/1Jhqzh6ETZoBOXBG5PMBTYkTQqBcPKQ= +github.com/gcash/bchwallet v0.8.3-0.20210524112536-14ca25bc6549/go.mod h1:4hGEkghrAx5yIOdvakkRP5ZT8K/ZMtJhrXXr1Mc6lz4= github.com/gcash/bchwallet/walletdb v0.0.0-20210524044131-61bcca2ae6f9 h1:lryOGJ7VBv4zFpLtFEsH8ik68OnTwN3A71cisqXgN6w= +github.com/gcash/bchwallet/walletdb v0.0.0-20210524044131-61bcca2ae6f9/go.mod h1:KNPI56t8uMlItrGyu3tS4NCd3jGfA6KjdUg4bsk+QJg= github.com/gcash/neutrino v0.0.0-20210524114821-3b1878290cf9 h1:V5UNzi/5pZxE5s6kfCe59VjJRmfkyI+npZizMcAvEdI= +github.com/gcash/neutrino v0.0.0-20210524114821-3b1878290cf9/go.mod h1:MshBO/Xf8SCndZFetZ8yg79db/JghnOiMmPiY1Eatlw= github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= @@ -393,6 +399,7 @@ github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITL github.com/go-critic/go-critic v0.5.6/go.mod h1:cVjj0DfqewQVIlIAGexPCaGaZDAqGE29PYDDADIVNEo= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -521,6 +528,7 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -1663,6 +1671,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= diff --git a/go.mod b/go.mod index 1599c3900c..c81719a3cf 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,13 @@ module decred.org/dcrdex -go 1.19 +go 1.21.10 + +toolchain go1.22.4 require ( decred.org/dcrwallet/v4 v4.1.1 fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 + github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 github.com/btcsuite/btcd v0.24.2-beta.rc1.0.20240625142744-cc26860b4026 github.com/btcsuite/btcd/btcec/v2 v2.2.2 github.com/btcsuite/btcd/btcutil v1.1.5 @@ -43,6 +46,7 @@ require ( github.com/decred/go-socks v1.1.0 github.com/decred/slog v1.2.0 github.com/decred/vspd/types/v2 v2.1.0 + github.com/dev-warrior777/go-monero v0.0.0-20240607223259-5c9a0b540b3d github.com/dgraph-io/badger v1.6.2 github.com/ethereum/go-ethereum v1.11.5 github.com/fatih/color v1.11.0 @@ -51,6 +55,7 @@ require ( github.com/gcash/bchutil v0.0.0-20210113190856-6ea28dff4000 github.com/go-chi/chi/v5 v5.0.1 github.com/gorilla/websocket v1.5.1 + github.com/haven-protocol-org/monero-go-utils v0.0.0-20211126154105-058b2666f217 github.com/huandu/skiplist v1.2.0 github.com/jessevdk/go-flags v1.5.0 github.com/jrick/logrotate v1.0.0 @@ -80,7 +85,6 @@ require ( github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/VictoriaMetrics/fastcache v1.6.0 // indirect github.com/aead/siphash v1.0.1 // indirect - github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcwallet/wallet/txrules v1.2.1 // indirect github.com/btcsuite/btcwallet/wallet/txsizes v1.2.4 // indirect diff --git a/go.sum b/go.sum index 145a03b358..f90713d4fa 100644 --- a/go.sum +++ b/go.sum @@ -173,6 +173,7 @@ github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -257,6 +258,7 @@ github.com/decred/dcrd/blockchain/stake/v5 v5.0.1/go.mod h1:y1tMD1TssTlPmKDYbSrF github.com/decred/dcrd/blockchain/standalone/v2 v2.2.1 h1:zeI9CHkLM9be4QOBmIAtoPfs6NCgJM1lpmRUYE61I8o= github.com/decred/dcrd/blockchain/standalone/v2 v2.2.1/go.mod h1:yXZz/EgWdGw5nqMEvyKj/iXZ9I2VSyO95xKj6mRUMIM= github.com/decred/dcrd/blockchain/v5 v5.0.1 h1:IGr8rJsgBVKDBI8STzeuGF6Mej0xbIX4gVVBA9yEMRU= +github.com/decred/dcrd/blockchain/v5 v5.0.1/go.mod h1:LtSV1+u8aBQzlExAQcl4HIJ6Bfi5f6Rvws/9euH4mDA= github.com/decred/dcrd/certgen v1.1.3 h1:MYENpBWVSP6FkkLBSSnaBGEOWobPcgYBLDDo88szi9c= github.com/decred/dcrd/certgen v1.1.3/go.mod h1:Od5y39J+r2ZlvrizyWu2cylcYu0+emTTVm3eix4W8bw= github.com/decred/dcrd/chaincfg/chainhash v1.0.4 h1:zRCv6tdncLfLTKYqu7hrXvs7hW+8FO/NvwoFvGsrluU= @@ -310,6 +312,8 @@ github.com/decred/vspd/types/v2 v2.1.0 h1:cUVlmHPeLVsksPRnr2WHsmC2t1Skl6g1WH0Hmp github.com/decred/vspd/types/v2 v2.1.0/go.mod h1:2xnNqedkt9GuL+pK8uIzDxqYxFlwLRflYFJH64b76n0= github.com/denis-tingajkin/go-header v0.4.2/go.mod h1:eLRHAVXzE5atsKAnNRDB90WHCFFnBUn4RN0nRcs1LJA= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= +github.com/dev-warrior777/go-monero v0.0.0-20240607223259-5c9a0b540b3d h1:+PB0b05axI9P6XBXcc2MfbxDScrLOW62WlieddYbaXQ= +github.com/dev-warrior777/go-monero v0.0.0-20240607223259-5c9a0b540b3d/go.mod h1:oP7WZQhC9BsCDFmyfpAofVNe7fEOaa0LrjAU05I5TlY= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= @@ -320,6 +324,7 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczC github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/docker v1.6.2 h1:HlFGsy+9/xrgMmhmN+NGhCc5SHGJ7I+kHosRR1xc/aI= +github.com/docker/docker v1.6.2/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -380,8 +385,11 @@ github.com/gcash/bchutil v0.0.0-20200506001747-c2894cd54b33/go.mod h1:wB++2ZcHUv github.com/gcash/bchutil v0.0.0-20210113190856-6ea28dff4000 h1:vVi7Ym3I9T4ZKhQy0/XLKzS3xAqX4K+/cSAmnvMR+HM= github.com/gcash/bchutil v0.0.0-20210113190856-6ea28dff4000/go.mod h1:H2USFGwtiu6CNMxiVQPqZkDzsoVSt9BLNqTfBBqGXRo= github.com/gcash/bchwallet v0.8.3-0.20210524112536-14ca25bc6549 h1:hC4Qxxvq0SN/1Jhqzh6ETZoBOXBG5PMBTYkTQqBcPKQ= +github.com/gcash/bchwallet v0.8.3-0.20210524112536-14ca25bc6549/go.mod h1:4hGEkghrAx5yIOdvakkRP5ZT8K/ZMtJhrXXr1Mc6lz4= github.com/gcash/bchwallet/walletdb v0.0.0-20210524044131-61bcca2ae6f9 h1:lryOGJ7VBv4zFpLtFEsH8ik68OnTwN3A71cisqXgN6w= +github.com/gcash/bchwallet/walletdb v0.0.0-20210524044131-61bcca2ae6f9/go.mod h1:KNPI56t8uMlItrGyu3tS4NCd3jGfA6KjdUg4bsk+QJg= github.com/gcash/neutrino v0.0.0-20210524114821-3b1878290cf9 h1:V5UNzi/5pZxE5s6kfCe59VjJRmfkyI+npZizMcAvEdI= +github.com/gcash/neutrino v0.0.0-20210524114821-3b1878290cf9/go.mod h1:MshBO/Xf8SCndZFetZ8yg79db/JghnOiMmPiY1Eatlw= github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= @@ -394,6 +402,7 @@ github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITL github.com/go-critic/go-critic v0.5.6/go.mod h1:cVjj0DfqewQVIlIAGexPCaGaZDAqGE29PYDDADIVNEo= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -524,6 +533,7 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -613,6 +623,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/haven-protocol-org/monero-go-utils v0.0.0-20211126154105-058b2666f217 h1:CflMOYZHhaBo+7up92oOYcesIG+qDCAKdJo+niKBFWM= +github.com/haven-protocol-org/monero-go-utils v0.0.0-20211126154105-058b2666f217/go.mod h1:vSMDRpw62HGWO1Fi9DQwfgs4e3JCbt475GWY/W5DQZI= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= @@ -1678,6 +1690,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= diff --git a/internal/adaptorsigs/dcr/dcr.go b/internal/adaptorsigs/dcr/dcr.go new file mode 100644 index 0000000000..8c9a00de4e --- /dev/null +++ b/internal/adaptorsigs/dcr/dcr.go @@ -0,0 +1,239 @@ +package dcr + +import "github.com/decred/dcrd/txscript/v4" + +func LockRefundTxScript(kal, kaf []byte, locktime int64) ([]byte, error) { + return txscript.NewScriptBuilder(). + AddOp(txscript.OP_IF). + AddOp(txscript.OP_2). + AddData(kal). + AddData(kaf). + AddOp(txscript.OP_2). + AddOp(txscript.OP_CHECKMULTISIG). + AddOp(txscript.OP_ELSE). + AddInt64(locktime). + AddOp(txscript.OP_CHECKSEQUENCEVERIFY). + AddOp(txscript.OP_DROP). + AddData(kaf). + AddOp(txscript.OP_CHECKSIG). + AddOp(txscript.OP_ENDIF). + Script() +} + +func LockTxScript(kal, kaf []byte) ([]byte, error) { + return txscript.NewScriptBuilder(). + AddOp(txscript.OP_2). + AddData(kal). + AddData(kaf). + AddOp(txscript.OP_2). + AddOp(txscript.OP_CHECKMULTISIG). + Script() +} + +// def verifySCLockTx(self, tx_bytes, script_out, +// swap_value, +// Kal, Kaf, +// feerate, +// check_lock_tx_inputs, vkbv=None): +// # Verify: +// # +// +// # Not necessary to check the lock txn is mineable, as protocol will wait for it to confirm +// # However by checking early we can avoid wasting time processing unmineable txns +// # Check fee is reasonable +// +// tx = self.loadTx(tx_bytes) +// txid = self.getTxid(tx) +// self._log.info('Verifying lock tx: {}.'.format(b2h(txid))) +// +// ensure(tx.nVersion == self.txVersion(), 'Bad version') +// ensure(tx.nLockTime == 0, 'Bad nLockTime') # TODO match txns created by cores +// +// script_pk = self.getScriptDest(script_out) +// locked_n = findOutput(tx, script_pk) +// ensure(locked_n is not None, 'Output not found in tx') +// locked_coin = tx.vout[locked_n].nValue +// +// # Check value +// ensure(locked_coin == swap_value, 'Bad locked value') +// +// # Check script +// A, B = self.extractScriptLockScriptValues(script_out) +// ensure(A == Kal, 'Bad script pubkey') +// ensure(B == Kaf, 'Bad script pubkey') +// +// if check_lock_tx_inputs: +// # TODO: Check that inputs are unspent +// # Verify fee rate +// inputs_value = 0 +// add_bytes = 0 +// add_witness_bytes = getCompactSizeLen(len(tx.vin)) +// for pi in tx.vin: +// ptx = self.rpc('getrawtransaction', [i2h(pi.prevout.hash), True]) +// prevout = ptx['vout'][pi.prevout.n] +// inputs_value += make_int(prevout['value']) +// +// prevout_type = prevout['scriptPubKey']['type'] +// if prevout_type == 'witness_v0_keyhash': +// add_witness_bytes += 107 # sig 72, pk 33 and 2 size bytes +// add_witness_bytes += getCompactSizeLen(107) +// else: +// # Assume P2PKH, TODO more types +// add_bytes += 107 # OP_PUSH72 OP_PUSH33 +// +// outputs_value = 0 +// for txo in tx.vout: +// outputs_value += txo.nValue +// fee_paid = inputs_value - outputs_value +// assert (fee_paid > 0) +// +// vsize = self.getTxVSize(tx, add_bytes, add_witness_bytes) +// fee_rate_paid = fee_paid * 1000 // vsize +// +// self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', locked_coin, vsize, fee_rate_paid) +// +// if not self.compareFeeRates(fee_rate_paid, feerate): +// self._log.warning('feerate paid doesn\'t match expected: %ld, %ld', fee_rate_paid, feerate) +// # TODO: Display warning to user +// +// return txid, locked_n +// +// def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out, +// prevout_id, prevout_n, prevout_seq, prevout_script, +// Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv=None): +// # Verify: +// # Must have only one input with correct prevout and sequence +// # Must have only one output to the p2wsh of the lock refund script +// # Output value must be locked_coin - lock tx fee +// +// tx = self.loadTx(tx_bytes) +// txid = self.getTxid(tx) +// self._log.info('Verifying lock refund tx: {}.'.format(b2h(txid))) +// +// ensure(tx.nVersion == self.txVersion(), 'Bad version') +// ensure(tx.nLockTime == 0, 'nLockTime not 0') +// ensure(len(tx.vin) == 1, 'tx doesn\'t have one input') +// +// ensure(tx.vin[0].nSequence == prevout_seq, 'Bad input nSequence') +// ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script), 'Input scriptsig mismatch') +// ensure(tx.vin[0].prevout.hash == b2i(prevout_id) and tx.vin[0].prevout.n == prevout_n, 'Input prevout mismatch') +// +// ensure(len(tx.vout) == 1, 'tx doesn\'t have one output') +// +// script_pk = self.getScriptDest(script_out) +// locked_n = findOutput(tx, script_pk) +// ensure(locked_n is not None, 'Output not found in tx') +// locked_coin = tx.vout[locked_n].nValue +// +// # Check script and values +// A, B, csv_val, C = self.extractScriptLockRefundScriptValues(script_out) +// ensure(A == Kal, 'Bad script pubkey') +// ensure(B == Kaf, 'Bad script pubkey') +// ensure(csv_val == csv_val_expect, 'Bad script csv value') +// ensure(C == Kaf, 'Bad script pubkey') +// +// fee_paid = swap_value - locked_coin +// assert (fee_paid > 0) +// +// dummy_witness_stack = self.getScriptLockTxDummyWitness(prevout_script) +// witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) +// vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) +// fee_rate_paid = fee_paid * 1000 // vsize +// +// self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', locked_coin, vsize, fee_rate_paid) +// +// if not self.compareFeeRates(fee_rate_paid, feerate): +// raise ValueError('Bad fee rate, expected: {}'.format(feerate)) +// +// return txid, locked_coin, locked_n +// +// def verifySCLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes, +// lock_refund_tx_id, prevout_script, +// Kal, +// prevout_n, prevout_value, feerate, vkbv=None): +// # Verify: +// # Must have only one input with correct prevout (n is always 0) and sequence +// # Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr +// tx = self.loadTx(tx_bytes) +// txid = self.getTxid(tx) +// self._log.info('Verifying lock refund spend tx: {}.'.format(b2h(txid))) +// +// ensure(tx.nVersion == self.txVersion(), 'Bad version') +// ensure(tx.nLockTime == 0, 'nLockTime not 0') +// ensure(len(tx.vin) == 1, 'tx doesn\'t have one input') +// +// ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence') +// ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script), 'Input scriptsig mismatch') +// ensure(tx.vin[0].prevout.hash == b2i(lock_refund_tx_id) and tx.vin[0].prevout.n == 0, 'Input prevout mismatch') +// +// ensure(len(tx.vout) == 1, 'tx doesn\'t have one output') +// +// # Destination doesn't matter to the follower +// ''' +// p2wpkh = CScript([OP_0, hash160(Kal)]) +// locked_n = findOutput(tx, p2wpkh) +// ensure(locked_n is not None, 'Output not found in lock refund spend tx') +// ''' +// tx_value = tx.vout[0].nValue +// +// fee_paid = prevout_value - tx_value +// assert (fee_paid > 0) +// +// dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(prevout_script) +// witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) +// vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) +// fee_rate_paid = fee_paid * 1000 // vsize +// +// self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', tx_value, vsize, fee_rate_paid) +// +// if not self.compareFeeRates(fee_rate_paid, feerate): +// raise ValueError('Bad fee rate, expected: {}'.format(feerate)) +// +// return True +// +// def verifySCLockSpendTx(self, tx_bytes, +// lock_tx_bytes, lock_tx_script, +// a_pkhash_f, feerate, vkbv=None): +// # Verify: +// # Must have only one input with correct prevout (n is always 0) and sequence +// # Must have only one output with destination and amount +// +// tx = self.loadTx(tx_bytes) +// txid = self.getTxid(tx) +// self._log.info('Verifying lock spend tx: {}.'.format(b2h(txid))) +// +// ensure(tx.nVersion == self.txVersion(), 'Bad version') +// ensure(tx.nLockTime == 0, 'nLockTime not 0') +// ensure(len(tx.vin) == 1, 'tx doesn\'t have one input') +// +// lock_tx = self.loadTx(lock_tx_bytes) +// lock_tx_id = self.getTxid(lock_tx) +// +// output_script = self.getScriptDest(lock_tx_script) +// locked_n = findOutput(lock_tx, output_script) +// ensure(locked_n is not None, 'Output not found in tx') +// locked_coin = lock_tx.vout[locked_n].nValue +// +// ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence') +// ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(lock_tx_script), 'Input scriptsig mismatch') +// ensure(tx.vin[0].prevout.hash == b2i(lock_tx_id) and tx.vin[0].prevout.n == locked_n, 'Input prevout mismatch') +// +// ensure(len(tx.vout) == 1, 'tx doesn\'t have one output') +// p2wpkh = self.getScriptForPubkeyHash(a_pkhash_f) +// ensure(tx.vout[0].scriptPubKey == p2wpkh, 'Bad output destination') +// +// # The value of the lock tx output should already be verified, if the fee is as expected the difference will be the correct amount +// fee_paid = locked_coin - tx.vout[0].nValue +// assert (fee_paid > 0) +// +// dummy_witness_stack = self.getScriptLockTxDummyWitness(lock_tx_script) +// witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) +// vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) +// fee_rate_paid = fee_paid * 1000 // vsize +// +// self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', tx.vout[0].nValue, vsize, fee_rate_paid) +// +// if not self.compareFeeRates(fee_rate_paid, feerate): +// raise ValueError('Bad fee rate, expected: {}'.format(feerate)) +// +// return True diff --git a/internal/cmd/xmrswap/main.go b/internal/cmd/xmrswap/main.go new file mode 100644 index 0000000000..69619d072f --- /dev/null +++ b/internal/cmd/xmrswap/main.go @@ -0,0 +1,1221 @@ +//go:build libsecp256k1 + +package main + +import ( + "bytes" + "context" + "encoding/hex" + "errors" + "fmt" + "math" + "math/big" + "net/http" + "os" + "path/filepath" + "time" + + "decred.org/dcrdex/dex" + "decred.org/dcrdex/dex/config" + dcradaptor "decred.org/dcrdex/internal/adaptorsigs/dcr" + "decred.org/dcrdex/internal/libsecp256k1" + "decred.org/dcrwallet/v4/rpc/client/dcrwallet" + dcrwalletjson "decred.org/dcrwallet/v4/rpc/jsonrpc/types" + "github.com/agl/ed25519/edwards25519" + "github.com/decred/dcrd/chaincfg/chainhash" + "github.com/decred/dcrd/chaincfg/v3" + "github.com/decred/dcrd/dcrec" + "github.com/decred/dcrd/dcrec/edwards/v2" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/decred/dcrd/rpcclient/v8" + "github.com/decred/dcrd/txscript/v4" + "github.com/decred/dcrd/txscript/v4/sign" + "github.com/decred/dcrd/txscript/v4/stdaddr" + "github.com/decred/dcrd/wire" + "github.com/decred/slog" + "github.com/dev-warrior777/go-monero/rpc" + "github.com/fatih/color" + "github.com/haven-protocol-org/monero-go-utils/base58" +) + +// TODO: Verification at all stages has not been implemented yet. + +// fieldIntSize is the size of a field element encoded +// as bytes. +const ( + fieldIntSize = 32 + dcrAmt = 7_000_000 // atoms + xmrAmt = 1_000 // 1e12 units + dumbFee = int64(6000) +) + +var ( + homeDir = os.Getenv("HOME") + dextestDir = filepath.Join(homeDir, "dextest") + bobDir = filepath.Join(dextestDir, "xmr", "wallets", "bob") + curve = edwards.Edwards() +) + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if err := run(ctx); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + } +} + +type walletClient = dcrwallet.Client + +type combinedClient struct { + *rpcclient.Client + *walletClient + chainParams *chaincfg.Params +} + +func newCombinedClient(nodeRPCClient *rpcclient.Client, chainParams *chaincfg.Params) *combinedClient { + return &combinedClient{ + nodeRPCClient, + dcrwallet.NewClient(dcrwallet.RawRequestCaller(nodeRPCClient), chainParams), + chainParams, + } +} + +type client struct { + xmr *rpc.Client + dcr *combinedClient + + kbvf, kbsf, kbvl, kbsl, vkbv *edwards.PrivateKey + pkbsf, pkbs *edwards.PublicKey + kaf, kal *secp256k1.PrivateKey + pkal, pkaf, pkasl, pkbsl *secp256k1.PublicKey + kbsfDleag, kbslDleag [libsecp256k1.ProofLen]byte + lockTxEsig [libsecp256k1.CTLen]byte + lockTx *wire.MsgTx + vIn int +} + +func newRPCWallet(settings map[string]string, logger dex.Logger, net dex.Network) (*combinedClient, error) { + certs, err := os.ReadFile(settings["rpccert"]) + if err != nil { + return nil, fmt.Errorf("TLS certificate read error: %w", err) + } + + cfg := &rpcclient.ConnConfig{ + Host: settings["rpclisten"], + Endpoint: "ws", + User: settings["rpcuser"], + Pass: settings["rpcpass"], + Certificates: certs, + DisableConnectOnNew: true, // don't start until Connect + } + if cfg.User == "" { + cfg.User = "user" + } + if cfg.Pass == "" { + cfg.Pass = "pass" + } + + nodeRPCClient, err := rpcclient.New(cfg, nil) + if err != nil { + return nil, fmt.Errorf("error setting up rpc client: %w", err) + } + + var params *chaincfg.Params + switch net { + case dex.Simnet: + params = chaincfg.SimNetParams() + case dex.Testnet: + params = chaincfg.TestNet3Params() + case dex.Mainnet: + params = chaincfg.MainNetParams() + default: + return nil, fmt.Errorf("unknown network ID: %d", uint8(net)) + } + + return newCombinedClient(nodeRPCClient, params), nil +} + +func newClient(ctx context.Context, xmrAddr, dcrNode string) (*client, error) { + xmr := rpc.New(rpc.Config{ + Address: xmrAddr, + Client: &http.Client{}, + }) + + settings, err := config.Parse(filepath.Join(dextestDir, "dcr", dcrNode, fmt.Sprintf("%s.conf", dcrNode))) + if err != nil { + return nil, err + } + settings["account"] = "default" + + dcr, err := newRPCWallet(settings, dex.StdOutLogger(dcrNode, slog.LevelTrace), dex.Simnet) + if err != nil { + return nil, err + } + + err = dcr.Connect(ctx, false) + if err != nil { + return nil, err + } + + return &client{ + xmr: xmr, + dcr: dcr, + }, nil +} + +// reverse reverses a byte string. +func reverse(s *[32]byte) { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } +} + +// bigIntToEncodedBytes converts a big integer into its corresponding +// 32 byte little endian representation. +func bigIntToEncodedBytes(a *big.Int) *[32]byte { + s := new([32]byte) + if a == nil { + return s + } + // Caveat: a can be longer than 32 bytes. + aB := a.Bytes() + + // If we have a short byte string, expand + // it so that it's long enough. + aBLen := len(aB) + if aBLen < fieldIntSize { + diff := fieldIntSize - aBLen + for i := 0; i < diff; i++ { + aB = append([]byte{0x00}, aB...) + } + } + + for i := 0; i < fieldIntSize; i++ { + s[i] = aB[i] + } + + // Reverse the byte string --> little endian after + // encoding. + reverse(s) + + return s +} + +// encodedBytesToBigInt converts a 32 byte little endian representation of +// an integer into a big, big endian integer. +func encodedBytesToBigInt(s *[32]byte) *big.Int { + // Use a copy so we don't screw up our original + // memory. + sCopy := new([32]byte) + for i := 0; i < fieldIntSize; i++ { + sCopy[i] = s[i] + } + reverse(sCopy) + + bi := new(big.Int).SetBytes(sCopy[:]) + + return bi +} + +// scalarAdd adds two scalars. +func scalarAdd(a, b *big.Int) *big.Int { + feA := bigIntToFieldElement(a) + feB := bigIntToFieldElement(b) + sum := new(edwards25519.FieldElement) + + edwards25519.FeAdd(sum, feA, feB) + sumArray := new([32]byte) + edwards25519.FeToBytes(sumArray, sum) + + return encodedBytesToBigInt(sumArray) +} + +// bigIntToFieldElement converts a big little endian integer into its corresponding +// 40 byte field representation. +func bigIntToFieldElement(a *big.Int) *edwards25519.FieldElement { + aB := bigIntToEncodedBytes(a) + fe := new(edwards25519.FieldElement) + edwards25519.FeFromBytes(fe, aB) + return fe +} + +func sumPubKeys(pubA, pubB *edwards.PublicKey) *edwards.PublicKey { + pkSumX, pkSumY := curve.Add(pubA.GetX(), pubA.GetY(), pubB.GetX(), pubB.GetY()) + return edwards.NewPublicKey(pkSumX, pkSumY) +} + +// Convert the DCR value to atoms. +func toAtoms(v float64) uint64 { + return uint64(math.Round(v * 1e8)) +} + +// createNewXMRWallet uses the "own" wallet to create a new xmr wallet from keys +// and open it. Can only create one wallet at a time. +func createNewXMRWallet(ctx context.Context, genReq rpc.GenerateFromKeysRequest) (*rpc.Client, error) { + xmrChecker := rpc.New(rpc.Config{ + Address: "http://127.0.0.1:28484/json_rpc", + Client: &http.Client{}, + }) + + _, err := xmrChecker.GenerateFromKeys(ctx, &genReq) + if err != nil { + return nil, fmt.Errorf("unable to generate wallet: %v", err) + } + + openReq := rpc.OpenWalletRequest{ + Filename: genReq.Filename, + } + + err = xmrChecker.OpenWallet(ctx, &openReq) + if err != nil { + return nil, err + } + return xmrChecker, nil +} + +type prettyLogger struct { + c *color.Color +} + +func (cl prettyLogger) Write(p []byte) (n int, err error) { + return cl.c.Fprint(os.Stdout, string(p)) +} + +func run(ctx context.Context) error { + pl := prettyLogger{c: color.New(color.FgGreen)} + log := dex.NewLogger("T", dex.LevelInfo, pl) + + log.Info("Running success.") + if err := success(ctx); err != nil { + return err + } + log.Info("Success completed without error.") + log.Info("------------------") + log.Info("Running alice bails before xmr init.") + if err := aliceBailsBeforeXmrInit(ctx); err != nil { + return err + } + log.Info("Alice bails before xmr init completed without error.") + log.Info("------------------") + log.Info("Running refund.") + if err := refund(ctx); err != nil { + return err + } + log.Info("Refund completed without error.") + log.Info("------------------") + log.Info("Running bob bails after xmr init.") + if err := bobBailsAfterXmrInit(ctx); err != nil { + return err + } + log.Info("Bob bails after xmr init completed without error.") + return nil +} + +// generateDleag starts the trade by creating some keys. +func (c *client) generateDleag(ctx context.Context) (pkbsf *edwards.PublicKey, kbvf *edwards.PrivateKey, + pkaf *secp256k1.PublicKey, dleag [libsecp256k1.ProofLen]byte, err error) { + fail := func(err error) (*edwards.PublicKey, *edwards.PrivateKey, + *secp256k1.PublicKey, [libsecp256k1.ProofLen]byte, error) { + return nil, nil, nil, [libsecp256k1.ProofLen]byte{}, err + } + // This private key is shared with bob and becomes half of the view key. + c.kbvf, err = edwards.GeneratePrivateKey() + if err != nil { + return fail(err) + } + + // Not shared. Becomes half the spend key. The pubkey is shared. + c.kbsf, err = edwards.GeneratePrivateKey() + if err != nil { + return fail(err) + } + c.pkbsf = c.kbsf.PubKey() + + // Not shared. This is used for all dcr signatures. Using a wallet + // address because funds may go here in the case of success. Any address + // would work for the spendTx though. + kafAddr, err := c.dcr.GetNewAddress(ctx, "default") + if err != nil { + return fail(err) + } + kafWIF, err := c.dcr.DumpPrivKey(ctx, kafAddr) + if err != nil { + return fail(err) + } + c.kaf = secp256k1.PrivKeyFromBytes(kafWIF.PrivKey()) + + // Share this pubkey with the other party. + c.pkaf = c.kaf.PubKey() + + c.kbsfDleag, err = libsecp256k1.Ed25519DleagProve(c.kbsf) + if err != nil { + return fail(err) + } + + c.pkasl, err = secp256k1.ParsePubKey(c.kbsfDleag[:33]) + if err != nil { + return fail(err) + } + + return c.pkbsf, c.kbvf, c.pkaf, c.kbsfDleag, nil +} + +// generateLockTxn creates even more keys and some transactions. +func (c *client) generateLockTxn(ctx context.Context, pkbsf *edwards.PublicKey, + kbvf *edwards.PrivateKey, pkaf *secp256k1.PublicKey, kbsfDleag [libsecp256k1.ProofLen]byte) (refundSig, + lockRefundTxScript, lockTxScript []byte, refundTx, spendRefundTx *wire.MsgTx, lockTxVout int, + pkbs *edwards.PublicKey, vkbv *edwards.PrivateKey, dleag [libsecp256k1.ProofLen]byte, err error) { + fail := func(err error) ([]byte, []byte, []byte, *wire.MsgTx, *wire.MsgTx, int, *edwards.PublicKey, *edwards.PrivateKey, [libsecp256k1.ProofLen]byte, error) { + return nil, nil, nil, nil, nil, 0, nil, nil, [libsecp256k1.ProofLen]byte{}, err + } + c.kbsfDleag = kbsfDleag + c.pkasl, err = secp256k1.ParsePubKey(c.kbsfDleag[:33]) + if err != nil { + return fail(err) + } + c.kbvf = kbvf + c.pkbsf = pkbsf + c.pkaf = pkaf + + // This becomes the other half of the view key. + c.kbvl, err = edwards.GeneratePrivateKey() + if err != nil { + return fail(err) + } + + // This becomes the other half of the spend key and is shared. + c.kbsl, err = edwards.GeneratePrivateKey() + if err != nil { + return fail(err) + } + + // This kept private. This is used for all dcr signatures. + c.kal, err = secp256k1.GeneratePrivateKey() + if err != nil { + return fail(err) + } + + pkal := c.kal.PubKey() + + // This is the full xmr view key and is shared. Alice can also calculate + // it using kbvl. + vkbvBig := scalarAdd(c.kbvf.GetD(), c.kbvl.GetD()) + vkbvBig.Mod(vkbvBig, curve.N) + var vkbvBytes [32]byte + vkbvBig.FillBytes(vkbvBytes[:]) + c.vkbv, _, err = edwards.PrivKeyFromScalar(vkbvBytes[:]) + if err != nil { + return fail(fmt.Errorf("unable to create vkbv: %v", err)) + } + + // The public key for the xmr spend key. No party knows the full private + // key yet. + c.pkbs = sumPubKeys(c.kbsl.PubKey(), c.pkbsf) + + // The lock tx is the initial dcr transaction. + lockTxScript, err = dcradaptor.LockTxScript(pkal.SerializeCompressed(), c.pkaf.SerializeCompressed()) + if err != nil { + return fail(err) + } + + scriptAddr, err := stdaddr.NewAddressScriptHashV0(lockTxScript, c.dcr.chainParams) + if err != nil { + return fail(fmt.Errorf("error encoding script address: %w", err)) + } + p2shLockScriptVer, p2shLockScript := scriptAddr.PaymentScript() + // Add the transaction output. + txOut := &wire.TxOut{ + Value: dcrAmt, + Version: p2shLockScriptVer, + PkScript: p2shLockScript, + } + c.lockTx = wire.NewMsgTx() + c.lockTx.AddTxOut(txOut) + txBytes, err := c.lockTx.Bytes() + if err != nil { + return fail(err) + } + + fundRes, err := c.dcr.FundRawTransaction(ctx, hex.EncodeToString(txBytes), "default", dcrwalletjson.FundRawTransactionOptions{}) + if err != nil { + return fail(err) + } + + txBytes, err = hex.DecodeString(fundRes.Hex) + if err != nil { + return fail(err) + } + + c.lockTx = wire.NewMsgTx() + if err = c.lockTx.FromBytes(txBytes); err != nil { + return fail(err) + } + for i, out := range c.lockTx.TxOut { + if bytes.Equal(out.PkScript, p2shLockScript) { + c.vIn = i + break + } + } + + durationLocktime := int64(2) // blocks + // Unable to use time for tests as this is multiples of 512 seconds. + // durationLocktime := int64(10) // seconds * 512 + // durationLocktime |= wire.SequenceLockTimeIsSeconds + + // The refund tx does not outright refund but moves funds to the refund + // script's address. This is signed by both parties before the initial tx. + lockRefundTxScript, err = dcradaptor.LockRefundTxScript(pkal.SerializeCompressed(), c.pkaf.SerializeCompressed(), durationLocktime) + if err != nil { + return fail(err) + } + + scriptAddr, err = stdaddr.NewAddressScriptHashV0(lockRefundTxScript, c.dcr.chainParams) + if err != nil { + return fail(fmt.Errorf("error encoding script address: %w", err)) + } + p2shScriptVer, p2shScript := scriptAddr.PaymentScript() + txOut = &wire.TxOut{ + Value: dcrAmt - dumbFee, + Version: p2shScriptVer, + PkScript: p2shScript, + } + refundTx = wire.NewMsgTx() + refundTx.AddTxOut(txOut) + h := c.lockTx.TxHash() + op := wire.NewOutPoint(&h, uint32(c.vIn), 0) + txIn := wire.NewTxIn(op, dcrAmt, nil) + refundTx.AddTxIn(txIn) + + // This sig must be shared with Alice. + refundSig, err = sign.RawTxInSignature(refundTx, c.vIn, lockTxScript, txscript.SigHashAll, c.kal.Serialize(), dcrec.STEcdsaSecp256k1) + if err != nil { + return fail(err) + } + + // SpendRefundTx is used in the final refund. Alice can sign it after a + // time and send wherever. Bob must use a signature that will review his + // half of the xmr key. + newAddr, err := c.dcr.GetNewAddress(ctx, "default") + if err != nil { + return fail(err) + } + p2AddrScriptVer, p2AddrScript := newAddr.PaymentScript() + txOut = &wire.TxOut{ + Value: dcrAmt - dumbFee - dumbFee, + Version: p2AddrScriptVer, + PkScript: p2AddrScript, + } + spendRefundTx = wire.NewMsgTx() + spendRefundTx.AddTxOut(txOut) + h = refundTx.TxHash() + op = wire.NewOutPoint(&h, 0, 0) + txIn = wire.NewTxIn(op, dcrAmt, nil) + txIn.Sequence = uint32(durationLocktime) + spendRefundTx.AddTxIn(txIn) + spendRefundTx.Version = wire.TxVersionTreasury + + c.kbslDleag, err = libsecp256k1.Ed25519DleagProve(c.kbsl) + if err != nil { + return fail(err) + } + c.pkbsl, err = secp256k1.ParsePubKey(c.kbslDleag[:33]) + if err != nil { + return fail(err) + } + + return refundSig, lockRefundTxScript, lockTxScript, refundTx, spendRefundTx, c.vIn, c.pkbs, c.vkbv, c.kbslDleag, nil +} + +// generateRefundSigs signs the refund tx and shares the spendRefund esig that +// allows bob to spend the refund tx. +func (c *client) generateRefundSigs(refundTx, spendRefundTx *wire.MsgTx, vIn int, lockTxScript, lockRefundTxScript []byte, dleag [libsecp256k1.ProofLen]byte) (esig [libsecp256k1.CTLen]byte, refundSig []byte, err error) { + fail := func(err error) ([libsecp256k1.CTLen]byte, []byte, error) { + return [libsecp256k1.CTLen]byte{}, nil, err + } + c.kbslDleag = dleag + c.vIn = vIn + c.pkbsl, err = secp256k1.ParsePubKey(c.kbslDleag[:33]) + if err != nil { + return fail(err) + } + + hash, err := txscript.CalcSignatureHash(lockRefundTxScript, txscript.SigHashAll, spendRefundTx, 0, nil) + if err != nil { + return fail(err) + } + + var h chainhash.Hash + copy(h[:], hash) + esig, err = libsecp256k1.EcdsaotvesEncSign(c.kaf, c.pkbsl, h) + if err != nil { + return fail(err) + } + + // Share with bob. + refundSig, err = sign.RawTxInSignature(refundTx, c.vIn, lockTxScript, txscript.SigHashAll, c.kaf.Serialize(), dcrec.STEcdsaSecp256k1) + if err != nil { + return fail(err) + } + + return esig, refundSig, nil +} + +// initDcr is the first transaction to happen and creates a dcr transaction. +func (c *client) initDcr(ctx context.Context) (spendTx *wire.MsgTx, err error) { + fail := func(err error) (*wire.MsgTx, error) { + return nil, err + } + pkaslAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1(0, stdaddr.Hash160(c.pkaf.SerializeCompressed()), c.dcr.chainParams) + if err != nil { + return fail(err) + } + p2AddrScriptVer, p2AddrScript := pkaslAddr.PaymentScript() + + txOut := &wire.TxOut{ + Value: dcrAmt - dumbFee, + Version: p2AddrScriptVer, + PkScript: p2AddrScript, + } + spendTx = wire.NewMsgTx() + spendTx.AddTxOut(txOut) + h := c.lockTx.TxHash() + op := wire.NewOutPoint(&h, uint32(c.vIn), 0) + txIn := wire.NewTxIn(op, dcrAmt, nil) + spendTx.AddTxIn(txIn) + + tx, complete, err := c.dcr.SignRawTransaction(ctx, c.lockTx) + if err != nil { + return fail(err) + } + if !complete { + return fail(errors.New("lock tx sign not complete")) + } + + _, err = c.dcr.SendRawTransaction(ctx, tx, false) + if err != nil { + return fail(fmt.Errorf("unable to send lock tx: %v", err)) + } + return spendTx, nil +} + +// initXmr sends an xmr transaciton. Alice can only do this after confirming the +// dcr transaction. +func (c *client) initXmr(ctx context.Context, vkbv *edwards.PrivateKey, pkbs *edwards.PublicKey) error { + c.vkbv = vkbv + c.pkbs = pkbs + var fullPubKey []byte + fullPubKey = append(fullPubKey, c.pkbs.SerializeCompressed()...) + fullPubKey = append(fullPubKey, c.vkbv.PubKey().SerializeCompressed()...) + + sharedAddr := base58.EncodeAddr(18, fullPubKey) + + dest := rpc.Destination{ + Amount: xmrAmt, + Address: string(sharedAddr), + } + sendReq := rpc.TransferRequest{ + Destinations: []rpc.Destination{dest}, + } + + sendRes, err := c.xmr.Transfer(ctx, &sendReq) + if err != nil { + return err + } + fmt.Printf("xmr sent\n%+v\n", *sendRes) + return nil +} + +// sendLockTxSig allows Alice to redeem the dcr. If bob does not send this alice +// can eventually take his btc. Otherwise bob refunding will reveal his half of +// the xmr spend key allowing Alice to refund. +func (c *client) sendLockTxSig(lockTxScript []byte, spendTx *wire.MsgTx) (esig [libsecp256k1.CTLen]byte, err error) { + hash, err := txscript.CalcSignatureHash(lockTxScript, txscript.SigHashAll, spendTx, 0, nil) + if err != nil { + return [libsecp256k1.CTLen]byte{}, err + } + + var h chainhash.Hash + copy(h[:], hash) + + esig, err = libsecp256k1.EcdsaotvesEncSign(c.kal, c.pkasl, h) + if err != nil { + return [libsecp256k1.CTLen]byte{}, err + } + c.lockTxEsig = esig + return esig, nil +} + +// redeemDcr redeems the dcr, revealing a signature that reveals half of the xmr +// spend key. +func (c *client) redeemDcr(ctx context.Context, esig [libsecp256k1.CTLen]byte, lockTxScript []byte, spendTx *wire.MsgTx) (kalSig []byte, err error) { + kasl := secp256k1.PrivKeyFromBytes(c.kbsf.Serialize()) + kalSig, err = libsecp256k1.EcdsaotvesDecSig(kasl, esig) + if err != nil { + return nil, err + } + kalSig = append(kalSig, byte(txscript.SigHashAll)) + + kafSig, err := sign.RawTxInSignature(spendTx, 0, lockTxScript, txscript.SigHashAll, c.kaf.Serialize(), dcrec.STEcdsaSecp256k1) + if err != nil { + return nil, err + } + + spendSig, err := txscript.NewScriptBuilder(). + AddData(kalSig). + AddData(kafSig). + AddData(lockTxScript). + Script() + + spendTx.TxIn[0].SignatureScript = spendSig + + tx, err := c.dcr.SendRawTransaction(ctx, spendTx, false) + if err != nil { + return nil, err + } + fmt.Println(tx) + + return kalSig, nil +} + +// redeemXmr redeems xmr by creating a new xmr wallet with the complete spend +// and view private keys. +func (c *client) redeemXmr(ctx context.Context, kalSig []byte) (*rpc.Client, error) { + kaslRecovered, err := libsecp256k1.EcdsaotvesRecEncKey(c.pkasl, c.lockTxEsig, kalSig[:len(kalSig)-1]) + if err != nil { + return nil, err + } + + kbsfRecovered, _, err := edwards.PrivKeyFromScalar(kaslRecovered.Serialize()) + if err != nil { + return nil, fmt.Errorf("unable to recover kbsf: %v", err) + } + vkbsBig := scalarAdd(c.kbsl.GetD(), kbsfRecovered.GetD()) + vkbsBig.Mod(vkbsBig, curve.N) + var vkbsBytes [32]byte + vkbsBig.FillBytes(vkbsBytes[:]) + vkbs, _, err := edwards.PrivKeyFromScalar(vkbsBytes[:]) + if err != nil { + return nil, fmt.Errorf("unable to create vkbs: %v", err) + } + + var fullPubKey []byte + fullPubKey = append(fullPubKey, vkbs.PubKey().Serialize()...) + fullPubKey = append(fullPubKey, c.vkbv.PubKey().Serialize()...) + walletAddr := base58.EncodeAddr(18, fullPubKey) + walletFileName := fmt.Sprintf("%s_spend", walletAddr) + + var vkbvBytes [32]byte + copy(vkbvBytes[:], c.vkbv.Serialize()) + + reverse(&vkbsBytes) + reverse(&vkbvBytes) + + genReq := rpc.GenerateFromKeysRequest{ + Filename: walletFileName, + Address: walletAddr, + SpendKey: hex.EncodeToString(vkbsBytes[:]), + ViewKey: hex.EncodeToString(vkbvBytes[:]), + } + + xmrChecker, err := createNewXMRWallet(ctx, genReq) + if err != nil { + return nil, err + } + + return xmrChecker, nil +} + +// startRefund starts the refund and can be done by either party. +func (c *client) startRefund(ctx context.Context, kalSig, kafSig, lockTxScript []byte, refundTx *wire.MsgTx) error { + refundSig, err := txscript.NewScriptBuilder(). + AddData(kalSig). + AddData(kafSig). + AddData(lockTxScript). + Script() + + refundTx.TxIn[0].SignatureScript = refundSig + + _, err = c.dcr.SendRawTransaction(ctx, refundTx, false) + if err != nil { + return err + } + return nil +} + +// refundDcr returns dcr to bob while revealing his half of the xmr spend key. +func (c *client) refundDcr(ctx context.Context, spendRefundTx *wire.MsgTx, esig [libsecp256k1.CTLen]byte, lockRefundTxScript []byte) (kafSig []byte, err error) { + kasf := secp256k1.PrivKeyFromBytes(c.kbsl.Serialize()) + kafSig, err = libsecp256k1.EcdsaotvesDecSig(kasf, esig) + if err != nil { + return nil, err + } + kafSig = append(kafSig, byte(txscript.SigHashAll)) + + kalSig, err := sign.RawTxInSignature(spendRefundTx, 0, lockRefundTxScript, txscript.SigHashAll, c.kal.Serialize(), dcrec.STEcdsaSecp256k1) + if err != nil { + return nil, err + } + refundSig, err := txscript.NewScriptBuilder(). + AddData(kalSig). + AddData(kafSig). + AddOp(txscript.OP_TRUE). + AddData(lockRefundTxScript). + Script() + + spendRefundTx.TxIn[0].SignatureScript = refundSig + + _, err = c.dcr.SendRawTransaction(ctx, spendRefundTx, false) + if err != nil { + return nil, err + } + // TODO: Confirm refund happened. + return kafSig, nil +} + +// refundXmr refunds xmr but cannot happen without the dcr refund happening first. +func (c *client) refundXmr(ctx context.Context, kafSig []byte, esig [libsecp256k1.CTLen]byte) (*rpc.Client, error) { + kbslRecovered, err := libsecp256k1.EcdsaotvesRecEncKey(c.pkbsl, esig, kafSig[:len(kafSig)-1]) + if err != nil { + return nil, err + } + + kaslRecovered, _, err := edwards.PrivKeyFromScalar(kbslRecovered.Serialize()) + if err != nil { + return nil, fmt.Errorf("unable to recover kasl: %v", err) + } + vkbsBig := scalarAdd(c.kbsf.GetD(), kaslRecovered.GetD()) + vkbsBig.Mod(vkbsBig, curve.N) + var vkbsBytes [32]byte + vkbsBig.FillBytes(vkbsBytes[:]) + vkbs, _, err := edwards.PrivKeyFromScalar(vkbsBytes[:]) + if err != nil { + return nil, fmt.Errorf("unable to create vkbs: %v", err) + } + + var fullPubKey []byte + fullPubKey = append(fullPubKey, vkbs.PubKey().Serialize()...) + fullPubKey = append(fullPubKey, c.vkbv.PubKey().Serialize()...) + walletAddr := base58.EncodeAddr(18, fullPubKey) + walletFileName := fmt.Sprintf("%s_spend", walletAddr) + + var vkbvBytes [32]byte + copy(vkbvBytes[:], c.vkbv.Serialize()) + + reverse(&vkbsBytes) + reverse(&vkbvBytes) + + genReq := rpc.GenerateFromKeysRequest{ + Filename: walletFileName, + Address: walletAddr, + SpendKey: hex.EncodeToString(vkbsBytes[:]), + ViewKey: hex.EncodeToString(vkbvBytes[:]), + } + + xmrChecker, err := createNewXMRWallet(ctx, genReq) + if err != nil { + return nil, err + } + + return xmrChecker, nil +} + +// takeDcr is the punish if Bob takes too long. Alice gets the dcr while bob +// gets nothing. +func (c *client) takeDcr(ctx context.Context, lockRefundTxScript []byte, spendRefundTx *wire.MsgTx) (err error) { + newAddr, err := c.dcr.GetNewAddress(ctx, "default") + if err != nil { + return err + } + p2AddrScriptVer, p2AddrScript := newAddr.PaymentScript() + txOut := &wire.TxOut{ + Value: dcrAmt - dumbFee - dumbFee, + Version: p2AddrScriptVer, + PkScript: p2AddrScript, + } + spendRefundTx.TxOut[0] = txOut + + kafSig, err := sign.RawTxInSignature(spendRefundTx, 0, lockRefundTxScript, txscript.SigHashAll, c.kaf.Serialize(), dcrec.STEcdsaSecp256k1) + if err != nil { + return err + } + refundSig, err := txscript.NewScriptBuilder(). + AddData(kafSig). + AddOp(txscript.OP_FALSE). + AddData(lockRefundTxScript). + Script() + + spendRefundTx.TxIn[0].SignatureScript = refundSig + + _, err = c.dcr.SendRawTransaction(ctx, spendRefundTx, false) + if err != nil { + return err + } + // TODO: Confirm refund happened. + return nil +} + +// success is a successful trade. +func success(ctx context.Context) error { + alice, err := newClient(ctx, "http://127.0.0.1:28284/json_rpc", "trading1") + if err != nil { + return err + } + balReq := rpc.GetBalanceRequest{ + AccountIndex: 0, + } + xmrBal, err := alice.xmr.GetBalance(ctx, &balReq) + if err != nil { + return err + } + fmt.Printf("alice xmr balance\n%+v\n", *xmrBal) + + dcrBal, err := alice.dcr.GetBalance(ctx, "default") + if err != nil { + return err + } + dcrBeforeBal := toAtoms(dcrBal.Balances[0].Total) + fmt.Printf("alice dcr balance %v\n", dcrBeforeBal) + + bob, err := newClient(ctx, "http://127.0.0.1:28184/json_rpc", "trading2") + if err != nil { + return err + } + + // Alice generates dleag. + + pkbsf, kbvf, pkaf, aliceDleag, err := alice.generateDleag(ctx) + if err != nil { + return err + } + + // Bob generates transactions but does not send anything yet. + + _, lockRefundTxScript, lockTxScript, refundTx, spendRefundTx, vIn, pkbs, vkbv, bobDleag, err := bob.generateLockTxn(ctx, pkbsf, kbvf, pkaf, aliceDleag) + if err != nil { + return fmt.Errorf("unalbe to generate lock transactions: %v", err) + } + + // Alice signs a refund script for Bob. + + _, _, err = alice.generateRefundSigs(refundTx, spendRefundTx, vIn, lockTxScript, lockRefundTxScript, bobDleag) + if err != nil { + return err + } + + // Bob initializes the swap with dcr being sent. + + spendTx, err := bob.initDcr(ctx) + if err != nil { + return err + } + + // Alice inits her monero side. + if err := alice.initXmr(ctx, vkbv, pkbs); err != nil { + return err + } + + time.Sleep(time.Second * 5) + + // Bob sends esig after confirming on chain xmr tx. + + bobEsig, err := bob.sendLockTxSig(lockTxScript, spendTx) + if err != nil { + return err + } + + // Alice redeems using the esig. + kalSig, err := alice.redeemDcr(ctx, bobEsig, lockTxScript, spendTx) + if err != nil { + return err + } + + // Prove that bob can't just sign the spend tx for the signature we need. + ks, err := sign.RawTxInSignature(spendTx, 0, lockTxScript, txscript.SigHashAll, bob.kal.Serialize(), dcrec.STEcdsaSecp256k1) + if err != nil { + return err + } + if bytes.Equal(ks, kalSig) { + return errors.New("bob was able to get the correct sig without alice") + } + + // Bob redeems the xmr with the dcr signature. + xmrChecker, err := bob.redeemXmr(ctx, kalSig) + if err != nil { + return err + } + + // NOTE: This wallet must sync so may take a long time on mainnet. + // TODO: Wait for wallet sync rather than a dumb sleep. + time.Sleep(time.Second * 40) + + xmrBal, err = xmrChecker.GetBalance(ctx, &balReq) + if err != nil { + return err + } + if xmrBal.Balance != xmrAmt { + return fmt.Errorf("expected redeem xmr balance of %d but got %d", xmrAmt, xmrBal.Balance) + } + + dcrBal, err = alice.dcr.GetBalance(ctx, "default") + if err != nil { + return err + } + dcrAfterBal := toAtoms(dcrBal.Balances[0].Total) + wantBal := dcrBeforeBal + dcrAmt - uint64(dumbFee) + if wantBal != dcrAfterBal { + return fmt.Errorf("expected alice balance to be %d but got %d", wantBal, dcrAfterBal) + } + + return nil +} + +// aliceBailsBeforeXmrInit is a trade that fails because alice does nothing after +// Bob inits. +func aliceBailsBeforeXmrInit(ctx context.Context) error { + alice, err := newClient(ctx, "http://127.0.0.1:28284/json_rpc", "trading1") + if err != nil { + return err + } + + bob, err := newClient(ctx, "http://127.0.0.1:28184/json_rpc", "trading2") + if err != nil { + return err + } + + dcrBal, err := bob.dcr.GetBalance(ctx, "default") + if err != nil { + return err + } + dcrBeforeBal := toAtoms(dcrBal.Balances[0].Total) + + // Alice generates dleag. + + pkbsf, kbvf, pkaf, aliceDleag, err := alice.generateDleag(ctx) + if err != nil { + return err + } + + // Bob generates transactions but does not send anything yet. + + bobRefundSig, lockRefundTxScript, lockTxScript, refundTx, spendRefundTx, vIn, _, _, bobDleag, err := bob.generateLockTxn(ctx, pkbsf, kbvf, pkaf, aliceDleag) + if err != nil { + return fmt.Errorf("unalbe to generate lock transactions: %v", err) + } + + // Alice signs a refund script for Bob. + + spendRefundESig, aliceRefundSig, err := alice.generateRefundSigs(refundTx, spendRefundTx, vIn, lockTxScript, lockRefundTxScript, bobDleag) + if err != nil { + return err + } + + // Bob initializes the swap with dcr being sent. + + _, err = bob.initDcr(ctx) + if err != nil { + return err + } + + time.Sleep(time.Second * 5) + + // Bob starts the refund. + if err := bob.startRefund(ctx, bobRefundSig, aliceRefundSig, lockTxScript, refundTx); err != nil { + return err + } + + time.Sleep(time.Second * 5) + + // Bob refunds. + _, err = bob.refundDcr(ctx, spendRefundTx, spendRefundESig, lockRefundTxScript) + if err != nil { + return err + } + + time.Sleep(time.Second * 5) + + dcrBal, err = bob.dcr.GetBalance(ctx, "default") + if err != nil { + return err + } + + var initFee uint64 + for _, input := range bob.lockTx.TxIn { + initFee += uint64(input.ValueIn) + } + for _, output := range bob.lockTx.TxOut { + initFee -= uint64(output.Value) + } + + dcrAfterBal := toAtoms(dcrBal.Balances[0].Total) + wantBal := dcrBeforeBal - initFee - uint64(dumbFee)*2 + if wantBal != dcrAfterBal { + return fmt.Errorf("expected bob balance to be %d but got %d", wantBal, dcrAfterBal) + } + + return nil +} + +// refund is a failed trade where both parties have sent their initial funds and +// both get them back minus fees. +func refund(ctx context.Context) error { + alice, err := newClient(ctx, "http://127.0.0.1:28284/json_rpc", "trading1") + if err != nil { + return err + } + + bob, err := newClient(ctx, "http://127.0.0.1:28184/json_rpc", "trading2") + if err != nil { + return err + } + + // Alice generates dleag. + + pkbsf, kbvf, pkaf, aliceDleag, err := alice.generateDleag(ctx) + if err != nil { + return err + } + + // Bob generates transactions but does not send anything yet. + + bobRefundSig, lockRefundTxScript, lockTxScript, refundTx, spendRefundTx, vIn, pkbs, vkbv, bobDleag, err := bob.generateLockTxn(ctx, pkbsf, kbvf, pkaf, aliceDleag) + if err != nil { + return fmt.Errorf("unalbe to generate lock transactions: %v", err) + } + + // Alice signs a refund script for Bob. + + spendRefundESig, aliceRefundSig, err := alice.generateRefundSigs(refundTx, spendRefundTx, vIn, lockTxScript, lockRefundTxScript, bobDleag) + if err != nil { + return err + } + + // Bob initializes the swap with dcr being sent. + + _, err = bob.initDcr(ctx) + if err != nil { + return err + } + + // Alice inits her monero side. + if err := alice.initXmr(ctx, vkbv, pkbs); err != nil { + return err + } + + time.Sleep(time.Second * 5) + + // Bob starts the refund. + if err := bob.startRefund(ctx, bobRefundSig, aliceRefundSig, lockTxScript, refundTx); err != nil { + return err + } + + time.Sleep(time.Second * 5) + + // Bob refunds. + kafSig, err := bob.refundDcr(ctx, spendRefundTx, spendRefundESig, lockRefundTxScript) + if err != nil { + return err + } + + // Alice refunds. + xmrChecker, err := alice.refundXmr(ctx, kafSig, spendRefundESig) + if err != nil { + return err + } + + // NOTE: This wallet must sync so may take a long time on mainnet. + // TODO: Wait for wallet sync rather than a dumb sleep. + time.Sleep(time.Second * 40) + + balReq := rpc.GetBalanceRequest{} + bal, err := xmrChecker.GetBalance(ctx, &balReq) + if err != nil { + return err + } + if bal.Balance != xmrAmt { + return fmt.Errorf("expected refund xmr balance of %d but got %d", xmrAmt, bal.Balance) + } + fmt.Printf("new xmr wallet balance\n%+v\n", *bal) + + return nil +} + +// bobBailsAfterXmrInit is a failed trade where bob disappears after both parties +// init and alice takes all his dcr while losing her xmr. Bob gets nothing. +func bobBailsAfterXmrInit(ctx context.Context) error { + alice, err := newClient(ctx, "http://127.0.0.1:28284/json_rpc", "trading1") + if err != nil { + return err + } + + bob, err := newClient(ctx, "http://127.0.0.1:28184/json_rpc", "trading2") + if err != nil { + return err + } + + dcrBal, err := alice.dcr.GetBalance(ctx, "default") + if err != nil { + return err + } + dcrBeforeBal := toAtoms(dcrBal.Balances[0].Total) + + // Alice generates dleag. + + pkbsf, kbvf, pkaf, aliceDleag, err := alice.generateDleag(ctx) + if err != nil { + return err + } + + // Bob generates transactions but does not send anything yet. + + bobRefundSig, lockRefundTxScript, lockTxScript, refundTx, spendRefundTx, vIn, pkbs, vkbv, bobDleag, err := bob.generateLockTxn(ctx, pkbsf, kbvf, pkaf, aliceDleag) + if err != nil { + return fmt.Errorf("unalbe to generate lock transactions: %v", err) + } + + // Alice signs a refund script for Bob. + + _, aliceRefundSig, err := alice.generateRefundSigs(refundTx, spendRefundTx, vIn, lockTxScript, lockRefundTxScript, bobDleag) + if err != nil { + return err + } + + // Bob initializes the swap with dcr being sent. + + _, err = bob.initDcr(ctx) + if err != nil { + return err + } + + // Alice inits her monero side. + if err := alice.initXmr(ctx, vkbv, pkbs); err != nil { + return err + } + + time.Sleep(time.Second * 5) + + // Alice starts the refund. + if err := alice.startRefund(ctx, bobRefundSig, aliceRefundSig, lockTxScript, refundTx); err != nil { + return err + } + + // Lessen this sleep for failure. Two blocks must be mined for success. + time.Sleep(time.Second * 35) + + if err := alice.takeDcr(ctx, lockRefundTxScript, spendRefundTx); err != nil { + return err + } + + time.Sleep(time.Second * 5) + + dcrBal, err = alice.dcr.GetBalance(ctx, "default") + if err != nil { + return err + } + + dcrAfterBal := toAtoms(dcrBal.Balances[0].Total) + wantBal := dcrBeforeBal + dcrAmt - uint64(dumbFee)*2 + if wantBal != dcrAfterBal { + return fmt.Errorf("expected alice balance to be %d but got %d", wantBal, dcrAfterBal) + } + return nil +}