From bf5dd8814825ac51c1cdf0fa4ff8106f64faf633 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Thu, 16 Nov 2023 14:58:42 +0100 Subject: [PATCH 1/7] go.mod: depend on lnd with https://github.com/lightningnetwork/lnd/pull/8183 --- .github/workflows/main.yml | 2 +- README.md | 2 +- cmd/chantools/dropchannelgraph.go | 4 ++-- cmd/chantools/root.go | 4 +++- cmd/chantools/zombierecovery_makeoffer.go | 9 ++++++++- dump/dump.go | 7 +++++-- go.mod | 20 ++++++++++++++------ go.sum | 19 ++++++++++--------- lnd/brontide.go | 8 +++++--- lnd/hdkeychain.go | 6 +++++- tools/Dockerfile | 2 +- 11 files changed, 55 insertions(+), 28 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 96143bd..6e9a53e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,7 @@ env: # go needs absolute directories, using the $HOME variable doesn't work here. GOCACHE: /home/runner/work/go/pkg/build GOPATH: /home/runner/work/go - GO_VERSION: 1.22.3 + GO_VERSION: 1.22.6 jobs: ######################## diff --git a/README.md b/README.md index 6902600..f34a4a3 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ $ sudo mv chantools-*/chantools /usr/local/bin/ If there isn't a pre-built binary for your operating system or architecture available or you want to build `chantools` from source for another reason, you -need to make sure you have `go 1.22.3` (or later) and `make` installed and can +need to make sure you have `go 1.22.6` (or later) and `make` installed and can then run the following commands: ```bash diff --git a/cmd/chantools/dropchannelgraph.go b/cmd/chantools/dropchannelgraph.go index d7f2b8d..d69e464 100644 --- a/cmd/chantools/dropchannelgraph.go +++ b/cmd/chantools/dropchannelgraph.go @@ -184,7 +184,7 @@ func newChanAnnouncement(localPubKey, remotePubKey *btcec.PublicKey, // The unconditional section of the announcement is the ShortChannelID // itself which compactly encodes the location of the funding output // within the blockchain. - chanAnn := &lnwire.ChannelAnnouncement{ + chanAnn := &lnwire.ChannelAnnouncement1{ ShortChannelID: shortChanID, Features: lnwire.NewRawFeatureVector(), ChainHash: chainHash, @@ -248,7 +248,7 @@ func newChanAnnouncement(localPubKey, remotePubKey *btcec.PublicKey, // We announce the channel with the default values. Some of // these values can later be changed by crafting a new ChannelUpdate. - chanUpdateAnn := &lnwire.ChannelUpdate{ + chanUpdateAnn := &lnwire.ChannelUpdate1{ ShortChannelID: shortChanID, ChainHash: chainHash, Timestamp: uint32(time.Now().Unix()), diff --git a/cmd/chantools/root.go b/cmd/chantools/root.go index e00e4a6..0f2b34c 100644 --- a/cmd/chantools/root.go +++ b/cmd/chantools/root.go @@ -310,7 +310,9 @@ func setupLogging() { addSubLogger("CHDB", channeldb.UseLogger) addSubLogger("BCKP", chanbackup.UseLogger) addSubLogger("PEER", peer.UseLogger) - err := logWriter.InitLogRotator("./results/chantools.log", 10, 3) + err := logWriter.InitLogRotator( + "./results/chantools.log", build.Gzip, 10, 3, + ) if err != nil { panic(err) } diff --git a/cmd/chantools/zombierecovery_makeoffer.go b/cmd/chantools/zombierecovery_makeoffer.go index 1b3cc6d..94f0daa 100644 --- a/cmd/chantools/zombierecovery_makeoffer.go +++ b/cmd/chantools/zombierecovery_makeoffer.go @@ -20,10 +20,12 @@ import ( "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/wallet" "github.com/lightninglabs/chantools/lnd" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" @@ -650,7 +652,12 @@ func matchScript(address string, key1, key2 *btcec.PublicKey, pkScript, nil case *btcutil.AddressTaproot: - pkScript, _, err := input.GenTaprootFundingScript(key1, key2, 0) + // FIXME: fill tapscriptRoot. + var tapscriptRoot fn.Option[chainhash.Hash] + + pkScript, _, err := input.GenTaprootFundingScript( + key1, key2, 0, tapscriptRoot, + ) if err != nil { return false, nil, nil, err } diff --git a/dump/dump.go b/dump/dump.go index f5800d1..24e512f 100644 --- a/dump/dump.go +++ b/dump/dump.go @@ -253,6 +253,9 @@ func CollectDebugInfo(channel *channeldb.OpenChannel, commitPoint, whoseCommit, chanType, ourChanCfg, theirChanCfg, ) + // FIXME: fill auxLeaf for Tapscript root channels. + var auxLeaf input.AuxTapLeaf + // First, we create the script for the delayed "pay-to-self" output. // This output has 2 main redemption clauses: either we can redeem the // output after a relative block delay, or the remote node can claim @@ -260,7 +263,7 @@ func CollectDebugInfo(channel *channeldb.OpenChannel, // commitment transaction. toLocalScript, err := lnwallet.CommitScriptToSelf( chanType, initiator, keyRing.ToLocalKey, keyRing.RevocationKey, - uint32(ourChanCfg.CsvDelay), leaseExpiry, + uint32(ourChanCfg.CsvDelay), leaseExpiry, auxLeaf, ) if err != nil { return nil, err @@ -268,7 +271,7 @@ func CollectDebugInfo(channel *channeldb.OpenChannel, // Next, we create the script paying to the remote. toRemoteScript, _, err := lnwallet.CommitScriptToRemote( - chanType, initiator, keyRing.ToRemoteKey, leaseExpiry, + chanType, initiator, keyRing.ToRemoteKey, leaseExpiry, auxLeaf, ) if err != nil { return nil, err diff --git a/go.mod b/go.mod index 00ec4a7..e03323c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/lightninglabs/chantools -go 1.22.3 +go 1.22.6 + +toolchain go1.23.1 require ( github.com/btcsuite/btcd v0.24.2-beta.rc1.0.20240625142744-cc26860b4026 @@ -9,7 +11,7 @@ require ( github.com/btcsuite/btcd/btcutil/psbt v1.1.8 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f - github.com/btcsuite/btcwallet v0.16.10-0.20240718224643-db3a4a2543bd + github.com/btcsuite/btcwallet v0.16.10-0.20240809133323-7d3434c65ae2 github.com/btcsuite/btcwallet/wallet/txrules v1.2.1 // indirect github.com/btcsuite/btcwallet/walletdb v1.4.2 github.com/coreos/bbolt v1.3.3 @@ -35,7 +37,10 @@ require ( golang.org/x/oauth2 v0.14.0 ) -require github.com/tv42/zbase32 v0.0.0-20220222190657-f76a9fc892fa +require ( + github.com/lightningnetwork/lnd/fn v1.2.1 + github.com/tv42/zbase32 v0.0.0-20220222190657-f76a9fc892fa +) require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect @@ -101,11 +106,11 @@ require ( github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jessevdk/go-flags v1.4.0 // indirect github.com/jonboulle/clockwork v0.2.2 // indirect - github.com/jrick/logrotate v1.0.0 // indirect + github.com/jrick/logrotate v1.1.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 // indirect github.com/kkdai/bstream v1.0.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/lib/pq v1.10.9 // indirect github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect @@ -116,7 +121,6 @@ require ( github.com/lightninglabs/pool/auctioneerrpc v1.1.2 // indirect github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb // indirect github.com/lightningnetwork/lnd/clock v1.1.1 // indirect - github.com/lightningnetwork/lnd/fn v1.2.1 // indirect github.com/lightningnetwork/lnd/healthcheck v1.2.5 // indirect github.com/lightningnetwork/lnd/sqldb v1.0.4 // indirect github.com/lightningnetwork/lnd/tlv v1.2.6 // indirect @@ -215,3 +219,7 @@ require ( replace google.golang.org/protobuf => github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display replace github.com/btcsuite/btcd/btcutil/psbt => github.com/guggero/btcd/btcutil/psbt v0.0.0-20240615145141-63f97ed9872a + +replace github.com/lightningnetwork/lnd => github.com/starius/lnd v0.18.0-beta-close-tx7 + +replace github.com/lightninglabs/pool => github.com/starius/pool v0.6.5-beta.newlnd2 diff --git a/go.sum b/go.sum index 7ad5daa..f89f59b 100644 --- a/go.sum +++ b/go.sum @@ -665,8 +665,8 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtyd 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/btcwallet v0.16.10-0.20240718224643-db3a4a2543bd h1:QDb8foTCRoXrfoZVEzSYgSde16MJh4gCtCin8OCS0kI= -github.com/btcsuite/btcwallet v0.16.10-0.20240718224643-db3a4a2543bd/go.mod h1:X2xDre+j1QphTRo54y2TikUzeSvreL1t1aMXrD8Kc5A= +github.com/btcsuite/btcwallet v0.16.10-0.20240809133323-7d3434c65ae2 h1:qa4Avm7p97JroZZyMJADbEb9u853pjleJYSeitENvLc= +github.com/btcsuite/btcwallet v0.16.10-0.20240809133323-7d3434c65ae2/go.mod h1:X2xDre+j1QphTRo54y2TikUzeSvreL1t1aMXrD8Kc5A= github.com/btcsuite/btcwallet/wallet/txauthor v1.3.4 h1:poyHFf7+5+RdxNp5r2T6IBRD7RyraUsYARYbp/7t4D8= github.com/btcsuite/btcwallet/wallet/txauthor v1.3.4/go.mod h1:GETGDQuyq+VFfH1S/+/7slLM/9aNa4l7P4ejX6dJfb0= github.com/btcsuite/btcwallet/wallet/txrules v1.2.1 h1:UZo7YRzdHbwhK7Rhv3PO9bXgTxiOH45edK5qdsdiatk= @@ -1100,8 +1100,9 @@ github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22 github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -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/jrick/logrotate v1.1.2 h1:6ePk462NCX7TfKtNp5JJ7MbA2YIslkpfgP03TlTYMN0= +github.com/jrick/logrotate v1.1.2/go.mod h1:f9tdWggSVK3iqavGpyvegq5IhNois7KXmasU6/N96OQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -1144,8 +1145,8 @@ github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+ github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -1182,16 +1183,12 @@ github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd h1:D8aRo github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd/go.mod h1:x3OmY2wsA18+Kc3TSV2QpSUewOCiscw2mKpXgZv2kZk= github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g= github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo= -github.com/lightninglabs/pool v0.6.5-beta.0.20240531084722-4000ec802aaa h1:UYqz8O8teeSU1383NLUMIADCD2QplOeJVjiB4k1xeSI= -github.com/lightninglabs/pool v0.6.5-beta.0.20240531084722-4000ec802aaa/go.mod h1:x2+mFwSvKlBqUES3z5TdoXolS2dWD1ksueRcMORyAJA= github.com/lightninglabs/pool/auctioneerrpc v1.1.2 h1:Dbg+9Z9jXnhimR27EN37foc4aB1uQqndm/YOO+XAdMA= github.com/lightninglabs/pool/auctioneerrpc v1.1.2/go.mod h1:1wKDzN2zEP8srOi0B9iySlEsPdoPhw6oo3Vbm1v4Mhw= github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display h1:Y2WiPkBS/00EiEg0qp0FhehxnQfk3vv8U6Xt3nN+rTY= github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb h1:yfM05S8DXKhuCBp5qSMZdtSwvJ+GFzl94KbXMNB1JDY= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI= -github.com/lightningnetwork/lnd v0.18.3-beta h1:I1Mcz79HGpVGPz0U2jSdxzzqzIi2cwUF0DXtzYJS7C8= -github.com/lightningnetwork/lnd v0.18.3-beta/go.mod h1:Xamph8AYM3iWyyn9w/tx+cLG6Tx1SSnSSPRFn71zuyQ= github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0= github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ= github.com/lightningnetwork/lnd/fn v1.2.1 h1:pPsVGrwi9QBwdLJzaEGK33wmiVKOxs/zc8H7+MamFf0= @@ -1385,6 +1382,10 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/starius/lnd v0.18.0-beta-close-tx7 h1:2MyKobLgXBjkOpjHNLPmBb4IiEGYc620e7nRsSIOUqA= +github.com/starius/lnd v0.18.0-beta-close-tx7/go.mod h1:Y4sP4cQS+V8IpDn6hD6zEyX3dkIwUqLkCPtHfXNaVY0= +github.com/starius/pool v0.6.5-beta.newlnd2 h1:TzIy4xkXJe7uu6kMvxr4qJOjmZO853fwDMKtAPkm0SY= +github.com/starius/pool v0.6.5-beta.newlnd2/go.mod h1:duhbOu3Q8DMg+LhFPWMfRNLp1fHwWP98Y0SyiuIUAjk= 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= diff --git a/lnd/brontide.go b/lnd/brontide.go index 9b45a48..4187e13 100644 --- a/lnd/brontide.go +++ b/lnd/brontide.go @@ -136,7 +136,7 @@ func ConnectPeer(conn *brontide.Conn, connReq *connmgr.ConnReq, ChannelUpdateInterval: discovery.DefaultChannelUpdateInterval, IsAlias: aliasmgr.IsAlias, SignAliasUpdate: func( - *lnwire.ChannelUpdate) (*ecdsa.Signature, error) { + *lnwire.ChannelUpdate1) (*ecdsa.Signature, error) { return nil, errors.New("unimplemented") }, @@ -194,7 +194,7 @@ func ConnectPeer(conn *brontide.Conn, connReq *connmgr.ConnReq, PrunePersistentPeerConnection: func(_ [33]byte) {}, FetchLastChanUpdate: func(_ lnwire.ShortChannelID) ( - *lnwire.ChannelUpdate, error) { + *lnwire.ChannelUpdate1, error) { return nil, errors.New("unimplemented") }, @@ -223,7 +223,9 @@ func ConnectPeer(conn *brontide.Conn, connReq *connmgr.ConnReq, RequestAlias: func() (lnwire.ShortChannelID, error) { return lnwire.ShortChannelID{}, nil }, - AddLocalAlias: func(_, _ lnwire.ShortChannelID, _ bool) error { + AddLocalAlias: func(_, _ lnwire.ShortChannelID, + _, _ bool) error { + return nil }, Quit: make(chan struct{}), diff --git a/lnd/hdkeychain.go b/lnd/hdkeychain.go index fd95c17..a086868 100644 --- a/lnd/hdkeychain.go +++ b/lnd/hdkeychain.go @@ -15,6 +15,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/shachain" @@ -399,7 +400,10 @@ func P2TaprootStaticRemote(pubKey *btcec.PublicKey, params *chaincfg.Params) (*btcutil.AddressTaproot, *input.CommitScriptTree, error) { - scriptTree, err := input.NewRemoteCommitScriptTree(pubKey) + // FIXME: fill tapLeaf for Tapscript root channels. + var tapLeaf fn.Option[txscript.TapLeaf] + + scriptTree, err := input.NewRemoteCommitScriptTree(pubKey, tapLeaf) if err != nil { return nil, nil, fmt.Errorf("could not create script: %w", err) } diff --git a/tools/Dockerfile b/tools/Dockerfile index 17555ef..03a2768 100644 --- a/tools/Dockerfile +++ b/tools/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22.3-bookworm +FROM golang:1.22.6-bookworm RUN apt-get update && apt-get install -y git ENV GOCACHE=/tmp/build/.cache From 98fcd8ac0e00ef86395cdcb29e26fc85efcd6117 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Sun, 31 Dec 2023 17:29:58 +0100 Subject: [PATCH 2/7] lnd: put MusigSessionManager to Signer Remove MockSigner from Signer. MockSigner has MusigSessionManager is an embedded field, and it is the only part of MockSigner needed in Signer. --- lnd/signer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnd/signer.go b/lnd/signer.go index 2173a43..84b32cd 100644 --- a/lnd/signer.go +++ b/lnd/signer.go @@ -20,7 +20,7 @@ import ( ) type Signer struct { - input.MockSigner + *input.MusigSessionManager ExtendedKey *hdkeychain.ExtendedKey ChainParams *chaincfg.Params From 5af2ebf9ad960c68e948139c433f1b76df2c28df Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Sun, 7 Jul 2024 01:03:15 -0300 Subject: [PATCH 3/7] lnd/signer: add ECDH method It can now be used as keychain.ECDHRing. --- lnd/signer.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lnd/signer.go b/lnd/signer.go index 84b32cd..d16b5b3 100644 --- a/lnd/signer.go +++ b/lnd/signer.go @@ -265,3 +265,26 @@ func ECDH(privKey *btcec.PrivateKey, pub *btcec.PublicKey) ([32]byte, error) { sPubKey := btcec.NewPublicKey(&s.X, &s.Y) return sha256.Sum256(sPubKey.SerializeCompressed()), nil } + +// ECDH performs a scalar multiplication (ECDH-like operation) between +// the target key descriptor and remote public key. The output +// returned will be the sha256 of the resulting shared point serialized +// in compressed format. If k is our private key, and P is the public +// key, we perform the following operation: +// +// sx := k*P +// s := sha256(sx.SerializeCompressed()) +// +// NOTE: This is part of the keychain.ECDHRing interface. +func (s *Signer) ECDH(keyDesc keychain.KeyDescriptor, pubKey *btcec.PublicKey) ( + [32]byte, error) { + + // First, derive the private key. + privKey, err := s.FetchPrivateKey(&keyDesc) + if err != nil { + return [32]byte{}, fmt.Errorf("failed to derive the private "+ + "key: %w", err) + } + + return ECDH(privKey, pubKey) +} From 05b187d33703bef14dc862d1165bdbe8bae0cdf7 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Sat, 30 Dec 2023 11:42:05 -0300 Subject: [PATCH 4/7] forceclose: factor out warnings to a const It will be reused in scbforceclose. --- cmd/chantools/forceclose.go | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/cmd/chantools/forceclose.go b/cmd/chantools/forceclose.go index 9e4cc57..5aa11b8 100644 --- a/cmd/chantools/forceclose.go +++ b/cmd/chantools/forceclose.go @@ -19,6 +19,20 @@ import ( "github.com/spf13/cobra" ) +const forceCloseWarning = ` +If you are certain that a node is offline for good (AFTER you've tried SCB!) +and a channel is still open, you can use this method to force-close your +latest state that you have in your channel.db. + +**!!! WARNING !!! DANGER !!! WARNING !!!** + +If you do this and the state that you publish is *not* the latest state, then +the remote node *could* punish you by taking the whole channel amount *if* they +come online before you can sweep the funds from the time locked (144 - 2000 +blocks) transaction *or* they have a watch tower looking out for them. + +**This should absolutely be the last resort and you have been warned!**` + type forceCloseCommand struct { APIURL string ChannelDB string @@ -35,18 +49,7 @@ func newForceCloseCommand() *cobra.Command { Use: "forceclose", Short: "Force-close the last state that is in the channel.db " + "provided", - Long: `If you are certain that a node is offline for good (AFTER -you've tried SCB!) and a channel is still open, you can use this method to -force-close your latest state that you have in your channel.db. - -**!!! WARNING !!! DANGER !!! WARNING !!!** - -If you do this and the state that you publish is *not* the latest state, then -the remote node *could* punish you by taking the whole channel amount *if* they -come online before you can sweep the funds from the time locked (144 - 2000 -blocks) transaction *or* they have a watch tower looking out for them. - -**This should absolutely be the last resort and you have been warned!**`, + Long: forceCloseWarning, Example: `chantools forceclose \ --fromsummary results/summary-xxxx-yyyy.json --channeldb ~/.lnd/data/graph/mainnet/channel.db \ From 7c7f09256e9ba89b31a9035c753924418b485a33 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Thu, 19 Sep 2024 17:07:05 -0300 Subject: [PATCH 5/7] add package scbforceclose It provides function SignCloseTx which produces a signed force close transaction from a channel backup and private key material. --- scbforceclose/sign_close_tx.go | 159 ++++++++++++++++++++ scbforceclose/sign_close_tx_test.go | 133 ++++++++++++++++ scbforceclose/testdata/channel_backups.json | 60 ++++++++ 3 files changed, 352 insertions(+) create mode 100644 scbforceclose/sign_close_tx.go create mode 100644 scbforceclose/sign_close_tx_test.go create mode 100644 scbforceclose/testdata/channel_backups.json diff --git a/scbforceclose/sign_close_tx.go b/scbforceclose/sign_close_tx.go new file mode 100644 index 0000000..7838b7b --- /dev/null +++ b/scbforceclose/sign_close_tx.go @@ -0,0 +1,159 @@ +package scbforceclose + +import ( + "errors" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/chanbackup" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/shachain" +) + +// SignCloseTx produces a signed commit tx from a channel backup. +func SignCloseTx(s chanbackup.Single, keyRing keychain.KeyRing, + ecdher keychain.ECDHRing, signer input.Signer) (*wire.MsgTx, error) { + + var errNoInputs = errors.New("channel backup does not have data " + + "needed to sign force close tx") + + closeTxInputs, err := s.CloseTxInputs.UnwrapOrErr(errNoInputs) + if err != nil { + return nil, err + } + + // Each of the keys in our local channel config only have their + // locators populated, so we'll re-derive the raw key now. + localMultiSigKey, err := keyRing.DeriveKey( + s.LocalChanCfg.MultiSigKey.KeyLocator, + ) + if err != nil { + return nil, fmt.Errorf("unable to derive multisig key: %w", err) + } + + // Determine the value of tapscriptRoot option. + tapscriptRootOpt := fn.None[chainhash.Hash]() + if s.Version.HasTapscriptRoot() { + tapscriptRootOpt = closeTxInputs.TapscriptRoot + } + + // Create signature descriptor. + signDesc, err := createSignDesc( + localMultiSigKey, s.RemoteChanCfg.MultiSigKey.PubKey, + s.Version, s.Capacity, tapscriptRootOpt, + ) + if err != nil { + return nil, fmt.Errorf("failed to create signDesc: %w", err) + } + + // Build inputs for GetSignedCommitTx. + inputs := lnwallet.SignedCommitTxInputs{ + CommitTx: closeTxInputs.CommitTx, + CommitSig: closeTxInputs.CommitSig, + OurKey: localMultiSigKey, + TheirKey: s.RemoteChanCfg.MultiSigKey, + SignDesc: signDesc, + } + + // Add special fields in case of a taproot channel. + if s.Version.IsTaproot() { + producer, err := createTaprootNonceProducer( + s.ShaChainRootDesc, localMultiSigKey.PubKey, ecdher, + ) + if err != nil { + return nil, err + } + inputs.Taproot = fn.Some(lnwallet.TaprootSignedCommitTxInputs{ + CommitHeight: closeTxInputs.CommitHeight, + TaprootNonceProducer: producer, + TapscriptRoot: tapscriptRootOpt, + }) + } + + return lnwallet.GetSignedCommitTx(inputs, signer) +} + +// createSignDesc creates SignDescriptor from local and remote keys, +// backup version and capacity. +// See LightningChannel.createSignDesc on how signDesc is produced. +func createSignDesc(localMultiSigKey keychain.KeyDescriptor, + remoteKey *btcec.PublicKey, version chanbackup.SingleBackupVersion, + capacity btcutil.Amount, tapscriptRoot fn.Option[chainhash.Hash]) ( + *input.SignDescriptor, error) { + + var fundingPkScript, multiSigScript []byte + + localKey := localMultiSigKey.PubKey + + var err error + if version.IsTaproot() { + fundingPkScript, _, err = input.GenTaprootFundingScript( + localKey, remoteKey, int64(capacity), tapscriptRoot, + ) + if err != nil { + return nil, err + } + } else { + multiSigScript, err = input.GenMultiSigScript( + localKey.SerializeCompressed(), + remoteKey.SerializeCompressed(), + ) + if err != nil { + return nil, err + } + + fundingPkScript, err = input.WitnessScriptHash(multiSigScript) + if err != nil { + return nil, err + } + } + + return &input.SignDescriptor{ + KeyDesc: localMultiSigKey, + WitnessScript: multiSigScript, + Output: &wire.TxOut{ + PkScript: fundingPkScript, + Value: int64(capacity), + }, + HashType: txscript.SigHashAll, + PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher( + fundingPkScript, int64(capacity), + ), + InputIndex: 0, + }, nil +} + +// createTaprootNonceProducer makes taproot nonce producer from a +// ShaChainRootDesc and our public multisig key. +func createTaprootNonceProducer(shaChainRootDesc keychain.KeyDescriptor, + localKey *btcec.PublicKey, ecdher keychain.ECDHRing) (shachain.Producer, + error) { + + if shaChainRootDesc.PubKey != nil { + return nil, errors.New("taproot channels always use ECDH, " + + "but legacy ShaChainRootDesc with pubkey found") + } + + // This is the scheme in which the shachain root is derived via an ECDH + // operation on the private key of ShaChainRootDesc and our public + // multisig key. + ecdh, err := ecdher.ECDH(shaChainRootDesc, localKey) + if err != nil { + return nil, fmt.Errorf("ecdh failed: %w", err) + } + + // The shachain root that seeds RevocationProducer for this channel. + revRoot := chainhash.Hash(ecdh) + + revocationProducer := shachain.NewRevocationProducer(revRoot) + + return channeldb.DeriveMusig2Shachain(revocationProducer) +} diff --git a/scbforceclose/sign_close_tx_test.go b/scbforceclose/sign_close_tx_test.go new file mode 100644 index 0000000..eb5d164 --- /dev/null +++ b/scbforceclose/sign_close_tx_test.go @@ -0,0 +1,133 @@ +package scbforceclose + +import ( + "bytes" + _ "embed" + "encoding/hex" + "encoding/json" + "strings" + "testing" + + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/lightninglabs/chantools/lnd" + "github.com/lightningnetwork/lnd/aezeed" + "github.com/lightningnetwork/lnd/chanbackup" + "github.com/lightningnetwork/lnd/input" + "github.com/stretchr/testify/require" +) + +//go:embed testdata/channel_backups.json +var channelBackupsJSON []byte + +// TestSignCloseTx tests that SignCloseTx produces valid transactions. +func TestSignCloseTx(t *testing.T) { + // Load prepared channel backups with seeds and passwords. + type TestCase struct { + Name string `json:"name"` + RootKey string `json:"rootkey"` + Password string `json:"password"` + Mnemonic string `json:"mnemonic"` + Single bool `json:"single"` + ChannelBackup string `json:"channel_backup"` + PkScript string `json:"pk_script"` + AmountSats int64 `json:"amount_sats"` + } + + var testdata struct { + Cases []TestCase `json:"cases"` + } + require.NoError(t, json.Unmarshal(channelBackupsJSON, &testdata)) + + chainParams := &chaincfg.RegressionNetParams + + for _, tc := range testdata.Cases { + t.Run(tc.Name, func(t *testing.T) { + var extendedKey *hdkeychain.ExtendedKey + if tc.RootKey != "" { + // Parse root key. + var err error + extendedKey, err = hdkeychain.NewKeyFromString( + tc.RootKey, + ) + require.NoError(t, err) + } else { + // Generate root key from seed and password. + words := strings.Split(tc.Mnemonic, " ") + require.Len(t, words, 24) + var mnemonic aezeed.Mnemonic + copy(mnemonic[:], words) + cipherSeed, err := mnemonic.ToCipherSeed( + []byte(tc.Password), + ) + require.NoError(t, err) + extendedKey, err = hdkeychain.NewMaster( + cipherSeed.Entropy[:], chainParams, + ) + require.NoError(t, err) + } + + // Make key ring and signer. + keyRing := &lnd.HDKeyRing{ + ExtendedKey: extendedKey, + ChainParams: chainParams, + } + + signer := &lnd.Signer{ + ExtendedKey: extendedKey, + ChainParams: chainParams, + } + musigSessionManager := input.NewMusigSessionManager( + signer.FetchPrivateKey, + ) + signer.MusigSessionManager = musigSessionManager + + // Unpack channel.backup. + backup, err := hex.DecodeString( + tc.ChannelBackup, + ) + require.NoError(t, err) + r := bytes.NewReader(backup) + + var s chanbackup.Single + if tc.Single { + err := s.UnpackFromReader(r, keyRing) + require.NoError(t, err) + } else { + var m chanbackup.Multi + err := m.UnpackFromReader(r, keyRing) + require.NoError(t, err) + + // Extract a single channel backup from + // multi backup. + require.Len(t, m.StaticBackups, 1) + s = m.StaticBackups[0] + } + + // Sign force close transaction. + sweepTx, err := SignCloseTx( + s, keyRing, signer, signer, + ) + require.NoError(t, err) + + // Check if the transaction is valid. + pkScript, err := hex.DecodeString(tc.PkScript) + require.NoError(t, err) + fetcher := txscript.NewCannedPrevOutputFetcher( + pkScript, tc.AmountSats, + ) + + sigHashes := txscript.NewTxSigHashes(sweepTx, fetcher) + + vm, err := txscript.NewEngine( + pkScript, sweepTx, 0, + txscript.StandardVerifyFlags, + nil, sigHashes, tc.AmountSats, fetcher, + ) + require.NoError(t, err) + + require.NoError(t, vm.Execute()) + }) + } +} diff --git a/scbforceclose/testdata/channel_backups.json b/scbforceclose/testdata/channel_backups.json new file mode 100644 index 0000000..e566241 --- /dev/null +++ b/scbforceclose/testdata/channel_backups.json @@ -0,0 +1,60 @@ +{ + "cases": [ + { + "name": "anchors", + "password": "El Psy Kongroo", + "mnemonic": "abstract noble exhaust silk pill game train lab lawsuit tunnel adult spin goddess elegant island ridge kidney sunset door train method install misery oyster", + "channel_backup": "78416c6f2904678efccccc03057e56c3df5c8e4a14d041d987f2051482003cabc9f699bb77aa6ab22eeb8f8ea821a5bada162b6f519394ed641cb02f97928604daf64dfb5b202a89c211769f3dd9020d55fcfdb192304348eabf1b59e6be5bfc1f3dc95f7eec50e1b35b0bcd440667d212c25b27b36f1213a82099600b0b2f50a08d1d13804560b89bd55dc8b0811086ee3bda13edce35573fc3900bd082ee42231b19e6be5252286eafbf79b7ffeefe2e132ab7bef2387b45e30039d4e2dfc9a82dcdbe929609902126045b953e9d1795898b4e7f2d9eb2754975701cb361fed0a15a1b0724aec181c2bf75ab4e78d234f2d54e2ee092cfd4936309090446d38b0c2809fff84c75883a72c5f8794a6abfea8119042b39a3058906d262f5e5610691b330239a35cdcbef4e401bd022ff65fb4349fa108232d0b99b5d0cb1f4083ee9eb7740de2fc5c17908dfe11ece88805f0acc8b3b52cbbe92d96b43bc962d1ef69544af07c7e0980b1c142c342afd792efecb9c2c365d0cb581621fd8987afce944882550baf62e3564c82c9986c5c5ea2e17b7b59a7b134448f500d1cb3c763690c046e93e3a51b4e69f2d005c7ed7c343a6370841a0326e57f78ee2fd5434e889948666f822dbaf6d627d462805d56e7bc85104d3b813775677306f18a48e4ba967980920cde4cc954b01b80a2cd0c3fa2eef1cdf1164f9e61b537ea335b2d76dd5c9957b2e31f343644918e560632ff9d9240678e2b799566be633489913d50833d5cfbc5856561d057978ba1bd49a1b6bb46389de09f04384dd0e352aa1b32234340972bc18fcd83aa811866d968e656283d7a4c71c4040a1fde8e02cdf4c8a71515df3f002b85b9dc3461f3770931d5c08ce92e699e4f54d79081333de04a4426f3852e061bdbb6e4076a145004bd507c37ca2161fe49ca53d0193f781a67e9ecb3329f04cda583181e047b663516c8c3f2b4e0a1a6728fd5bdbf9f0331ff7578c71766f8e228b8323e0", + "pk_script": "0020caf2b519c62b353cfe75f4588696b0b3e7b6b3995a5e7f6bca62e707ae35f37b", + "amount_sats": 10000000 + }, + { + "name": "script_enforced_lease", + "password": "El Psy Kongroo", + "mnemonic": "about latin outer when reunion menu matrix gorilla custom purpose dwarf into knock sunset equal pattern wash pepper daughter secret empower ice jeans hand", + "channel_backup": "02adea9c773a3a7d053feb6cdb9eb11cd9938440088865502528f417b7b21f97a3f7bf1dba23721da43ab2c39b1bc8e0b469f1bbd89303c2b08a7ff7980420e5957ea0b0d9b485617a27b4c71d54eef2fac2e94d74525756bac60fc8b6dac01f3b9680281ede6bd3b5503d1b09d4a5634ee50749fc0994838b75fea6056f3d2d098c75fc1b5bbb4c724c591b4ca573d40a521a5f0c2769368c3a0fa364c5617f519d8b755819df178ab5a7066631f50c5733224b556acd3fcbc58c39fc8f46fe3a04eb212cbd00944fcddb5b3ea121f9bdf2a114e965b736643a7bf5556fff497f0a57fd4f1aff1f806715a866a1813d1daf5c4410c6df2e3838e9dcf73f995179371240f8b15e908cacdd608cf6f2e73c2f1e329dd52795425c959e170d66684a65fabed97504cf6497fc8ef336527a20140f2dedb043e94959b6e81790ae2acd9c6fff41b79645b4be0041203b3826ad58446019bbaa0978db7bf662d78c20ada65ec5f09baafa01b4ca06fb69068468127ced0194d7b74ea5398128aef167dad15485e2404e43c8c0110b491733956adae01bbc170a5694356a4b436c95af31aa92f61d1d98c15e2a88431597ef623adab35979d2af72ea809c5ebcd663a26f614ece9040fd666b282449499b83b0c4000b094d8106d257547beda1bdf60f3ab8b759b5bf077b4d533d3afcb2f971315b0c5df04953ac3896645a1beb97ec906ffce547829425613d5e0a6359760b08f3233bcbb20bf45f84136dd6516356b52c4e5bc97ca7eddc3d9ffe5bdfa848f6e1203e66735c38743630a34f2da355c22afd3c035c1bdc8164d8c9548d5ecf26e873739bed0ab351bcfa18325bb0442f5052b7cd3d63c638ec57ae19cf4480684c837a1849e70bd63280254e350abbd59307ec12e756b76de1861244c3afff2f3d75e29b6114f4558ffd098b90bec8b75c03515b177b490d603f529ede44275fe2c43c5ee2d0ebf44c8245da7d693569ead7c25a0ce1225b66c9e4a94ac8377f5c", + "pk_script": "002018c3a51857ce7d6ddb34278735c1694cde9fee300193a4c0910d806f046cba48", + "amount_sats": 10000000 + }, + { + "name": "zero-conf_anchors_channel", + "password": "El Psy Kongroo", + "mnemonic": "absent half lock alone envelope attitude liquid success token load innocent sign finish belt oblige omit nurse ski sick shock dizzy major forward dentist", + "channel_backup": "80faa24b32177ec4f543e29de9aad66d35824e4e3b20c144d147bf21e3189f93ec8b07edca69a9d2ff2d498a53c158d5f3844b1d32ab3e7cf748b661bb671b4b295961c1c74c28334775348d76d173feefb857f64565934c0014feb5a02bee8d035831e5d99ce35a84693e7ebc3d81a66cf9310bf23a329471d1e3a784379936fb56b64b5ac0acd396bc777c6a6826ba8dd340a10a004888069d1a93f5946cdfe00a4c76fd1ddd3e8c52cbb18d361946e8dd2a3162a201eece77ce9674dd9f36de88c568117a5ebe6b324130e79d37776978fd2584713337e7047766374f505bfab386dc6e04c40072b70d464c9579505d5348b6adabb605cc2ee939cbf1c23896887bdadfbcc209792260f7f0ce71955612246d9f1d64bab0adcb52281e2ec67687fe1f9ba59f416d66f3957b456a2da6de442de515f179d61231239f2b13d2a774973ca53a405db7e4feae14aeaec835f9646c500b6ccfdf1d1cd042647ce3889dedee174b78a3ce790b9e9fbfc025f896c60a2a28eb9d122bb784cddd6d99ee4f09080b60b2c4f5d14f14da9be2de5ffe85a3577a6c57e192879c9b406bd7faf55e37e65ae46d3c58d755286997a8e14a6f92632aca978f7fdb8bff2797e8f2505e9325aa3a4e3a3c11c48f05f3a0379367d02838c09a2b0e351a6c81d62257cfc4f248953efa453cc9af6d5dbba596cb3a84f356195298244535b716403f7a35a7f76b421a07a532b501d4e32931a7b3bbd9113ff1ea191fbdc4d310d282ac6ee92ef910efb32c3a5b65268c58d6c093969a50d29aa8fa044eab64b85adc173a8ebec5919207d823bae02a83b278f5a31d2a2e79f15ace7f417c073f6a12f4be3c93e2c894902066fba996765ba51be80377754edbaf8c166ed097b5e13e9274c03394e537deeab1825873dde6881b5d44c319e61052dd49eb7fbbc8ac4ef27c95d1777845ea4074abcfac1c3fcb2545376984c86f226a435369734bf76ed89462af2fb1d3381647b3ef7d3c6c", + "pk_script": "0020cef489819bbeba507b79f89bd7dd12274a36cf89668bf7357ba400d9ebb566a9", + "amount_sats": 10000000 + }, + { + "name": "zero-conf_script-enforced_leased_channel", + "password": "El Psy Kongroo", + "mnemonic": "abstract creek climb clock sport twist sample expose fit pulp wrestle benefit head spirit scatter vapor figure song enact swallow bean what shop soap", + "channel_backup": "bf53dd38c33aa1d1bd976dca3a383fb6744286b855109d4f1a118303f1efb0474de39caba4a7ce63787c39f9c80cd24f817fa2566b702274bafa380b3c92612c2ba897cc65aec9c28787a9701abe4ab92c07e37859dba3c1e77fb198d2f24ae5c7e5627f6626cd432f6baea6b0ebc62c8c3f57113db85e871b124181660997d84ff9f275d59403088f93df3f724ead74a6e317d04a3fb14f2bd7a1b481d6da3298b06fc5c4e217a834ce1768848687c36576d2d4fd7cc02740c18d8db4c33345b22448c9c8c955ed14845c685f812e5da8d54be53ba6922c6821719b3812fc7957c56b2e4c47ffe7ebef417b4e6f91b49e75b965b438f3039d8ce2c90ed82be60e6b2e7b09f6c223a9432a06cb49551f75c3339054e82e3baf6821d114b933dad726c37675a0772815b6ed44b04f497324ba1281f967e19636c5cc501cfd1e1892d945f478a37b902f858f5fa1a3640c2c59154c5976d4c50498cd64b97ff8347cc70eabb6a5ca2004fb190990fa0d0642c9715dbc8658674506ccafce6d64a5d7e0924f0796882e2a108912d35cd7179eeff7c23fd1ee1a2bbf595ea5b90d517b500b5f2c0f0a2bb47c9b7f496afe05db04257676d83e5a33b9724a99d41c4062ebebe743314bd3b5581cc374110b49bb4584645e339e80b9588b4981a758a0be808151d4e7a1f4dfbc538602686c6d03bfee77f089411149caee486745a80461937dc1074fdbab630b1280e7a367c80b0ca53235ab6777c9ff3eee9acf56e299745fd422dd72058fd8742338c64f2369dbc9752f86fafbab28a48c4438f49f452c0e6772e7e0a2c56c9e1d40d6f46ff7e6c7c2932fb5fee9e840989305e2e3568b7501ba22beae112aa9c8d661243fbdbf8162a3bacda60f1414f9f3a19d06ef48fb0c11f9edda1d7e4d4b4cfbdc7b85746890c3e1bacc74628b72c22617213f0e6a09f044ef8f1fb102fe3e4bd2a4c5575fd25b9268d6ce7bd7d7c2b40667e9a4b9b8aa01eac66c497641d26ca54b593f23", + "pk_script": "0020d90ecbfd48f0225d1e2c701a4a3fc60ded0dd0569b1e5d9bb8331ab56624c616", + "amount_sats": 10000000 + }, + { + "name": "taproot", + "password": "El Psy Kongroo", + "mnemonic": "absorb sting naive sail knock original mention any paddle safe throw pottery viable edge embody inmate heavy nerve stone wear step hero circle lottery", + "channel_backup": "b0c49e857a337ca535ec222244a5c2311d871538223c5ff9bd14f87b3598137fa09fca4f2b98ba4ba1fdebf2edb644eca05ad77b7be16e2cddd6d842a5d18be9a9b9bdde457db33549df54861bfa2bb8894341e1f5eeccabb2d47b15033b569e0a471d55c31da016262efa9966c857abaa34066d4f85a891adf2c0e34aea7c02b144000ae990c8851fc13ee5edbad79d31f518f09576910dc7405a9535925324e7cba971896c09d86478f5cc7aa9127d17b7b841762c2605c4cd042900e0f263060782502756abae2fc3127248b38251665a42528c4cc462ef9beb09d1673401db574afd725c9f31fb2e9dccd7bb920c41a78c4d7c819e6bf3052e3111f711b33b59c4779b9e48368d41f99bab74dccf61c4a9edbf3a6551c6042f6b0eb6607d4839754438de266048506af1161a8a5883e0ce32d6bd51dc6793be793693e494401d26a02ae48a870e4b8d792dc9ff590b13a3099f6b6a42ffb97e4790e759889a8ee5fa685e86c61e185f4795568a1e75b598b45f94e25e62455c7feae2e6b44e1e6f11817f4efb7af58e64a92b53de5a08c40b4d2b0fa0470f90cbe7b0ffb4101334304fb1bed5bf3b3146eb82234d1fdd4f3f31e89d0f09b667a6de11acafb17d6ed302e88b048d935ebd9ed1e33929105291ee54aa6aba714a5c9216294648fc6002f6bea441935a6a8eb9172d9343cdbe39289d725e59f34830497ae7277a58931ca0ecdd6c6976d7dc0bc22df6ca787a3b56e09b77b187ce0314eca0fe2bda9960cf8f06f49cb9a0e856c84055ec6f5b5e60aea041eef1b10ee14e3f28ea40eaae9a8eede2b8f910b2239472fd757be19a412be471a5ec6cfcb9c92c1969054544a36ffb6c75531fffd47da3d51a676b33acad327065fba745a40e635ff3266d4ffebe1c507449fa1043f98a04cc0c882365bdcdb53db89a4d8a841fc9783c1cd45e5555600d46932b8f047d27e0a1d2d58958664c38a0b703a36511c8cd0dfa3769a4e71a9249e730304bb5dbd03a736d8d5fb62954b863b966a5451ebe236c6d98310d09213524a80fc0d4649477", + "pk_script": "51202deb800785339a99621799be0f62b653fc917579d416eee280403e381b6a8384", + "amount_sats": 10000000 + }, + { + "name": "taproot_zero_conf", + "password": "El Psy Kongroo", + "mnemonic": "abandon digital cable worth sausage tape wait reject oyster sunset punch pudding arctic armor label negative seminar estate adapt carbon cage school bench ankle", + "channel_backup": "a6fca570c5b6ecc4e8cd1c53ab844f9a0deb12bcd5248b42376f14f44c876b019fbd7e5106b0e6a009712a982a026989b4f83a2f216e8ca99620bac77ed73ebecfffd0e4df16da6352e80313a8978ac1067fdcc2e046108ae9e7ab26a4cbd4e62b704cca3510fdefb0fa2e64f11c1666eac0e2b46d9af96d4e19cb99292bf9682420c562751b068cc25bb35ab7afb509037f9b202838c18ad2f86d8b19d8f24c738af1bcc5b12de84ed553fe7ebeb0d16277d7ffdd2d0781528ff271a338bac8d51b98ca4f7213538b7f6db78376350796884a62b9bd0b71855e2205e9c2cfbced59e361d6478ab4ebc1108554f296713151839fe694723ea6eb2d991fff5e85b83082e8ab4c60d1d8a1fa864360610bfbb508737ee492aaefa0fcc0910a7efd5fa764988eb4bc76268da062d51ce30672c3894b7cc4c71b5e60faded8458ed144edf50257be2c93cd41891049b5d98fb351cad1095f9932bf75af711d2cec4390a3e0852a1bc841d1165775b403d594267d63a1c047d87a2ca1a056c7ede6a43e1fd59b7bf845d479714029e368ab77380353a4227e1d9ac6e8bb0e32f8dd03843aff9a8a8e1f58033e28c95f2afb076d277cc3e45d9f6bcabc242ad552717bf632f7472bac0e1ba14c4be6fa40c879e3093d7daad1fac4e7c57822fe739c652f1f34f45ba90ef74a74c2f44c3cd5ac239dc92fc82ae7112543f02f8db4c22ff907f7c58d2d47177a813efc8503b79613c9cf7d441e8d19bc582c48e27beb39d919db63ef73d3d5c3f288a0e7caa2f0946ddb7ac5ecf6a3ea211716f274c307ac77f8278ac7cd738c2ca0ca9873ddac0d3430cf6adab04a5a26da098c72030a4f91f89c0fdb1a2bc2e22ce3e2ebb16a50f06cb4e4adc4505e3bc4ebe1f48b247ccce0cb2becd9a0105f31108dfb6d31ce0aac954dff7f7130cdfcf138336f200498eb31fccdbfddc8fa212304ef0e088df930d94521ae4da903a5620c88696cf6f967d95bd5c8f04ee2c53a33b9440f1c45cf14234fed9fd50f9c175d18ab7f7c34752600baeaa4b832a39880db3a31567c", + "pk_script": "5120216c4e32dc41cb78fb949cdfffc9e66cf4fc9fb29e95a3a38bae82d0072b0f29", + "amount_sats": 10000000 + }, + { + "name": "custom_channel", + "rootkey": "tprv8ZgxMBicQKsPf1ujkCE39iYKKmdzD4MgdpKb3EtiJrNCnE2K4GFMtL11adjRsCwA6J1jVKndF6GXpMdSpBGev9cczcjyhrSDVVeEXmwtmCt", + "single": true, + "channel_backup": "cbb0bde3b8a7a1158fc2186290c980862a384027bc6d7c8e82b4ae48b9294abe200cef8f07a96703981e573aa6b5e78f9b07dd240592e281f8e4027654c47ae612863d3c52d8f7ae33f433ceef26093179d5cfce3d11495f92542ad935953a97bd0454e76276494c24bab9448985103db9fbc1dea3bb8408f2b7fd0987745c3c3343f51c1285288e0248bb9288a0f65067225640281c97b201c29f9a8ff55d81f727484adeb58e4985559b1790fe23257bcf50186cb69e3dcc21f33131232e6510b080cdbc934d0c5d6c7283a1666e319a5f1862f5e285f22e06d9f35750a35778bae69c790fa2332caac18a25086d9f0500071214606f1a350bddc083a34b4362e3e70fe0c4063d3230387495e88570564dfa242ccfceb1d607f64726a0545066c482b73d00d6ac3e5527bd0aba6c8f79d2268c1824bda5ac9f48a7e545057f41cda74b9bcc0823c3357b85feb219ebf3bbe4b1d2c936b1589ab86f9e88f54f4893a31c1bd6ef1f71a59403a5daf51ed922350be2885b3604dd689e75c24f9d7c028138d9428a23b8f2cb624fa0ac2f6f4cbd9b36121f8bd20130c166a419cace33ba17dc03547e8679a319cd2d7061653559939e74e5e848dc62e1c051b1efe2a26f8f7028a1bedf2ddebfa7b89e4bd5a09248be62ea0a636c144ca80c9531dda69ea4007931105fe48348422b8eaea139d98ed34e5c805cc672f58c534bf44e64a2716fe3755700b704faae45e37c164f8ac286f99a903fdf70c781b043df7607c3bd662a6ae5e31e99f0ebc517c966d5d0bbc045110150b5b75d64a2bf2c6442a8cb6d91f0b88188094e6bc3bbc619bbfe7513c970ea4d538903cd3c1cb03962d813dcbb1573a544f0f2b45e780222ea3b998fc4e9d1a60aa0ac6ac9874fa3f2fef76c03d5857ee225dbbdf167b24d838fd76d8dbcc04e98c7b7481ea4de79be2a793cce7a363c91210eccff0e047b038e98658c233d410aea99b71d", + "pk_script": "5120559ed0f63cde7befccbd0f98fdfb1058a2a556eb33704dda981b90a5c205f742", + "amount_sats": 100000 + } + ] +} From 2cae7afd56fe70ca65378be26c4e0c5e8936b3a8 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Sat, 30 Dec 2023 14:06:52 -0300 Subject: [PATCH 6/7] new command: scbforceclose --- cmd/chantools/root.go | 1 + cmd/chantools/scbforceclose.go | 226 +++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 cmd/chantools/scbforceclose.go diff --git a/cmd/chantools/root.go b/cmd/chantools/root.go index 0f2b34c..44d8c4e 100644 --- a/cmd/chantools/root.go +++ b/cmd/chantools/root.go @@ -114,6 +114,7 @@ func main() { newFilterBackupCommand(), newFixOldBackupCommand(), newForceCloseCommand(), + newScbForceCloseCommand(), newGenImportScriptCommand(), newMigrateDBCommand(), newPullAnchorCommand(), diff --git a/cmd/chantools/scbforceclose.go b/cmd/chantools/scbforceclose.go new file mode 100644 index 0000000..de5d41b --- /dev/null +++ b/cmd/chantools/scbforceclose.go @@ -0,0 +1,226 @@ +package main + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "os" + "strings" + + "github.com/lightninglabs/chantools/btc" + "github.com/lightninglabs/chantools/lnd" + "github.com/lightninglabs/chantools/scbforceclose" + "github.com/lightningnetwork/lnd/chanbackup" + "github.com/lightningnetwork/lnd/input" + "github.com/spf13/cobra" +) + +type scbForceCloseCommand struct { + APIURL string + Publish bool + + // channel.backup. + SingleBackup string + SingleFile string + MultiBackup string + MultiFile string + + rootKey *rootKey + cmd *cobra.Command +} + +func newScbForceCloseCommand() *cobra.Command { + cc := &scbForceCloseCommand{} + cc.cmd = &cobra.Command{ + Use: "scbforceclose", + Short: "Force-close the last state that is in the SCB " + + "provided", + Long: forceCloseWarning, + Example: `chantools scbforceclose --multi_file channel.backup`, + RunE: cc.Execute, + } + cc.cmd.Flags().StringVar( + &cc.APIURL, "apiurl", defaultAPIURL, "API URL to use (must "+ + "be esplora compatible)", + ) + + cc.cmd.Flags().StringVar( + &cc.SingleBackup, "single_backup", "", "a hex encoded single "+ + "channel backup obtained from exportchanbackup for "+ + "force-closing channels", + ) + cc.cmd.Flags().StringVar( + &cc.MultiBackup, "multi_backup", "", "a hex encoded "+ + "multi-channel backup obtained from exportchanbackup "+ + "for force-closing channels", + ) + cc.cmd.Flags().StringVar( + &cc.SingleFile, "single_file", "", "the path to a "+ + "single-channel backup file", + ) + cc.cmd.Flags().StringVar( + &cc.MultiFile, "multi_file", "", "the path to a "+ + "single-channel backup file (channel.backup)", + ) + + cc.cmd.Flags().BoolVar( + &cc.Publish, "publish", false, "publish force-closing TX to "+ + "the chain API instead of just printing the TX", + ) + + cc.rootKey = newRootKey(cc.cmd, "decrypting the backup and signing tx") + + return cc.cmd +} + +func (c *scbForceCloseCommand) Execute(_ *cobra.Command, _ []string) error { + extendedKey, err := c.rootKey.read() + if err != nil { + return fmt.Errorf("error reading root key: %w", err) + } + + api := &btc.ExplorerAPI{BaseURL: c.APIURL} + + keyRing := &lnd.HDKeyRing{ + ExtendedKey: extendedKey, + ChainParams: chainParams, + } + + signer := &lnd.Signer{ + ExtendedKey: extendedKey, + ChainParams: chainParams, + } + signer.MusigSessionManager = input.NewMusigSessionManager( + signer.FetchPrivateKey, + ) + + var backups []chanbackup.Single + if c.SingleBackup != "" || c.SingleFile != "" { + if c.SingleBackup != "" && c.SingleFile != "" { + return errors.New("must not pass --single_backup and " + + "--single_file together") + } + var singleBackupBytes []byte + if c.SingleBackup != "" { + singleBackupBytes, err = hex.DecodeString( + c.SingleBackup, + ) + } else if c.SingleFile != "" { + singleBackupBytes, err = os.ReadFile(c.SingleFile) + } + if err != nil { + return fmt.Errorf("failed to get single backup: %w", + err) + } + var s chanbackup.Single + r := bytes.NewReader(singleBackupBytes) + if err := s.UnpackFromReader(r, keyRing); err != nil { + return fmt.Errorf("failed to unpack single backup: %w", + err) + } + backups = append(backups, s) + } + if c.MultiBackup != "" || c.MultiFile != "" { + if len(backups) != 0 { + return errors.New("must not pass single and multi " + + "backups together") + } + if c.MultiBackup != "" && c.MultiFile != "" { + return errors.New("must not pass --multi_backup and " + + "--multi_file together") + } + var multiBackupBytes []byte + if c.MultiBackup != "" { + multiBackupBytes, err = hex.DecodeString(c.MultiBackup) + } else if c.MultiFile != "" { + multiBackupBytes, err = os.ReadFile(c.MultiFile) + } + if err != nil { + return fmt.Errorf("failed to get multi backup: %w", err) + } + var m chanbackup.Multi + r := bytes.NewReader(multiBackupBytes) + if err := m.UnpackFromReader(r, keyRing); err != nil { + return fmt.Errorf("failed to unpack multi backup: %w", + err) + } + backups = append(backups, m.StaticBackups...) + } + + backupsWithInputs := make([]chanbackup.Single, 0, len(backups)) + for _, s := range backups { + if s.CloseTxInputs.IsSome() { + backupsWithInputs = append(backupsWithInputs, s) + } + } + + fmt.Println() + fmt.Printf("Found %d channel backups, %d of them have close tx.\n", + len(backups), len(backupsWithInputs)) + + if len(backupsWithInputs) == 0 { + fmt.Println("No channel backups that can be used for force " + + "close.") + return nil + } + + fmt.Println() + fmt.Println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@") + fmt.Println(strings.TrimSpace(forceCloseWarning)) + fmt.Println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@") + fmt.Println() + + fmt.Printf("Type YES to proceed: ") + var userInput string + if _, err := fmt.Scan(&userInput); err != nil { + return errors.New("failed to read user input") + } + if strings.TrimSpace(userInput) != "YES" { + return errors.New("canceled by user, must type uppercase 'YES'") + } + + if c.Publish { + fmt.Println("Signed transactions will be broadcasted " + + "automatically.") + fmt.Printf("Type YES again to proceed: ") + if _, err := fmt.Scan(&userInput); err != nil { + return errors.New("failed to read user input") + } + if strings.TrimSpace(userInput) != "YES" { + return errors.New("canceled by user, must type " + + "uppercase 'YES'") + } + } + + for _, s := range backupsWithInputs { + signedTx, err := scbforceclose.SignCloseTx( + s, keyRing, signer, signer, + ) + if err != nil { + return fmt.Errorf("signCloseTx failed for %s: %w", + s.FundingOutpoint, err) + } + var buf bytes.Buffer + if err := signedTx.Serialize(&buf); err != nil { + return fmt.Errorf("failed to serialize signed %s: %w", + s.FundingOutpoint, err) + } + txHex := hex.EncodeToString(buf.Bytes()) + fmt.Println("Channel point:", s.FundingOutpoint) + fmt.Println("Raw transaction hex:", txHex) + fmt.Println() + + // Publish TX. + if c.Publish { + response, err := api.PublishTx(txHex) + if err != nil { + return err + } + log.Infof("Published TX %s, response: %s", + signedTx.TxHash(), response) + } + } + + return nil +} From a386bde5fc30c5adf16d4785cdefd475831b5370 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Wed, 17 Jul 2024 10:43:06 -0300 Subject: [PATCH 7/7] dump: include close tx and related fields Include fields closetx, commit sig, commit height, tapscript root. That will add the new data to the output of chantools dumpbackup. --- dump/dump.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/dump/dump.go b/dump/dump.go index 24e512f..e5c4988 100644 --- a/dump/dump.go +++ b/dump/dump.go @@ -45,6 +45,16 @@ type BackupSingle struct { LocalChanCfg ChannelConfig RemoteChanCfg ChannelConfig ShaChainRootDesc KeyDescriptor + CloseTxInputs *CloseTxInputs +} + +// CloseTxInputs is a struct that contains data needed to produce a force close +// transaction from a channel backup as a last resort recovery method. +type CloseTxInputs struct { + CommitTx string + CommitSig string + CommitHeight uint64 + TapscriptRoot string } // OpenChannel is the information we want to dump from an open channel in lnd's @@ -406,7 +416,40 @@ func BackupDump(multi *chanbackup.Multi, params, single.ShaChainRootDesc, ), } + + single.CloseTxInputs.WhenSome( + func(inputs chanbackup.CloseTxInputs) { + // Serialize unsigned transaction. + var buf bytes.Buffer + err := inputs.CommitTx.Serialize(&buf) + if err != nil { + buf.WriteString("error serializing " + + "commit tx: " + err.Error()) + } + tx := buf.Bytes() + + // Serialize TapscriptRoot if present. + var tapscriptRoot string + inputs.TapscriptRoot.WhenSome( + func(tr chainhash.Hash) { + tapscriptRoot = tr.String() + }, + ) + + // Put all CloseTxInputs to dump in human + // readable form. + dumpSingles[idx].CloseTxInputs = &CloseTxInputs{ + CommitTx: hex.EncodeToString(tx), + CommitSig: hex.EncodeToString( + inputs.CommitSig, + ), + CommitHeight: inputs.CommitHeight, + TapscriptRoot: tapscriptRoot, + } + }, + ) } + return dumpSingles }