Release v0.3.0
This commit is contained in:
parent
4e9aa7a3c5
commit
8107c4478b
13
go.mod
13
go.mod
|
@ -3,13 +3,14 @@ module github.com/muun/recovery_tool
|
|||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/btcsuite/btcd v0.20.1-beta
|
||||
github.com/btcsuite/btcd v0.21.0-beta
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
|
||||
github.com/btcsuite/btcwallet v0.10.0
|
||||
github.com/btcsuite/btcwallet/walletdb v1.1.0
|
||||
github.com/lightninglabs/neutrino v0.10.0
|
||||
github.com/muun/libwallet v0.2.0
|
||||
github.com/btcsuite/btcutil v1.0.2
|
||||
github.com/btcsuite/btcwallet v0.11.1-0.20200612012534-48addcd5591a
|
||||
github.com/btcsuite/btcwallet/walletdb v1.3.3
|
||||
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200
|
||||
github.com/muun/libwallet v0.5.0
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/lightninglabs/neutrino => github.com/muun/neutrino v0.0.0-20190914162326-7082af0fa257
|
||||
|
|
179
go.sum
179
go.sum
|
@ -1,14 +1,21 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ=
|
||||
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
|
||||
|
@ -16,49 +23,87 @@ github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcug
|
|||
github.com/btcsuite/btcd v0.20.0-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.20.1-beta.0.20200513120220-b470eee47728/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46/go.mod h1:Yktc19YNjh/Iz2//CX0vfRTS4IJKM/RKO5YZ9Fn+Pgo=
|
||||
github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M=
|
||||
github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
|
||||
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
|
||||
github.com/btcsuite/btcutil/psbt v1.0.2 h1:gCVY3KxdoEVU7Q6TjusPO+GANIwVgr9yTLqM+a6CZr8=
|
||||
github.com/btcsuite/btcutil/psbt v1.0.2/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ=
|
||||
github.com/btcsuite/btcwallet v0.10.0 h1:fFZncfYJ7VByePTGttzJc3qfCyDzU95ucZYk0M912lU=
|
||||
github.com/btcsuite/btcwallet v0.10.0/go.mod h1:4TqBEuceheGNdeLNrelliLHJzmXauMM2vtWfuy1pFiM=
|
||||
github.com/btcsuite/btcwallet v0.10.1-0.20191109031858-c49e7ef3ecf1/go.mod h1:4TqBEuceheGNdeLNrelliLHJzmXauMM2vtWfuy1pFiM=
|
||||
github.com/btcsuite/btcwallet v0.11.1-0.20200612012534-48addcd5591a h1:AZ1Mf0gd9mgJqrTTIFUc17ep9EKUbQusVAIzJ6X+x3Q=
|
||||
github.com/btcsuite/btcwallet v0.11.1-0.20200612012534-48addcd5591a/go.mod h1:9+AH3V5mcTtNXTKe+fe63fDLKGOwQbZqmvOVUef+JFE=
|
||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 h1:KGHMW5sd7yDdDMkCZ/JpP0KltolFsQcB973brBnfj4c=
|
||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU=
|
||||
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w=
|
||||
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0/go.mod h1:UwQE78yCerZ313EXZwEiu3jNAtfXj2n2+c8RWiE/WNA=
|
||||
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0 h1:6DxkcoMnCPY4E9cUDPB5tbuuf40SmmMkSQkoE8vCT+s=
|
||||
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs=
|
||||
github.com/btcsuite/btcwallet/walletdb v1.0.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
|
||||
github.com/btcsuite/btcwallet/walletdb v1.1.0 h1:JHAL7wZ8pX4SULabeAv/wPO9sseRWMGzE80lfVmRw6Y=
|
||||
github.com/btcsuite/btcwallet/walletdb v1.1.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
|
||||
github.com/btcsuite/btcwallet/walletdb v1.3.1/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc=
|
||||
github.com/btcsuite/btcwallet/walletdb v1.3.2/go.mod h1:GZCMPNpUu5KE3ASoVd+k06p/1OW8OwNGCCaNWRto2cQ=
|
||||
github.com/btcsuite/btcwallet/walletdb v1.3.3 h1:u6e7vRIKBF++cJy+hOHaMGg+88ZTwvpaY27AFvtB668=
|
||||
github.com/btcsuite/btcwallet/walletdb v1.3.3/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU=
|
||||
github.com/btcsuite/btcwallet/wtxmgr v1.0.0 h1:aIHgViEmZmZfe0tQQqF1xyd2qBqFWxX5vZXkkbjtbeA=
|
||||
github.com/btcsuite/btcwallet/wtxmgr v1.0.0/go.mod h1:vc4gBprll6BP0UJ+AIGDaySoc7MdAmZf8kelfNb8CFY=
|
||||
github.com/btcsuite/btcwallet/wtxmgr v1.2.0 h1:ZUYPsSv8GjF9KK7lboB2OVHF0uYEcHxgrCfFWqPd9NA=
|
||||
github.com/btcsuite/btcwallet/wtxmgr v1.2.0/go.mod h1:h8hkcKUE3X7lMPzTUoGnNiw5g7VhGrKEW3KpR2r0VnY=
|
||||
github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941/go.mod h1:QcFA8DZHtuIAdYKCq/BzELOaznRsCvwf4zTPmaYwaig=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/goleveldb v1.0.0 h1:Tvd0BfvqX9o823q1j2UZ/epQo09eJh6dTcRp79ilIN4=
|
||||
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJGQE=
|
||||
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
|
||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/lru v1.0.0 h1:Kbsb1SFDsIlaupWPwsPp+dkxiBY1frcS07PCPgotKz8=
|
||||
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
|
||||
github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
|
@ -66,15 +111,42 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
|||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v0.0.0-20170724004829-f2862b476edc/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
|
||||
github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
|
||||
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
|
||||
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
|
||||
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v0.0.0-20181116074157-8ec929ed50c3/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc=
|
||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
|
||||
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
||||
|
@ -93,22 +165,44 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
|
|||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lightninglabs/gozmq v0.0.0-20190710231225-cea2a031735d h1:tt8hwvxl6fksSfchjBGaWu+pnWJQfG1OWiCM20qOSAE=
|
||||
github.com/lightninglabs/gozmq v0.0.0-20190710231225-cea2a031735d/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
|
||||
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc=
|
||||
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
|
||||
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI=
|
||||
github.com/lightningnetwork/lightning-onion v0.0.0-20190909101754-850081b08b6a/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
|
||||
github.com/lightningnetwork/lightning-onion v1.0.1 h1:qChGgS5+aPxFeR6JiUsGvanei1bn6WJpYbvosw/1604=
|
||||
github.com/lightningnetwork/lightning-onion v1.0.1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
|
||||
github.com/lightningnetwork/lnd v0.8.0-beta h1:HmmhSRTq48qobqQF8YLqNa8eKU8dDBNbWWpr2VzycJM=
|
||||
github.com/lightningnetwork/lnd v0.8.0-beta/go.mod h1:nq06y2BDv7vwWeMmwgB7P3pT7/Uj7sGf5FzHISVD6t4=
|
||||
github.com/lightningnetwork/lnd v0.10.4-beta h1:Af2zOCPePeaU8Tkl8IqtTjr4BP3zYfi+hAtQYcCMM58=
|
||||
github.com/lightningnetwork/lnd v0.10.4-beta/go.mod h1:4d02pduRVtZwgTJ+EimKJTsEAY0jDwi0SPE9h5aRneM=
|
||||
github.com/lightningnetwork/lnd/cert v1.0.2/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo=
|
||||
github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo=
|
||||
github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg=
|
||||
github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0=
|
||||
github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms=
|
||||
github.com/lightningnetwork/lnd/queue v1.0.4 h1:8Dq3vxAFSACPy+pKN88oPFhuCpCoAAChPBwa4BJxH4k=
|
||||
github.com/lightningnetwork/lnd/queue v1.0.4/go.mod h1:YTkTVZCxz8tAYreH27EO3s8572ODumWrNdYW2E/YKxg=
|
||||
github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU=
|
||||
github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0=
|
||||
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
|
||||
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
|
||||
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8 h1:PRMAcldsl4mXKJeRNB/KVNz6TlbS6hk2Rs42PqgU3Ws=
|
||||
github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/muun/libwallet v0.2.0 h1:Y64skcAZnwuxuSmoDPDui9RQv+3fxShDMN3UiROQ7CA=
|
||||
github.com/muun/libwallet v0.2.0/go.mod h1:eOp7//0x8kWLXWuUyRtQlJ5WN0ZIaQNhL9++LOincrM=
|
||||
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
|
||||
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/muun/libwallet v0.5.0 h1:3YcUuQsnViXdrXntBwV3sLH2RKHC5uNODhuawp+2dg8=
|
||||
github.com/muun/libwallet v0.5.0/go.mod h1:EdLg8d1sGJ4q4VUKRJyfNDBnbWc+rs5b8pHHu6KF5LY=
|
||||
github.com/muun/neutrino v0.0.0-20190914162326-7082af0fa257 h1:NW17wq2gZlEFeW3/Zx3wSmqlD0wKGf7YvhpP+CNCsbE=
|
||||
github.com/muun/neutrino v0.0.0-20190914162326-7082af0fa257/go.mod h1:awTrhbCWjWNH4yVwZ4IE7nZbvpQ27e7OyD+jao7wRxA=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
|
@ -120,45 +214,87 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
|
|||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY=
|
||||
github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 h1:ASw9n1EHMftwnP3Az4XW6e308+gNsrHzmdhd0Olz9Hs=
|
||||
go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20200720140940-1a48f808d81f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -169,24 +305,63 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gormigrate.v1 v1.6.0 h1:XpYM6RHQPmzwY7Uyu+t+xxMXc86JYFJn4nEc9HzQjsI=
|
||||
gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw=
|
||||
gopkg.in/macaroon-bakery.v2 v2.0.1/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETfr3S9MbIA=
|
||||
gopkg.in/macaroon.v2 v2.0.0/go.mod h1:+I6LnTMkm/uV5ew/0nsulNjL16SK4+C8yDmRUzHR17I=
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
@ -8,24 +8,24 @@ import (
|
|||
)
|
||||
|
||||
func buildExtendedKey(rawKey, recoveryCode string) *libwallet.DecryptedPrivateKey {
|
||||
recoveryCodeBytes := extractBytes(recoveryCode)
|
||||
salt := extractSalt(rawKey)
|
||||
|
||||
privKey := libwallet.NewChallengePrivateKey(recoveryCodeBytes, salt)
|
||||
decryptionKey, err := libwallet.RecoveryCodeToKey(recoveryCode, salt)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to process recovery code: %v", err)
|
||||
}
|
||||
|
||||
key, err := privKey.DecryptKey(rawKey, libwallet.Mainnet())
|
||||
walletKey, err := decryptionKey.DecryptKey(rawKey, libwallet.Mainnet())
|
||||
if err != nil {
|
||||
log.Fatalf("failed to decrypt key: %v", err)
|
||||
}
|
||||
|
||||
return key
|
||||
return walletKey
|
||||
}
|
||||
|
||||
func extractSalt(rawKey string) []byte {
|
||||
func extractSalt(rawKey string) string {
|
||||
bytes := base58.Decode(rawKey)
|
||||
return bytes[len(bytes)-8:]
|
||||
}
|
||||
saltBytes := bytes[len(bytes)-8:]
|
||||
|
||||
func extractBytes(recoveryCode string) []byte {
|
||||
return []byte(recoveryCode)
|
||||
return string(saltBytes)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,241 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
chainService, close, _ := startChainService()
|
||||
defer close()
|
||||
|
||||
printWelcomeMessage()
|
||||
|
||||
recoveryCode := readRecoveryCode()
|
||||
|
||||
userRawKey := readKey("first encrypted private key", 147)
|
||||
userKey := buildExtendedKey(userRawKey, recoveryCode)
|
||||
userKey.Key.Path = "m/1'/1'"
|
||||
|
||||
muunRawKey := readKey("second encrypted private key", 147)
|
||||
muunKey := buildExtendedKey(muunRawKey, recoveryCode)
|
||||
|
||||
sweepAddress := readSweepAddress()
|
||||
|
||||
fmt.Println("")
|
||||
fmt.Println("Preparing to scan the blockchain from your wallet creation block")
|
||||
fmt.Println("Note that only confirmed transactions can be detected")
|
||||
fmt.Println("\nThis may take a while")
|
||||
|
||||
sweeper := Sweeper{
|
||||
ChainService: chainService,
|
||||
UserKey: userKey.Key,
|
||||
MuunKey: muunKey.Key,
|
||||
Birthday: muunKey.Birthday,
|
||||
SweepAddress: sweepAddress,
|
||||
}
|
||||
|
||||
utxos := sweeper.GetUTXOs()
|
||||
|
||||
fmt.Println("")
|
||||
|
||||
if len(utxos) > 0 {
|
||||
fmt.Printf("The recovery tool has found the following confirmed UTXOs:\n%v", utxos)
|
||||
} else {
|
||||
fmt.Printf("No confirmed UTXOs found")
|
||||
fmt.Println()
|
||||
return
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
txOutputAmount, txWeightInBytes, err := sweeper.GetSweepTxAmountAndWeightInBytes(utxos)
|
||||
if err != nil {
|
||||
printError(err)
|
||||
}
|
||||
|
||||
fee := readFee(txOutputAmount, txWeightInBytes)
|
||||
|
||||
// Then we re-build the sweep tx with the actual fee
|
||||
sweepTx, err := sweeper.BuildSweepTx(utxos, fee)
|
||||
if err != nil {
|
||||
printError(err)
|
||||
}
|
||||
|
||||
fmt.Println("Transaction ready to be sent")
|
||||
|
||||
err = sweeper.BroadcastTx(sweepTx)
|
||||
if err != nil {
|
||||
printError(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Transaction sent! You can check the status here: https://blockstream.info/tx/%v", sweepTx.TxHash().String())
|
||||
fmt.Println("")
|
||||
fmt.Printf("We appreciate all kinds of feedback. If you have any, send it to contact@muun.com")
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
func printError(err error) {
|
||||
log.Printf("The recovery tool failed with the following error: %v", err.Error())
|
||||
log.Printf("")
|
||||
log.Printf("You can try again or contact us at support@muun.com")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
func printWelcomeMessage() {
|
||||
fmt.Println("Welcome to Muun's Recovery Tool")
|
||||
fmt.Println("")
|
||||
fmt.Println("You can use this tool to transfer all funds from your Muun account to an")
|
||||
fmt.Println("address of your choosing.")
|
||||
fmt.Println("")
|
||||
fmt.Println("To do this you will need:")
|
||||
fmt.Println("1. Your Recovery Code, which you wrote down during your security setup")
|
||||
fmt.Println("2. Your two encrypted private keys, which you exported from your wallet")
|
||||
fmt.Println("3. A destination bitcoin address where all your funds will be sent")
|
||||
fmt.Println("")
|
||||
fmt.Println("If you have any questions, we'll be happy to answer them. Contact us at support@muun.com")
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
func readRecoveryCode() string {
|
||||
fmt.Println("")
|
||||
fmt.Printf("Enter your Recovery Code")
|
||||
fmt.Println()
|
||||
fmt.Println("(it looks like this: 'ABCD-1234-POW2-R561-P120-JK26-12RW-45TT')")
|
||||
fmt.Print("> ")
|
||||
var userInput string
|
||||
fmt.Scan(&userInput)
|
||||
userInput = strings.TrimSpace(userInput)
|
||||
|
||||
finalRC := strings.ToUpper(userInput)
|
||||
|
||||
if strings.Count(finalRC, "-") != 7 {
|
||||
fmt.Printf("Invalid recovery code. Did you add the '-' separator between each 4-characters segment?")
|
||||
fmt.Println()
|
||||
fmt.Println("Please, try again")
|
||||
|
||||
return readRecoveryCode()
|
||||
}
|
||||
|
||||
if len(finalRC) != 39 {
|
||||
fmt.Println("Your recovery code must have 39 characters")
|
||||
fmt.Println("Please, try again")
|
||||
|
||||
return readRecoveryCode()
|
||||
}
|
||||
|
||||
return finalRC
|
||||
}
|
||||
|
||||
func readKey(keyType string, characters int) string {
|
||||
fmt.Println("")
|
||||
fmt.Printf("Enter your %v", keyType)
|
||||
fmt.Println()
|
||||
fmt.Println("(it looks like this: '9xzpc7y6sNtRvh8Fh...')")
|
||||
fmt.Print("> ")
|
||||
|
||||
userInput := scanMultiline(characters)
|
||||
|
||||
if len(userInput) != characters {
|
||||
fmt.Printf("Your %v must have %v characters", keyType, characters)
|
||||
fmt.Println("")
|
||||
fmt.Println("Please, try again")
|
||||
|
||||
return readKey(keyType, characters)
|
||||
}
|
||||
|
||||
return userInput
|
||||
}
|
||||
|
||||
func readSweepAddress() btcutil.Address {
|
||||
fmt.Println("")
|
||||
fmt.Println("Enter your destination bitcoin address")
|
||||
fmt.Print("> ")
|
||||
var userInput string
|
||||
fmt.Scan(&userInput)
|
||||
userInput = strings.TrimSpace(userInput)
|
||||
|
||||
addr, err := btcutil.DecodeAddress(userInput, &chainParams)
|
||||
if err != nil {
|
||||
fmt.Println("This is not a valid bitcoin address")
|
||||
fmt.Println("")
|
||||
fmt.Println("Please, try again")
|
||||
|
||||
return readSweepAddress()
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
func readFee(totalBalance, weight int64) int64 {
|
||||
fmt.Println("")
|
||||
fmt.Printf("Enter the fee in satoshis per byte. Tx weight: %v bytes. You can check the status of the mempool here: https://bitcoinfees.earn.com/#fees", weight)
|
||||
fmt.Println()
|
||||
fmt.Println("(Example: 5)")
|
||||
fmt.Print("> ")
|
||||
var userInput string
|
||||
fmt.Scan(&userInput)
|
||||
feeInSatsPerByte, err := strconv.ParseInt(userInput, 10, 64)
|
||||
if err != nil || feeInSatsPerByte <= 0 {
|
||||
fmt.Printf("The fee must be a number")
|
||||
fmt.Println("")
|
||||
fmt.Println("Please, try again")
|
||||
|
||||
return readFee(totalBalance, weight)
|
||||
}
|
||||
|
||||
totalFee := feeInSatsPerByte * weight
|
||||
|
||||
if totalBalance-totalFee < 546 {
|
||||
fmt.Printf("The fee is too high. The amount left must be higher than dust")
|
||||
fmt.Println("")
|
||||
fmt.Println("Please, try again")
|
||||
|
||||
return readFee(totalBalance, weight)
|
||||
}
|
||||
|
||||
return totalFee
|
||||
}
|
||||
|
||||
func readConfirmation(value, fee int64, address string) {
|
||||
fmt.Println("")
|
||||
fmt.Printf("About to send %v satoshis with fee: %v satoshis to %v", value, fee, address)
|
||||
fmt.Println()
|
||||
fmt.Println("Confirm? (y/n)")
|
||||
fmt.Print("> ")
|
||||
var userInput string
|
||||
fmt.Scan(&userInput)
|
||||
|
||||
if userInput == "y" || userInput == "Y" {
|
||||
return
|
||||
}
|
||||
|
||||
if userInput == "n" || userInput == "N" {
|
||||
log.Println()
|
||||
log.Printf("Recovery tool stopped")
|
||||
log.Println()
|
||||
log.Printf("You can try again or contact us at support@muun.com")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("You can only enter 'y' to accept or 'n' to cancel")
|
||||
readConfirmation(value, fee, address)
|
||||
}
|
||||
|
||||
func scanMultiline(minChars int) string {
|
||||
var result strings.Builder
|
||||
|
||||
for result.Len() < minChars {
|
||||
var line string
|
||||
fmt.Scan(&line)
|
||||
|
||||
result.WriteString(strings.TrimSpace(line))
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
263
recovery_tool.go
263
recovery_tool.go
|
@ -2,12 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
@ -16,80 +11,7 @@ import (
|
|||
"github.com/muun/libwallet"
|
||||
)
|
||||
|
||||
func main() {
|
||||
chainService, close, _ := startChainService()
|
||||
defer close()
|
||||
|
||||
printWelcomeMessage()
|
||||
|
||||
recoveryCode := readRecoveryCode()
|
||||
|
||||
userRawKey := readKey("first encrypted private key", 147)
|
||||
userKey := buildExtendedKey(userRawKey, recoveryCode)
|
||||
userKey.Key.Path = "m/1'/1'"
|
||||
|
||||
muunRawKey := readKey("second encrypted private key", 147)
|
||||
muunKey := buildExtendedKey(muunRawKey, recoveryCode)
|
||||
derivedMuunKey, err := muunKey.Key.DeriveTo("m/1'/1'")
|
||||
if err != nil {
|
||||
printError(err)
|
||||
}
|
||||
|
||||
sweepAddress := readSweepAddress()
|
||||
|
||||
fmt.Println("")
|
||||
fmt.Println("Starting to scan the blockchain. This may take a while.")
|
||||
|
||||
g := NewAddressGenerator(userKey.Key, muunKey.Key)
|
||||
g.Generate()
|
||||
|
||||
birthday := muunKey.Birthday
|
||||
if birthday == 0xFFFF {
|
||||
birthday = 0
|
||||
}
|
||||
|
||||
utxos := startRescan(chainService, g.Addresses(), birthday)
|
||||
fmt.Println("")
|
||||
|
||||
if len(utxos) > 0 {
|
||||
fmt.Printf("The recovery tool has found the following utxos: %v", utxos)
|
||||
} else {
|
||||
fmt.Printf("No utxos found")
|
||||
fmt.Println()
|
||||
return
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// This is fun:
|
||||
// First we build a sweep tx with 0 fee with the only purpouse of seeing its signed size
|
||||
zeroFeehexSweepTx := buildSweepTx(utxos, sweepAddress, 0)
|
||||
zeroFeeSweepTx, err := buildSignedTx(utxos, zeroFeehexSweepTx, userKey.Key, derivedMuunKey)
|
||||
if err != nil {
|
||||
printError(err)
|
||||
}
|
||||
weightInBytes := int64(zeroFeeSweepTx.SerializeSize())
|
||||
fee := readFee(zeroFeeSweepTx.TxOut[0].Value, weightInBytes)
|
||||
// Then we re-build the sweep tx with the actual fee
|
||||
hexSweepTx := buildSweepTx(utxos, sweepAddress, fee)
|
||||
tx, err := buildSignedTx(utxos, hexSweepTx, userKey.Key, derivedMuunKey)
|
||||
if err != nil {
|
||||
printError(err)
|
||||
}
|
||||
fmt.Println("Transaction ready to be sent")
|
||||
|
||||
err = chainService.SendTransaction(tx)
|
||||
if err != nil {
|
||||
printError(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Transaction sent! You can check the status here: https://blockstream.info/tx/%v", tx.TxHash().String())
|
||||
fmt.Println("")
|
||||
fmt.Printf("If you have any feedback, feel free to share it with us. Our email is contact@muun.com")
|
||||
fmt.Println("")
|
||||
|
||||
}
|
||||
|
||||
func buildSweepTx(utxos []*RelevantTx, sweepAddress btcutil.Address, fee int64) string {
|
||||
func buildSweepTx(utxos []*RelevantTx, sweepAddress btcutil.Address, fee int64) []byte {
|
||||
|
||||
tx := wire.NewMsgTx(2)
|
||||
value := int64(0)
|
||||
|
@ -121,32 +43,26 @@ func buildSweepTx(utxos []*RelevantTx, sweepAddress btcutil.Address, fee int64)
|
|||
readConfirmation(value, fee, sweepAddress.String())
|
||||
}
|
||||
|
||||
return hex.EncodeToString(writer.Bytes())
|
||||
return writer.Bytes()
|
||||
}
|
||||
|
||||
func buildSignedTx(utxos []*RelevantTx, hexSweepTx string, userKey *libwallet.HDPrivateKey,
|
||||
func buildSignedTx(utxos []*RelevantTx, sweepTx []byte, userKey *libwallet.HDPrivateKey,
|
||||
muunKey *libwallet.HDPrivateKey) (*wire.MsgTx, error) {
|
||||
|
||||
pstx, err := libwallet.NewPartiallySignedTransaction(hexSweepTx)
|
||||
inputList := &libwallet.InputList{}
|
||||
for _, utxo := range utxos {
|
||||
inputList.Add(&input{
|
||||
utxo,
|
||||
[]byte{},
|
||||
})
|
||||
}
|
||||
|
||||
pstx, err := libwallet.NewPartiallySignedTransaction(inputList, sweepTx)
|
||||
if err != nil {
|
||||
printError(err)
|
||||
}
|
||||
|
||||
for index, utxo := range utxos {
|
||||
input := &input{
|
||||
utxo,
|
||||
[]byte{},
|
||||
}
|
||||
|
||||
pstx.AddInput(input)
|
||||
sig, err := pstx.MuunSignatureForInput(index, userKey.PublicKey(), muunKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
input.muunSignature = sig
|
||||
}
|
||||
|
||||
signedTx, err := pstx.Sign(userKey, muunKey.PublicKey())
|
||||
signedTx, err := pstx.FullySign(userKey, muunKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -156,155 +72,6 @@ func buildSignedTx(utxos []*RelevantTx, hexSweepTx string, userKey *libwallet.HD
|
|||
return wireTx, nil
|
||||
}
|
||||
|
||||
func printError(err error) {
|
||||
log.Printf("The recovery tool failed with the following error: %v", err.Error())
|
||||
log.Printf("")
|
||||
log.Printf("You can try again or contact us at support@muun.com")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
func printWelcomeMessage() {
|
||||
fmt.Println("Welcome to Muun's Recovery Tool")
|
||||
fmt.Println("")
|
||||
fmt.Println("You can use this tool to swipe all the balance in your muun account to an")
|
||||
fmt.Println("address of your choosing.")
|
||||
fmt.Println("")
|
||||
fmt.Println("To do this you will need:")
|
||||
fmt.Println("* The recovery code, that you set up when you created your muun account")
|
||||
fmt.Println("* The two encrypted private keys that you exported from your muun wallet")
|
||||
fmt.Println("* A destination bitcoin address where all your funds will be sent")
|
||||
fmt.Println("")
|
||||
fmt.Println("If you have any questions, contact us at contact@muun.com")
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
func readRecoveryCode() string {
|
||||
fmt.Println("")
|
||||
fmt.Printf("Enter your Recovery Code")
|
||||
fmt.Println()
|
||||
fmt.Println("(it looks like this: 'ABCD-1234-POW2-R561-P120-JK26-12RW-45TT')")
|
||||
fmt.Print("> ")
|
||||
var userInput string
|
||||
fmt.Scan(&userInput)
|
||||
userInput = strings.TrimSpace(userInput)
|
||||
|
||||
finalRC := strings.ToUpper(userInput)
|
||||
|
||||
if strings.Count(finalRC, "-") != 7 {
|
||||
fmt.Printf("Wrong recovery code, remember to add the '-' separator between the 4 characters chunks")
|
||||
fmt.Println()
|
||||
fmt.Println("Please, try again")
|
||||
|
||||
return readRecoveryCode()
|
||||
}
|
||||
|
||||
if len(finalRC) != 39 {
|
||||
fmt.Println("Your recovery code must have 39 characters")
|
||||
fmt.Println("Please, try again")
|
||||
|
||||
return readRecoveryCode()
|
||||
}
|
||||
|
||||
return finalRC
|
||||
}
|
||||
|
||||
func readKey(keyType string, characters int) string {
|
||||
fmt.Println("")
|
||||
fmt.Printf("Enter your %v", keyType)
|
||||
fmt.Println()
|
||||
fmt.Println("(it looks like this: '9xzpc7y6sNtRvh8Fh...')")
|
||||
fmt.Print("> ")
|
||||
var userInput string
|
||||
fmt.Scan(&userInput)
|
||||
userInput = strings.TrimSpace(userInput)
|
||||
|
||||
if len(userInput) != characters {
|
||||
fmt.Printf("Your %v must have %v characters", keyType, characters)
|
||||
fmt.Println("")
|
||||
fmt.Println("Please, try again")
|
||||
|
||||
return readKey(keyType, characters)
|
||||
}
|
||||
|
||||
return userInput
|
||||
}
|
||||
|
||||
func readSweepAddress() btcutil.Address {
|
||||
fmt.Println("")
|
||||
fmt.Println("Enter your destination bitcoin address")
|
||||
fmt.Print("> ")
|
||||
var userInput string
|
||||
fmt.Scan(&userInput)
|
||||
userInput = strings.TrimSpace(userInput)
|
||||
|
||||
addr, err := btcutil.DecodeAddress(userInput, &chainParams)
|
||||
if err != nil {
|
||||
fmt.Println("This is not a valid bitcoin address")
|
||||
fmt.Println("")
|
||||
fmt.Println("Please, try again")
|
||||
|
||||
return readSweepAddress()
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
func readFee(totalBalance, weight int64) int64 {
|
||||
fmt.Println("")
|
||||
fmt.Printf("Enter the fee in satoshis per byte. Tx weight: %v bytes. You can check the status of the mempool here: https://bitcoinfees.earn.com/#fees", weight)
|
||||
fmt.Println()
|
||||
fmt.Println("(Example: 5)")
|
||||
fmt.Print("> ")
|
||||
var userInput string
|
||||
fmt.Scan(&userInput)
|
||||
feeInSatsPerByte, err := strconv.ParseInt(userInput, 10, 64)
|
||||
if err != nil || feeInSatsPerByte <= 0 {
|
||||
fmt.Printf("The fee must be a number")
|
||||
fmt.Println("")
|
||||
fmt.Println("Please, try again")
|
||||
|
||||
return readFee(totalBalance, weight)
|
||||
}
|
||||
|
||||
totalFee := feeInSatsPerByte * weight
|
||||
|
||||
if totalBalance-totalFee < 546 {
|
||||
fmt.Printf("The fee is too high. The amount left must be higher than dust")
|
||||
fmt.Println("")
|
||||
fmt.Println("Please, try again")
|
||||
|
||||
return readFee(totalBalance, weight)
|
||||
}
|
||||
|
||||
return totalFee
|
||||
}
|
||||
|
||||
func readConfirmation(value, fee int64, address string) {
|
||||
fmt.Println("")
|
||||
fmt.Printf("About to send %v satoshis with fee: %v satoshis to %v", value, fee, address)
|
||||
fmt.Println()
|
||||
fmt.Println("Confirm? (y/n)")
|
||||
fmt.Print("> ")
|
||||
var userInput string
|
||||
fmt.Scan(&userInput)
|
||||
|
||||
if userInput == "y" || userInput == "Y" {
|
||||
return
|
||||
}
|
||||
|
||||
if userInput == "n" || userInput == "N" {
|
||||
log.Println()
|
||||
log.Printf("Recovery tool stopped")
|
||||
log.Println()
|
||||
log.Printf("You can try again or contact us at support@muun.com")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("You can only enter 'y' to accept or 'n' to cancel")
|
||||
readConfirmation(value, fee, address)
|
||||
}
|
||||
|
||||
type input struct {
|
||||
tx *RelevantTx
|
||||
muunSignature []byte
|
||||
|
@ -334,6 +101,10 @@ func (i *input) SubmarineSwapV2() libwallet.InputSubmarineSwapV2 {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (i *input) IncomingSwap() libwallet.InputIncomingSwap {
|
||||
return nil
|
||||
}
|
||||
|
||||
type outpoint struct {
|
||||
tx *RelevantTx
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightninglabs/neutrino"
|
||||
"github.com/muun/libwallet"
|
||||
)
|
||||
|
||||
type Sweeper struct {
|
||||
ChainService *neutrino.ChainService
|
||||
UserKey *libwallet.HDPrivateKey
|
||||
MuunKey *libwallet.HDPrivateKey
|
||||
Birthday int
|
||||
SweepAddress btcutil.Address
|
||||
}
|
||||
|
||||
func (s *Sweeper) GetUTXOs() []*RelevantTx {
|
||||
g := NewAddressGenerator(s.UserKey, s.MuunKey)
|
||||
g.Generate()
|
||||
|
||||
birthday := s.Birthday
|
||||
if birthday == 0xFFFF {
|
||||
birthday = 0
|
||||
}
|
||||
|
||||
return startRescan(s.ChainService, g.Addresses(), birthday)
|
||||
}
|
||||
|
||||
func (s *Sweeper) GetSweepTxAmountAndWeightInBytes(utxos []*RelevantTx) (outputAmount int64, weightInBytes int64, err error) {
|
||||
// we build a sweep tx with 0 fee with the only purpose of checking its signed size
|
||||
zeroFeeSweepTx, err := s.BuildSweepTx(utxos, 0)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
outputAmount = zeroFeeSweepTx.TxOut[0].Value
|
||||
weightInBytes = int64(zeroFeeSweepTx.SerializeSize())
|
||||
|
||||
return outputAmount, weightInBytes, nil
|
||||
}
|
||||
|
||||
func (s *Sweeper) BuildSweepTx(utxos []*RelevantTx, fee int64) (*wire.MsgTx, error) {
|
||||
derivedMuunKey, err := s.MuunKey.DeriveTo("m/1'/1'")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sweepTx := buildSweepTx(utxos, s.SweepAddress, fee)
|
||||
return buildSignedTx(utxos, sweepTx, s.UserKey, derivedMuunKey)
|
||||
}
|
||||
|
||||
func (s *Sweeper) BroadcastTx(tx *wire.MsgTx) error {
|
||||
return s.ChainService.SendTransaction(tx)
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
.vscode
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
|
@ -0,0 +1,25 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- "1.8.x"
|
||||
- "1.9.x"
|
||||
- "1.10.x"
|
||||
|
||||
env:
|
||||
- TRAVIS_GOARCH=amd64
|
||||
- TRAVIS_GOARCH=386
|
||||
|
||||
before_install:
|
||||
- export GOARCH=$TRAVIS_GOARCH
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
before_script:
|
||||
- go get -u github.com/klauspost/asmfmt/cmd/asmfmt
|
||||
|
||||
script:
|
||||
- diff -au <(gofmt -d .) <(printf "")
|
||||
- diff -au <(asmfmt -d .) <(printf "")
|
||||
- go test -v ./...
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Andreas Auernhammer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,82 @@
|
|||
[![Godoc Reference](https://godoc.org/github.com/aead/chacha20?status.svg)](https://godoc.org/github.com/aead/chacha20)
|
||||
[![Build Status](https://travis-ci.org/aead/chacha20.svg?branch=master)](https://travis-ci.org/aead/chacha20)
|
||||
[![Go Report Card](https://goreportcard.com/badge/aead/chacha20)](https://goreportcard.com/report/aead/chacha20)
|
||||
|
||||
## The ChaCha20 stream cipher
|
||||
|
||||
ChaCha is a stream cipher family created by Daniel J. Bernstein.
|
||||
The most common ChaCha variant is ChaCha20 (20 rounds). ChaCha20 is
|
||||
standardized in [RFC 7539](https://tools.ietf.org/html/rfc7539 "RFC 7539").
|
||||
|
||||
This package provides implementations of three ChaCha versions:
|
||||
- ChaCha20 with a 64 bit nonce (can en/decrypt up to 2^64 * 64 bytes for one key-nonce combination)
|
||||
- ChaCha20 with a 96 bit nonce (can en/decrypt up to 2^32 * 64 bytes ~ 256 GB for one key-nonce combination)
|
||||
- XChaCha20 with a 192 bit nonce (can en/decrypt up to 2^64 * 64 bytes for one key-nonce combination)
|
||||
|
||||
Furthermore the chacha sub package implements ChaCha20/12 and ChaCha20/8.
|
||||
These versions use 12 or 8 rounds instead of 20.
|
||||
But it's recommended to use ChaCha20 (with 20 rounds) - it will be fast enough for almost all purposes.
|
||||
|
||||
### Installation
|
||||
Install in your GOPATH: `go get -u github.com/aead/chacha20`
|
||||
|
||||
### Requirements
|
||||
All go versions >= 1.8.7 are supported.
|
||||
The code may also work on Go 1.7 but this is not tested.
|
||||
|
||||
### Performance
|
||||
|
||||
#### AMD64
|
||||
Hardware: Intel i7-6500U 2.50GHz x 2
|
||||
System: Linux Ubuntu 16.04 - kernel: 4.4.0-62-generic
|
||||
Go version: 1.8.0
|
||||
```
|
||||
AVX2
|
||||
name speed cpb
|
||||
ChaCha20_64-4 573MB/s ± 0% 4.16
|
||||
ChaCha20_1K-4 2.19GB/s ± 0% 1.06
|
||||
XChaCha20_64-4 261MB/s ± 0% 9.13
|
||||
XChaCha20_1K-4 1.69GB/s ± 4% 1.37
|
||||
XORKeyStream64-4 474MB/s ± 2% 5.02
|
||||
XORKeyStream1K-4 2.09GB/s ± 1% 1.11
|
||||
XChaCha20_XORKeyStream64-4 262MB/s ± 0% 9.09
|
||||
XChaCha20_XORKeyStream1K-4 1.71GB/s ± 1% 1.36
|
||||
|
||||
SSSE3
|
||||
name speed cpb
|
||||
ChaCha20_64-4 583MB/s ± 0% 4.08
|
||||
ChaCha20_1K-4 1.15GB/s ± 1% 2.02
|
||||
XChaCha20_64-4 267MB/s ± 0% 8.92
|
||||
XChaCha20_1K-4 984MB/s ± 5% 2.42
|
||||
XORKeyStream64-4 492MB/s ± 1% 4.84
|
||||
XORKeyStream1K-4 1.10GB/s ± 5% 2.11
|
||||
XChaCha20_XORKeyStream64-4 266MB/s ± 0% 8.96
|
||||
XChaCha20_XORKeyStream1K-4 1.00GB/s ± 2% 2.32
|
||||
```
|
||||
#### 386
|
||||
Hardware: Intel i7-6500U 2.50GHz x 2
|
||||
System: Linux Ubuntu 16.04 - kernel: 4.4.0-62-generic
|
||||
Go version: 1.8.0
|
||||
```
|
||||
SSSE3
|
||||
name speed cpb
|
||||
ChaCha20_64-4 570MB/s ± 0% 4.18
|
||||
ChaCha20_1K-4 650MB/s ± 0% 3.66
|
||||
XChaCha20_64-4 223MB/s ± 0% 10.69
|
||||
XChaCha20_1K-4 584MB/s ± 1% 4.08
|
||||
XORKeyStream64-4 392MB/s ± 1% 6.08
|
||||
XORKeyStream1K-4 629MB/s ± 1% 3.79
|
||||
XChaCha20_XORKeyStream64-4 222MB/s ± 0% 10.73
|
||||
XChaCha20_XORKeyStream1K-4 585MB/s ± 0% 4.07
|
||||
|
||||
SSE2
|
||||
name speed cpb
|
||||
ChaCha20_64-4 509MB/s ± 0% 4.68
|
||||
ChaCha20_1K-4 553MB/s ± 2% 4.31
|
||||
XChaCha20_64-4 201MB/s ± 0% 11.86
|
||||
XChaCha20_1K-4 498MB/s ± 4% 4.78
|
||||
XORKeyStream64-4 359MB/s ± 1% 6.64
|
||||
XORKeyStream1K-4 545MB/s ± 0% 4.37
|
||||
XChaCha20_XORKeyStream64-4 201MB/s ± 1% 11.86
|
||||
XChaCha20_XORKeyStream1K-4 507MB/s ± 0% 4.70
|
||||
```
|
|
@ -0,0 +1,197 @@
|
|||
// Copyright (c) 2016 Andreas Auernhammer. All rights reserved.
|
||||
// Use of this source code is governed by a license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Package chacha implements some low-level functions of the
|
||||
// ChaCha cipher family.
|
||||
package chacha // import "github.com/aead/chacha20/chacha"
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math"
|
||||
)
|
||||
|
||||
const (
|
||||
// NonceSize is the size of the ChaCha20 nonce in bytes.
|
||||
NonceSize = 8
|
||||
|
||||
// INonceSize is the size of the IETF-ChaCha20 nonce in bytes.
|
||||
INonceSize = 12
|
||||
|
||||
// XNonceSize is the size of the XChaCha20 nonce in bytes.
|
||||
XNonceSize = 24
|
||||
|
||||
// KeySize is the size of the key in bytes.
|
||||
KeySize = 32
|
||||
)
|
||||
|
||||
var (
|
||||
useSSE2 bool
|
||||
useSSSE3 bool
|
||||
useAVX bool
|
||||
useAVX2 bool
|
||||
)
|
||||
|
||||
var (
|
||||
errKeySize = errors.New("chacha20/chacha: bad key length")
|
||||
errInvalidNonce = errors.New("chacha20/chacha: bad nonce length")
|
||||
)
|
||||
|
||||
func setup(state *[64]byte, nonce, key []byte) (err error) {
|
||||
if len(key) != KeySize {
|
||||
err = errKeySize
|
||||
return
|
||||
}
|
||||
var Nonce [16]byte
|
||||
switch len(nonce) {
|
||||
case NonceSize:
|
||||
copy(Nonce[8:], nonce)
|
||||
initialize(state, key, &Nonce)
|
||||
case INonceSize:
|
||||
copy(Nonce[4:], nonce)
|
||||
initialize(state, key, &Nonce)
|
||||
case XNonceSize:
|
||||
var tmpKey [32]byte
|
||||
var hNonce [16]byte
|
||||
|
||||
copy(hNonce[:], nonce[:16])
|
||||
copy(tmpKey[:], key)
|
||||
HChaCha20(&tmpKey, &hNonce, &tmpKey)
|
||||
copy(Nonce[8:], nonce[16:])
|
||||
initialize(state, tmpKey[:], &Nonce)
|
||||
|
||||
// BUG(aead): A "good" compiler will remove this (optimizations)
|
||||
// But using the provided key instead of tmpKey,
|
||||
// will change the key (-> probably confuses users)
|
||||
for i := range tmpKey {
|
||||
tmpKey[i] = 0
|
||||
}
|
||||
default:
|
||||
err = errInvalidNonce
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// XORKeyStream crypts bytes from src to dst using the given nonce and key.
|
||||
// The length of the nonce determinds the version of ChaCha20:
|
||||
// - NonceSize: ChaCha20/r with a 64 bit nonce and a 2^64 * 64 byte period.
|
||||
// - INonceSize: ChaCha20/r as defined in RFC 7539 and a 2^32 * 64 byte period.
|
||||
// - XNonceSize: XChaCha20/r with a 192 bit nonce and a 2^64 * 64 byte period.
|
||||
// The rounds argument specifies the number of rounds performed for keystream
|
||||
// generation - valid values are 8, 12 or 20. The src and dst may be the same slice
|
||||
// but otherwise should not overlap. If len(dst) < len(src) this function panics.
|
||||
// If the nonce is neither 64, 96 nor 192 bits long, this function panics.
|
||||
func XORKeyStream(dst, src, nonce, key []byte, rounds int) {
|
||||
if rounds != 20 && rounds != 12 && rounds != 8 {
|
||||
panic("chacha20/chacha: bad number of rounds")
|
||||
}
|
||||
if len(dst) < len(src) {
|
||||
panic("chacha20/chacha: dst buffer is to small")
|
||||
}
|
||||
if len(nonce) == INonceSize && uint64(len(src)) > (1<<38) {
|
||||
panic("chacha20/chacha: src is too large")
|
||||
}
|
||||
|
||||
var block, state [64]byte
|
||||
if err := setup(&state, nonce, key); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
xorKeyStream(dst, src, &block, &state, rounds)
|
||||
}
|
||||
|
||||
// Cipher implements ChaCha20/r (XChaCha20/r) for a given number of rounds r.
|
||||
type Cipher struct {
|
||||
state, block [64]byte
|
||||
off int
|
||||
rounds int // 20 for ChaCha20
|
||||
noncesize int
|
||||
}
|
||||
|
||||
// NewCipher returns a new *chacha.Cipher implementing the ChaCha20/r or XChaCha20/r
|
||||
// (r = 8, 12 or 20) stream cipher. The nonce must be unique for one key for all time.
|
||||
// The length of the nonce determinds the version of ChaCha20:
|
||||
// - NonceSize: ChaCha20/r with a 64 bit nonce and a 2^64 * 64 byte period.
|
||||
// - INonceSize: ChaCha20/r as defined in RFC 7539 and a 2^32 * 64 byte period.
|
||||
// - XNonceSize: XChaCha20/r with a 192 bit nonce and a 2^64 * 64 byte period.
|
||||
// If the nonce is neither 64, 96 nor 192 bits long, a non-nil error is returned.
|
||||
func NewCipher(nonce, key []byte, rounds int) (*Cipher, error) {
|
||||
if rounds != 20 && rounds != 12 && rounds != 8 {
|
||||
panic("chacha20/chacha: bad number of rounds")
|
||||
}
|
||||
|
||||
c := new(Cipher)
|
||||
if err := setup(&(c.state), nonce, key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.rounds = rounds
|
||||
|
||||
if len(nonce) == INonceSize {
|
||||
c.noncesize = INonceSize
|
||||
} else {
|
||||
c.noncesize = NonceSize
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// XORKeyStream crypts bytes from src to dst. Src and dst may be the same slice
|
||||
// but otherwise should not overlap. If len(dst) < len(src) the function panics.
|
||||
func (c *Cipher) XORKeyStream(dst, src []byte) {
|
||||
if len(dst) < len(src) {
|
||||
panic("chacha20/chacha: dst buffer is to small")
|
||||
}
|
||||
|
||||
if c.off > 0 {
|
||||
n := len(c.block[c.off:])
|
||||
if len(src) <= n {
|
||||
for i, v := range src {
|
||||
dst[i] = v ^ c.block[c.off]
|
||||
c.off++
|
||||
}
|
||||
if c.off == 64 {
|
||||
c.off = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for i, v := range c.block[c.off:] {
|
||||
dst[i] = src[i] ^ v
|
||||
}
|
||||
src = src[n:]
|
||||
dst = dst[n:]
|
||||
c.off = 0
|
||||
}
|
||||
|
||||
// check for counter overflow
|
||||
blocksToXOR := len(src) / 64
|
||||
if len(src)%64 != 0 {
|
||||
blocksToXOR++
|
||||
}
|
||||
var overflow bool
|
||||
if c.noncesize == INonceSize {
|
||||
overflow = binary.LittleEndian.Uint32(c.state[48:]) > math.MaxUint32-uint32(blocksToXOR)
|
||||
} else {
|
||||
overflow = binary.LittleEndian.Uint64(c.state[48:]) > math.MaxUint64-uint64(blocksToXOR)
|
||||
}
|
||||
if overflow {
|
||||
panic("chacha20/chacha: counter overflow")
|
||||
}
|
||||
|
||||
c.off += xorKeyStream(dst, src, &(c.block), &(c.state), c.rounds)
|
||||
}
|
||||
|
||||
// SetCounter skips ctr * 64 byte blocks. SetCounter(0) resets the cipher.
|
||||
// This function always skips the unused keystream of the current 64 byte block.
|
||||
func (c *Cipher) SetCounter(ctr uint64) {
|
||||
if c.noncesize == INonceSize {
|
||||
binary.LittleEndian.PutUint32(c.state[48:], uint32(ctr))
|
||||
} else {
|
||||
binary.LittleEndian.PutUint64(c.state[48:], ctr)
|
||||
}
|
||||
c.off = 0
|
||||
}
|
||||
|
||||
// HChaCha20 generates 32 pseudo-random bytes from a 128 bit nonce and a 256 bit secret key.
|
||||
// It can be used as a key-derivation-function (KDF).
|
||||
func HChaCha20(out *[32]byte, nonce *[16]byte, key *[32]byte) { hChaCha20(out, nonce, key) }
|
|
@ -0,0 +1,406 @@
|
|||
// Copyright (c) 2016 Andreas Auernhammer. All rights reserved.
|
||||
// Use of this source code is governed by a license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build amd64,!gccgo,!appengine,!nacl
|
||||
|
||||
#include "const.s"
|
||||
#include "macro.s"
|
||||
|
||||
#define TWO 0(SP)
|
||||
#define C16 32(SP)
|
||||
#define C8 64(SP)
|
||||
#define STATE_0 96(SP)
|
||||
#define STATE_1 128(SP)
|
||||
#define STATE_2 160(SP)
|
||||
#define STATE_3 192(SP)
|
||||
#define TMP_0 224(SP)
|
||||
#define TMP_1 256(SP)
|
||||
|
||||
// func xorKeyStreamAVX(dst, src []byte, block, state *[64]byte, rounds int) int
|
||||
TEXT ·xorKeyStreamAVX2(SB), 4, $320-80
|
||||
MOVQ dst_base+0(FP), DI
|
||||
MOVQ src_base+24(FP), SI
|
||||
MOVQ block+48(FP), BX
|
||||
MOVQ state+56(FP), AX
|
||||
MOVQ rounds+64(FP), DX
|
||||
MOVQ src_len+32(FP), CX
|
||||
|
||||
MOVQ SP, R8
|
||||
ADDQ $32, SP
|
||||
ANDQ $-32, SP
|
||||
|
||||
VMOVDQU 0(AX), Y2
|
||||
VMOVDQU 32(AX), Y3
|
||||
VPERM2I128 $0x22, Y2, Y0, Y0
|
||||
VPERM2I128 $0x33, Y2, Y1, Y1
|
||||
VPERM2I128 $0x22, Y3, Y2, Y2
|
||||
VPERM2I128 $0x33, Y3, Y3, Y3
|
||||
|
||||
TESTQ CX, CX
|
||||
JZ done
|
||||
|
||||
VMOVDQU ·one_AVX2<>(SB), Y4
|
||||
VPADDD Y4, Y3, Y3
|
||||
|
||||
VMOVDQA Y0, STATE_0
|
||||
VMOVDQA Y1, STATE_1
|
||||
VMOVDQA Y2, STATE_2
|
||||
VMOVDQA Y3, STATE_3
|
||||
|
||||
VMOVDQU ·rol16_AVX2<>(SB), Y4
|
||||
VMOVDQU ·rol8_AVX2<>(SB), Y5
|
||||
VMOVDQU ·two_AVX2<>(SB), Y6
|
||||
VMOVDQA Y4, Y14
|
||||
VMOVDQA Y5, Y15
|
||||
VMOVDQA Y4, C16
|
||||
VMOVDQA Y5, C8
|
||||
VMOVDQA Y6, TWO
|
||||
|
||||
CMPQ CX, $64
|
||||
JBE between_0_and_64
|
||||
CMPQ CX, $192
|
||||
JBE between_64_and_192
|
||||
CMPQ CX, $320
|
||||
JBE between_192_and_320
|
||||
CMPQ CX, $448
|
||||
JBE between_320_and_448
|
||||
|
||||
at_least_512:
|
||||
VMOVDQA Y0, Y4
|
||||
VMOVDQA Y1, Y5
|
||||
VMOVDQA Y2, Y6
|
||||
VPADDQ TWO, Y3, Y7
|
||||
VMOVDQA Y0, Y8
|
||||
VMOVDQA Y1, Y9
|
||||
VMOVDQA Y2, Y10
|
||||
VPADDQ TWO, Y7, Y11
|
||||
VMOVDQA Y0, Y12
|
||||
VMOVDQA Y1, Y13
|
||||
VMOVDQA Y2, Y14
|
||||
VPADDQ TWO, Y11, Y15
|
||||
|
||||
MOVQ DX, R9
|
||||
|
||||
chacha_loop_512:
|
||||
VMOVDQA Y8, TMP_0
|
||||
CHACHA_QROUND_AVX(Y0, Y1, Y2, Y3, Y8, C16, C8)
|
||||
CHACHA_QROUND_AVX(Y4, Y5, Y6, Y7, Y8, C16, C8)
|
||||
VMOVDQA TMP_0, Y8
|
||||
VMOVDQA Y0, TMP_0
|
||||
CHACHA_QROUND_AVX(Y8, Y9, Y10, Y11, Y0, C16, C8)
|
||||
CHACHA_QROUND_AVX(Y12, Y13, Y14, Y15, Y0, C16, C8)
|
||||
CHACHA_SHUFFLE_AVX(Y1, Y2, Y3)
|
||||
CHACHA_SHUFFLE_AVX(Y5, Y6, Y7)
|
||||
CHACHA_SHUFFLE_AVX(Y9, Y10, Y11)
|
||||
CHACHA_SHUFFLE_AVX(Y13, Y14, Y15)
|
||||
|
||||
CHACHA_QROUND_AVX(Y12, Y13, Y14, Y15, Y0, C16, C8)
|
||||
CHACHA_QROUND_AVX(Y8, Y9, Y10, Y11, Y0, C16, C8)
|
||||
VMOVDQA TMP_0, Y0
|
||||
VMOVDQA Y8, TMP_0
|
||||
CHACHA_QROUND_AVX(Y4, Y5, Y6, Y7, Y8, C16, C8)
|
||||
CHACHA_QROUND_AVX(Y0, Y1, Y2, Y3, Y8, C16, C8)
|
||||
VMOVDQA TMP_0, Y8
|
||||
CHACHA_SHUFFLE_AVX(Y3, Y2, Y1)
|
||||
CHACHA_SHUFFLE_AVX(Y7, Y6, Y5)
|
||||
CHACHA_SHUFFLE_AVX(Y11, Y10, Y9)
|
||||
CHACHA_SHUFFLE_AVX(Y15, Y14, Y13)
|
||||
SUBQ $2, R9
|
||||
JA chacha_loop_512
|
||||
|
||||
VMOVDQA Y12, TMP_0
|
||||
VMOVDQA Y13, TMP_1
|
||||
VPADDD STATE_0, Y0, Y0
|
||||
VPADDD STATE_1, Y1, Y1
|
||||
VPADDD STATE_2, Y2, Y2
|
||||
VPADDD STATE_3, Y3, Y3
|
||||
XOR_AVX2(DI, SI, 0, Y0, Y1, Y2, Y3, Y12, Y13)
|
||||
VMOVDQA STATE_0, Y0
|
||||
VMOVDQA STATE_1, Y1
|
||||
VMOVDQA STATE_2, Y2
|
||||
VMOVDQA STATE_3, Y3
|
||||
VPADDQ TWO, Y3, Y3
|
||||
|
||||
VPADDD Y0, Y4, Y4
|
||||
VPADDD Y1, Y5, Y5
|
||||
VPADDD Y2, Y6, Y6
|
||||
VPADDD Y3, Y7, Y7
|
||||
XOR_AVX2(DI, SI, 128, Y4, Y5, Y6, Y7, Y12, Y13)
|
||||
VPADDQ TWO, Y3, Y3
|
||||
|
||||
VPADDD Y0, Y8, Y8
|
||||
VPADDD Y1, Y9, Y9
|
||||
VPADDD Y2, Y10, Y10
|
||||
VPADDD Y3, Y11, Y11
|
||||
XOR_AVX2(DI, SI, 256, Y8, Y9, Y10, Y11, Y12, Y13)
|
||||
VPADDQ TWO, Y3, Y3
|
||||
|
||||
VPADDD TMP_0, Y0, Y12
|
||||
VPADDD TMP_1, Y1, Y13
|
||||
VPADDD Y2, Y14, Y14
|
||||
VPADDD Y3, Y15, Y15
|
||||
VPADDQ TWO, Y3, Y3
|
||||
|
||||
CMPQ CX, $512
|
||||
JB less_than_512
|
||||
|
||||
XOR_AVX2(DI, SI, 384, Y12, Y13, Y14, Y15, Y4, Y5)
|
||||
VMOVDQA Y3, STATE_3
|
||||
ADDQ $512, SI
|
||||
ADDQ $512, DI
|
||||
SUBQ $512, CX
|
||||
CMPQ CX, $448
|
||||
JA at_least_512
|
||||
|
||||
TESTQ CX, CX
|
||||
JZ done
|
||||
|
||||
VMOVDQA C16, Y14
|
||||
VMOVDQA C8, Y15
|
||||
|
||||
CMPQ CX, $64
|
||||
JBE between_0_and_64
|
||||
CMPQ CX, $192
|
||||
JBE between_64_and_192
|
||||
CMPQ CX, $320
|
||||
JBE between_192_and_320
|
||||
JMP between_320_and_448
|
||||
|
||||
less_than_512:
|
||||
XOR_UPPER_AVX2(DI, SI, 384, Y12, Y13, Y14, Y15, Y4, Y5)
|
||||
EXTRACT_LOWER(BX, Y12, Y13, Y14, Y15, Y4)
|
||||
ADDQ $448, SI
|
||||
ADDQ $448, DI
|
||||
SUBQ $448, CX
|
||||
JMP finalize
|
||||
|
||||
between_320_and_448:
|
||||
VMOVDQA Y0, Y4
|
||||
VMOVDQA Y1, Y5
|
||||
VMOVDQA Y2, Y6
|
||||
VPADDQ TWO, Y3, Y7
|
||||
VMOVDQA Y0, Y8
|
||||
VMOVDQA Y1, Y9
|
||||
VMOVDQA Y2, Y10
|
||||
VPADDQ TWO, Y7, Y11
|
||||
|
||||
MOVQ DX, R9
|
||||
|
||||
chacha_loop_384:
|
||||
CHACHA_QROUND_AVX(Y0, Y1, Y2, Y3, Y13, Y14, Y15)
|
||||
CHACHA_QROUND_AVX(Y4, Y5, Y6, Y7, Y13, Y14, Y15)
|
||||
CHACHA_QROUND_AVX(Y8, Y9, Y10, Y11, Y13, Y14, Y15)
|
||||
CHACHA_SHUFFLE_AVX(Y1, Y2, Y3)
|
||||
CHACHA_SHUFFLE_AVX(Y5, Y6, Y7)
|
||||
CHACHA_SHUFFLE_AVX(Y9, Y10, Y11)
|
||||
CHACHA_QROUND_AVX(Y0, Y1, Y2, Y3, Y13, Y14, Y15)
|
||||
CHACHA_QROUND_AVX(Y4, Y5, Y6, Y7, Y13, Y14, Y15)
|
||||
CHACHA_QROUND_AVX(Y8, Y9, Y10, Y11, Y13, Y14, Y15)
|
||||
CHACHA_SHUFFLE_AVX(Y3, Y2, Y1)
|
||||
CHACHA_SHUFFLE_AVX(Y7, Y6, Y5)
|
||||
CHACHA_SHUFFLE_AVX(Y11, Y10, Y9)
|
||||
SUBQ $2, R9
|
||||
JA chacha_loop_384
|
||||
|
||||
VPADDD STATE_0, Y0, Y0
|
||||
VPADDD STATE_1, Y1, Y1
|
||||
VPADDD STATE_2, Y2, Y2
|
||||
VPADDD STATE_3, Y3, Y3
|
||||
XOR_AVX2(DI, SI, 0, Y0, Y1, Y2, Y3, Y12, Y13)
|
||||
VMOVDQA STATE_0, Y0
|
||||
VMOVDQA STATE_1, Y1
|
||||
VMOVDQA STATE_2, Y2
|
||||
VMOVDQA STATE_3, Y3
|
||||
VPADDQ TWO, Y3, Y3
|
||||
|
||||
VPADDD Y0, Y4, Y4
|
||||
VPADDD Y1, Y5, Y5
|
||||
VPADDD Y2, Y6, Y6
|
||||
VPADDD Y3, Y7, Y7
|
||||
XOR_AVX2(DI, SI, 128, Y4, Y5, Y6, Y7, Y12, Y13)
|
||||
VPADDQ TWO, Y3, Y3
|
||||
|
||||
VPADDD Y0, Y8, Y8
|
||||
VPADDD Y1, Y9, Y9
|
||||
VPADDD Y2, Y10, Y10
|
||||
VPADDD Y3, Y11, Y11
|
||||
VPADDQ TWO, Y3, Y3
|
||||
|
||||
CMPQ CX, $384
|
||||
JB less_than_384
|
||||
|
||||
XOR_AVX2(DI, SI, 256, Y8, Y9, Y10, Y11, Y12, Y13)
|
||||
SUBQ $384, CX
|
||||
TESTQ CX, CX
|
||||
JE done
|
||||
|
||||
ADDQ $384, SI
|
||||
ADDQ $384, DI
|
||||
JMP between_0_and_64
|
||||
|
||||
less_than_384:
|
||||
XOR_UPPER_AVX2(DI, SI, 256, Y8, Y9, Y10, Y11, Y12, Y13)
|
||||
EXTRACT_LOWER(BX, Y8, Y9, Y10, Y11, Y12)
|
||||
ADDQ $320, SI
|
||||
ADDQ $320, DI
|
||||
SUBQ $320, CX
|
||||
JMP finalize
|
||||
|
||||
between_192_and_320:
|
||||
VMOVDQA Y0, Y4
|
||||
VMOVDQA Y1, Y5
|
||||
VMOVDQA Y2, Y6
|
||||
VMOVDQA Y3, Y7
|
||||
VMOVDQA Y0, Y8
|
||||
VMOVDQA Y1, Y9
|
||||
VMOVDQA Y2, Y10
|
||||
VPADDQ TWO, Y3, Y11
|
||||
|
||||
MOVQ DX, R9
|
||||
|
||||
chacha_loop_256:
|
||||
CHACHA_QROUND_AVX(Y4, Y5, Y6, Y7, Y13, Y14, Y15)
|
||||
CHACHA_QROUND_AVX(Y8, Y9, Y10, Y11, Y13, Y14, Y15)
|
||||
CHACHA_SHUFFLE_AVX(Y5, Y6, Y7)
|
||||
CHACHA_SHUFFLE_AVX(Y9, Y10, Y11)
|
||||
CHACHA_QROUND_AVX(Y4, Y5, Y6, Y7, Y13, Y14, Y15)
|
||||
CHACHA_QROUND_AVX(Y8, Y9, Y10, Y11, Y13, Y14, Y15)
|
||||
CHACHA_SHUFFLE_AVX(Y7, Y6, Y5)
|
||||
CHACHA_SHUFFLE_AVX(Y11, Y10, Y9)
|
||||
SUBQ $2, R9
|
||||
JA chacha_loop_256
|
||||
|
||||
VPADDD Y0, Y4, Y4
|
||||
VPADDD Y1, Y5, Y5
|
||||
VPADDD Y2, Y6, Y6
|
||||
VPADDD Y3, Y7, Y7
|
||||
VPADDQ TWO, Y3, Y3
|
||||
XOR_AVX2(DI, SI, 0, Y4, Y5, Y6, Y7, Y12, Y13)
|
||||
VPADDD Y0, Y8, Y8
|
||||
VPADDD Y1, Y9, Y9
|
||||
VPADDD Y2, Y10, Y10
|
||||
VPADDD Y3, Y11, Y11
|
||||
VPADDQ TWO, Y3, Y3
|
||||
|
||||
CMPQ CX, $256
|
||||
JB less_than_256
|
||||
|
||||
XOR_AVX2(DI, SI, 128, Y8, Y9, Y10, Y11, Y12, Y13)
|
||||
SUBQ $256, CX
|
||||
TESTQ CX, CX
|
||||
JE done
|
||||
|
||||
ADDQ $256, SI
|
||||
ADDQ $256, DI
|
||||
JMP between_0_and_64
|
||||
|
||||
less_than_256:
|
||||
XOR_UPPER_AVX2(DI, SI, 128, Y8, Y9, Y10, Y11, Y12, Y13)
|
||||
EXTRACT_LOWER(BX, Y8, Y9, Y10, Y11, Y12)
|
||||
ADDQ $192, SI
|
||||
ADDQ $192, DI
|
||||
SUBQ $192, CX
|
||||
JMP finalize
|
||||
|
||||
between_64_and_192:
|
||||
VMOVDQA Y0, Y4
|
||||
VMOVDQA Y1, Y5
|
||||
VMOVDQA Y2, Y6
|
||||
VMOVDQA Y3, Y7
|
||||
|
||||
MOVQ DX, R9
|
||||
|
||||
chacha_loop_128:
|
||||
CHACHA_QROUND_AVX(Y4, Y5, Y6, Y7, Y13, Y14, Y15)
|
||||
CHACHA_SHUFFLE_AVX(Y5, Y6, Y7)
|
||||
CHACHA_QROUND_AVX(Y4, Y5, Y6, Y7, Y13, Y14, Y15)
|
||||
CHACHA_SHUFFLE_AVX(Y7, Y6, Y5)
|
||||
SUBQ $2, R9
|
||||
JA chacha_loop_128
|
||||
|
||||
VPADDD Y0, Y4, Y4
|
||||
VPADDD Y1, Y5, Y5
|
||||
VPADDD Y2, Y6, Y6
|
||||
VPADDD Y3, Y7, Y7
|
||||
VPADDQ TWO, Y3, Y3
|
||||
|
||||
CMPQ CX, $128
|
||||
JB less_than_128
|
||||
|
||||
XOR_AVX2(DI, SI, 0, Y4, Y5, Y6, Y7, Y12, Y13)
|
||||
SUBQ $128, CX
|
||||
TESTQ CX, CX
|
||||
JE done
|
||||
|
||||
ADDQ $128, SI
|
||||
ADDQ $128, DI
|
||||
JMP between_0_and_64
|
||||
|
||||
less_than_128:
|
||||
XOR_UPPER_AVX2(DI, SI, 0, Y4, Y5, Y6, Y7, Y12, Y13)
|
||||
EXTRACT_LOWER(BX, Y4, Y5, Y6, Y7, Y13)
|
||||
ADDQ $64, SI
|
||||
ADDQ $64, DI
|
||||
SUBQ $64, CX
|
||||
JMP finalize
|
||||
|
||||
between_0_and_64:
|
||||
VMOVDQA X0, X4
|
||||
VMOVDQA X1, X5
|
||||
VMOVDQA X2, X6
|
||||
VMOVDQA X3, X7
|
||||
|
||||
MOVQ DX, R9
|
||||
|
||||
chacha_loop_64:
|
||||
CHACHA_QROUND_AVX(X4, X5, X6, X7, X13, X14, X15)
|
||||
CHACHA_SHUFFLE_AVX(X5, X6, X7)
|
||||
CHACHA_QROUND_AVX(X4, X5, X6, X7, X13, X14, X15)
|
||||
CHACHA_SHUFFLE_AVX(X7, X6, X5)
|
||||
SUBQ $2, R9
|
||||
JA chacha_loop_64
|
||||
|
||||
VPADDD X0, X4, X4
|
||||
VPADDD X1, X5, X5
|
||||
VPADDD X2, X6, X6
|
||||
VPADDD X3, X7, X7
|
||||
VMOVDQU ·one<>(SB), X0
|
||||
VPADDQ X0, X3, X3
|
||||
|
||||
CMPQ CX, $64
|
||||
JB less_than_64
|
||||
|
||||
XOR_AVX(DI, SI, 0, X4, X5, X6, X7, X13)
|
||||
SUBQ $64, CX
|
||||
JMP done
|
||||
|
||||
less_than_64:
|
||||
VMOVDQU X4, 0(BX)
|
||||
VMOVDQU X5, 16(BX)
|
||||
VMOVDQU X6, 32(BX)
|
||||
VMOVDQU X7, 48(BX)
|
||||
|
||||
finalize:
|
||||
XORQ R11, R11
|
||||
XORQ R12, R12
|
||||
MOVQ CX, BP
|
||||
|
||||
xor_loop:
|
||||
MOVB 0(SI), R11
|
||||
MOVB 0(BX), R12
|
||||
XORQ R11, R12
|
||||
MOVB R12, 0(DI)
|
||||
INCQ SI
|
||||
INCQ BX
|
||||
INCQ DI
|
||||
DECQ BP
|
||||
JA xor_loop
|
||||
|
||||
done:
|
||||
VMOVDQU X3, 48(AX)
|
||||
VZEROUPPER
|
||||
MOVQ R8, SP
|
||||
MOVQ CX, ret+72(FP)
|
||||
RET
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright (c) 2016 Andreas Auernhammer. All rights reserved.
|
||||
// Use of this source code is governed by a license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build 386,!gccgo,!appengine,!nacl
|
||||
|
||||
package chacha
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
func init() {
|
||||
useSSE2 = cpu.X86.HasSSE2
|
||||
useSSSE3 = cpu.X86.HasSSSE3
|
||||
useAVX = false
|
||||
useAVX2 = false
|
||||
}
|
||||
|
||||
func initialize(state *[64]byte, key []byte, nonce *[16]byte) {
|
||||
binary.LittleEndian.PutUint32(state[0:], sigma[0])
|
||||
binary.LittleEndian.PutUint32(state[4:], sigma[1])
|
||||
binary.LittleEndian.PutUint32(state[8:], sigma[2])
|
||||
binary.LittleEndian.PutUint32(state[12:], sigma[3])
|
||||
copy(state[16:], key[:])
|
||||
copy(state[48:], nonce[:])
|
||||
}
|
||||
|
||||
// This function is implemented in chacha_386.s
|
||||
//go:noescape
|
||||
func hChaCha20SSE2(out *[32]byte, nonce *[16]byte, key *[32]byte)
|
||||
|
||||
// This function is implemented in chacha_386.s
|
||||
//go:noescape
|
||||
func hChaCha20SSSE3(out *[32]byte, nonce *[16]byte, key *[32]byte)
|
||||
|
||||
// This function is implemented in chacha_386.s
|
||||
//go:noescape
|
||||
func xorKeyStreamSSE2(dst, src []byte, block, state *[64]byte, rounds int) int
|
||||
|
||||
func hChaCha20(out *[32]byte, nonce *[16]byte, key *[32]byte) {
|
||||
switch {
|
||||
case useSSSE3:
|
||||
hChaCha20SSSE3(out, nonce, key)
|
||||
case useSSE2:
|
||||
hChaCha20SSE2(out, nonce, key)
|
||||
default:
|
||||
hChaCha20Generic(out, nonce, key)
|
||||
}
|
||||
}
|
||||
|
||||
func xorKeyStream(dst, src []byte, block, state *[64]byte, rounds int) int {
|
||||
if useSSE2 {
|
||||
return xorKeyStreamSSE2(dst, src, block, state, rounds)
|
||||
} else {
|
||||
return xorKeyStreamGeneric(dst, src, block, state, rounds)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
// Copyright (c) 2016 Andreas Auernhammer. All rights reserved.
|
||||
// Use of this source code is governed by a license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build 386,!gccgo,!appengine,!nacl
|
||||
|
||||
#include "const.s"
|
||||
#include "macro.s"
|
||||
|
||||
// FINALIZE xors len bytes from src and block using
|
||||
// the temp. registers t0 and t1 and writes the result
|
||||
// to dst.
|
||||
#define FINALIZE(dst, src, block, len, t0, t1) \
|
||||
XORL t0, t0; \
|
||||
XORL t1, t1; \
|
||||
FINALIZE_LOOP:; \
|
||||
MOVB 0(src), t0; \
|
||||
MOVB 0(block), t1; \
|
||||
XORL t0, t1; \
|
||||
MOVB t1, 0(dst); \
|
||||
INCL src; \
|
||||
INCL block; \
|
||||
INCL dst; \
|
||||
DECL len; \
|
||||
JG FINALIZE_LOOP \
|
||||
|
||||
#define Dst DI
|
||||
#define Nonce AX
|
||||
#define Key BX
|
||||
#define Rounds DX
|
||||
|
||||
// func hChaCha20SSE2(out *[32]byte, nonce *[16]byte, key *[32]byte)
|
||||
TEXT ·hChaCha20SSE2(SB), 4, $0-12
|
||||
MOVL out+0(FP), Dst
|
||||
MOVL nonce+4(FP), Nonce
|
||||
MOVL key+8(FP), Key
|
||||
|
||||
MOVOU ·sigma<>(SB), X0
|
||||
MOVOU 0*16(Key), X1
|
||||
MOVOU 1*16(Key), X2
|
||||
MOVOU 0*16(Nonce), X3
|
||||
MOVL $20, Rounds
|
||||
|
||||
chacha_loop:
|
||||
CHACHA_QROUND_SSE2(X0, X1, X2, X3, X4)
|
||||
CHACHA_SHUFFLE_SSE(X1, X2, X3)
|
||||
CHACHA_QROUND_SSE2(X0, X1, X2, X3, X4)
|
||||
CHACHA_SHUFFLE_SSE(X3, X2, X1)
|
||||
SUBL $2, Rounds
|
||||
JNZ chacha_loop
|
||||
|
||||
MOVOU X0, 0*16(Dst)
|
||||
MOVOU X3, 1*16(Dst)
|
||||
RET
|
||||
|
||||
// func hChaCha20SSSE3(out *[32]byte, nonce *[16]byte, key *[32]byte)
|
||||
TEXT ·hChaCha20SSSE3(SB), 4, $0-12
|
||||
MOVL out+0(FP), Dst
|
||||
MOVL nonce+4(FP), Nonce
|
||||
MOVL key+8(FP), Key
|
||||
|
||||
MOVOU ·sigma<>(SB), X0
|
||||
MOVOU 0*16(Key), X1
|
||||
MOVOU 1*16(Key), X2
|
||||
MOVOU 0*16(Nonce), X3
|
||||
MOVL $20, Rounds
|
||||
|
||||
MOVOU ·rol16<>(SB), X5
|
||||
MOVOU ·rol8<>(SB), X6
|
||||
|
||||
chacha_loop:
|
||||
CHACHA_QROUND_SSSE3(X0, X1, X2, X3, X4, X5, X6)
|
||||
CHACHA_SHUFFLE_SSE(X1, X2, X3)
|
||||
CHACHA_QROUND_SSSE3(X0, X1, X2, X3, X4, X5, X6)
|
||||
CHACHA_SHUFFLE_SSE(X3, X2, X1)
|
||||
SUBL $2, Rounds
|
||||
JNZ chacha_loop
|
||||
|
||||
MOVOU X0, 0*16(Dst)
|
||||
MOVOU X3, 1*16(Dst)
|
||||
RET
|
||||
|
||||
#undef Dst
|
||||
#undef Nonce
|
||||
#undef Key
|
||||
#undef Rounds
|
||||
|
||||
#define State AX
|
||||
#define Dst DI
|
||||
#define Src SI
|
||||
#define Len DX
|
||||
#define Tmp0 BX
|
||||
#define Tmp1 BP
|
||||
|
||||
// func xorKeyStreamSSE2(dst, src []byte, block, state *[64]byte, rounds int) int
|
||||
TEXT ·xorKeyStreamSSE2(SB), 4, $0-40
|
||||
MOVL dst_base+0(FP), Dst
|
||||
MOVL src_base+12(FP), Src
|
||||
MOVL state+28(FP), State
|
||||
MOVL src_len+16(FP), Len
|
||||
MOVL $0, ret+36(FP) // Number of bytes written to the keystream buffer - 0 iff len mod 64 == 0
|
||||
|
||||
MOVOU 0*16(State), X0
|
||||
MOVOU 1*16(State), X1
|
||||
MOVOU 2*16(State), X2
|
||||
MOVOU 3*16(State), X3
|
||||
TESTL Len, Len
|
||||
JZ DONE
|
||||
|
||||
GENERATE_KEYSTREAM:
|
||||
MOVO X0, X4
|
||||
MOVO X1, X5
|
||||
MOVO X2, X6
|
||||
MOVO X3, X7
|
||||
MOVL rounds+32(FP), Tmp0
|
||||
|
||||
CHACHA_LOOP:
|
||||
CHACHA_QROUND_SSE2(X4, X5, X6, X7, X0)
|
||||
CHACHA_SHUFFLE_SSE(X5, X6, X7)
|
||||
CHACHA_QROUND_SSE2(X4, X5, X6, X7, X0)
|
||||
CHACHA_SHUFFLE_SSE(X7, X6, X5)
|
||||
SUBL $2, Tmp0
|
||||
JA CHACHA_LOOP
|
||||
|
||||
MOVOU 0*16(State), X0 // Restore X0 from state
|
||||
PADDL X0, X4
|
||||
PADDL X1, X5
|
||||
PADDL X2, X6
|
||||
PADDL X3, X7
|
||||
MOVOU ·one<>(SB), X0
|
||||
PADDQ X0, X3
|
||||
|
||||
CMPL Len, $64
|
||||
JL BUFFER_KEYSTREAM
|
||||
|
||||
XOR_SSE(Dst, Src, 0, X4, X5, X6, X7, X0)
|
||||
MOVOU 0*16(State), X0 // Restore X0 from state
|
||||
ADDL $64, Src
|
||||
ADDL $64, Dst
|
||||
SUBL $64, Len
|
||||
JZ DONE
|
||||
JMP GENERATE_KEYSTREAM // There is at least one more plaintext byte
|
||||
|
||||
BUFFER_KEYSTREAM:
|
||||
MOVL block+24(FP), State
|
||||
MOVOU X4, 0(State)
|
||||
MOVOU X5, 16(State)
|
||||
MOVOU X6, 32(State)
|
||||
MOVOU X7, 48(State)
|
||||
MOVL Len, ret+36(FP) // Number of bytes written to the keystream buffer - 0 < Len < 64
|
||||
FINALIZE(Dst, Src, State, Len, Tmp0, Tmp1)
|
||||
|
||||
DONE:
|
||||
MOVL state+28(FP), State
|
||||
MOVOU X3, 3*16(State)
|
||||
RET
|
||||
|
||||
#undef State
|
||||
#undef Dst
|
||||
#undef Src
|
||||
#undef Len
|
||||
#undef Tmp0
|
||||
#undef Tmp1
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright (c) 2017 Andreas Auernhammer. All rights reserved.
|
||||
// Use of this source code is governed by a license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build go1.7,amd64,!gccgo,!appengine,!nacl
|
||||
|
||||
package chacha
|
||||
|
||||
import "golang.org/x/sys/cpu"
|
||||
|
||||
func init() {
|
||||
useSSE2 = cpu.X86.HasSSE2
|
||||
useSSSE3 = cpu.X86.HasSSSE3
|
||||
useAVX = cpu.X86.HasAVX
|
||||
useAVX2 = cpu.X86.HasAVX2
|
||||
}
|
||||
|
||||
// This function is implemented in chacha_amd64.s
|
||||
//go:noescape
|
||||
func initialize(state *[64]byte, key []byte, nonce *[16]byte)
|
||||
|
||||
// This function is implemented in chacha_amd64.s
|
||||
//go:noescape
|
||||
func hChaCha20SSE2(out *[32]byte, nonce *[16]byte, key *[32]byte)
|
||||
|
||||
// This function is implemented in chacha_amd64.s
|
||||
//go:noescape
|
||||
func hChaCha20SSSE3(out *[32]byte, nonce *[16]byte, key *[32]byte)
|
||||
|
||||
// This function is implemented in chachaAVX2_amd64.s
|
||||
//go:noescape
|
||||
func hChaCha20AVX(out *[32]byte, nonce *[16]byte, key *[32]byte)
|
||||
|
||||
// This function is implemented in chacha_amd64.s
|
||||
//go:noescape
|
||||
func xorKeyStreamSSE2(dst, src []byte, block, state *[64]byte, rounds int) int
|
||||
|
||||
// This function is implemented in chacha_amd64.s
|
||||
//go:noescape
|
||||
func xorKeyStreamSSSE3(dst, src []byte, block, state *[64]byte, rounds int) int
|
||||
|
||||
// This function is implemented in chacha_amd64.s
|
||||
//go:noescape
|
||||
func xorKeyStreamAVX(dst, src []byte, block, state *[64]byte, rounds int) int
|
||||
|
||||
// This function is implemented in chachaAVX2_amd64.s
|
||||
//go:noescape
|
||||
func xorKeyStreamAVX2(dst, src []byte, block, state *[64]byte, rounds int) int
|
||||
|
||||
func hChaCha20(out *[32]byte, nonce *[16]byte, key *[32]byte) {
|
||||
switch {
|
||||
case useAVX:
|
||||
hChaCha20AVX(out, nonce, key)
|
||||
case useSSSE3:
|
||||
hChaCha20SSSE3(out, nonce, key)
|
||||
case useSSE2:
|
||||
hChaCha20SSE2(out, nonce, key)
|
||||
default:
|
||||
hChaCha20Generic(out, nonce, key)
|
||||
}
|
||||
}
|
||||
|
||||
func xorKeyStream(dst, src []byte, block, state *[64]byte, rounds int) int {
|
||||
switch {
|
||||
case useAVX2:
|
||||
return xorKeyStreamAVX2(dst, src, block, state, rounds)
|
||||
case useAVX:
|
||||
return xorKeyStreamAVX(dst, src, block, state, rounds)
|
||||
case useSSSE3:
|
||||
return xorKeyStreamSSSE3(dst, src, block, state, rounds)
|
||||
case useSSE2:
|
||||
return xorKeyStreamSSE2(dst, src, block, state, rounds)
|
||||
default:
|
||||
return xorKeyStreamGeneric(dst, src, block, state, rounds)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,319 @@
|
|||
// Copyright (c) 2016 Andreas Auernhammer. All rights reserved.
|
||||
// Use of this source code is governed by a license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package chacha
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
var sigma = [4]uint32{0x61707865, 0x3320646e, 0x79622d32, 0x6b206574}
|
||||
|
||||
func xorKeyStreamGeneric(dst, src []byte, block, state *[64]byte, rounds int) int {
|
||||
for len(src) >= 64 {
|
||||
chachaGeneric(block, state, rounds)
|
||||
|
||||
for i, v := range block {
|
||||
dst[i] = src[i] ^ v
|
||||
}
|
||||
src = src[64:]
|
||||
dst = dst[64:]
|
||||
}
|
||||
|
||||
n := len(src)
|
||||
if n > 0 {
|
||||
chachaGeneric(block, state, rounds)
|
||||
for i, v := range src {
|
||||
dst[i] = v ^ block[i]
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func chachaGeneric(dst *[64]byte, state *[64]byte, rounds int) {
|
||||
v00 := binary.LittleEndian.Uint32(state[0:])
|
||||
v01 := binary.LittleEndian.Uint32(state[4:])
|
||||
v02 := binary.LittleEndian.Uint32(state[8:])
|
||||
v03 := binary.LittleEndian.Uint32(state[12:])
|
||||
v04 := binary.LittleEndian.Uint32(state[16:])
|
||||
v05 := binary.LittleEndian.Uint32(state[20:])
|
||||
v06 := binary.LittleEndian.Uint32(state[24:])
|
||||
v07 := binary.LittleEndian.Uint32(state[28:])
|
||||
v08 := binary.LittleEndian.Uint32(state[32:])
|
||||
v09 := binary.LittleEndian.Uint32(state[36:])
|
||||
v10 := binary.LittleEndian.Uint32(state[40:])
|
||||
v11 := binary.LittleEndian.Uint32(state[44:])
|
||||
v12 := binary.LittleEndian.Uint32(state[48:])
|
||||
v13 := binary.LittleEndian.Uint32(state[52:])
|
||||
v14 := binary.LittleEndian.Uint32(state[56:])
|
||||
v15 := binary.LittleEndian.Uint32(state[60:])
|
||||
|
||||
s00, s01, s02, s03, s04, s05, s06, s07 := v00, v01, v02, v03, v04, v05, v06, v07
|
||||
s08, s09, s10, s11, s12, s13, s14, s15 := v08, v09, v10, v11, v12, v13, v14, v15
|
||||
|
||||
for i := 0; i < rounds; i += 2 {
|
||||
v00 += v04
|
||||
v12 ^= v00
|
||||
v12 = (v12 << 16) | (v12 >> 16)
|
||||
v08 += v12
|
||||
v04 ^= v08
|
||||
v04 = (v04 << 12) | (v04 >> 20)
|
||||
v00 += v04
|
||||
v12 ^= v00
|
||||
v12 = (v12 << 8) | (v12 >> 24)
|
||||
v08 += v12
|
||||
v04 ^= v08
|
||||
v04 = (v04 << 7) | (v04 >> 25)
|
||||
v01 += v05
|
||||
v13 ^= v01
|
||||
v13 = (v13 << 16) | (v13 >> 16)
|
||||
v09 += v13
|
||||
v05 ^= v09
|
||||
v05 = (v05 << 12) | (v05 >> 20)
|
||||
v01 += v05
|
||||
v13 ^= v01
|
||||
v13 = (v13 << 8) | (v13 >> 24)
|
||||
v09 += v13
|
||||
v05 ^= v09
|
||||
v05 = (v05 << 7) | (v05 >> 25)
|
||||
v02 += v06
|
||||
v14 ^= v02
|
||||
v14 = (v14 << 16) | (v14 >> 16)
|
||||
v10 += v14
|
||||
v06 ^= v10
|
||||
v06 = (v06 << 12) | (v06 >> 20)
|
||||
v02 += v06
|
||||
v14 ^= v02
|
||||
v14 = (v14 << 8) | (v14 >> 24)
|
||||
v10 += v14
|
||||
v06 ^= v10
|
||||
v06 = (v06 << 7) | (v06 >> 25)
|
||||
v03 += v07
|
||||
v15 ^= v03
|
||||
v15 = (v15 << 16) | (v15 >> 16)
|
||||
v11 += v15
|
||||
v07 ^= v11
|
||||
v07 = (v07 << 12) | (v07 >> 20)
|
||||
v03 += v07
|
||||
v15 ^= v03
|
||||
v15 = (v15 << 8) | (v15 >> 24)
|
||||
v11 += v15
|
||||
v07 ^= v11
|
||||
v07 = (v07 << 7) | (v07 >> 25)
|
||||
v00 += v05
|
||||
v15 ^= v00
|
||||
v15 = (v15 << 16) | (v15 >> 16)
|
||||
v10 += v15
|
||||
v05 ^= v10
|
||||
v05 = (v05 << 12) | (v05 >> 20)
|
||||
v00 += v05
|
||||
v15 ^= v00
|
||||
v15 = (v15 << 8) | (v15 >> 24)
|
||||
v10 += v15
|
||||
v05 ^= v10
|
||||
v05 = (v05 << 7) | (v05 >> 25)
|
||||
v01 += v06
|
||||
v12 ^= v01
|
||||
v12 = (v12 << 16) | (v12 >> 16)
|
||||
v11 += v12
|
||||
v06 ^= v11
|
||||
v06 = (v06 << 12) | (v06 >> 20)
|
||||
v01 += v06
|
||||
v12 ^= v01
|
||||
v12 = (v12 << 8) | (v12 >> 24)
|
||||
v11 += v12
|
||||
v06 ^= v11
|
||||
v06 = (v06 << 7) | (v06 >> 25)
|
||||
v02 += v07
|
||||
v13 ^= v02
|
||||
v13 = (v13 << 16) | (v13 >> 16)
|
||||
v08 += v13
|
||||
v07 ^= v08
|
||||
v07 = (v07 << 12) | (v07 >> 20)
|
||||
v02 += v07
|
||||
v13 ^= v02
|
||||
v13 = (v13 << 8) | (v13 >> 24)
|
||||
v08 += v13
|
||||
v07 ^= v08
|
||||
v07 = (v07 << 7) | (v07 >> 25)
|
||||
v03 += v04
|
||||
v14 ^= v03
|
||||
v14 = (v14 << 16) | (v14 >> 16)
|
||||
v09 += v14
|
||||
v04 ^= v09
|
||||
v04 = (v04 << 12) | (v04 >> 20)
|
||||
v03 += v04
|
||||
v14 ^= v03
|
||||
v14 = (v14 << 8) | (v14 >> 24)
|
||||
v09 += v14
|
||||
v04 ^= v09
|
||||
v04 = (v04 << 7) | (v04 >> 25)
|
||||
}
|
||||
|
||||
v00 += s00
|
||||
v01 += s01
|
||||
v02 += s02
|
||||
v03 += s03
|
||||
v04 += s04
|
||||
v05 += s05
|
||||
v06 += s06
|
||||
v07 += s07
|
||||
v08 += s08
|
||||
v09 += s09
|
||||
v10 += s10
|
||||
v11 += s11
|
||||
v12 += s12
|
||||
v13 += s13
|
||||
v14 += s14
|
||||
v15 += s15
|
||||
|
||||
s12++
|
||||
binary.LittleEndian.PutUint32(state[48:], s12)
|
||||
if s12 == 0 { // indicates overflow
|
||||
s13++
|
||||
binary.LittleEndian.PutUint32(state[52:], s13)
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(dst[0:], v00)
|
||||
binary.LittleEndian.PutUint32(dst[4:], v01)
|
||||
binary.LittleEndian.PutUint32(dst[8:], v02)
|
||||
binary.LittleEndian.PutUint32(dst[12:], v03)
|
||||
binary.LittleEndian.PutUint32(dst[16:], v04)
|
||||
binary.LittleEndian.PutUint32(dst[20:], v05)
|
||||
binary.LittleEndian.PutUint32(dst[24:], v06)
|
||||
binary.LittleEndian.PutUint32(dst[28:], v07)
|
||||
binary.LittleEndian.PutUint32(dst[32:], v08)
|
||||
binary.LittleEndian.PutUint32(dst[36:], v09)
|
||||
binary.LittleEndian.PutUint32(dst[40:], v10)
|
||||
binary.LittleEndian.PutUint32(dst[44:], v11)
|
||||
binary.LittleEndian.PutUint32(dst[48:], v12)
|
||||
binary.LittleEndian.PutUint32(dst[52:], v13)
|
||||
binary.LittleEndian.PutUint32(dst[56:], v14)
|
||||
binary.LittleEndian.PutUint32(dst[60:], v15)
|
||||
}
|
||||
|
||||
func hChaCha20Generic(out *[32]byte, nonce *[16]byte, key *[32]byte) {
|
||||
v00 := sigma[0]
|
||||
v01 := sigma[1]
|
||||
v02 := sigma[2]
|
||||
v03 := sigma[3]
|
||||
v04 := binary.LittleEndian.Uint32(key[0:])
|
||||
v05 := binary.LittleEndian.Uint32(key[4:])
|
||||
v06 := binary.LittleEndian.Uint32(key[8:])
|
||||
v07 := binary.LittleEndian.Uint32(key[12:])
|
||||
v08 := binary.LittleEndian.Uint32(key[16:])
|
||||
v09 := binary.LittleEndian.Uint32(key[20:])
|
||||
v10 := binary.LittleEndian.Uint32(key[24:])
|
||||
v11 := binary.LittleEndian.Uint32(key[28:])
|
||||
v12 := binary.LittleEndian.Uint32(nonce[0:])
|
||||
v13 := binary.LittleEndian.Uint32(nonce[4:])
|
||||
v14 := binary.LittleEndian.Uint32(nonce[8:])
|
||||
v15 := binary.LittleEndian.Uint32(nonce[12:])
|
||||
|
||||
for i := 0; i < 20; i += 2 {
|
||||
v00 += v04
|
||||
v12 ^= v00
|
||||
v12 = (v12 << 16) | (v12 >> 16)
|
||||
v08 += v12
|
||||
v04 ^= v08
|
||||
v04 = (v04 << 12) | (v04 >> 20)
|
||||
v00 += v04
|
||||
v12 ^= v00
|
||||
v12 = (v12 << 8) | (v12 >> 24)
|
||||
v08 += v12
|
||||
v04 ^= v08
|
||||
v04 = (v04 << 7) | (v04 >> 25)
|
||||
v01 += v05
|
||||
v13 ^= v01
|
||||
v13 = (v13 << 16) | (v13 >> 16)
|
||||
v09 += v13
|
||||
v05 ^= v09
|
||||
v05 = (v05 << 12) | (v05 >> 20)
|
||||
v01 += v05
|
||||
v13 ^= v01
|
||||
v13 = (v13 << 8) | (v13 >> 24)
|
||||
v09 += v13
|
||||
v05 ^= v09
|
||||
v05 = (v05 << 7) | (v05 >> 25)
|
||||
v02 += v06
|
||||
v14 ^= v02
|
||||
v14 = (v14 << 16) | (v14 >> 16)
|
||||
v10 += v14
|
||||
v06 ^= v10
|
||||
v06 = (v06 << 12) | (v06 >> 20)
|
||||
v02 += v06
|
||||
v14 ^= v02
|
||||
v14 = (v14 << 8) | (v14 >> 24)
|
||||
v10 += v14
|
||||
v06 ^= v10
|
||||
v06 = (v06 << 7) | (v06 >> 25)
|
||||
v03 += v07
|
||||
v15 ^= v03
|
||||
v15 = (v15 << 16) | (v15 >> 16)
|
||||
v11 += v15
|
||||
v07 ^= v11
|
||||
v07 = (v07 << 12) | (v07 >> 20)
|
||||
v03 += v07
|
||||
v15 ^= v03
|
||||
v15 = (v15 << 8) | (v15 >> 24)
|
||||
v11 += v15
|
||||
v07 ^= v11
|
||||
v07 = (v07 << 7) | (v07 >> 25)
|
||||
v00 += v05
|
||||
v15 ^= v00
|
||||
v15 = (v15 << 16) | (v15 >> 16)
|
||||
v10 += v15
|
||||
v05 ^= v10
|
||||
v05 = (v05 << 12) | (v05 >> 20)
|
||||
v00 += v05
|
||||
v15 ^= v00
|
||||
v15 = (v15 << 8) | (v15 >> 24)
|
||||
v10 += v15
|
||||
v05 ^= v10
|
||||
v05 = (v05 << 7) | (v05 >> 25)
|
||||
v01 += v06
|
||||
v12 ^= v01
|
||||
v12 = (v12 << 16) | (v12 >> 16)
|
||||
v11 += v12
|
||||
v06 ^= v11
|
||||
v06 = (v06 << 12) | (v06 >> 20)
|
||||
v01 += v06
|
||||
v12 ^= v01
|
||||
v12 = (v12 << 8) | (v12 >> 24)
|
||||
v11 += v12
|
||||
v06 ^= v11
|
||||
v06 = (v06 << 7) | (v06 >> 25)
|
||||
v02 += v07
|
||||
v13 ^= v02
|
||||
v13 = (v13 << 16) | (v13 >> 16)
|
||||
v08 += v13
|
||||
v07 ^= v08
|
||||
v07 = (v07 << 12) | (v07 >> 20)
|
||||
v02 += v07
|
||||
v13 ^= v02
|
||||
v13 = (v13 << 8) | (v13 >> 24)
|
||||
v08 += v13
|
||||
v07 ^= v08
|
||||
v07 = (v07 << 7) | (v07 >> 25)
|
||||
v03 += v04
|
||||
v14 ^= v03
|
||||
v14 = (v14 << 16) | (v14 >> 16)
|
||||
v09 += v14
|
||||
v04 ^= v09
|
||||
v04 = (v04 << 12) | (v04 >> 20)
|
||||
v03 += v04
|
||||
v14 ^= v03
|
||||
v14 = (v14 << 8) | (v14 >> 24)
|
||||
v09 += v14
|
||||
v04 ^= v09
|
||||
v04 = (v04 << 7) | (v04 >> 25)
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(out[0:], v00)
|
||||
binary.LittleEndian.PutUint32(out[4:], v01)
|
||||
binary.LittleEndian.PutUint32(out[8:], v02)
|
||||
binary.LittleEndian.PutUint32(out[12:], v03)
|
||||
binary.LittleEndian.PutUint32(out[16:], v12)
|
||||
binary.LittleEndian.PutUint32(out[20:], v13)
|
||||
binary.LittleEndian.PutUint32(out[24:], v14)
|
||||
binary.LittleEndian.PutUint32(out[28:], v15)
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) 2016 Andreas Auernhammer. All rights reserved.
|
||||
// Use of this source code is governed by a license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build !amd64,!386 gccgo appengine nacl
|
||||
|
||||
package chacha
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
func init() {
|
||||
useSSE2 = false
|
||||
useSSSE3 = false
|
||||
useAVX = false
|
||||
useAVX2 = false
|
||||
}
|
||||
|
||||
func initialize(state *[64]byte, key []byte, nonce *[16]byte) {
|
||||
binary.LittleEndian.PutUint32(state[0:], sigma[0])
|
||||
binary.LittleEndian.PutUint32(state[4:], sigma[1])
|
||||
binary.LittleEndian.PutUint32(state[8:], sigma[2])
|
||||
binary.LittleEndian.PutUint32(state[12:], sigma[3])
|
||||
copy(state[16:], key[:])
|
||||
copy(state[48:], nonce[:])
|
||||
}
|
||||
|
||||
func xorKeyStream(dst, src []byte, block, state *[64]byte, rounds int) int {
|
||||
return xorKeyStreamGeneric(dst, src, block, state, rounds)
|
||||
}
|
||||
|
||||
func hChaCha20(out *[32]byte, nonce *[16]byte, key *[32]byte) {
|
||||
hChaCha20Generic(out, nonce, key)
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) 2018 Andreas Auernhammer. All rights reserved.
|
||||
// Use of this source code is governed by a license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build 386,!gccgo,!appengine,!nacl amd64,!gccgo,!appengine,!nacl
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
DATA ·sigma<>+0x00(SB)/4, $0x61707865
|
||||
DATA ·sigma<>+0x04(SB)/4, $0x3320646e
|
||||
DATA ·sigma<>+0x08(SB)/4, $0x79622d32
|
||||
DATA ·sigma<>+0x0C(SB)/4, $0x6b206574
|
||||
GLOBL ·sigma<>(SB), (NOPTR+RODATA), $16 // The 4 ChaCha initialization constants
|
||||
|
||||
// SSE2/SSE3/AVX constants
|
||||
|
||||
DATA ·one<>+0x00(SB)/8, $1
|
||||
DATA ·one<>+0x08(SB)/8, $0
|
||||
GLOBL ·one<>(SB), (NOPTR+RODATA), $16 // The constant 1 as 128 bit value
|
||||
|
||||
DATA ·rol16<>+0x00(SB)/8, $0x0504070601000302
|
||||
DATA ·rol16<>+0x08(SB)/8, $0x0D0C0F0E09080B0A
|
||||
GLOBL ·rol16<>(SB), (NOPTR+RODATA), $16 // The PSHUFB 16 bit left rotate constant
|
||||
|
||||
DATA ·rol8<>+0x00(SB)/8, $0x0605040702010003
|
||||
DATA ·rol8<>+0x08(SB)/8, $0x0E0D0C0F0A09080B
|
||||
GLOBL ·rol8<>(SB), (NOPTR+RODATA), $16 // The PSHUFB 8 bit left rotate constant
|
||||
|
||||
// AVX2 constants
|
||||
|
||||
DATA ·one_AVX2<>+0x00(SB)/8, $0
|
||||
DATA ·one_AVX2<>+0x08(SB)/8, $0
|
||||
DATA ·one_AVX2<>+0x10(SB)/8, $1
|
||||
DATA ·one_AVX2<>+0x18(SB)/8, $0
|
||||
GLOBL ·one_AVX2<>(SB), (NOPTR+RODATA), $32 // The constant 1 as 256 bit value
|
||||
|
||||
DATA ·two_AVX2<>+0x00(SB)/8, $2
|
||||
DATA ·two_AVX2<>+0x08(SB)/8, $0
|
||||
DATA ·two_AVX2<>+0x10(SB)/8, $2
|
||||
DATA ·two_AVX2<>+0x18(SB)/8, $0
|
||||
GLOBL ·two_AVX2<>(SB), (NOPTR+RODATA), $32
|
||||
|
||||
DATA ·rol16_AVX2<>+0x00(SB)/8, $0x0504070601000302
|
||||
DATA ·rol16_AVX2<>+0x08(SB)/8, $0x0D0C0F0E09080B0A
|
||||
DATA ·rol16_AVX2<>+0x10(SB)/8, $0x0504070601000302
|
||||
DATA ·rol16_AVX2<>+0x18(SB)/8, $0x0D0C0F0E09080B0A
|
||||
GLOBL ·rol16_AVX2<>(SB), (NOPTR+RODATA), $32 // The VPSHUFB 16 bit left rotate constant
|
||||
|
||||
DATA ·rol8_AVX2<>+0x00(SB)/8, $0x0605040702010003
|
||||
DATA ·rol8_AVX2<>+0x08(SB)/8, $0x0E0D0C0F0A09080B
|
||||
DATA ·rol8_AVX2<>+0x10(SB)/8, $0x0605040702010003
|
||||
DATA ·rol8_AVX2<>+0x18(SB)/8, $0x0E0D0C0F0A09080B
|
||||
GLOBL ·rol8_AVX2<>(SB), (NOPTR+RODATA), $32 // The VPSHUFB 8 bit left rotate constant
|
|
@ -0,0 +1,163 @@
|
|||
// Copyright (c) 2018 Andreas Auernhammer. All rights reserved.
|
||||
// Use of this source code is governed by a license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build 386,!gccgo,!appengine,!nacl amd64,!gccgo,!appengine,!nacl
|
||||
|
||||
// ROTL_SSE rotates all 4 32 bit values of the XMM register v
|
||||
// left by n bits using SSE2 instructions (0 <= n <= 32).
|
||||
// The XMM register t is used as a temp. register.
|
||||
#define ROTL_SSE(n, t, v) \
|
||||
MOVO v, t; \
|
||||
PSLLL $n, t; \
|
||||
PSRLL $(32-n), v; \
|
||||
PXOR t, v
|
||||
|
||||
// ROTL_AVX rotates all 4/8 32 bit values of the AVX/AVX2 register v
|
||||
// left by n bits using AVX/AVX2 instructions (0 <= n <= 32).
|
||||
// The AVX/AVX2 register t is used as a temp. register.
|
||||
#define ROTL_AVX(n, t, v) \
|
||||
VPSLLD $n, v, t; \
|
||||
VPSRLD $(32-n), v, v; \
|
||||
VPXOR v, t, v
|
||||
|
||||
// CHACHA_QROUND_SSE2 performs a ChaCha quarter-round using the
|
||||
// 4 XMM registers v0, v1, v2 and v3. It uses only ROTL_SSE2 for
|
||||
// rotations. The XMM register t is used as a temp. register.
|
||||
#define CHACHA_QROUND_SSE2(v0, v1, v2, v3, t) \
|
||||
PADDL v1, v0; \
|
||||
PXOR v0, v3; \
|
||||
ROTL_SSE(16, t, v3); \
|
||||
PADDL v3, v2; \
|
||||
PXOR v2, v1; \
|
||||
ROTL_SSE(12, t, v1); \
|
||||
PADDL v1, v0; \
|
||||
PXOR v0, v3; \
|
||||
ROTL_SSE(8, t, v3); \
|
||||
PADDL v3, v2; \
|
||||
PXOR v2, v1; \
|
||||
ROTL_SSE(7, t, v1)
|
||||
|
||||
// CHACHA_QROUND_SSSE3 performs a ChaCha quarter-round using the
|
||||
// 4 XMM registers v0, v1, v2 and v3. It uses PSHUFB for 8/16 bit
|
||||
// rotations. The XMM register t is used as a temp. register.
|
||||
//
|
||||
// r16 holds the PSHUFB constant for a 16 bit left rotate.
|
||||
// r8 holds the PSHUFB constant for a 8 bit left rotate.
|
||||
#define CHACHA_QROUND_SSSE3(v0, v1, v2, v3, t, r16, r8) \
|
||||
PADDL v1, v0; \
|
||||
PXOR v0, v3; \
|
||||
PSHUFB r16, v3; \
|
||||
PADDL v3, v2; \
|
||||
PXOR v2, v1; \
|
||||
ROTL_SSE(12, t, v1); \
|
||||
PADDL v1, v0; \
|
||||
PXOR v0, v3; \
|
||||
PSHUFB r8, v3; \
|
||||
PADDL v3, v2; \
|
||||
PXOR v2, v1; \
|
||||
ROTL_SSE(7, t, v1)
|
||||
|
||||
// CHACHA_QROUND_AVX performs a ChaCha quarter-round using the
|
||||
// 4 AVX/AVX2 registers v0, v1, v2 and v3. It uses VPSHUFB for 8/16 bit
|
||||
// rotations. The AVX/AVX2 register t is used as a temp. register.
|
||||
//
|
||||
// r16 holds the VPSHUFB constant for a 16 bit left rotate.
|
||||
// r8 holds the VPSHUFB constant for a 8 bit left rotate.
|
||||
#define CHACHA_QROUND_AVX(v0, v1, v2, v3, t, r16, r8) \
|
||||
VPADDD v0, v1, v0; \
|
||||
VPXOR v3, v0, v3; \
|
||||
VPSHUFB r16, v3, v3; \
|
||||
VPADDD v2, v3, v2; \
|
||||
VPXOR v1, v2, v1; \
|
||||
ROTL_AVX(12, t, v1); \
|
||||
VPADDD v0, v1, v0; \
|
||||
VPXOR v3, v0, v3; \
|
||||
VPSHUFB r8, v3, v3; \
|
||||
VPADDD v2, v3, v2; \
|
||||
VPXOR v1, v2, v1; \
|
||||
ROTL_AVX(7, t, v1)
|
||||
|
||||
// CHACHA_SHUFFLE_SSE performs a ChaCha shuffle using the
|
||||
// 3 XMM registers v1, v2 and v3. The inverse shuffle is
|
||||
// performed by switching v1 and v3: CHACHA_SHUFFLE_SSE(v3, v2, v1).
|
||||
#define CHACHA_SHUFFLE_SSE(v1, v2, v3) \
|
||||
PSHUFL $0x39, v1, v1; \
|
||||
PSHUFL $0x4E, v2, v2; \
|
||||
PSHUFL $0x93, v3, v3
|
||||
|
||||
// CHACHA_SHUFFLE_AVX performs a ChaCha shuffle using the
|
||||
// 3 AVX/AVX2 registers v1, v2 and v3. The inverse shuffle is
|
||||
// performed by switching v1 and v3: CHACHA_SHUFFLE_AVX(v3, v2, v1).
|
||||
#define CHACHA_SHUFFLE_AVX(v1, v2, v3) \
|
||||
VPSHUFD $0x39, v1, v1; \
|
||||
VPSHUFD $0x4E, v2, v2; \
|
||||
VPSHUFD $0x93, v3, v3
|
||||
|
||||
// XOR_SSE extracts 4x16 byte vectors from src at
|
||||
// off, xors all vectors with the corresponding XMM
|
||||
// register (v0 - v3) and writes the result to dst
|
||||
// at off.
|
||||
// The XMM register t is used as a temp. register.
|
||||
#define XOR_SSE(dst, src, off, v0, v1, v2, v3, t) \
|
||||
MOVOU 0+off(src), t; \
|
||||
PXOR v0, t; \
|
||||
MOVOU t, 0+off(dst); \
|
||||
MOVOU 16+off(src), t; \
|
||||
PXOR v1, t; \
|
||||
MOVOU t, 16+off(dst); \
|
||||
MOVOU 32+off(src), t; \
|
||||
PXOR v2, t; \
|
||||
MOVOU t, 32+off(dst); \
|
||||
MOVOU 48+off(src), t; \
|
||||
PXOR v3, t; \
|
||||
MOVOU t, 48+off(dst)
|
||||
|
||||
// XOR_AVX extracts 4x16 byte vectors from src at
|
||||
// off, xors all vectors with the corresponding AVX
|
||||
// register (v0 - v3) and writes the result to dst
|
||||
// at off.
|
||||
// The XMM register t is used as a temp. register.
|
||||
#define XOR_AVX(dst, src, off, v0, v1, v2, v3, t) \
|
||||
VPXOR 0+off(src), v0, t; \
|
||||
VMOVDQU t, 0+off(dst); \
|
||||
VPXOR 16+off(src), v1, t; \
|
||||
VMOVDQU t, 16+off(dst); \
|
||||
VPXOR 32+off(src), v2, t; \
|
||||
VMOVDQU t, 32+off(dst); \
|
||||
VPXOR 48+off(src), v3, t; \
|
||||
VMOVDQU t, 48+off(dst)
|
||||
|
||||
#define XOR_AVX2(dst, src, off, v0, v1, v2, v3, t0, t1) \
|
||||
VMOVDQU (0+off)(src), t0; \
|
||||
VPERM2I128 $32, v1, v0, t1; \
|
||||
VPXOR t0, t1, t0; \
|
||||
VMOVDQU t0, (0+off)(dst); \
|
||||
VMOVDQU (32+off)(src), t0; \
|
||||
VPERM2I128 $32, v3, v2, t1; \
|
||||
VPXOR t0, t1, t0; \
|
||||
VMOVDQU t0, (32+off)(dst); \
|
||||
VMOVDQU (64+off)(src), t0; \
|
||||
VPERM2I128 $49, v1, v0, t1; \
|
||||
VPXOR t0, t1, t0; \
|
||||
VMOVDQU t0, (64+off)(dst); \
|
||||
VMOVDQU (96+off)(src), t0; \
|
||||
VPERM2I128 $49, v3, v2, t1; \
|
||||
VPXOR t0, t1, t0; \
|
||||
VMOVDQU t0, (96+off)(dst)
|
||||
|
||||
#define XOR_UPPER_AVX2(dst, src, off, v0, v1, v2, v3, t0, t1) \
|
||||
VMOVDQU (0+off)(src), t0; \
|
||||
VPERM2I128 $32, v1, v0, t1; \
|
||||
VPXOR t0, t1, t0; \
|
||||
VMOVDQU t0, (0+off)(dst); \
|
||||
VMOVDQU (32+off)(src), t0; \
|
||||
VPERM2I128 $32, v3, v2, t1; \
|
||||
VPXOR t0, t1, t0; \
|
||||
VMOVDQU t0, (32+off)(dst); \
|
||||
|
||||
#define EXTRACT_LOWER(dst, v0, v1, v2, v3, t0) \
|
||||
VPERM2I128 $49, v1, v0, t0; \
|
||||
VMOVDQU t0, 0(dst); \
|
||||
VPERM2I128 $49, v3, v2, t0; \
|
||||
VMOVDQU t0, 32(dst)
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) 2016 Andreas Auernhammer. All rights reserved.
|
||||
// Use of this source code is governed by a license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Package chacha20 implements the ChaCha20 / XChaCha20 stream chipher.
|
||||
// Notice that one specific key-nonce combination must be unique for all time.
|
||||
//
|
||||
// There are three versions of ChaCha20:
|
||||
// - ChaCha20 with a 64 bit nonce (en/decrypt up to 2^64 * 64 bytes for one key-nonce combination)
|
||||
// - ChaCha20 with a 96 bit nonce (en/decrypt up to 2^32 * 64 bytes (~256 GB) for one key-nonce combination)
|
||||
// - XChaCha20 with a 192 bit nonce (en/decrypt up to 2^64 * 64 bytes for one key-nonce combination)
|
||||
package chacha20 // import "github.com/aead/chacha20"
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
|
||||
"github.com/aead/chacha20/chacha"
|
||||
)
|
||||
|
||||
// XORKeyStream crypts bytes from src to dst using the given nonce and key.
|
||||
// The length of the nonce determinds the version of ChaCha20:
|
||||
// - 8 bytes: ChaCha20 with a 64 bit nonce and a 2^64 * 64 byte period.
|
||||
// - 12 bytes: ChaCha20 as defined in RFC 7539 and a 2^32 * 64 byte period.
|
||||
// - 24 bytes: XChaCha20 with a 192 bit nonce and a 2^64 * 64 byte period.
|
||||
// Src and dst may be the same slice but otherwise should not overlap.
|
||||
// If len(dst) < len(src) this function panics.
|
||||
// If the nonce is neither 64, 96 nor 192 bits long, this function panics.
|
||||
func XORKeyStream(dst, src, nonce, key []byte) {
|
||||
chacha.XORKeyStream(dst, src, nonce, key, 20)
|
||||
}
|
||||
|
||||
// NewCipher returns a new cipher.Stream implementing a ChaCha20 version.
|
||||
// The nonce must be unique for one key for all time.
|
||||
// The length of the nonce determinds the version of ChaCha20:
|
||||
// - 8 bytes: ChaCha20 with a 64 bit nonce and a 2^64 * 64 byte period.
|
||||
// - 12 bytes: ChaCha20 as defined in RFC 7539 and a 2^32 * 64 byte period.
|
||||
// - 24 bytes: XChaCha20 with a 192 bit nonce and a 2^64 * 64 byte period.
|
||||
// If the nonce is neither 64, 96 nor 192 bits long, a non-nil error is returned.
|
||||
func NewCipher(nonce, key []byte) (cipher.Stream, error) {
|
||||
return chacha.NewCipher(nonce, key, 20)
|
||||
}
|
|
@ -1149,18 +1149,9 @@ func (b *BlockChain) initChainState() error {
|
|||
|
||||
blockIndexBucket := dbTx.Metadata().Bucket(blockIndexBucketName)
|
||||
|
||||
// Determine how many blocks will be loaded into the index so we can
|
||||
// allocate the right amount.
|
||||
var blockCount int32
|
||||
cursor := blockIndexBucket.Cursor()
|
||||
for ok := cursor.First(); ok; ok = cursor.Next() {
|
||||
blockCount++
|
||||
}
|
||||
blockNodes := make([]blockNode, blockCount)
|
||||
|
||||
var i int32
|
||||
var lastNode *blockNode
|
||||
cursor = blockIndexBucket.Cursor()
|
||||
cursor := blockIndexBucket.Cursor()
|
||||
for ok := cursor.First(); ok; ok = cursor.Next() {
|
||||
header, status, err := deserializeBlockRow(cursor.Value())
|
||||
if err != nil {
|
||||
|
@ -1193,7 +1184,7 @@ func (b *BlockChain) initChainState() error {
|
|||
|
||||
// Initialize the block node for the block, connect it,
|
||||
// and add it to the block index.
|
||||
node := &blockNodes[i]
|
||||
node := new(blockNode)
|
||||
initBlockNode(node, header, parent)
|
||||
node.status = status
|
||||
b.index.addNode(node)
|
||||
|
|
|
@ -111,6 +111,22 @@ func (entry *UtxoEntry) Clone() *UtxoEntry {
|
|||
}
|
||||
}
|
||||
|
||||
// NewUtxoEntry returns a new UtxoEntry built from the arguments.
|
||||
func NewUtxoEntry(
|
||||
txOut *wire.TxOut, blockHeight int32, isCoinbase bool) *UtxoEntry {
|
||||
var cbFlag txoFlags
|
||||
if isCoinbase {
|
||||
cbFlag |= tfCoinBase
|
||||
}
|
||||
|
||||
return &UtxoEntry{
|
||||
amount: txOut.Value,
|
||||
pkScript: txOut.PkScript,
|
||||
blockHeight: blockHeight,
|
||||
packedFlags: cbFlag,
|
||||
}
|
||||
}
|
||||
|
||||
// UtxoViewpoint represents a view into the set of unspent transaction outputs
|
||||
// from a specific point of view in the chain. For example, it could be for
|
||||
// the end of the main chain, some point in the history of the main chain, or
|
||||
|
|
|
@ -226,20 +226,24 @@ func (f *fieldVal) SetBytes(b *[32]byte) *fieldVal {
|
|||
return f
|
||||
}
|
||||
|
||||
// SetByteSlice packs the passed big-endian value into the internal field value
|
||||
// representation. Only the first 32-bytes are used. As a result, it is up to
|
||||
// the caller to ensure numbers of the appropriate size are used or the value
|
||||
// will be truncated.
|
||||
// SetByteSlice interprets the provided slice as a 256-bit big-endian unsigned
|
||||
// integer (meaning it is truncated to the first 32 bytes), packs it into the
|
||||
// internal field value representation, and returns the updated field value.
|
||||
//
|
||||
// Note that since passing a slice with more than 32 bytes is truncated, it is
|
||||
// possible that the truncated value is less than the field prime. It is up to
|
||||
// the caller to decide whether it needs to provide numbers of the appropriate
|
||||
// size or if it is acceptable to use this function with the described
|
||||
// truncation behavior.
|
||||
//
|
||||
// The field value is returned to support chaining. This enables syntax like:
|
||||
// f := new(fieldVal).SetByteSlice(byteSlice)
|
||||
func (f *fieldVal) SetByteSlice(b []byte) *fieldVal {
|
||||
var b32 [32]byte
|
||||
for i := 0; i < len(b); i++ {
|
||||
if i < 32 {
|
||||
b32[i+(32-len(b))] = b[i]
|
||||
}
|
||||
if len(b) > 32 {
|
||||
b = b[:32]
|
||||
}
|
||||
copy(b32[32-len(b):], b)
|
||||
return f.SetBytes(&b32)
|
||||
}
|
||||
|
||||
|
|
|
@ -59,6 +59,23 @@ func NewDebugLevelCmd(levelSpec string) *DebugLevelCmd {
|
|||
}
|
||||
}
|
||||
|
||||
// GenerateToAddressCmd defines the generatetoaddress JSON-RPC command.
|
||||
type GenerateToAddressCmd struct {
|
||||
NumBlocks int64
|
||||
Address string
|
||||
MaxTries *int64 `jsonrpcdefault:"1000000"`
|
||||
}
|
||||
|
||||
// NewGenerateToAddressCmd returns a new instance which can be used to issue a
|
||||
// generatetoaddress JSON-RPC command.
|
||||
func NewGenerateToAddressCmd(numBlocks int64, address string, maxTries *int64) *GenerateToAddressCmd {
|
||||
return &GenerateToAddressCmd{
|
||||
NumBlocks: numBlocks,
|
||||
Address: address,
|
||||
MaxTries: maxTries,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateCmd defines the generate JSON-RPC command.
|
||||
type GenerateCmd struct {
|
||||
NumBlocks uint32
|
||||
|
@ -131,6 +148,7 @@ func init() {
|
|||
MustRegisterCmd("debuglevel", (*DebugLevelCmd)(nil), flags)
|
||||
MustRegisterCmd("node", (*NodeCmd)(nil), flags)
|
||||
MustRegisterCmd("generate", (*GenerateCmd)(nil), flags)
|
||||
MustRegisterCmd("generatetoaddress", (*GenerateToAddressCmd)(nil), flags)
|
||||
MustRegisterCmd("getbestblock", (*GetBestBlockCmd)(nil), flags)
|
||||
MustRegisterCmd("getcurrentnet", (*GetCurrentNetCmd)(nil), flags)
|
||||
MustRegisterCmd("getheaders", (*GetHeadersCmd)(nil), flags)
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
package btcjson
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
|
@ -63,10 +64,15 @@ type CreateRawTransactionCmd struct {
|
|||
// NewCreateRawTransactionCmd returns a new instance which can be used to issue
|
||||
// a createrawtransaction JSON-RPC command.
|
||||
//
|
||||
// Amounts are in BTC.
|
||||
// Amounts are in BTC. Passing in nil and the empty slice as inputs is equivalent,
|
||||
// both gets interpreted as the empty slice.
|
||||
func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]float64,
|
||||
lockTime *int64) *CreateRawTransactionCmd {
|
||||
|
||||
// to make sure we're serializing this to the empty list and not null, we
|
||||
// explicitly initialize the list
|
||||
if inputs == nil {
|
||||
inputs = []TransactionInput{}
|
||||
}
|
||||
return &CreateRawTransactionCmd{
|
||||
Inputs: inputs,
|
||||
Amounts: amounts,
|
||||
|
@ -74,6 +80,37 @@ func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]fl
|
|||
}
|
||||
}
|
||||
|
||||
// FundRawTransactionOpts are the different options that can be passed to rawtransaction
|
||||
type FundRawTransactionOpts struct {
|
||||
ChangeAddress *string `json:"changeAddress,omitempty"`
|
||||
ChangePosition *int `json:"changePosition,omitempty"`
|
||||
ChangeType *string `json:"change_type,omitempty"`
|
||||
IncludeWatching *bool `json:"includeWatching,omitempty"`
|
||||
LockUnspents *bool `json:"lockUnspents,omitempty"`
|
||||
FeeRate *float64 `json:"feeRate,omitempty"` // BTC/kB
|
||||
SubtractFeeFromOutputs []int `json:"subtractFeeFromOutputs,omitempty"`
|
||||
Replaceable *bool `json:"replaceable,omitempty"`
|
||||
ConfTarget *int `json:"conf_target,omitempty"`
|
||||
EstimateMode *EstimateSmartFeeMode `json:"estimate_mode,omitempty"`
|
||||
}
|
||||
|
||||
// FundRawTransactionCmd defines the fundrawtransaction JSON-RPC command
|
||||
type FundRawTransactionCmd struct {
|
||||
HexTx string
|
||||
Options FundRawTransactionOpts
|
||||
IsWitness *bool
|
||||
}
|
||||
|
||||
// NewFundRawTransactionCmd returns a new instance which can be used to issue
|
||||
// a fundrawtransaction JSON-RPC command
|
||||
func NewFundRawTransactionCmd(serializedTx []byte, opts FundRawTransactionOpts, isWitness *bool) *FundRawTransactionCmd {
|
||||
return &FundRawTransactionCmd{
|
||||
HexTx: hex.EncodeToString(serializedTx),
|
||||
Options: opts,
|
||||
IsWitness: isWitness,
|
||||
}
|
||||
}
|
||||
|
||||
// DecodeRawTransactionCmd defines the decoderawtransaction JSON-RPC command.
|
||||
type DecodeRawTransactionCmd struct {
|
||||
HexTx string
|
||||
|
@ -130,8 +167,7 @@ func NewGetBestBlockHashCmd() *GetBestBlockHashCmd {
|
|||
// GetBlockCmd defines the getblock JSON-RPC command.
|
||||
type GetBlockCmd struct {
|
||||
Hash string
|
||||
Verbose *bool `jsonrpcdefault:"true"`
|
||||
VerboseTx *bool `jsonrpcdefault:"false"`
|
||||
Verbosity *int `jsonrpcdefault:"1"`
|
||||
}
|
||||
|
||||
// NewGetBlockCmd returns a new instance which can be used to issue a getblock
|
||||
|
@ -139,11 +175,10 @@ type GetBlockCmd struct {
|
|||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetBlockCmd(hash string, verbose, verboseTx *bool) *GetBlockCmd {
|
||||
func NewGetBlockCmd(hash string, verbosity *int) *GetBlockCmd {
|
||||
return &GetBlockCmd{
|
||||
Hash: hash,
|
||||
Verbose: verbose,
|
||||
VerboseTx: verboseTx,
|
||||
Verbosity: verbosity,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,6 +228,50 @@ func NewGetBlockHeaderCmd(hash string, verbose *bool) *GetBlockHeaderCmd {
|
|||
}
|
||||
}
|
||||
|
||||
// HashOrHeight defines a type that can be used as hash_or_height value in JSON-RPC commands.
|
||||
type HashOrHeight struct {
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface
|
||||
func (h HashOrHeight) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(h.Value)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface
|
||||
func (h *HashOrHeight) UnmarshalJSON(data []byte) error {
|
||||
var unmarshalled interface{}
|
||||
if err := json.Unmarshal(data, &unmarshalled); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch v := unmarshalled.(type) {
|
||||
case float64:
|
||||
h.Value = int(v)
|
||||
case string:
|
||||
h.Value = v
|
||||
default:
|
||||
return fmt.Errorf("invalid hash_or_height value: %v", unmarshalled)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBlockStatsCmd defines the getblockstats JSON-RPC command.
|
||||
type GetBlockStatsCmd struct {
|
||||
HashOrHeight HashOrHeight
|
||||
Stats *[]string
|
||||
}
|
||||
|
||||
// NewGetBlockStatsCmd returns a new instance which can be used to issue a
|
||||
// getblockstats JSON-RPC command. Either height or hash must be specified.
|
||||
func NewGetBlockStatsCmd(hashOrHeight HashOrHeight, stats *[]string) *GetBlockStatsCmd {
|
||||
return &GetBlockStatsCmd{
|
||||
HashOrHeight: hashOrHeight,
|
||||
Stats: stats,
|
||||
}
|
||||
}
|
||||
|
||||
// TemplateRequest is a request object as defined in BIP22
|
||||
// (https://en.bitcoin.it/wiki/BIP_0022), it is optionally provided as an
|
||||
// pointer argument to GetBlockTemplateCmd.
|
||||
|
@ -321,6 +400,24 @@ func NewGetChainTipsCmd() *GetChainTipsCmd {
|
|||
return &GetChainTipsCmd{}
|
||||
}
|
||||
|
||||
// GetChainTxStatsCmd defines the getchaintxstats JSON-RPC command.
|
||||
type GetChainTxStatsCmd struct {
|
||||
NBlocks *int32
|
||||
BlockHash *string
|
||||
}
|
||||
|
||||
// NewGetChainTxStatsCmd returns a new instance which can be used to issue a
|
||||
// getchaintxstats JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetChainTxStatsCmd(nBlocks *int32, blockHash *string) *GetChainTxStatsCmd {
|
||||
return &GetChainTxStatsCmd{
|
||||
NBlocks: nBlocks,
|
||||
BlockHash: blockHash,
|
||||
}
|
||||
}
|
||||
|
||||
// GetConnectionCountCmd defines the getconnectioncount JSON-RPC command.
|
||||
type GetConnectionCountCmd struct{}
|
||||
|
||||
|
@ -791,6 +888,7 @@ func init() {
|
|||
|
||||
MustRegisterCmd("addnode", (*AddNodeCmd)(nil), flags)
|
||||
MustRegisterCmd("createrawtransaction", (*CreateRawTransactionCmd)(nil), flags)
|
||||
MustRegisterCmd("fundrawtransaction", (*FundRawTransactionCmd)(nil), flags)
|
||||
MustRegisterCmd("decoderawtransaction", (*DecodeRawTransactionCmd)(nil), flags)
|
||||
MustRegisterCmd("decodescript", (*DecodeScriptCmd)(nil), flags)
|
||||
MustRegisterCmd("getaddednodeinfo", (*GetAddedNodeInfoCmd)(nil), flags)
|
||||
|
@ -800,10 +898,12 @@ func init() {
|
|||
MustRegisterCmd("getblockcount", (*GetBlockCountCmd)(nil), flags)
|
||||
MustRegisterCmd("getblockhash", (*GetBlockHashCmd)(nil), flags)
|
||||
MustRegisterCmd("getblockheader", (*GetBlockHeaderCmd)(nil), flags)
|
||||
MustRegisterCmd("getblockstats", (*GetBlockStatsCmd)(nil), flags)
|
||||
MustRegisterCmd("getblocktemplate", (*GetBlockTemplateCmd)(nil), flags)
|
||||
MustRegisterCmd("getcfilter", (*GetCFilterCmd)(nil), flags)
|
||||
MustRegisterCmd("getcfilterheader", (*GetCFilterHeaderCmd)(nil), flags)
|
||||
MustRegisterCmd("getchaintips", (*GetChainTipsCmd)(nil), flags)
|
||||
MustRegisterCmd("getchaintxstats", (*GetChainTxStatsCmd)(nil), flags)
|
||||
MustRegisterCmd("getconnectioncount", (*GetConnectionCountCmd)(nil), flags)
|
||||
MustRegisterCmd("getdifficulty", (*GetDifficultyCmd)(nil), flags)
|
||||
MustRegisterCmd("getgenerate", (*GetGenerateCmd)(nil), flags)
|
||||
|
|
|
@ -4,7 +4,14 @@
|
|||
|
||||
package btcjson
|
||||
|
||||
import "encoding/json"
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
// GetBlockHeaderVerboseResult models the data from the getblockheader command when
|
||||
// the verbose flag is set. When the verbose flag is not set, getblockheader
|
||||
|
@ -24,9 +31,44 @@ type GetBlockHeaderVerboseResult struct {
|
|||
NextHash string `json:"nextblockhash,omitempty"`
|
||||
}
|
||||
|
||||
// GetBlockStatsResult models the data from the getblockstats command.
|
||||
type GetBlockStatsResult struct {
|
||||
AverageFee int64 `json:"avgfee"`
|
||||
AverageFeeRate int64 `json:"avgfeerate"`
|
||||
AverageTxSize int64 `json:"avgtxsize"`
|
||||
FeeratePercentiles []int64 `json:"feerate_percentiles"`
|
||||
Hash string `json:"blockhash"`
|
||||
Height int64 `json:"height"`
|
||||
Ins int64 `json:"ins"`
|
||||
MaxFee int64 `json:"maxfee"`
|
||||
MaxFeeRate int64 `json:"maxfeerate"`
|
||||
MaxTxSize int64 `json:"maxtxsize"`
|
||||
MedianFee int64 `json:"medianfee"`
|
||||
MedianTime int64 `json:"mediantime"`
|
||||
MedianTxSize int64 `json:"mediantxsize"`
|
||||
MinFee int64 `json:"minfee"`
|
||||
MinFeeRate int64 `json:"minfeerate"`
|
||||
MinTxSize int64 `json:"mintxsize"`
|
||||
Outs int64 `json:"outs"`
|
||||
SegWitTotalSize int64 `json:"swtotal_size"`
|
||||
SegWitTotalWeight int64 `json:"swtotal_weight"`
|
||||
SegWitTxs int64 `json:"swtxs"`
|
||||
Subsidy int64 `json:"subsidy"`
|
||||
Time int64 `json:"time"`
|
||||
TotalOut int64 `json:"total_out"`
|
||||
TotalSize int64 `json:"total_size"`
|
||||
TotalWeight int64 `json:"total_weight"`
|
||||
Txs int64 `json:"txs"`
|
||||
UTXOIncrease int64 `json:"utxo_increase"`
|
||||
UTXOSizeIncrease int64 `json:"utxo_size_inc"`
|
||||
}
|
||||
|
||||
// GetBlockVerboseResult models the data from the getblock command when the
|
||||
// verbose flag is set. When the verbose flag is not set, getblock returns a
|
||||
// hex-encoded string.
|
||||
// verbose flag is set to 1. When the verbose flag is set to 0, getblock returns a
|
||||
// hex-encoded string. When the verbose flag is set to 1, getblock returns an object
|
||||
// whose tx field is an array of transaction hashes. When the verbose flag is set to 2,
|
||||
// getblock returns an object whose tx field is an array of raw transactions.
|
||||
// Use GetBlockVerboseTxResult to unmarshal data received from passing verbose=2 to getblock.
|
||||
type GetBlockVerboseResult struct {
|
||||
Hash string `json:"hash"`
|
||||
Confirmations int64 `json:"confirmations"`
|
||||
|
@ -38,7 +80,7 @@ type GetBlockVerboseResult struct {
|
|||
VersionHex string `json:"versionHex"`
|
||||
MerkleRoot string `json:"merkleroot"`
|
||||
Tx []string `json:"tx,omitempty"`
|
||||
RawTx []TxRawResult `json:"rawtx,omitempty"`
|
||||
RawTx []TxRawResult `json:"rawtx,omitempty"` // Note: this field is always empty when verbose != 2.
|
||||
Time int64 `json:"time"`
|
||||
Nonce uint32 `json:"nonce"`
|
||||
Bits string `json:"bits"`
|
||||
|
@ -47,6 +89,43 @@ type GetBlockVerboseResult struct {
|
|||
NextHash string `json:"nextblockhash,omitempty"`
|
||||
}
|
||||
|
||||
// GetBlockVerboseTxResult models the data from the getblock command when the
|
||||
// verbose flag is set to 2. When the verbose flag is set to 0, getblock returns a
|
||||
// hex-encoded string. When the verbose flag is set to 1, getblock returns an object
|
||||
// whose tx field is an array of transaction hashes. When the verbose flag is set to 2,
|
||||
// getblock returns an object whose tx field is an array of raw transactions.
|
||||
// Use GetBlockVerboseResult to unmarshal data received from passing verbose=1 to getblock.
|
||||
type GetBlockVerboseTxResult struct {
|
||||
Hash string `json:"hash"`
|
||||
Confirmations int64 `json:"confirmations"`
|
||||
StrippedSize int32 `json:"strippedsize"`
|
||||
Size int32 `json:"size"`
|
||||
Weight int32 `json:"weight"`
|
||||
Height int64 `json:"height"`
|
||||
Version int32 `json:"version"`
|
||||
VersionHex string `json:"versionHex"`
|
||||
MerkleRoot string `json:"merkleroot"`
|
||||
Tx []TxRawResult `json:"tx,omitempty"`
|
||||
Time int64 `json:"time"`
|
||||
Nonce uint32 `json:"nonce"`
|
||||
Bits string `json:"bits"`
|
||||
Difficulty float64 `json:"difficulty"`
|
||||
PreviousHash string `json:"previousblockhash"`
|
||||
NextHash string `json:"nextblockhash,omitempty"`
|
||||
}
|
||||
|
||||
// GetChainTxStatsResult models the data from the getchaintxstats command.
|
||||
type GetChainTxStatsResult struct {
|
||||
Time int64 `json:"time"`
|
||||
TxCount int64 `json:"txcount"`
|
||||
WindowFinalBlockHash string `json:"window_final_block_hash"`
|
||||
WindowFinalBlockHeight int32 `json:"window_final_block_height"`
|
||||
WindowBlockCount int32 `json:"window_block_count"`
|
||||
WindowTxCount int32 `json:"window_tx_count"`
|
||||
WindowInterval int32 `json:"window_interval"`
|
||||
TxRate float64 `json:"txrate"`
|
||||
}
|
||||
|
||||
// CreateMultiSigResult models the data returned from the createmultisig
|
||||
// command.
|
||||
type CreateMultiSigResult struct {
|
||||
|
@ -206,23 +285,35 @@ type GetBlockTemplateResult struct {
|
|||
RejectReasion string `json:"reject-reason,omitempty"`
|
||||
}
|
||||
|
||||
// GetMempoolEntryResult models the data returned from the getmempoolentry's
|
||||
// fee field
|
||||
|
||||
type MempoolFees struct {
|
||||
Base float64 `json:"base"`
|
||||
Modified float64 `json:"modified"`
|
||||
Ancestor float64 `json:"ancestor"`
|
||||
Descendant float64 `json:"descendant"`
|
||||
}
|
||||
|
||||
// GetMempoolEntryResult models the data returned from the getmempoolentry
|
||||
// command.
|
||||
type GetMempoolEntryResult struct {
|
||||
Size int32 `json:"size"`
|
||||
Fee float64 `json:"fee"`
|
||||
ModifiedFee float64 `json:"modifiedfee"`
|
||||
Time int64 `json:"time"`
|
||||
Height int64 `json:"height"`
|
||||
StartingPriority float64 `json:"startingpriority"`
|
||||
CurrentPriority float64 `json:"currentpriority"`
|
||||
DescendantCount int64 `json:"descendantcount"`
|
||||
DescendantSize int64 `json:"descendantsize"`
|
||||
DescendantFees float64 `json:"descendantfees"`
|
||||
AncestorCount int64 `json:"ancestorcount"`
|
||||
AncestorSize int64 `json:"ancestorsize"`
|
||||
AncestorFees float64 `json:"ancestorfees"`
|
||||
Depends []string `json:"depends"`
|
||||
VSize int32 `json:"vsize"`
|
||||
Size int32 `json:"size"`
|
||||
Weight int64 `json:"weight"`
|
||||
Fee float64 `json:"fee"`
|
||||
ModifiedFee float64 `json:"modifiedfee"`
|
||||
Time int64 `json:"time"`
|
||||
Height int64 `json:"height"`
|
||||
DescendantCount int64 `json:"descendantcount"`
|
||||
DescendantSize int64 `json:"descendantsize"`
|
||||
DescendantFees float64 `json:"descendantfees"`
|
||||
AncestorCount int64 `json:"ancestorcount"`
|
||||
AncestorSize int64 `json:"ancestorsize"`
|
||||
AncestorFees float64 `json:"ancestorfees"`
|
||||
WTxId string `json:"wtxid"`
|
||||
Fees MempoolFees `json:"fees"`
|
||||
Depends []string `json:"depends"`
|
||||
}
|
||||
|
||||
// GetMempoolInfoResult models the data returned from the getmempoolinfo
|
||||
|
@ -584,3 +675,58 @@ type ValidateAddressChainResult struct {
|
|||
IsValid bool `json:"isvalid"`
|
||||
Address string `json:"address,omitempty"`
|
||||
}
|
||||
|
||||
// EstimateSmartFeeResult models the data returned buy the chain server
|
||||
// estimatesmartfee command
|
||||
type EstimateSmartFeeResult struct {
|
||||
FeeRate *float64 `json:"feerate,omitempty"`
|
||||
Errors []string `json:"errors,omitempty"`
|
||||
Blocks int64 `json:"blocks"`
|
||||
}
|
||||
|
||||
var _ json.Unmarshaler = &FundRawTransactionResult{}
|
||||
|
||||
type rawFundRawTransactionResult struct {
|
||||
Transaction string `json:"hex"`
|
||||
Fee float64 `json:"fee"`
|
||||
ChangePosition int `json:"changepos"`
|
||||
}
|
||||
|
||||
// FundRawTransactionResult is the result of the fundrawtransaction JSON-RPC call
|
||||
type FundRawTransactionResult struct {
|
||||
Transaction *wire.MsgTx
|
||||
Fee btcutil.Amount
|
||||
ChangePosition int // the position of the added change output, or -1
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals the result of the fundrawtransaction JSON-RPC call
|
||||
func (f *FundRawTransactionResult) UnmarshalJSON(data []byte) error {
|
||||
var rawRes rawFundRawTransactionResult
|
||||
if err := json.Unmarshal(data, &rawRes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txBytes, err := hex.DecodeString(rawRes.Transaction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var msgTx wire.MsgTx
|
||||
witnessErr := msgTx.Deserialize(bytes.NewReader(txBytes))
|
||||
if witnessErr != nil {
|
||||
legacyErr := msgTx.DeserializeNoWitness(bytes.NewReader(txBytes))
|
||||
if legacyErr != nil {
|
||||
return legacyErr
|
||||
}
|
||||
}
|
||||
|
||||
fee, err := btcutil.NewAmount(rawRes.Fee)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.Transaction = &msgTx
|
||||
f.Fee = fee
|
||||
f.ChangePosition = rawRes.ChangePosition
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ func NewStopNotifyNewTransactionsCmd() *StopNotifyNewTransactionsCmd {
|
|||
|
||||
// NotifyReceivedCmd defines the notifyreceived JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use LoadTxFilterCmd instead.
|
||||
// Deprecated: Use LoadTxFilterCmd instead.
|
||||
type NotifyReceivedCmd struct {
|
||||
Addresses []string
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ type NotifyReceivedCmd struct {
|
|||
// NewNotifyReceivedCmd returns a new instance which can be used to issue a
|
||||
// notifyreceived JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewLoadTxFilterCmd instead.
|
||||
// Deprecated: Use NewLoadTxFilterCmd instead.
|
||||
func NewNotifyReceivedCmd(addresses []string) *NotifyReceivedCmd {
|
||||
return &NotifyReceivedCmd{
|
||||
Addresses: addresses,
|
||||
|
@ -128,7 +128,7 @@ func NewLoadTxFilterCmd(reload bool, addresses []string, outPoints []OutPoint) *
|
|||
|
||||
// NotifySpentCmd defines the notifyspent JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use LoadTxFilterCmd instead.
|
||||
// Deprecated: Use LoadTxFilterCmd instead.
|
||||
type NotifySpentCmd struct {
|
||||
OutPoints []OutPoint
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ type NotifySpentCmd struct {
|
|||
// NewNotifySpentCmd returns a new instance which can be used to issue a
|
||||
// notifyspent JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewLoadTxFilterCmd instead.
|
||||
// Deprecated: Use NewLoadTxFilterCmd instead.
|
||||
func NewNotifySpentCmd(outPoints []OutPoint) *NotifySpentCmd {
|
||||
return &NotifySpentCmd{
|
||||
OutPoints: outPoints,
|
||||
|
@ -145,7 +145,7 @@ func NewNotifySpentCmd(outPoints []OutPoint) *NotifySpentCmd {
|
|||
|
||||
// StopNotifyReceivedCmd defines the stopnotifyreceived JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use LoadTxFilterCmd instead.
|
||||
// Deprecated: Use LoadTxFilterCmd instead.
|
||||
type StopNotifyReceivedCmd struct {
|
||||
Addresses []string
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ type StopNotifyReceivedCmd struct {
|
|||
// NewStopNotifyReceivedCmd returns a new instance which can be used to issue a
|
||||
// stopnotifyreceived JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewLoadTxFilterCmd instead.
|
||||
// Deprecated: Use NewLoadTxFilterCmd instead.
|
||||
func NewStopNotifyReceivedCmd(addresses []string) *StopNotifyReceivedCmd {
|
||||
return &StopNotifyReceivedCmd{
|
||||
Addresses: addresses,
|
||||
|
@ -162,7 +162,7 @@ func NewStopNotifyReceivedCmd(addresses []string) *StopNotifyReceivedCmd {
|
|||
|
||||
// StopNotifySpentCmd defines the stopnotifyspent JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use LoadTxFilterCmd instead.
|
||||
// Deprecated: Use LoadTxFilterCmd instead.
|
||||
type StopNotifySpentCmd struct {
|
||||
OutPoints []OutPoint
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ type StopNotifySpentCmd struct {
|
|||
// NewStopNotifySpentCmd returns a new instance which can be used to issue a
|
||||
// stopnotifyspent JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewLoadTxFilterCmd instead.
|
||||
// Deprecated: Use NewLoadTxFilterCmd instead.
|
||||
func NewStopNotifySpentCmd(outPoints []OutPoint) *StopNotifySpentCmd {
|
||||
return &StopNotifySpentCmd{
|
||||
OutPoints: outPoints,
|
||||
|
@ -179,7 +179,7 @@ func NewStopNotifySpentCmd(outPoints []OutPoint) *StopNotifySpentCmd {
|
|||
|
||||
// RescanCmd defines the rescan JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use RescanBlocksCmd instead.
|
||||
// Deprecated: Use RescanBlocksCmd instead.
|
||||
type RescanCmd struct {
|
||||
BeginBlock string
|
||||
Addresses []string
|
||||
|
@ -193,7 +193,7 @@ type RescanCmd struct {
|
|||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewRescanBlocksCmd instead.
|
||||
// Deprecated: Use NewRescanBlocksCmd instead.
|
||||
func NewRescanCmd(beginBlock string, addresses []string, outPoints []OutPoint, endBlock *string) *RescanCmd {
|
||||
return &RescanCmd{
|
||||
BeginBlock: beginBlock,
|
||||
|
|
|
@ -12,14 +12,14 @@ const (
|
|||
// BlockConnectedNtfnMethod is the legacy, deprecated method used for
|
||||
// notifications from the chain server that a block has been connected.
|
||||
//
|
||||
// NOTE: Deprecated. Use FilteredBlockConnectedNtfnMethod instead.
|
||||
// Deprecated: Use FilteredBlockConnectedNtfnMethod instead.
|
||||
BlockConnectedNtfnMethod = "blockconnected"
|
||||
|
||||
// BlockDisconnectedNtfnMethod is the legacy, deprecated method used for
|
||||
// notifications from the chain server that a block has been
|
||||
// disconnected.
|
||||
//
|
||||
// NOTE: Deprecated. Use FilteredBlockDisconnectedNtfnMethod instead.
|
||||
// Deprecated: Use FilteredBlockDisconnectedNtfnMethod instead.
|
||||
BlockDisconnectedNtfnMethod = "blockdisconnected"
|
||||
|
||||
// FilteredBlockConnectedNtfnMethod is the new method used for
|
||||
|
@ -35,7 +35,7 @@ const (
|
|||
// notifications from the chain server that a transaction which pays to
|
||||
// a registered address has been processed.
|
||||
//
|
||||
// NOTE: Deprecated. Use RelevantTxAcceptedNtfnMethod and
|
||||
// Deprecated: Use RelevantTxAcceptedNtfnMethod and
|
||||
// FilteredBlockConnectedNtfnMethod instead.
|
||||
RecvTxNtfnMethod = "recvtx"
|
||||
|
||||
|
@ -43,7 +43,7 @@ const (
|
|||
// notifications from the chain server that a transaction which spends a
|
||||
// registered outpoint has been processed.
|
||||
//
|
||||
// NOTE: Deprecated. Use RelevantTxAcceptedNtfnMethod and
|
||||
// Deprecated: Use RelevantTxAcceptedNtfnMethod and
|
||||
// FilteredBlockConnectedNtfnMethod instead.
|
||||
RedeemingTxNtfnMethod = "redeemingtx"
|
||||
|
||||
|
@ -51,14 +51,14 @@ const (
|
|||
// notifications from the chain server that a legacy, deprecated rescan
|
||||
// operation has finished.
|
||||
//
|
||||
// NOTE: Deprecated. Not used with rescanblocks command.
|
||||
// Deprecated: Not used with rescanblocks command.
|
||||
RescanFinishedNtfnMethod = "rescanfinished"
|
||||
|
||||
// RescanProgressNtfnMethod is the legacy, deprecated method used for
|
||||
// notifications from the chain server that a legacy, deprecated rescan
|
||||
// operation this is underway has made progress.
|
||||
//
|
||||
// NOTE: Deprecated. Not used with rescanblocks command.
|
||||
// Deprecated: Not used with rescanblocks command.
|
||||
RescanProgressNtfnMethod = "rescanprogress"
|
||||
|
||||
// TxAcceptedNtfnMethod is the method used for notifications from the
|
||||
|
@ -79,7 +79,7 @@ const (
|
|||
|
||||
// BlockConnectedNtfn defines the blockconnected JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Use FilteredBlockConnectedNtfn instead.
|
||||
// Deprecated: Use FilteredBlockConnectedNtfn instead.
|
||||
type BlockConnectedNtfn struct {
|
||||
Hash string
|
||||
Height int32
|
||||
|
@ -89,7 +89,7 @@ type BlockConnectedNtfn struct {
|
|||
// NewBlockConnectedNtfn returns a new instance which can be used to issue a
|
||||
// blockconnected JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewFilteredBlockConnectedNtfn instead.
|
||||
// Deprecated: Use NewFilteredBlockConnectedNtfn instead.
|
||||
func NewBlockConnectedNtfn(hash string, height int32, time int64) *BlockConnectedNtfn {
|
||||
return &BlockConnectedNtfn{
|
||||
Hash: hash,
|
||||
|
@ -100,7 +100,7 @@ func NewBlockConnectedNtfn(hash string, height int32, time int64) *BlockConnecte
|
|||
|
||||
// BlockDisconnectedNtfn defines the blockdisconnected JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Use FilteredBlockDisconnectedNtfn instead.
|
||||
// Deprecated: Use FilteredBlockDisconnectedNtfn instead.
|
||||
type BlockDisconnectedNtfn struct {
|
||||
Hash string
|
||||
Height int32
|
||||
|
@ -110,7 +110,7 @@ type BlockDisconnectedNtfn struct {
|
|||
// NewBlockDisconnectedNtfn returns a new instance which can be used to issue a
|
||||
// blockdisconnected JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewFilteredBlockDisconnectedNtfn instead.
|
||||
// Deprecated: Use NewFilteredBlockDisconnectedNtfn instead.
|
||||
func NewBlockDisconnectedNtfn(hash string, height int32, time int64) *BlockDisconnectedNtfn {
|
||||
return &BlockDisconnectedNtfn{
|
||||
Hash: hash,
|
||||
|
@ -163,7 +163,7 @@ type BlockDetails struct {
|
|||
|
||||
// RecvTxNtfn defines the recvtx JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Use RelevantTxAcceptedNtfn and FilteredBlockConnectedNtfn
|
||||
// Deprecated: Use RelevantTxAcceptedNtfn and FilteredBlockConnectedNtfn
|
||||
// instead.
|
||||
type RecvTxNtfn struct {
|
||||
HexTx string
|
||||
|
@ -173,7 +173,7 @@ type RecvTxNtfn struct {
|
|||
// NewRecvTxNtfn returns a new instance which can be used to issue a recvtx
|
||||
// JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewRelevantTxAcceptedNtfn and
|
||||
// Deprecated: Use NewRelevantTxAcceptedNtfn and
|
||||
// NewFilteredBlockConnectedNtfn instead.
|
||||
func NewRecvTxNtfn(hexTx string, block *BlockDetails) *RecvTxNtfn {
|
||||
return &RecvTxNtfn{
|
||||
|
@ -184,7 +184,7 @@ func NewRecvTxNtfn(hexTx string, block *BlockDetails) *RecvTxNtfn {
|
|||
|
||||
// RedeemingTxNtfn defines the redeemingtx JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Use RelevantTxAcceptedNtfn and FilteredBlockConnectedNtfn
|
||||
// Deprecated: Use RelevantTxAcceptedNtfn and FilteredBlockConnectedNtfn
|
||||
// instead.
|
||||
type RedeemingTxNtfn struct {
|
||||
HexTx string
|
||||
|
@ -194,7 +194,7 @@ type RedeemingTxNtfn struct {
|
|||
// NewRedeemingTxNtfn returns a new instance which can be used to issue a
|
||||
// redeemingtx JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewRelevantTxAcceptedNtfn and
|
||||
// Deprecated: Use NewRelevantTxAcceptedNtfn and
|
||||
// NewFilteredBlockConnectedNtfn instead.
|
||||
func NewRedeemingTxNtfn(hexTx string, block *BlockDetails) *RedeemingTxNtfn {
|
||||
return &RedeemingTxNtfn{
|
||||
|
@ -205,7 +205,7 @@ func NewRedeemingTxNtfn(hexTx string, block *BlockDetails) *RedeemingTxNtfn {
|
|||
|
||||
// RescanFinishedNtfn defines the rescanfinished JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Not used with rescanblocks command.
|
||||
// Deprecated: Not used with rescanblocks command.
|
||||
type RescanFinishedNtfn struct {
|
||||
Hash string
|
||||
Height int32
|
||||
|
@ -215,7 +215,7 @@ type RescanFinishedNtfn struct {
|
|||
// NewRescanFinishedNtfn returns a new instance which can be used to issue a
|
||||
// rescanfinished JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Not used with rescanblocks command.
|
||||
// Deprecated: Not used with rescanblocks command.
|
||||
func NewRescanFinishedNtfn(hash string, height int32, time int64) *RescanFinishedNtfn {
|
||||
return &RescanFinishedNtfn{
|
||||
Hash: hash,
|
||||
|
@ -226,7 +226,7 @@ func NewRescanFinishedNtfn(hash string, height int32, time int64) *RescanFinishe
|
|||
|
||||
// RescanProgressNtfn defines the rescanprogress JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Not used with rescanblocks command.
|
||||
// Deprecated: Not used with rescanblocks command.
|
||||
type RescanProgressNtfn struct {
|
||||
Hash string
|
||||
Height int32
|
||||
|
@ -236,7 +236,7 @@ type RescanProgressNtfn struct {
|
|||
// NewRescanProgressNtfn returns a new instance which can be used to issue a
|
||||
// rescanprogress JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Not used with rescanblocks command.
|
||||
// Deprecated: Not used with rescanblocks command.
|
||||
func NewRescanProgressNtfn(hash string, height int32, time int64) *RescanProgressNtfn {
|
||||
return &RescanProgressNtfn{
|
||||
Hash: hash,
|
||||
|
|
|
@ -22,10 +22,10 @@ type RPCError struct {
|
|||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// Guarantee RPCError satisifies the builtin error interface.
|
||||
// Guarantee RPCError satisfies the builtin error interface.
|
||||
var _, _ error = RPCError{}, (*RPCError)(nil)
|
||||
|
||||
// Error returns a string describing the RPC error. This satisifies the
|
||||
// Error returns a string describing the RPC error. This satisfies the
|
||||
// builtin error interface.
|
||||
func (e RPCError) Error() string {
|
||||
return fmt.Sprintf("%d: %s", e.Code, e.Message)
|
||||
|
|
|
@ -39,6 +39,7 @@ const (
|
|||
ErrRPCDatabase RPCErrorCode = -20
|
||||
ErrRPCDeserialization RPCErrorCode = -22
|
||||
ErrRPCVerify RPCErrorCode = -25
|
||||
ErrRPCInWarmup RPCErrorCode = -28
|
||||
)
|
||||
|
||||
// Peer-to-peer client errors.
|
||||
|
|
|
@ -287,6 +287,6 @@ func RegisteredCmdMethods() []string {
|
|||
methods = append(methods, k)
|
||||
}
|
||||
|
||||
sort.Sort(sort.StringSlice(methods))
|
||||
sort.Strings(methods)
|
||||
return methods
|
||||
}
|
||||
|
|
|
@ -81,6 +81,30 @@ func NewEncryptWalletCmd(passphrase string) *EncryptWalletCmd {
|
|||
}
|
||||
}
|
||||
|
||||
// EstimateSmartFeeMode defines the different fee estimation modes available
|
||||
// for the estimatesmartfee JSON-RPC command.
|
||||
type EstimateSmartFeeMode string
|
||||
|
||||
var (
|
||||
EstimateModeUnset EstimateSmartFeeMode = "UNSET"
|
||||
EstimateModeEconomical EstimateSmartFeeMode = "ECONOMICAL"
|
||||
EstimateModeConservative EstimateSmartFeeMode = "CONSERVATIVE"
|
||||
)
|
||||
|
||||
// EstimateSmartFeeCmd defines the estimatesmartfee JSON-RPC command.
|
||||
type EstimateSmartFeeCmd struct {
|
||||
ConfTarget int64
|
||||
EstimateMode *EstimateSmartFeeMode `jsonrpcdefault:"\"CONSERVATIVE\""`
|
||||
}
|
||||
|
||||
// NewEstimateSmartFeeCmd returns a new instance which can be used to issue a
|
||||
// estimatesmartfee JSON-RPC command.
|
||||
func NewEstimateSmartFeeCmd(confTarget int64, mode *EstimateSmartFeeMode) *EstimateSmartFeeCmd {
|
||||
return &EstimateSmartFeeCmd{
|
||||
ConfTarget: confTarget, EstimateMode: mode,
|
||||
}
|
||||
}
|
||||
|
||||
// EstimateFeeCmd defines the estimatefee JSON-RPC command.
|
||||
type EstimateFeeCmd struct {
|
||||
NumBlocks int64
|
||||
|
@ -164,6 +188,15 @@ func NewGetBalanceCmd(account *string, minConf *int) *GetBalanceCmd {
|
|||
}
|
||||
}
|
||||
|
||||
// GetBalancesCmd defines the getbalances JSON-RPC command.
|
||||
type GetBalancesCmd struct{}
|
||||
|
||||
// NewGetBalancesCmd returns a new instance which can be used to issue a
|
||||
// getbalances JSON-RPC command.
|
||||
func NewGetBalancesCmd() *GetBalancesCmd {
|
||||
return &GetBalancesCmd{}
|
||||
}
|
||||
|
||||
// GetNewAddressCmd defines the getnewaddress JSON-RPC command.
|
||||
type GetNewAddressCmd struct {
|
||||
Account *string
|
||||
|
@ -662,12 +695,14 @@ func init() {
|
|||
MustRegisterCmd("createmultisig", (*CreateMultisigCmd)(nil), flags)
|
||||
MustRegisterCmd("dumpprivkey", (*DumpPrivKeyCmd)(nil), flags)
|
||||
MustRegisterCmd("encryptwallet", (*EncryptWalletCmd)(nil), flags)
|
||||
MustRegisterCmd("estimatesmartfee", (*EstimateSmartFeeCmd)(nil), flags)
|
||||
MustRegisterCmd("estimatefee", (*EstimateFeeCmd)(nil), flags)
|
||||
MustRegisterCmd("estimatepriority", (*EstimatePriorityCmd)(nil), flags)
|
||||
MustRegisterCmd("getaccount", (*GetAccountCmd)(nil), flags)
|
||||
MustRegisterCmd("getaccountaddress", (*GetAccountAddressCmd)(nil), flags)
|
||||
MustRegisterCmd("getaddressesbyaccount", (*GetAddressesByAccountCmd)(nil), flags)
|
||||
MustRegisterCmd("getbalance", (*GetBalanceCmd)(nil), flags)
|
||||
MustRegisterCmd("getbalances", (*GetBalancesCmd)(nil), flags)
|
||||
MustRegisterCmd("getnewaddress", (*GetNewAddressCmd)(nil), flags)
|
||||
MustRegisterCmd("getrawchangeaddress", (*GetRawChangeAddressCmd)(nil), flags)
|
||||
MustRegisterCmd("getreceivedbyaccount", (*GetReceivedByAccountCmd)(nil), flags)
|
||||
|
|
|
@ -159,3 +159,17 @@ type GetBestBlockResult struct {
|
|||
Hash string `json:"hash"`
|
||||
Height int32 `json:"height"`
|
||||
}
|
||||
|
||||
// BalanceDetailsResult models the details data from the `getbalances` command.
|
||||
type BalanceDetailsResult struct {
|
||||
Trusted float64 `json:"trusted"`
|
||||
UntrustedPending float64 `json:"untrusted_pending"`
|
||||
Immature float64 `json:"immature"`
|
||||
Used *float64 `json:"used"`
|
||||
}
|
||||
|
||||
// GetBalancesResult models the data returned from the getbalances command.
|
||||
type GetBalancesResult struct {
|
||||
Mine BalanceDetailsResult `json:"mine"`
|
||||
WatchOnly *BalanceDetailsResult `json:"watchonly"`
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ type DynamicBanScore struct {
|
|||
func (s *DynamicBanScore) String() string {
|
||||
s.mtx.Lock()
|
||||
r := fmt.Sprintf("persistent %v + transient %v at %v = %v as of now",
|
||||
s.persistent, s.transient, s.lastUnix, s.Int())
|
||||
s.persistent, s.transient, s.lastUnix, s.int(time.Now()))
|
||||
s.mtx.Unlock()
|
||||
return r
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
rpctest
|
||||
=======
|
||||
|
||||
[![Build Status](http://img.shields.io/travis/btcsuite/btcd.svg)](https://travis-ci.org/btcsuite/btcd)
|
||||
[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
|
||||
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/btcsuite/btcd/integration/rpctest)
|
||||
|
||||
Package rpctest provides a btcd-specific RPC testing harness crafting and
|
||||
executing integration tests by driving a `btcd` instance via the `RPC`
|
||||
interface. Each instance of an active harness comes equipped with a simple
|
||||
in-memory HD wallet capable of properly syncing to the generated chain,
|
||||
creating new addresses, and crafting fully signed transactions paying to an
|
||||
arbitrary set of outputs.
|
||||
|
||||
This package was designed specifically to act as an RPC testing harness for
|
||||
`btcd`. However, the constructs presented are general enough to be adapted to
|
||||
any project wishing to programmatically drive a `btcd` instance of its
|
||||
systems/integration tests.
|
||||
|
||||
## Installation and Updating
|
||||
|
||||
```bash
|
||||
$ go get -u github.com/btcsuite/btcd/integration/rpctest
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Package rpctest is licensed under the [copyfree](http://copyfree.org) ISC
|
||||
License.
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
// Copyright (c) 2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rpctest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"math/big"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
// solveBlock attempts to find a nonce which makes the passed block header hash
|
||||
// to a value less than the target difficulty. When a successful solution is
|
||||
// found true is returned and the nonce field of the passed header is updated
|
||||
// with the solution. False is returned if no solution exists.
|
||||
func solveBlock(header *wire.BlockHeader, targetDifficulty *big.Int) bool {
|
||||
// sbResult is used by the solver goroutines to send results.
|
||||
type sbResult struct {
|
||||
found bool
|
||||
nonce uint32
|
||||
}
|
||||
|
||||
// solver accepts a block header and a nonce range to test. It is
|
||||
// intended to be run as a goroutine.
|
||||
quit := make(chan bool)
|
||||
results := make(chan sbResult)
|
||||
solver := func(hdr wire.BlockHeader, startNonce, stopNonce uint32) {
|
||||
// We need to modify the nonce field of the header, so make sure
|
||||
// we work with a copy of the original header.
|
||||
for i := startNonce; i >= startNonce && i <= stopNonce; i++ {
|
||||
select {
|
||||
case <-quit:
|
||||
return
|
||||
default:
|
||||
hdr.Nonce = i
|
||||
hash := hdr.BlockHash()
|
||||
if blockchain.HashToBig(&hash).Cmp(targetDifficulty) <= 0 {
|
||||
select {
|
||||
case results <- sbResult{true, i}:
|
||||
return
|
||||
case <-quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
select {
|
||||
case results <- sbResult{false, 0}:
|
||||
case <-quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
startNonce := uint32(0)
|
||||
stopNonce := uint32(math.MaxUint32)
|
||||
numCores := uint32(runtime.NumCPU())
|
||||
noncesPerCore := (stopNonce - startNonce) / numCores
|
||||
for i := uint32(0); i < numCores; i++ {
|
||||
rangeStart := startNonce + (noncesPerCore * i)
|
||||
rangeStop := startNonce + (noncesPerCore * (i + 1)) - 1
|
||||
if i == numCores-1 {
|
||||
rangeStop = stopNonce
|
||||
}
|
||||
go solver(*header, rangeStart, rangeStop)
|
||||
}
|
||||
for i := uint32(0); i < numCores; i++ {
|
||||
result := <-results
|
||||
if result.found {
|
||||
close(quit)
|
||||
header.Nonce = result.nonce
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// standardCoinbaseScript returns a standard script suitable for use as the
|
||||
// signature script of the coinbase transaction of a new block. In particular,
|
||||
// it starts with the block height that is required by version 2 blocks.
|
||||
func standardCoinbaseScript(nextBlockHeight int32, extraNonce uint64) ([]byte, error) {
|
||||
return txscript.NewScriptBuilder().AddInt64(int64(nextBlockHeight)).
|
||||
AddInt64(int64(extraNonce)).Script()
|
||||
}
|
||||
|
||||
// createCoinbaseTx returns a coinbase transaction paying an appropriate
|
||||
// subsidy based on the passed block height to the provided address.
|
||||
func createCoinbaseTx(coinbaseScript []byte, nextBlockHeight int32,
|
||||
addr btcutil.Address, mineTo []wire.TxOut,
|
||||
net *chaincfg.Params) (*btcutil.Tx, error) {
|
||||
|
||||
// Create the script to pay to the provided payment address.
|
||||
pkScript, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx := wire.NewMsgTx(wire.TxVersion)
|
||||
tx.AddTxIn(&wire.TxIn{
|
||||
// Coinbase transactions have no inputs, so previous outpoint is
|
||||
// zero hash and max index.
|
||||
PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{},
|
||||
wire.MaxPrevOutIndex),
|
||||
SignatureScript: coinbaseScript,
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
})
|
||||
if len(mineTo) == 0 {
|
||||
tx.AddTxOut(&wire.TxOut{
|
||||
Value: blockchain.CalcBlockSubsidy(nextBlockHeight, net),
|
||||
PkScript: pkScript,
|
||||
})
|
||||
} else {
|
||||
for i := range mineTo {
|
||||
tx.AddTxOut(&mineTo[i])
|
||||
}
|
||||
}
|
||||
return btcutil.NewTx(tx), nil
|
||||
}
|
||||
|
||||
// CreateBlock creates a new block building from the previous block with a
|
||||
// specified blockversion and timestamp. If the timestamp passed is zero (not
|
||||
// initialized), then the timestamp of the previous block will be used plus 1
|
||||
// second is used. Passing nil for the previous block results in a block that
|
||||
// builds off of the genesis block for the specified chain.
|
||||
func CreateBlock(prevBlock *btcutil.Block, inclusionTxs []*btcutil.Tx,
|
||||
blockVersion int32, blockTime time.Time, miningAddr btcutil.Address,
|
||||
mineTo []wire.TxOut, net *chaincfg.Params) (*btcutil.Block, error) {
|
||||
|
||||
var (
|
||||
prevHash *chainhash.Hash
|
||||
blockHeight int32
|
||||
prevBlockTime time.Time
|
||||
)
|
||||
|
||||
// If the previous block isn't specified, then we'll construct a block
|
||||
// that builds off of the genesis block for the chain.
|
||||
if prevBlock == nil {
|
||||
prevHash = net.GenesisHash
|
||||
blockHeight = 1
|
||||
prevBlockTime = net.GenesisBlock.Header.Timestamp.Add(time.Minute)
|
||||
} else {
|
||||
prevHash = prevBlock.Hash()
|
||||
blockHeight = prevBlock.Height() + 1
|
||||
prevBlockTime = prevBlock.MsgBlock().Header.Timestamp
|
||||
}
|
||||
|
||||
// If a target block time was specified, then use that as the header's
|
||||
// timestamp. Otherwise, add one second to the previous block unless
|
||||
// it's the genesis block in which case use the current time.
|
||||
var ts time.Time
|
||||
switch {
|
||||
case !blockTime.IsZero():
|
||||
ts = blockTime
|
||||
default:
|
||||
ts = prevBlockTime.Add(time.Second)
|
||||
}
|
||||
|
||||
extraNonce := uint64(0)
|
||||
coinbaseScript, err := standardCoinbaseScript(blockHeight, extraNonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
coinbaseTx, err := createCoinbaseTx(coinbaseScript, blockHeight,
|
||||
miningAddr, mineTo, net)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a new block ready to be solved.
|
||||
blockTxns := []*btcutil.Tx{coinbaseTx}
|
||||
if inclusionTxs != nil {
|
||||
blockTxns = append(blockTxns, inclusionTxs...)
|
||||
}
|
||||
merkles := blockchain.BuildMerkleTreeStore(blockTxns, false)
|
||||
var block wire.MsgBlock
|
||||
block.Header = wire.BlockHeader{
|
||||
Version: blockVersion,
|
||||
PrevBlock: *prevHash,
|
||||
MerkleRoot: *merkles[len(merkles)-1],
|
||||
Timestamp: ts,
|
||||
Bits: net.PowLimitBits,
|
||||
}
|
||||
for _, tx := range blockTxns {
|
||||
if err := block.AddTransaction(tx.MsgTx()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
found := solveBlock(&block.Header, net.PowLimit)
|
||||
if !found {
|
||||
return nil, errors.New("Unable to solve block")
|
||||
}
|
||||
|
||||
utilBlock := btcutil.NewBlock(&block)
|
||||
utilBlock.SetHeight(blockHeight)
|
||||
return utilBlock, nil
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) 2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rpctest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// compileMtx guards access to the executable path so that the project is
|
||||
// only compiled once.
|
||||
compileMtx sync.Mutex
|
||||
|
||||
// executablePath is the path to the compiled executable. This is the empty
|
||||
// string until btcd is compiled. This should not be accessed directly;
|
||||
// instead use the function btcdExecutablePath().
|
||||
executablePath string
|
||||
)
|
||||
|
||||
// btcdExecutablePath returns a path to the btcd executable to be used by
|
||||
// rpctests. To ensure the code tests against the most up-to-date version of
|
||||
// btcd, this method compiles btcd the first time it is called. After that, the
|
||||
// generated binary is used for subsequent test harnesses. The executable file
|
||||
// is not cleaned up, but since it lives at a static path in a temp directory,
|
||||
// it is not a big deal.
|
||||
func btcdExecutablePath() (string, error) {
|
||||
compileMtx.Lock()
|
||||
defer compileMtx.Unlock()
|
||||
|
||||
// If btcd has already been compiled, just use that.
|
||||
if len(executablePath) != 0 {
|
||||
return executablePath, nil
|
||||
}
|
||||
|
||||
testDir, err := baseDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Build btcd and output an executable in a static temp path.
|
||||
outputPath := filepath.Join(testDir, "btcd")
|
||||
if runtime.GOOS == "windows" {
|
||||
outputPath += ".exe"
|
||||
}
|
||||
cmd := exec.Command(
|
||||
"go", "build", "-o", outputPath, "github.com/btcsuite/btcd",
|
||||
)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to build btcd: %v", err)
|
||||
}
|
||||
|
||||
// Save executable path so future calls do not recompile.
|
||||
executablePath = outputPath
|
||||
return executablePath, nil
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Package rpctest provides a btcd-specific RPC testing harness crafting and
|
||||
// executing integration tests by driving a `btcd` instance via the `RPC`
|
||||
// interface. Each instance of an active harness comes equipped with a simple
|
||||
// in-memory HD wallet capable of properly syncing to the generated chain,
|
||||
// creating new addresses, and crafting fully signed transactions paying to an
|
||||
// arbitrary set of outputs.
|
||||
//
|
||||
// This package was designed specifically to act as an RPC testing harness for
|
||||
// `btcd`. However, the constructs presented are general enough to be adapted to
|
||||
// any project wishing to programmatically drive a `btcd` instance of its
|
||||
// systems/integration tests.
|
||||
package rpctest
|
|
@ -0,0 +1,591 @@
|
|||
// Copyright (c) 2016-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rpctest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcutil/hdkeychain"
|
||||
)
|
||||
|
||||
var (
|
||||
// hdSeed is the BIP 32 seed used by the memWallet to initialize it's
|
||||
// HD root key. This value is hard coded in order to ensure
|
||||
// deterministic behavior across test runs.
|
||||
hdSeed = [chainhash.HashSize]byte{
|
||||
0x79, 0xa6, 0x1a, 0xdb, 0xc6, 0xe5, 0xa2, 0xe1,
|
||||
0x39, 0xd2, 0x71, 0x3a, 0x54, 0x6e, 0xc7, 0xc8,
|
||||
0x75, 0x63, 0x2e, 0x75, 0xf1, 0xdf, 0x9c, 0x3f,
|
||||
0xa6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
)
|
||||
|
||||
// utxo represents an unspent output spendable by the memWallet. The maturity
|
||||
// height of the transaction is recorded in order to properly observe the
|
||||
// maturity period of direct coinbase outputs.
|
||||
type utxo struct {
|
||||
pkScript []byte
|
||||
value btcutil.Amount
|
||||
keyIndex uint32
|
||||
maturityHeight int32
|
||||
isLocked bool
|
||||
}
|
||||
|
||||
// isMature returns true if the target utxo is considered "mature" at the
|
||||
// passed block height. Otherwise, false is returned.
|
||||
func (u *utxo) isMature(height int32) bool {
|
||||
return height >= u.maturityHeight
|
||||
}
|
||||
|
||||
// chainUpdate encapsulates an update to the current main chain. This struct is
|
||||
// used to sync up the memWallet each time a new block is connected to the main
|
||||
// chain.
|
||||
type chainUpdate struct {
|
||||
blockHeight int32
|
||||
filteredTxns []*btcutil.Tx
|
||||
isConnect bool // True if connect, false if disconnect
|
||||
}
|
||||
|
||||
// undoEntry is functionally the opposite of a chainUpdate. An undoEntry is
|
||||
// created for each new block received, then stored in a log in order to
|
||||
// properly handle block re-orgs.
|
||||
type undoEntry struct {
|
||||
utxosDestroyed map[wire.OutPoint]*utxo
|
||||
utxosCreated []wire.OutPoint
|
||||
}
|
||||
|
||||
// memWallet is a simple in-memory wallet whose purpose is to provide basic
|
||||
// wallet functionality to the harness. The wallet uses a hard-coded HD key
|
||||
// hierarchy which promotes reproducibility between harness test runs.
|
||||
type memWallet struct {
|
||||
coinbaseKey *btcec.PrivateKey
|
||||
coinbaseAddr btcutil.Address
|
||||
|
||||
// hdRoot is the root master private key for the wallet.
|
||||
hdRoot *hdkeychain.ExtendedKey
|
||||
|
||||
// hdIndex is the next available key index offset from the hdRoot.
|
||||
hdIndex uint32
|
||||
|
||||
// currentHeight is the latest height the wallet is known to be synced
|
||||
// to.
|
||||
currentHeight int32
|
||||
|
||||
// addrs tracks all addresses belonging to the wallet. The addresses
|
||||
// are indexed by their keypath from the hdRoot.
|
||||
addrs map[uint32]btcutil.Address
|
||||
|
||||
// utxos is the set of utxos spendable by the wallet.
|
||||
utxos map[wire.OutPoint]*utxo
|
||||
|
||||
// reorgJournal is a map storing an undo entry for each new block
|
||||
// received. Once a block is disconnected, the undo entry for the
|
||||
// particular height is evaluated, thereby rewinding the effect of the
|
||||
// disconnected block on the wallet's set of spendable utxos.
|
||||
reorgJournal map[int32]*undoEntry
|
||||
|
||||
chainUpdates []*chainUpdate
|
||||
chainUpdateSignal chan struct{}
|
||||
chainMtx sync.Mutex
|
||||
|
||||
net *chaincfg.Params
|
||||
|
||||
rpc *rpcclient.Client
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// newMemWallet creates and returns a fully initialized instance of the
|
||||
// memWallet given a particular blockchain's parameters.
|
||||
func newMemWallet(net *chaincfg.Params, harnessID uint32) (*memWallet, error) {
|
||||
// The wallet's final HD seed is: hdSeed || harnessID. This method
|
||||
// ensures that each harness instance uses a deterministic root seed
|
||||
// based on its harness ID.
|
||||
var harnessHDSeed [chainhash.HashSize + 4]byte
|
||||
copy(harnessHDSeed[:], hdSeed[:])
|
||||
binary.BigEndian.PutUint32(harnessHDSeed[:chainhash.HashSize], harnessID)
|
||||
|
||||
hdRoot, err := hdkeychain.NewMaster(harnessHDSeed[:], net)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// The first child key from the hd root is reserved as the coinbase
|
||||
// generation address.
|
||||
coinbaseChild, err := hdRoot.Child(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
coinbaseKey, err := coinbaseChild.ECPrivKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
coinbaseAddr, err := keyToAddr(coinbaseKey, net)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Track the coinbase generation address to ensure we properly track
|
||||
// newly generated bitcoin we can spend.
|
||||
addrs := make(map[uint32]btcutil.Address)
|
||||
addrs[0] = coinbaseAddr
|
||||
|
||||
return &memWallet{
|
||||
net: net,
|
||||
coinbaseKey: coinbaseKey,
|
||||
coinbaseAddr: coinbaseAddr,
|
||||
hdIndex: 1,
|
||||
hdRoot: hdRoot,
|
||||
addrs: addrs,
|
||||
utxos: make(map[wire.OutPoint]*utxo),
|
||||
chainUpdateSignal: make(chan struct{}),
|
||||
reorgJournal: make(map[int32]*undoEntry),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start launches all goroutines required for the wallet to function properly.
|
||||
func (m *memWallet) Start() {
|
||||
go m.chainSyncer()
|
||||
}
|
||||
|
||||
// SyncedHeight returns the height the wallet is known to be synced to.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *memWallet) SyncedHeight() int32 {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
return m.currentHeight
|
||||
}
|
||||
|
||||
// SetRPCClient saves the passed rpc connection to btcd as the wallet's
|
||||
// personal rpc connection.
|
||||
func (m *memWallet) SetRPCClient(rpcClient *rpcclient.Client) {
|
||||
m.rpc = rpcClient
|
||||
}
|
||||
|
||||
// IngestBlock is a call-back which is to be triggered each time a new block is
|
||||
// connected to the main chain. It queues the update for the chain syncer,
|
||||
// calling the private version in sequential order.
|
||||
func (m *memWallet) IngestBlock(height int32, header *wire.BlockHeader, filteredTxns []*btcutil.Tx) {
|
||||
// Append this new chain update to the end of the queue of new chain
|
||||
// updates.
|
||||
m.chainMtx.Lock()
|
||||
m.chainUpdates = append(m.chainUpdates, &chainUpdate{height,
|
||||
filteredTxns, true})
|
||||
m.chainMtx.Unlock()
|
||||
|
||||
// Launch a goroutine to signal the chainSyncer that a new update is
|
||||
// available. We do this in a new goroutine in order to avoid blocking
|
||||
// the main loop of the rpc client.
|
||||
go func() {
|
||||
m.chainUpdateSignal <- struct{}{}
|
||||
}()
|
||||
}
|
||||
|
||||
// ingestBlock updates the wallet's internal utxo state based on the outputs
|
||||
// created and destroyed within each block.
|
||||
func (m *memWallet) ingestBlock(update *chainUpdate) {
|
||||
// Update the latest synced height, then process each filtered
|
||||
// transaction in the block creating and destroying utxos within
|
||||
// the wallet as a result.
|
||||
m.currentHeight = update.blockHeight
|
||||
undo := &undoEntry{
|
||||
utxosDestroyed: make(map[wire.OutPoint]*utxo),
|
||||
}
|
||||
for _, tx := range update.filteredTxns {
|
||||
mtx := tx.MsgTx()
|
||||
isCoinbase := blockchain.IsCoinBaseTx(mtx)
|
||||
txHash := mtx.TxHash()
|
||||
m.evalOutputs(mtx.TxOut, &txHash, isCoinbase, undo)
|
||||
m.evalInputs(mtx.TxIn, undo)
|
||||
}
|
||||
|
||||
// Finally, record the undo entry for this block so we can
|
||||
// properly update our internal state in response to the block
|
||||
// being re-org'd from the main chain.
|
||||
m.reorgJournal[update.blockHeight] = undo
|
||||
}
|
||||
|
||||
// chainSyncer is a goroutine dedicated to processing new blocks in order to
|
||||
// keep the wallet's utxo state up to date.
|
||||
//
|
||||
// NOTE: This MUST be run as a goroutine.
|
||||
func (m *memWallet) chainSyncer() {
|
||||
var update *chainUpdate
|
||||
|
||||
for range m.chainUpdateSignal {
|
||||
// A new update is available, so pop the new chain update from
|
||||
// the front of the update queue.
|
||||
m.chainMtx.Lock()
|
||||
update = m.chainUpdates[0]
|
||||
m.chainUpdates[0] = nil // Set to nil to prevent GC leak.
|
||||
m.chainUpdates = m.chainUpdates[1:]
|
||||
m.chainMtx.Unlock()
|
||||
|
||||
m.Lock()
|
||||
if update.isConnect {
|
||||
m.ingestBlock(update)
|
||||
} else {
|
||||
m.unwindBlock(update)
|
||||
}
|
||||
m.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// evalOutputs evaluates each of the passed outputs, creating a new matching
|
||||
// utxo within the wallet if we're able to spend the output.
|
||||
func (m *memWallet) evalOutputs(outputs []*wire.TxOut, txHash *chainhash.Hash,
|
||||
isCoinbase bool, undo *undoEntry) {
|
||||
|
||||
for i, output := range outputs {
|
||||
pkScript := output.PkScript
|
||||
|
||||
// Scan all the addresses we currently control to see if the
|
||||
// output is paying to us.
|
||||
for keyIndex, addr := range m.addrs {
|
||||
pkHash := addr.ScriptAddress()
|
||||
if !bytes.Contains(pkScript, pkHash) {
|
||||
continue
|
||||
}
|
||||
|
||||
// If this is a coinbase output, then we mark the
|
||||
// maturity height at the proper block height in the
|
||||
// future.
|
||||
var maturityHeight int32
|
||||
if isCoinbase {
|
||||
maturityHeight = m.currentHeight + int32(m.net.CoinbaseMaturity)
|
||||
}
|
||||
|
||||
op := wire.OutPoint{Hash: *txHash, Index: uint32(i)}
|
||||
m.utxos[op] = &utxo{
|
||||
value: btcutil.Amount(output.Value),
|
||||
keyIndex: keyIndex,
|
||||
maturityHeight: maturityHeight,
|
||||
pkScript: pkScript,
|
||||
}
|
||||
undo.utxosCreated = append(undo.utxosCreated, op)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// evalInputs scans all the passed inputs, destroying any utxos within the
|
||||
// wallet which are spent by an input.
|
||||
func (m *memWallet) evalInputs(inputs []*wire.TxIn, undo *undoEntry) {
|
||||
for _, txIn := range inputs {
|
||||
op := txIn.PreviousOutPoint
|
||||
oldUtxo, ok := m.utxos[op]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
undo.utxosDestroyed[op] = oldUtxo
|
||||
delete(m.utxos, op)
|
||||
}
|
||||
}
|
||||
|
||||
// UnwindBlock is a call-back which is to be executed each time a block is
|
||||
// disconnected from the main chain. It queues the update for the chain syncer,
|
||||
// calling the private version in sequential order.
|
||||
func (m *memWallet) UnwindBlock(height int32, header *wire.BlockHeader) {
|
||||
// Append this new chain update to the end of the queue of new chain
|
||||
// updates.
|
||||
m.chainMtx.Lock()
|
||||
m.chainUpdates = append(m.chainUpdates, &chainUpdate{height,
|
||||
nil, false})
|
||||
m.chainMtx.Unlock()
|
||||
|
||||
// Launch a goroutine to signal the chainSyncer that a new update is
|
||||
// available. We do this in a new goroutine in order to avoid blocking
|
||||
// the main loop of the rpc client.
|
||||
go func() {
|
||||
m.chainUpdateSignal <- struct{}{}
|
||||
}()
|
||||
}
|
||||
|
||||
// unwindBlock undoes the effect that a particular block had on the wallet's
|
||||
// internal utxo state.
|
||||
func (m *memWallet) unwindBlock(update *chainUpdate) {
|
||||
undo := m.reorgJournal[update.blockHeight]
|
||||
|
||||
for _, utxo := range undo.utxosCreated {
|
||||
delete(m.utxos, utxo)
|
||||
}
|
||||
|
||||
for outPoint, utxo := range undo.utxosDestroyed {
|
||||
m.utxos[outPoint] = utxo
|
||||
}
|
||||
|
||||
delete(m.reorgJournal, update.blockHeight)
|
||||
}
|
||||
|
||||
// newAddress returns a new address from the wallet's hd key chain. It also
|
||||
// loads the address into the RPC client's transaction filter to ensure any
|
||||
// transactions that involve it are delivered via the notifications.
|
||||
func (m *memWallet) newAddress() (btcutil.Address, error) {
|
||||
index := m.hdIndex
|
||||
|
||||
childKey, err := m.hdRoot.Child(index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
privKey, err := childKey.ECPrivKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr, err := keyToAddr(privKey, m.net)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = m.rpc.LoadTxFilter(false, []btcutil.Address{addr}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.addrs[index] = addr
|
||||
|
||||
m.hdIndex++
|
||||
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// NewAddress returns a fresh address spendable by the wallet.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *memWallet) NewAddress() (btcutil.Address, error) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
return m.newAddress()
|
||||
}
|
||||
|
||||
// fundTx attempts to fund a transaction sending amt bitcoin. The coins are
|
||||
// selected such that the final amount spent pays enough fees as dictated by the
|
||||
// passed fee rate. The passed fee rate should be expressed in
|
||||
// satoshis-per-byte. The transaction being funded can optionally include a
|
||||
// change output indicated by the change boolean.
|
||||
//
|
||||
// NOTE: The memWallet's mutex must be held when this function is called.
|
||||
func (m *memWallet) fundTx(tx *wire.MsgTx, amt btcutil.Amount,
|
||||
feeRate btcutil.Amount, change bool) error {
|
||||
|
||||
const (
|
||||
// spendSize is the largest number of bytes of a sigScript
|
||||
// which spends a p2pkh output: OP_DATA_73 <sig> OP_DATA_33 <pubkey>
|
||||
spendSize = 1 + 73 + 1 + 33
|
||||
)
|
||||
|
||||
var (
|
||||
amtSelected btcutil.Amount
|
||||
txSize int
|
||||
)
|
||||
|
||||
for outPoint, utxo := range m.utxos {
|
||||
// Skip any outputs that are still currently immature or are
|
||||
// currently locked.
|
||||
if !utxo.isMature(m.currentHeight) || utxo.isLocked {
|
||||
continue
|
||||
}
|
||||
|
||||
amtSelected += utxo.value
|
||||
|
||||
// Add the selected output to the transaction, updating the
|
||||
// current tx size while accounting for the size of the future
|
||||
// sigScript.
|
||||
tx.AddTxIn(wire.NewTxIn(&outPoint, nil, nil))
|
||||
txSize = tx.SerializeSize() + spendSize*len(tx.TxIn)
|
||||
|
||||
// Calculate the fee required for the txn at this point
|
||||
// observing the specified fee rate. If we don't have enough
|
||||
// coins from he current amount selected to pay the fee, then
|
||||
// continue to grab more coins.
|
||||
reqFee := btcutil.Amount(txSize * int(feeRate))
|
||||
if amtSelected-reqFee < amt {
|
||||
continue
|
||||
}
|
||||
|
||||
// If we have any change left over and we should create a change
|
||||
// output, then add an additional output to the transaction
|
||||
// reserved for it.
|
||||
changeVal := amtSelected - amt - reqFee
|
||||
if changeVal > 0 && change {
|
||||
addr, err := m.newAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pkScript, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
changeOutput := &wire.TxOut{
|
||||
Value: int64(changeVal),
|
||||
PkScript: pkScript,
|
||||
}
|
||||
tx.AddTxOut(changeOutput)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we've reached this point, then coin selection failed due to an
|
||||
// insufficient amount of coins.
|
||||
return fmt.Errorf("not enough funds for coin selection")
|
||||
}
|
||||
|
||||
// SendOutputs creates, then sends a transaction paying to the specified output
|
||||
// while observing the passed fee rate. The passed fee rate should be expressed
|
||||
// in satoshis-per-byte.
|
||||
func (m *memWallet) SendOutputs(outputs []*wire.TxOut,
|
||||
feeRate btcutil.Amount) (*chainhash.Hash, error) {
|
||||
|
||||
tx, err := m.CreateTransaction(outputs, feeRate, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m.rpc.SendRawTransaction(tx, true)
|
||||
}
|
||||
|
||||
// SendOutputsWithoutChange creates and sends a transaction that pays to the
|
||||
// specified outputs while observing the passed fee rate and ignoring a change
|
||||
// output. The passed fee rate should be expressed in sat/b.
|
||||
func (m *memWallet) SendOutputsWithoutChange(outputs []*wire.TxOut,
|
||||
feeRate btcutil.Amount) (*chainhash.Hash, error) {
|
||||
|
||||
tx, err := m.CreateTransaction(outputs, feeRate, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m.rpc.SendRawTransaction(tx, true)
|
||||
}
|
||||
|
||||
// CreateTransaction returns a fully signed transaction paying to the specified
|
||||
// outputs while observing the desired fee rate. The passed fee rate should be
|
||||
// expressed in satoshis-per-byte. The transaction being created can optionally
|
||||
// include a change output indicated by the change boolean.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *memWallet) CreateTransaction(outputs []*wire.TxOut,
|
||||
feeRate btcutil.Amount, change bool) (*wire.MsgTx, error) {
|
||||
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
tx := wire.NewMsgTx(wire.TxVersion)
|
||||
|
||||
// Tally up the total amount to be sent in order to perform coin
|
||||
// selection shortly below.
|
||||
var outputAmt btcutil.Amount
|
||||
for _, output := range outputs {
|
||||
outputAmt += btcutil.Amount(output.Value)
|
||||
tx.AddTxOut(output)
|
||||
}
|
||||
|
||||
// Attempt to fund the transaction with spendable utxos.
|
||||
if err := m.fundTx(tx, outputAmt, feeRate, change); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Populate all the selected inputs with valid sigScript for spending.
|
||||
// Along the way record all outputs being spent in order to avoid a
|
||||
// potential double spend.
|
||||
spentOutputs := make([]*utxo, 0, len(tx.TxIn))
|
||||
for i, txIn := range tx.TxIn {
|
||||
outPoint := txIn.PreviousOutPoint
|
||||
utxo := m.utxos[outPoint]
|
||||
|
||||
extendedKey, err := m.hdRoot.Child(utxo.keyIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
privKey, err := extendedKey.ECPrivKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sigScript, err := txscript.SignatureScript(tx, i, utxo.pkScript,
|
||||
txscript.SigHashAll, privKey, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txIn.SignatureScript = sigScript
|
||||
|
||||
spentOutputs = append(spentOutputs, utxo)
|
||||
}
|
||||
|
||||
// As these outputs are now being spent by this newly created
|
||||
// transaction, mark the outputs are "locked". This action ensures
|
||||
// these outputs won't be double spent by any subsequent transactions.
|
||||
// These locked outputs can be freed via a call to UnlockOutputs.
|
||||
for _, utxo := range spentOutputs {
|
||||
utxo.isLocked = true
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
// UnlockOutputs unlocks any outputs which were previously locked due to
|
||||
// being selected to fund a transaction via the CreateTransaction method.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *memWallet) UnlockOutputs(inputs []*wire.TxIn) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
for _, input := range inputs {
|
||||
utxo, ok := m.utxos[input.PreviousOutPoint]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
utxo.isLocked = false
|
||||
}
|
||||
}
|
||||
|
||||
// ConfirmedBalance returns the confirmed balance of the wallet.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *memWallet) ConfirmedBalance() btcutil.Amount {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
var balance btcutil.Amount
|
||||
for _, utxo := range m.utxos {
|
||||
// Prevent any immature or locked outputs from contributing to
|
||||
// the wallet's total confirmed balance.
|
||||
if !utxo.isMature(m.currentHeight) || utxo.isLocked {
|
||||
continue
|
||||
}
|
||||
|
||||
balance += utxo.value
|
||||
}
|
||||
|
||||
return balance
|
||||
}
|
||||
|
||||
// keyToAddr maps the passed private to corresponding p2pkh address.
|
||||
func keyToAddr(key *btcec.PrivateKey, net *chaincfg.Params) (btcutil.Address, error) {
|
||||
serializedKey := key.PubKey().SerializeCompressed()
|
||||
pubKeyAddr, err := btcutil.NewAddressPubKey(serializedKey, net)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pubKeyAddr.AddressPubKeyHash(), nil
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
// Copyright (c) 2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rpctest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
rpc "github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
// nodeConfig contains all the args, and data required to launch a btcd process
|
||||
// and connect the rpc client to it.
|
||||
type nodeConfig struct {
|
||||
rpcUser string
|
||||
rpcPass string
|
||||
listen string
|
||||
rpcListen string
|
||||
rpcConnect string
|
||||
dataDir string
|
||||
logDir string
|
||||
profile string
|
||||
debugLevel string
|
||||
extra []string
|
||||
prefix string
|
||||
|
||||
exe string
|
||||
endpoint string
|
||||
certFile string
|
||||
keyFile string
|
||||
certificates []byte
|
||||
}
|
||||
|
||||
// newConfig returns a newConfig with all default values.
|
||||
func newConfig(prefix, certFile, keyFile string, extra []string) (*nodeConfig, error) {
|
||||
btcdPath, err := btcdExecutablePath()
|
||||
if err != nil {
|
||||
btcdPath = "btcd"
|
||||
}
|
||||
|
||||
a := &nodeConfig{
|
||||
listen: "127.0.0.1:18555",
|
||||
rpcListen: "127.0.0.1:18556",
|
||||
rpcUser: "user",
|
||||
rpcPass: "pass",
|
||||
extra: extra,
|
||||
prefix: prefix,
|
||||
exe: btcdPath,
|
||||
endpoint: "ws",
|
||||
certFile: certFile,
|
||||
keyFile: keyFile,
|
||||
}
|
||||
if err := a.setDefaults(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// setDefaults sets the default values of the config. It also creates the
|
||||
// temporary data, and log directories which must be cleaned up with a call to
|
||||
// cleanup().
|
||||
func (n *nodeConfig) setDefaults() error {
|
||||
datadir, err := ioutil.TempDir("", n.prefix+"-data")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.dataDir = datadir
|
||||
logdir, err := ioutil.TempDir("", n.prefix+"-logs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.logDir = logdir
|
||||
cert, err := ioutil.ReadFile(n.certFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.certificates = cert
|
||||
return nil
|
||||
}
|
||||
|
||||
// arguments returns an array of arguments that be used to launch the btcd
|
||||
// process.
|
||||
func (n *nodeConfig) arguments() []string {
|
||||
args := []string{}
|
||||
if n.rpcUser != "" {
|
||||
// --rpcuser
|
||||
args = append(args, fmt.Sprintf("--rpcuser=%s", n.rpcUser))
|
||||
}
|
||||
if n.rpcPass != "" {
|
||||
// --rpcpass
|
||||
args = append(args, fmt.Sprintf("--rpcpass=%s", n.rpcPass))
|
||||
}
|
||||
if n.listen != "" {
|
||||
// --listen
|
||||
args = append(args, fmt.Sprintf("--listen=%s", n.listen))
|
||||
}
|
||||
if n.rpcListen != "" {
|
||||
// --rpclisten
|
||||
args = append(args, fmt.Sprintf("--rpclisten=%s", n.rpcListen))
|
||||
}
|
||||
if n.rpcConnect != "" {
|
||||
// --rpcconnect
|
||||
args = append(args, fmt.Sprintf("--rpcconnect=%s", n.rpcConnect))
|
||||
}
|
||||
// --rpccert
|
||||
args = append(args, fmt.Sprintf("--rpccert=%s", n.certFile))
|
||||
// --rpckey
|
||||
args = append(args, fmt.Sprintf("--rpckey=%s", n.keyFile))
|
||||
if n.dataDir != "" {
|
||||
// --datadir
|
||||
args = append(args, fmt.Sprintf("--datadir=%s", n.dataDir))
|
||||
}
|
||||
if n.logDir != "" {
|
||||
// --logdir
|
||||
args = append(args, fmt.Sprintf("--logdir=%s", n.logDir))
|
||||
}
|
||||
if n.profile != "" {
|
||||
// --profile
|
||||
args = append(args, fmt.Sprintf("--profile=%s", n.profile))
|
||||
}
|
||||
if n.debugLevel != "" {
|
||||
// --debuglevel
|
||||
args = append(args, fmt.Sprintf("--debuglevel=%s", n.debugLevel))
|
||||
}
|
||||
args = append(args, n.extra...)
|
||||
return args
|
||||
}
|
||||
|
||||
// command returns the exec.Cmd which will be used to start the btcd process.
|
||||
func (n *nodeConfig) command() *exec.Cmd {
|
||||
return exec.Command(n.exe, n.arguments()...)
|
||||
}
|
||||
|
||||
// rpcConnConfig returns the rpc connection config that can be used to connect
|
||||
// to the btcd process that is launched via Start().
|
||||
func (n *nodeConfig) rpcConnConfig() rpc.ConnConfig {
|
||||
return rpc.ConnConfig{
|
||||
Host: n.rpcListen,
|
||||
Endpoint: n.endpoint,
|
||||
User: n.rpcUser,
|
||||
Pass: n.rpcPass,
|
||||
Certificates: n.certificates,
|
||||
DisableAutoReconnect: true,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the string representation of this nodeConfig.
|
||||
func (n *nodeConfig) String() string {
|
||||
return n.prefix
|
||||
}
|
||||
|
||||
// cleanup removes the tmp data and log directories.
|
||||
func (n *nodeConfig) cleanup() error {
|
||||
dirs := []string{
|
||||
n.logDir,
|
||||
n.dataDir,
|
||||
}
|
||||
var err error
|
||||
for _, dir := range dirs {
|
||||
if err = os.RemoveAll(dir); err != nil {
|
||||
log.Printf("Cannot remove dir %s: %v", dir, err)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// node houses the necessary state required to configure, launch, and manage a
|
||||
// btcd process.
|
||||
type node struct {
|
||||
config *nodeConfig
|
||||
|
||||
cmd *exec.Cmd
|
||||
pidFile string
|
||||
|
||||
dataDir string
|
||||
}
|
||||
|
||||
// newNode creates a new node instance according to the passed config. dataDir
|
||||
// will be used to hold a file recording the pid of the launched process, and
|
||||
// as the base for the log and data directories for btcd.
|
||||
func newNode(config *nodeConfig, dataDir string) (*node, error) {
|
||||
return &node{
|
||||
config: config,
|
||||
dataDir: dataDir,
|
||||
cmd: config.command(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// start creates a new btcd process, and writes its pid in a file reserved for
|
||||
// recording the pid of the launched process. This file can be used to
|
||||
// terminate the process in case of a hang, or panic. In the case of a failing
|
||||
// test case, or panic, it is important that the process be stopped via stop(),
|
||||
// otherwise, it will persist unless explicitly killed.
|
||||
func (n *node) start() error {
|
||||
if err := n.cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pid, err := os.Create(filepath.Join(n.dataDir,
|
||||
fmt.Sprintf("%s.pid", n.config)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.pidFile = pid.Name()
|
||||
if _, err = fmt.Fprintf(pid, "%d\n", n.cmd.Process.Pid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := pid.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// stop interrupts the running btcd process process, and waits until it exits
|
||||
// properly. On windows, interrupt is not supported, so a kill signal is used
|
||||
// instead
|
||||
func (n *node) stop() error {
|
||||
if n.cmd == nil || n.cmd.Process == nil {
|
||||
// return if not properly initialized
|
||||
// or error starting the process
|
||||
return nil
|
||||
}
|
||||
defer n.cmd.Wait()
|
||||
if runtime.GOOS == "windows" {
|
||||
return n.cmd.Process.Signal(os.Kill)
|
||||
}
|
||||
return n.cmd.Process.Signal(os.Interrupt)
|
||||
}
|
||||
|
||||
// cleanup cleanups process and args files. The file housing the pid of the
|
||||
// created process will be deleted, as well as any directories created by the
|
||||
// process.
|
||||
func (n *node) cleanup() error {
|
||||
if n.pidFile != "" {
|
||||
if err := os.Remove(n.pidFile); err != nil {
|
||||
log.Printf("unable to remove file %s: %v", n.pidFile,
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
return n.config.cleanup()
|
||||
}
|
||||
|
||||
// shutdown terminates the running btcd process, and cleans up all
|
||||
// file/directories created by node.
|
||||
func (n *node) shutdown() error {
|
||||
if err := n.stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := n.cleanup(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// genCertPair generates a key/cert pair to the paths provided.
|
||||
func genCertPair(certFile, keyFile string) error {
|
||||
org := "rpctest autogenerated cert"
|
||||
validUntil := time.Now().Add(10 * 365 * 24 * time.Hour)
|
||||
cert, key, err := btcutil.NewTLSCertPair(org, validUntil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write cert and key files.
|
||||
if err = ioutil.WriteFile(certFile, cert, 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ioutil.WriteFile(keyFile, key, 0600); err != nil {
|
||||
os.Remove(certFile)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
500
vendor/github.com/btcsuite/btcd/integration/rpctest/rpc_harness.go
generated
vendored
Normal file
500
vendor/github.com/btcsuite/btcd/integration/rpctest/rpc_harness.go
generated
vendored
Normal file
|
@ -0,0 +1,500 @@
|
|||
// Copyright (c) 2016-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rpctest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// These constants define the minimum and maximum p2p and rpc port
|
||||
// numbers used by a test harness. The min port is inclusive while the
|
||||
// max port is exclusive.
|
||||
minPeerPort = 10000
|
||||
maxPeerPort = 35000
|
||||
minRPCPort = maxPeerPort
|
||||
maxRPCPort = 60000
|
||||
|
||||
// BlockVersion is the default block version used when generating
|
||||
// blocks.
|
||||
BlockVersion = 4
|
||||
)
|
||||
|
||||
var (
|
||||
// current number of active test nodes.
|
||||
numTestInstances = 0
|
||||
|
||||
// processID is the process ID of the current running process. It is
|
||||
// used to calculate ports based upon it when launching an rpc
|
||||
// harnesses. The intent is to allow multiple process to run in
|
||||
// parallel without port collisions.
|
||||
//
|
||||
// It should be noted however that there is still some small probability
|
||||
// that there will be port collisions either due to other processes
|
||||
// running or simply due to the stars aligning on the process IDs.
|
||||
processID = os.Getpid()
|
||||
|
||||
// testInstances is a private package-level slice used to keep track of
|
||||
// all active test harnesses. This global can be used to perform
|
||||
// various "joins", shutdown several active harnesses after a test,
|
||||
// etc.
|
||||
testInstances = make(map[string]*Harness)
|
||||
|
||||
// Used to protest concurrent access to above declared variables.
|
||||
harnessStateMtx sync.RWMutex
|
||||
)
|
||||
|
||||
// HarnessTestCase represents a test-case which utilizes an instance of the
|
||||
// Harness to exercise functionality.
|
||||
type HarnessTestCase func(r *Harness, t *testing.T)
|
||||
|
||||
// Harness fully encapsulates an active btcd process to provide a unified
|
||||
// platform for creating rpc driven integration tests involving btcd. The
|
||||
// active btcd node will typically be run in simnet mode in order to allow for
|
||||
// easy generation of test blockchains. The active btcd process is fully
|
||||
// managed by Harness, which handles the necessary initialization, and teardown
|
||||
// of the process along with any temporary directories created as a result.
|
||||
// Multiple Harness instances may be run concurrently, in order to allow for
|
||||
// testing complex scenarios involving multiple nodes. The harness also
|
||||
// includes an in-memory wallet to streamline various classes of tests.
|
||||
type Harness struct {
|
||||
// ActiveNet is the parameters of the blockchain the Harness belongs
|
||||
// to.
|
||||
ActiveNet *chaincfg.Params
|
||||
|
||||
Node *rpcclient.Client
|
||||
node *node
|
||||
handlers *rpcclient.NotificationHandlers
|
||||
|
||||
wallet *memWallet
|
||||
|
||||
testNodeDir string
|
||||
maxConnRetries int
|
||||
nodeNum int
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// New creates and initializes new instance of the rpc test harness.
|
||||
// Optionally, websocket handlers and a specified configuration may be passed.
|
||||
// In the case that a nil config is passed, a default configuration will be
|
||||
// used.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access.
|
||||
func New(activeNet *chaincfg.Params, handlers *rpcclient.NotificationHandlers,
|
||||
extraArgs []string) (*Harness, error) {
|
||||
|
||||
harnessStateMtx.Lock()
|
||||
defer harnessStateMtx.Unlock()
|
||||
|
||||
// Add a flag for the appropriate network type based on the provided
|
||||
// chain params.
|
||||
switch activeNet.Net {
|
||||
case wire.MainNet:
|
||||
// No extra flags since mainnet is the default
|
||||
case wire.TestNet3:
|
||||
extraArgs = append(extraArgs, "--testnet")
|
||||
case wire.TestNet:
|
||||
extraArgs = append(extraArgs, "--regtest")
|
||||
case wire.SimNet:
|
||||
extraArgs = append(extraArgs, "--simnet")
|
||||
default:
|
||||
return nil, fmt.Errorf("rpctest.New must be called with one " +
|
||||
"of the supported chain networks")
|
||||
}
|
||||
|
||||
testDir, err := baseDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
harnessID := strconv.Itoa(numTestInstances)
|
||||
nodeTestData, err := ioutil.TempDir(testDir, "harness-"+harnessID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certFile := filepath.Join(nodeTestData, "rpc.cert")
|
||||
keyFile := filepath.Join(nodeTestData, "rpc.key")
|
||||
if err := genCertPair(certFile, keyFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wallet, err := newMemWallet(activeNet, uint32(numTestInstances))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
miningAddr := fmt.Sprintf("--miningaddr=%s", wallet.coinbaseAddr)
|
||||
extraArgs = append(extraArgs, miningAddr)
|
||||
|
||||
config, err := newConfig("rpctest", certFile, keyFile, extraArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate p2p+rpc listening addresses.
|
||||
config.listen, config.rpcListen = generateListeningAddresses()
|
||||
|
||||
// Create the testing node bounded to the simnet.
|
||||
node, err := newNode(config, nodeTestData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nodeNum := numTestInstances
|
||||
numTestInstances++
|
||||
|
||||
if handlers == nil {
|
||||
handlers = &rpcclient.NotificationHandlers{}
|
||||
}
|
||||
|
||||
// If a handler for the OnFilteredBlock{Connected,Disconnected} callback
|
||||
// callback has already been set, then create a wrapper callback which
|
||||
// executes both the currently registered callback and the mem wallet's
|
||||
// callback.
|
||||
if handlers.OnFilteredBlockConnected != nil {
|
||||
obc := handlers.OnFilteredBlockConnected
|
||||
handlers.OnFilteredBlockConnected = func(height int32, header *wire.BlockHeader, filteredTxns []*btcutil.Tx) {
|
||||
wallet.IngestBlock(height, header, filteredTxns)
|
||||
obc(height, header, filteredTxns)
|
||||
}
|
||||
} else {
|
||||
// Otherwise, we can claim the callback ourselves.
|
||||
handlers.OnFilteredBlockConnected = wallet.IngestBlock
|
||||
}
|
||||
if handlers.OnFilteredBlockDisconnected != nil {
|
||||
obd := handlers.OnFilteredBlockDisconnected
|
||||
handlers.OnFilteredBlockDisconnected = func(height int32, header *wire.BlockHeader) {
|
||||
wallet.UnwindBlock(height, header)
|
||||
obd(height, header)
|
||||
}
|
||||
} else {
|
||||
handlers.OnFilteredBlockDisconnected = wallet.UnwindBlock
|
||||
}
|
||||
|
||||
h := &Harness{
|
||||
handlers: handlers,
|
||||
node: node,
|
||||
maxConnRetries: 20,
|
||||
testNodeDir: nodeTestData,
|
||||
ActiveNet: activeNet,
|
||||
nodeNum: nodeNum,
|
||||
wallet: wallet,
|
||||
}
|
||||
|
||||
// Track this newly created test instance within the package level
|
||||
// global map of all active test instances.
|
||||
testInstances[h.testNodeDir] = h
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// SetUp initializes the rpc test state. Initialization includes: starting up a
|
||||
// simnet node, creating a websockets client and connecting to the started
|
||||
// node, and finally: optionally generating and submitting a testchain with a
|
||||
// configurable number of mature coinbase outputs coinbase outputs.
|
||||
//
|
||||
// NOTE: This method and TearDown should always be called from the same
|
||||
// goroutine as they are not concurrent safe.
|
||||
func (h *Harness) SetUp(createTestChain bool, numMatureOutputs uint32) error {
|
||||
// Start the btcd node itself. This spawns a new process which will be
|
||||
// managed
|
||||
if err := h.node.start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := h.connectRPCClient(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.wallet.Start()
|
||||
|
||||
// Filter transactions that pay to the coinbase associated with the
|
||||
// wallet.
|
||||
filterAddrs := []btcutil.Address{h.wallet.coinbaseAddr}
|
||||
if err := h.Node.LoadTxFilter(true, filterAddrs, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure btcd properly dispatches our registered call-back for each new
|
||||
// block. Otherwise, the memWallet won't function properly.
|
||||
if err := h.Node.NotifyBlocks(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a test chain with the desired number of mature coinbase
|
||||
// outputs.
|
||||
if createTestChain && numMatureOutputs != 0 {
|
||||
numToGenerate := (uint32(h.ActiveNet.CoinbaseMaturity) +
|
||||
numMatureOutputs)
|
||||
_, err := h.Node.Generate(numToGenerate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Block until the wallet has fully synced up to the tip of the main
|
||||
// chain.
|
||||
_, height, err := h.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ticker := time.NewTicker(time.Millisecond * 100)
|
||||
for range ticker.C {
|
||||
walletHeight := h.wallet.SyncedHeight()
|
||||
if walletHeight == height {
|
||||
break
|
||||
}
|
||||
}
|
||||
ticker.Stop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// tearDown stops the running rpc test instance. All created processes are
|
||||
// killed, and temporary directories removed.
|
||||
//
|
||||
// This function MUST be called with the harness state mutex held (for writes).
|
||||
func (h *Harness) tearDown() error {
|
||||
if h.Node != nil {
|
||||
h.Node.Shutdown()
|
||||
}
|
||||
|
||||
if err := h.node.shutdown(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(h.testNodeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(testInstances, h.testNodeDir)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TearDown stops the running rpc test instance. All created processes are
|
||||
// killed, and temporary directories removed.
|
||||
//
|
||||
// NOTE: This method and SetUp should always be called from the same goroutine
|
||||
// as they are not concurrent safe.
|
||||
func (h *Harness) TearDown() error {
|
||||
harnessStateMtx.Lock()
|
||||
defer harnessStateMtx.Unlock()
|
||||
|
||||
return h.tearDown()
|
||||
}
|
||||
|
||||
// connectRPCClient attempts to establish an RPC connection to the created btcd
|
||||
// process belonging to this Harness instance. If the initial connection
|
||||
// attempt fails, this function will retry h.maxConnRetries times, backing off
|
||||
// the time between subsequent attempts. If after h.maxConnRetries attempts,
|
||||
// we're not able to establish a connection, this function returns with an
|
||||
// error.
|
||||
func (h *Harness) connectRPCClient() error {
|
||||
var client *rpcclient.Client
|
||||
var err error
|
||||
|
||||
rpcConf := h.node.config.rpcConnConfig()
|
||||
for i := 0; i < h.maxConnRetries; i++ {
|
||||
if client, err = rpcclient.New(&rpcConf, h.handlers); err != nil {
|
||||
time.Sleep(time.Duration(i) * 50 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if client == nil {
|
||||
return fmt.Errorf("connection timeout")
|
||||
}
|
||||
|
||||
h.Node = client
|
||||
h.wallet.SetRPCClient(client)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewAddress returns a fresh address spendable by the Harness' internal
|
||||
// wallet.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (h *Harness) NewAddress() (btcutil.Address, error) {
|
||||
return h.wallet.NewAddress()
|
||||
}
|
||||
|
||||
// ConfirmedBalance returns the confirmed balance of the Harness' internal
|
||||
// wallet.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (h *Harness) ConfirmedBalance() btcutil.Amount {
|
||||
return h.wallet.ConfirmedBalance()
|
||||
}
|
||||
|
||||
// SendOutputs creates, signs, and finally broadcasts a transaction spending
|
||||
// the harness' available mature coinbase outputs creating new outputs
|
||||
// according to targetOutputs.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (h *Harness) SendOutputs(targetOutputs []*wire.TxOut,
|
||||
feeRate btcutil.Amount) (*chainhash.Hash, error) {
|
||||
|
||||
return h.wallet.SendOutputs(targetOutputs, feeRate)
|
||||
}
|
||||
|
||||
// SendOutputsWithoutChange creates and sends a transaction that pays to the
|
||||
// specified outputs while observing the passed fee rate and ignoring a change
|
||||
// output. The passed fee rate should be expressed in sat/b.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (h *Harness) SendOutputsWithoutChange(targetOutputs []*wire.TxOut,
|
||||
feeRate btcutil.Amount) (*chainhash.Hash, error) {
|
||||
|
||||
return h.wallet.SendOutputsWithoutChange(targetOutputs, feeRate)
|
||||
}
|
||||
|
||||
// CreateTransaction returns a fully signed transaction paying to the specified
|
||||
// outputs while observing the desired fee rate. The passed fee rate should be
|
||||
// expressed in satoshis-per-byte. The transaction being created can optionally
|
||||
// include a change output indicated by the change boolean. Any unspent outputs
|
||||
// selected as inputs for the crafted transaction are marked as unspendable in
|
||||
// order to avoid potential double-spends by future calls to this method. If the
|
||||
// created transaction is cancelled for any reason then the selected inputs MUST
|
||||
// be freed via a call to UnlockOutputs. Otherwise, the locked inputs won't be
|
||||
// returned to the pool of spendable outputs.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (h *Harness) CreateTransaction(targetOutputs []*wire.TxOut,
|
||||
feeRate btcutil.Amount, change bool) (*wire.MsgTx, error) {
|
||||
|
||||
return h.wallet.CreateTransaction(targetOutputs, feeRate, change)
|
||||
}
|
||||
|
||||
// UnlockOutputs unlocks any outputs which were previously marked as
|
||||
// unspendabe due to being selected to fund a transaction via the
|
||||
// CreateTransaction method.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (h *Harness) UnlockOutputs(inputs []*wire.TxIn) {
|
||||
h.wallet.UnlockOutputs(inputs)
|
||||
}
|
||||
|
||||
// RPCConfig returns the harnesses current rpc configuration. This allows other
|
||||
// potential RPC clients created within tests to connect to a given test
|
||||
// harness instance.
|
||||
func (h *Harness) RPCConfig() rpcclient.ConnConfig {
|
||||
return h.node.config.rpcConnConfig()
|
||||
}
|
||||
|
||||
// P2PAddress returns the harness' P2P listening address. This allows potential
|
||||
// peers (such as SPV peers) created within tests to connect to a given test
|
||||
// harness instance.
|
||||
func (h *Harness) P2PAddress() string {
|
||||
return h.node.config.listen
|
||||
}
|
||||
|
||||
// GenerateAndSubmitBlock creates a block whose contents include the passed
|
||||
// transactions and submits it to the running simnet node. For generating
|
||||
// blocks with only a coinbase tx, callers can simply pass nil instead of
|
||||
// transactions to be mined. Additionally, a custom block version can be set by
|
||||
// the caller. A blockVersion of -1 indicates that the current default block
|
||||
// version should be used. An uninitialized time.Time should be used for the
|
||||
// blockTime parameter if one doesn't wish to set a custom time.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (h *Harness) GenerateAndSubmitBlock(txns []*btcutil.Tx, blockVersion int32,
|
||||
blockTime time.Time) (*btcutil.Block, error) {
|
||||
return h.GenerateAndSubmitBlockWithCustomCoinbaseOutputs(txns,
|
||||
blockVersion, blockTime, []wire.TxOut{})
|
||||
}
|
||||
|
||||
// GenerateAndSubmitBlockWithCustomCoinbaseOutputs creates a block whose
|
||||
// contents include the passed coinbase outputs and transactions and submits
|
||||
// it to the running simnet node. For generating blocks with only a coinbase tx,
|
||||
// callers can simply pass nil instead of transactions to be mined.
|
||||
// Additionally, a custom block version can be set by the caller. A blockVersion
|
||||
// of -1 indicates that the current default block version should be used. An
|
||||
// uninitialized time.Time should be used for the blockTime parameter if one
|
||||
// doesn't wish to set a custom time. The mineTo list of outputs will be added
|
||||
// to the coinbase; this is not checked for correctness until the block is
|
||||
// submitted; thus, it is the caller's responsibility to ensure that the outputs
|
||||
// are correct. If the list is empty, the coinbase reward goes to the wallet
|
||||
// managed by the Harness.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (h *Harness) GenerateAndSubmitBlockWithCustomCoinbaseOutputs(
|
||||
txns []*btcutil.Tx, blockVersion int32, blockTime time.Time,
|
||||
mineTo []wire.TxOut) (*btcutil.Block, error) {
|
||||
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
if blockVersion == -1 {
|
||||
blockVersion = BlockVersion
|
||||
}
|
||||
|
||||
prevBlockHash, prevBlockHeight, err := h.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mBlock, err := h.Node.GetBlock(prevBlockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prevBlock := btcutil.NewBlock(mBlock)
|
||||
prevBlock.SetHeight(prevBlockHeight)
|
||||
|
||||
// Create a new block including the specified transactions
|
||||
newBlock, err := CreateBlock(prevBlock, txns, blockVersion,
|
||||
blockTime, h.wallet.coinbaseAddr, mineTo, h.ActiveNet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Submit the block to the simnet node.
|
||||
if err := h.Node.SubmitBlock(newBlock, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newBlock, nil
|
||||
}
|
||||
|
||||
// generateListeningAddresses returns two strings representing listening
|
||||
// addresses designated for the current rpc test. If there haven't been any
|
||||
// test instances created, the default ports are used. Otherwise, in order to
|
||||
// support multiple test nodes running at once, the p2p and rpc port are
|
||||
// incremented after each initialization.
|
||||
func generateListeningAddresses() (string, string) {
|
||||
localhost := "127.0.0.1"
|
||||
|
||||
portString := func(minPort, maxPort int) string {
|
||||
port := minPort + numTestInstances + ((20 * processID) %
|
||||
(maxPort - minPort))
|
||||
return strconv.Itoa(port)
|
||||
}
|
||||
|
||||
p2p := net.JoinHostPort(localhost, portString(minPeerPort, maxPeerPort))
|
||||
rpc := net.JoinHostPort(localhost, portString(minRPCPort, maxRPCPort))
|
||||
return p2p, rpc
|
||||
}
|
||||
|
||||
// baseDir is the directory path of the temp directory for all rpctest files.
|
||||
func baseDir() (string, error) {
|
||||
dirPath := filepath.Join(os.TempDir(), "btcd", "rpctest")
|
||||
err := os.MkdirAll(dirPath, 0755)
|
||||
return dirPath, err
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
// Copyright (c) 2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rpctest
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
)
|
||||
|
||||
// JoinType is an enum representing a particular type of "node join". A node
|
||||
// join is a synchronization tool used to wait until a subset of nodes have a
|
||||
// consistent state with respect to an attribute.
|
||||
type JoinType uint8
|
||||
|
||||
const (
|
||||
// Blocks is a JoinType which waits until all nodes share the same
|
||||
// block height.
|
||||
Blocks JoinType = iota
|
||||
|
||||
// Mempools is a JoinType which blocks until all nodes have identical
|
||||
// mempool.
|
||||
Mempools
|
||||
)
|
||||
|
||||
// JoinNodes is a synchronization tool used to block until all passed nodes are
|
||||
// fully synced with respect to an attribute. This function will block for a
|
||||
// period of time, finally returning once all nodes are synced according to the
|
||||
// passed JoinType. This function be used to to ensure all active test
|
||||
// harnesses are at a consistent state before proceeding to an assertion or
|
||||
// check within rpc tests.
|
||||
func JoinNodes(nodes []*Harness, joinType JoinType) error {
|
||||
switch joinType {
|
||||
case Blocks:
|
||||
return syncBlocks(nodes)
|
||||
case Mempools:
|
||||
return syncMempools(nodes)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncMempools blocks until all nodes have identical mempools.
|
||||
func syncMempools(nodes []*Harness) error {
|
||||
poolsMatch := false
|
||||
|
||||
retry:
|
||||
for !poolsMatch {
|
||||
firstPool, err := nodes[0].Node.GetRawMempool()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If all nodes have an identical mempool with respect to the
|
||||
// first node, then we're done. Otherwise, drop back to the top
|
||||
// of the loop and retry after a short wait period.
|
||||
for _, node := range nodes[1:] {
|
||||
nodePool, err := node.Node.GetRawMempool()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(firstPool, nodePool) {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
continue retry
|
||||
}
|
||||
}
|
||||
|
||||
poolsMatch = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncBlocks blocks until all nodes report the same best chain.
|
||||
func syncBlocks(nodes []*Harness) error {
|
||||
blocksMatch := false
|
||||
|
||||
retry:
|
||||
for !blocksMatch {
|
||||
var prevHash *chainhash.Hash
|
||||
var prevHeight int32
|
||||
for _, node := range nodes {
|
||||
blockHash, blockHeight, err := node.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if prevHash != nil && (*blockHash != *prevHash ||
|
||||
blockHeight != prevHeight) {
|
||||
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
continue retry
|
||||
}
|
||||
prevHash, prevHeight = blockHash, blockHeight
|
||||
}
|
||||
|
||||
blocksMatch = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConnectNode establishes a new peer-to-peer connection between the "from"
|
||||
// harness and the "to" harness. The connection made is flagged as persistent,
|
||||
// therefore in the case of disconnects, "from" will attempt to reestablish a
|
||||
// connection to the "to" harness.
|
||||
func ConnectNode(from *Harness, to *Harness) error {
|
||||
peerInfo, err := from.Node.GetPeerInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
numPeers := len(peerInfo)
|
||||
|
||||
targetAddr := to.node.config.listen
|
||||
if err := from.Node.AddNode(targetAddr, rpcclient.ANAdd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Block until a new connection has been established.
|
||||
peerInfo, err = from.Node.GetPeerInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for len(peerInfo) <= numPeers {
|
||||
peerInfo, err = from.Node.GetPeerInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TearDownAll tears down all active test harnesses.
|
||||
func TearDownAll() error {
|
||||
harnessStateMtx.Lock()
|
||||
defer harnessStateMtx.Unlock()
|
||||
|
||||
for _, harness := range testInstances {
|
||||
if err := harness.tearDown(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ActiveHarnesses returns a slice of all currently active test harnesses. A
|
||||
// test harness if considered "active" if it has been created, but not yet torn
|
||||
// down.
|
||||
func ActiveHarnesses() []*Harness {
|
||||
harnessStateMtx.RLock()
|
||||
defer harnessStateMtx.RUnlock()
|
||||
|
||||
activeNodes := make([]*Harness, 0, len(testInstances))
|
||||
for _, harness := range testInstances {
|
||||
activeNodes = append(activeNodes, harness)
|
||||
}
|
||||
|
||||
return activeNodes
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package peer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// mruInventoryMap provides a concurrency safe map that is limited to a maximum
|
||||
// number of items with eviction for the oldest entry when the limit is
|
||||
// exceeded.
|
||||
type mruInventoryMap struct {
|
||||
invMtx sync.Mutex
|
||||
invMap map[wire.InvVect]*list.Element // nearly O(1) lookups
|
||||
invList *list.List // O(1) insert, update, delete
|
||||
limit uint
|
||||
}
|
||||
|
||||
// String returns the map as a human-readable string.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *mruInventoryMap) String() string {
|
||||
m.invMtx.Lock()
|
||||
defer m.invMtx.Unlock()
|
||||
|
||||
lastEntryNum := len(m.invMap) - 1
|
||||
curEntry := 0
|
||||
buf := bytes.NewBufferString("[")
|
||||
for iv := range m.invMap {
|
||||
buf.WriteString(fmt.Sprintf("%v", iv))
|
||||
if curEntry < lastEntryNum {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
curEntry++
|
||||
}
|
||||
buf.WriteString("]")
|
||||
|
||||
return fmt.Sprintf("<%d>%s", m.limit, buf.String())
|
||||
}
|
||||
|
||||
// Exists returns whether or not the passed inventory item is in the map.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *mruInventoryMap) Exists(iv *wire.InvVect) bool {
|
||||
m.invMtx.Lock()
|
||||
_, exists := m.invMap[*iv]
|
||||
m.invMtx.Unlock()
|
||||
|
||||
return exists
|
||||
}
|
||||
|
||||
// Add adds the passed inventory to the map and handles eviction of the oldest
|
||||
// item if adding the new item would exceed the max limit. Adding an existing
|
||||
// item makes it the most recently used item.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *mruInventoryMap) Add(iv *wire.InvVect) {
|
||||
m.invMtx.Lock()
|
||||
defer m.invMtx.Unlock()
|
||||
|
||||
// When the limit is zero, nothing can be added to the map, so just
|
||||
// return.
|
||||
if m.limit == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// When the entry already exists move it to the front of the list
|
||||
// thereby marking it most recently used.
|
||||
if node, exists := m.invMap[*iv]; exists {
|
||||
m.invList.MoveToFront(node)
|
||||
return
|
||||
}
|
||||
|
||||
// Evict the least recently used entry (back of the list) if the the new
|
||||
// entry would exceed the size limit for the map. Also reuse the list
|
||||
// node so a new one doesn't have to be allocated.
|
||||
if uint(len(m.invMap))+1 > m.limit {
|
||||
node := m.invList.Back()
|
||||
lru := node.Value.(*wire.InvVect)
|
||||
|
||||
// Evict least recently used item.
|
||||
delete(m.invMap, *lru)
|
||||
|
||||
// Reuse the list node of the item that was just evicted for the
|
||||
// new item.
|
||||
node.Value = iv
|
||||
m.invList.MoveToFront(node)
|
||||
m.invMap[*iv] = node
|
||||
return
|
||||
}
|
||||
|
||||
// The limit hasn't been reached yet, so just add the new item.
|
||||
node := m.invList.PushFront(iv)
|
||||
m.invMap[*iv] = node
|
||||
}
|
||||
|
||||
// Delete deletes the passed inventory item from the map (if it exists).
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *mruInventoryMap) Delete(iv *wire.InvVect) {
|
||||
m.invMtx.Lock()
|
||||
if node, exists := m.invMap[*iv]; exists {
|
||||
m.invList.Remove(node)
|
||||
delete(m.invMap, *iv)
|
||||
}
|
||||
m.invMtx.Unlock()
|
||||
}
|
||||
|
||||
// newMruInventoryMap returns a new inventory map that is limited to the number
|
||||
// of entries specified by limit. When the number of entries exceeds the limit,
|
||||
// the oldest (least recently used) entry will be removed to make room for the
|
||||
// new entry.
|
||||
func newMruInventoryMap(limit uint) *mruInventoryMap {
|
||||
m := mruInventoryMap{
|
||||
invMap: make(map[wire.InvVect]*list.Element),
|
||||
invList: list.New(),
|
||||
limit: limit,
|
||||
}
|
||||
return &m
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package peer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// mruNonceMap provides a concurrency safe map that is limited to a maximum
|
||||
// number of items with eviction for the oldest entry when the limit is
|
||||
// exceeded.
|
||||
type mruNonceMap struct {
|
||||
mtx sync.Mutex
|
||||
nonceMap map[uint64]*list.Element // nearly O(1) lookups
|
||||
nonceList *list.List // O(1) insert, update, delete
|
||||
limit uint
|
||||
}
|
||||
|
||||
// String returns the map as a human-readable string.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *mruNonceMap) String() string {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
lastEntryNum := len(m.nonceMap) - 1
|
||||
curEntry := 0
|
||||
buf := bytes.NewBufferString("[")
|
||||
for nonce := range m.nonceMap {
|
||||
buf.WriteString(fmt.Sprintf("%d", nonce))
|
||||
if curEntry < lastEntryNum {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
curEntry++
|
||||
}
|
||||
buf.WriteString("]")
|
||||
|
||||
return fmt.Sprintf("<%d>%s", m.limit, buf.String())
|
||||
}
|
||||
|
||||
// Exists returns whether or not the passed nonce is in the map.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *mruNonceMap) Exists(nonce uint64) bool {
|
||||
m.mtx.Lock()
|
||||
_, exists := m.nonceMap[nonce]
|
||||
m.mtx.Unlock()
|
||||
|
||||
return exists
|
||||
}
|
||||
|
||||
// Add adds the passed nonce to the map and handles eviction of the oldest item
|
||||
// if adding the new item would exceed the max limit. Adding an existing item
|
||||
// makes it the most recently used item.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *mruNonceMap) Add(nonce uint64) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
// When the limit is zero, nothing can be added to the map, so just
|
||||
// return.
|
||||
if m.limit == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// When the entry already exists move it to the front of the list
|
||||
// thereby marking it most recently used.
|
||||
if node, exists := m.nonceMap[nonce]; exists {
|
||||
m.nonceList.MoveToFront(node)
|
||||
return
|
||||
}
|
||||
|
||||
// Evict the least recently used entry (back of the list) if the the new
|
||||
// entry would exceed the size limit for the map. Also reuse the list
|
||||
// node so a new one doesn't have to be allocated.
|
||||
if uint(len(m.nonceMap))+1 > m.limit {
|
||||
node := m.nonceList.Back()
|
||||
lru := node.Value.(uint64)
|
||||
|
||||
// Evict least recently used item.
|
||||
delete(m.nonceMap, lru)
|
||||
|
||||
// Reuse the list node of the item that was just evicted for the
|
||||
// new item.
|
||||
node.Value = nonce
|
||||
m.nonceList.MoveToFront(node)
|
||||
m.nonceMap[nonce] = node
|
||||
return
|
||||
}
|
||||
|
||||
// The limit hasn't been reached yet, so just add the new item.
|
||||
node := m.nonceList.PushFront(nonce)
|
||||
m.nonceMap[nonce] = node
|
||||
}
|
||||
|
||||
// Delete deletes the passed nonce from the map (if it exists).
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *mruNonceMap) Delete(nonce uint64) {
|
||||
m.mtx.Lock()
|
||||
if node, exists := m.nonceMap[nonce]; exists {
|
||||
m.nonceList.Remove(node)
|
||||
delete(m.nonceMap, nonce)
|
||||
}
|
||||
m.mtx.Unlock()
|
||||
}
|
||||
|
||||
// newMruNonceMap returns a new nonce map that is limited to the number of
|
||||
// entries specified by limit. When the number of entries exceeds the limit,
|
||||
// the oldest (least recently used) entry will be removed to make room for the
|
||||
// new entry.
|
||||
func newMruNonceMap(limit uint) *mruNonceMap {
|
||||
m := mruNonceMap{
|
||||
nonceMap: make(map[uint64]*list.Element),
|
||||
nonceList: list.New(),
|
||||
limit: limit,
|
||||
}
|
||||
return &m
|
||||
}
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/go-socks/socks"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/decred/dcrd/lru"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -82,7 +83,7 @@ var (
|
|||
|
||||
// sentNonces houses the unique nonces that are generated when pushing
|
||||
// version messages that are used to detect self connections.
|
||||
sentNonces = newMruNonceMap(50)
|
||||
sentNonces = lru.NewCache(50)
|
||||
|
||||
// allowSelfConns is only used to allow the tests to bypass the self
|
||||
// connection detecting and disconnect logic since they intentionally
|
||||
|
@ -450,7 +451,7 @@ type Peer struct {
|
|||
|
||||
wireEncoding wire.MessageEncoding
|
||||
|
||||
knownInventory *mruInventoryMap
|
||||
knownInventory lru.Cache
|
||||
prevGetBlocksMtx sync.Mutex
|
||||
prevGetBlocksBegin *chainhash.Hash
|
||||
prevGetBlocksStop *chainhash.Hash
|
||||
|
@ -1626,7 +1627,7 @@ out:
|
|||
|
||||
// Don't send inventory that became known after
|
||||
// the initial check.
|
||||
if p.knownInventory.Exists(iv) {
|
||||
if p.knownInventory.Contains(iv) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -1832,7 +1833,7 @@ func (p *Peer) QueueMessageWithEncoding(msg wire.Message, doneChan chan<- struct
|
|||
func (p *Peer) QueueInventory(invVect *wire.InvVect) {
|
||||
// Don't add the inventory to the send queue if the peer is already
|
||||
// known to have it.
|
||||
if p.knownInventory.Exists(invVect) {
|
||||
if p.knownInventory.Contains(invVect) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1891,7 +1892,7 @@ func (p *Peer) readRemoteVersionMsg() error {
|
|||
}
|
||||
|
||||
// Detect self connections.
|
||||
if !allowSelfConns && sentNonces.Exists(msg.Nonce) {
|
||||
if !allowSelfConns && sentNonces.Contains(msg.Nonce) {
|
||||
return errors.New("disconnecting peer connected to self")
|
||||
}
|
||||
|
||||
|
@ -2097,7 +2098,7 @@ func (p *Peer) negotiateInboundProtocol() error {
|
|||
return p.readRemoteVerAckMsg()
|
||||
}
|
||||
|
||||
// negotiateOutoundProtocol performs the negotiation protocol for an outbound
|
||||
// negotiateOutboundProtocol performs the negotiation protocol for an outbound
|
||||
// peer. The events should occur in the following order, otherwise an error is
|
||||
// returned:
|
||||
//
|
||||
|
@ -2224,7 +2225,7 @@ func newPeerBase(origCfg *Config, inbound bool) *Peer {
|
|||
p := Peer{
|
||||
inbound: inbound,
|
||||
wireEncoding: wire.BaseEncoding,
|
||||
knownInventory: newMruInventoryMap(maxKnownInventory),
|
||||
knownInventory: lru.NewCache(maxKnownInventory),
|
||||
stallControl: make(chan stallControlMsg, 1), // nonblocking sync
|
||||
outputQueue: make(chan outMsg, outputBufferSize),
|
||||
sendQueue: make(chan outMsg, 1), // nonblocking sync
|
||||
|
|
|
@ -52,14 +52,61 @@ func (c *Client) GetBestBlockHash() (*chainhash.Hash, error) {
|
|||
return c.GetBestBlockHashAsync().Receive()
|
||||
}
|
||||
|
||||
// legacyGetBlockRequest constructs and sends a legacy getblock request which
|
||||
// contains two separate bools to denote verbosity, in contract to a single int
|
||||
// parameter.
|
||||
func (c *Client) legacyGetBlockRequest(hash string, verbose,
|
||||
verboseTx bool) ([]byte, error) {
|
||||
|
||||
hashJSON, err := json.Marshal(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
verboseJSON, err := json.Marshal(btcjson.Bool(verbose))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
verboseTxJSON, err := json.Marshal(btcjson.Bool(verboseTx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.RawRequest("getblock", []json.RawMessage{
|
||||
hashJSON, verboseJSON, verboseTxJSON,
|
||||
})
|
||||
}
|
||||
|
||||
// waitForGetBlockRes waits for the response of a getblock request. If the
|
||||
// response indicates an invalid parameter was provided, a legacy style of the
|
||||
// request is resent and its response is returned instead.
|
||||
func (c *Client) waitForGetBlockRes(respChan chan *response, hash string,
|
||||
verbose, verboseTx bool) ([]byte, error) {
|
||||
|
||||
res, err := receiveFuture(respChan)
|
||||
|
||||
// If we receive an invalid parameter error, then we may be
|
||||
// communicating with a btcd node which only understands the legacy
|
||||
// request, so we'll try that.
|
||||
if err, ok := err.(*btcjson.RPCError); ok &&
|
||||
err.Code == btcjson.ErrRPCInvalidParams.Code {
|
||||
return c.legacyGetBlockRequest(hash, verbose, verboseTx)
|
||||
}
|
||||
|
||||
// Otherwise, we can return the response as is.
|
||||
return res, err
|
||||
}
|
||||
|
||||
// FutureGetBlockResult is a future promise to deliver the result of a
|
||||
// GetBlockAsync RPC invocation (or an applicable error).
|
||||
type FutureGetBlockResult chan *response
|
||||
type FutureGetBlockResult struct {
|
||||
client *Client
|
||||
hash string
|
||||
Response chan *response
|
||||
}
|
||||
|
||||
// Receive waits for the response promised by the future and returns the raw
|
||||
// block requested from the server given its hash.
|
||||
func (r FutureGetBlockResult) Receive() (*wire.MsgBlock, error) {
|
||||
res, err := receiveFuture(r)
|
||||
res, err := r.client.waitForGetBlockRes(r.Response, r.hash, false, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -97,8 +144,12 @@ func (c *Client) GetBlockAsync(blockHash *chainhash.Hash) FutureGetBlockResult {
|
|||
hash = blockHash.String()
|
||||
}
|
||||
|
||||
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Bool(false), nil)
|
||||
return c.sendCmd(cmd)
|
||||
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Int(0))
|
||||
return FutureGetBlockResult{
|
||||
client: c,
|
||||
hash: hash,
|
||||
Response: c.sendCmd(cmd),
|
||||
}
|
||||
}
|
||||
|
||||
// GetBlock returns a raw block from the server given its hash.
|
||||
|
@ -111,12 +162,16 @@ func (c *Client) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) {
|
|||
|
||||
// FutureGetBlockVerboseResult is a future promise to deliver the result of a
|
||||
// GetBlockVerboseAsync RPC invocation (or an applicable error).
|
||||
type FutureGetBlockVerboseResult chan *response
|
||||
type FutureGetBlockVerboseResult struct {
|
||||
client *Client
|
||||
hash string
|
||||
Response chan *response
|
||||
}
|
||||
|
||||
// Receive waits for the response promised by the future and returns the data
|
||||
// structure from the server with information about the requested block.
|
||||
func (r FutureGetBlockVerboseResult) Receive() (*btcjson.GetBlockVerboseResult, error) {
|
||||
res, err := receiveFuture(r)
|
||||
res, err := r.client.waitForGetBlockRes(r.Response, r.hash, true, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -140,9 +195,14 @@ func (c *Client) GetBlockVerboseAsync(blockHash *chainhash.Hash) FutureGetBlockV
|
|||
if blockHash != nil {
|
||||
hash = blockHash.String()
|
||||
}
|
||||
|
||||
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Bool(true), nil)
|
||||
return c.sendCmd(cmd)
|
||||
// From the bitcoin-cli getblock documentation:
|
||||
// "If verbosity is 1, returns an Object with information about block ."
|
||||
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Int(1))
|
||||
return FutureGetBlockVerboseResult{
|
||||
client: c,
|
||||
hash: hash,
|
||||
Response: c.sendCmd(cmd),
|
||||
}
|
||||
}
|
||||
|
||||
// GetBlockVerbose returns a data structure from the server with information
|
||||
|
@ -154,19 +214,52 @@ func (c *Client) GetBlockVerbose(blockHash *chainhash.Hash) (*btcjson.GetBlockVe
|
|||
return c.GetBlockVerboseAsync(blockHash).Receive()
|
||||
}
|
||||
|
||||
// FutureGetBlockVerboseTxResult is a future promise to deliver the result of a
|
||||
// GetBlockVerboseTxResult RPC invocation (or an applicable error).
|
||||
type FutureGetBlockVerboseTxResult struct {
|
||||
client *Client
|
||||
hash string
|
||||
Response chan *response
|
||||
}
|
||||
|
||||
// Receive waits for the response promised by the future and returns a verbose
|
||||
// version of the block including detailed information about its transactions.
|
||||
func (r FutureGetBlockVerboseTxResult) Receive() (*btcjson.GetBlockVerboseTxResult, error) {
|
||||
res, err := r.client.waitForGetBlockRes(r.Response, r.hash, true, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var blockResult btcjson.GetBlockVerboseTxResult
|
||||
err = json.Unmarshal(res, &blockResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &blockResult, nil
|
||||
}
|
||||
|
||||
// GetBlockVerboseTxAsync returns an instance of a type that can be used to get
|
||||
// the result of the RPC at some future time by invoking the Receive function on
|
||||
// the returned instance.
|
||||
//
|
||||
// See GetBlockVerboseTx or the blocking version and more details.
|
||||
func (c *Client) GetBlockVerboseTxAsync(blockHash *chainhash.Hash) FutureGetBlockVerboseResult {
|
||||
func (c *Client) GetBlockVerboseTxAsync(blockHash *chainhash.Hash) FutureGetBlockVerboseTxResult {
|
||||
hash := ""
|
||||
if blockHash != nil {
|
||||
hash = blockHash.String()
|
||||
}
|
||||
|
||||
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Bool(true), btcjson.Bool(true))
|
||||
return c.sendCmd(cmd)
|
||||
// From the bitcoin-cli getblock documentation:
|
||||
//
|
||||
// If verbosity is 2, returns an Object with information about block
|
||||
// and information about each transaction.
|
||||
cmd := btcjson.NewGetBlockCmd(hash, btcjson.Int(2))
|
||||
return FutureGetBlockVerboseTxResult{
|
||||
client: c,
|
||||
hash: hash,
|
||||
Response: c.sendCmd(cmd),
|
||||
}
|
||||
}
|
||||
|
||||
// GetBlockVerboseTx returns a data structure from the server with information
|
||||
|
@ -174,7 +267,7 @@ func (c *Client) GetBlockVerboseTxAsync(blockHash *chainhash.Hash) FutureGetBloc
|
|||
//
|
||||
// See GetBlockVerbose if only transaction hashes are preferred.
|
||||
// See GetBlock to retrieve a raw block instead.
|
||||
func (c *Client) GetBlockVerboseTx(blockHash *chainhash.Hash) (*btcjson.GetBlockVerboseResult, error) {
|
||||
func (c *Client) GetBlockVerboseTx(blockHash *chainhash.Hash) (*btcjson.GetBlockVerboseTxResult, error) {
|
||||
return c.GetBlockVerboseTxAsync(blockHash).Receive()
|
||||
}
|
||||
|
||||
|
@ -214,6 +307,79 @@ func (c *Client) GetBlockCount() (int64, error) {
|
|||
return c.GetBlockCountAsync().Receive()
|
||||
}
|
||||
|
||||
// FutureGetChainTxStatsResult is a future promise to deliver the result of a
|
||||
// GetChainTxStatsAsync RPC invocation (or an applicable error).
|
||||
type FutureGetChainTxStatsResult chan *response
|
||||
|
||||
// Receive waits for the response promised by the future and returns transaction statistics
|
||||
func (r FutureGetChainTxStatsResult) Receive() (*btcjson.GetChainTxStatsResult, error) {
|
||||
res, err := receiveFuture(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var chainTxStats btcjson.GetChainTxStatsResult
|
||||
err = json.Unmarshal(res, &chainTxStats)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &chainTxStats, nil
|
||||
}
|
||||
|
||||
// GetChainTxStatsAsync returns an instance of a type that can be used to get
|
||||
// the result of the RPC at some future time by invoking the Receive function on
|
||||
// the returned instance.
|
||||
//
|
||||
// See GetChainTxStats for the blocking version and more details.
|
||||
func (c *Client) GetChainTxStatsAsync() FutureGetChainTxStatsResult {
|
||||
cmd := btcjson.NewGetChainTxStatsCmd(nil, nil)
|
||||
return c.sendCmd(cmd)
|
||||
}
|
||||
|
||||
// GetChainTxStatsNBlocksAsync returns an instance of a type that can be used to get
|
||||
// the result of the RPC at some future time by invoking the Receive function on
|
||||
// the returned instance.
|
||||
//
|
||||
// See GetChainTxStatsNBlocks for the blocking version and more details.
|
||||
func (c *Client) GetChainTxStatsNBlocksAsync(nBlocks int32) FutureGetChainTxStatsResult {
|
||||
cmd := btcjson.NewGetChainTxStatsCmd(&nBlocks, nil)
|
||||
return c.sendCmd(cmd)
|
||||
}
|
||||
|
||||
// GetChainTxStatsNBlocksBlockHashAsync returns an instance of a type that can be used to get
|
||||
// the result of the RPC at some future time by invoking the Receive function on
|
||||
// the returned instance.
|
||||
//
|
||||
// See GetChainTxStatsNBlocksBlockHash for the blocking version and more details.
|
||||
func (c *Client) GetChainTxStatsNBlocksBlockHashAsync(nBlocks int32, blockHash chainhash.Hash) FutureGetChainTxStatsResult {
|
||||
hash := blockHash.String()
|
||||
cmd := btcjson.NewGetChainTxStatsCmd(&nBlocks, &hash)
|
||||
return c.sendCmd(cmd)
|
||||
}
|
||||
|
||||
// GetChainTxStats returns statistics about the total number and rate of transactions in the chain.
|
||||
//
|
||||
// Size of the window is one month and it ends at chain tip.
|
||||
func (c *Client) GetChainTxStats() (*btcjson.GetChainTxStatsResult, error) {
|
||||
return c.GetChainTxStatsAsync().Receive()
|
||||
}
|
||||
|
||||
// GetChainTxStatsNBlocks returns statistics about the total number and rate of transactions in the chain.
|
||||
//
|
||||
// The argument specifies size of the window in number of blocks. The window ends at chain tip.
|
||||
func (c *Client) GetChainTxStatsNBlocks(nBlocks int32) (*btcjson.GetChainTxStatsResult, error) {
|
||||
return c.GetChainTxStatsNBlocksAsync(nBlocks).Receive()
|
||||
}
|
||||
|
||||
// GetChainTxStatsNBlocksBlockHash returns statistics about the total number and rate of transactions in the chain.
|
||||
//
|
||||
// First argument specifies size of the window in number of blocks.
|
||||
// Second argument is the hash of the block that ends the window.
|
||||
func (c *Client) GetChainTxStatsNBlocksBlockHash(nBlocks int32, blockHash chainhash.Hash) (*btcjson.GetChainTxStatsResult, error) {
|
||||
return c.GetChainTxStatsNBlocksBlockHashAsync(nBlocks, blockHash).Receive()
|
||||
}
|
||||
|
||||
// FutureGetDifficultyResult is a future promise to deliver the result of a
|
||||
// GetDifficultyAsync RPC invocation (or an applicable error).
|
||||
type FutureGetDifficultyResult chan *response
|
||||
|
@ -649,6 +815,41 @@ func (c *Client) EstimateFee(numBlocks int64) (float64, error) {
|
|||
return c.EstimateFeeAsync(numBlocks).Receive()
|
||||
}
|
||||
|
||||
// FutureEstimateFeeResult is a future promise to deliver the result of a
|
||||
// EstimateSmartFeeAsync RPC invocation (or an applicable error).
|
||||
type FutureEstimateSmartFeeResult chan *response
|
||||
|
||||
// Receive waits for the response promised by the future and returns the
|
||||
// estimated fee.
|
||||
func (r FutureEstimateSmartFeeResult) Receive() (*btcjson.EstimateSmartFeeResult, error) {
|
||||
res, err := receiveFuture(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var verified btcjson.EstimateSmartFeeResult
|
||||
err = json.Unmarshal(res, &verified)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &verified, nil
|
||||
}
|
||||
|
||||
// EstimateSmartFeeAsync returns an instance of a type that can be used to get the
|
||||
// result of the RPC at some future time by invoking the Receive function on the
|
||||
// returned instance.
|
||||
//
|
||||
// See EstimateSmartFee for the blocking version and more details.
|
||||
func (c *Client) EstimateSmartFeeAsync(confTarget int64, mode *btcjson.EstimateSmartFeeMode) FutureEstimateSmartFeeResult {
|
||||
cmd := btcjson.NewEstimateSmartFeeCmd(confTarget, mode)
|
||||
return c.sendCmd(cmd)
|
||||
}
|
||||
|
||||
// EstimateSmartFee requests the server to estimate a fee level based on the given parameters.
|
||||
func (c *Client) EstimateSmartFee(confTarget int64, mode *btcjson.EstimateSmartFeeMode) (*btcjson.EstimateSmartFeeResult, error) {
|
||||
return c.EstimateSmartFeeAsync(confTarget, mode).Receive()
|
||||
}
|
||||
|
||||
// FutureVerifyChainResult is a future promise to deliver the result of a
|
||||
// VerifyChainAsync, VerifyChainLevelAsyncRPC, or VerifyChainBlocksAsync
|
||||
// invocation (or an applicable error).
|
||||
|
@ -982,3 +1183,44 @@ func (c *Client) GetCFilterHeader(blockHash *chainhash.Hash,
|
|||
filterType wire.FilterType) (*wire.MsgCFHeaders, error) {
|
||||
return c.GetCFilterHeaderAsync(blockHash, filterType).Receive()
|
||||
}
|
||||
|
||||
// FutureGetBlockStatsResult is a future promise to deliver the result of a
|
||||
// GetBlockStatsAsync RPC invocation (or an applicable error).
|
||||
type FutureGetBlockStatsResult chan *response
|
||||
|
||||
// Receive waits for the response promised by the future and returns statistics
|
||||
// of a block at a certain height.
|
||||
func (r FutureGetBlockStatsResult) Receive() (*btcjson.GetBlockStatsResult, error) {
|
||||
res, err := receiveFuture(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var blockStats btcjson.GetBlockStatsResult
|
||||
err = json.Unmarshal(res, &blockStats)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &blockStats, nil
|
||||
}
|
||||
|
||||
// GetBlockStatsAsync returns an instance of a type that can be used to get
|
||||
// the result of the RPC at some future time by invoking the Receive function on
|
||||
// the returned instance.
|
||||
//
|
||||
// See GetBlockStats or the blocking version and more details.
|
||||
func (c *Client) GetBlockStatsAsync(hashOrHeight interface{}, stats *[]string) FutureGetBlockStatsResult {
|
||||
if hash, ok := hashOrHeight.(*chainhash.Hash); ok {
|
||||
hashOrHeight = hash.String()
|
||||
}
|
||||
|
||||
cmd := btcjson.NewGetBlockStatsCmd(btcjson.HashOrHeight{Value: hashOrHeight}, stats)
|
||||
return c.sendCmd(cmd)
|
||||
}
|
||||
|
||||
// GetBlockStats returns block statistics. First argument specifies height or hash of the target block.
|
||||
// Second argument allows to select certain stats to return.
|
||||
func (c *Client) GetBlockStats(hashOrHeight interface{}, stats *[]string) (*btcjson.GetBlockStatsResult, error) {
|
||||
return c.GetBlockStatsAsync(hashOrHeight, stats).Receive()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) 2017 The Namecoin developers
|
||||
// Copyright (c) 2019 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rpcclient
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func readCookieFile(path string) (username, password string, err error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
scanner.Scan()
|
||||
err = scanner.Err()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s := scanner.Text()
|
||||
|
||||
parts := strings.SplitN(s, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
err = fmt.Errorf("malformed cookie file")
|
||||
return
|
||||
}
|
||||
|
||||
username, password = parts[0], parts[1]
|
||||
return
|
||||
}
|
|
@ -106,7 +106,7 @@ Some of the commands are extensions specific to a particular RPC server. For
|
|||
example, the DebugLevel call is an extension only provided by btcd (and
|
||||
btcwallet passthrough). Therefore if you call one of these commands against
|
||||
an RPC server that doesn't provide them, you will get an unimplemented error
|
||||
from the server. An effort has been made to call out which commmands are
|
||||
from the server. An effort has been made to call out which commands are
|
||||
extensions in their documentation.
|
||||
|
||||
Also, it is important to realize that btcd intentionally separates the wallet
|
||||
|
|
|
@ -19,12 +19,14 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/go-socks/socks"
|
||||
"github.com/btcsuite/websocket"
|
||||
)
|
||||
|
@ -138,6 +140,10 @@ type Client struct {
|
|||
// config holds the connection configuration assoiated with this client.
|
||||
config *ConnConfig
|
||||
|
||||
// chainParams holds the params for the chain that this client is using,
|
||||
// and is used for many wallet methods.
|
||||
chainParams *chaincfg.Params
|
||||
|
||||
// wsConn is the underlying websocket connection when not in HTTP POST
|
||||
// mode.
|
||||
wsConn *websocket.Conn
|
||||
|
@ -283,31 +289,29 @@ func (c *Client) trackRegisteredNtfns(cmd interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
type (
|
||||
// inMessage is the first type that an incoming message is unmarshaled
|
||||
// into. It supports both requests (for notification support) and
|
||||
// responses. The partially-unmarshaled message is a notification if
|
||||
// the embedded ID (from the response) is nil. Otherwise, it is a
|
||||
// response.
|
||||
inMessage struct {
|
||||
ID *float64 `json:"id"`
|
||||
*rawNotification
|
||||
*rawResponse
|
||||
}
|
||||
// inMessage is the first type that an incoming message is unmarshaled
|
||||
// into. It supports both requests (for notification support) and
|
||||
// responses. The partially-unmarshaled message is a notification if
|
||||
// the embedded ID (from the response) is nil. Otherwise, it is a
|
||||
// response.
|
||||
type inMessage struct {
|
||||
ID *float64 `json:"id"`
|
||||
*rawNotification
|
||||
*rawResponse
|
||||
}
|
||||
|
||||
// rawNotification is a partially-unmarshaled JSON-RPC notification.
|
||||
rawNotification struct {
|
||||
Method string `json:"method"`
|
||||
Params []json.RawMessage `json:"params"`
|
||||
}
|
||||
// rawNotification is a partially-unmarshaled JSON-RPC notification.
|
||||
type rawNotification struct {
|
||||
Method string `json:"method"`
|
||||
Params []json.RawMessage `json:"params"`
|
||||
}
|
||||
|
||||
// rawResponse is a partially-unmarshaled JSON-RPC response. For this
|
||||
// to be valid (according to JSON-RPC 1.0 spec), ID may not be nil.
|
||||
rawResponse struct {
|
||||
Result json.RawMessage `json:"result"`
|
||||
Error *btcjson.RPCError `json:"error"`
|
||||
}
|
||||
)
|
||||
// rawResponse is a partially-unmarshaled JSON-RPC response. For this
|
||||
// to be valid (according to JSON-RPC 1.0 spec), ID may not be nil.
|
||||
type rawResponse struct {
|
||||
Result json.RawMessage `json:"result"`
|
||||
Error *btcjson.RPCError `json:"error"`
|
||||
}
|
||||
|
||||
// response is the raw bytes of a JSON-RPC result, or the error if the response
|
||||
// error object was non-null.
|
||||
|
@ -848,7 +852,12 @@ func (c *Client) sendPost(jReq *jsonRequest) {
|
|||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Configure basic access authorization.
|
||||
httpReq.SetBasicAuth(c.config.User, c.config.Pass)
|
||||
user, pass, err := c.config.getAuth()
|
||||
if err != nil {
|
||||
jReq.responseChan <- &response{result: nil, err: err}
|
||||
return
|
||||
}
|
||||
httpReq.SetBasicAuth(user, pass)
|
||||
|
||||
log.Tracef("Sending command [%s] with id %d", jReq.method, jReq.id)
|
||||
c.sendPostRequest(httpReq, jReq)
|
||||
|
@ -1093,6 +1102,22 @@ type ConnConfig struct {
|
|||
// Pass is the passphrase to use to authenticate to the RPC server.
|
||||
Pass string
|
||||
|
||||
// CookiePath is the path to a cookie file containing the username and
|
||||
// passphrase to use to authenticate to the RPC server. It is used
|
||||
// instead of User and Pass if non-empty.
|
||||
CookiePath string
|
||||
|
||||
cookieLastCheckTime time.Time
|
||||
cookieLastModTime time.Time
|
||||
cookieLastUser string
|
||||
cookieLastPass string
|
||||
cookieLastErr error
|
||||
|
||||
// Params is the string representing the network that the server
|
||||
// is running. If there is no parameter set in the config, then
|
||||
// mainnet will be used by default.
|
||||
Params string
|
||||
|
||||
// DisableTLS specifies whether transport layer security should be
|
||||
// disabled. It is recommended to always use TLS if the RPC server
|
||||
// supports it as otherwise your username and password is sent across
|
||||
|
@ -1141,6 +1166,43 @@ type ConnConfig struct {
|
|||
EnableBCInfoHacks bool
|
||||
}
|
||||
|
||||
// getAuth returns the username and passphrase that will actually be used for
|
||||
// this connection. This will be the result of checking the cookie if a cookie
|
||||
// path is configured; if not, it will be the user-configured username and
|
||||
// passphrase.
|
||||
func (config *ConnConfig) getAuth() (username, passphrase string, err error) {
|
||||
// Try username+passphrase auth first.
|
||||
if config.Pass != "" {
|
||||
return config.User, config.Pass, nil
|
||||
}
|
||||
|
||||
// If no username or passphrase is set, try cookie auth.
|
||||
return config.retrieveCookie()
|
||||
}
|
||||
|
||||
// retrieveCookie returns the cookie username and passphrase.
|
||||
func (config *ConnConfig) retrieveCookie() (username, passphrase string, err error) {
|
||||
if !config.cookieLastCheckTime.IsZero() && time.Now().Before(config.cookieLastCheckTime.Add(30*time.Second)) {
|
||||
return config.cookieLastUser, config.cookieLastPass, config.cookieLastErr
|
||||
}
|
||||
|
||||
config.cookieLastCheckTime = time.Now()
|
||||
|
||||
st, err := os.Stat(config.CookiePath)
|
||||
if err != nil {
|
||||
config.cookieLastErr = err
|
||||
return config.cookieLastUser, config.cookieLastPass, config.cookieLastErr
|
||||
}
|
||||
|
||||
modTime := st.ModTime()
|
||||
if !modTime.Equal(config.cookieLastModTime) {
|
||||
config.cookieLastModTime = modTime
|
||||
config.cookieLastUser, config.cookieLastPass, config.cookieLastErr = readCookieFile(config.CookiePath)
|
||||
}
|
||||
|
||||
return config.cookieLastUser, config.cookieLastPass, config.cookieLastErr
|
||||
}
|
||||
|
||||
// newHTTPClient returns a new http client that is configured according to the
|
||||
// proxy and TLS settings in the associated connection configuration.
|
||||
func newHTTPClient(config *ConnConfig) (*http.Client, error) {
|
||||
|
@ -1210,7 +1272,11 @@ func dial(config *ConnConfig) (*websocket.Conn, error) {
|
|||
|
||||
// The RPC server requires basic authorization, so create a custom
|
||||
// request header with the Authorization header set.
|
||||
login := config.User + ":" + config.Pass
|
||||
user, pass, err := config.getAuth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
login := user + ":" + pass
|
||||
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login))
|
||||
requestHeader := make(http.Header)
|
||||
requestHeader.Add("Authorization", auth)
|
||||
|
@ -1290,6 +1356,23 @@ func New(config *ConnConfig, ntfnHandlers *NotificationHandlers) (*Client, error
|
|||
shutdown: make(chan struct{}),
|
||||
}
|
||||
|
||||
// Default network is mainnet, no parameters are necessary but if mainnet
|
||||
// is specified it will be the param
|
||||
switch config.Params {
|
||||
case "":
|
||||
fallthrough
|
||||
case chaincfg.MainNetParams.Name:
|
||||
client.chainParams = &chaincfg.MainNetParams
|
||||
case chaincfg.TestNet3Params.Name:
|
||||
client.chainParams = &chaincfg.TestNet3Params
|
||||
case chaincfg.RegressionNetParams.Name:
|
||||
client.chainParams = &chaincfg.RegressionNetParams
|
||||
case chaincfg.SimNetParams.Name:
|
||||
client.chainParams = &chaincfg.SimNetParams
|
||||
default:
|
||||
return nil, fmt.Errorf("rpcclient.New: Unknown chain %s", config.Params)
|
||||
}
|
||||
|
||||
if start {
|
||||
log.Infof("Established connection to RPC server %s",
|
||||
config.Host)
|
||||
|
|
|
@ -61,6 +61,53 @@ func (c *Client) Generate(numBlocks uint32) ([]*chainhash.Hash, error) {
|
|||
return c.GenerateAsync(numBlocks).Receive()
|
||||
}
|
||||
|
||||
// FutureGenerateToAddressResult is a future promise to deliver the result of a
|
||||
// GenerateToAddressResult RPC invocation (or an applicable error).
|
||||
type FutureGenerateToAddressResult chan *response
|
||||
|
||||
// Receive waits for the response promised by the future and returns the hashes of
|
||||
// of the generated blocks.
|
||||
func (f FutureGenerateToAddressResult) Receive() ([]*chainhash.Hash, error) {
|
||||
res, err := receiveFuture(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Unmarshal result as a list of strings.
|
||||
var result []string
|
||||
err = json.Unmarshal(res, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert each block hash to a chainhash.Hash and store a pointer to
|
||||
// each.
|
||||
convertedResult := make([]*chainhash.Hash, len(result))
|
||||
for i, hashString := range result {
|
||||
convertedResult[i], err = chainhash.NewHashFromStr(hashString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return convertedResult, nil
|
||||
}
|
||||
|
||||
// GenerateToAddressAsync returns an instance of a type that can be used to get
|
||||
// the result of the RPC at some future time by invoking the Receive function on
|
||||
// the returned instance.
|
||||
//
|
||||
// See GenerateToAddress for the blocking version and more details.
|
||||
func (c *Client) GenerateToAddressAsync(numBlocks int64, address btcutil.Address, maxTries *int64) FutureGenerateToAddressResult {
|
||||
cmd := btcjson.NewGenerateToAddressCmd(numBlocks, address.EncodeAddress(), maxTries)
|
||||
return c.sendCmd(cmd)
|
||||
}
|
||||
|
||||
// GenerateToAddress generates numBlocks blocks to the given address and returns their hashes.
|
||||
func (c *Client) GenerateToAddress(numBlocks int64, address btcutil.Address, maxTries *int64) ([]*chainhash.Hash, error) {
|
||||
return c.GenerateToAddressAsync(numBlocks, address, maxTries).Receive()
|
||||
}
|
||||
|
||||
// FutureGetGenerateResult is a future promise to deliver the result of a
|
||||
// GetGenerateAsync RPC invocation (or an applicable error).
|
||||
type FutureGetGenerateResult chan *response
|
||||
|
|
|
@ -95,7 +95,7 @@ type NotificationHandlers struct {
|
|||
// NotifyBlocks has been made to register for the notification and the
|
||||
// function is non-nil.
|
||||
//
|
||||
// NOTE: Deprecated. Use OnFilteredBlockConnected instead.
|
||||
// Deprecated: Use OnFilteredBlockConnected instead.
|
||||
OnBlockConnected func(hash *chainhash.Hash, height int32, t time.Time)
|
||||
|
||||
// OnFilteredBlockConnected is invoked when a block is connected to the
|
||||
|
@ -111,7 +111,7 @@ type NotificationHandlers struct {
|
|||
// NotifyBlocks has been made to register for the notification and the
|
||||
// function is non-nil.
|
||||
//
|
||||
// NOTE: Deprecated. Use OnFilteredBlockDisconnected instead.
|
||||
// Deprecated: Use OnFilteredBlockDisconnected instead.
|
||||
OnBlockDisconnected func(hash *chainhash.Hash, height int32, t time.Time)
|
||||
|
||||
// OnFilteredBlockDisconnected is invoked when a block is disconnected
|
||||
|
@ -127,7 +127,7 @@ type NotificationHandlers struct {
|
|||
// preceding call to NotifyReceived, Rescan, or RescanEndHeight has been
|
||||
// made to register for the notification and the function is non-nil.
|
||||
//
|
||||
// NOTE: Deprecated. Use OnRelevantTxAccepted instead.
|
||||
// Deprecated: Use OnRelevantTxAccepted instead.
|
||||
OnRecvTx func(transaction *btcutil.Tx, details *btcjson.BlockDetails)
|
||||
|
||||
// OnRedeemingTx is invoked when a transaction that spends a registered
|
||||
|
@ -141,7 +141,7 @@ type NotificationHandlers struct {
|
|||
// funds to the registered addresses. This means it is possible for
|
||||
// this to invoked indirectly as the result of a NotifyReceived call.
|
||||
//
|
||||
// NOTE: Deprecated. Use OnRelevantTxAccepted instead.
|
||||
// Deprecated: Use OnRelevantTxAccepted instead.
|
||||
OnRedeemingTx func(transaction *btcutil.Tx, details *btcjson.BlockDetails)
|
||||
|
||||
// OnRelevantTxAccepted is invoked when an unmined transaction passes
|
||||
|
@ -157,14 +157,14 @@ type NotificationHandlers struct {
|
|||
// result of a rescan request, due to how btcd may send various rescan
|
||||
// notifications after the rescan request has already returned.
|
||||
//
|
||||
// NOTE: Deprecated. Not used with RescanBlocks.
|
||||
// Deprecated: Not used with RescanBlocks.
|
||||
OnRescanFinished func(hash *chainhash.Hash, height int32, blkTime time.Time)
|
||||
|
||||
// OnRescanProgress is invoked periodically when a rescan is underway.
|
||||
// It will only be invoked if a preceding call to Rescan or
|
||||
// RescanEndHeight has been made and the function is non-nil.
|
||||
//
|
||||
// NOTE: Deprecated. Not used with RescanBlocks.
|
||||
// Deprecated: Not used with RescanBlocks.
|
||||
OnRescanProgress func(hash *chainhash.Hash, height int32, blkTime time.Time)
|
||||
|
||||
// OnTxAccepted is invoked when a transaction is accepted into the
|
||||
|
@ -905,7 +905,7 @@ func (c *Client) NotifyBlocks() error {
|
|||
// FutureNotifySpentResult is a future promise to deliver the result of a
|
||||
// NotifySpentAsync RPC invocation (or an applicable error).
|
||||
//
|
||||
// NOTE: Deprecated. Use FutureLoadTxFilterResult instead.
|
||||
// Deprecated: Use FutureLoadTxFilterResult instead.
|
||||
type FutureNotifySpentResult chan *response
|
||||
|
||||
// Receive waits for the response promised by the future and returns an error
|
||||
|
@ -951,7 +951,7 @@ func newOutPointFromWire(op *wire.OutPoint) btcjson.OutPoint {
|
|||
//
|
||||
// NOTE: This is a btcd extension and requires a websocket connection.
|
||||
//
|
||||
// NOTE: Deprecated. Use LoadTxFilterAsync instead.
|
||||
// Deprecated: Use LoadTxFilterAsync instead.
|
||||
func (c *Client) NotifySpentAsync(outpoints []*wire.OutPoint) FutureNotifySpentResult {
|
||||
// Not supported in HTTP POST mode.
|
||||
if c.config.HTTPPostMode {
|
||||
|
@ -983,7 +983,7 @@ func (c *Client) NotifySpentAsync(outpoints []*wire.OutPoint) FutureNotifySpentR
|
|||
//
|
||||
// NOTE: This is a btcd extension and requires a websocket connection.
|
||||
//
|
||||
// NOTE: Deprecated. Use LoadTxFilter instead.
|
||||
// Deprecated: Use LoadTxFilter instead.
|
||||
func (c *Client) NotifySpent(outpoints []*wire.OutPoint) error {
|
||||
return c.NotifySpentAsync(outpoints).Receive()
|
||||
}
|
||||
|
@ -1040,7 +1040,7 @@ func (c *Client) NotifyNewTransactions(verbose bool) error {
|
|||
// FutureNotifyReceivedResult is a future promise to deliver the result of a
|
||||
// NotifyReceivedAsync RPC invocation (or an applicable error).
|
||||
//
|
||||
// NOTE: Deprecated. Use FutureLoadTxFilterResult instead.
|
||||
// Deprecated: Use FutureLoadTxFilterResult instead.
|
||||
type FutureNotifyReceivedResult chan *response
|
||||
|
||||
// Receive waits for the response promised by the future and returns an error
|
||||
|
@ -1078,7 +1078,7 @@ func (c *Client) notifyReceivedInternal(addresses []string) FutureNotifyReceived
|
|||
//
|
||||
// NOTE: This is a btcd extension and requires a websocket connection.
|
||||
//
|
||||
// NOTE: Deprecated. Use LoadTxFilterAsync instead.
|
||||
// Deprecated: Use LoadTxFilterAsync instead.
|
||||
func (c *Client) NotifyReceivedAsync(addresses []btcutil.Address) FutureNotifyReceivedResult {
|
||||
// Not supported in HTTP POST mode.
|
||||
if c.config.HTTPPostMode {
|
||||
|
@ -1118,7 +1118,7 @@ func (c *Client) NotifyReceivedAsync(addresses []btcutil.Address) FutureNotifyRe
|
|||
//
|
||||
// NOTE: This is a btcd extension and requires a websocket connection.
|
||||
//
|
||||
// NOTE: Deprecated. Use LoadTxFilter instead.
|
||||
// Deprecated: Use LoadTxFilter instead.
|
||||
func (c *Client) NotifyReceived(addresses []btcutil.Address) error {
|
||||
return c.NotifyReceivedAsync(addresses).Receive()
|
||||
}
|
||||
|
@ -1126,7 +1126,7 @@ func (c *Client) NotifyReceived(addresses []btcutil.Address) error {
|
|||
// FutureRescanResult is a future promise to deliver the result of a RescanAsync
|
||||
// or RescanEndHeightAsync RPC invocation (or an applicable error).
|
||||
//
|
||||
// NOTE: Deprecated. Use FutureRescanBlocksResult instead.
|
||||
// Deprecated: Use FutureRescanBlocksResult instead.
|
||||
type FutureRescanResult chan *response
|
||||
|
||||
// Receive waits for the response promised by the future and returns an error
|
||||
|
@ -1150,7 +1150,7 @@ func (r FutureRescanResult) Receive() error {
|
|||
//
|
||||
// NOTE: This is a btcd extension and requires a websocket connection.
|
||||
//
|
||||
// NOTE: Deprecated. Use RescanBlocksAsync instead.
|
||||
// Deprecated: Use RescanBlocksAsync instead.
|
||||
func (c *Client) RescanAsync(startBlock *chainhash.Hash,
|
||||
addresses []btcutil.Address,
|
||||
outpoints []*wire.OutPoint) FutureRescanResult {
|
||||
|
@ -1215,7 +1215,7 @@ func (c *Client) RescanAsync(startBlock *chainhash.Hash,
|
|||
//
|
||||
// NOTE: This is a btcd extension and requires a websocket connection.
|
||||
//
|
||||
// NOTE: Deprecated. Use RescanBlocks instead.
|
||||
// Deprecated: Use RescanBlocks instead.
|
||||
func (c *Client) Rescan(startBlock *chainhash.Hash,
|
||||
addresses []btcutil.Address,
|
||||
outpoints []*wire.OutPoint) error {
|
||||
|
@ -1231,7 +1231,7 @@ func (c *Client) Rescan(startBlock *chainhash.Hash,
|
|||
//
|
||||
// NOTE: This is a btcd extension and requires a websocket connection.
|
||||
//
|
||||
// NOTE: Deprecated. Use RescanBlocksAsync instead.
|
||||
// Deprecated: Use RescanBlocksAsync instead.
|
||||
func (c *Client) RescanEndBlockAsync(startBlock *chainhash.Hash,
|
||||
addresses []btcutil.Address, outpoints []*wire.OutPoint,
|
||||
endBlock *chainhash.Hash) FutureRescanResult {
|
||||
|
@ -1293,7 +1293,7 @@ func (c *Client) RescanEndBlockAsync(startBlock *chainhash.Hash,
|
|||
//
|
||||
// NOTE: This is a btcd extension and requires a websocket connection.
|
||||
//
|
||||
// NOTE: Deprecated. Use RescanBlocks instead.
|
||||
// Deprecated: Use RescanBlocks instead.
|
||||
func (c *Client) RescanEndHeight(startBlock *chainhash.Hash,
|
||||
addresses []btcutil.Address, outpoints []*wire.OutPoint,
|
||||
endBlock *chainhash.Hash) error {
|
||||
|
|
|
@ -205,6 +205,47 @@ func (c *Client) DecodeRawTransaction(serializedTx []byte) (*btcjson.TxRawResult
|
|||
return c.DecodeRawTransactionAsync(serializedTx).Receive()
|
||||
}
|
||||
|
||||
// FutureFundRawTransactionResult is a future promise to deliver the result
|
||||
// of a FutureFundRawTransactionAsync RPC invocation (or an applicable error).
|
||||
type FutureFundRawTransactionResult chan *response
|
||||
|
||||
// Receive waits for the response promised by the future and returns information
|
||||
// about a funding attempt
|
||||
func (r FutureFundRawTransactionResult) Receive() (*btcjson.FundRawTransactionResult, error) {
|
||||
res, err := receiveFuture(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var marshalled btcjson.FundRawTransactionResult
|
||||
if err := json.Unmarshal(res, &marshalled); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &marshalled, nil
|
||||
}
|
||||
|
||||
// FundRawTransactionAsync returns an instance of a type that can be used to
|
||||
// get the result of the RPC at some future time by invoking the Receive
|
||||
// function on the returned instance.
|
||||
//
|
||||
// See FundRawTransaction for the blocking version and more details.
|
||||
func (c *Client) FundRawTransactionAsync(tx *wire.MsgTx, opts btcjson.FundRawTransactionOpts, isWitness *bool) FutureFundRawTransactionResult {
|
||||
var txBuf bytes.Buffer
|
||||
if err := tx.Serialize(&txBuf); err != nil {
|
||||
return newFutureError(err)
|
||||
}
|
||||
|
||||
cmd := btcjson.NewFundRawTransactionCmd(txBuf.Bytes(), opts, isWitness)
|
||||
return c.sendCmd(cmd)
|
||||
}
|
||||
|
||||
// FundRawTransaction returns the result of trying to fund the given transaction with
|
||||
// funds from the node wallet
|
||||
func (c *Client) FundRawTransaction(tx *wire.MsgTx, opts btcjson.FundRawTransactionOpts, isWitness *bool) (*btcjson.FundRawTransactionResult, error) {
|
||||
return c.FundRawTransactionAsync(tx, opts, isWitness).Receive()
|
||||
}
|
||||
|
||||
// FutureCreateRawTransactionResult is a future promise to deliver the result
|
||||
// of a CreateRawTransactionAsync RPC invocation (or an applicable error).
|
||||
type FutureCreateRawTransactionResult chan *response
|
||||
|
@ -233,8 +274,13 @@ func (r FutureCreateRawTransactionResult) Receive() (*wire.MsgTx, error) {
|
|||
|
||||
// Deserialize the transaction and return it.
|
||||
var msgTx wire.MsgTx
|
||||
if err := msgTx.Deserialize(bytes.NewReader(serializedTx)); err != nil {
|
||||
return nil, err
|
||||
// we try both the new and old encoding format
|
||||
witnessErr := msgTx.Deserialize(bytes.NewReader(serializedTx))
|
||||
if witnessErr != nil {
|
||||
legacyErr := msgTx.DeserializeNoWitness(bytes.NewReader(serializedTx))
|
||||
if legacyErr != nil {
|
||||
return nil, legacyErr
|
||||
}
|
||||
}
|
||||
return &msgTx, nil
|
||||
}
|
||||
|
@ -256,7 +302,8 @@ func (c *Client) CreateRawTransactionAsync(inputs []btcjson.TransactionInput,
|
|||
}
|
||||
|
||||
// CreateRawTransaction returns a new transaction spending the provided inputs
|
||||
// and sending to the provided addresses.
|
||||
// and sending to the provided addresses. If the inputs are either nil or an
|
||||
// empty slice, it is interpreted as an empty slice.
|
||||
func (c *Client) CreateRawTransaction(inputs []btcjson.TransactionInput,
|
||||
amounts map[btcutil.Address]btcutil.Amount, lockTime *int64) (*wire.MsgTx, error) {
|
||||
|
||||
|
|
|
@ -20,7 +20,8 @@ import (
|
|||
// *****************************
|
||||
|
||||
// FutureGetTransactionResult is a future promise to deliver the result
|
||||
// of a GetTransactionAsync RPC invocation (or an applicable error).
|
||||
// of a GetTransactionAsync or GetTransactionWatchOnlyAsync RPC invocation
|
||||
// (or an applicable error).
|
||||
type FutureGetTransactionResult chan *response
|
||||
|
||||
// Receive waits for the response promised by the future and returns detailed
|
||||
|
@ -63,6 +64,28 @@ func (c *Client) GetTransaction(txHash *chainhash.Hash) (*btcjson.GetTransaction
|
|||
return c.GetTransactionAsync(txHash).Receive()
|
||||
}
|
||||
|
||||
// GetTransactionWatchOnlyAsync returns an instance of a type that can be used
|
||||
// to get the result of the RPC at some future time by invoking the Receive function on
|
||||
// the returned instance.
|
||||
//
|
||||
// See GetTransactionWatchOnly for the blocking version and more details.
|
||||
func (c *Client) GetTransactionWatchOnlyAsync(txHash *chainhash.Hash, watchOnly bool) FutureGetTransactionResult {
|
||||
hash := ""
|
||||
if txHash != nil {
|
||||
hash = txHash.String()
|
||||
}
|
||||
|
||||
cmd := btcjson.NewGetTransactionCmd(hash, &watchOnly)
|
||||
return c.sendCmd(cmd)
|
||||
}
|
||||
|
||||
// GetTransactionWatchOnly returns detailed information about a wallet
|
||||
// transaction, and allow including watch-only addresses in balance
|
||||
// calculation and details.
|
||||
func (c *Client) GetTransactionWatchOnly(txHash *chainhash.Hash, watchOnly bool) (*btcjson.GetTransactionResult, error) {
|
||||
return c.GetTransactionWatchOnlyAsync(txHash, watchOnly).Receive()
|
||||
}
|
||||
|
||||
// FutureListTransactionsResult is a future promise to deliver the result of a
|
||||
// ListTransactionsAsync, ListTransactionsCountAsync, or
|
||||
// ListTransactionsCountFromAsync RPC invocation (or an applicable error).
|
||||
|
@ -753,13 +776,16 @@ func (c *Client) SendManyComment(fromAccount string,
|
|||
|
||||
// FutureAddMultisigAddressResult is a future promise to deliver the result of a
|
||||
// AddMultisigAddressAsync RPC invocation (or an applicable error).
|
||||
type FutureAddMultisigAddressResult chan *response
|
||||
type FutureAddMultisigAddressResult struct {
|
||||
responseChannel chan *response
|
||||
network *chaincfg.Params
|
||||
}
|
||||
|
||||
// Receive waits for the response promised by the future and returns the
|
||||
// multisignature address that requires the specified number of signatures for
|
||||
// the provided addresses.
|
||||
func (r FutureAddMultisigAddressResult) Receive() (btcutil.Address, error) {
|
||||
res, err := receiveFuture(r)
|
||||
res, err := receiveFuture(r.responseChannel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -771,7 +797,7 @@ func (r FutureAddMultisigAddressResult) Receive() (btcutil.Address, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return btcutil.DecodeAddress(addr, &chaincfg.MainNetParams)
|
||||
return btcutil.DecodeAddress(addr, r.network)
|
||||
}
|
||||
|
||||
// AddMultisigAddressAsync returns an instance of a type that can be used to get
|
||||
|
@ -786,14 +812,17 @@ func (c *Client) AddMultisigAddressAsync(requiredSigs int, addresses []btcutil.A
|
|||
}
|
||||
|
||||
cmd := btcjson.NewAddMultisigAddressCmd(requiredSigs, addrs, &account)
|
||||
return c.sendCmd(cmd)
|
||||
result := FutureAddMultisigAddressResult{
|
||||
network: c.chainParams,
|
||||
responseChannel: c.sendCmd(cmd),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// AddMultisigAddress adds a multisignature address that requires the specified
|
||||
// number of signatures for the provided addresses to the wallet.
|
||||
func (c *Client) AddMultisigAddress(requiredSigs int, addresses []btcutil.Address, account string) (btcutil.Address, error) {
|
||||
return c.AddMultisigAddressAsync(requiredSigs, addresses,
|
||||
account).Receive()
|
||||
return c.AddMultisigAddressAsync(requiredSigs, addresses, account).Receive()
|
||||
}
|
||||
|
||||
// FutureCreateMultisigResult is a future promise to deliver the result of a
|
||||
|
@ -868,12 +897,15 @@ func (c *Client) CreateNewAccount(account string) error {
|
|||
|
||||
// FutureGetNewAddressResult is a future promise to deliver the result of a
|
||||
// GetNewAddressAsync RPC invocation (or an applicable error).
|
||||
type FutureGetNewAddressResult chan *response
|
||||
type FutureGetNewAddressResult struct {
|
||||
responseChannel chan *response
|
||||
network *chaincfg.Params
|
||||
}
|
||||
|
||||
// Receive waits for the response promised by the future and returns a new
|
||||
// address.
|
||||
func (r FutureGetNewAddressResult) Receive() (btcutil.Address, error) {
|
||||
res, err := receiveFuture(r)
|
||||
res, err := receiveFuture(r.responseChannel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -885,7 +917,7 @@ func (r FutureGetNewAddressResult) Receive() (btcutil.Address, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return btcutil.DecodeAddress(addr, &chaincfg.MainNetParams)
|
||||
return btcutil.DecodeAddress(addr, r.network)
|
||||
}
|
||||
|
||||
// GetNewAddressAsync returns an instance of a type that can be used to get the
|
||||
|
@ -895,23 +927,31 @@ func (r FutureGetNewAddressResult) Receive() (btcutil.Address, error) {
|
|||
// See GetNewAddress for the blocking version and more details.
|
||||
func (c *Client) GetNewAddressAsync(account string) FutureGetNewAddressResult {
|
||||
cmd := btcjson.NewGetNewAddressCmd(&account)
|
||||
return c.sendCmd(cmd)
|
||||
result := FutureGetNewAddressResult{
|
||||
network: c.chainParams,
|
||||
responseChannel: c.sendCmd(cmd),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetNewAddress returns a new address.
|
||||
// GetNewAddress returns a new address, and decodes based on the client's
|
||||
// chain params.
|
||||
func (c *Client) GetNewAddress(account string) (btcutil.Address, error) {
|
||||
return c.GetNewAddressAsync(account).Receive()
|
||||
}
|
||||
|
||||
// FutureGetRawChangeAddressResult is a future promise to deliver the result of
|
||||
// a GetRawChangeAddressAsync RPC invocation (or an applicable error).
|
||||
type FutureGetRawChangeAddressResult chan *response
|
||||
type FutureGetRawChangeAddressResult struct {
|
||||
responseChannel chan *response
|
||||
network *chaincfg.Params
|
||||
}
|
||||
|
||||
// Receive waits for the response promised by the future and returns a new
|
||||
// address for receiving change that will be associated with the provided
|
||||
// account. Note that this is only for raw transactions and NOT for normal use.
|
||||
func (r FutureGetRawChangeAddressResult) Receive() (btcutil.Address, error) {
|
||||
res, err := receiveFuture(r)
|
||||
res, err := receiveFuture(r.responseChannel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -923,7 +963,7 @@ func (r FutureGetRawChangeAddressResult) Receive() (btcutil.Address, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return btcutil.DecodeAddress(addr, &chaincfg.MainNetParams)
|
||||
return btcutil.DecodeAddress(addr, r.network)
|
||||
}
|
||||
|
||||
// GetRawChangeAddressAsync returns an instance of a type that can be used to
|
||||
|
@ -933,7 +973,11 @@ func (r FutureGetRawChangeAddressResult) Receive() (btcutil.Address, error) {
|
|||
// See GetRawChangeAddress for the blocking version and more details.
|
||||
func (c *Client) GetRawChangeAddressAsync(account string) FutureGetRawChangeAddressResult {
|
||||
cmd := btcjson.NewGetRawChangeAddressCmd(&account)
|
||||
return c.sendCmd(cmd)
|
||||
result := FutureGetRawChangeAddressResult{
|
||||
network: c.chainParams,
|
||||
responseChannel: c.sendCmd(cmd),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetRawChangeAddress returns a new address for receiving change that will be
|
||||
|
@ -945,12 +989,15 @@ func (c *Client) GetRawChangeAddress(account string) (btcutil.Address, error) {
|
|||
|
||||
// FutureAddWitnessAddressResult is a future promise to deliver the result of
|
||||
// a AddWitnessAddressAsync RPC invocation (or an applicable error).
|
||||
type FutureAddWitnessAddressResult chan *response
|
||||
type FutureAddWitnessAddressResult struct {
|
||||
responseChannel chan *response
|
||||
network *chaincfg.Params
|
||||
}
|
||||
|
||||
// Receive waits for the response promised by the future and returns the new
|
||||
// address.
|
||||
func (r FutureAddWitnessAddressResult) Receive() (btcutil.Address, error) {
|
||||
res, err := receiveFuture(r)
|
||||
res, err := receiveFuture(r.responseChannel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -962,7 +1009,7 @@ func (r FutureAddWitnessAddressResult) Receive() (btcutil.Address, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return btcutil.DecodeAddress(addr, &chaincfg.MainNetParams)
|
||||
return btcutil.DecodeAddress(addr, r.network)
|
||||
}
|
||||
|
||||
// AddWitnessAddressAsync returns an instance of a type that can be used to get
|
||||
|
@ -972,7 +1019,11 @@ func (r FutureAddWitnessAddressResult) Receive() (btcutil.Address, error) {
|
|||
// See AddWitnessAddress for the blocking version and more details.
|
||||
func (c *Client) AddWitnessAddressAsync(address string) FutureAddWitnessAddressResult {
|
||||
cmd := btcjson.NewAddWitnessAddressCmd(address)
|
||||
return c.sendCmd(cmd)
|
||||
response := FutureAddWitnessAddressResult{
|
||||
network: c.chainParams,
|
||||
responseChannel: c.sendCmd(cmd),
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
// AddWitnessAddress adds a witness address for a script and returns the new
|
||||
|
@ -983,12 +1034,15 @@ func (c *Client) AddWitnessAddress(address string) (btcutil.Address, error) {
|
|||
|
||||
// FutureGetAccountAddressResult is a future promise to deliver the result of a
|
||||
// GetAccountAddressAsync RPC invocation (or an applicable error).
|
||||
type FutureGetAccountAddressResult chan *response
|
||||
type FutureGetAccountAddressResult struct {
|
||||
responseChannel chan *response
|
||||
network *chaincfg.Params
|
||||
}
|
||||
|
||||
// Receive waits for the response promised by the future and returns the current
|
||||
// Bitcoin address for receiving payments to the specified account.
|
||||
func (r FutureGetAccountAddressResult) Receive() (btcutil.Address, error) {
|
||||
res, err := receiveFuture(r)
|
||||
res, err := receiveFuture(r.responseChannel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1000,7 +1054,7 @@ func (r FutureGetAccountAddressResult) Receive() (btcutil.Address, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return btcutil.DecodeAddress(addr, &chaincfg.MainNetParams)
|
||||
return btcutil.DecodeAddress(addr, r.network)
|
||||
}
|
||||
|
||||
// GetAccountAddressAsync returns an instance of a type that can be used to get
|
||||
|
@ -1010,7 +1064,11 @@ func (r FutureGetAccountAddressResult) Receive() (btcutil.Address, error) {
|
|||
// See GetAccountAddress for the blocking version and more details.
|
||||
func (c *Client) GetAccountAddressAsync(account string) FutureGetAccountAddressResult {
|
||||
cmd := btcjson.NewGetAccountAddressCmd(account)
|
||||
return c.sendCmd(cmd)
|
||||
result := FutureGetAccountAddressResult{
|
||||
network: c.chainParams,
|
||||
responseChannel: c.sendCmd(cmd),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetAccountAddress returns the current Bitcoin address for receiving payments
|
||||
|
@ -1086,12 +1144,15 @@ func (c *Client) SetAccount(address btcutil.Address, account string) error {
|
|||
|
||||
// FutureGetAddressesByAccountResult is a future promise to deliver the result
|
||||
// of a GetAddressesByAccountAsync RPC invocation (or an applicable error).
|
||||
type FutureGetAddressesByAccountResult chan *response
|
||||
type FutureGetAddressesByAccountResult struct {
|
||||
responseChannel chan *response
|
||||
network *chaincfg.Params
|
||||
}
|
||||
|
||||
// Receive waits for the response promised by the future and returns the list of
|
||||
// addresses associated with the passed account.
|
||||
func (r FutureGetAddressesByAccountResult) Receive() ([]btcutil.Address, error) {
|
||||
res, err := receiveFuture(r)
|
||||
res, err := receiveFuture(r.responseChannel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1103,17 +1164,15 @@ func (r FutureGetAddressesByAccountResult) Receive() ([]btcutil.Address, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
addrs := make([]btcutil.Address, 0, len(addrStrings))
|
||||
for _, addrStr := range addrStrings {
|
||||
addr, err := btcutil.DecodeAddress(addrStr,
|
||||
&chaincfg.MainNetParams)
|
||||
addresses := make([]btcutil.Address, len(addrStrings))
|
||||
for i, addrString := range addrStrings {
|
||||
addresses[i], err = btcutil.DecodeAddress(addrString, r.network)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
// GetAddressesByAccountAsync returns an instance of a type that can be used to
|
||||
|
@ -1123,7 +1182,11 @@ func (r FutureGetAddressesByAccountResult) Receive() ([]btcutil.Address, error)
|
|||
// See GetAddressesByAccount for the blocking version and more details.
|
||||
func (c *Client) GetAddressesByAccountAsync(account string) FutureGetAddressesByAccountResult {
|
||||
cmd := btcjson.NewGetAddressesByAccountCmd(account)
|
||||
return c.sendCmd(cmd)
|
||||
result := FutureGetAddressesByAccountResult{
|
||||
network: c.chainParams,
|
||||
responseChannel: c.sendCmd(cmd),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetAddressesByAccount returns the list of addresses associated with the
|
||||
|
@ -1507,6 +1570,43 @@ func (c *Client) GetBalanceMinConf(account string, minConfirms int) (btcutil.Amo
|
|||
return c.GetBalanceMinConfAsync(account, minConfirms).Receive()
|
||||
}
|
||||
|
||||
// FutureGetBalancesResult is a future promise to deliver the result of a
|
||||
// GetBalancesAsync RPC invocation (or an applicable error).
|
||||
type FutureGetBalancesResult chan *response
|
||||
|
||||
// Receive waits for the response promised by the future and returns the
|
||||
// available balances from the server.
|
||||
func (r FutureGetBalancesResult) Receive() (*btcjson.GetBalancesResult, error) {
|
||||
res, err := receiveFuture(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Unmarshal result as a floating point number.
|
||||
var balances btcjson.GetBalancesResult
|
||||
err = json.Unmarshal(res, &balances)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &balances, nil
|
||||
}
|
||||
|
||||
// GetBalancesAsync returns an instance of a type that can be used to get the
|
||||
// result of the RPC at some future time by invoking the Receive function on the
|
||||
// returned instance.
|
||||
//
|
||||
// See GetBalances for the blocking version and more details.
|
||||
func (c *Client) GetBalancesAsync() FutureGetBalancesResult {
|
||||
cmd := btcjson.NewGetBalancesCmd()
|
||||
return c.sendCmd(cmd)
|
||||
}
|
||||
|
||||
// GetBalances returns the available balances from the server.
|
||||
func (c *Client) GetBalances() (*btcjson.GetBalancesResult, error) {
|
||||
return c.GetBalancesAsync().Receive()
|
||||
}
|
||||
|
||||
// FutureGetReceivedByAccountResult is a future promise to deliver the result of
|
||||
// a GetReceivedByAccountAsync or GetReceivedByAccountMinConfAsync RPC
|
||||
// invocation (or an applicable error).
|
||||
|
|
|
@ -586,7 +586,7 @@ func (vm *Engine) checkPubKeyEncoding(pubKey []byte) error {
|
|||
if vm.hasFlag(ScriptVerifyWitnessPubKeyType) &&
|
||||
vm.isWitnessVersionActive(0) && !btcec.IsCompressedPubKey(pubKey) {
|
||||
|
||||
str := "only uncompressed keys are accepted post-segwit"
|
||||
str := "only compressed keys are accepted post-segwit"
|
||||
return scriptError(ErrWitnessPubKeyType, str)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
module github.com/btcsuite/btcutil
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/aead/siphash v1.0.1
|
||||
github.com/btcsuite/btcd v0.20.1-beta
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d
|
||||
)
|
|
@ -0,0 +1,54 @@
|
|||
github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd h1:qdGvebPBDuYDPGi1WCPjy1tGyMpmDK8IEapSsszn7HE=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723 h1:ZA/jbKoGcVAnER6pCHPEkGdZOV7U1oLUedErBHCUMs0=
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495 h1:6IyqGr3fnd0tM3YxipK27TUskaOVUjU2nG45yzwcQKY=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d h1:2+ZP7EfsZV7Vvmx3TIqSlSzATMkTAKqM14YGFPoSKjI=
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
@ -0,0 +1,16 @@
|
|||
ISC License
|
||||
|
||||
Copyright (c) 2013-2017 The btcsuite developers
|
||||
Copyright (c) 2016-2017 The Lightning Network Developers
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
@ -0,0 +1,77 @@
|
|||
package psbt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// Bip32Derivation encapsulates the data for the input and output
|
||||
// Bip32Derivation key-value fields.
|
||||
//
|
||||
// TODO(roasbeef): use hdkeychain here instead?
|
||||
type Bip32Derivation struct {
|
||||
// PubKey is the raw pubkey serialized in compressed format.
|
||||
PubKey []byte
|
||||
|
||||
// MasterKeyFingerprint is the finger print of the master pubkey.
|
||||
MasterKeyFingerprint uint32
|
||||
|
||||
// Bip32Path is the BIP 32 path with child index as a distinct integer.
|
||||
Bip32Path []uint32
|
||||
}
|
||||
|
||||
// checkValid ensures that the PubKey in the Bip32Derivation struct is valid.
|
||||
func (pb *Bip32Derivation) checkValid() bool {
|
||||
return validatePubkey(pb.PubKey)
|
||||
}
|
||||
|
||||
// Bip32Sorter implements sort.Interface for the Bip32Derivation struct.
|
||||
type Bip32Sorter []*Bip32Derivation
|
||||
|
||||
func (s Bip32Sorter) Len() int { return len(s) }
|
||||
|
||||
func (s Bip32Sorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
func (s Bip32Sorter) Less(i, j int) bool {
|
||||
return bytes.Compare(s[i].PubKey, s[j].PubKey) < 0
|
||||
}
|
||||
|
||||
// readBip32Derivation deserializes a byte slice containing chunks of 4 byte
|
||||
// little endian encodings of uint32 values, the first of which is the
|
||||
// masterkeyfingerprint and the remainder of which are the derivation path.
|
||||
func readBip32Derivation(path []byte) (uint32, []uint32, error) {
|
||||
|
||||
if len(path)%4 != 0 || len(path)/4-1 < 1 {
|
||||
return 0, nil, ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
masterKeyInt := binary.LittleEndian.Uint32(path[:4])
|
||||
|
||||
var paths []uint32
|
||||
for i := 4; i < len(path); i += 4 {
|
||||
paths = append(paths, binary.LittleEndian.Uint32(path[i:i+4]))
|
||||
}
|
||||
|
||||
return masterKeyInt, paths, nil
|
||||
}
|
||||
|
||||
// SerializeBIP32Derivation takes a master key fingerprint as defined in BIP32,
|
||||
// along with a path specified as a list of uint32 values, and returns a
|
||||
// bytestring specifying the derivation in the format required by BIP174: //
|
||||
// master key fingerprint (4) || child index (4) || child index (4) || ....
|
||||
func SerializeBIP32Derivation(masterKeyFingerprint uint32,
|
||||
bip32Path []uint32) []byte {
|
||||
|
||||
var masterKeyBytes [4]byte
|
||||
binary.LittleEndian.PutUint32(masterKeyBytes[:], masterKeyFingerprint)
|
||||
|
||||
derivationPath := make([]byte, 0, 4+4*len(bip32Path))
|
||||
derivationPath = append(derivationPath, masterKeyBytes[:]...)
|
||||
for _, path := range bip32Path {
|
||||
var pathbytes [4]byte
|
||||
binary.LittleEndian.PutUint32(pathbytes[:], path)
|
||||
derivationPath = append(derivationPath, pathbytes[:]...)
|
||||
}
|
||||
|
||||
return derivationPath
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) 2018 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package psbt
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// MinTxVersion is the lowest transaction version that we'll permit.
|
||||
const MinTxVersion = 1
|
||||
|
||||
// New on provision of an input and output 'skeleton' for the transaction, a
|
||||
// new partially populated PBST packet. The populated packet will include the
|
||||
// unsigned transaction, and the set of known inputs and outputs contained
|
||||
// within the unsigned transaction. The values of nLockTime, nSequence (per
|
||||
// input) and transaction version (must be 1 of 2) must be specified here. Note
|
||||
// that the default nSequence value is wire.MaxTxInSequenceNum. Referencing
|
||||
// the PSBT BIP, this function serves the roles of teh Creator.
|
||||
func New(inputs []*wire.OutPoint,
|
||||
outputs []*wire.TxOut, version int32, nLockTime uint32,
|
||||
nSequences []uint32) (*Packet, error) {
|
||||
|
||||
// Create the new struct; the input and output lists will be empty, the
|
||||
// unsignedTx object must be constructed and serialized, and that
|
||||
// serialization should be entered as the only entry for the
|
||||
// globalKVPairs list.
|
||||
//
|
||||
// Ensure that the version of the transaction is greater then our
|
||||
// minimum allowed transaction version. There must be one sequence
|
||||
// number per input.
|
||||
if version < MinTxVersion || len(nSequences) != len(inputs) {
|
||||
return nil, ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
unsignedTx := wire.NewMsgTx(version)
|
||||
unsignedTx.LockTime = nLockTime
|
||||
for i, in := range inputs {
|
||||
unsignedTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: *in,
|
||||
Sequence: nSequences[i],
|
||||
})
|
||||
}
|
||||
for _, out := range outputs {
|
||||
unsignedTx.AddTxOut(out)
|
||||
}
|
||||
|
||||
// The input and output lists are empty, but there is a list of those
|
||||
// two lists, and each one must be of length matching the unsigned
|
||||
// transaction; the unknown list can be nil.
|
||||
pInputs := make([]PInput, len(unsignedTx.TxIn))
|
||||
pOutputs := make([]POutput, len(unsignedTx.TxOut))
|
||||
|
||||
// This new Psbt is "raw" and contains no key-value fields, so sanity
|
||||
// checking with c.Cpsbt.SanityCheck() is not required.
|
||||
return &Packet{
|
||||
UnsignedTx: unsignedTx,
|
||||
Inputs: pInputs,
|
||||
Outputs: pOutputs,
|
||||
Unknowns: nil,
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright (c) 2018 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package psbt
|
||||
|
||||
// The Extractor requires provision of a single PSBT
|
||||
// in which all necessary signatures are encoded, and
|
||||
// uses it to construct a fully valid network serialized
|
||||
// transaction.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// Extract takes a finalized psbt.Packet and outputs a finalized transaction
|
||||
// instance. Note that if the PSBT is in-complete, then an error
|
||||
// ErrIncompletePSBT will be returned. As the extracted transaction has been
|
||||
// fully finalized, it will be ready for network broadcast once returned.
|
||||
func Extract(p *Packet) (*wire.MsgTx, error) {
|
||||
// If the packet isn't complete, then we'll return an error as it
|
||||
// doesn't have all the required witness data.
|
||||
if !p.IsComplete() {
|
||||
return nil, ErrIncompletePSBT
|
||||
}
|
||||
|
||||
// First, we'll make a copy of the underlying unsigned transaction (the
|
||||
// initial template) so we don't mutate it during our activates below.
|
||||
finalTx := p.UnsignedTx.Copy()
|
||||
|
||||
// For each input, we'll now populate any relevant witness and
|
||||
// sigScript data.
|
||||
for i, tin := range finalTx.TxIn {
|
||||
// We'll grab the corresponding internal packet input which
|
||||
// matches this materialized transaction input and emplace that
|
||||
// final sigScript (if present).
|
||||
pInput := p.Inputs[i]
|
||||
if pInput.FinalScriptSig != nil {
|
||||
tin.SignatureScript = pInput.FinalScriptSig
|
||||
}
|
||||
|
||||
// Similarly, if there's a final witness, then we'll also need
|
||||
// to extract that as well, parsing the lower-level transaction
|
||||
// encoding.
|
||||
if pInput.FinalScriptWitness != nil {
|
||||
// In order to set the witness, need to re-deserialize
|
||||
// the field as encoded within the PSBT packet. For
|
||||
// each input, the witness is encoded as a stack with
|
||||
// one or more items.
|
||||
witnessReader := bytes.NewReader(
|
||||
pInput.FinalScriptWitness,
|
||||
)
|
||||
|
||||
// First we extract the number of witness elements
|
||||
// encoded in the above witnessReader.
|
||||
witCount, err := wire.ReadVarInt(witnessReader, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now that we know how may inputs we'll need, we'll
|
||||
// construct a packing slice, then read out each input
|
||||
// (with a varint prefix) from the witnessReader.
|
||||
tin.Witness = make(wire.TxWitness, witCount)
|
||||
for j := uint64(0); j < witCount; j++ {
|
||||
wit, err := wire.ReadVarBytes(
|
||||
witnessReader, 0, txscript.MaxScriptSize, "witness",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tin.Witness[j] = wit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return finalTx, nil
|
||||
}
|
|
@ -0,0 +1,462 @@
|
|||
// Copyright (c) 2018 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package psbt
|
||||
|
||||
// The Finalizer requires provision of a single PSBT input
|
||||
// in which all necessary signatures are encoded, and
|
||||
// uses it to construct valid final sigScript and scriptWitness
|
||||
// fields.
|
||||
// NOTE that p2sh (legacy) and p2wsh currently support only
|
||||
// multisig and no other custom script.
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
)
|
||||
|
||||
// isFinalized considers this input finalized if it contains at least one of
|
||||
// the FinalScriptSig or FinalScriptWitness are filled (which only occurs in a
|
||||
// successful call to Finalize*).
|
||||
func isFinalized(p *Packet, inIndex int) bool {
|
||||
input := p.Inputs[inIndex]
|
||||
return input.FinalScriptSig != nil || input.FinalScriptWitness != nil
|
||||
}
|
||||
|
||||
// isFinalizableWitnessInput returns true if the target input is a witness UTXO
|
||||
// that can be finalized.
|
||||
func isFinalizableWitnessInput(pInput *PInput) bool {
|
||||
pkScript := pInput.WitnessUtxo.PkScript
|
||||
|
||||
switch {
|
||||
// If this is a native witness output, then we require both
|
||||
// the witness script, but not a redeem script.
|
||||
case txscript.IsWitnessProgram(pkScript):
|
||||
if txscript.IsPayToWitnessScriptHash(pkScript) {
|
||||
if pInput.WitnessScript == nil ||
|
||||
pInput.RedeemScript != nil {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
// A P2WKH output on the other hand doesn't need
|
||||
// neither a witnessScript or redeemScript.
|
||||
if pInput.WitnessScript != nil ||
|
||||
pInput.RedeemScript != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// For nested P2SH inputs, we verify that a witness script is known.
|
||||
case txscript.IsPayToScriptHash(pkScript):
|
||||
if pInput.RedeemScript == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// If this is a nested P2SH input, then it must also have a
|
||||
// witness script, while we don't need one for P2WKH.
|
||||
if txscript.IsPayToWitnessScriptHash(pInput.RedeemScript) {
|
||||
if pInput.WitnessScript == nil {
|
||||
return false
|
||||
}
|
||||
} else if txscript.IsPayToWitnessPubKeyHash(pInput.RedeemScript) {
|
||||
if pInput.WitnessScript != nil {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
// unrecognized type
|
||||
return false
|
||||
}
|
||||
|
||||
// If this isn't a nested nested P2SH output or a native witness
|
||||
// output, then we can't finalize this input as we don't understand it.
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// isFinalizableLegacyInput returns true of the passed input a legacy input
|
||||
// (non-witness) that can be finalized.
|
||||
func isFinalizableLegacyInput(p *Packet, pInput *PInput, inIndex int) bool {
|
||||
// If the input has a witness, then it's invalid.
|
||||
if pInput.WitnessScript != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Otherwise, we'll verify that we only have a RedeemScript if the prev
|
||||
// output script is P2SH.
|
||||
outIndex := p.UnsignedTx.TxIn[inIndex].PreviousOutPoint.Index
|
||||
if txscript.IsPayToScriptHash(pInput.NonWitnessUtxo.TxOut[outIndex].PkScript) {
|
||||
if pInput.RedeemScript == nil {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if pInput.RedeemScript != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// isFinalizable checks whether the structure of the entry for the input of the
|
||||
// psbt.Packet at index inIndex contains sufficient information to finalize
|
||||
// this input.
|
||||
func isFinalizable(p *Packet, inIndex int) bool {
|
||||
pInput := p.Inputs[inIndex]
|
||||
|
||||
// The input cannot be finalized without any signatures
|
||||
if pInput.PartialSigs == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// For an input to be finalized, we'll one of two possible top-level
|
||||
// UTXOs present. Each UTXO type has a distinct set of requirements to
|
||||
// be considered finalized.
|
||||
switch {
|
||||
|
||||
// A witness input must be either native P2WSH or nested P2SH with all
|
||||
// relevant sigScript or witness data populated.
|
||||
case pInput.WitnessUtxo != nil:
|
||||
if !isFinalizableWitnessInput(&pInput) {
|
||||
return false
|
||||
}
|
||||
|
||||
case pInput.NonWitnessUtxo != nil:
|
||||
if !isFinalizableLegacyInput(p, &pInput, inIndex) {
|
||||
return false
|
||||
}
|
||||
|
||||
// If neither a known UTXO type isn't present at all, then we'll
|
||||
// return false as we need one of them.
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// MaybeFinalize attempts to finalize the input at index inIndex in the PSBT p,
|
||||
// returning true with no error if it succeeds, OR if the input has already
|
||||
// been finalized.
|
||||
func MaybeFinalize(p *Packet, inIndex int) (bool, error) {
|
||||
if isFinalized(p, inIndex) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if !isFinalizable(p, inIndex) {
|
||||
return false, ErrNotFinalizable
|
||||
}
|
||||
|
||||
if err := Finalize(p, inIndex); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// MaybeFinalizeAll attempts to finalize all inputs of the psbt.Packet that are
|
||||
// not already finalized, and returns an error if it fails to do so.
|
||||
func MaybeFinalizeAll(p *Packet) error {
|
||||
|
||||
for i := range p.UnsignedTx.TxIn {
|
||||
success, err := MaybeFinalize(p, i)
|
||||
if err != nil || !success {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finalize assumes that the provided psbt.Packet struct has all partial
|
||||
// signatures and redeem scripts/witness scripts already prepared for the
|
||||
// specified input, and so removes all temporary data and replaces them with
|
||||
// completed sigScript and witness fields, which are stored in key-types 07 and
|
||||
// 08. The witness/non-witness utxo fields in the inputs (key-types 00 and 01)
|
||||
// are left intact as they may be needed for validation (?). If there is any
|
||||
// invalid or incomplete data, an error is returned.
|
||||
func Finalize(p *Packet, inIndex int) error {
|
||||
pInput := p.Inputs[inIndex]
|
||||
|
||||
// Depending on the UTXO type, we either attempt to finalize it as a
|
||||
// witness or legacy UTXO.
|
||||
switch {
|
||||
case pInput.WitnessUtxo != nil:
|
||||
if err := finalizeWitnessInput(p, inIndex); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case pInput.NonWitnessUtxo != nil:
|
||||
if err := finalizeNonWitnessInput(p, inIndex); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
default:
|
||||
return ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
// Before returning we sanity check the PSBT to ensure we don't extract
|
||||
// an invalid transaction or produce an invalid intermediate state.
|
||||
if err := p.SanityCheck(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkFinalScriptSigWitness checks whether a given input in the psbt.Packet
|
||||
// struct already has the fields 07 (FinalInScriptSig) or 08 (FinalInWitness).
|
||||
// If so, it returns true. It does not modify the Psbt.
|
||||
func checkFinalScriptSigWitness(p *Packet, inIndex int) bool {
|
||||
pInput := p.Inputs[inIndex]
|
||||
|
||||
if pInput.FinalScriptSig != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if pInput.FinalScriptWitness != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// finalizeNonWitnessInput attempts to create a PsbtInFinalScriptSig field for
|
||||
// the input at index inIndex, and removes all other fields except for the UTXO
|
||||
// field, for an input of type non-witness, or returns an error.
|
||||
func finalizeNonWitnessInput(p *Packet, inIndex int) error {
|
||||
// If this input has already been finalized, then we'll return an error
|
||||
// as we can't proceed.
|
||||
if checkFinalScriptSigWitness(p, inIndex) {
|
||||
return ErrInputAlreadyFinalized
|
||||
}
|
||||
|
||||
// Our goal here is to construct a sigScript given the pubkey,
|
||||
// signature (keytype 02), of which there might be multiple, and the
|
||||
// redeem script field (keytype 04) if present (note, it is not present
|
||||
// for p2pkh type inputs).
|
||||
var sigScript []byte
|
||||
|
||||
pInput := p.Inputs[inIndex]
|
||||
containsRedeemScript := pInput.RedeemScript != nil
|
||||
|
||||
var (
|
||||
pubKeys [][]byte
|
||||
sigs [][]byte
|
||||
)
|
||||
for _, ps := range pInput.PartialSigs {
|
||||
pubKeys = append(pubKeys, ps.PubKey)
|
||||
|
||||
sigOK := checkSigHashFlags(ps.Signature, &pInput)
|
||||
if !sigOK {
|
||||
return ErrInvalidSigHashFlags
|
||||
}
|
||||
|
||||
sigs = append(sigs, ps.Signature)
|
||||
}
|
||||
|
||||
// We have failed to identify at least 1 (sig, pub) pair in the PSBT,
|
||||
// which indicates it was not ready to be finalized. As a result, we
|
||||
// can't proceed.
|
||||
if len(sigs) < 1 || len(pubKeys) < 1 {
|
||||
return ErrNotFinalizable
|
||||
}
|
||||
|
||||
// If this input doesn't need a redeem script (P2PKH), then we'll
|
||||
// construct a simple sigScript that's just the signature then the
|
||||
// pubkey (OP_CHECKSIG).
|
||||
var err error
|
||||
if !containsRedeemScript {
|
||||
// At this point, we should only have a single signature and
|
||||
// pubkey.
|
||||
if len(sigs) != 1 || len(pubKeys) != 1 {
|
||||
return ErrNotFinalizable
|
||||
}
|
||||
|
||||
// In this case, our sigScript is just: <sig> <pubkey>.
|
||||
builder := txscript.NewScriptBuilder()
|
||||
builder.AddData(sigs[0]).AddData(pubKeys[0])
|
||||
sigScript, err = builder.Script()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// This is assumed p2sh multisig Given redeemScript and pubKeys
|
||||
// we can decide in what order signatures must be appended.
|
||||
orderedSigs, err := extractKeyOrderFromScript(
|
||||
pInput.RedeemScript, pubKeys, sigs,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// At this point, we assume that this is a mult-sig input, so
|
||||
// we construct our sigScript which looks something like this
|
||||
// (mind the extra element for the extra multi-sig pop):
|
||||
// * <nil> <sigs...> <redeemScript>
|
||||
//
|
||||
// TODO(waxwing): the below is specific to the multisig case.
|
||||
builder := txscript.NewScriptBuilder()
|
||||
builder.AddOp(txscript.OP_FALSE)
|
||||
for _, os := range orderedSigs {
|
||||
builder.AddData(os)
|
||||
}
|
||||
builder.AddData(pInput.RedeemScript)
|
||||
sigScript, err = builder.Script()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, a sigScript has been constructed. Remove all fields
|
||||
// other than non-witness utxo (00) and finaliscriptsig (07)
|
||||
newInput := NewPsbtInput(pInput.NonWitnessUtxo, nil)
|
||||
newInput.FinalScriptSig = sigScript
|
||||
|
||||
// Overwrite the entry in the input list at the correct index. Note
|
||||
// that this removes all the other entries in the list for this input
|
||||
// index.
|
||||
p.Inputs[inIndex] = *newInput
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// finalizeWitnessInput attempts to create PsbtInFinalScriptSig field and
|
||||
// PsbtInFinalScriptWitness field for input at index inIndex, and removes all
|
||||
// other fields except for the utxo field, for an input of type witness, or
|
||||
// returns an error.
|
||||
func finalizeWitnessInput(p *Packet, inIndex int) error {
|
||||
// If this input has already been finalized, then we'll return an error
|
||||
// as we can't proceed.
|
||||
if checkFinalScriptSigWitness(p, inIndex) {
|
||||
return ErrInputAlreadyFinalized
|
||||
}
|
||||
|
||||
// Depending on the actual output type, we'll either populate a
|
||||
// serializedWitness or a witness as well asa sigScript.
|
||||
var (
|
||||
sigScript []byte
|
||||
serializedWitness []byte
|
||||
)
|
||||
|
||||
pInput := p.Inputs[inIndex]
|
||||
|
||||
// First we'll validate and collect the pubkey+sig pairs from the set
|
||||
// of partial signatures.
|
||||
var (
|
||||
pubKeys [][]byte
|
||||
sigs [][]byte
|
||||
)
|
||||
for _, ps := range pInput.PartialSigs {
|
||||
pubKeys = append(pubKeys, ps.PubKey)
|
||||
|
||||
sigOK := checkSigHashFlags(ps.Signature, &pInput)
|
||||
if !sigOK {
|
||||
return ErrInvalidSigHashFlags
|
||||
|
||||
}
|
||||
|
||||
sigs = append(sigs, ps.Signature)
|
||||
}
|
||||
|
||||
// If at this point, we don't have any pubkey+sig pairs, then we bail
|
||||
// as we can't proceed.
|
||||
if len(sigs) == 0 || len(pubKeys) == 0 {
|
||||
return ErrNotFinalizable
|
||||
}
|
||||
|
||||
containsRedeemScript := pInput.RedeemScript != nil
|
||||
cointainsWitnessScript := pInput.WitnessScript != nil
|
||||
|
||||
// If there's no redeem script, then we assume that this is native
|
||||
// segwit input.
|
||||
var err error
|
||||
if !containsRedeemScript {
|
||||
// If we have only a sigley pubkey+sig pair, and no witness
|
||||
// script, then we assume this is a P2WKH input.
|
||||
if len(pubKeys) == 1 && len(sigs) == 1 &&
|
||||
!cointainsWitnessScript {
|
||||
|
||||
serializedWitness, err = writePKHWitness(
|
||||
sigs[0], pubKeys[0],
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Otherwise, we must have a witnessScript field, so
|
||||
// we'll generate a valid multi-sig witness.
|
||||
//
|
||||
// NOTE: We tacitly assume multisig.
|
||||
//
|
||||
// TODO(roasbeef): need to add custom finalize for
|
||||
// non-multisig P2WSH outputs (HTLCs, delay outputs,
|
||||
// etc).
|
||||
if !cointainsWitnessScript {
|
||||
return ErrNotFinalizable
|
||||
}
|
||||
|
||||
serializedWitness, err = getMultisigScriptWitness(
|
||||
pInput.WitnessScript, pubKeys, sigs,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Otherwise, we assume that this is a p2wsh multi-sig output,
|
||||
// which is nested in a p2sh, or a p2wkh nested in a p2sh.
|
||||
//
|
||||
// In this case, we'll take the redeem script (the witness
|
||||
// program in this case), and push it on the stack within the
|
||||
// sigScript.
|
||||
builder := txscript.NewScriptBuilder()
|
||||
builder.AddData(pInput.RedeemScript)
|
||||
sigScript, err = builder.Script()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If don't have a witness script, then we assume this is a
|
||||
// nested p2wkh output.
|
||||
if !cointainsWitnessScript {
|
||||
// Assumed p2sh-p2wkh Here the witness is just (sig,
|
||||
// pub) as for p2pkh case
|
||||
if len(sigs) != 1 || len(pubKeys) != 1 {
|
||||
return ErrNotFinalizable
|
||||
}
|
||||
|
||||
serializedWitness, err = writePKHWitness(sigs[0], pubKeys[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else {
|
||||
// Otherwise, we assume that this is a p2wsh multi-sig,
|
||||
// so we generate the proper witness.
|
||||
serializedWitness, err = getMultisigScriptWitness(
|
||||
pInput.WitnessScript, pubKeys, sigs,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, a witness has been constructed, and a sigScript (if
|
||||
// nested; else it's []). Remove all fields other than witness utxo
|
||||
// (01) and finalscriptsig (07), finalscriptwitness (08).
|
||||
newInput := NewPsbtInput(nil, pInput.WitnessUtxo)
|
||||
if len(sigScript) > 0 {
|
||||
newInput.FinalScriptSig = sigScript
|
||||
}
|
||||
|
||||
newInput.FinalScriptWitness = serializedWitness
|
||||
|
||||
// Finally, we overwrite the entry in the input list at the correct
|
||||
// index.
|
||||
p.Inputs[inIndex] = *newInput
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
module github.com/btcsuite/btcutil/psbt
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/btcsuite/btcd v0.20.1-beta
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
)
|
|
@ -0,0 +1,36 @@
|
|||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/btcutil v0.0.0-20191219182022-e17c9730c422 h1:EqnrgSSg0SFWRlEZLExgjtuUR/IPnuQ6qw6nwRda4Uk=
|
||||
github.com/btcsuite/btcutil v0.0.0-20191219182022-e17c9730c422/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44 h1:9lP3x0pW80sDI6t1UMSLA4to18W7R7imwAI/sWS9S8Q=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
@ -0,0 +1,367 @@
|
|||
package psbt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// PInput is a struct encapsulating all the data that can be attached to any
|
||||
// specific input of the PSBT.
|
||||
type PInput struct {
|
||||
NonWitnessUtxo *wire.MsgTx
|
||||
WitnessUtxo *wire.TxOut
|
||||
PartialSigs []*PartialSig
|
||||
SighashType txscript.SigHashType
|
||||
RedeemScript []byte
|
||||
WitnessScript []byte
|
||||
Bip32Derivation []*Bip32Derivation
|
||||
FinalScriptSig []byte
|
||||
FinalScriptWitness []byte
|
||||
Unknowns []*Unknown
|
||||
}
|
||||
|
||||
// NewPsbtInput creates an instance of PsbtInput given either a nonWitnessUtxo
|
||||
// or a witnessUtxo.
|
||||
//
|
||||
// NOTE: Only one of the two arguments should be specified, with the other
|
||||
// being `nil`; otherwise the created PsbtInput object will fail IsSane()
|
||||
// checks and will not be usable.
|
||||
func NewPsbtInput(nonWitnessUtxo *wire.MsgTx,
|
||||
witnessUtxo *wire.TxOut) *PInput {
|
||||
|
||||
return &PInput{
|
||||
NonWitnessUtxo: nonWitnessUtxo,
|
||||
WitnessUtxo: witnessUtxo,
|
||||
PartialSigs: []*PartialSig{},
|
||||
SighashType: 0,
|
||||
RedeemScript: nil,
|
||||
WitnessScript: nil,
|
||||
Bip32Derivation: []*Bip32Derivation{},
|
||||
FinalScriptSig: nil,
|
||||
FinalScriptWitness: nil,
|
||||
Unknowns: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// IsSane returns true only if there are no conflicting values in the Psbt
|
||||
// PInput. It checks that witness and non-witness utxo entries do not both
|
||||
// exist, and that witnessScript entries are only added to witness inputs.
|
||||
func (pi *PInput) IsSane() bool {
|
||||
|
||||
if pi.NonWitnessUtxo != nil && pi.WitnessUtxo != nil {
|
||||
return false
|
||||
}
|
||||
if pi.WitnessUtxo == nil && pi.WitnessScript != nil {
|
||||
return false
|
||||
}
|
||||
if pi.WitnessUtxo == nil && pi.FinalScriptWitness != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// deserialize attempts to deserialize a new PInput from the passed io.Reader.
|
||||
func (pi *PInput) deserialize(r io.Reader) error {
|
||||
for {
|
||||
keyint, keydata, err := getKey(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if keyint == -1 {
|
||||
// Reached separator byte
|
||||
break
|
||||
}
|
||||
value, err := wire.ReadVarBytes(
|
||||
r, 0, MaxPsbtValueLength, "PSBT value",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch InputType(keyint) {
|
||||
|
||||
case NonWitnessUtxoType:
|
||||
if pi.NonWitnessUtxo != nil {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
if keydata != nil {
|
||||
return ErrInvalidKeydata
|
||||
}
|
||||
tx := wire.NewMsgTx(2)
|
||||
|
||||
err := tx.Deserialize(bytes.NewReader(value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pi.NonWitnessUtxo = tx
|
||||
|
||||
case WitnessUtxoType:
|
||||
if pi.WitnessUtxo != nil {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
if keydata != nil {
|
||||
return ErrInvalidKeydata
|
||||
}
|
||||
txout, err := readTxOut(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pi.WitnessUtxo = txout
|
||||
|
||||
case PartialSigType:
|
||||
newPartialSig := PartialSig{
|
||||
PubKey: keydata,
|
||||
Signature: value,
|
||||
}
|
||||
|
||||
if !newPartialSig.checkValid() {
|
||||
return ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
// Duplicate keys are not allowed
|
||||
for _, x := range pi.PartialSigs {
|
||||
if bytes.Equal(x.PubKey, newPartialSig.PubKey) {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
}
|
||||
|
||||
pi.PartialSigs = append(pi.PartialSigs, &newPartialSig)
|
||||
|
||||
case SighashType:
|
||||
if pi.SighashType != 0 {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
if keydata != nil {
|
||||
return ErrInvalidKeydata
|
||||
}
|
||||
|
||||
// Bounds check on value here since the sighash type must be a
|
||||
// 32-bit unsigned integer.
|
||||
if len(value) != 4 {
|
||||
return ErrInvalidKeydata
|
||||
}
|
||||
|
||||
shtype := txscript.SigHashType(
|
||||
binary.LittleEndian.Uint32(value),
|
||||
)
|
||||
pi.SighashType = shtype
|
||||
|
||||
case RedeemScriptInputType:
|
||||
if pi.RedeemScript != nil {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
if keydata != nil {
|
||||
return ErrInvalidKeydata
|
||||
}
|
||||
pi.RedeemScript = value
|
||||
|
||||
case WitnessScriptInputType:
|
||||
if pi.WitnessScript != nil {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
if keydata != nil {
|
||||
return ErrInvalidKeydata
|
||||
}
|
||||
pi.WitnessScript = value
|
||||
|
||||
case Bip32DerivationInputType:
|
||||
if !validatePubkey(keydata) {
|
||||
return ErrInvalidPsbtFormat
|
||||
}
|
||||
master, derivationPath, err := readBip32Derivation(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Duplicate keys are not allowed
|
||||
for _, x := range pi.Bip32Derivation {
|
||||
if bytes.Equal(x.PubKey, keydata) {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
}
|
||||
|
||||
pi.Bip32Derivation = append(
|
||||
pi.Bip32Derivation,
|
||||
&Bip32Derivation{
|
||||
PubKey: keydata,
|
||||
MasterKeyFingerprint: master,
|
||||
Bip32Path: derivationPath,
|
||||
},
|
||||
)
|
||||
|
||||
case FinalScriptSigType:
|
||||
if pi.FinalScriptSig != nil {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
if keydata != nil {
|
||||
return ErrInvalidKeydata
|
||||
}
|
||||
|
||||
pi.FinalScriptSig = value
|
||||
|
||||
case FinalScriptWitnessType:
|
||||
if pi.FinalScriptWitness != nil {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
if keydata != nil {
|
||||
return ErrInvalidKeydata
|
||||
}
|
||||
|
||||
pi.FinalScriptWitness = value
|
||||
|
||||
default:
|
||||
// A fall through case for any proprietary types.
|
||||
keyintanddata := []byte{byte(keyint)}
|
||||
keyintanddata = append(keyintanddata, keydata...)
|
||||
newUnknown := &Unknown{
|
||||
Key: keyintanddata,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
// Duplicate key+keydata are not allowed
|
||||
for _, x := range pi.Unknowns {
|
||||
if bytes.Equal(x.Key, newUnknown.Key) &&
|
||||
bytes.Equal(x.Value, newUnknown.Value) {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
}
|
||||
|
||||
pi.Unknowns = append(pi.Unknowns, newUnknown)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// serialize attempts to serialize the target PInput into the passed io.Writer.
|
||||
func (pi *PInput) serialize(w io.Writer) error {
|
||||
|
||||
if !pi.IsSane() {
|
||||
return ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
if pi.NonWitnessUtxo != nil {
|
||||
var buf bytes.Buffer
|
||||
err := pi.NonWitnessUtxo.Serialize(&buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = serializeKVPairWithType(
|
||||
w, uint8(NonWitnessUtxoType), nil, buf.Bytes(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if pi.WitnessUtxo != nil {
|
||||
var buf bytes.Buffer
|
||||
err := wire.WriteTxOut(&buf, 0, 0, pi.WitnessUtxo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = serializeKVPairWithType(
|
||||
w, uint8(WitnessUtxoType), nil, buf.Bytes(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if pi.FinalScriptSig == nil && pi.FinalScriptWitness == nil {
|
||||
sort.Sort(PartialSigSorter(pi.PartialSigs))
|
||||
for _, ps := range pi.PartialSigs {
|
||||
err := serializeKVPairWithType(
|
||||
w, uint8(PartialSigType), ps.PubKey,
|
||||
ps.Signature,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if pi.SighashType != 0 {
|
||||
var shtBytes [4]byte
|
||||
binary.LittleEndian.PutUint32(
|
||||
shtBytes[:], uint32(pi.SighashType),
|
||||
)
|
||||
|
||||
err := serializeKVPairWithType(
|
||||
w, uint8(SighashType), nil, shtBytes[:],
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if pi.RedeemScript != nil {
|
||||
err := serializeKVPairWithType(
|
||||
w, uint8(RedeemScriptInputType), nil,
|
||||
pi.RedeemScript,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if pi.WitnessScript != nil {
|
||||
err := serializeKVPairWithType(
|
||||
w, uint8(WitnessScriptInputType), nil,
|
||||
pi.WitnessScript,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(Bip32Sorter(pi.Bip32Derivation))
|
||||
for _, kd := range pi.Bip32Derivation {
|
||||
err := serializeKVPairWithType(
|
||||
w,
|
||||
uint8(Bip32DerivationInputType), kd.PubKey,
|
||||
SerializeBIP32Derivation(
|
||||
kd.MasterKeyFingerprint, kd.Bip32Path,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if pi.FinalScriptSig != nil {
|
||||
err := serializeKVPairWithType(
|
||||
w, uint8(FinalScriptSigType), nil, pi.FinalScriptSig,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if pi.FinalScriptWitness != nil {
|
||||
err := serializeKVPairWithType(
|
||||
w, uint8(FinalScriptWitnessType), nil, pi.FinalScriptWitness,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown is a special case; we don't have a key type, only a key and
|
||||
// a value field
|
||||
for _, kv := range pi.Unknowns {
|
||||
err := serializeKVpair(w, kv.Key, kv.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package psbt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// POutput is a struct encapsulating all the data that can be attached
|
||||
// to any specific output of the PSBT.
|
||||
type POutput struct {
|
||||
RedeemScript []byte
|
||||
WitnessScript []byte
|
||||
Bip32Derivation []*Bip32Derivation
|
||||
}
|
||||
|
||||
// NewPsbtOutput creates an instance of PsbtOutput; the three parameters
|
||||
// redeemScript, witnessScript and Bip32Derivation are all allowed to be
|
||||
// `nil`.
|
||||
func NewPsbtOutput(redeemScript []byte, witnessScript []byte,
|
||||
bip32Derivation []*Bip32Derivation) *POutput {
|
||||
return &POutput{
|
||||
RedeemScript: redeemScript,
|
||||
WitnessScript: witnessScript,
|
||||
Bip32Derivation: bip32Derivation,
|
||||
}
|
||||
}
|
||||
|
||||
// deserialize attempts to recode a new POutput from the passed io.Reader.
|
||||
func (po *POutput) deserialize(r io.Reader) error {
|
||||
for {
|
||||
keyint, keydata, err := getKey(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if keyint == -1 {
|
||||
// Reached separator byte
|
||||
break
|
||||
}
|
||||
|
||||
value, err := wire.ReadVarBytes(
|
||||
r, 0, MaxPsbtValueLength, "PSBT value",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch OutputType(keyint) {
|
||||
|
||||
case RedeemScriptOutputType:
|
||||
if po.RedeemScript != nil {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
if keydata != nil {
|
||||
return ErrInvalidKeydata
|
||||
}
|
||||
po.RedeemScript = value
|
||||
|
||||
case WitnessScriptOutputType:
|
||||
if po.WitnessScript != nil {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
if keydata != nil {
|
||||
return ErrInvalidKeydata
|
||||
}
|
||||
po.WitnessScript = value
|
||||
|
||||
case Bip32DerivationOutputType:
|
||||
if !validatePubkey(keydata) {
|
||||
return ErrInvalidKeydata
|
||||
}
|
||||
master, derivationPath, err := readBip32Derivation(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Duplicate keys are not allowed
|
||||
for _, x := range po.Bip32Derivation {
|
||||
if bytes.Equal(x.PubKey, keydata) {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
}
|
||||
|
||||
po.Bip32Derivation = append(po.Bip32Derivation,
|
||||
&Bip32Derivation{
|
||||
PubKey: keydata,
|
||||
MasterKeyFingerprint: master,
|
||||
Bip32Path: derivationPath,
|
||||
},
|
||||
)
|
||||
|
||||
default:
|
||||
// Unknown type is allowed for inputs but not outputs.
|
||||
return ErrInvalidPsbtFormat
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// serialize attempts to write out the target POutput into the passed
|
||||
// io.Writer.
|
||||
func (po *POutput) serialize(w io.Writer) error {
|
||||
if po.RedeemScript != nil {
|
||||
err := serializeKVPairWithType(
|
||||
w, uint8(RedeemScriptOutputType), nil, po.RedeemScript,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if po.WitnessScript != nil {
|
||||
err := serializeKVPairWithType(
|
||||
w, uint8(WitnessScriptOutputType), nil, po.WitnessScript,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(Bip32Sorter(po.Bip32Derivation))
|
||||
for _, kd := range po.Bip32Derivation {
|
||||
err := serializeKVPairWithType(w,
|
||||
uint8(Bip32DerivationOutputType),
|
||||
kd.PubKey,
|
||||
SerializeBIP32Derivation(
|
||||
kd.MasterKeyFingerprint,
|
||||
kd.Bip32Path,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package psbt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
)
|
||||
|
||||
// PartialSig encapsulate a (BTC public key, ECDSA signature)
|
||||
// pair, note that the fields are stored as byte slices, not
|
||||
// btcec.PublicKey or btcec.Signature (because manipulations will
|
||||
// be with the former not the latter, here); compliance with consensus
|
||||
// serialization is enforced with .checkValid()
|
||||
type PartialSig struct {
|
||||
PubKey []byte
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
// PartialSigSorter implements sort.Interface for PartialSig.
|
||||
type PartialSigSorter []*PartialSig
|
||||
|
||||
func (s PartialSigSorter) Len() int { return len(s) }
|
||||
|
||||
func (s PartialSigSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
func (s PartialSigSorter) Less(i, j int) bool {
|
||||
return bytes.Compare(s[i].PubKey, s[j].PubKey) < 0
|
||||
}
|
||||
|
||||
// validatePubkey checks if pubKey is *any* valid pubKey serialization in a
|
||||
// Bitcoin context (compressed/uncomp. OK).
|
||||
func validatePubkey(pubKey []byte) bool {
|
||||
_, err := btcec.ParsePubKey(pubKey, btcec.S256())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// validateSignature checks that the passed byte slice is a valid DER-encoded
|
||||
// ECDSA signature, including the sighash flag. It does *not* of course
|
||||
// validate the signature against any message or public key.
|
||||
func validateSignature(sig []byte) bool {
|
||||
_, err := btcec.ParseDERSignature(sig, btcec.S256())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// checkValid checks that both the pbukey and sig are valid. See the methods
|
||||
// (PartialSig, validatePubkey, validateSignature) for more details.
|
||||
//
|
||||
// TODO(waxwing): update for Schnorr will be needed here if/when that
|
||||
// activates.
|
||||
func (ps *PartialSig) checkValid() bool {
|
||||
return validatePubkey(ps.PubKey) && validateSignature(ps.Signature)
|
||||
}
|
|
@ -0,0 +1,407 @@
|
|||
// Copyright (c) 2018 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package psbt is an implementation of Partially Signed Bitcoin
|
||||
// Transactions (PSBT). The format is defined in BIP 174:
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
|
||||
package psbt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// psbtMagicLength is the length of the magic bytes used to signal the start of
|
||||
// a serialized PSBT packet.
|
||||
const psbtMagicLength = 5
|
||||
|
||||
var (
|
||||
// psbtMagic is the separator
|
||||
psbtMagic = [psbtMagicLength]byte{0x70,
|
||||
0x73, 0x62, 0x74, 0xff, // = "psbt" + 0xff sep
|
||||
}
|
||||
)
|
||||
|
||||
// MaxPsbtValueLength is the size of the largest transaction serialization
|
||||
// that could be passed in a NonWitnessUtxo field. This is definitely
|
||||
//less than 4M.
|
||||
const MaxPsbtValueLength = 4000000
|
||||
|
||||
// MaxPsbtKeyLength is the length of the largest key that we'll successfully
|
||||
// deserialize from the wire. Anything more will return ErrInvalidKeydata.
|
||||
const MaxPsbtKeyLength = 10000
|
||||
|
||||
var (
|
||||
|
||||
// ErrInvalidPsbtFormat is a generic error for any situation in which a
|
||||
// provided Psbt serialization does not conform to the rules of BIP174.
|
||||
ErrInvalidPsbtFormat = errors.New("Invalid PSBT serialization format")
|
||||
|
||||
// ErrDuplicateKey indicates that a passed Psbt serialization is invalid
|
||||
// due to having the same key repeated in the same key-value pair.
|
||||
ErrDuplicateKey = errors.New("Invalid Psbt due to duplicate key")
|
||||
|
||||
// ErrInvalidKeydata indicates that a key-value pair in the PSBT
|
||||
// serialization contains data in the key which is not valid.
|
||||
ErrInvalidKeydata = errors.New("Invalid key data")
|
||||
|
||||
// ErrInvalidMagicBytes indicates that a passed Psbt serialization is invalid
|
||||
// due to having incorrect magic bytes.
|
||||
ErrInvalidMagicBytes = errors.New("Invalid Psbt due to incorrect magic bytes")
|
||||
|
||||
// ErrInvalidRawTxSigned indicates that the raw serialized transaction in the
|
||||
// global section of the passed Psbt serialization is invalid because it
|
||||
// contains scriptSigs/witnesses (i.e. is fully or partially signed), which
|
||||
// is not allowed by BIP174.
|
||||
ErrInvalidRawTxSigned = errors.New("Invalid Psbt, raw transaction must " +
|
||||
"be unsigned.")
|
||||
|
||||
// ErrInvalidPrevOutNonWitnessTransaction indicates that the transaction
|
||||
// hash (i.e. SHA256^2) of the fully serialized previous transaction
|
||||
// provided in the NonWitnessUtxo key-value field doesn't match the prevout
|
||||
// hash in the UnsignedTx field in the PSBT itself.
|
||||
ErrInvalidPrevOutNonWitnessTransaction = errors.New("Prevout hash does " +
|
||||
"not match the provided non-witness utxo serialization")
|
||||
|
||||
// ErrInvalidSignatureForInput indicates that the signature the user is
|
||||
// trying to append to the PSBT is invalid, either because it does
|
||||
// not correspond to the previous transaction hash, or redeem script,
|
||||
// or witness script.
|
||||
// NOTE this does not include ECDSA signature checking.
|
||||
ErrInvalidSignatureForInput = errors.New("Signature does not correspond " +
|
||||
"to this input")
|
||||
|
||||
// ErrInputAlreadyFinalized indicates that the PSBT passed to a Finalizer
|
||||
// already contains the finalized scriptSig or witness.
|
||||
ErrInputAlreadyFinalized = errors.New("Cannot finalize PSBT, finalized " +
|
||||
"scriptSig or scriptWitnes already exists")
|
||||
|
||||
// ErrIncompletePSBT indicates that the Extractor object
|
||||
// was unable to successfully extract the passed Psbt struct because
|
||||
// it is not complete
|
||||
ErrIncompletePSBT = errors.New("PSBT cannot be extracted as it is " +
|
||||
"incomplete")
|
||||
|
||||
// ErrNotFinalizable indicates that the PSBT struct does not have
|
||||
// sufficient data (e.g. signatures) for finalization
|
||||
ErrNotFinalizable = errors.New("PSBT is not finalizable")
|
||||
|
||||
// ErrInvalidSigHashFlags indicates that a signature added to the PSBT
|
||||
// uses Sighash flags that are not in accordance with the requirement
|
||||
// according to the entry in PsbtInSighashType, or otherwise not the
|
||||
// default value (SIGHASH_ALL)
|
||||
ErrInvalidSigHashFlags = errors.New("Invalid Sighash Flags")
|
||||
|
||||
// ErrUnsupportedScriptType indicates that the redeem script or
|
||||
// scriptwitness given is not supported by this codebase, or is otherwise
|
||||
// not valid.
|
||||
ErrUnsupportedScriptType = errors.New("Unsupported script type")
|
||||
)
|
||||
|
||||
// Unknown is a struct encapsulating a key-value pair for which the key type is
|
||||
// unknown by this package; these fields are allowed in both the 'Global' and
|
||||
// the 'Input' section of a PSBT.
|
||||
type Unknown struct {
|
||||
Key []byte
|
||||
Value []byte
|
||||
}
|
||||
|
||||
// Packet is the actual psbt repreesntation. It is a is a set of 1 + N + M
|
||||
// key-value pair lists, 1 global, defining the unsigned transaction structure
|
||||
// with N inputs and M outputs. These key-value pairs can contain scripts,
|
||||
// signatures, key derivations and other transaction-defining data.
|
||||
type Packet struct {
|
||||
// UnsignedTx is the decoded unsigned transaction for this PSBT.
|
||||
UnsignedTx *wire.MsgTx // Deserialization of unsigned tx
|
||||
|
||||
// Inputs contains all the information needed to properly sign this
|
||||
// target input within the above transaction.
|
||||
Inputs []PInput
|
||||
|
||||
// Outputs contains all information required to spend any outputs
|
||||
// produced by this PSBT.
|
||||
Outputs []POutput
|
||||
|
||||
// Unknowns are the set of custom types (global only) within this PSBT.
|
||||
Unknowns []Unknown
|
||||
}
|
||||
|
||||
// validateUnsignedTx returns true if the transaction is unsigned. Note that
|
||||
// more basic sanity requirements, such as the presence of inputs and outputs,
|
||||
// is implicitly checked in the call to MsgTx.Deserialize().
|
||||
func validateUnsignedTX(tx *wire.MsgTx) bool {
|
||||
for _, tin := range tx.TxIn {
|
||||
if len(tin.SignatureScript) != 0 || len(tin.Witness) != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// NewFromUnsignedTx creates a new Psbt struct, without any signatures (i.e.
|
||||
// only the global section is non-empty) using the passed unsigned transaction.
|
||||
func NewFromUnsignedTx(tx *wire.MsgTx) (*Packet, error) {
|
||||
|
||||
if !validateUnsignedTX(tx) {
|
||||
return nil, ErrInvalidRawTxSigned
|
||||
}
|
||||
|
||||
inSlice := make([]PInput, len(tx.TxIn))
|
||||
outSlice := make([]POutput, len(tx.TxOut))
|
||||
unknownSlice := make([]Unknown, 0)
|
||||
|
||||
retPsbt := Packet{
|
||||
UnsignedTx: tx,
|
||||
Inputs: inSlice,
|
||||
Outputs: outSlice,
|
||||
Unknowns: unknownSlice,
|
||||
}
|
||||
|
||||
return &retPsbt, nil
|
||||
}
|
||||
|
||||
// NewFromRawBytes returns a new instance of a Packet struct created by reading
|
||||
// from a byte slice. If the format is invalid, an error is returned. If the
|
||||
// argument b64 is true, the passed byte slice is decoded from base64 encoding
|
||||
// before processing.
|
||||
//
|
||||
// NOTE: To create a Packet from one's own data, rather than reading in a
|
||||
// serialization from a counterparty, one should use a psbt.New.
|
||||
func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) {
|
||||
|
||||
// If the PSBT is encoded in bas64, then we'll create a new wrapper
|
||||
// reader that'll allow us to incrementally decode the contents of the
|
||||
// io.Reader.
|
||||
if b64 {
|
||||
based64EncodedReader := r
|
||||
r = base64.NewDecoder(base64.StdEncoding, based64EncodedReader)
|
||||
}
|
||||
|
||||
// The Packet struct does not store the fixed magic bytes, but they
|
||||
// must be present or the serialization must be explicitly rejected.
|
||||
var magic [5]byte
|
||||
if _, err := io.ReadFull(r, magic[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if magic != psbtMagic {
|
||||
return nil, ErrInvalidMagicBytes
|
||||
}
|
||||
|
||||
// Next we parse the GLOBAL section. There is currently only 1 known
|
||||
// key type, UnsignedTx. We insist this exists first; unknowns are
|
||||
// allowed, but only after.
|
||||
keyint, keydata, err := getKey(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if GlobalType(keyint) != UnsignedTxType || keydata != nil {
|
||||
return nil, ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
// Now that we've verified the global type is present, we'll decode it
|
||||
// into a proper unsigned transaction, and validate it.
|
||||
value, err := wire.ReadVarBytes(
|
||||
r, 0, MaxPsbtValueLength, "PSBT value",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgTx := wire.NewMsgTx(2)
|
||||
err = msgTx.Deserialize(bytes.NewReader(value))
|
||||
if err != nil {
|
||||
// If there are no inputs in this yet incomplete transaction,
|
||||
// the wire package still incorrectly assumes it's encoded in
|
||||
// the witness format. We can fix this by just trying the non-
|
||||
// witness encoding too. If that also fails, it's probably an
|
||||
// invalid transaction.
|
||||
msgTx = wire.NewMsgTx(2)
|
||||
err2 := msgTx.DeserializeNoWitness(bytes.NewReader(value))
|
||||
|
||||
// If the second attempt also failed, something else is wrong
|
||||
// and it probably makes more sense to return the original
|
||||
// error instead of the error from the workaround.
|
||||
if err2 != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !validateUnsignedTX(msgTx) {
|
||||
return nil, ErrInvalidRawTxSigned
|
||||
}
|
||||
|
||||
// Next we parse any unknowns that may be present, making sure that we
|
||||
// break at the separator.
|
||||
var unknownSlice []Unknown
|
||||
for {
|
||||
keyint, keydata, err := getKey(r)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidPsbtFormat
|
||||
}
|
||||
if keyint == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
value, err := wire.ReadVarBytes(
|
||||
r, 0, MaxPsbtValueLength, "PSBT value",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyintanddata := []byte{byte(keyint)}
|
||||
keyintanddata = append(keyintanddata, keydata...)
|
||||
|
||||
newUnknown := Unknown{
|
||||
Key: keyintanddata,
|
||||
Value: value,
|
||||
}
|
||||
unknownSlice = append(unknownSlice, newUnknown)
|
||||
}
|
||||
|
||||
// Next we parse the INPUT section.
|
||||
inSlice := make([]PInput, len(msgTx.TxIn))
|
||||
for i := range msgTx.TxIn {
|
||||
input := PInput{}
|
||||
err = input.deserialize(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inSlice[i] = input
|
||||
}
|
||||
|
||||
// Next we parse the OUTPUT section.
|
||||
outSlice := make([]POutput, len(msgTx.TxOut))
|
||||
for i := range msgTx.TxOut {
|
||||
output := POutput{}
|
||||
err = output.deserialize(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
outSlice[i] = output
|
||||
}
|
||||
|
||||
// Populate the new Packet object
|
||||
newPsbt := Packet{
|
||||
UnsignedTx: msgTx,
|
||||
Inputs: inSlice,
|
||||
Outputs: outSlice,
|
||||
Unknowns: unknownSlice,
|
||||
}
|
||||
|
||||
// Extended sanity checking is applied here to make sure the
|
||||
// externally-passed Packet follows all the rules.
|
||||
if err = newPsbt.SanityCheck(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &newPsbt, nil
|
||||
}
|
||||
|
||||
// Serialize creates a binary serialization of the referenced Packet struct
|
||||
// with lexicographical ordering (by key) of the subsections.
|
||||
func (p *Packet) Serialize(w io.Writer) error {
|
||||
|
||||
// First we write out the precise set of magic bytes that identify a
|
||||
// valid PSBT transaction.
|
||||
if _, err := w.Write(psbtMagic[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Next we prep to write out the unsigned transaction by first
|
||||
// serializing it into an intermediate buffer.
|
||||
serializedTx := bytes.NewBuffer(
|
||||
make([]byte, 0, p.UnsignedTx.SerializeSize()),
|
||||
)
|
||||
if err := p.UnsignedTx.Serialize(serializedTx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now that we have the serialized transaction, we'll write it out to
|
||||
// the proper global type.
|
||||
err := serializeKVPairWithType(
|
||||
w, uint8(UnsignedTxType), nil, serializedTx.Bytes(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// With that our global section is done, so we'll write out the
|
||||
// separator.
|
||||
separator := []byte{0x00}
|
||||
if _, err := w.Write(separator); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pInput := range p.Inputs {
|
||||
err := pInput.serialize(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write(separator); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, pOutput := range p.Outputs {
|
||||
err := pOutput.serialize(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write(separator); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// B64Encode returns the base64 encoding of the serialization of
|
||||
// the current PSBT, or an error if the encoding fails.
|
||||
func (p *Packet) B64Encode() (string, error) {
|
||||
var b bytes.Buffer
|
||||
if err := p.Serialize(&b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(b.Bytes()), nil
|
||||
}
|
||||
|
||||
// IsComplete returns true only if all of the inputs are
|
||||
// finalized; this is particularly important in that it decides
|
||||
// whether the final extraction to a network serialized signed
|
||||
// transaction will be possible.
|
||||
func (p *Packet) IsComplete() bool {
|
||||
for i := 0; i < len(p.UnsignedTx.TxIn); i++ {
|
||||
if !isFinalized(p, i) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SanityCheck checks conditions on a PSBT to ensure that it obeys the
|
||||
// rules of BIP174, and returns true if so, false if not.
|
||||
func (p *Packet) SanityCheck() error {
|
||||
|
||||
if !validateUnsignedTX(p.UnsignedTx) {
|
||||
return ErrInvalidRawTxSigned
|
||||
}
|
||||
|
||||
for _, tin := range p.Inputs {
|
||||
if !tin.IsSane() {
|
||||
return ErrInvalidPsbtFormat
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
// Copyright (c) 2018 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package psbt
|
||||
|
||||
// signer encapsulates the role 'Signer' as specified in BIP174; it controls
|
||||
// the insertion of signatures; the Sign() function will attempt to insert
|
||||
// signatures using Updater.addPartialSignature, after first ensuring the Psbt
|
||||
// is in the correct state.
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
)
|
||||
|
||||
// SignOutcome is a enum-like value that expresses the outcome of a call to the
|
||||
// Sign method.
|
||||
type SignOutcome int
|
||||
|
||||
const (
|
||||
// SignSuccesful indicates that the partial signature was successfully
|
||||
// attached.
|
||||
SignSuccesful = 0
|
||||
|
||||
// SignFinalized indicates that this input is already finalized, so the provided
|
||||
// signature was *not* attached
|
||||
SignFinalized = 1
|
||||
|
||||
// SignInvalid indicates that the provided signature data was not valid. In this case
|
||||
// an error will also be returned.
|
||||
SignInvalid = -1
|
||||
)
|
||||
|
||||
// Sign allows the caller to sign a PSBT at a particular input; they
|
||||
// must provide a signature and a pubkey, both as byte slices; they can also
|
||||
// optionally provide both witnessScript and/or redeemScript, otherwise these
|
||||
// arguments must be set as nil (and in that case, they must already be present
|
||||
// in the PSBT if required for signing to succeed).
|
||||
//
|
||||
// This serves as a wrapper around Updater.addPartialSignature; it ensures that
|
||||
// the redeemScript and witnessScript are updated as needed (note that the
|
||||
// Updater is allowed to add redeemScripts and witnessScripts independently,
|
||||
// before signing), and ensures that the right form of utxo field
|
||||
// (NonWitnessUtxo or WitnessUtxo) is included in the input so that signature
|
||||
// insertion (and then finalization) can take place.
|
||||
func (u *Updater) Sign(inIndex int, sig []byte, pubKey []byte,
|
||||
redeemScript []byte, witnessScript []byte) (SignOutcome, error) {
|
||||
|
||||
if isFinalized(u.Upsbt, inIndex) {
|
||||
return SignFinalized, nil
|
||||
}
|
||||
|
||||
// Add the witnessScript to the PSBT in preparation. If it already
|
||||
// exists, it will be overwritten.
|
||||
if witnessScript != nil {
|
||||
err := u.AddInWitnessScript(witnessScript, inIndex)
|
||||
if err != nil {
|
||||
return SignInvalid, err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the redeemScript to the PSBT in preparation. If it already
|
||||
// exists, it will be overwritten.
|
||||
if redeemScript != nil {
|
||||
err := u.AddInRedeemScript(redeemScript, inIndex)
|
||||
if err != nil {
|
||||
return SignInvalid, err
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, the PSBT must have the requisite witnessScript or
|
||||
// redeemScript fields for signing to succeed.
|
||||
//
|
||||
// Case 1: if witnessScript is present, it must be of type witness;
|
||||
// if not, signature insertion will of course fail.
|
||||
switch {
|
||||
case u.Upsbt.Inputs[inIndex].WitnessScript != nil:
|
||||
if u.Upsbt.Inputs[inIndex].WitnessUtxo == nil {
|
||||
err := nonWitnessToWitness(u.Upsbt, inIndex)
|
||||
if err != nil {
|
||||
return SignInvalid, err
|
||||
}
|
||||
}
|
||||
|
||||
err := u.addPartialSignature(inIndex, sig, pubKey)
|
||||
if err != nil {
|
||||
return SignInvalid, err
|
||||
}
|
||||
|
||||
// Case 2: no witness script, only redeem script; can be legacy p2sh or
|
||||
// p2sh-wrapped p2wkh.
|
||||
case u.Upsbt.Inputs[inIndex].RedeemScript != nil:
|
||||
// We only need to decide if the input is witness, and we don't
|
||||
// rely on the witnessutxo/nonwitnessutxo in the PSBT, instead
|
||||
// we check the redeemScript content.
|
||||
if txscript.IsWitnessProgram(redeemScript) {
|
||||
if u.Upsbt.Inputs[inIndex].WitnessUtxo == nil {
|
||||
err := nonWitnessToWitness(u.Upsbt, inIndex)
|
||||
if err != nil {
|
||||
return SignInvalid, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If it is not a valid witness program, we here assume that
|
||||
// the provided WitnessUtxo/NonWitnessUtxo field was correct.
|
||||
err := u.addPartialSignature(inIndex, sig, pubKey)
|
||||
if err != nil {
|
||||
return SignInvalid, err
|
||||
}
|
||||
|
||||
// Case 3: Neither provided only works for native p2wkh, or non-segwit
|
||||
// non-p2sh. To check if it's segwit, check the scriptPubKey of the
|
||||
// output.
|
||||
default:
|
||||
if u.Upsbt.Inputs[inIndex].WitnessUtxo == nil {
|
||||
outIndex := u.Upsbt.UnsignedTx.TxIn[inIndex].PreviousOutPoint.Index
|
||||
script := u.Upsbt.Inputs[inIndex].NonWitnessUtxo.TxOut[outIndex].PkScript
|
||||
|
||||
if txscript.IsWitnessProgram(script) {
|
||||
err := nonWitnessToWitness(u.Upsbt, inIndex)
|
||||
if err != nil {
|
||||
return SignInvalid, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := u.addPartialSignature(inIndex, sig, pubKey)
|
||||
if err != nil {
|
||||
return SignInvalid, err
|
||||
}
|
||||
}
|
||||
|
||||
return SignSuccesful, nil
|
||||
}
|
||||
|
||||
// nonWitnessToWitness extracts the TxOut from the existing NonWitnessUtxo
|
||||
// field in the given PSBT input and sets it as type witness by replacing the
|
||||
// NonWitnessUtxo field with a WitnessUtxo field. See
|
||||
// https://github.com/bitcoin/bitcoin/pull/14197.
|
||||
func nonWitnessToWitness(p *Packet, inIndex int) error {
|
||||
outIndex := p.UnsignedTx.TxIn[inIndex].PreviousOutPoint.Index
|
||||
txout := p.Inputs[inIndex].NonWitnessUtxo.TxOut[outIndex]
|
||||
|
||||
// Remove the non-witness first, else sanity check will not pass:
|
||||
p.Inputs[inIndex].NonWitnessUtxo = nil
|
||||
u := Updater{
|
||||
Upsbt: p,
|
||||
}
|
||||
|
||||
return u.AddInWitnessUtxo(txout, inIndex)
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
package psbt
|
||||
|
||||
// GlobalType is the set of types that are used at the global scope level
|
||||
// within the PSBT.
|
||||
type GlobalType uint8
|
||||
|
||||
const (
|
||||
// UnsignedTxType is the global scope key that houses the unsigned
|
||||
// transaction of the PSBT. The value is a transaction in network
|
||||
// serialization. The scriptSigs and witnesses for each input must be
|
||||
// empty. The transaction must be in the old serialization format
|
||||
// (without witnesses). A PSBT must have a transaction, otherwise it is
|
||||
// invalid.
|
||||
UnsignedTxType GlobalType = 0
|
||||
|
||||
// XpubType houses a global xpub for the entire PSBT packet.
|
||||
//
|
||||
// The key ({0x01}|{xpub}) is he 78 byte serialized extended public key
|
||||
// as defined by BIP 32. Extended public keys are those that can be
|
||||
// used to derive public keys used in the inputs and outputs of this
|
||||
// transaction. It should be the public key at the highest hardened
|
||||
// derivation index so that
|
||||
// the unhardened child keys used in the transaction can be derived.
|
||||
//
|
||||
// The value is the master key fingerprint as defined by BIP 32
|
||||
// concatenated with the derivation path of the public key. The
|
||||
// derivation path is represented as 32-bit little endian unsigned
|
||||
// integer indexes concatenated with each other. The number of 32 bit
|
||||
// unsigned integer indexes must match the depth provided in the
|
||||
// extended public key.
|
||||
XpubType GlobalType = 1
|
||||
|
||||
// VersionType houses the global version number of this PSBT. There is
|
||||
// no key (only contains the byte type), then the value if omitted, is
|
||||
// assumed to be zero.
|
||||
VersionType GlobalType = 0xFB
|
||||
|
||||
// ProprietaryGlobalType is used to house any proper chary global-scope
|
||||
// keys within the PSBT.
|
||||
//
|
||||
// The key is ({0xFC}|<prefix>|{subtype}|{key data}) a variable length
|
||||
// identifier prefix, followed by a subtype, followed by the key data
|
||||
// itself.
|
||||
//
|
||||
// The value is any data as defined by the proprietary type user.
|
||||
ProprietaryGlobalType = 0xFC
|
||||
)
|
||||
|
||||
// InputType is the set of types that are defined for each input included
|
||||
// within the PSBT.
|
||||
type InputType uint32
|
||||
|
||||
const (
|
||||
// NonWitnessUtxoType has no key ({0x00}) and houses the transaction in
|
||||
// network serialization format the current input spends from. This
|
||||
// should only be present for inputs which spend non-segwit outputs.
|
||||
// However, if it is unknown whether an input spends a segwit output,
|
||||
// this type should be used. The entire input transaction is needed in
|
||||
// order to be able to verify the values of the input (pre-segwit they
|
||||
// aren't in the signature digest).
|
||||
NonWitnessUtxoType InputType = 0
|
||||
|
||||
// WitnessUtxoType has no key ({0x01}), and houses the entire
|
||||
// transaction output in network serialization which the current input
|
||||
// spends from. This should only be present for inputs which spend
|
||||
// segwit outputs, including P2SH embedded ones (value || script).
|
||||
WitnessUtxoType InputType = 1
|
||||
|
||||
// PartialSigType is used to include a partial signature with key
|
||||
// ({0x02}|{public key}).
|
||||
//
|
||||
// The value is the signature as would be pushed to the stack from a
|
||||
// scriptSig or witness..
|
||||
PartialSigType InputType = 2
|
||||
|
||||
// SighashType is an empty key ({0x03}).
|
||||
//
|
||||
// The value contains the 32-bit unsigned integer specifying the
|
||||
// sighash type to be used for this input. Signatures for this input
|
||||
// must use the sighash type, finalizers must fail to finalize inputs
|
||||
// which have signatures that do not match the specified sighash type.
|
||||
// Signers who cannot produce signatures with the sighash type must not
|
||||
// provide a signature.
|
||||
SighashType InputType = 3
|
||||
|
||||
// RedeemScriptInputType is an empty key ({0x40}).
|
||||
//
|
||||
// The value is the redeem script of the input if present.
|
||||
RedeemScriptInputType InputType = 4
|
||||
|
||||
// WitnessScriptInputType is an empty key ({0x05}).
|
||||
//
|
||||
// The value is the witness script of this input, if it has one.
|
||||
WitnessScriptInputType InputType = 5
|
||||
|
||||
// Bip32DerivationInputType is a type that carries the pubkey along
|
||||
// with the key ({0x06}|{public key}).
|
||||
//
|
||||
// The value is master key fingerprint as defined by BIP 32
|
||||
// concatenated with the derivation path of the public key. The
|
||||
// derivation path is represented as 32 bit unsigned integer indexes
|
||||
// concatenated with each other. Public keys are those that will be
|
||||
// needed to sign this input.
|
||||
Bip32DerivationInputType InputType = 6
|
||||
|
||||
// FinalScriptSigType is an empty key ({0x07}).
|
||||
//
|
||||
// The value contains a fully constructed scriptSig with signatures and
|
||||
// any other scripts necessary for the input to pass validation.
|
||||
FinalScriptSigType InputType = 7
|
||||
|
||||
// FinalScriptWitnessType is an empty key ({0x08}). The value is a
|
||||
// fully constructed scriptWitness with signatures and any other
|
||||
// scripts necessary for the input to pass validation.
|
||||
FinalScriptWitnessType InputType = 8
|
||||
|
||||
// ProprietaryInputType is a custom type for use by devs.
|
||||
//
|
||||
// The key ({0xFC}|<prefix>|{subtype}|{key data}), is a Variable length
|
||||
// identifier prefix, followed by a subtype, followed by the key data
|
||||
// itself.
|
||||
//
|
||||
// The value is any value data as defined by the proprietary type user.
|
||||
ProprietaryInputType InputType = 0xFC
|
||||
)
|
||||
|
||||
// OutputType is the set of types defined per output within the PSBT.
|
||||
type OutputType uint32
|
||||
|
||||
const (
|
||||
// RedeemScriptOutputType is an empty key ({0x00}>
|
||||
//
|
||||
// The value is the redeemScript for this output if it has one.
|
||||
RedeemScriptOutputType OutputType = 0
|
||||
|
||||
// WitnessScriptOutputType is an empty key ({0x01}).
|
||||
//
|
||||
// The value is the witness script of this input, if it has one.
|
||||
WitnessScriptOutputType OutputType = 1
|
||||
|
||||
j // Bip32DerivationOutputType is used to communicate derivation information
|
||||
// needed to spend this output. The key is ({0x02}|{public key}).
|
||||
//
|
||||
// The value is master key fingerprint concatenated with the derivation
|
||||
// path of the public key. The derivation path is represented as 32-bit
|
||||
// little endian unsigned integer indexes concatenated with each other.
|
||||
// Public keys are those needed to spend this output.
|
||||
Bip32DerivationOutputType OutputType = 2
|
||||
)
|
|
@ -0,0 +1,367 @@
|
|||
// Copyright (c) 2018 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package psbt
|
||||
|
||||
// The Updater requires provision of a single PSBT and is able to add data to
|
||||
// both input and output sections. It can be called repeatedly to add more
|
||||
// data. It also allows addition of signatures via the addPartialSignature
|
||||
// function; this is called internally to the package in the Sign() function of
|
||||
// Updater, located in signer.go
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
// Updater encapsulates the role 'Updater' as specified in BIP174; it accepts
|
||||
// Psbt structs and has methods to add fields to the inputs and outputs.
|
||||
type Updater struct {
|
||||
Upsbt *Packet
|
||||
}
|
||||
|
||||
// NewUpdater returns a new instance of Updater, if the passed Psbt struct is
|
||||
// in a valid form, else an error.
|
||||
func NewUpdater(p *Packet) (*Updater, error) {
|
||||
if err := p.SanityCheck(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Updater{Upsbt: p}, nil
|
||||
|
||||
}
|
||||
|
||||
// AddInNonWitnessUtxo adds the utxo information for an input which is
|
||||
// non-witness. This requires provision of a full transaction (which is the
|
||||
// source of the corresponding prevOut), and the input index. If addition of
|
||||
// this key-value pair to the Psbt fails, an error is returned.
|
||||
func (p *Updater) AddInNonWitnessUtxo(tx *wire.MsgTx, inIndex int) error {
|
||||
if inIndex > len(p.Upsbt.Inputs)-1 {
|
||||
return ErrInvalidPrevOutNonWitnessTransaction
|
||||
}
|
||||
|
||||
p.Upsbt.Inputs[inIndex].NonWitnessUtxo = tx
|
||||
|
||||
if err := p.Upsbt.SanityCheck(); err != nil {
|
||||
return ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddInWitnessUtxo adds the utxo information for an input which is witness.
|
||||
// This requires provision of a full transaction *output* (which is the source
|
||||
// of the corresponding prevOut); not the full transaction because BIP143 means
|
||||
// the output information is sufficient, and the input index. If addition of
|
||||
// this key-value pair to the Psbt fails, an error is returned.
|
||||
func (p *Updater) AddInWitnessUtxo(txout *wire.TxOut, inIndex int) error {
|
||||
if inIndex > len(p.Upsbt.Inputs)-1 {
|
||||
return ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
p.Upsbt.Inputs[inIndex].WitnessUtxo = txout
|
||||
|
||||
if err := p.Upsbt.SanityCheck(); err != nil {
|
||||
return ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addPartialSignature allows the Updater role to insert fields of type partial
|
||||
// signature into a Psbt, consisting of both the pubkey (as keydata) and the
|
||||
// ECDSA signature (as value). Note that the Signer role is encapsulated in
|
||||
// this function; signatures are only allowed to be added that follow the
|
||||
// sanity-check on signing rules explained in the BIP under `Signer`; if the
|
||||
// rules are not satisfied, an ErrInvalidSignatureForInput is returned.
|
||||
//
|
||||
// NOTE: This function does *not* validate the ECDSA signature itself.
|
||||
func (p *Updater) addPartialSignature(inIndex int, sig []byte,
|
||||
pubkey []byte) error {
|
||||
|
||||
partialSig := PartialSig{
|
||||
PubKey: pubkey, Signature: sig,
|
||||
}
|
||||
|
||||
// First validate the passed (sig, pub).
|
||||
if !partialSig.checkValid() {
|
||||
return ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
pInput := p.Upsbt.Inputs[inIndex]
|
||||
|
||||
// First check; don't add duplicates.
|
||||
for _, x := range pInput.PartialSigs {
|
||||
if bytes.Equal(x.PubKey, partialSig.PubKey) {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
}
|
||||
|
||||
// Next, we perform a series of additional sanity checks.
|
||||
if pInput.NonWitnessUtxo != nil {
|
||||
if len(p.Upsbt.UnsignedTx.TxIn) < inIndex+1 {
|
||||
return ErrInvalidPrevOutNonWitnessTransaction
|
||||
}
|
||||
|
||||
if pInput.NonWitnessUtxo.TxHash() !=
|
||||
p.Upsbt.UnsignedTx.TxIn[inIndex].PreviousOutPoint.Hash {
|
||||
return ErrInvalidSignatureForInput
|
||||
}
|
||||
|
||||
// To validate that the redeem script matches, we must pull out
|
||||
// the scriptPubKey of the corresponding output and compare
|
||||
// that with the P2SH scriptPubKey that is generated by
|
||||
// redeemScript.
|
||||
if pInput.RedeemScript != nil {
|
||||
outIndex := p.Upsbt.UnsignedTx.TxIn[inIndex].PreviousOutPoint.Index
|
||||
scriptPubKey := pInput.NonWitnessUtxo.TxOut[outIndex].PkScript
|
||||
scriptHash := btcutil.Hash160(pInput.RedeemScript)
|
||||
|
||||
scriptHashScript, err := txscript.NewScriptBuilder().
|
||||
AddOp(txscript.OP_HASH160).
|
||||
AddData(scriptHash).
|
||||
AddOp(txscript.OP_EQUAL).
|
||||
Script()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !bytes.Equal(scriptHashScript, scriptPubKey) {
|
||||
return ErrInvalidSignatureForInput
|
||||
}
|
||||
}
|
||||
|
||||
} else if pInput.WitnessUtxo != nil {
|
||||
scriptPubKey := pInput.WitnessUtxo.PkScript
|
||||
|
||||
var script []byte
|
||||
if pInput.RedeemScript != nil {
|
||||
scriptHash := btcutil.Hash160(pInput.RedeemScript)
|
||||
scriptHashScript, err := txscript.NewScriptBuilder().
|
||||
AddOp(txscript.OP_HASH160).
|
||||
AddData(scriptHash).
|
||||
AddOp(txscript.OP_EQUAL).
|
||||
Script()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !bytes.Equal(scriptHashScript, scriptPubKey) {
|
||||
return ErrInvalidSignatureForInput
|
||||
}
|
||||
|
||||
script = pInput.RedeemScript
|
||||
} else {
|
||||
script = scriptPubKey
|
||||
}
|
||||
|
||||
// If a witnessScript field is present, this is a P2WSH,
|
||||
// whether nested or not (that is handled by the assignment to
|
||||
// `script` above); in that case, sanity check that `script` is
|
||||
// the p2wsh of witnessScript. Contrariwise, if no
|
||||
// witnessScript field is present, this will be signed as
|
||||
// p2wkh.
|
||||
if pInput.WitnessScript != nil {
|
||||
witnessScriptHash := sha256.Sum256(pInput.WitnessScript)
|
||||
witnessScriptHashScript, err := txscript.NewScriptBuilder().
|
||||
AddOp(txscript.OP_0).
|
||||
AddData(witnessScriptHash[:]).
|
||||
Script()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !bytes.Equal(script, witnessScriptHashScript[:]) {
|
||||
return ErrInvalidSignatureForInput
|
||||
}
|
||||
} else {
|
||||
// Otherwise, this is a p2wkh input.
|
||||
pubkeyHash := btcutil.Hash160(pubkey)
|
||||
pubkeyHashScript, err := txscript.NewScriptBuilder().
|
||||
AddOp(txscript.OP_0).
|
||||
AddData(pubkeyHash).
|
||||
Script()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate that we're able to properly reconstruct the
|
||||
// witness program.
|
||||
if !bytes.Equal(pubkeyHashScript, script) {
|
||||
return ErrInvalidSignatureForInput
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// Attaching signature without utxo field is not allowed.
|
||||
return ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
p.Upsbt.Inputs[inIndex].PartialSigs = append(
|
||||
p.Upsbt.Inputs[inIndex].PartialSigs, &partialSig,
|
||||
)
|
||||
|
||||
if err := p.Upsbt.SanityCheck(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Addition of a non-duplicate-key partial signature cannot violate
|
||||
// sanity-check rules.
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddInSighashType adds the sighash type information for an input. The
|
||||
// sighash type is passed as a 32 bit unsigned integer, along with the index
|
||||
// for the input. An error is returned if addition of this key-value pair to
|
||||
// the Psbt fails.
|
||||
func (p *Updater) AddInSighashType(sighashType txscript.SigHashType,
|
||||
inIndex int) error {
|
||||
|
||||
p.Upsbt.Inputs[inIndex].SighashType = sighashType
|
||||
|
||||
if err := p.Upsbt.SanityCheck(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddInRedeemScript adds the redeem script information for an input. The
|
||||
// redeem script is passed serialized, as a byte slice, along with the index of
|
||||
// the input. An error is returned if addition of this key-value pair to the
|
||||
// Psbt fails.
|
||||
func (p *Updater) AddInRedeemScript(redeemScript []byte,
|
||||
inIndex int) error {
|
||||
|
||||
p.Upsbt.Inputs[inIndex].RedeemScript = redeemScript
|
||||
|
||||
if err := p.Upsbt.SanityCheck(); err != nil {
|
||||
return ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddInWitnessScript adds the witness script information for an input. The
|
||||
// witness script is passed serialized, as a byte slice, along with the index
|
||||
// of the input. An error is returned if addition of this key-value pair to the
|
||||
// Psbt fails.
|
||||
func (p *Updater) AddInWitnessScript(witnessScript []byte,
|
||||
inIndex int) error {
|
||||
|
||||
p.Upsbt.Inputs[inIndex].WitnessScript = witnessScript
|
||||
|
||||
if err := p.Upsbt.SanityCheck(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddInBip32Derivation takes a master key fingerprint as defined in BIP32, a
|
||||
// BIP32 path as a slice of uint32 values, and a serialized pubkey as a byte
|
||||
// slice, along with the integer index of the input, and inserts this data into
|
||||
// that input.
|
||||
//
|
||||
// NOTE: This can be called multiple times for the same input. An error is
|
||||
// returned if addition of this key-value pair to the Psbt fails.
|
||||
func (p *Updater) AddInBip32Derivation(masterKeyFingerprint uint32,
|
||||
bip32Path []uint32, pubKeyData []byte, inIndex int) error {
|
||||
|
||||
bip32Derivation := Bip32Derivation{
|
||||
PubKey: pubKeyData,
|
||||
MasterKeyFingerprint: masterKeyFingerprint,
|
||||
Bip32Path: bip32Path,
|
||||
}
|
||||
|
||||
if !bip32Derivation.checkValid() {
|
||||
return ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
// Don't allow duplicate keys
|
||||
for _, x := range p.Upsbt.Inputs[inIndex].Bip32Derivation {
|
||||
if bytes.Equal(x.PubKey, bip32Derivation.PubKey) {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
}
|
||||
|
||||
p.Upsbt.Inputs[inIndex].Bip32Derivation = append(
|
||||
p.Upsbt.Inputs[inIndex].Bip32Derivation, &bip32Derivation,
|
||||
)
|
||||
|
||||
if err := p.Upsbt.SanityCheck(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddOutBip32Derivation takes a master key fingerprint as defined in BIP32, a
|
||||
// BIP32 path as a slice of uint32 values, and a serialized pubkey as a byte
|
||||
// slice, along with the integer index of the output, and inserts this data
|
||||
// into that output.
|
||||
//
|
||||
// NOTE: That this can be called multiple times for the same output. An error
|
||||
// is returned if addition of this key-value pair to the Psbt fails.
|
||||
func (p *Updater) AddOutBip32Derivation(masterKeyFingerprint uint32,
|
||||
bip32Path []uint32, pubKeyData []byte, outIndex int) error {
|
||||
|
||||
bip32Derivation := Bip32Derivation{
|
||||
PubKey: pubKeyData,
|
||||
MasterKeyFingerprint: masterKeyFingerprint,
|
||||
Bip32Path: bip32Path,
|
||||
}
|
||||
|
||||
if !bip32Derivation.checkValid() {
|
||||
return ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
// Don't allow duplicate keys
|
||||
for _, x := range p.Upsbt.Outputs[outIndex].Bip32Derivation {
|
||||
if bytes.Equal(x.PubKey, bip32Derivation.PubKey) {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
}
|
||||
|
||||
p.Upsbt.Outputs[outIndex].Bip32Derivation = append(
|
||||
p.Upsbt.Outputs[outIndex].Bip32Derivation, &bip32Derivation,
|
||||
)
|
||||
|
||||
if err := p.Upsbt.SanityCheck(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddOutRedeemScript takes a redeem script as a byte slice and appends it to
|
||||
// the output at index outIndex.
|
||||
func (p *Updater) AddOutRedeemScript(redeemScript []byte,
|
||||
outIndex int) error {
|
||||
|
||||
p.Upsbt.Outputs[outIndex].RedeemScript = redeemScript
|
||||
|
||||
if err := p.Upsbt.SanityCheck(); err != nil {
|
||||
return ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddOutWitnessScript takes a witness script as a byte slice and appends it to
|
||||
// the output at index outIndex.
|
||||
func (p *Updater) AddOutWitnessScript(witnessScript []byte,
|
||||
outIndex int) error {
|
||||
|
||||
p.Upsbt.Outputs[outIndex].WitnessScript = witnessScript
|
||||
|
||||
if err := p.Upsbt.SanityCheck(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,277 @@
|
|||
// Copyright (c) 2018 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package psbt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// writeTxWitness is a A utility function due to non-exported witness
|
||||
// serialization (writeTxWitness encodes the bitcoin protocol encoding for a
|
||||
// transaction input's witness into w).
|
||||
func writeTxWitness(w io.Writer, wit [][]byte) error {
|
||||
if err := wire.WriteVarInt(w, 0, uint64(len(wit))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range wit {
|
||||
err := wire.WriteVarBytes(w, 0, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writePKHWitness writes a witness for a p2wkh spending input
|
||||
func writePKHWitness(sig []byte, pub []byte) ([]byte, error) {
|
||||
var (
|
||||
buf bytes.Buffer
|
||||
witnessItems = [][]byte{sig, pub}
|
||||
)
|
||||
|
||||
if err := writeTxWitness(&buf, witnessItems); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// checkIsMultisigScript is a utility function to check whether a given
|
||||
// redeemscript fits the standard multisig template used in all P2SH based
|
||||
// multisig, given a set of pubkeys for redemption.
|
||||
func checkIsMultiSigScript(pubKeys [][]byte, sigs [][]byte,
|
||||
script []byte) bool {
|
||||
|
||||
// First insist that the script type is multisig.
|
||||
if txscript.GetScriptClass(script) != txscript.MultiSigTy {
|
||||
return false
|
||||
}
|
||||
|
||||
// Inspect the script to ensure that the number of sigs and pubkeys is
|
||||
// correct
|
||||
numSigs, numPubKeys, err := txscript.CalcMultiSigStats(script)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the number of sigs provided, doesn't match the number of required
|
||||
// pubkeys, then we can't proceed as we're not yet final.
|
||||
if numPubKeys != len(pubKeys) || numSigs != len(sigs) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// extractKeyOrderFromScript is a utility function to extract an ordered list
|
||||
// of signatures, given a serialized script (redeemscript or witness script), a
|
||||
// list of pubkeys and the signatures corresponding to those pubkeys. This
|
||||
// function is used to ensure that the signatures will be embedded in the final
|
||||
// scriptSig or scriptWitness in the correct order.
|
||||
func extractKeyOrderFromScript(script []byte, expectedPubkeys [][]byte,
|
||||
sigs [][]byte) ([][]byte, error) {
|
||||
|
||||
// If this isn't a proper finalized multi-sig script, then we can't
|
||||
// proceed.
|
||||
if !checkIsMultiSigScript(expectedPubkeys, sigs, script) {
|
||||
return nil, ErrUnsupportedScriptType
|
||||
}
|
||||
|
||||
// Arrange the pubkeys and sigs into a slice of format:
|
||||
// * [[pub,sig], [pub,sig],..]
|
||||
type sigWithPub struct {
|
||||
pubKey []byte
|
||||
sig []byte
|
||||
}
|
||||
var pubsSigs []sigWithPub
|
||||
for i, pub := range expectedPubkeys {
|
||||
pubsSigs = append(pubsSigs, sigWithPub{
|
||||
pubKey: pub,
|
||||
sig: sigs[i],
|
||||
})
|
||||
}
|
||||
|
||||
// Now that we have the set of (pubkey, sig) pairs, we'll construct a
|
||||
// position map that we can use to swap the order in the slice above to
|
||||
// match how things are laid out in the script.
|
||||
type positionEntry struct {
|
||||
index int
|
||||
value sigWithPub
|
||||
}
|
||||
var positionMap []positionEntry
|
||||
|
||||
// For each pubkey in our pubsSigs slice, we'll now construct a proper
|
||||
// positionMap entry, based on _where_ in the script the pubkey first
|
||||
// appears.
|
||||
for _, p := range pubsSigs {
|
||||
pos := bytes.Index(script, p.pubKey)
|
||||
if pos < 0 {
|
||||
return nil, errors.New("script does not contain pubkeys")
|
||||
}
|
||||
|
||||
positionMap = append(positionMap, positionEntry{
|
||||
index: pos,
|
||||
value: p,
|
||||
})
|
||||
}
|
||||
|
||||
// Now that we have the position map full populated, we'll use the
|
||||
// index data to properly sort the entries in the map based on where
|
||||
// they appear in the script.
|
||||
sort.Slice(positionMap, func(i, j int) bool {
|
||||
return positionMap[i].index < positionMap[j].index
|
||||
})
|
||||
|
||||
// Finally, we can simply iterate through the position map in order to
|
||||
// extract the proper signature ordering.
|
||||
sortedSigs := make([][]byte, 0, len(positionMap))
|
||||
for _, x := range positionMap {
|
||||
sortedSigs = append(sortedSigs, x.value.sig)
|
||||
}
|
||||
|
||||
return sortedSigs, nil
|
||||
}
|
||||
|
||||
// getMultisigScriptWitness creates a full psbt serialized Witness field for
|
||||
// the transaction, given the public keys and signatures to be appended. This
|
||||
// function will only accept witnessScripts of the type M of N multisig. This
|
||||
// is used for both p2wsh and nested p2wsh multisig cases.
|
||||
func getMultisigScriptWitness(witnessScript []byte, pubKeys [][]byte,
|
||||
sigs [][]byte) ([]byte, error) {
|
||||
|
||||
// First using the script as a guide, we'll properly order the sigs
|
||||
// according to how their corresponding pubkeys appear in the
|
||||
// witnessScript.
|
||||
orderedSigs, err := extractKeyOrderFromScript(
|
||||
witnessScript, pubKeys, sigs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now that we know the proper order, we'll append each of the
|
||||
// signatures into a new witness stack, then top it off with the
|
||||
// witness script at the end, prepending the nil as we need the extra
|
||||
// pop..
|
||||
witnessElements := make(wire.TxWitness, 0, len(sigs)+2)
|
||||
witnessElements = append(witnessElements, nil)
|
||||
for _, os := range orderedSigs {
|
||||
witnessElements = append(witnessElements, os)
|
||||
}
|
||||
witnessElements = append(witnessElements, witnessScript)
|
||||
|
||||
// Now that we have the full witness stack, we'll serialize it in the
|
||||
// expected format, and return the final bytes.
|
||||
var buf bytes.Buffer
|
||||
if err = writeTxWitness(&buf, witnessElements); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// checkSigHashFlags compares the sighash flag byte on a signature with the
|
||||
// value expected according to any PsbtInSighashType field in this section of
|
||||
// the PSBT, and returns true if they match, false otherwise.
|
||||
// If no SighashType field exists, it is assumed to be SIGHASH_ALL.
|
||||
//
|
||||
// TODO(waxwing): sighash type not restricted to one byte in future?
|
||||
func checkSigHashFlags(sig []byte, input *PInput) bool {
|
||||
expectedSighashType := txscript.SigHashAll
|
||||
if input.SighashType != 0 {
|
||||
expectedSighashType = input.SighashType
|
||||
}
|
||||
|
||||
return expectedSighashType == txscript.SigHashType(sig[len(sig)-1])
|
||||
}
|
||||
|
||||
// serializeKVpair writes out a kv pair using a varbyte prefix for each.
|
||||
func serializeKVpair(w io.Writer, key []byte, value []byte) error {
|
||||
if err := wire.WriteVarBytes(w, 0, key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wire.WriteVarBytes(w, 0, value)
|
||||
}
|
||||
|
||||
// serializeKVPairWithType writes out to the passed writer a type coupled with
|
||||
// a key.
|
||||
func serializeKVPairWithType(w io.Writer, kt uint8, keydata []byte,
|
||||
value []byte) error {
|
||||
|
||||
// If the key has no data, then we write a blank slice.
|
||||
if keydata == nil {
|
||||
keydata = []byte{}
|
||||
}
|
||||
|
||||
// The final key to be written is: {type} || {keyData}
|
||||
serializedKey := append([]byte{byte(kt)}, keydata...)
|
||||
return serializeKVpair(w, serializedKey, value)
|
||||
}
|
||||
|
||||
// getKey retrieves a single key - both the key type and the keydata (if
|
||||
// present) from the stream and returns the key type as an integer, or -1 if
|
||||
// the key was of zero length. This integer is is used to indicate the presence
|
||||
// of a separator byte which indicates the end of a given key-value pair list,
|
||||
// and the keydata as a byte slice or nil if none is present.
|
||||
func getKey(r io.Reader) (int, []byte, error) {
|
||||
|
||||
// For the key, we read the varint separately, instead of using the
|
||||
// available ReadVarBytes, because we have a specific treatment of 0x00
|
||||
// here:
|
||||
count, err := wire.ReadVarInt(r, 0)
|
||||
if err != nil {
|
||||
return -1, nil, ErrInvalidPsbtFormat
|
||||
}
|
||||
if count == 0 {
|
||||
// A separator indicates end of key-value pair list.
|
||||
return -1, nil, nil
|
||||
}
|
||||
|
||||
// Check that we don't attempt to decode a dangerously large key.
|
||||
if count > MaxPsbtKeyLength {
|
||||
return -1, nil, ErrInvalidKeydata
|
||||
}
|
||||
|
||||
// Next, we ready out the designated number of bytes, which may include
|
||||
// a type, key, and optional data.
|
||||
keyTypeAndData := make([]byte, count)
|
||||
if _, err := io.ReadFull(r, keyTypeAndData[:]); err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
|
||||
keyType := int(string(keyTypeAndData)[0])
|
||||
|
||||
// Note that the second return value will usually be empty, since most
|
||||
// keys contain no more than the key type byte.
|
||||
if len(keyTypeAndData) == 1 {
|
||||
return keyType, nil, nil
|
||||
}
|
||||
|
||||
// Otherwise, we return the key, along with any data that it may
|
||||
// contain.
|
||||
return keyType, keyTypeAndData[1:], nil
|
||||
|
||||
}
|
||||
|
||||
// readTxOut is a limited version of wire.ReadTxOut, because the latter is not
|
||||
// exported.
|
||||
func readTxOut(txout []byte) (*wire.TxOut, error) {
|
||||
if len(txout) < 10 {
|
||||
return nil, ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
valueSer := binary.LittleEndian.Uint64(txout[:8])
|
||||
scriptPubKey := txout[9:]
|
||||
|
||||
return wire.NewTxOut(int64(valueSer), scriptPubKey), nil
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
txsort
|
||||
======
|
||||
|
||||
[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)](https://travis-ci.org/btcsuite/btcutil)
|
||||
[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
|
||||
[![GoDoc](http://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/btcsuite/btcutil/txsort)
|
||||
|
||||
Package txsort provides the transaction sorting according to [BIP 69](https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki).
|
||||
|
||||
BIP 69 defines a standard lexicographical sort order of transaction inputs and
|
||||
outputs. This is useful to standardize transactions for faster multi-party
|
||||
agreement as well as preventing information leaks in a single-party use case.
|
||||
|
||||
The BIP goes into more detail, but for a quick and simplistic overview, the
|
||||
order for inputs is defined as first sorting on the previous output hash and
|
||||
then on the index as a tie breaker. The order for outputs is defined as first
|
||||
sorting on the amount and then on the raw public key script bytes as a tie
|
||||
breaker.
|
||||
|
||||
A comprehensive suite of tests is provided to ensure proper functionality.
|
||||
|
||||
## Installation and Updating
|
||||
|
||||
```bash
|
||||
$ go get -u github.com/btcsuite/btcutil/txsort
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Package txsort is licensed under the [copyfree](http://copyfree.org) ISC
|
||||
License.
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package txsort provides the transaction sorting according to BIP 69.
|
||||
|
||||
Overview
|
||||
|
||||
BIP 69 defines a standard lexicographical sort order of transaction inputs and
|
||||
outputs. This is useful to standardize transactions for faster multi-party
|
||||
agreement as well as preventing information leaks in a single-party use case.
|
||||
|
||||
The BIP goes into more detail, but for a quick and simplistic overview, the
|
||||
order for inputs is defined as first sorting on the previous output hash and
|
||||
then on the index as a tie breaker. The order for outputs is defined as first
|
||||
sorting on the amount and then on the raw public key script bytes as a tie
|
||||
breaker.
|
||||
*/
|
||||
package txsort
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Provides functions for sorting tx inputs and outputs according to BIP 69
|
||||
// (https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki)
|
||||
|
||||
package txsort
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sort"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// InPlaceSort modifies the passed transaction inputs and outputs to be sorted
|
||||
// based on BIP 69.
|
||||
//
|
||||
// WARNING: This function must NOT be called with published transactions since
|
||||
// it will mutate the transaction if it's not already sorted. This can cause
|
||||
// issues if you mutate a tx in a block, for example, which would invalidate the
|
||||
// block. It could also cause cached hashes, such as in a btcutil.Tx to become
|
||||
// invalidated.
|
||||
//
|
||||
// The function should only be used if the caller is creating the transaction or
|
||||
// is otherwise 100% positive mutating will not cause adverse affects due to
|
||||
// other dependencies.
|
||||
func InPlaceSort(tx *wire.MsgTx) {
|
||||
sort.Sort(sortableInputSlice(tx.TxIn))
|
||||
sort.Sort(sortableOutputSlice(tx.TxOut))
|
||||
}
|
||||
|
||||
// Sort returns a new transaction with the inputs and outputs sorted based on
|
||||
// BIP 69. The passed transaction is not modified and the new transaction
|
||||
// might have a different hash if any sorting was done.
|
||||
func Sort(tx *wire.MsgTx) *wire.MsgTx {
|
||||
txCopy := tx.Copy()
|
||||
sort.Sort(sortableInputSlice(txCopy.TxIn))
|
||||
sort.Sort(sortableOutputSlice(txCopy.TxOut))
|
||||
return txCopy
|
||||
}
|
||||
|
||||
// IsSorted checks whether tx has inputs and outputs sorted according to BIP
|
||||
// 69.
|
||||
func IsSorted(tx *wire.MsgTx) bool {
|
||||
if !sort.IsSorted(sortableInputSlice(tx.TxIn)) {
|
||||
return false
|
||||
}
|
||||
if !sort.IsSorted(sortableOutputSlice(tx.TxOut)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type sortableInputSlice []*wire.TxIn
|
||||
type sortableOutputSlice []*wire.TxOut
|
||||
|
||||
// For SortableInputSlice and SortableOutputSlice, three functions are needed
|
||||
// to make it sortable with sort.Sort() -- Len, Less, and Swap
|
||||
// Len and Swap are trivial. Less is BIP 69 specific.
|
||||
func (s sortableInputSlice) Len() int { return len(s) }
|
||||
func (s sortableOutputSlice) Len() int { return len(s) }
|
||||
func (s sortableOutputSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s sortableInputSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// Input comparison function.
|
||||
// First sort based on input hash (reversed / rpc-style), then index.
|
||||
func (s sortableInputSlice) Less(i, j int) bool {
|
||||
// Input hashes are the same, so compare the index.
|
||||
ihash := s[i].PreviousOutPoint.Hash
|
||||
jhash := s[j].PreviousOutPoint.Hash
|
||||
if ihash == jhash {
|
||||
return s[i].PreviousOutPoint.Index < s[j].PreviousOutPoint.Index
|
||||
}
|
||||
|
||||
// At this point, the hashes are not equal, so reverse them to
|
||||
// big-endian and return the result of the comparison.
|
||||
const hashSize = chainhash.HashSize
|
||||
for b := 0; b < hashSize/2; b++ {
|
||||
ihash[b], ihash[hashSize-1-b] = ihash[hashSize-1-b], ihash[b]
|
||||
jhash[b], jhash[hashSize-1-b] = jhash[hashSize-1-b], jhash[b]
|
||||
}
|
||||
return bytes.Compare(ihash[:], jhash[:]) == -1
|
||||
}
|
||||
|
||||
// Output comparison function.
|
||||
// First sort based on amount (smallest first), then PkScript.
|
||||
func (s sortableOutputSlice) Less(i, j int) bool {
|
||||
if s[i].Value == s[j].Value {
|
||||
return bytes.Compare(s[i].PkScript, s[j].PkScript) < 0
|
||||
}
|
||||
return s[i].Value < s[j].Value
|
||||
}
|
|
@ -16,6 +16,28 @@ import (
|
|||
"github.com/lightninglabs/gozmq"
|
||||
)
|
||||
|
||||
const (
|
||||
// rawBlockZMQCommand is the command used to receive raw block
|
||||
// notifications from bitcoind through ZMQ.
|
||||
rawBlockZMQCommand = "rawblock"
|
||||
|
||||
// rawTxZMQCommand is the command used to receive raw transaction
|
||||
// notifications from bitcoind through ZMQ.
|
||||
rawTxZMQCommand = "rawtx"
|
||||
|
||||
// maxRawBlockSize is the maximum size in bytes for a raw block received
|
||||
// from bitcoind through ZMQ.
|
||||
maxRawBlockSize = 4e6
|
||||
|
||||
// maxRawTxSize is the maximum size in bytes for a raw transaction
|
||||
// received from bitcoind through ZMQ.
|
||||
maxRawTxSize = maxRawBlockSize
|
||||
|
||||
// seqNumLen is the length of the sequence number of a message sent from
|
||||
// bitcoind through ZMQ.
|
||||
seqNumLen = 4
|
||||
)
|
||||
|
||||
// BitcoindConn represents a persistent client connection to a bitcoind node
|
||||
// that listens for events read from a ZMQ connection.
|
||||
type BitcoindConn struct {
|
||||
|
@ -79,7 +101,7 @@ func NewBitcoindConn(chainParams *chaincfg.Params,
|
|||
// concern to ensure one type of event isn't dropped from the connection
|
||||
// queue due to another type of event filling it up.
|
||||
zmqBlockConn, err := gozmq.Subscribe(
|
||||
zmqBlockHost, []string{"rawblock"}, zmqPollInterval,
|
||||
zmqBlockHost, []string{rawBlockZMQCommand}, zmqPollInterval,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to subscribe for zmq block "+
|
||||
|
@ -87,7 +109,7 @@ func NewBitcoindConn(chainParams *chaincfg.Params,
|
|||
}
|
||||
|
||||
zmqTxConn, err := gozmq.Subscribe(
|
||||
zmqTxHost, []string{"rawtx"}, zmqPollInterval,
|
||||
zmqTxHost, []string{rawTxZMQCommand}, zmqPollInterval,
|
||||
)
|
||||
if err != nil {
|
||||
zmqBlockConn.Close()
|
||||
|
@ -164,6 +186,20 @@ func (c *BitcoindConn) blockEventHandler() {
|
|||
log.Info("Started listening for bitcoind block notifications via ZMQ "+
|
||||
"on", c.zmqBlockConn.RemoteAddr())
|
||||
|
||||
// Set up the buffers we expect our messages to consume. ZMQ
|
||||
// messages from bitcoind include three parts: the command, the
|
||||
// data, and the sequence number.
|
||||
//
|
||||
// We'll allocate a fixed data slice that we'll reuse when reading
|
||||
// blocks from bitcoind through ZMQ. There's no need to recycle this
|
||||
// slice (zero out) after using it, as further reads will overwrite the
|
||||
// slice and we'll only be deserializing the bytes needed.
|
||||
var (
|
||||
command [len(rawBlockZMQCommand)]byte
|
||||
seqNum [seqNumLen]byte
|
||||
data = make([]byte, maxRawBlockSize)
|
||||
)
|
||||
|
||||
for {
|
||||
// Before attempting to read from the ZMQ socket, we'll make
|
||||
// sure to check if we've been requested to shut down.
|
||||
|
@ -174,7 +210,11 @@ func (c *BitcoindConn) blockEventHandler() {
|
|||
}
|
||||
|
||||
// Poll an event from the ZMQ socket.
|
||||
msgBytes, err := c.zmqBlockConn.Receive()
|
||||
var (
|
||||
bufs = [][]byte{command[:], data, seqNum[:]}
|
||||
err error
|
||||
)
|
||||
bufs, err = c.zmqBlockConn.Receive(bufs)
|
||||
if err != nil {
|
||||
// EOF should only be returned if the connection was
|
||||
// explicitly closed, so we can exit at this point.
|
||||
|
@ -187,22 +227,24 @@ func (c *BitcoindConn) blockEventHandler() {
|
|||
// error to prevent spamming the logs.
|
||||
netErr, ok := err.(net.Error)
|
||||
if ok && netErr.Timeout() {
|
||||
log.Trace("Re-establishing timed out ZMQ " +
|
||||
"block connection")
|
||||
continue
|
||||
}
|
||||
|
||||
log.Errorf("Unable to receive ZMQ rawblock message: %v",
|
||||
err)
|
||||
log.Errorf("Unable to receive ZMQ %v message: %v",
|
||||
rawBlockZMQCommand, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// We have an event! We'll now ensure it is a block event,
|
||||
// deserialize it, and report it to the different rescan
|
||||
// clients.
|
||||
eventType := string(msgBytes[0])
|
||||
eventType := string(bufs[0])
|
||||
switch eventType {
|
||||
case "rawblock":
|
||||
case rawBlockZMQCommand:
|
||||
block := &wire.MsgBlock{}
|
||||
r := bytes.NewReader(msgBytes[1])
|
||||
r := bytes.NewReader(bufs[1])
|
||||
if err := block.Deserialize(r); err != nil {
|
||||
log.Errorf("Unable to deserialize block: %v",
|
||||
err)
|
||||
|
@ -229,8 +271,9 @@ func (c *BitcoindConn) blockEventHandler() {
|
|||
continue
|
||||
}
|
||||
|
||||
log.Warnf("Received unexpected event type from "+
|
||||
"rawblock subscription: %v", eventType)
|
||||
log.Warnf("Received unexpected event type from %v "+
|
||||
"subscription: %v", rawBlockZMQCommand,
|
||||
eventType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -245,6 +288,20 @@ func (c *BitcoindConn) txEventHandler() {
|
|||
log.Info("Started listening for bitcoind transaction notifications "+
|
||||
"via ZMQ on", c.zmqTxConn.RemoteAddr())
|
||||
|
||||
// Set up the buffers we expect our messages to consume. ZMQ
|
||||
// messages from bitcoind include three parts: the command, the
|
||||
// data, and the sequence number.
|
||||
//
|
||||
// We'll allocate a fixed data slice that we'll reuse when reading
|
||||
// transactions from bitcoind through ZMQ. There's no need to recycle
|
||||
// this slice (zero out) after using it, as further reads will overwrite
|
||||
// the slice and we'll only be deserializing the bytes needed.
|
||||
var (
|
||||
command [len(rawTxZMQCommand)]byte
|
||||
seqNum [seqNumLen]byte
|
||||
data = make([]byte, maxRawTxSize)
|
||||
)
|
||||
|
||||
for {
|
||||
// Before attempting to read from the ZMQ socket, we'll make
|
||||
// sure to check if we've been requested to shut down.
|
||||
|
@ -255,7 +312,11 @@ func (c *BitcoindConn) txEventHandler() {
|
|||
}
|
||||
|
||||
// Poll an event from the ZMQ socket.
|
||||
msgBytes, err := c.zmqTxConn.Receive()
|
||||
var (
|
||||
bufs = [][]byte{command[:], data, seqNum[:]}
|
||||
err error
|
||||
)
|
||||
bufs, err = c.zmqTxConn.Receive(bufs)
|
||||
if err != nil {
|
||||
// EOF should only be returned if the connection was
|
||||
// explicitly closed, so we can exit at this point.
|
||||
|
@ -268,22 +329,24 @@ func (c *BitcoindConn) txEventHandler() {
|
|||
// error to prevent spamming the logs.
|
||||
netErr, ok := err.(net.Error)
|
||||
if ok && netErr.Timeout() {
|
||||
log.Trace("Re-establishing timed out ZMQ " +
|
||||
"transaction connection")
|
||||
continue
|
||||
}
|
||||
|
||||
log.Errorf("Unable to receive ZMQ rawtx message: %v",
|
||||
err)
|
||||
log.Errorf("Unable to receive ZMQ %v message: %v",
|
||||
rawTxZMQCommand, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// We have an event! We'll now ensure it is a transaction event,
|
||||
// deserialize it, and report it to the different rescan
|
||||
// clients.
|
||||
eventType := string(msgBytes[0])
|
||||
eventType := string(bufs[0])
|
||||
switch eventType {
|
||||
case "rawtx":
|
||||
case rawTxZMQCommand:
|
||||
tx := &wire.MsgTx{}
|
||||
r := bytes.NewReader(msgBytes[1])
|
||||
r := bytes.NewReader(bufs[1])
|
||||
if err := tx.Deserialize(r); err != nil {
|
||||
log.Errorf("Unable to deserialize "+
|
||||
"transaction: %v", err)
|
||||
|
@ -310,8 +373,8 @@ func (c *BitcoindConn) txEventHandler() {
|
|||
continue
|
||||
}
|
||||
|
||||
log.Warnf("Received unexpected event type from rawtx "+
|
||||
"subscription: %v", eventType)
|
||||
log.Warnf("Received unexpected event type from %v "+
|
||||
"subscription: %v", rawTxZMQCommand, eventType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -199,6 +199,11 @@ func (s *NeutrinoClient) FilterBlocks(
|
|||
// the filter returns a positive match, the full block is then requested
|
||||
// and scanned for addresses using the block filterer.
|
||||
for i, blk := range req.Blocks {
|
||||
// TODO(wilmer): Investigate why polling it still necessary
|
||||
// here. While testing, I ran into a few instances where the
|
||||
// filter was not retrieved, leading to a panic. This should not
|
||||
// happen in most cases thanks to the query logic revamp within
|
||||
// Neutrino, but it seems there's still an uncovered edge case.
|
||||
filter, err := s.pollCFilter(&blk.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -312,7 +317,9 @@ func (s *NeutrinoClient) pollCFilter(hash *chainhash.Hash) (*gcs.Filter, error)
|
|||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
filter, err = s.CS.GetCFilter(*hash, wire.GCSFilterRegular)
|
||||
filter, err = s.CS.GetCFilter(
|
||||
*hash, wire.GCSFilterRegular, neutrino.OptimisticBatch(),
|
||||
)
|
||||
if err != nil {
|
||||
count++
|
||||
continue
|
||||
|
|
3241
vendor/github.com/btcsuite/btcwallet/internal/legacy/keystore/keystore.go
generated
vendored
Normal file
3241
vendor/github.com/btcsuite/btcwallet/internal/legacy/keystore/keystore.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
17
vendor/github.com/btcsuite/btcwallet/internal/legacy/rename/rename_unix.go
generated
vendored
Normal file
17
vendor/github.com/btcsuite/btcwallet/internal/legacy/rename/rename_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) 2013-2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !windows,!plan9
|
||||
|
||||
package rename
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Atomic provides an atomic file rename. newpath is replaced if it
|
||||
// already exists.
|
||||
func Atomic(oldpath, newpath string) error {
|
||||
return os.Rename(oldpath, newpath)
|
||||
}
|
71
vendor/github.com/btcsuite/btcwallet/internal/legacy/rename/rename_windows.go
generated
vendored
Normal file
71
vendor/github.com/btcsuite/btcwallet/internal/legacy/rename/rename_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
// The following is adapted from goleveldb
|
||||
// (https://github.com/syndtr/goleveldb) under the following license:
|
||||
//
|
||||
// Copyright 2012 Suryandaru Triandana <syndtr@gmail.com>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package rename
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
procMoveFileExW = modkernel32.NewProc("MoveFileExW")
|
||||
)
|
||||
|
||||
const (
|
||||
_MOVEFILE_REPLACE_EXISTING = 1
|
||||
)
|
||||
|
||||
func moveFileEx(from *uint16, to *uint16, flags uint32) error {
|
||||
r1, _, e1 := syscall.Syscall(procMoveFileExW.Addr(), 3,
|
||||
uintptr(unsafe.Pointer(from)), uintptr(unsafe.Pointer(to)),
|
||||
uintptr(flags))
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
return error(e1)
|
||||
} else {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Atomic provides an atomic file rename. newpath is replaced if it
|
||||
// already exists.
|
||||
func Atomic(oldpath, newpath string) error {
|
||||
from, err := syscall.UTF16PtrFromString(oldpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
to, err := syscall.UTF16PtrFromString(newpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return moveFileEx(from, to, _MOVEFILE_REPLACE_EXISTING)
|
||||
}
|
|
@ -0,0 +1,323 @@
|
|||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcutil/hdkeychain"
|
||||
"github.com/btcsuite/btcwallet/internal/legacy/keystore"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
// ProvideSeed is used to prompt for the wallet seed which maybe required during
|
||||
// upgrades.
|
||||
func ProvideSeed() ([]byte, error) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
fmt.Print("Enter existing wallet seed: ")
|
||||
seedStr, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seedStr = strings.TrimSpace(strings.ToLower(seedStr))
|
||||
|
||||
seed, err := hex.DecodeString(seedStr)
|
||||
if err != nil || len(seed) < hdkeychain.MinSeedBytes ||
|
||||
len(seed) > hdkeychain.MaxSeedBytes {
|
||||
|
||||
fmt.Printf("Invalid seed specified. Must be a "+
|
||||
"hexadecimal value that is at least %d bits and "+
|
||||
"at most %d bits\n", hdkeychain.MinSeedBytes*8,
|
||||
hdkeychain.MaxSeedBytes*8)
|
||||
continue
|
||||
}
|
||||
|
||||
return seed, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ProvidePrivPassphrase is used to prompt for the private passphrase which
|
||||
// maybe required during upgrades.
|
||||
func ProvidePrivPassphrase() ([]byte, error) {
|
||||
prompt := "Enter the private passphrase of your wallet: "
|
||||
for {
|
||||
fmt.Print(prompt)
|
||||
pass, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Print("\n")
|
||||
pass = bytes.TrimSpace(pass)
|
||||
if len(pass) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
return pass, nil
|
||||
}
|
||||
}
|
||||
|
||||
// promptList prompts the user with the given prefix, list of valid responses,
|
||||
// and default list entry to use. The function will repeat the prompt to the
|
||||
// user until they enter a valid response.
|
||||
func promptList(reader *bufio.Reader, prefix string, validResponses []string, defaultEntry string) (string, error) {
|
||||
// Setup the prompt according to the parameters.
|
||||
validStrings := strings.Join(validResponses, "/")
|
||||
var prompt string
|
||||
if defaultEntry != "" {
|
||||
prompt = fmt.Sprintf("%s (%s) [%s]: ", prefix, validStrings,
|
||||
defaultEntry)
|
||||
} else {
|
||||
prompt = fmt.Sprintf("%s (%s): ", prefix, validStrings)
|
||||
}
|
||||
|
||||
// Prompt the user until one of the valid responses is given.
|
||||
for {
|
||||
fmt.Print(prompt)
|
||||
reply, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
reply = strings.TrimSpace(strings.ToLower(reply))
|
||||
if reply == "" {
|
||||
reply = defaultEntry
|
||||
}
|
||||
|
||||
for _, validResponse := range validResponses {
|
||||
if reply == validResponse {
|
||||
return reply, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// promptListBool prompts the user for a boolean (yes/no) with the given prefix.
|
||||
// The function will repeat the prompt to the user until they enter a valid
|
||||
// reponse.
|
||||
func promptListBool(reader *bufio.Reader, prefix string, defaultEntry string) (bool, error) {
|
||||
// Setup the valid responses.
|
||||
valid := []string{"n", "no", "y", "yes"}
|
||||
response, err := promptList(reader, prefix, valid, defaultEntry)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return response == "yes" || response == "y", nil
|
||||
}
|
||||
|
||||
// promptPass prompts the user for a passphrase with the given prefix. The
|
||||
// function will ask the user to confirm the passphrase and will repeat the
|
||||
// prompts until they enter a matching response.
|
||||
func promptPass(reader *bufio.Reader, prefix string, confirm bool) ([]byte, error) {
|
||||
// Prompt the user until they enter a passphrase.
|
||||
prompt := fmt.Sprintf("%s: ", prefix)
|
||||
for {
|
||||
fmt.Print(prompt)
|
||||
pass, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Print("\n")
|
||||
pass = bytes.TrimSpace(pass)
|
||||
if len(pass) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if !confirm {
|
||||
return pass, nil
|
||||
}
|
||||
|
||||
fmt.Print("Confirm passphrase: ")
|
||||
confirm, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Print("\n")
|
||||
confirm = bytes.TrimSpace(confirm)
|
||||
if !bytes.Equal(pass, confirm) {
|
||||
fmt.Println("The entered passphrases do not match")
|
||||
continue
|
||||
}
|
||||
|
||||
return pass, nil
|
||||
}
|
||||
}
|
||||
|
||||
// PrivatePass prompts the user for a private passphrase with varying behavior
|
||||
// depending on whether the passed legacy keystore exists. When it does, the
|
||||
// user is prompted for the existing passphrase which is then used to unlock it.
|
||||
// On the other hand, when the legacy keystore is nil, the user is prompted for
|
||||
// a new private passphrase. All prompts are repeated until the user enters a
|
||||
// valid response.
|
||||
func PrivatePass(reader *bufio.Reader, legacyKeyStore *keystore.Store) ([]byte, error) {
|
||||
// When there is not an existing legacy wallet, simply prompt the user
|
||||
// for a new private passphase and return it.
|
||||
if legacyKeyStore == nil {
|
||||
return promptPass(reader, "Enter the private "+
|
||||
"passphrase for your new wallet", true)
|
||||
}
|
||||
|
||||
// At this point, there is an existing legacy wallet, so prompt the user
|
||||
// for the existing private passphrase and ensure it properly unlocks
|
||||
// the legacy wallet so all of the addresses can later be imported.
|
||||
fmt.Println("You have an existing legacy wallet. All addresses from " +
|
||||
"your existing legacy wallet will be imported into the new " +
|
||||
"wallet format.")
|
||||
for {
|
||||
privPass, err := promptPass(reader, "Enter the private "+
|
||||
"passphrase for your existing wallet", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Keep prompting the user until the passphrase is correct.
|
||||
if err := legacyKeyStore.Unlock([]byte(privPass)); err != nil {
|
||||
if err == keystore.ErrWrongPassphrase {
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return privPass, nil
|
||||
}
|
||||
}
|
||||
|
||||
// PublicPass prompts the user whether they want to add an additional layer of
|
||||
// encryption to the wallet. When the user answers yes and there is already a
|
||||
// public passphrase provided via the passed config, it prompts them whether or
|
||||
// not to use that configured passphrase. It will also detect when the same
|
||||
// passphrase is used for the private and public passphrase and prompt the user
|
||||
// if they are sure they want to use the same passphrase for both. Finally, all
|
||||
// prompts are repeated until the user enters a valid response.
|
||||
func PublicPass(reader *bufio.Reader, privPass []byte,
|
||||
defaultPubPassphrase, configPubPassphrase []byte) ([]byte, error) {
|
||||
|
||||
pubPass := defaultPubPassphrase
|
||||
usePubPass, err := promptListBool(reader, "Do you want "+
|
||||
"to add an additional layer of encryption for public "+
|
||||
"data?", "no")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !usePubPass {
|
||||
return pubPass, nil
|
||||
}
|
||||
|
||||
if !bytes.Equal(configPubPassphrase, pubPass) {
|
||||
useExisting, err := promptListBool(reader, "Use the "+
|
||||
"existing configured public passphrase for encryption "+
|
||||
"of public data?", "no")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if useExisting {
|
||||
return configPubPassphrase, nil
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
pubPass, err = promptPass(reader, "Enter the public "+
|
||||
"passphrase for your new wallet", true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bytes.Equal(pubPass, privPass) {
|
||||
useSamePass, err := promptListBool(reader,
|
||||
"Are you sure want to use the same passphrase "+
|
||||
"for public and private data?", "no")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if useSamePass {
|
||||
break
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Println("NOTE: Use the --walletpass option to configure your " +
|
||||
"public passphrase.")
|
||||
return pubPass, nil
|
||||
}
|
||||
|
||||
// Seed prompts the user whether they want to use an existing wallet generation
|
||||
// seed. When the user answers no, a seed will be generated and displayed to
|
||||
// the user along with prompting them for confirmation. When the user answers
|
||||
// yes, a the user is prompted for it. All prompts are repeated until the user
|
||||
// enters a valid response.
|
||||
func Seed(reader *bufio.Reader) ([]byte, error) {
|
||||
// Ascertain the wallet generation seed.
|
||||
useUserSeed, err := promptListBool(reader, "Do you have an "+
|
||||
"existing wallet seed you want to use?", "no")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !useUserSeed {
|
||||
seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Println("Your wallet generation seed is:")
|
||||
fmt.Printf("%x\n", seed)
|
||||
fmt.Println("IMPORTANT: Keep the seed in a safe place as you\n" +
|
||||
"will NOT be able to restore your wallet without it.")
|
||||
fmt.Println("Please keep in mind that anyone who has access\n" +
|
||||
"to the seed can also restore your wallet thereby\n" +
|
||||
"giving them access to all your funds, so it is\n" +
|
||||
"imperative that you keep it in a secure location.")
|
||||
|
||||
for {
|
||||
fmt.Print(`Once you have stored the seed in a safe ` +
|
||||
`and secure location, enter "OK" to continue: `)
|
||||
confirmSeed, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
confirmSeed = strings.TrimSpace(confirmSeed)
|
||||
confirmSeed = strings.Trim(confirmSeed, `"`)
|
||||
if confirmSeed == "OK" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return seed, nil
|
||||
}
|
||||
|
||||
for {
|
||||
fmt.Print("Enter existing wallet seed: ")
|
||||
seedStr, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seedStr = strings.TrimSpace(strings.ToLower(seedStr))
|
||||
|
||||
seed, err := hex.DecodeString(seedStr)
|
||||
if err != nil || len(seed) < hdkeychain.MinSeedBytes ||
|
||||
len(seed) > hdkeychain.MaxSeedBytes {
|
||||
|
||||
fmt.Printf("Invalid seed specified. Must be a "+
|
||||
"hexadecimal value that is at least %d bits and "+
|
||||
"at most %d bits\n", hdkeychain.MinSeedBytes*8,
|
||||
hdkeychain.MaxSeedBytes*8)
|
||||
continue
|
||||
}
|
||||
|
||||
return seed, nil
|
||||
}
|
||||
}
|
|
@ -32,8 +32,9 @@ report. Package waddrmgr is licensed under the liberal ISC license.
|
|||
- Import WIF keys
|
||||
- Import pay-to-script-hash scripts for things such as multi-signature
|
||||
transactions
|
||||
- Ability to export a watching-only version which does not contain any private
|
||||
- Ability to start in watching-only mode which does not contain any private
|
||||
key material
|
||||
- Ability to convert to watching-only mode
|
||||
- Programmatically detectable errors, including encapsulation of errors from
|
||||
packages it relies on
|
||||
- Address synchronization capabilities
|
||||
|
|
|
@ -850,6 +850,7 @@ func forEachAccount(ns walletdb.ReadBucket, scope *KeyScope,
|
|||
}
|
||||
|
||||
// fetchLastAccount retrieves the last account from the database.
|
||||
// If no accounts, returns twos-complement representation of -1, so that the next account is zero
|
||||
func fetchLastAccount(ns walletdb.ReadBucket, scope *KeyScope) (uint32, error) {
|
||||
scopedBucket, err := fetchReadScopeBucket(ns, scope)
|
||||
if err != nil {
|
||||
|
@ -859,6 +860,9 @@ func fetchLastAccount(ns walletdb.ReadBucket, scope *KeyScope) (uint32, error) {
|
|||
metaBucket := scopedBucket.NestedReadBucket(metaBucketName)
|
||||
|
||||
val := metaBucket.Get(lastAccountName)
|
||||
if val == nil {
|
||||
return (1 << 32) - 1, nil
|
||||
}
|
||||
if len(val) != 4 {
|
||||
str := fmt.Sprintf("malformed metadata '%s' stored in database",
|
||||
lastAccountName)
|
||||
|
|
|
@ -123,6 +123,14 @@ var DefaultScryptOptions = ScryptOptions{
|
|||
P: 1,
|
||||
}
|
||||
|
||||
// FastScryptOptions are the scrypt options that should be used for testing
|
||||
// purposes only where speed is more important than security.
|
||||
var FastScryptOptions = ScryptOptions{
|
||||
N: 16,
|
||||
R: 8,
|
||||
P: 1,
|
||||
}
|
||||
|
||||
// addrKey is used to uniquely identify an address even when those addresses
|
||||
// would end up being the same bitcoin address (as is the case for
|
||||
// pay-to-pubkey and pay-to-pubkey-hash style of addresses).
|
||||
|
@ -430,53 +438,61 @@ func (m *Manager) Close() {
|
|||
//
|
||||
// TODO(roasbeef): addrtype of raw key means it'll look in scripts to possibly
|
||||
// mark as gucci?
|
||||
func (m *Manager) NewScopedKeyManager(ns walletdb.ReadWriteBucket, scope KeyScope,
|
||||
addrSchema ScopeAddrSchema) (*ScopedKeyManager, error) {
|
||||
func (m *Manager) NewScopedKeyManager(ns walletdb.ReadWriteBucket,
|
||||
scope KeyScope, addrSchema ScopeAddrSchema) (*ScopedKeyManager, error) {
|
||||
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
// If the manager is locked, then we can't create a new scoped manager.
|
||||
if m.locked {
|
||||
return nil, managerError(ErrLocked, errLocked, nil)
|
||||
}
|
||||
var rootPriv *hdkeychain.ExtendedKey
|
||||
if !m.watchingOnly {
|
||||
// If the manager is locked, then we can't create a new scoped
|
||||
// manager.
|
||||
if m.locked {
|
||||
return nil, managerError(ErrLocked, errLocked, nil)
|
||||
}
|
||||
|
||||
// Now that we know the manager is unlocked, we'll need to fetch the
|
||||
// root master HD private key. This is required as we'll be attempting
|
||||
// the following derivation: m/purpose'/cointype'
|
||||
//
|
||||
// Note that the path to the coin type is requires hardened derivation,
|
||||
// therefore this can only be done if the wallet's root key hasn't been
|
||||
// neutered.
|
||||
masterRootPrivEnc, _, err := fetchMasterHDKeys(ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Now that we know the manager is unlocked, we'll need to
|
||||
// fetch the root master HD private key. This is required as
|
||||
// we'll be attempting the following derivation:
|
||||
// m/purpose'/cointype'
|
||||
//
|
||||
// Note that the path to the coin type is requires hardened
|
||||
// derivation, therefore this can only be done if the wallet's
|
||||
// root key hasn't been neutered.
|
||||
masterRootPrivEnc, _, err := fetchMasterHDKeys(ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the master root private key isn't found within the database, but
|
||||
// we need to bail here as we can't create the cointype key without the
|
||||
// master root private key.
|
||||
if masterRootPrivEnc == nil {
|
||||
return nil, managerError(ErrWatchingOnly, "", nil)
|
||||
}
|
||||
// If the master root private key isn't found within the
|
||||
// database, but we need to bail here as we can't create the
|
||||
// cointype key without the master root private key.
|
||||
if masterRootPrivEnc == nil {
|
||||
return nil, managerError(ErrWatchingOnly, "", nil)
|
||||
}
|
||||
|
||||
// Before we can derive any new scoped managers using this key, we'll
|
||||
// need to fully decrypt it.
|
||||
serializedMasterRootPriv, err := m.cryptoKeyPriv.Decrypt(masterRootPrivEnc)
|
||||
if err != nil {
|
||||
str := fmt.Sprintf("failed to decrypt master root serialized private key")
|
||||
return nil, managerError(ErrLocked, str, err)
|
||||
}
|
||||
// Before we can derive any new scoped managers using this
|
||||
// key, we'll need to fully decrypt it.
|
||||
serializedMasterRootPriv, err :=
|
||||
m.cryptoKeyPriv.Decrypt(masterRootPrivEnc)
|
||||
if err != nil {
|
||||
str := fmt.Sprintf("failed to decrypt master root " +
|
||||
"serialized private key")
|
||||
return nil, managerError(ErrLocked, str, err)
|
||||
}
|
||||
|
||||
// Now that we know the root priv is within the database, we'll decode
|
||||
// it into a usable object.
|
||||
rootPriv, err := hdkeychain.NewKeyFromString(
|
||||
string(serializedMasterRootPriv),
|
||||
)
|
||||
zero.Bytes(serializedMasterRootPriv)
|
||||
if err != nil {
|
||||
str := fmt.Sprintf("failed to create master extended private key")
|
||||
return nil, managerError(ErrKeyChain, str, err)
|
||||
// Now that we know the root priv is within the database,
|
||||
// we'll decode it into a usable object.
|
||||
rootPriv, err = hdkeychain.NewKeyFromString(
|
||||
string(serializedMasterRootPriv),
|
||||
)
|
||||
zero.Bytes(serializedMasterRootPriv)
|
||||
if err != nil {
|
||||
str := fmt.Sprintf("failed to create master extended " +
|
||||
"private key")
|
||||
return nil, managerError(ErrKeyChain, str, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we have the root private key, we'll fetch the scope bucket
|
||||
|
@ -498,19 +514,21 @@ func (m *Manager) NewScopedKeyManager(ns walletdb.ReadWriteBucket, scope KeyScop
|
|||
}
|
||||
scopeKey := scopeToBytes(&scope)
|
||||
schemaBytes := scopeSchemaToBytes(&addrSchema)
|
||||
err = scopeSchemas.Put(scopeKey[:], schemaBytes)
|
||||
err := scopeSchemas.Put(scopeKey[:], schemaBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// With the database state created, we'll now derive the cointype key
|
||||
// using the master HD private key, then encrypt it along with the
|
||||
// first account using our crypto keys.
|
||||
err = createManagerKeyScope(
|
||||
ns, scope, rootPriv, m.cryptoKeyPub, m.cryptoKeyPriv,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if !m.watchingOnly {
|
||||
// With the database state created, we'll now derive the
|
||||
// cointype key using the master HD private key, then encrypt
|
||||
// it along with the first account using our crypto keys.
|
||||
err = createManagerKeyScope(
|
||||
ns, scope, rootPriv, m.cryptoKeyPub, m.cryptoKeyPriv,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, we'll register this new scoped manager with the root
|
||||
|
@ -730,6 +748,43 @@ func (m *Manager) ForEachActiveAddress(ns walletdb.ReadBucket, fn func(addr btcu
|
|||
return nil
|
||||
}
|
||||
|
||||
// ForEachRelevantActiveAddress invokes the given closure on each active
|
||||
// address relevant to the wallet. Ideally, only addresses within the default
|
||||
// key scopes would be relevant, but due to a bug (now fixed) in which change
|
||||
// addresses could be created outside of the default key scopes, we now need to
|
||||
// check for those as well.
|
||||
func (m *Manager) ForEachRelevantActiveAddress(ns walletdb.ReadBucket,
|
||||
fn func(addr btcutil.Address) error) error {
|
||||
|
||||
m.mtx.RLock()
|
||||
defer m.mtx.RUnlock()
|
||||
|
||||
for _, scopedMgr := range m.scopedManagers {
|
||||
// If the manager is for a default key scope, we'll return all
|
||||
// addresses, otherwise we'll only return internal addresses, as
|
||||
// that's the branch used for change addresses.
|
||||
isDefaultKeyScope := false
|
||||
for _, defaultKeyScope := range DefaultKeyScopes {
|
||||
if scopedMgr.Scope() == defaultKeyScope {
|
||||
isDefaultKeyScope = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
if isDefaultKeyScope {
|
||||
err = scopedMgr.ForEachActiveAddress(ns, fn)
|
||||
} else {
|
||||
err = scopedMgr.ForEachInternalActiveAddress(ns, fn)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForEachAccountAddress calls the given function with each address of
|
||||
// the given account stored in the manager, breaking early on error.
|
||||
func (m *Manager) ForEachAccountAddress(ns walletdb.ReadBucket, account uint32,
|
||||
|
@ -1253,7 +1308,7 @@ func newManager(chainParams *chaincfg.Params, masterKeyPub *snacl.SecretKey,
|
|||
masterKeyPriv *snacl.SecretKey, cryptoKeyPub EncryptorDecryptor,
|
||||
cryptoKeyPrivEncrypted, cryptoKeyScriptEncrypted []byte, syncInfo *syncState,
|
||||
birthday time.Time, privPassphraseSalt [saltSize]byte,
|
||||
scopedManagers map[KeyScope]*ScopedKeyManager) *Manager {
|
||||
scopedManagers map[KeyScope]*ScopedKeyManager, watchingOnly bool) *Manager {
|
||||
|
||||
m := &Manager{
|
||||
chainParams: chainParams,
|
||||
|
@ -1271,6 +1326,7 @@ func newManager(chainParams *chaincfg.Params, masterKeyPub *snacl.SecretKey,
|
|||
scopedManagers: scopedManagers,
|
||||
externalAddrSchemas: make(map[AddressType][]KeyScope),
|
||||
internalAddrSchemas: make(map[AddressType][]KeyScope),
|
||||
watchingOnly: watchingOnly,
|
||||
}
|
||||
|
||||
for _, sMgr := range m.scopedManagers {
|
||||
|
@ -1495,9 +1551,8 @@ func loadManager(ns walletdb.ReadBucket, pubPassphrase []byte,
|
|||
mgr := newManager(
|
||||
chainParams, &masterKeyPub, &masterKeyPriv,
|
||||
cryptoKeyPub, cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo,
|
||||
birthday, privPassphraseSalt, scopedManagers,
|
||||
birthday, privPassphraseSalt, scopedManagers, watchingOnly,
|
||||
)
|
||||
mgr.watchingOnly = watchingOnly
|
||||
|
||||
for _, scopedManager := range scopedManagers {
|
||||
scopedManager.rootManager = mgr
|
||||
|
@ -1631,27 +1686,40 @@ func createManagerKeyScope(ns walletdb.ReadWriteBucket,
|
|||
)
|
||||
}
|
||||
|
||||
// Create creates a new address manager in the given namespace. The seed must
|
||||
// conform to the standards described in hdkeychain.NewMaster and will be used
|
||||
// to create the master root node from which all hierarchical deterministic
|
||||
// addresses are derived. This allows all chained addresses in the address
|
||||
// manager to be recovered by using the same seed.
|
||||
// Create creates a new address manager in the given namespace.
|
||||
//
|
||||
// All private and public keys and information are protected by secret keys
|
||||
// derived from the provided private and public passphrases. The public
|
||||
// passphrase is required on subsequent opens of the address manager, and the
|
||||
// private passphrase is required to unlock the address manager in order to
|
||||
// gain access to any private keys and information.
|
||||
// The seed must conform to the standards described in
|
||||
// hdkeychain.NewMaster and will be used to create the master root
|
||||
// node from which all hierarchical deterministic addresses are
|
||||
// derived. This allows all chained addresses in the address manager
|
||||
// to be recovered by using the same seed.
|
||||
//
|
||||
// If a config structure is passed to the function, that configuration will
|
||||
// override the defaults.
|
||||
// If the provided seed value is nil the address manager will be
|
||||
// created in watchingOnly mode in which case no default accounts or
|
||||
// scoped managers are created - it is up to the caller to create a
|
||||
// new one with NewAccountWatchingOnly and NewScopedKeyManager.
|
||||
//
|
||||
// A ManagerError with an error code of ErrAlreadyExists will be returned the
|
||||
// address manager already exists in the specified namespace.
|
||||
func Create(ns walletdb.ReadWriteBucket, seed, pubPassphrase, privPassphrase []byte,
|
||||
// All private and public keys and information are protected by secret
|
||||
// keys derived from the provided private and public passphrases. The
|
||||
// public passphrase is required on subsequent opens of the address
|
||||
// manager, and the private passphrase is required to unlock the
|
||||
// address manager in order to gain access to any private keys and
|
||||
// information.
|
||||
//
|
||||
// If a config structure is passed to the function, that configuration
|
||||
// will override the defaults.
|
||||
//
|
||||
// A ManagerError with an error code of ErrAlreadyExists will be
|
||||
// returned the address manager already exists in the specified
|
||||
// namespace.
|
||||
func Create(ns walletdb.ReadWriteBucket,
|
||||
seed, pubPassphrase, privPassphrase []byte,
|
||||
chainParams *chaincfg.Params, config *ScryptOptions,
|
||||
birthday time.Time) error {
|
||||
|
||||
// If the seed argument is nil we create in watchingOnly mode.
|
||||
isWatchingOnly := seed == nil
|
||||
|
||||
// Return an error if the manager has already been created in
|
||||
// the given database namespace.
|
||||
exists := managerExists(ns)
|
||||
|
@ -1660,13 +1728,17 @@ func Create(ns walletdb.ReadWriteBucket, seed, pubPassphrase, privPassphrase []b
|
|||
}
|
||||
|
||||
// Ensure the private passphrase is not empty.
|
||||
if len(privPassphrase) == 0 {
|
||||
if !isWatchingOnly && len(privPassphrase) == 0 {
|
||||
str := "private passphrase may not be empty"
|
||||
return managerError(ErrEmptyPassphrase, str, nil)
|
||||
}
|
||||
|
||||
// Perform the initial bucket creation and database namespace setup.
|
||||
if err := createManagerNS(ns, ScopeAddrMap); err != nil {
|
||||
defaultScopes := map[KeyScope]ScopeAddrSchema{}
|
||||
if !isWatchingOnly {
|
||||
defaultScopes = ScopeAddrMap
|
||||
}
|
||||
if err := createManagerNS(ns, defaultScopes); err != nil {
|
||||
return maybeConvertDbError(err)
|
||||
}
|
||||
|
||||
|
@ -1681,22 +1753,6 @@ func Create(ns walletdb.ReadWriteBucket, seed, pubPassphrase, privPassphrase []b
|
|||
str := "failed to master public key"
|
||||
return managerError(ErrCrypto, str, err)
|
||||
}
|
||||
masterKeyPriv, err := newSecretKey(&privPassphrase, config)
|
||||
if err != nil {
|
||||
str := "failed to master private key"
|
||||
return managerError(ErrCrypto, str, err)
|
||||
}
|
||||
defer masterKeyPriv.Zero()
|
||||
|
||||
// Generate the private passphrase salt. This is used when hashing
|
||||
// passwords to detect whether an unlock can be avoided when the manager
|
||||
// is already unlocked.
|
||||
var privPassphraseSalt [saltSize]byte
|
||||
_, err = rand.Read(privPassphraseSalt[:])
|
||||
if err != nil {
|
||||
str := "failed to read random source for passphrase salt"
|
||||
return managerError(ErrCrypto, str, err)
|
||||
}
|
||||
|
||||
// Generate new crypto public, private, and script keys. These keys are
|
||||
// used to protect the actual public and private data such as addresses,
|
||||
|
@ -1706,18 +1762,6 @@ func Create(ns walletdb.ReadWriteBucket, seed, pubPassphrase, privPassphrase []b
|
|||
str := "failed to generate crypto public key"
|
||||
return managerError(ErrCrypto, str, err)
|
||||
}
|
||||
cryptoKeyPriv, err := newCryptoKey()
|
||||
if err != nil {
|
||||
str := "failed to generate crypto private key"
|
||||
return managerError(ErrCrypto, str, err)
|
||||
}
|
||||
defer cryptoKeyPriv.Zero()
|
||||
cryptoKeyScript, err := newCryptoKey()
|
||||
if err != nil {
|
||||
str := "failed to generate crypto script key"
|
||||
return managerError(ErrCrypto, str, err)
|
||||
}
|
||||
defer cryptoKeyScript.Zero()
|
||||
|
||||
// Encrypt the crypto keys with the associated master keys.
|
||||
cryptoKeyPubEnc, err := masterKeyPub.Encrypt(cryptoKeyPub.Bytes())
|
||||
|
@ -1725,70 +1769,120 @@ func Create(ns walletdb.ReadWriteBucket, seed, pubPassphrase, privPassphrase []b
|
|||
str := "failed to encrypt crypto public key"
|
||||
return managerError(ErrCrypto, str, err)
|
||||
}
|
||||
cryptoKeyPrivEnc, err := masterKeyPriv.Encrypt(cryptoKeyPriv.Bytes())
|
||||
if err != nil {
|
||||
str := "failed to encrypt crypto private key"
|
||||
return managerError(ErrCrypto, str, err)
|
||||
}
|
||||
cryptoKeyScriptEnc, err := masterKeyPriv.Encrypt(cryptoKeyScript.Bytes())
|
||||
if err != nil {
|
||||
str := "failed to encrypt crypto script key"
|
||||
return managerError(ErrCrypto, str, err)
|
||||
}
|
||||
|
||||
// Use the genesis block for the passed chain as the created at block
|
||||
// for the default.
|
||||
createdAt := &BlockStamp{Hash: *chainParams.GenesisHash, Height: 0}
|
||||
createdAt := &BlockStamp{
|
||||
Hash: *chainParams.GenesisHash,
|
||||
Height: 0,
|
||||
Timestamp: chainParams.GenesisBlock.Header.Timestamp,
|
||||
}
|
||||
|
||||
// Create the initial sync state.
|
||||
syncInfo := newSyncState(createdAt, createdAt)
|
||||
|
||||
// Save the master key params to the database.
|
||||
pubParams := masterKeyPub.Marshal()
|
||||
privParams := masterKeyPriv.Marshal()
|
||||
err = putMasterKeyParams(ns, pubParams, privParams)
|
||||
if err != nil {
|
||||
return maybeConvertDbError(err)
|
||||
}
|
||||
|
||||
// Generate the BIP0044 HD key structure to ensure the provided seed
|
||||
// can generate the required structure with no issues.
|
||||
var privParams []byte = nil
|
||||
var masterKeyPriv *snacl.SecretKey
|
||||
var cryptoKeyPrivEnc []byte = nil
|
||||
var cryptoKeyScriptEnc []byte = nil
|
||||
if !isWatchingOnly {
|
||||
masterKeyPriv, err = newSecretKey(&privPassphrase, config)
|
||||
if err != nil {
|
||||
str := "failed to master private key"
|
||||
return managerError(ErrCrypto, str, err)
|
||||
}
|
||||
defer masterKeyPriv.Zero()
|
||||
|
||||
// Derive the master extended key from the seed.
|
||||
rootKey, err := hdkeychain.NewMaster(seed, chainParams)
|
||||
if err != nil {
|
||||
str := "failed to derive master extended key"
|
||||
return managerError(ErrKeyChain, str, err)
|
||||
}
|
||||
rootPubKey, err := rootKey.Neuter()
|
||||
if err != nil {
|
||||
str := "failed to neuter master extended key"
|
||||
return managerError(ErrKeyChain, str, err)
|
||||
}
|
||||
// Generate the private passphrase salt. This is used when
|
||||
// hashing passwords to detect whether an unlock can be
|
||||
// avoided when the manager is already unlocked.
|
||||
var privPassphraseSalt [saltSize]byte
|
||||
_, err = rand.Read(privPassphraseSalt[:])
|
||||
if err != nil {
|
||||
str := "failed to read random source for passphrase salt"
|
||||
return managerError(ErrCrypto, str, err)
|
||||
}
|
||||
|
||||
// Next, for each registers default manager scope, we'll create the
|
||||
// hardened cointype key for it, as well as the first default account.
|
||||
for _, defaultScope := range DefaultKeyScopes {
|
||||
err := createManagerKeyScope(
|
||||
ns, defaultScope, rootKey, cryptoKeyPub, cryptoKeyPriv,
|
||||
)
|
||||
cryptoKeyPriv, err := newCryptoKey()
|
||||
if err != nil {
|
||||
str := "failed to generate crypto private key"
|
||||
return managerError(ErrCrypto, str, err)
|
||||
}
|
||||
defer cryptoKeyPriv.Zero()
|
||||
cryptoKeyScript, err := newCryptoKey()
|
||||
if err != nil {
|
||||
str := "failed to generate crypto script key"
|
||||
return managerError(ErrCrypto, str, err)
|
||||
}
|
||||
defer cryptoKeyScript.Zero()
|
||||
|
||||
cryptoKeyPrivEnc, err =
|
||||
masterKeyPriv.Encrypt(cryptoKeyPriv.Bytes())
|
||||
if err != nil {
|
||||
str := "failed to encrypt crypto private key"
|
||||
return managerError(ErrCrypto, str, err)
|
||||
}
|
||||
cryptoKeyScriptEnc, err =
|
||||
masterKeyPriv.Encrypt(cryptoKeyScript.Bytes())
|
||||
if err != nil {
|
||||
str := "failed to encrypt crypto script key"
|
||||
return managerError(ErrCrypto, str, err)
|
||||
}
|
||||
|
||||
// Generate the BIP0044 HD key structure to ensure the
|
||||
// provided seed can generate the required structure with no
|
||||
// issues.
|
||||
|
||||
// Derive the master extended key from the seed.
|
||||
rootKey, err := hdkeychain.NewMaster(seed, chainParams)
|
||||
if err != nil {
|
||||
str := "failed to derive master extended key"
|
||||
return managerError(ErrKeyChain, str, err)
|
||||
}
|
||||
rootPubKey, err := rootKey.Neuter()
|
||||
if err != nil {
|
||||
str := "failed to neuter master extended key"
|
||||
return managerError(ErrKeyChain, str, err)
|
||||
}
|
||||
|
||||
// Next, for each registers default manager scope, we'll
|
||||
// create the hardened cointype key for it, as well as the
|
||||
// first default account.
|
||||
for _, defaultScope := range DefaultKeyScopes {
|
||||
err := createManagerKeyScope(
|
||||
ns, defaultScope, rootKey, cryptoKeyPub, cryptoKeyPriv,
|
||||
)
|
||||
if err != nil {
|
||||
return maybeConvertDbError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Before we proceed, we'll also store the root master private
|
||||
// key within the database in an encrypted format. This is
|
||||
// required as in the future, we may need to create additional
|
||||
// scoped key managers.
|
||||
masterHDPrivKeyEnc, err :=
|
||||
cryptoKeyPriv.Encrypt([]byte(rootKey.String()))
|
||||
if err != nil {
|
||||
return maybeConvertDbError(err)
|
||||
}
|
||||
masterHDPubKeyEnc, err :=
|
||||
cryptoKeyPub.Encrypt([]byte(rootPubKey.String()))
|
||||
if err != nil {
|
||||
return maybeConvertDbError(err)
|
||||
}
|
||||
err = putMasterHDKeys(ns, masterHDPrivKeyEnc, masterHDPubKeyEnc)
|
||||
if err != nil {
|
||||
return maybeConvertDbError(err)
|
||||
}
|
||||
|
||||
privParams = masterKeyPriv.Marshal()
|
||||
}
|
||||
|
||||
// Before we proceed, we'll also store the root master private key
|
||||
// within the database in an encrypted format. This is required as in
|
||||
// the future, we may need to create additional scoped key managers.
|
||||
masterHDPrivKeyEnc, err := cryptoKeyPriv.Encrypt([]byte(rootKey.String()))
|
||||
if err != nil {
|
||||
return maybeConvertDbError(err)
|
||||
}
|
||||
masterHDPubKeyEnc, err := cryptoKeyPub.Encrypt([]byte(rootPubKey.String()))
|
||||
if err != nil {
|
||||
return maybeConvertDbError(err)
|
||||
}
|
||||
err = putMasterHDKeys(ns, masterHDPrivKeyEnc, masterHDPubKeyEnc)
|
||||
// Save the master key params to the database.
|
||||
err = putMasterKeyParams(ns, pubParams, privParams)
|
||||
if err != nil {
|
||||
return maybeConvertDbError(err)
|
||||
}
|
||||
|
@ -1800,9 +1894,9 @@ func Create(ns walletdb.ReadWriteBucket, seed, pubPassphrase, privPassphrase []b
|
|||
return maybeConvertDbError(err)
|
||||
}
|
||||
|
||||
// Save the fact this is not a watching-only address manager to the
|
||||
// Save the watching-only mode of the address manager to the
|
||||
// database.
|
||||
err = putWatchingOnly(ns, false)
|
||||
err = putWatchingOnly(ns, isWatchingOnly)
|
||||
if err != nil {
|
||||
return maybeConvertDbError(err)
|
||||
}
|
||||
|
|
|
@ -1187,7 +1187,7 @@ func (s *ScopedKeyManager) LastInternalAddress(ns walletdb.ReadBucket,
|
|||
}
|
||||
|
||||
// NewRawAccount creates a new account for the scoped manager. This method
|
||||
// differs from the NewAccount method in that this method takes the acount
|
||||
// differs from the NewAccount method in that this method takes the account
|
||||
// number *directly*, rather than taking a string name for the account, then
|
||||
// mapping that to the next highest account number.
|
||||
func (s *ScopedKeyManager) NewRawAccount(ns walletdb.ReadWriteBucket, number uint32) error {
|
||||
|
@ -1209,6 +1209,24 @@ func (s *ScopedKeyManager) NewRawAccount(ns walletdb.ReadWriteBucket, number uin
|
|||
return s.newAccount(ns, number, name)
|
||||
}
|
||||
|
||||
// NewRawAccountWatchingOnly creates a new watching only account for
|
||||
// the scoped manager. This method differs from the
|
||||
// NewAccountWatchingOnly method in that this method takes the account
|
||||
// number *directly*, rather than taking a string name for the
|
||||
// account, then mapping that to the next highest account number.
|
||||
func (s *ScopedKeyManager) NewRawAccountWatchingOnly(
|
||||
ns walletdb.ReadWriteBucket, number uint32,
|
||||
pubKey *hdkeychain.ExtendedKey) error {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
|
||||
// As this is an ad hoc account that may not follow our normal linear
|
||||
// derivation, we'll create a new name for this account based off of
|
||||
// the account number.
|
||||
name := fmt.Sprintf("act:%v", number)
|
||||
return s.newAccountWatchingOnly(ns, number, name, pubKey)
|
||||
}
|
||||
|
||||
// NewAccount creates and returns a new account stored in the manager based on
|
||||
// the given account name. If an account with the same name already exists,
|
||||
// ErrDuplicateAccount will be returned. Since creating a new account requires
|
||||
|
@ -1326,6 +1344,70 @@ func (s *ScopedKeyManager) newAccount(ns walletdb.ReadWriteBucket,
|
|||
return putLastAccount(ns, &s.scope, account)
|
||||
}
|
||||
|
||||
// NewAccountWatchingOnly is similar to NewAccount, but for watch-only wallets.
|
||||
func (s *ScopedKeyManager) NewAccountWatchingOnly(ns walletdb.ReadWriteBucket, name string,
|
||||
pubKey *hdkeychain.ExtendedKey) (uint32, error) {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
|
||||
// Fetch latest account, and create a new account in the same
|
||||
// transaction Fetch the latest account number to generate the next
|
||||
// account number
|
||||
account, err := fetchLastAccount(ns, &s.scope)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
account++
|
||||
|
||||
// With the name validated, we'll create a new account for the new
|
||||
// contiguous account.
|
||||
if err := s.newAccountWatchingOnly(ns, account, name, pubKey); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// newAccountWatchingOnly is similar to newAccount, but for watching-only wallets.
|
||||
//
|
||||
// NOTE: This function MUST be called with the manager lock held for writes.
|
||||
func (s *ScopedKeyManager) newAccountWatchingOnly(ns walletdb.ReadWriteBucket, account uint32, name string,
|
||||
pubKey *hdkeychain.ExtendedKey) error {
|
||||
|
||||
// Validate the account name.
|
||||
if err := ValidateAccountName(name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check that account with the same name does not exist
|
||||
_, err := s.lookupAccount(ns, name)
|
||||
if err == nil {
|
||||
str := fmt.Sprintf("account with the same name already exists")
|
||||
return managerError(ErrDuplicateAccount, str, err)
|
||||
}
|
||||
|
||||
// Encrypt the default account keys with the associated crypto keys.
|
||||
acctPubEnc, err := s.rootManager.cryptoKeyPub.Encrypt(
|
||||
[]byte(pubKey.String()),
|
||||
)
|
||||
if err != nil {
|
||||
str := "failed to encrypt public key for account"
|
||||
return managerError(ErrCrypto, str, err)
|
||||
}
|
||||
|
||||
// We have the encrypted account extended keys, so save them to the
|
||||
// database
|
||||
err = putAccountInfo(
|
||||
ns, &s.scope, account, acctPubEnc, nil, 0, 0, name,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save last account metadata
|
||||
return putLastAccount(ns, &s.scope, account)
|
||||
}
|
||||
|
||||
// RenameAccount renames an account stored in the manager based on the given
|
||||
// account number with the given name. If an account with the same name
|
||||
// already exists, ErrDuplicateAccount will be returned.
|
||||
|
@ -1700,6 +1782,7 @@ func (s *ScopedKeyManager) ForEachAccount(ns walletdb.ReadBucket,
|
|||
}
|
||||
|
||||
// LastAccount returns the last account stored in the manager.
|
||||
// If no accounts, returns twos-complement representation of -1
|
||||
func (s *ScopedKeyManager) LastAccount(ns walletdb.ReadBucket) (uint32, error) {
|
||||
return fetchLastAccount(ns, &s.scope)
|
||||
}
|
||||
|
@ -1760,3 +1843,30 @@ func (s *ScopedKeyManager) ForEachActiveAddress(ns walletdb.ReadBucket,
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForEachInternalActiveAddress invokes the given closure on each _internal_
|
||||
// active address belonging to the scoped key manager, breaking early on error.
|
||||
func (s *ScopedKeyManager) ForEachInternalActiveAddress(ns walletdb.ReadBucket,
|
||||
fn func(addr btcutil.Address) error) error {
|
||||
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
|
||||
addrFn := func(rowInterface interface{}) error {
|
||||
managedAddr, err := s.rowInterfaceToManaged(ns, rowInterface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Skip any non-internal branch addresses.
|
||||
if !managedAddr.Internal() {
|
||||
return nil
|
||||
}
|
||||
return fn(managedAddr.Address())
|
||||
}
|
||||
|
||||
if err := forEachActiveAddress(ns, &s.scope, addrFn); err != nil {
|
||||
return maybeConvertDbError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
wallet
|
||||
======
|
||||
|
||||
[![Build Status](https://travis-ci.org/btcsuite/btcwallet.png?branch=master)]
|
||||
(https://travis-ci.org/btcsuite/btcwallet)
|
||||
|
||||
## Feature Overview
|
||||
|
||||
TODO: Flesh out this section
|
||||
|
||||
## Documentation
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/btcsuite/btcwallet/wallet?status.png)]
|
||||
(http://godoc.org/github.com/btcsuite/btcwallet/wallet)
|
||||
|
||||
Full `go doc` style documentation for the project can be viewed online without
|
||||
installing this package by using the GoDoc site here:
|
||||
http://godoc.org/github.com/btcsuite/btcwallet/wallet
|
||||
|
||||
You can also view the documentation locally once the package is installed with
|
||||
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
|
||||
http://localhost:6060/pkg/github.com/btcsuite/btcwallet/wallet
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ go get github.com/btcsuite/btcwallet/wallet
|
||||
```
|
||||
|
||||
Package wallet is licensed under the [copyfree](http://copyfree.org) ISC
|
||||
License.
|
|
@ -0,0 +1,485 @@
|
|||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package wallet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcwallet/chain"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||
)
|
||||
|
||||
const (
|
||||
// birthdayBlockDelta is the maximum time delta allowed between our
|
||||
// birthday timestamp and our birthday block's timestamp when searching
|
||||
// for a better birthday block candidate (if possible).
|
||||
birthdayBlockDelta = 2 * time.Hour
|
||||
)
|
||||
|
||||
func (w *Wallet) handleChainNotifications() {
|
||||
defer w.wg.Done()
|
||||
|
||||
chainClient, err := w.requireChainClient()
|
||||
if err != nil {
|
||||
log.Errorf("handleChainNotifications called without RPC client")
|
||||
return
|
||||
}
|
||||
|
||||
catchUpHashes := func(w *Wallet, client chain.Interface,
|
||||
height int32) error {
|
||||
// TODO(aakselrod): There's a race conditon here, which
|
||||
// happens when a reorg occurs between the
|
||||
// rescanProgress notification and the last GetBlockHash
|
||||
// call. The solution when using btcd is to make btcd
|
||||
// send blockconnected notifications with each block
|
||||
// the way Neutrino does, and get rid of the loop. The
|
||||
// other alternative is to check the final hash and,
|
||||
// if it doesn't match the original hash returned by
|
||||
// the notification, to roll back and restart the
|
||||
// rescan.
|
||||
log.Infof("Catching up block hashes to height %d, this"+
|
||||
" might take a while", height)
|
||||
err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
|
||||
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||||
|
||||
startBlock := w.Manager.SyncedTo()
|
||||
|
||||
for i := startBlock.Height + 1; i <= height; i++ {
|
||||
hash, err := client.GetBlockHash(int64(i))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header, err := chainClient.GetBlockHeader(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bs := waddrmgr.BlockStamp{
|
||||
Height: i,
|
||||
Hash: *hash,
|
||||
Timestamp: header.Timestamp,
|
||||
}
|
||||
err = w.Manager.SetSyncedTo(ns, &bs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Failed to update address manager "+
|
||||
"sync state for height %d: %v", height, err)
|
||||
}
|
||||
|
||||
log.Info("Done catching up block hashes")
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case n, ok := <-chainClient.Notifications():
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var notificationName string
|
||||
var err error
|
||||
switch n := n.(type) {
|
||||
case chain.ClientConnected:
|
||||
// Before attempting to sync with our backend,
|
||||
// we'll make sure that our birthday block has
|
||||
// been set correctly to potentially prevent
|
||||
// missing relevant events.
|
||||
birthdayStore := &walletBirthdayStore{
|
||||
db: w.db,
|
||||
manager: w.Manager,
|
||||
}
|
||||
birthdayBlock, err := birthdaySanityCheck(
|
||||
chainClient, birthdayStore,
|
||||
)
|
||||
if err != nil && !waddrmgr.IsError(err, waddrmgr.ErrBirthdayBlockNotSet) {
|
||||
panic(fmt.Errorf("Unable to sanity "+
|
||||
"check wallet birthday block: %v",
|
||||
err))
|
||||
}
|
||||
|
||||
err = w.syncWithChain(birthdayBlock)
|
||||
if err != nil && !w.ShuttingDown() {
|
||||
panic(fmt.Errorf("Unable to synchronize "+
|
||||
"wallet to chain: %v", err))
|
||||
}
|
||||
case chain.BlockConnected:
|
||||
err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
|
||||
return w.connectBlock(tx, wtxmgr.BlockMeta(n))
|
||||
})
|
||||
notificationName = "block connected"
|
||||
case chain.BlockDisconnected:
|
||||
err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
|
||||
return w.disconnectBlock(tx, wtxmgr.BlockMeta(n))
|
||||
})
|
||||
notificationName = "block disconnected"
|
||||
case chain.RelevantTx:
|
||||
err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
|
||||
return w.addRelevantTx(tx, n.TxRecord, n.Block)
|
||||
})
|
||||
notificationName = "relevant transaction"
|
||||
case chain.FilteredBlockConnected:
|
||||
// Atomically update for the whole block.
|
||||
if len(n.RelevantTxs) > 0 {
|
||||
err = walletdb.Update(w.db, func(
|
||||
tx walletdb.ReadWriteTx) error {
|
||||
var err error
|
||||
for _, rec := range n.RelevantTxs {
|
||||
err = w.addRelevantTx(tx, rec,
|
||||
n.Block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
notificationName = "filtered block connected"
|
||||
|
||||
// The following require some database maintenance, but also
|
||||
// need to be reported to the wallet's rescan goroutine.
|
||||
case *chain.RescanProgress:
|
||||
err = catchUpHashes(w, chainClient, n.Height)
|
||||
notificationName = "rescan progress"
|
||||
select {
|
||||
case w.rescanNotifications <- n:
|
||||
case <-w.quitChan():
|
||||
return
|
||||
}
|
||||
case *chain.RescanFinished:
|
||||
err = catchUpHashes(w, chainClient, n.Height)
|
||||
notificationName = "rescan finished"
|
||||
w.SetChainSynced(true)
|
||||
select {
|
||||
case w.rescanNotifications <- n:
|
||||
case <-w.quitChan():
|
||||
return
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
// If we received a block connected notification
|
||||
// while rescanning, then we can ignore logging
|
||||
// the error as we'll properly catch up once we
|
||||
// process the RescanFinished notification.
|
||||
if notificationName == "block connected" &&
|
||||
waddrmgr.IsError(err, waddrmgr.ErrBlockNotFound) &&
|
||||
!w.ChainSynced() {
|
||||
|
||||
log.Debugf("Received block connected "+
|
||||
"notification for height %v "+
|
||||
"while rescanning",
|
||||
n.(chain.BlockConnected).Height)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Errorf("Unable to process chain backend "+
|
||||
"%v notification: %v", notificationName,
|
||||
err)
|
||||
}
|
||||
case <-w.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// connectBlock handles a chain server notification by marking a wallet
|
||||
// that's currently in-sync with the chain server as being synced up to
|
||||
// the passed block.
|
||||
func (w *Wallet) connectBlock(dbtx walletdb.ReadWriteTx, b wtxmgr.BlockMeta) error {
|
||||
addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||||
|
||||
bs := waddrmgr.BlockStamp{
|
||||
Height: b.Height,
|
||||
Hash: b.Hash,
|
||||
Timestamp: b.Time,
|
||||
}
|
||||
err := w.Manager.SetSyncedTo(addrmgrNs, &bs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Notify interested clients of the connected block.
|
||||
//
|
||||
// TODO: move all notifications outside of the database transaction.
|
||||
w.NtfnServer.notifyAttachedBlock(dbtx, &b)
|
||||
return nil
|
||||
}
|
||||
|
||||
// disconnectBlock handles a chain server reorganize by rolling back all
|
||||
// block history from the reorged block for a wallet in-sync with the chain
|
||||
// server.
|
||||
func (w *Wallet) disconnectBlock(dbtx walletdb.ReadWriteTx, b wtxmgr.BlockMeta) error {
|
||||
addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||||
txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey)
|
||||
|
||||
if !w.ChainSynced() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect the removed block and all blocks after it if we know about
|
||||
// the disconnected block. Otherwise, the block is in the future.
|
||||
if b.Height <= w.Manager.SyncedTo().Height {
|
||||
hash, err := w.Manager.BlockHash(addrmgrNs, b.Height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bytes.Equal(hash[:], b.Hash[:]) {
|
||||
bs := waddrmgr.BlockStamp{
|
||||
Height: b.Height - 1,
|
||||
}
|
||||
hash, err = w.Manager.BlockHash(addrmgrNs, bs.Height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Hash = *hash
|
||||
|
||||
client := w.ChainClient()
|
||||
header, err := client.GetBlockHeader(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bs.Timestamp = header.Timestamp
|
||||
err = w.Manager.SetSyncedTo(addrmgrNs, &bs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.TxStore.Rollback(txmgrNs, b.Height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify interested clients of the disconnected block.
|
||||
w.NtfnServer.notifyDetachedBlock(&b.Hash)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Wallet) addRelevantTx(dbtx walletdb.ReadWriteTx, rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) error {
|
||||
addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||||
txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey)
|
||||
|
||||
// At the moment all notified transactions are assumed to actually be
|
||||
// relevant. This assumption will not hold true when SPV support is
|
||||
// added, but until then, simply insert the transaction because there
|
||||
// should either be one or more relevant inputs or outputs.
|
||||
err := w.TxStore.InsertTx(txmgrNs, rec, block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check every output to determine whether it is controlled by a wallet
|
||||
// key. If so, mark the output as a credit.
|
||||
for i, output := range rec.MsgTx.TxOut {
|
||||
_, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript,
|
||||
w.chainParams)
|
||||
if err != nil {
|
||||
// Non-standard outputs are skipped.
|
||||
continue
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
ma, err := w.Manager.Address(addrmgrNs, addr)
|
||||
if err == nil {
|
||||
// TODO: Credits should be added with the
|
||||
// account they belong to, so wtxmgr is able to
|
||||
// track per-account balances.
|
||||
err = w.TxStore.AddCredit(txmgrNs, rec, block, uint32(i),
|
||||
ma.Internal())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = w.Manager.MarkUsed(addrmgrNs, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Marked address %v used", addr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Missing addresses are skipped. Other errors should
|
||||
// be propagated.
|
||||
if !waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send notification of mined or unmined transaction to any interested
|
||||
// clients.
|
||||
//
|
||||
// TODO: Avoid the extra db hits.
|
||||
if block == nil {
|
||||
details, err := w.TxStore.UniqueTxDetails(txmgrNs, &rec.Hash, nil)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot query transaction details for notification: %v", err)
|
||||
}
|
||||
|
||||
// It's possible that the transaction was not found within the
|
||||
// wallet's set of unconfirmed transactions due to it already
|
||||
// being confirmed, so we'll avoid notifying it.
|
||||
//
|
||||
// TODO(wilmer): ideally we should find the culprit to why we're
|
||||
// receiving an additional unconfirmed chain.RelevantTx
|
||||
// notification from the chain backend.
|
||||
if details != nil {
|
||||
w.NtfnServer.notifyUnminedTransaction(dbtx, details)
|
||||
}
|
||||
} else {
|
||||
details, err := w.TxStore.UniqueTxDetails(txmgrNs, &rec.Hash, &block.Block)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot query transaction details for notification: %v", err)
|
||||
}
|
||||
|
||||
// We'll only notify the transaction if it was found within the
|
||||
// wallet's set of confirmed transactions.
|
||||
if details != nil {
|
||||
w.NtfnServer.notifyMinedTransaction(dbtx, details, block)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// chainConn is an interface that abstracts the chain connection logic required
|
||||
// to perform a wallet's birthday block sanity check.
|
||||
type chainConn interface {
|
||||
// GetBestBlock returns the hash and height of the best block known to
|
||||
// the backend.
|
||||
GetBestBlock() (*chainhash.Hash, int32, error)
|
||||
|
||||
// GetBlockHash returns the hash of the block with the given height.
|
||||
GetBlockHash(int64) (*chainhash.Hash, error)
|
||||
|
||||
// GetBlockHeader returns the header for the block with the given hash.
|
||||
GetBlockHeader(*chainhash.Hash) (*wire.BlockHeader, error)
|
||||
}
|
||||
|
||||
// birthdayStore is an interface that abstracts the wallet's sync-related
|
||||
// information required to perform a birthday block sanity check.
|
||||
type birthdayStore interface {
|
||||
// Birthday returns the birthday timestamp of the wallet.
|
||||
Birthday() time.Time
|
||||
|
||||
// BirthdayBlock returns the birthday block of the wallet. The boolean
|
||||
// returned should signal whether the wallet has already verified the
|
||||
// correctness of its birthday block.
|
||||
BirthdayBlock() (waddrmgr.BlockStamp, bool, error)
|
||||
|
||||
// SetBirthdayBlock updates the birthday block of the wallet to the
|
||||
// given block. The boolean can be used to signal whether this block
|
||||
// should be sanity checked the next time the wallet starts.
|
||||
//
|
||||
// NOTE: This should also set the wallet's synced tip to reflect the new
|
||||
// birthday block. This will allow the wallet to rescan from this point
|
||||
// to detect any potentially missed events.
|
||||
SetBirthdayBlock(waddrmgr.BlockStamp) error
|
||||
}
|
||||
|
||||
// walletBirthdayStore is a wrapper around the wallet's database and address
|
||||
// manager that satisfies the birthdayStore interface.
|
||||
type walletBirthdayStore struct {
|
||||
db walletdb.DB
|
||||
manager *waddrmgr.Manager
|
||||
}
|
||||
|
||||
var _ birthdayStore = (*walletBirthdayStore)(nil)
|
||||
|
||||
// Birthday returns the birthday timestamp of the wallet.
|
||||
func (s *walletBirthdayStore) Birthday() time.Time {
|
||||
return s.manager.Birthday()
|
||||
}
|
||||
|
||||
// BirthdayBlock returns the birthday block of the wallet.
|
||||
func (s *walletBirthdayStore) BirthdayBlock() (waddrmgr.BlockStamp, bool, error) {
|
||||
var (
|
||||
birthdayBlock waddrmgr.BlockStamp
|
||||
birthdayBlockVerified bool
|
||||
)
|
||||
|
||||
err := walletdb.View(s.db, func(tx walletdb.ReadTx) error {
|
||||
var err error
|
||||
ns := tx.ReadBucket(waddrmgrNamespaceKey)
|
||||
birthdayBlock, birthdayBlockVerified, err = s.manager.BirthdayBlock(ns)
|
||||
return err
|
||||
})
|
||||
|
||||
return birthdayBlock, birthdayBlockVerified, err
|
||||
}
|
||||
|
||||
// SetBirthdayBlock updates the birthday block of the wallet to the
|
||||
// given block. The boolean can be used to signal whether this block
|
||||
// should be sanity checked the next time the wallet starts.
|
||||
//
|
||||
// NOTE: This should also set the wallet's synced tip to reflect the new
|
||||
// birthday block. This will allow the wallet to rescan from this point
|
||||
// to detect any potentially missed events.
|
||||
func (s *walletBirthdayStore) SetBirthdayBlock(block waddrmgr.BlockStamp) error {
|
||||
return walletdb.Update(s.db, func(tx walletdb.ReadWriteTx) error {
|
||||
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||||
err := s.manager.SetBirthdayBlock(ns, block, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.manager.SetSyncedTo(ns, &block)
|
||||
})
|
||||
}
|
||||
|
||||
// birthdaySanityCheck is a helper function that ensures a birthday block
|
||||
// correctly reflects the birthday timestamp within a reasonable timestamp
|
||||
// delta. It's intended to be run after the wallet establishes its connection
|
||||
// with the backend, but before it begins syncing. This is done as the second
|
||||
// part to the wallet's address manager migration where we populate the birthday
|
||||
// block to ensure we do not miss any relevant events throughout rescans.
|
||||
// waddrmgr.ErrBirthdayBlockNotSet is returned if the birthday block has not
|
||||
// been set yet.
|
||||
func birthdaySanityCheck(chainConn chainConn,
|
||||
birthdayStore birthdayStore) (*waddrmgr.BlockStamp, error) {
|
||||
|
||||
// We'll start by fetching our wallet's birthday timestamp and block.
|
||||
birthdayTimestamp := birthdayStore.Birthday()
|
||||
birthdayBlock, birthdayBlockVerified, err := birthdayStore.BirthdayBlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the birthday block has already been verified to be correct, we can
|
||||
// exit our sanity check to prevent potentially fetching a better
|
||||
// candidate.
|
||||
if birthdayBlockVerified {
|
||||
log.Debugf("Birthday block has already been verified: "+
|
||||
"height=%d, hash=%v", birthdayBlock.Height,
|
||||
birthdayBlock.Hash)
|
||||
|
||||
return &birthdayBlock, nil
|
||||
}
|
||||
|
||||
// Otherwise, we'll attempt to locate a better one now that we have
|
||||
// access to the chain.
|
||||
newBirthdayBlock, err := locateBirthdayBlock(chainConn, birthdayTimestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := birthdayStore.SetBirthdayBlock(*newBirthdayBlock); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newBirthdayBlock, nil
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright (c) 2016 The Decred developers
|
||||
// Copyright (c) 2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package wallet
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
// Note: The following common types should never reference the Wallet type.
|
||||
// Long term goal is to move these to their own package so that the database
|
||||
// access APIs can create them directly for the wallet to return.
|
||||
|
||||
// BlockIdentity identifies a block, or the lack of one (used to describe an
|
||||
// unmined transaction).
|
||||
type BlockIdentity struct {
|
||||
Hash chainhash.Hash
|
||||
Height int32
|
||||
}
|
||||
|
||||
// None returns whether there is no block described by the instance. When
|
||||
// associated with a transaction, this indicates the transaction is unmined.
|
||||
func (b *BlockIdentity) None() bool {
|
||||
// BUG: Because dcrwallet uses both 0 and -1 in various places to refer
|
||||
// to an unmined transaction this must check against both and may not
|
||||
// ever be usable to represent the genesis block.
|
||||
return *b == BlockIdentity{Height: -1} || *b == BlockIdentity{}
|
||||
}
|
||||
|
||||
// OutputKind describes a kind of transaction output. This is used to
|
||||
// differentiate between coinbase, stakebase, and normal outputs.
|
||||
type OutputKind byte
|
||||
|
||||
// Defined OutputKind constants
|
||||
const (
|
||||
OutputKindNormal OutputKind = iota
|
||||
OutputKindCoinbase
|
||||
)
|
||||
|
||||
// TransactionOutput describes an output that was or is at least partially
|
||||
// controlled by the wallet. Depending on context, this could refer to an
|
||||
// unspent output, or a spent one.
|
||||
type TransactionOutput struct {
|
||||
OutPoint wire.OutPoint
|
||||
Output wire.TxOut
|
||||
OutputKind OutputKind
|
||||
// These should be added later when the DB can return them more
|
||||
// efficiently:
|
||||
//TxLockTime uint32
|
||||
//TxExpiry uint32
|
||||
ContainingBlock BlockIdentity
|
||||
ReceiveTime time.Time
|
||||
}
|
||||
|
||||
// OutputRedeemer identifies the transaction input which redeems an output.
|
||||
type OutputRedeemer struct {
|
||||
TxHash chainhash.Hash
|
||||
InputIndex uint32
|
||||
}
|
||||
|
||||
// P2SHMultiSigOutput describes a transaction output with a pay-to-script-hash
|
||||
// output script and an imported redemption script. Along with common details
|
||||
// of the output, this structure also includes the P2SH address the script was
|
||||
// created from and the number of signatures required to redeem it.
|
||||
//
|
||||
// TODO: Could be useful to return how many of the required signatures can be
|
||||
// created by this wallet.
|
||||
type P2SHMultiSigOutput struct {
|
||||
// TODO: Add a TransactionOutput member to this struct and remove these
|
||||
// fields which are duplicated by it. This improves consistency. Only
|
||||
// not done now because wtxmgr APIs don't support an efficient way of
|
||||
// fetching other Transactionoutput data together with the rest of the
|
||||
// multisig info.
|
||||
OutPoint wire.OutPoint
|
||||
OutputAmount btcutil.Amount
|
||||
ContainingBlock BlockIdentity
|
||||
|
||||
P2SHAddress *btcutil.AddressScriptHash
|
||||
RedeemScript []byte
|
||||
M, N uint8 // M of N signatures required to redeem
|
||||
Redeemer *OutputRedeemer // nil unless spent
|
||||
}
|
|
@ -0,0 +1,290 @@
|
|||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package wallet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||
)
|
||||
|
||||
// byAmount defines the methods needed to satisify sort.Interface to
|
||||
// sort credits by their output amount.
|
||||
type byAmount []wtxmgr.Credit
|
||||
|
||||
func (s byAmount) Len() int { return len(s) }
|
||||
func (s byAmount) Less(i, j int) bool { return s[i].Amount < s[j].Amount }
|
||||
func (s byAmount) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
func makeInputSource(eligible []wtxmgr.Credit) txauthor.InputSource {
|
||||
// Pick largest outputs first. This is only done for compatibility with
|
||||
// previous tx creation code, not because it's a good idea.
|
||||
sort.Sort(sort.Reverse(byAmount(eligible)))
|
||||
|
||||
// Current inputs and their total value. These are closed over by the
|
||||
// returned input source and reused across multiple calls.
|
||||
currentTotal := btcutil.Amount(0)
|
||||
currentInputs := make([]*wire.TxIn, 0, len(eligible))
|
||||
currentScripts := make([][]byte, 0, len(eligible))
|
||||
currentInputValues := make([]btcutil.Amount, 0, len(eligible))
|
||||
|
||||
return func(target btcutil.Amount) (btcutil.Amount, []*wire.TxIn,
|
||||
[]btcutil.Amount, [][]byte, error) {
|
||||
|
||||
for currentTotal < target && len(eligible) != 0 {
|
||||
nextCredit := &eligible[0]
|
||||
eligible = eligible[1:]
|
||||
nextInput := wire.NewTxIn(&nextCredit.OutPoint, nil, nil)
|
||||
currentTotal += nextCredit.Amount
|
||||
currentInputs = append(currentInputs, nextInput)
|
||||
currentScripts = append(currentScripts, nextCredit.PkScript)
|
||||
currentInputValues = append(currentInputValues, nextCredit.Amount)
|
||||
}
|
||||
return currentTotal, currentInputs, currentInputValues, currentScripts, nil
|
||||
}
|
||||
}
|
||||
|
||||
// secretSource is an implementation of txauthor.SecretSource for the wallet's
|
||||
// address manager.
|
||||
type secretSource struct {
|
||||
*waddrmgr.Manager
|
||||
addrmgrNs walletdb.ReadBucket
|
||||
}
|
||||
|
||||
func (s secretSource) GetKey(addr btcutil.Address) (*btcec.PrivateKey, bool, error) {
|
||||
ma, err := s.Address(s.addrmgrNs, addr)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
mpka, ok := ma.(waddrmgr.ManagedPubKeyAddress)
|
||||
if !ok {
|
||||
e := fmt.Errorf("managed address type for %v is `%T` but "+
|
||||
"want waddrmgr.ManagedPubKeyAddress", addr, ma)
|
||||
return nil, false, e
|
||||
}
|
||||
privKey, err := mpka.PrivKey()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return privKey, ma.Compressed(), nil
|
||||
}
|
||||
|
||||
func (s secretSource) GetScript(addr btcutil.Address) ([]byte, error) {
|
||||
ma, err := s.Address(s.addrmgrNs, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msa, ok := ma.(waddrmgr.ManagedScriptAddress)
|
||||
if !ok {
|
||||
e := fmt.Errorf("managed address type for %v is `%T` but "+
|
||||
"want waddrmgr.ManagedScriptAddress", addr, ma)
|
||||
return nil, e
|
||||
}
|
||||
return msa.Script()
|
||||
}
|
||||
|
||||
// txToOutputs creates a signed transaction which includes each output from
|
||||
// outputs. Previous outputs to reedeem are chosen from the passed account's
|
||||
// UTXO set and minconf policy. An additional output may be added to return
|
||||
// change to the wallet. An appropriate fee is included based on the wallet's
|
||||
// current relay fee. The wallet must be unlocked to create the transaction.
|
||||
//
|
||||
// NOTE: The dryRun argument can be set true to create a tx that doesn't alter
|
||||
// the database. A tx created with this set to true will intentionally have no
|
||||
// input scripts added and SHOULD NOT be broadcasted.
|
||||
func (w *Wallet) txToOutputs(outputs []*wire.TxOut, account uint32,
|
||||
minconf int32, feeSatPerKb btcutil.Amount, dryRun bool) (
|
||||
tx *txauthor.AuthoredTx, err error) {
|
||||
|
||||
chainClient, err := w.requireChainClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbtx, err := w.db.BeginReadWriteTx()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer dbtx.Rollback()
|
||||
|
||||
addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||||
|
||||
// Get current block's height and hash.
|
||||
bs, err := chainClient.BlockStamp()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
eligible, err := w.findEligibleOutputs(dbtx, account, minconf, bs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inputSource := makeInputSource(eligible)
|
||||
changeSource := func() ([]byte, error) {
|
||||
// Derive the change output script. We'll use the default key
|
||||
// scope responsible for P2WPKH addresses to do so. As a hack to
|
||||
// allow spending from the imported account, change addresses
|
||||
// are created from account 0.
|
||||
var changeAddr btcutil.Address
|
||||
var err error
|
||||
changeKeyScope := waddrmgr.KeyScopeBIP0084
|
||||
if account == waddrmgr.ImportedAddrAccount {
|
||||
changeAddr, err = w.newChangeAddress(
|
||||
addrmgrNs, 0, changeKeyScope,
|
||||
)
|
||||
} else {
|
||||
changeAddr, err = w.newChangeAddress(
|
||||
addrmgrNs, account, changeKeyScope,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return txscript.PayToAddrScript(changeAddr)
|
||||
}
|
||||
tx, err = txauthor.NewUnsignedTransaction(outputs, feeSatPerKb,
|
||||
inputSource, changeSource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Randomize change position, if change exists, before signing. This
|
||||
// doesn't affect the serialize size, so the change amount will still
|
||||
// be valid.
|
||||
if tx.ChangeIndex >= 0 {
|
||||
tx.RandomizeChangePosition()
|
||||
}
|
||||
|
||||
// If a dry run was requested, we return now before adding the input
|
||||
// scripts, and don't commit the database transaction. The DB will be
|
||||
// rolled back when this method returns to ensure the dry run didn't
|
||||
// alter the DB in any way.
|
||||
if dryRun {
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
err = tx.AddAllInputScripts(secretSource{w.Manager, addrmgrNs})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = validateMsgTx(tx.Tx, tx.PrevScripts, tx.PrevInputValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := dbtx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tx.ChangeIndex >= 0 && account == waddrmgr.ImportedAddrAccount {
|
||||
changeAmount := btcutil.Amount(tx.Tx.TxOut[tx.ChangeIndex].Value)
|
||||
log.Warnf("Spend from imported account produced change: moving"+
|
||||
" %v from imported account into default account.", changeAmount)
|
||||
}
|
||||
|
||||
// Finally, we'll request the backend to notify us of the transaction
|
||||
// that pays to the change address, if there is one, when it confirms.
|
||||
if tx.ChangeIndex >= 0 {
|
||||
changePkScript := tx.Tx.TxOut[tx.ChangeIndex].PkScript
|
||||
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
|
||||
changePkScript, w.chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := chainClient.NotifyReceived(addrs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func (w *Wallet) findEligibleOutputs(dbtx walletdb.ReadTx, account uint32, minconf int32, bs *waddrmgr.BlockStamp) ([]wtxmgr.Credit, error) {
|
||||
addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
|
||||
txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
|
||||
|
||||
unspent, err := w.TxStore.UnspentOutputs(txmgrNs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: Eventually all of these filters (except perhaps output locking)
|
||||
// should be handled by the call to UnspentOutputs (or similar).
|
||||
// Because one of these filters requires matching the output script to
|
||||
// the desired account, this change depends on making wtxmgr a waddrmgr
|
||||
// dependancy and requesting unspent outputs for a single account.
|
||||
eligible := make([]wtxmgr.Credit, 0, len(unspent))
|
||||
for i := range unspent {
|
||||
output := &unspent[i]
|
||||
|
||||
// Only include this output if it meets the required number of
|
||||
// confirmations. Coinbase transactions must have have reached
|
||||
// maturity before their outputs may be spent.
|
||||
if !confirmed(minconf, output.Height, bs.Height) {
|
||||
continue
|
||||
}
|
||||
if output.FromCoinBase {
|
||||
target := int32(w.chainParams.CoinbaseMaturity)
|
||||
if !confirmed(target, output.Height, bs.Height) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Locked unspent outputs are skipped.
|
||||
if w.LockedOutpoint(output.OutPoint) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Only include the output if it is associated with the passed
|
||||
// account.
|
||||
//
|
||||
// TODO: Handle multisig outputs by determining if enough of the
|
||||
// addresses are controlled.
|
||||
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
|
||||
output.PkScript, w.chainParams)
|
||||
if err != nil || len(addrs) != 1 {
|
||||
continue
|
||||
}
|
||||
_, addrAcct, err := w.Manager.AddrAccount(addrmgrNs, addrs[0])
|
||||
if err != nil || addrAcct != account {
|
||||
continue
|
||||
}
|
||||
eligible = append(eligible, *output)
|
||||
}
|
||||
return eligible, nil
|
||||
}
|
||||
|
||||
// validateMsgTx verifies transaction input scripts for tx. All previous output
|
||||
// scripts from outputs redeemed by the transaction, in the same order they are
|
||||
// spent, must be passed in the prevScripts slice.
|
||||
func validateMsgTx(tx *wire.MsgTx, prevScripts [][]byte, inputValues []btcutil.Amount) error {
|
||||
hashCache := txscript.NewTxSigHashes(tx)
|
||||
for i, prevScript := range prevScripts {
|
||||
vm, err := txscript.NewEngine(prevScript, tx, i,
|
||||
txscript.StandardVerifyFlags, nil, hashCache, int64(inputValues[i]))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create script engine: %s", err)
|
||||
}
|
||||
err = vm.Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot validate transaction: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package wallet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// checkCreateDir checks that the path exists and is a directory.
|
||||
// If path does not exist, it is created.
|
||||
func checkCreateDir(path string) error {
|
||||
if fi, err := os.Stat(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Attempt data directory creation
|
||||
if err = os.MkdirAll(path, 0700); err != nil {
|
||||
return fmt.Errorf("cannot create directory: %s", err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("error checking directory: %s", err)
|
||||
}
|
||||
} else {
|
||||
if !fi.IsDir() {
|
||||
return fmt.Errorf("path '%s' is not a directory", path)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package wallet provides ...
|
||||
TODO: Flesh out this section
|
||||
|
||||
Overview
|
||||
|
||||
*/
|
||||
package wallet
|
|
@ -0,0 +1,284 @@
|
|||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package wallet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcwallet/internal/prompt"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
)
|
||||
|
||||
const (
|
||||
walletDbName = "wallet.db"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrLoaded describes the error condition of attempting to load or
|
||||
// create a wallet when the loader has already done so.
|
||||
ErrLoaded = errors.New("wallet already loaded")
|
||||
|
||||
// ErrNotLoaded describes the error condition of attempting to close a
|
||||
// loaded wallet when a wallet has not been loaded.
|
||||
ErrNotLoaded = errors.New("wallet is not loaded")
|
||||
|
||||
// ErrExists describes the error condition of attempting to create a new
|
||||
// wallet when one exists already.
|
||||
ErrExists = errors.New("wallet already exists")
|
||||
)
|
||||
|
||||
// Loader implements the creating of new and opening of existing wallets, while
|
||||
// providing a callback system for other subsystems to handle the loading of a
|
||||
// wallet. This is primarily intended for use by the RPC servers, to enable
|
||||
// methods and services which require the wallet when the wallet is loaded by
|
||||
// another subsystem.
|
||||
//
|
||||
// Loader is safe for concurrent access.
|
||||
type Loader struct {
|
||||
callbacks []func(*Wallet)
|
||||
chainParams *chaincfg.Params
|
||||
dbDirPath string
|
||||
noFreelistSync bool
|
||||
recoveryWindow uint32
|
||||
wallet *Wallet
|
||||
db walletdb.DB
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewLoader constructs a Loader with an optional recovery window. If the
|
||||
// recovery window is non-zero, the wallet will attempt to recovery addresses
|
||||
// starting from the last SyncedTo height.
|
||||
func NewLoader(chainParams *chaincfg.Params, dbDirPath string,
|
||||
noFreelistSync bool, recoveryWindow uint32) *Loader {
|
||||
|
||||
return &Loader{
|
||||
chainParams: chainParams,
|
||||
dbDirPath: dbDirPath,
|
||||
noFreelistSync: noFreelistSync,
|
||||
recoveryWindow: recoveryWindow,
|
||||
}
|
||||
}
|
||||
|
||||
// onLoaded executes each added callback and prevents loader from loading any
|
||||
// additional wallets. Requires mutex to be locked.
|
||||
func (l *Loader) onLoaded(w *Wallet, db walletdb.DB) {
|
||||
for _, fn := range l.callbacks {
|
||||
fn(w)
|
||||
}
|
||||
|
||||
l.wallet = w
|
||||
l.db = db
|
||||
l.callbacks = nil // not needed anymore
|
||||
}
|
||||
|
||||
// RunAfterLoad adds a function to be executed when the loader creates or opens
|
||||
// a wallet. Functions are executed in a single goroutine in the order they are
|
||||
// added.
|
||||
func (l *Loader) RunAfterLoad(fn func(*Wallet)) {
|
||||
l.mu.Lock()
|
||||
if l.wallet != nil {
|
||||
w := l.wallet
|
||||
l.mu.Unlock()
|
||||
fn(w)
|
||||
} else {
|
||||
l.callbacks = append(l.callbacks, fn)
|
||||
l.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// CreateNewWallet creates a new wallet using the provided public and private
|
||||
// passphrases. The seed is optional. If non-nil, addresses are derived from
|
||||
// this seed. If nil, a secure random seed is generated.
|
||||
func (l *Loader) CreateNewWallet(pubPassphrase, privPassphrase, seed []byte,
|
||||
bday time.Time) (*Wallet, error) {
|
||||
|
||||
return l.createNewWallet(
|
||||
pubPassphrase, privPassphrase, seed, bday, false,
|
||||
)
|
||||
}
|
||||
|
||||
// CreateNewWatchingOnlyWallet creates a new wallet using the provided
|
||||
// public passphrase. No seed or private passphrase may be provided
|
||||
// since the wallet is watching-only.
|
||||
func (l *Loader) CreateNewWatchingOnlyWallet(pubPassphrase []byte,
|
||||
bday time.Time) (*Wallet, error) {
|
||||
|
||||
return l.createNewWallet(
|
||||
pubPassphrase, nil, nil, bday, true,
|
||||
)
|
||||
}
|
||||
|
||||
func (l *Loader) createNewWallet(pubPassphrase, privPassphrase,
|
||||
seed []byte, bday time.Time, isWatchingOnly bool) (*Wallet, error) {
|
||||
|
||||
defer l.mu.Unlock()
|
||||
l.mu.Lock()
|
||||
|
||||
if l.wallet != nil {
|
||||
return nil, ErrLoaded
|
||||
}
|
||||
|
||||
dbPath := filepath.Join(l.dbDirPath, walletDbName)
|
||||
exists, err := fileExists(dbPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exists {
|
||||
return nil, ErrExists
|
||||
}
|
||||
|
||||
// Create the wallet database backed by bolt db.
|
||||
err = os.MkdirAll(l.dbDirPath, 0700)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db, err := walletdb.Create("bdb", dbPath, l.noFreelistSync)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize the newly created database for the wallet before opening.
|
||||
if isWatchingOnly {
|
||||
err = CreateWatchingOnly(db, pubPassphrase, l.chainParams, bday)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
err = Create(
|
||||
db, pubPassphrase, privPassphrase, seed, l.chainParams, bday,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Open the newly-created wallet.
|
||||
w, err := Open(db, pubPassphrase, nil, l.chainParams, l.recoveryWindow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w.Start()
|
||||
|
||||
l.onLoaded(w, db)
|
||||
return w, nil
|
||||
}
|
||||
|
||||
var errNoConsole = errors.New("db upgrade requires console access for additional input")
|
||||
|
||||
func noConsole() ([]byte, error) {
|
||||
return nil, errNoConsole
|
||||
}
|
||||
|
||||
// OpenExistingWallet opens the wallet from the loader's wallet database path
|
||||
// and the public passphrase. If the loader is being called by a context where
|
||||
// standard input prompts may be used during wallet upgrades, setting
|
||||
// canConsolePrompt will enables these prompts.
|
||||
func (l *Loader) OpenExistingWallet(pubPassphrase []byte, canConsolePrompt bool) (*Wallet, error) {
|
||||
defer l.mu.Unlock()
|
||||
l.mu.Lock()
|
||||
|
||||
if l.wallet != nil {
|
||||
return nil, ErrLoaded
|
||||
}
|
||||
|
||||
// Ensure that the network directory exists.
|
||||
if err := checkCreateDir(l.dbDirPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Open the database using the boltdb backend.
|
||||
dbPath := filepath.Join(l.dbDirPath, walletDbName)
|
||||
db, err := walletdb.Open("bdb", dbPath, l.noFreelistSync)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to open database: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cbs *waddrmgr.OpenCallbacks
|
||||
if canConsolePrompt {
|
||||
cbs = &waddrmgr.OpenCallbacks{
|
||||
ObtainSeed: prompt.ProvideSeed,
|
||||
ObtainPrivatePass: prompt.ProvidePrivPassphrase,
|
||||
}
|
||||
} else {
|
||||
cbs = &waddrmgr.OpenCallbacks{
|
||||
ObtainSeed: noConsole,
|
||||
ObtainPrivatePass: noConsole,
|
||||
}
|
||||
}
|
||||
w, err := Open(db, pubPassphrase, cbs, l.chainParams, l.recoveryWindow)
|
||||
if err != nil {
|
||||
// If opening the wallet fails (e.g. because of wrong
|
||||
// passphrase), we must close the backing database to
|
||||
// allow future calls to walletdb.Open().
|
||||
e := db.Close()
|
||||
if e != nil {
|
||||
log.Warnf("Error closing database: %v", e)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
w.Start()
|
||||
|
||||
l.onLoaded(w, db)
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// WalletExists returns whether a file exists at the loader's database path.
|
||||
// This may return an error for unexpected I/O failures.
|
||||
func (l *Loader) WalletExists() (bool, error) {
|
||||
dbPath := filepath.Join(l.dbDirPath, walletDbName)
|
||||
return fileExists(dbPath)
|
||||
}
|
||||
|
||||
// LoadedWallet returns the loaded wallet, if any, and a bool for whether the
|
||||
// wallet has been loaded or not. If true, the wallet pointer should be safe to
|
||||
// dereference.
|
||||
func (l *Loader) LoadedWallet() (*Wallet, bool) {
|
||||
l.mu.Lock()
|
||||
w := l.wallet
|
||||
l.mu.Unlock()
|
||||
return w, w != nil
|
||||
}
|
||||
|
||||
// UnloadWallet stops the loaded wallet, if any, and closes the wallet database.
|
||||
// This returns ErrNotLoaded if the wallet has not been loaded with
|
||||
// CreateNewWallet or LoadExistingWallet. The Loader may be reused if this
|
||||
// function returns without error.
|
||||
func (l *Loader) UnloadWallet() error {
|
||||
defer l.mu.Unlock()
|
||||
l.mu.Lock()
|
||||
|
||||
if l.wallet == nil {
|
||||
return ErrNotLoaded
|
||||
}
|
||||
|
||||
l.wallet.Stop()
|
||||
l.wallet.WaitForShutdown()
|
||||
err := l.db.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.wallet = nil
|
||||
l.db = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func fileExists(filePath string) (bool, error) {
|
||||
_, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package wallet
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btclog"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/btcsuite/btcwallet/walletdb/migration"
|
||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||
)
|
||||
|
||||
// log is a logger that is initialized with no output filters. This
|
||||
// means the package will not perform any logging by default until the caller
|
||||
// requests it.
|
||||
var log btclog.Logger
|
||||
|
||||
// The default amount of logging is none.
|
||||
func init() {
|
||||
DisableLog()
|
||||
}
|
||||
|
||||
// DisableLog disables all library log output. Logging output is disabled
|
||||
// by default until either UseLogger or SetLogWriter are called.
|
||||
func DisableLog() {
|
||||
UseLogger(btclog.Disabled)
|
||||
}
|
||||
|
||||
// UseLogger uses a specified Logger to output package logging info.
|
||||
// This should be used in preference to SetLogWriter if the caller is also
|
||||
// using btclog.
|
||||
func UseLogger(logger btclog.Logger) {
|
||||
log = logger
|
||||
|
||||
migration.UseLogger(logger)
|
||||
waddrmgr.UseLogger(logger)
|
||||
wtxmgr.UseLogger(logger)
|
||||
}
|
||||
|
||||
// LogClosure is a closure that can be printed with %v to be used to
|
||||
// generate expensive-to-create data for a detailed log level and avoid doing
|
||||
// the work if the data isn't printed.
|
||||
type logClosure func() string
|
||||
|
||||
// String invokes the log closure and returns the results string.
|
||||
func (c logClosure) String() string {
|
||||
return c()
|
||||
}
|
||||
|
||||
// newLogClosure returns a new closure over the passed function which allows
|
||||
// it to be used as a parameter in a logging function that is only invoked when
|
||||
// the logging level is such that the message will actually be logged.
|
||||
func newLogClosure(c func() string) logClosure {
|
||||
return logClosure(c)
|
||||
}
|
||||
|
||||
// pickNoun returns the singular or plural form of a noun depending
|
||||
// on the count n.
|
||||
func pickNoun(n int, singular, plural string) string {
|
||||
if n == 1 {
|
||||
return singular
|
||||
}
|
||||
return plural
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/chain"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
)
|
||||
|
||||
type mockChainClient struct {
|
||||
}
|
||||
|
||||
var _ chain.Interface = (*mockChainClient)(nil)
|
||||
|
||||
func (m *mockChainClient) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockChainClient) Stop() {
|
||||
}
|
||||
|
||||
func (m *mockChainClient) WaitForShutdown() {}
|
||||
|
||||
func (m *mockChainClient) GetBestBlock() (*chainhash.Hash, int32, error) {
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
func (m *mockChainClient) GetBlock(*chainhash.Hash) (*wire.MsgBlock, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockChainClient) GetBlockHash(int64) (*chainhash.Hash, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockChainClient) GetBlockHeader(*chainhash.Hash) (*wire.BlockHeader,
|
||||
error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockChainClient) IsCurrent() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *mockChainClient) FilterBlocks(*chain.FilterBlocksRequest) (
|
||||
*chain.FilterBlocksResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockChainClient) BlockStamp() (*waddrmgr.BlockStamp, error) {
|
||||
return &waddrmgr.BlockStamp{
|
||||
Height: 500000,
|
||||
Hash: chainhash.Hash{},
|
||||
Timestamp: time.Unix(1234, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *mockChainClient) SendRawTransaction(*wire.MsgTx, bool) (
|
||||
*chainhash.Hash, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockChainClient) Rescan(*chainhash.Hash, []btcutil.Address,
|
||||
map[wire.OutPoint]btcutil.Address) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockChainClient) NotifyReceived([]btcutil.Address) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockChainClient) NotifyBlocks() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockChainClient) Notifications() <-chan interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockChainClient) BackEnd() string {
|
||||
return "mock"
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
// Copyright (c) 2017 The btcsuite developers
|
||||
// Copyright (c) 2016 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package wallet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
)
|
||||
|
||||
// MakeMultiSigScript creates a multi-signature script that can be redeemed with
|
||||
// nRequired signatures of the passed keys and addresses. If the address is a
|
||||
// P2PKH address, the associated pubkey is looked up by the wallet if possible,
|
||||
// otherwise an error is returned for a missing pubkey.
|
||||
//
|
||||
// This function only works with pubkeys and P2PKH addresses derived from them.
|
||||
func (w *Wallet) MakeMultiSigScript(addrs []btcutil.Address, nRequired int) ([]byte, error) {
|
||||
pubKeys := make([]*btcutil.AddressPubKey, len(addrs))
|
||||
|
||||
var dbtx walletdb.ReadTx
|
||||
var addrmgrNs walletdb.ReadBucket
|
||||
defer func() {
|
||||
if dbtx != nil {
|
||||
dbtx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
// The address list will made up either of addreseses (pubkey hash), for
|
||||
// which we need to look up the keys in wallet, straight pubkeys, or a
|
||||
// mixture of the two.
|
||||
for i, addr := range addrs {
|
||||
switch addr := addr.(type) {
|
||||
default:
|
||||
return nil, errors.New("cannot make multisig script for " +
|
||||
"a non-secp256k1 public key or P2PKH address")
|
||||
|
||||
case *btcutil.AddressPubKey:
|
||||
pubKeys[i] = addr
|
||||
|
||||
case *btcutil.AddressPubKeyHash:
|
||||
if dbtx == nil {
|
||||
var err error
|
||||
dbtx, err = w.db.BeginReadTx()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addrmgrNs = dbtx.ReadBucket(waddrmgrNamespaceKey)
|
||||
}
|
||||
addrInfo, err := w.Manager.Address(addrmgrNs, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serializedPubKey := addrInfo.(waddrmgr.ManagedPubKeyAddress).
|
||||
PubKey().SerializeCompressed()
|
||||
|
||||
pubKeyAddr, err := btcutil.NewAddressPubKey(
|
||||
serializedPubKey, w.chainParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubKeys[i] = pubKeyAddr
|
||||
}
|
||||
}
|
||||
|
||||
return txscript.MultiSigScript(pubKeys, nRequired)
|
||||
}
|
||||
|
||||
// ImportP2SHRedeemScript adds a P2SH redeem script to the wallet.
|
||||
func (w *Wallet) ImportP2SHRedeemScript(script []byte) (*btcutil.AddressScriptHash, error) {
|
||||
var p2shAddr *btcutil.AddressScriptHash
|
||||
err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
|
||||
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||||
|
||||
// TODO(oga) blockstamp current block?
|
||||
bs := &waddrmgr.BlockStamp{
|
||||
Hash: *w.ChainParams().GenesisHash,
|
||||
Height: 0,
|
||||
}
|
||||
|
||||
// As this is a regular P2SH script, we'll import this into the
|
||||
// BIP0044 scope.
|
||||
bip44Mgr, err := w.Manager.FetchScopedKeyManager(
|
||||
waddrmgr.KeyScopeBIP0084,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addrInfo, err := bip44Mgr.ImportScript(addrmgrNs, script, bs)
|
||||
if err != nil {
|
||||
// Don't care if it's already there, but still have to
|
||||
// set the p2shAddr since the address manager didn't
|
||||
// return anything useful.
|
||||
if waddrmgr.IsError(err, waddrmgr.ErrDuplicateAddress) {
|
||||
// This function will never error as it always
|
||||
// hashes the script to the correct length.
|
||||
p2shAddr, _ = btcutil.NewAddressScriptHash(script,
|
||||
w.chainParams)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
p2shAddr = addrInfo.Address().(*btcutil.AddressScriptHash)
|
||||
return nil
|
||||
})
|
||||
return p2shAddr, err
|
||||
}
|
|
@ -0,0 +1,637 @@
|
|||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package wallet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||
)
|
||||
|
||||
// TODO: It would be good to send errors during notification creation to the rpc
|
||||
// server instead of just logging them here so the client is aware that wallet
|
||||
// isn't working correctly and notifications are missing.
|
||||
|
||||
// TODO: Anything dealing with accounts here is expensive because the database
|
||||
// is not organized correctly for true account support, but do the slow thing
|
||||
// instead of the easy thing since the db can be fixed later, and we want the
|
||||
// api correct now.
|
||||
|
||||
// NotificationServer is a server that interested clients may hook into to
|
||||
// receive notifications of changes in a wallet. A client is created for each
|
||||
// registered notification. Clients are guaranteed to receive messages in the
|
||||
// order wallet created them, but there is no guaranteed synchronization between
|
||||
// different clients.
|
||||
type NotificationServer struct {
|
||||
transactions []chan *TransactionNotifications
|
||||
currentTxNtfn *TransactionNotifications // coalesce this since wallet does not add mined txs together
|
||||
spentness map[uint32][]chan *SpentnessNotifications
|
||||
accountClients []chan *AccountNotification
|
||||
mu sync.Mutex // Only protects registered client channels
|
||||
wallet *Wallet // smells like hacks
|
||||
}
|
||||
|
||||
func newNotificationServer(wallet *Wallet) *NotificationServer {
|
||||
return &NotificationServer{
|
||||
spentness: make(map[uint32][]chan *SpentnessNotifications),
|
||||
wallet: wallet,
|
||||
}
|
||||
}
|
||||
|
||||
func lookupInputAccount(dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetails, deb wtxmgr.DebitRecord) uint32 {
|
||||
addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
|
||||
txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
|
||||
|
||||
// TODO: Debits should record which account(s?) they
|
||||
// debit from so this doesn't need to be looked up.
|
||||
prevOP := &details.MsgTx.TxIn[deb.Index].PreviousOutPoint
|
||||
prev, err := w.TxStore.TxDetails(txmgrNs, &prevOP.Hash)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot query previous transaction details for %v: %v", prevOP.Hash, err)
|
||||
return 0
|
||||
}
|
||||
if prev == nil {
|
||||
log.Errorf("Missing previous transaction %v", prevOP.Hash)
|
||||
return 0
|
||||
}
|
||||
prevOut := prev.MsgTx.TxOut[prevOP.Index]
|
||||
_, addrs, _, err := txscript.ExtractPkScriptAddrs(prevOut.PkScript, w.chainParams)
|
||||
var inputAcct uint32
|
||||
if err == nil && len(addrs) > 0 {
|
||||
_, inputAcct, err = w.Manager.AddrAccount(addrmgrNs, addrs[0])
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Cannot fetch account for previous output %v: %v", prevOP, err)
|
||||
inputAcct = 0
|
||||
}
|
||||
return inputAcct
|
||||
}
|
||||
|
||||
func lookupOutputChain(dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetails,
|
||||
cred wtxmgr.CreditRecord) (account uint32, internal bool) {
|
||||
|
||||
addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
|
||||
|
||||
output := details.MsgTx.TxOut[cred.Index]
|
||||
_, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript, w.chainParams)
|
||||
var ma waddrmgr.ManagedAddress
|
||||
if err == nil && len(addrs) > 0 {
|
||||
ma, err = w.Manager.Address(addrmgrNs, addrs[0])
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Cannot fetch account for wallet output: %v", err)
|
||||
} else {
|
||||
account = ma.Account()
|
||||
internal = ma.Internal()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func makeTxSummary(dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetails) TransactionSummary {
|
||||
serializedTx := details.SerializedTx
|
||||
if serializedTx == nil {
|
||||
var buf bytes.Buffer
|
||||
err := details.MsgTx.Serialize(&buf)
|
||||
if err != nil {
|
||||
log.Errorf("Transaction serialization: %v", err)
|
||||
}
|
||||
serializedTx = buf.Bytes()
|
||||
}
|
||||
var fee btcutil.Amount
|
||||
if len(details.Debits) == len(details.MsgTx.TxIn) {
|
||||
for _, deb := range details.Debits {
|
||||
fee += deb.Amount
|
||||
}
|
||||
for _, txOut := range details.MsgTx.TxOut {
|
||||
fee -= btcutil.Amount(txOut.Value)
|
||||
}
|
||||
}
|
||||
var inputs []TransactionSummaryInput
|
||||
if len(details.Debits) != 0 {
|
||||
inputs = make([]TransactionSummaryInput, len(details.Debits))
|
||||
for i, d := range details.Debits {
|
||||
inputs[i] = TransactionSummaryInput{
|
||||
Index: d.Index,
|
||||
PreviousAccount: lookupInputAccount(dbtx, w, details, d),
|
||||
PreviousAmount: d.Amount,
|
||||
}
|
||||
}
|
||||
}
|
||||
outputs := make([]TransactionSummaryOutput, 0, len(details.MsgTx.TxOut))
|
||||
for i := range details.MsgTx.TxOut {
|
||||
credIndex := len(outputs)
|
||||
mine := len(details.Credits) > credIndex && details.Credits[credIndex].Index == uint32(i)
|
||||
if !mine {
|
||||
continue
|
||||
}
|
||||
acct, internal := lookupOutputChain(dbtx, w, details, details.Credits[credIndex])
|
||||
output := TransactionSummaryOutput{
|
||||
Index: uint32(i),
|
||||
Account: acct,
|
||||
Internal: internal,
|
||||
}
|
||||
outputs = append(outputs, output)
|
||||
}
|
||||
return TransactionSummary{
|
||||
Hash: &details.Hash,
|
||||
Transaction: serializedTx,
|
||||
MyInputs: inputs,
|
||||
MyOutputs: outputs,
|
||||
Fee: fee,
|
||||
Timestamp: details.Received.Unix(),
|
||||
Label: details.Label,
|
||||
}
|
||||
}
|
||||
|
||||
func totalBalances(dbtx walletdb.ReadTx, w *Wallet, m map[uint32]btcutil.Amount) error {
|
||||
addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
|
||||
unspent, err := w.TxStore.UnspentOutputs(dbtx.ReadBucket(wtxmgrNamespaceKey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range unspent {
|
||||
output := &unspent[i]
|
||||
var outputAcct uint32
|
||||
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
|
||||
output.PkScript, w.chainParams)
|
||||
if err == nil && len(addrs) > 0 {
|
||||
_, outputAcct, err = w.Manager.AddrAccount(addrmgrNs, addrs[0])
|
||||
}
|
||||
if err == nil {
|
||||
_, ok := m[outputAcct]
|
||||
if ok {
|
||||
m[outputAcct] += output.Amount
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func flattenBalanceMap(m map[uint32]btcutil.Amount) []AccountBalance {
|
||||
s := make([]AccountBalance, 0, len(m))
|
||||
for k, v := range m {
|
||||
s = append(s, AccountBalance{Account: k, TotalBalance: v})
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func relevantAccounts(w *Wallet, m map[uint32]btcutil.Amount, txs []TransactionSummary) {
|
||||
for _, tx := range txs {
|
||||
for _, d := range tx.MyInputs {
|
||||
m[d.PreviousAccount] = 0
|
||||
}
|
||||
for _, c := range tx.MyOutputs {
|
||||
m[c.Account] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *NotificationServer) notifyUnminedTransaction(dbtx walletdb.ReadTx, details *wtxmgr.TxDetails) {
|
||||
// Sanity check: should not be currently coalescing a notification for
|
||||
// mined transactions at the same time that an unmined tx is notified.
|
||||
if s.currentTxNtfn != nil {
|
||||
log.Errorf("Notifying unmined tx notification (%s) while creating notification for blocks",
|
||||
details.Hash)
|
||||
}
|
||||
|
||||
defer s.mu.Unlock()
|
||||
s.mu.Lock()
|
||||
clients := s.transactions
|
||||
if len(clients) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
unminedTxs := []TransactionSummary{makeTxSummary(dbtx, s.wallet, details)}
|
||||
unminedHashes, err := s.wallet.TxStore.UnminedTxHashes(dbtx.ReadBucket(wtxmgrNamespaceKey))
|
||||
if err != nil {
|
||||
log.Errorf("Cannot fetch unmined transaction hashes: %v", err)
|
||||
return
|
||||
}
|
||||
bals := make(map[uint32]btcutil.Amount)
|
||||
relevantAccounts(s.wallet, bals, unminedTxs)
|
||||
err = totalBalances(dbtx, s.wallet, bals)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot determine balances for relevant accounts: %v", err)
|
||||
return
|
||||
}
|
||||
n := &TransactionNotifications{
|
||||
UnminedTransactions: unminedTxs,
|
||||
UnminedTransactionHashes: unminedHashes,
|
||||
NewBalances: flattenBalanceMap(bals),
|
||||
}
|
||||
for _, c := range clients {
|
||||
c <- n
|
||||
}
|
||||
}
|
||||
|
||||
func (s *NotificationServer) notifyDetachedBlock(hash *chainhash.Hash) {
|
||||
if s.currentTxNtfn == nil {
|
||||
s.currentTxNtfn = &TransactionNotifications{}
|
||||
}
|
||||
s.currentTxNtfn.DetachedBlocks = append(s.currentTxNtfn.DetachedBlocks, hash)
|
||||
}
|
||||
|
||||
func (s *NotificationServer) notifyMinedTransaction(dbtx walletdb.ReadTx, details *wtxmgr.TxDetails, block *wtxmgr.BlockMeta) {
|
||||
if s.currentTxNtfn == nil {
|
||||
s.currentTxNtfn = &TransactionNotifications{}
|
||||
}
|
||||
n := len(s.currentTxNtfn.AttachedBlocks)
|
||||
if n == 0 || *s.currentTxNtfn.AttachedBlocks[n-1].Hash != block.Hash {
|
||||
s.currentTxNtfn.AttachedBlocks = append(s.currentTxNtfn.AttachedBlocks, Block{
|
||||
Hash: &block.Hash,
|
||||
Height: block.Height,
|
||||
Timestamp: block.Time.Unix(),
|
||||
})
|
||||
n++
|
||||
}
|
||||
txs := s.currentTxNtfn.AttachedBlocks[n-1].Transactions
|
||||
s.currentTxNtfn.AttachedBlocks[n-1].Transactions =
|
||||
append(txs, makeTxSummary(dbtx, s.wallet, details))
|
||||
}
|
||||
|
||||
func (s *NotificationServer) notifyAttachedBlock(dbtx walletdb.ReadTx, block *wtxmgr.BlockMeta) {
|
||||
if s.currentTxNtfn == nil {
|
||||
s.currentTxNtfn = &TransactionNotifications{}
|
||||
}
|
||||
|
||||
// Add block details if it wasn't already included for previously
|
||||
// notified mined transactions.
|
||||
n := len(s.currentTxNtfn.AttachedBlocks)
|
||||
if n == 0 || *s.currentTxNtfn.AttachedBlocks[n-1].Hash != block.Hash {
|
||||
s.currentTxNtfn.AttachedBlocks = append(s.currentTxNtfn.AttachedBlocks, Block{
|
||||
Hash: &block.Hash,
|
||||
Height: block.Height,
|
||||
Timestamp: block.Time.Unix(),
|
||||
})
|
||||
}
|
||||
|
||||
// For now (until notification coalescing isn't necessary) just use
|
||||
// chain length to determine if this is the new best block.
|
||||
if s.wallet.ChainSynced() {
|
||||
if len(s.currentTxNtfn.DetachedBlocks) >= len(s.currentTxNtfn.AttachedBlocks) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
defer s.mu.Unlock()
|
||||
s.mu.Lock()
|
||||
clients := s.transactions
|
||||
if len(clients) == 0 {
|
||||
s.currentTxNtfn = nil
|
||||
return
|
||||
}
|
||||
|
||||
// The UnminedTransactions field is intentionally not set. Since the
|
||||
// hashes of all detached blocks are reported, and all transactions
|
||||
// moved from a mined block back to unconfirmed are either in the
|
||||
// UnminedTransactionHashes slice or don't exist due to conflicting with
|
||||
// a mined transaction in the new best chain, there is no possiblity of
|
||||
// a new, previously unseen transaction appearing in unconfirmed.
|
||||
|
||||
txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
|
||||
unminedHashes, err := s.wallet.TxStore.UnminedTxHashes(txmgrNs)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot fetch unmined transaction hashes: %v", err)
|
||||
return
|
||||
}
|
||||
s.currentTxNtfn.UnminedTransactionHashes = unminedHashes
|
||||
|
||||
bals := make(map[uint32]btcutil.Amount)
|
||||
for _, b := range s.currentTxNtfn.AttachedBlocks {
|
||||
relevantAccounts(s.wallet, bals, b.Transactions)
|
||||
}
|
||||
err = totalBalances(dbtx, s.wallet, bals)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot determine balances for relevant accounts: %v", err)
|
||||
return
|
||||
}
|
||||
s.currentTxNtfn.NewBalances = flattenBalanceMap(bals)
|
||||
|
||||
for _, c := range clients {
|
||||
c <- s.currentTxNtfn
|
||||
}
|
||||
s.currentTxNtfn = nil
|
||||
}
|
||||
|
||||
// TransactionNotifications is a notification of changes to the wallet's
|
||||
// transaction set and the current chain tip that wallet is considered to be
|
||||
// synced with. All transactions added to the blockchain are organized by the
|
||||
// block they were mined in.
|
||||
//
|
||||
// During a chain switch, all removed block hashes are included. Detached
|
||||
// blocks are sorted in the reverse order they were mined. Attached blocks are
|
||||
// sorted in the order mined.
|
||||
//
|
||||
// All newly added unmined transactions are included. Removed unmined
|
||||
// transactions are not explicitly included. Instead, the hashes of all
|
||||
// transactions still unmined are included.
|
||||
//
|
||||
// If any transactions were involved, each affected account's new total balance
|
||||
// is included.
|
||||
//
|
||||
// TODO: Because this includes stuff about blocks and can be fired without any
|
||||
// changes to transactions, it needs a better name.
|
||||
type TransactionNotifications struct {
|
||||
AttachedBlocks []Block
|
||||
DetachedBlocks []*chainhash.Hash
|
||||
UnminedTransactions []TransactionSummary
|
||||
UnminedTransactionHashes []*chainhash.Hash
|
||||
NewBalances []AccountBalance
|
||||
}
|
||||
|
||||
// Block contains the properties and all relevant transactions of an attached
|
||||
// block.
|
||||
type Block struct {
|
||||
Hash *chainhash.Hash
|
||||
Height int32
|
||||
Timestamp int64
|
||||
Transactions []TransactionSummary
|
||||
}
|
||||
|
||||
// TransactionSummary contains a transaction relevant to the wallet and marks
|
||||
// which inputs and outputs were relevant.
|
||||
type TransactionSummary struct {
|
||||
Hash *chainhash.Hash
|
||||
Transaction []byte
|
||||
MyInputs []TransactionSummaryInput
|
||||
MyOutputs []TransactionSummaryOutput
|
||||
Fee btcutil.Amount
|
||||
Timestamp int64
|
||||
Label string
|
||||
}
|
||||
|
||||
// TransactionSummaryInput describes a transaction input that is relevant to the
|
||||
// wallet. The Index field marks the transaction input index of the transaction
|
||||
// (not included here). The PreviousAccount and PreviousAmount fields describe
|
||||
// how much this input debits from a wallet account.
|
||||
type TransactionSummaryInput struct {
|
||||
Index uint32
|
||||
PreviousAccount uint32
|
||||
PreviousAmount btcutil.Amount
|
||||
}
|
||||
|
||||
// TransactionSummaryOutput describes wallet properties of a transaction output
|
||||
// controlled by the wallet. The Index field marks the transaction output index
|
||||
// of the transaction (not included here).
|
||||
type TransactionSummaryOutput struct {
|
||||
Index uint32
|
||||
Account uint32
|
||||
Internal bool
|
||||
}
|
||||
|
||||
// AccountBalance associates a total (zero confirmation) balance with an
|
||||
// account. Balances for other minimum confirmation counts require more
|
||||
// expensive logic and it is not clear which minimums a client is interested in,
|
||||
// so they are not included.
|
||||
type AccountBalance struct {
|
||||
Account uint32
|
||||
TotalBalance btcutil.Amount
|
||||
}
|
||||
|
||||
// TransactionNotificationsClient receives TransactionNotifications from the
|
||||
// NotificationServer over the channel C.
|
||||
type TransactionNotificationsClient struct {
|
||||
C <-chan *TransactionNotifications
|
||||
server *NotificationServer
|
||||
}
|
||||
|
||||
// TransactionNotifications returns a client for receiving
|
||||
// TransactionNotifiations notifications over a channel. The channel is
|
||||
// unbuffered.
|
||||
//
|
||||
// When finished, the Done method should be called on the client to disassociate
|
||||
// it from the server.
|
||||
func (s *NotificationServer) TransactionNotifications() TransactionNotificationsClient {
|
||||
c := make(chan *TransactionNotifications)
|
||||
s.mu.Lock()
|
||||
s.transactions = append(s.transactions, c)
|
||||
s.mu.Unlock()
|
||||
return TransactionNotificationsClient{
|
||||
C: c,
|
||||
server: s,
|
||||
}
|
||||
}
|
||||
|
||||
// Done deregisters the client from the server and drains any remaining
|
||||
// messages. It must be called exactly once when the client is finished
|
||||
// receiving notifications.
|
||||
func (c *TransactionNotificationsClient) Done() {
|
||||
go func() {
|
||||
// Drain notifications until the client channel is removed from
|
||||
// the server and closed.
|
||||
for range c.C {
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
s := c.server
|
||||
s.mu.Lock()
|
||||
clients := s.transactions
|
||||
for i, ch := range clients {
|
||||
if c.C == ch {
|
||||
clients[i] = clients[len(clients)-1]
|
||||
s.transactions = clients[:len(clients)-1]
|
||||
close(ch)
|
||||
break
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
// SpentnessNotifications is a notification that is fired for transaction
|
||||
// outputs controlled by some account's keys. The notification may be about a
|
||||
// newly added unspent transaction output or that a previously unspent output is
|
||||
// now spent. When spent, the notification includes the spending transaction's
|
||||
// hash and input index.
|
||||
type SpentnessNotifications struct {
|
||||
hash *chainhash.Hash
|
||||
spenderHash *chainhash.Hash
|
||||
index uint32
|
||||
spenderIndex uint32
|
||||
}
|
||||
|
||||
// Hash returns the transaction hash of the spent output.
|
||||
func (n *SpentnessNotifications) Hash() *chainhash.Hash {
|
||||
return n.hash
|
||||
}
|
||||
|
||||
// Index returns the transaction output index of the spent output.
|
||||
func (n *SpentnessNotifications) Index() uint32 {
|
||||
return n.index
|
||||
}
|
||||
|
||||
// Spender returns the spending transction's hash and input index, if any. If
|
||||
// the output is unspent, the final bool return is false.
|
||||
func (n *SpentnessNotifications) Spender() (*chainhash.Hash, uint32, bool) {
|
||||
return n.spenderHash, n.spenderIndex, n.spenderHash != nil
|
||||
}
|
||||
|
||||
// notifyUnspentOutput notifies registered clients of a new unspent output that
|
||||
// is controlled by the wallet.
|
||||
func (s *NotificationServer) notifyUnspentOutput(account uint32, hash *chainhash.Hash, index uint32) {
|
||||
defer s.mu.Unlock()
|
||||
s.mu.Lock()
|
||||
clients := s.spentness[account]
|
||||
if len(clients) == 0 {
|
||||
return
|
||||
}
|
||||
n := &SpentnessNotifications{
|
||||
hash: hash,
|
||||
index: index,
|
||||
}
|
||||
for _, c := range clients {
|
||||
c <- n
|
||||
}
|
||||
}
|
||||
|
||||
// notifySpentOutput notifies registered clients that a previously-unspent
|
||||
// output is now spent, and includes the spender hash and input index in the
|
||||
// notification.
|
||||
func (s *NotificationServer) notifySpentOutput(account uint32, op *wire.OutPoint, spenderHash *chainhash.Hash, spenderIndex uint32) {
|
||||
defer s.mu.Unlock()
|
||||
s.mu.Lock()
|
||||
clients := s.spentness[account]
|
||||
if len(clients) == 0 {
|
||||
return
|
||||
}
|
||||
n := &SpentnessNotifications{
|
||||
hash: &op.Hash,
|
||||
index: op.Index,
|
||||
spenderHash: spenderHash,
|
||||
spenderIndex: spenderIndex,
|
||||
}
|
||||
for _, c := range clients {
|
||||
c <- n
|
||||
}
|
||||
}
|
||||
|
||||
// SpentnessNotificationsClient receives SpentnessNotifications from the
|
||||
// NotificationServer over the channel C.
|
||||
type SpentnessNotificationsClient struct {
|
||||
C <-chan *SpentnessNotifications
|
||||
account uint32
|
||||
server *NotificationServer
|
||||
}
|
||||
|
||||
// AccountSpentnessNotifications registers a client for spentness changes of
|
||||
// outputs controlled by the account.
|
||||
func (s *NotificationServer) AccountSpentnessNotifications(account uint32) SpentnessNotificationsClient {
|
||||
c := make(chan *SpentnessNotifications)
|
||||
s.mu.Lock()
|
||||
s.spentness[account] = append(s.spentness[account], c)
|
||||
s.mu.Unlock()
|
||||
return SpentnessNotificationsClient{
|
||||
C: c,
|
||||
account: account,
|
||||
server: s,
|
||||
}
|
||||
}
|
||||
|
||||
// Done deregisters the client from the server and drains any remaining
|
||||
// messages. It must be called exactly once when the client is finished
|
||||
// receiving notifications.
|
||||
func (c *SpentnessNotificationsClient) Done() {
|
||||
go func() {
|
||||
// Drain notifications until the client channel is removed from
|
||||
// the server and closed.
|
||||
for range c.C {
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
s := c.server
|
||||
s.mu.Lock()
|
||||
clients := s.spentness[c.account]
|
||||
for i, ch := range clients {
|
||||
if c.C == ch {
|
||||
clients[i] = clients[len(clients)-1]
|
||||
s.spentness[c.account] = clients[:len(clients)-1]
|
||||
close(ch)
|
||||
break
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
// AccountNotification contains properties regarding an account, such as its
|
||||
// name and the number of derived and imported keys. When any of these
|
||||
// properties change, the notification is fired.
|
||||
type AccountNotification struct {
|
||||
AccountNumber uint32
|
||||
AccountName string
|
||||
ExternalKeyCount uint32
|
||||
InternalKeyCount uint32
|
||||
ImportedKeyCount uint32
|
||||
}
|
||||
|
||||
func (s *NotificationServer) notifyAccountProperties(props *waddrmgr.AccountProperties) {
|
||||
defer s.mu.Unlock()
|
||||
s.mu.Lock()
|
||||
clients := s.accountClients
|
||||
if len(clients) == 0 {
|
||||
return
|
||||
}
|
||||
n := &AccountNotification{
|
||||
AccountNumber: props.AccountNumber,
|
||||
AccountName: props.AccountName,
|
||||
ExternalKeyCount: props.ExternalKeyCount,
|
||||
InternalKeyCount: props.InternalKeyCount,
|
||||
ImportedKeyCount: props.ImportedKeyCount,
|
||||
}
|
||||
for _, c := range clients {
|
||||
c <- n
|
||||
}
|
||||
}
|
||||
|
||||
// AccountNotificationsClient receives AccountNotifications over the channel C.
|
||||
type AccountNotificationsClient struct {
|
||||
C chan *AccountNotification
|
||||
server *NotificationServer
|
||||
}
|
||||
|
||||
// AccountNotifications returns a client for receiving AccountNotifications over
|
||||
// a channel. The channel is unbuffered. When finished, the client's Done
|
||||
// method should be called to disassociate the client from the server.
|
||||
func (s *NotificationServer) AccountNotifications() AccountNotificationsClient {
|
||||
c := make(chan *AccountNotification)
|
||||
s.mu.Lock()
|
||||
s.accountClients = append(s.accountClients, c)
|
||||
s.mu.Unlock()
|
||||
return AccountNotificationsClient{
|
||||
C: c,
|
||||
server: s,
|
||||
}
|
||||
}
|
||||
|
||||
// Done deregisters the client from the server and drains any remaining
|
||||
// messages. It must be called exactly once when the client is finished
|
||||
// receiving notifications.
|
||||
func (c *AccountNotificationsClient) Done() {
|
||||
go func() {
|
||||
for range c.C {
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
s := c.server
|
||||
s.mu.Lock()
|
||||
clients := s.accountClients
|
||||
for i, ch := range clients {
|
||||
if c.C == ch {
|
||||
clients[i] = clients[len(clients)-1]
|
||||
s.accountClients = clients[:len(clients)-1]
|
||||
close(ch)
|
||||
break
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}()
|
||||
}
|
|
@ -0,0 +1,410 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcutil/hdkeychain"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||
)
|
||||
|
||||
// RecoveryManager maintains the state required to recover previously used
|
||||
// addresses, and coordinates batched processing of the blocks to search.
|
||||
type RecoveryManager struct {
|
||||
// recoveryWindow defines the key-derivation lookahead used when
|
||||
// attempting to recover the set of used addresses.
|
||||
recoveryWindow uint32
|
||||
|
||||
// started is true after the first block has been added to the batch.
|
||||
started bool
|
||||
|
||||
// blockBatch contains a list of blocks that have not yet been searched
|
||||
// for recovered addresses.
|
||||
blockBatch []wtxmgr.BlockMeta
|
||||
|
||||
// state encapsulates and allocates the necessary recovery state for all
|
||||
// key scopes and subsidiary derivation paths.
|
||||
state *RecoveryState
|
||||
|
||||
// chainParams are the parameters that describe the chain we're trying
|
||||
// to recover funds on.
|
||||
chainParams *chaincfg.Params
|
||||
}
|
||||
|
||||
// NewRecoveryManager initializes a new RecoveryManager with a derivation
|
||||
// look-ahead of `recoveryWindow` child indexes, and pre-allocates a backing
|
||||
// array for `batchSize` blocks to scan at once.
|
||||
func NewRecoveryManager(recoveryWindow, batchSize uint32,
|
||||
chainParams *chaincfg.Params) *RecoveryManager {
|
||||
|
||||
return &RecoveryManager{
|
||||
recoveryWindow: recoveryWindow,
|
||||
blockBatch: make([]wtxmgr.BlockMeta, 0, batchSize),
|
||||
chainParams: chainParams,
|
||||
state: NewRecoveryState(recoveryWindow),
|
||||
}
|
||||
}
|
||||
|
||||
// Resurrect restores all known addresses for the provided scopes that can be
|
||||
// found in the walletdb namespace, in addition to restoring all outpoints that
|
||||
// have been previously found. This method ensures that the recovery state's
|
||||
// horizons properly start from the last found address of a prior recovery
|
||||
// attempt.
|
||||
func (rm *RecoveryManager) Resurrect(ns walletdb.ReadBucket,
|
||||
scopedMgrs map[waddrmgr.KeyScope]*waddrmgr.ScopedKeyManager,
|
||||
credits []wtxmgr.Credit) error {
|
||||
|
||||
// First, for each scope that we are recovering, rederive all of the
|
||||
// addresses up to the last found address known to each branch.
|
||||
for keyScope, scopedMgr := range scopedMgrs {
|
||||
// Load the current account properties for this scope, using the
|
||||
// the default account number.
|
||||
// TODO(conner): rescan for all created accounts if we allow
|
||||
// users to use non-default address
|
||||
scopeState := rm.state.StateForScope(keyScope)
|
||||
acctProperties, err := scopedMgr.AccountProperties(
|
||||
ns, waddrmgr.DefaultAccountNum,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fetch the external key count, which bounds the indexes we
|
||||
// will need to rederive.
|
||||
externalCount := acctProperties.ExternalKeyCount
|
||||
|
||||
// Walk through all indexes through the last external key,
|
||||
// deriving each address and adding it to the external branch
|
||||
// recovery state's set of addresses to look for.
|
||||
for i := uint32(0); i < externalCount; i++ {
|
||||
keyPath := externalKeyPath(i)
|
||||
addr, err := scopedMgr.DeriveFromKeyPath(ns, keyPath)
|
||||
if err != nil && err != hdkeychain.ErrInvalidChild {
|
||||
return err
|
||||
} else if err == hdkeychain.ErrInvalidChild {
|
||||
scopeState.ExternalBranch.MarkInvalidChild(i)
|
||||
continue
|
||||
}
|
||||
|
||||
scopeState.ExternalBranch.AddAddr(i, addr.Address())
|
||||
}
|
||||
|
||||
// Fetch the internal key count, which bounds the indexes we
|
||||
// will need to rederive.
|
||||
internalCount := acctProperties.InternalKeyCount
|
||||
|
||||
// Walk through all indexes through the last internal key,
|
||||
// deriving each address and adding it to the internal branch
|
||||
// recovery state's set of addresses to look for.
|
||||
for i := uint32(0); i < internalCount; i++ {
|
||||
keyPath := internalKeyPath(i)
|
||||
addr, err := scopedMgr.DeriveFromKeyPath(ns, keyPath)
|
||||
if err != nil && err != hdkeychain.ErrInvalidChild {
|
||||
return err
|
||||
} else if err == hdkeychain.ErrInvalidChild {
|
||||
scopeState.InternalBranch.MarkInvalidChild(i)
|
||||
continue
|
||||
}
|
||||
|
||||
scopeState.InternalBranch.AddAddr(i, addr.Address())
|
||||
}
|
||||
|
||||
// The key counts will point to the next key that can be
|
||||
// derived, so we subtract one to point to last known key. If
|
||||
// the key count is zero, then no addresses have been found.
|
||||
if externalCount > 0 {
|
||||
scopeState.ExternalBranch.ReportFound(externalCount - 1)
|
||||
}
|
||||
if internalCount > 0 {
|
||||
scopeState.InternalBranch.ReportFound(internalCount - 1)
|
||||
}
|
||||
}
|
||||
|
||||
// In addition, we will re-add any outpoints that are known the wallet
|
||||
// to our global set of watched outpoints, so that we can watch them for
|
||||
// spends.
|
||||
for _, credit := range credits {
|
||||
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
|
||||
credit.PkScript, rm.chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rm.state.AddWatchedOutPoint(&credit.OutPoint, addrs[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddToBlockBatch appends the block information, consisting of hash and height,
|
||||
// to the batch of blocks to be searched.
|
||||
func (rm *RecoveryManager) AddToBlockBatch(hash *chainhash.Hash, height int32,
|
||||
timestamp time.Time) {
|
||||
|
||||
if !rm.started {
|
||||
log.Infof("Seed birthday surpassed, starting recovery "+
|
||||
"of wallet from height=%d hash=%v with "+
|
||||
"recovery-window=%d", height, *hash, rm.recoveryWindow)
|
||||
rm.started = true
|
||||
}
|
||||
|
||||
block := wtxmgr.BlockMeta{
|
||||
Block: wtxmgr.Block{
|
||||
Hash: *hash,
|
||||
Height: height,
|
||||
},
|
||||
Time: timestamp,
|
||||
}
|
||||
rm.blockBatch = append(rm.blockBatch, block)
|
||||
}
|
||||
|
||||
// BlockBatch returns a buffer of blocks that have not yet been searched.
|
||||
func (rm *RecoveryManager) BlockBatch() []wtxmgr.BlockMeta {
|
||||
return rm.blockBatch
|
||||
}
|
||||
|
||||
// ResetBlockBatch resets the internal block buffer to conserve memory.
|
||||
func (rm *RecoveryManager) ResetBlockBatch() {
|
||||
rm.blockBatch = rm.blockBatch[:0]
|
||||
}
|
||||
|
||||
// State returns the current RecoveryState.
|
||||
func (rm *RecoveryManager) State() *RecoveryState {
|
||||
return rm.state
|
||||
}
|
||||
|
||||
// RecoveryState manages the initialization and lookup of ScopeRecoveryStates
|
||||
// for any actively used key scopes.
|
||||
//
|
||||
// In order to ensure that all addresses are properly recovered, the window
|
||||
// should be sized as the sum of maximum possible inter-block and intra-block
|
||||
// gap between used addresses of a particular branch.
|
||||
//
|
||||
// These are defined as:
|
||||
// - Inter-Block Gap: The maximum difference between the derived child indexes
|
||||
// of the last addresses used in any block and the next address consumed
|
||||
// by a later block.
|
||||
// - Intra-Block Gap: The maximum difference between the derived child indexes
|
||||
// of the first address used in any block and the last address used in the
|
||||
// same block.
|
||||
type RecoveryState struct {
|
||||
// recoveryWindow defines the key-derivation lookahead used when
|
||||
// attempting to recover the set of used addresses. This value will be
|
||||
// used to instantiate a new RecoveryState for each requested scope.
|
||||
recoveryWindow uint32
|
||||
|
||||
// scopes maintains a map of each requested key scope to its active
|
||||
// RecoveryState.
|
||||
scopes map[waddrmgr.KeyScope]*ScopeRecoveryState
|
||||
|
||||
// watchedOutPoints contains the set of all outpoints known to the
|
||||
// wallet. This is updated iteratively as new outpoints are found during
|
||||
// a rescan.
|
||||
watchedOutPoints map[wire.OutPoint]btcutil.Address
|
||||
}
|
||||
|
||||
// NewRecoveryState creates a new RecoveryState using the provided
|
||||
// recoveryWindow. Each RecoveryState that is subsequently initialized for a
|
||||
// particular key scope will receive the same recoveryWindow.
|
||||
func NewRecoveryState(recoveryWindow uint32) *RecoveryState {
|
||||
scopes := make(map[waddrmgr.KeyScope]*ScopeRecoveryState)
|
||||
|
||||
return &RecoveryState{
|
||||
recoveryWindow: recoveryWindow,
|
||||
scopes: scopes,
|
||||
watchedOutPoints: make(map[wire.OutPoint]btcutil.Address),
|
||||
}
|
||||
}
|
||||
|
||||
// StateForScope returns a ScopeRecoveryState for the provided key scope. If one
|
||||
// does not already exist, a new one will be generated with the RecoveryState's
|
||||
// recoveryWindow.
|
||||
func (rs *RecoveryState) StateForScope(
|
||||
keyScope waddrmgr.KeyScope) *ScopeRecoveryState {
|
||||
|
||||
// If the account recovery state already exists, return it.
|
||||
if scopeState, ok := rs.scopes[keyScope]; ok {
|
||||
return scopeState
|
||||
}
|
||||
|
||||
// Otherwise, initialize the recovery state for this scope with the
|
||||
// chosen recovery window.
|
||||
rs.scopes[keyScope] = NewScopeRecoveryState(rs.recoveryWindow)
|
||||
|
||||
return rs.scopes[keyScope]
|
||||
}
|
||||
|
||||
// WatchedOutPoints returns the global set of outpoints that are known to belong
|
||||
// to the wallet during recovery.
|
||||
func (rs *RecoveryState) WatchedOutPoints() map[wire.OutPoint]btcutil.Address {
|
||||
return rs.watchedOutPoints
|
||||
}
|
||||
|
||||
// AddWatchedOutPoint updates the recovery state's set of known outpoints that
|
||||
// we will monitor for spends during recovery.
|
||||
func (rs *RecoveryState) AddWatchedOutPoint(outPoint *wire.OutPoint,
|
||||
addr btcutil.Address) {
|
||||
|
||||
rs.watchedOutPoints[*outPoint] = addr
|
||||
}
|
||||
|
||||
// ScopeRecoveryState is used to manage the recovery of addresses generated
|
||||
// under a particular BIP32 account. Each account tracks both an external and
|
||||
// internal branch recovery state, both of which use the same recovery window.
|
||||
type ScopeRecoveryState struct {
|
||||
// ExternalBranch is the recovery state of addresses generated for
|
||||
// external use, i.e. receiving addresses.
|
||||
ExternalBranch *BranchRecoveryState
|
||||
|
||||
// InternalBranch is the recovery state of addresses generated for
|
||||
// internal use, i.e. change addresses.
|
||||
InternalBranch *BranchRecoveryState
|
||||
}
|
||||
|
||||
// NewScopeRecoveryState initializes an ScopeRecoveryState with the chosen
|
||||
// recovery window.
|
||||
func NewScopeRecoveryState(recoveryWindow uint32) *ScopeRecoveryState {
|
||||
return &ScopeRecoveryState{
|
||||
ExternalBranch: NewBranchRecoveryState(recoveryWindow),
|
||||
InternalBranch: NewBranchRecoveryState(recoveryWindow),
|
||||
}
|
||||
}
|
||||
|
||||
// BranchRecoveryState maintains the required state in-order to properly
|
||||
// recover addresses derived from a particular account's internal or external
|
||||
// derivation branch.
|
||||
//
|
||||
// A branch recovery state supports operations for:
|
||||
// - Expanding the look-ahead horizon based on which indexes have been found.
|
||||
// - Registering derived addresses with indexes within the horizon.
|
||||
// - Reporting an invalid child index that falls into the horizon.
|
||||
// - Reporting that an address has been found.
|
||||
// - Retrieving all currently derived addresses for the branch.
|
||||
// - Looking up a particular address by its child index.
|
||||
type BranchRecoveryState struct {
|
||||
// recoveryWindow defines the key-derivation lookahead used when
|
||||
// attempting to recover the set of addresses on this branch.
|
||||
recoveryWindow uint32
|
||||
|
||||
// horizion records the highest child index watched by this branch.
|
||||
horizon uint32
|
||||
|
||||
// nextUnfound maintains the child index of the successor to the highest
|
||||
// index that has been found during recovery of this branch.
|
||||
nextUnfound uint32
|
||||
|
||||
// addresses is a map of child index to address for all actively watched
|
||||
// addresses belonging to this branch.
|
||||
addresses map[uint32]btcutil.Address
|
||||
|
||||
// invalidChildren records the set of child indexes that derive to
|
||||
// invalid keys.
|
||||
invalidChildren map[uint32]struct{}
|
||||
}
|
||||
|
||||
// NewBranchRecoveryState creates a new BranchRecoveryState that can be used to
|
||||
// track either the external or internal branch of an account's derivation path.
|
||||
func NewBranchRecoveryState(recoveryWindow uint32) *BranchRecoveryState {
|
||||
return &BranchRecoveryState{
|
||||
recoveryWindow: recoveryWindow,
|
||||
addresses: make(map[uint32]btcutil.Address),
|
||||
invalidChildren: make(map[uint32]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// ExtendHorizon returns the current horizon and the number of addresses that
|
||||
// must be derived in order to maintain the desired recovery window.
|
||||
func (brs *BranchRecoveryState) ExtendHorizon() (uint32, uint32) {
|
||||
|
||||
// Compute the new horizon, which should surpass our last found address
|
||||
// by the recovery window.
|
||||
curHorizon := brs.horizon
|
||||
|
||||
nInvalid := brs.NumInvalidInHorizon()
|
||||
minValidHorizon := brs.nextUnfound + brs.recoveryWindow + nInvalid
|
||||
|
||||
// If the current horizon is sufficient, we will not have to derive any
|
||||
// new keys.
|
||||
if curHorizon >= minValidHorizon {
|
||||
return curHorizon, 0
|
||||
}
|
||||
|
||||
// Otherwise, the number of addresses we should derive corresponds to
|
||||
// the delta of the two horizons, and we update our new horizon.
|
||||
delta := minValidHorizon - curHorizon
|
||||
brs.horizon = minValidHorizon
|
||||
|
||||
return curHorizon, delta
|
||||
}
|
||||
|
||||
// AddAddr adds a freshly derived address from our lookahead into the map of
|
||||
// known addresses for this branch.
|
||||
func (brs *BranchRecoveryState) AddAddr(index uint32, addr btcutil.Address) {
|
||||
brs.addresses[index] = addr
|
||||
}
|
||||
|
||||
// GetAddr returns the address derived from a given child index.
|
||||
func (brs *BranchRecoveryState) GetAddr(index uint32) btcutil.Address {
|
||||
return brs.addresses[index]
|
||||
}
|
||||
|
||||
// ReportFound updates the last found index if the reported index exceeds the
|
||||
// current value.
|
||||
func (brs *BranchRecoveryState) ReportFound(index uint32) {
|
||||
if index >= brs.nextUnfound {
|
||||
brs.nextUnfound = index + 1
|
||||
|
||||
// Prune all invalid child indexes that fall below our last
|
||||
// found index. We don't need to keep these entries any longer,
|
||||
// since they will not affect our required look-ahead.
|
||||
for childIndex := range brs.invalidChildren {
|
||||
if childIndex < index {
|
||||
delete(brs.invalidChildren, childIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MarkInvalidChild records that a particular child index results in deriving an
|
||||
// invalid address. In addition, the branch's horizon is increment, as we expect
|
||||
// the caller to perform an additional derivation to replace the invalid child.
|
||||
// This is used to ensure that we are always have the proper lookahead when an
|
||||
// invalid child is encountered.
|
||||
func (brs *BranchRecoveryState) MarkInvalidChild(index uint32) {
|
||||
brs.invalidChildren[index] = struct{}{}
|
||||
brs.horizon++
|
||||
}
|
||||
|
||||
// NextUnfound returns the child index of the successor to the highest found
|
||||
// child index.
|
||||
func (brs *BranchRecoveryState) NextUnfound() uint32 {
|
||||
return brs.nextUnfound
|
||||
}
|
||||
|
||||
// Addrs returns a map of all currently derived child indexes to the their
|
||||
// corresponding addresses.
|
||||
func (brs *BranchRecoveryState) Addrs() map[uint32]btcutil.Address {
|
||||
return brs.addresses
|
||||
}
|
||||
|
||||
// NumInvalidInHorizon computes the number of invalid child indexes that lie
|
||||
// between the last found and current horizon. This informs how many additional
|
||||
// indexes to derive in order to maintain the proper number of valid addresses
|
||||
// within our horizon.
|
||||
func (brs *BranchRecoveryState) NumInvalidInHorizon() uint32 {
|
||||
var nInvalid uint32
|
||||
for childIndex := range brs.invalidChildren {
|
||||
if brs.nextUnfound <= childIndex && childIndex < brs.horizon {
|
||||
nInvalid++
|
||||
}
|
||||
}
|
||||
|
||||
return nInvalid
|
||||
}
|
|
@ -0,0 +1,318 @@
|
|||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package wallet
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/chain"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||
)
|
||||
|
||||
// RescanProgressMsg reports the current progress made by a rescan for a
|
||||
// set of wallet addresses.
|
||||
type RescanProgressMsg struct {
|
||||
Addresses []btcutil.Address
|
||||
Notification *chain.RescanProgress
|
||||
}
|
||||
|
||||
// RescanFinishedMsg reports the addresses that were rescanned when a
|
||||
// rescanfinished message was received rescanning a batch of addresses.
|
||||
type RescanFinishedMsg struct {
|
||||
Addresses []btcutil.Address
|
||||
Notification *chain.RescanFinished
|
||||
}
|
||||
|
||||
// RescanJob is a job to be processed by the RescanManager. The job includes
|
||||
// a set of wallet addresses, a starting height to begin the rescan, and
|
||||
// outpoints spendable by the addresses thought to be unspent. After the
|
||||
// rescan completes, the error result of the rescan RPC is sent on the Err
|
||||
// channel.
|
||||
type RescanJob struct {
|
||||
InitialSync bool
|
||||
Addrs []btcutil.Address
|
||||
OutPoints map[wire.OutPoint]btcutil.Address
|
||||
BlockStamp waddrmgr.BlockStamp
|
||||
err chan error
|
||||
}
|
||||
|
||||
// rescanBatch is a collection of one or more RescanJobs that were merged
|
||||
// together before a rescan is performed.
|
||||
type rescanBatch struct {
|
||||
initialSync bool
|
||||
addrs []btcutil.Address
|
||||
outpoints map[wire.OutPoint]btcutil.Address
|
||||
bs waddrmgr.BlockStamp
|
||||
errChans []chan error
|
||||
}
|
||||
|
||||
// SubmitRescan submits a RescanJob to the RescanManager. A channel is
|
||||
// returned with the final error of the rescan. The channel is buffered
|
||||
// and does not need to be read to prevent a deadlock.
|
||||
func (w *Wallet) SubmitRescan(job *RescanJob) <-chan error {
|
||||
errChan := make(chan error, 1)
|
||||
job.err = errChan
|
||||
select {
|
||||
case w.rescanAddJob <- job:
|
||||
case <-w.quitChan():
|
||||
errChan <- ErrWalletShuttingDown
|
||||
}
|
||||
return errChan
|
||||
}
|
||||
|
||||
// batch creates the rescanBatch for a single rescan job.
|
||||
func (job *RescanJob) batch() *rescanBatch {
|
||||
return &rescanBatch{
|
||||
initialSync: job.InitialSync,
|
||||
addrs: job.Addrs,
|
||||
outpoints: job.OutPoints,
|
||||
bs: job.BlockStamp,
|
||||
errChans: []chan error{job.err},
|
||||
}
|
||||
}
|
||||
|
||||
// merge merges the work from k into j, setting the starting height to
|
||||
// the minimum of the two jobs. This method does not check for
|
||||
// duplicate addresses or outpoints.
|
||||
func (b *rescanBatch) merge(job *RescanJob) {
|
||||
if job.InitialSync {
|
||||
b.initialSync = true
|
||||
}
|
||||
b.addrs = append(b.addrs, job.Addrs...)
|
||||
|
||||
for op, addr := range job.OutPoints {
|
||||
b.outpoints[op] = addr
|
||||
}
|
||||
|
||||
if job.BlockStamp.Height < b.bs.Height {
|
||||
b.bs = job.BlockStamp
|
||||
}
|
||||
b.errChans = append(b.errChans, job.err)
|
||||
}
|
||||
|
||||
// done iterates through all error channels, duplicating sending the error
|
||||
// to inform callers that the rescan finished (or could not complete due
|
||||
// to an error).
|
||||
func (b *rescanBatch) done(err error) {
|
||||
for _, c := range b.errChans {
|
||||
c <- err
|
||||
}
|
||||
}
|
||||
|
||||
// rescanBatchHandler handles incoming rescan request, serializing rescan
|
||||
// submissions, and possibly batching many waiting requests together so they
|
||||
// can be handled by a single rescan after the current one completes.
|
||||
func (w *Wallet) rescanBatchHandler() {
|
||||
defer w.wg.Done()
|
||||
|
||||
var curBatch, nextBatch *rescanBatch
|
||||
quit := w.quitChan()
|
||||
|
||||
for {
|
||||
select {
|
||||
case job := <-w.rescanAddJob:
|
||||
if curBatch == nil {
|
||||
// Set current batch as this job and send
|
||||
// request.
|
||||
curBatch = job.batch()
|
||||
select {
|
||||
case w.rescanBatch <- curBatch:
|
||||
case <-quit:
|
||||
job.err <- ErrWalletShuttingDown
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Create next batch if it doesn't exist, or
|
||||
// merge the job.
|
||||
if nextBatch == nil {
|
||||
nextBatch = job.batch()
|
||||
} else {
|
||||
nextBatch.merge(job)
|
||||
}
|
||||
}
|
||||
|
||||
case n := <-w.rescanNotifications:
|
||||
switch n := n.(type) {
|
||||
case *chain.RescanProgress:
|
||||
if curBatch == nil {
|
||||
log.Warnf("Received rescan progress " +
|
||||
"notification but no rescan " +
|
||||
"currently running")
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case w.rescanProgress <- &RescanProgressMsg{
|
||||
Addresses: curBatch.addrs,
|
||||
Notification: n,
|
||||
}:
|
||||
case <-quit:
|
||||
for _, errChan := range curBatch.errChans {
|
||||
errChan <- ErrWalletShuttingDown
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
case *chain.RescanFinished:
|
||||
if curBatch == nil {
|
||||
log.Warnf("Received rescan finished " +
|
||||
"notification but no rescan " +
|
||||
"currently running")
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case w.rescanFinished <- &RescanFinishedMsg{
|
||||
Addresses: curBatch.addrs,
|
||||
Notification: n,
|
||||
}:
|
||||
case <-quit:
|
||||
for _, errChan := range curBatch.errChans {
|
||||
errChan <- ErrWalletShuttingDown
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
curBatch, nextBatch = nextBatch, nil
|
||||
|
||||
if curBatch != nil {
|
||||
select {
|
||||
case w.rescanBatch <- curBatch:
|
||||
case <-quit:
|
||||
for _, errChan := range curBatch.errChans {
|
||||
errChan <- ErrWalletShuttingDown
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
// Unexpected message
|
||||
panic(n)
|
||||
}
|
||||
|
||||
case <-quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rescanProgressHandler handles notifications for partially and fully completed
|
||||
// rescans by marking each rescanned address as partially or fully synced.
|
||||
func (w *Wallet) rescanProgressHandler() {
|
||||
quit := w.quitChan()
|
||||
out:
|
||||
for {
|
||||
// These can't be processed out of order since both chans are
|
||||
// unbuffured and are sent from same context (the batch
|
||||
// handler).
|
||||
select {
|
||||
case msg := <-w.rescanProgress:
|
||||
n := msg.Notification
|
||||
log.Infof("Rescanned through block %v (height %d)",
|
||||
n.Hash, n.Height)
|
||||
|
||||
case msg := <-w.rescanFinished:
|
||||
n := msg.Notification
|
||||
addrs := msg.Addresses
|
||||
noun := pickNoun(len(addrs), "address", "addresses")
|
||||
log.Infof("Finished rescan for %d %s (synced to block "+
|
||||
"%s, height %d)", len(addrs), noun, n.Hash,
|
||||
n.Height)
|
||||
|
||||
go w.resendUnminedTxs()
|
||||
|
||||
case <-quit:
|
||||
break out
|
||||
}
|
||||
}
|
||||
w.wg.Done()
|
||||
}
|
||||
|
||||
// rescanRPCHandler reads batch jobs sent by rescanBatchHandler and sends the
|
||||
// RPC requests to perform a rescan. New jobs are not read until a rescan
|
||||
// finishes.
|
||||
func (w *Wallet) rescanRPCHandler() {
|
||||
chainClient, err := w.requireChainClient()
|
||||
if err != nil {
|
||||
log.Errorf("rescanRPCHandler called without an RPC client")
|
||||
w.wg.Done()
|
||||
return
|
||||
}
|
||||
|
||||
quit := w.quitChan()
|
||||
|
||||
out:
|
||||
for {
|
||||
select {
|
||||
case batch := <-w.rescanBatch:
|
||||
// Log the newly-started rescan.
|
||||
numAddrs := len(batch.addrs)
|
||||
noun := pickNoun(numAddrs, "address", "addresses")
|
||||
log.Infof("Started rescan from block %v (height %d) for %d %s",
|
||||
batch.bs.Hash, batch.bs.Height, numAddrs, noun)
|
||||
|
||||
err := chainClient.Rescan(&batch.bs.Hash, batch.addrs,
|
||||
batch.outpoints)
|
||||
if err != nil {
|
||||
log.Errorf("Rescan for %d %s failed: %v", numAddrs,
|
||||
noun, err)
|
||||
}
|
||||
batch.done(err)
|
||||
case <-quit:
|
||||
break out
|
||||
}
|
||||
}
|
||||
|
||||
w.wg.Done()
|
||||
}
|
||||
|
||||
// Rescan begins a rescan for all active addresses and unspent outputs of
|
||||
// a wallet. This is intended to be used to sync a wallet back up to the
|
||||
// current best block in the main chain, and is considered an initial sync
|
||||
// rescan.
|
||||
func (w *Wallet) Rescan(addrs []btcutil.Address, unspent []wtxmgr.Credit) error {
|
||||
return w.rescanWithTarget(addrs, unspent, nil)
|
||||
}
|
||||
|
||||
// rescanWithTarget performs a rescan starting at the optional startStamp. If
|
||||
// none is provided, the rescan will begin from the manager's sync tip.
|
||||
func (w *Wallet) rescanWithTarget(addrs []btcutil.Address,
|
||||
unspent []wtxmgr.Credit, startStamp *waddrmgr.BlockStamp) error {
|
||||
|
||||
outpoints := make(map[wire.OutPoint]btcutil.Address, len(unspent))
|
||||
for _, output := range unspent {
|
||||
_, outputAddrs, _, err := txscript.ExtractPkScriptAddrs(
|
||||
output.PkScript, w.chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outpoints[output.OutPoint] = outputAddrs[0]
|
||||
}
|
||||
|
||||
// If a start block stamp was provided, we will use that as the initial
|
||||
// starting point for the rescan.
|
||||
if startStamp == nil {
|
||||
startStamp = &waddrmgr.BlockStamp{}
|
||||
*startStamp = w.Manager.SyncedTo()
|
||||
}
|
||||
|
||||
job := &RescanJob{
|
||||
InitialSync: true,
|
||||
Addrs: addrs,
|
||||
OutPoints: outpoints,
|
||||
BlockStamp: *startStamp,
|
||||
}
|
||||
|
||||
// Submit merged job and block until rescan completes.
|
||||
select {
|
||||
case err := <-w.SubmitRescan(job):
|
||||
return err
|
||||
case <-w.quitChan():
|
||||
return ErrWalletShuttingDown
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
ISC License
|
||||
|
||||
Copyright (c) 2013-2017 The btcsuite developers
|
||||
Copyright (c) 2015-2016 The Decred developers
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
@ -0,0 +1,367 @@
|
|||
// Copyright (c) 2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package txauthor provides transaction creation code for wallets.
|
||||
package txauthor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/wallet/txrules"
|
||||
"github.com/btcsuite/btcwallet/wallet/txsizes"
|
||||
)
|
||||
|
||||
// SumOutputValues sums up the list of TxOuts and returns an Amount.
|
||||
func SumOutputValues(outputs []*wire.TxOut) (totalOutput btcutil.Amount) {
|
||||
for _, txOut := range outputs {
|
||||
totalOutput += btcutil.Amount(txOut.Value)
|
||||
}
|
||||
return totalOutput
|
||||
}
|
||||
|
||||
// InputSource provides transaction inputs referencing spendable outputs to
|
||||
// construct a transaction outputting some target amount. If the target amount
|
||||
// can not be satisified, this can be signaled by returning a total amount less
|
||||
// than the target or by returning a more detailed error implementing
|
||||
// InputSourceError.
|
||||
type InputSource func(target btcutil.Amount) (total btcutil.Amount, inputs []*wire.TxIn,
|
||||
inputValues []btcutil.Amount, scripts [][]byte, err error)
|
||||
|
||||
// InputSourceError describes the failure to provide enough input value from
|
||||
// unspent transaction outputs to meet a target amount. A typed error is used
|
||||
// so input sources can provide their own implementations describing the reason
|
||||
// for the error, for example, due to spendable policies or locked coins rather
|
||||
// than the wallet not having enough available input value.
|
||||
type InputSourceError interface {
|
||||
error
|
||||
InputSourceError()
|
||||
}
|
||||
|
||||
// Default implementation of InputSourceError.
|
||||
type insufficientFundsError struct{}
|
||||
|
||||
func (insufficientFundsError) InputSourceError() {}
|
||||
func (insufficientFundsError) Error() string {
|
||||
return "insufficient funds available to construct transaction"
|
||||
}
|
||||
|
||||
// AuthoredTx holds the state of a newly-created transaction and the change
|
||||
// output (if one was added).
|
||||
type AuthoredTx struct {
|
||||
Tx *wire.MsgTx
|
||||
PrevScripts [][]byte
|
||||
PrevInputValues []btcutil.Amount
|
||||
TotalInput btcutil.Amount
|
||||
ChangeIndex int // negative if no change
|
||||
}
|
||||
|
||||
// ChangeSource provides P2PKH change output scripts for transaction creation.
|
||||
type ChangeSource func() ([]byte, error)
|
||||
|
||||
// NewUnsignedTransaction creates an unsigned transaction paying to one or more
|
||||
// non-change outputs. An appropriate transaction fee is included based on the
|
||||
// transaction size.
|
||||
//
|
||||
// Transaction inputs are chosen from repeated calls to fetchInputs with
|
||||
// increasing targets amounts.
|
||||
//
|
||||
// If any remaining output value can be returned to the wallet via a change
|
||||
// output without violating mempool dust rules, a P2WPKH change output is
|
||||
// appended to the transaction outputs. Since the change output may not be
|
||||
// necessary, fetchChange is called zero or one times to generate this script.
|
||||
// This function must return a P2WPKH script or smaller, otherwise fee estimation
|
||||
// will be incorrect.
|
||||
//
|
||||
// If successful, the transaction, total input value spent, and all previous
|
||||
// output scripts are returned. If the input source was unable to provide
|
||||
// enough input value to pay for every output any any necessary fees, an
|
||||
// InputSourceError is returned.
|
||||
//
|
||||
// BUGS: Fee estimation may be off when redeeming non-compressed P2PKH outputs.
|
||||
func NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb btcutil.Amount,
|
||||
fetchInputs InputSource, fetchChange ChangeSource) (*AuthoredTx, error) {
|
||||
|
||||
targetAmount := SumOutputValues(outputs)
|
||||
estimatedSize := txsizes.EstimateVirtualSize(0, 1, 0, outputs, true)
|
||||
targetFee := txrules.FeeForSerializeSize(relayFeePerKb, estimatedSize)
|
||||
|
||||
for {
|
||||
inputAmount, inputs, inputValues, scripts, err := fetchInputs(targetAmount + targetFee)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if inputAmount < targetAmount+targetFee {
|
||||
return nil, insufficientFundsError{}
|
||||
}
|
||||
|
||||
// We count the types of inputs, which we'll use to estimate
|
||||
// the vsize of the transaction.
|
||||
var nested, p2wpkh, p2pkh int
|
||||
for _, pkScript := range scripts {
|
||||
switch {
|
||||
// If this is a p2sh output, we assume this is a
|
||||
// nested P2WKH.
|
||||
case txscript.IsPayToScriptHash(pkScript):
|
||||
nested++
|
||||
case txscript.IsPayToWitnessPubKeyHash(pkScript):
|
||||
p2wpkh++
|
||||
default:
|
||||
p2pkh++
|
||||
}
|
||||
}
|
||||
|
||||
maxSignedSize := txsizes.EstimateVirtualSize(p2pkh, p2wpkh,
|
||||
nested, outputs, true)
|
||||
maxRequiredFee := txrules.FeeForSerializeSize(relayFeePerKb, maxSignedSize)
|
||||
remainingAmount := inputAmount - targetAmount
|
||||
if remainingAmount < maxRequiredFee {
|
||||
targetFee = maxRequiredFee
|
||||
continue
|
||||
}
|
||||
|
||||
unsignedTransaction := &wire.MsgTx{
|
||||
Version: wire.TxVersion,
|
||||
TxIn: inputs,
|
||||
TxOut: outputs,
|
||||
LockTime: 0,
|
||||
}
|
||||
changeIndex := -1
|
||||
changeAmount := inputAmount - targetAmount - maxRequiredFee
|
||||
if changeAmount != 0 && !txrules.IsDustAmount(changeAmount,
|
||||
txsizes.P2WPKHPkScriptSize, relayFeePerKb) {
|
||||
changeScript, err := fetchChange()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(changeScript) > txsizes.P2WPKHPkScriptSize {
|
||||
return nil, errors.New("fee estimation requires change " +
|
||||
"scripts no larger than P2WPKH output scripts")
|
||||
}
|
||||
change := wire.NewTxOut(int64(changeAmount), changeScript)
|
||||
l := len(outputs)
|
||||
unsignedTransaction.TxOut = append(outputs[:l:l], change)
|
||||
changeIndex = l
|
||||
}
|
||||
|
||||
return &AuthoredTx{
|
||||
Tx: unsignedTransaction,
|
||||
PrevScripts: scripts,
|
||||
PrevInputValues: inputValues,
|
||||
TotalInput: inputAmount,
|
||||
ChangeIndex: changeIndex,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// RandomizeOutputPosition randomizes the position of a transaction's output by
|
||||
// swapping it with a random output. The new index is returned. This should be
|
||||
// done before signing.
|
||||
func RandomizeOutputPosition(outputs []*wire.TxOut, index int) int {
|
||||
r := cprng.Int31n(int32(len(outputs)))
|
||||
outputs[r], outputs[index] = outputs[index], outputs[r]
|
||||
return int(r)
|
||||
}
|
||||
|
||||
// RandomizeChangePosition randomizes the position of an authored transaction's
|
||||
// change output. This should be done before signing.
|
||||
func (tx *AuthoredTx) RandomizeChangePosition() {
|
||||
tx.ChangeIndex = RandomizeOutputPosition(tx.Tx.TxOut, tx.ChangeIndex)
|
||||
}
|
||||
|
||||
// SecretsSource provides private keys and redeem scripts necessary for
|
||||
// constructing transaction input signatures. Secrets are looked up by the
|
||||
// corresponding Address for the previous output script. Addresses for lookup
|
||||
// are created using the source's blockchain parameters and means a single
|
||||
// SecretsSource can only manage secrets for a single chain.
|
||||
//
|
||||
// TODO: Rewrite this interface to look up private keys and redeem scripts for
|
||||
// pubkeys, pubkey hashes, script hashes, etc. as separate interface methods.
|
||||
// This would remove the ChainParams requirement of the interface and could
|
||||
// avoid unnecessary conversions from previous output scripts to Addresses.
|
||||
// This can not be done without modifications to the txscript package.
|
||||
type SecretsSource interface {
|
||||
txscript.KeyDB
|
||||
txscript.ScriptDB
|
||||
ChainParams() *chaincfg.Params
|
||||
}
|
||||
|
||||
// AddAllInputScripts modifies transaction a transaction by adding inputs
|
||||
// scripts for each input. Previous output scripts being redeemed by each input
|
||||
// are passed in prevPkScripts and the slice length must match the number of
|
||||
// inputs. Private keys and redeem scripts are looked up using a SecretsSource
|
||||
// based on the previous output script.
|
||||
func AddAllInputScripts(tx *wire.MsgTx, prevPkScripts [][]byte, inputValues []btcutil.Amount,
|
||||
secrets SecretsSource) error {
|
||||
|
||||
inputs := tx.TxIn
|
||||
hashCache := txscript.NewTxSigHashes(tx)
|
||||
chainParams := secrets.ChainParams()
|
||||
|
||||
if len(inputs) != len(prevPkScripts) {
|
||||
return errors.New("tx.TxIn and prevPkScripts slices must " +
|
||||
"have equal length")
|
||||
}
|
||||
|
||||
for i := range inputs {
|
||||
pkScript := prevPkScripts[i]
|
||||
|
||||
switch {
|
||||
// If this is a p2sh output, who's script hash pre-image is a
|
||||
// witness program, then we'll need to use a modified signing
|
||||
// function which generates both the sigScript, and the witness
|
||||
// script.
|
||||
case txscript.IsPayToScriptHash(pkScript):
|
||||
err := spendNestedWitnessPubKeyHash(inputs[i], pkScript,
|
||||
int64(inputValues[i]), chainParams, secrets,
|
||||
tx, hashCache, i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case txscript.IsPayToWitnessPubKeyHash(pkScript):
|
||||
err := spendWitnessKeyHash(inputs[i], pkScript,
|
||||
int64(inputValues[i]), chainParams, secrets,
|
||||
tx, hashCache, i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
sigScript := inputs[i].SignatureScript
|
||||
script, err := txscript.SignTxOutput(chainParams, tx, i,
|
||||
pkScript, txscript.SigHashAll, secrets, secrets,
|
||||
sigScript)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inputs[i].SignatureScript = script
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// spendWitnessKeyHash generates, and sets a valid witness for spending the
|
||||
// passed pkScript with the specified input amount. The input amount *must*
|
||||
// correspond to the output value of the previous pkScript, or else verification
|
||||
// will fail since the new sighash digest algorithm defined in BIP0143 includes
|
||||
// the input value in the sighash.
|
||||
func spendWitnessKeyHash(txIn *wire.TxIn, pkScript []byte,
|
||||
inputValue int64, chainParams *chaincfg.Params, secrets SecretsSource,
|
||||
tx *wire.MsgTx, hashCache *txscript.TxSigHashes, idx int) error {
|
||||
|
||||
// First obtain the key pair associated with this p2wkh address.
|
||||
_, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript,
|
||||
chainParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
privKey, compressed, err := secrets.GetKey(addrs[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pubKey := privKey.PubKey()
|
||||
|
||||
// Once we have the key pair, generate a p2wkh address type, respecting
|
||||
// the compression type of the generated key.
|
||||
var pubKeyHash []byte
|
||||
if compressed {
|
||||
pubKeyHash = btcutil.Hash160(pubKey.SerializeCompressed())
|
||||
} else {
|
||||
pubKeyHash = btcutil.Hash160(pubKey.SerializeUncompressed())
|
||||
}
|
||||
p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, chainParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// With the concrete address type, we can now generate the
|
||||
// corresponding witness program to be used to generate a valid witness
|
||||
// which will allow us to spend this output.
|
||||
witnessProgram, err := txscript.PayToAddrScript(p2wkhAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
witnessScript, err := txscript.WitnessSignature(tx, hashCache, idx,
|
||||
inputValue, witnessProgram, txscript.SigHashAll, privKey, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txIn.Witness = witnessScript
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// spendNestedWitnessPubKey generates both a sigScript, and valid witness for
|
||||
// spending the passed pkScript with the specified input amount. The generated
|
||||
// sigScript is the version 0 p2wkh witness program corresponding to the queried
|
||||
// key. The witness stack is identical to that of one which spends a regular
|
||||
// p2wkh output. The input amount *must* correspond to the output value of the
|
||||
// previous pkScript, or else verification will fail since the new sighash
|
||||
// digest algorithm defined in BIP0143 includes the input value in the sighash.
|
||||
func spendNestedWitnessPubKeyHash(txIn *wire.TxIn, pkScript []byte,
|
||||
inputValue int64, chainParams *chaincfg.Params, secrets SecretsSource,
|
||||
tx *wire.MsgTx, hashCache *txscript.TxSigHashes, idx int) error {
|
||||
|
||||
// First we need to obtain the key pair related to this p2sh output.
|
||||
_, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript,
|
||||
chainParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
privKey, compressed, err := secrets.GetKey(addrs[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pubKey := privKey.PubKey()
|
||||
|
||||
var pubKeyHash []byte
|
||||
if compressed {
|
||||
pubKeyHash = btcutil.Hash160(pubKey.SerializeCompressed())
|
||||
} else {
|
||||
pubKeyHash = btcutil.Hash160(pubKey.SerializeUncompressed())
|
||||
}
|
||||
|
||||
// Next, we'll generate a valid sigScript that'll allow us to spend
|
||||
// the p2sh output. The sigScript will contain only a single push of
|
||||
// the p2wkh witness program corresponding to the matching public key
|
||||
// of this address.
|
||||
p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, chainParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
witnessProgram, err := txscript.PayToAddrScript(p2wkhAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bldr := txscript.NewScriptBuilder()
|
||||
bldr.AddData(witnessProgram)
|
||||
sigScript, err := bldr.Script()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
txIn.SignatureScript = sigScript
|
||||
|
||||
// With the sigScript in place, we'll next generate the proper witness
|
||||
// that'll allow us to spend the p2wkh output.
|
||||
witnessScript, err := txscript.WitnessSignature(tx, hashCache, idx,
|
||||
inputValue, witnessProgram, txscript.SigHashAll, privKey, compressed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txIn.Witness = witnessScript
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddAllInputScripts modifies an authored transaction by adding inputs scripts
|
||||
// for each input of an authored transaction. Private keys and redeem scripts
|
||||
// are looked up using a SecretsSource based on the previous output script.
|
||||
func (tx *AuthoredTx) AddAllInputScripts(secrets SecretsSource) error {
|
||||
return AddAllInputScripts(tx.Tx, tx.PrevScripts, tx.PrevInputValues, secrets)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue