@@ -2,6 +2,7 @@ package main
22
33import  (
44	"crypto/subtle" 
5+ 	"encoding/hex" 
56	"encoding/json" 
67	"errors" 
78	"fmt" 
@@ -32,7 +33,9 @@ type cacheEntry struct {
3233}
3334
3435type  rescueClosedCommand  struct  {
35- 	ChannelDB  string 
36+ 	ChannelDB    string 
37+ 	Addr         string 
38+ 	CommitPoint  string 
3639
3740	rootKey  * rootKey 
3841	inputs   * inputFlags 
@@ -51,16 +54,34 @@ output that belongs to our side. This can only be used if we have a channel DB
5154that contains the latest commit point. Normally you would use SCB to get the 
5255funds from those channels. But this method can help if the other node doesn't 
5356know about the channels any more but we still have the channel.db from the 
54- moment they force-closed.` ,
57+ moment they force-closed. 
58+ 
59+ The alternative use case for this command is if you got the commit point by 
60+ running the fund-recovery branch of my guggero/lnd fork in combination with the 
61+ fakechanbackup command. Then you need to specify the --commit_point and  
62+ --force_close_addr flags instead of the --channeldb and --fromsummary flags.` ,
5563		Example : `chantools rescueclosed --rootkey xprvxxxxxxxxxx \ 
5664	--fromsummary results/summary-xxxxxx.json \ 
57- 	--channeldb ~/.lnd/data/graph/mainnet/channel.db` ,
65+ 	--channeldb ~/.lnd/data/graph/mainnet/channel.db 
66+ 
67+ chantools rescueclosed --rootkey xprvxxxxxxxxxx \ 
68+ 	--force_close_addr bc1q... \ 
69+ 	--commit_point 03xxxx` ,
5870		RunE : cc .Execute ,
5971	}
6072	cc .cmd .Flags ().StringVar (
6173		& cc .ChannelDB , "channeldb" , "" , "lnd channel.db file to use " + 
6274			"for rescuing force-closed channels" ,
6375	)
76+ 	cc .cmd .Flags ().StringVar (
77+ 		& cc .Addr , "force_close_addr" , "" , "the address the channel " + 
78+ 			"was force closed to" ,
79+ 	)
80+ 	cc .cmd .Flags ().StringVar (
81+ 		& cc .CommitPoint , "commit_point" , "" , "the commit point that " + 
82+ 			"was obtained from the logs after running the " + 
83+ 			"fund-recovery branch of guggero/lnd" ,
84+ 	)
6485
6586	cc .rootKey  =  newRootKey (cc .cmd , "decrypting the backup" )
6687	cc .inputs  =  newInputFlags (cc .cmd )
@@ -74,21 +95,49 @@ func (c *rescueClosedCommand) Execute(_ *cobra.Command, _ []string) error {
7495		return  fmt .Errorf ("error reading root key: %v" , err )
7596	}
7697
77- 	// Check that we have a channel DB.  
78- 	if   c . ChannelDB   ==   ""  { 
79- 		 return   fmt . Errorf ( "rescue DB is required" ) 
80- 	} 
81- 	db , err  :=  lnd .OpenDB (c .ChannelDB , true )
82- 	if  err  !=  nil  {
83- 		return  fmt .Errorf ("error opening rescue DB: %v" , err )
84- 	}
98+ 	// What way of recovery has the user chosen? From summary and DB or from  
99+ 	// address and commit point? 
100+ 	switch  { 
101+ 	case   c . ChannelDB   !=   "" : 
102+ 		 db , err  :=  lnd .OpenDB (c .ChannelDB , true )
103+ 		 if  err  !=  nil  {
104+ 			 return  fmt .Errorf ("error opening rescue DB: %v" , err )
105+ 		 }
85106
86- 	// Parse channel entries from any of the possible input files. 
87- 	entries , err  :=  c .inputs .parseInputType ()
88- 	if  err  !=  nil  {
89- 		return  err 
107+ 		// Parse channel entries from any of the possible input files. 
108+ 		entries , err  :=  c .inputs .parseInputType ()
109+ 		if  err  !=  nil  {
110+ 			return  err 
111+ 		}
112+ 		return  rescueClosedChannels (extendedKey , entries , db )
113+ 
114+ 	case  c .Addr  !=  "" :
115+ 		// First parse address to get targetPubKeyHash from it later. 
116+ 		targetAddr , err  :=  btcutil .DecodeAddress (c .Addr , chainParams )
117+ 		if  err  !=  nil  {
118+ 			return  fmt .Errorf ("error parsing addr: %v" , err )
119+ 		}
120+ 
121+ 		// Now parse the commit point. 
122+ 		commitPointRaw , err  :=  hex .DecodeString (c .CommitPoint )
123+ 		if  err  !=  nil  {
124+ 			return  fmt .Errorf ("error decoding commit point: %v" ,
125+ 				err )
126+ 		}
127+ 		commitPoint , err  :=  btcec .ParsePubKey (
128+ 			commitPointRaw , btcec .S256 (),
129+ 		)
130+ 		if  err  !=  nil  {
131+ 			return  fmt .Errorf ("error parsing commit point: %v" , err )
132+ 		}
133+ 
134+ 		return  rescueClosedChannel (extendedKey , targetAddr , commitPoint )
135+ 
136+ 	default :
137+ 		return  fmt .Errorf ("you either need to specify --channeldb and "  + 
138+ 			"--fromsummary or --force_close_addr and "  + 
139+ 			"--commit_point but not a mixture of them" )
90140	}
91- 	return  rescueClosedChannels (extendedKey , entries , db )
92141}
93142
94143func  rescueClosedChannels (extendedKey  * hdkeychain.ExtendedKey ,
@@ -168,6 +217,47 @@ func rescueClosedChannels(extendedKey *hdkeychain.ExtendedKey,
168217	return  ioutil .WriteFile (fileName , summaryBytes , 0644 )
169218}
170219
220+ func  rescueClosedChannel (extendedKey  * hdkeychain.ExtendedKey ,
221+ 	addr  btcutil.Address , commitPoint  * btcec.PublicKey ) error  {
222+ 
223+ 	// Make the check on the decoded address according to the active 
224+ 	// network (testnet or mainnet only). 
225+ 	if  ! addr .IsForNet (chainParams ) {
226+ 		return  fmt .Errorf ("address: %v is not valid for this network: " + 
227+ 			"%v" , addr , chainParams .Name )
228+ 	}
229+ 
230+ 	// Must be a bech32 native SegWit address. 
231+ 	switch  addr .(type ) {
232+ 	case  * btcutil.AddressWitnessPubKeyHash :
233+ 		log .Infof ("Brute forcing private key for tweaked public key " + 
234+ 			"hash %x\n " , addr .ScriptAddress ())
235+ 
236+ 	default :
237+ 		return  fmt .Errorf ("address: must be a bech32 P2WPKH address" )
238+ 	}
239+ 
240+ 	err  :=  fillCache (extendedKey )
241+ 	if  err  !=  nil  {
242+ 		return  err 
243+ 	}
244+ 
245+ 	wif , err  :=  addrInCache (addr .String (), commitPoint )
246+ 	switch  {
247+ 	case  err  ==  nil :
248+ 		log .Infof ("Found private key %s for address %v!" , wif , addr )
249+ 
250+ 		return  nil 
251+ 
252+ 	case  err  ==  errAddrNotFound :
253+ 		return  fmt .Errorf ("did not find private key for address %v" ,
254+ 			addr )
255+ 
256+ 	default :
257+ 		return  err 
258+ 	}
259+ }
260+ 
171261func  addrInCache (addr  string , perCommitPoint  * btcec.PublicKey ) (string , error ) {
172262	targetPubKeyHash , scriptHash , err  :=  lnd .DecodeAddressHash (
173263		addr , chainParams ,
0 commit comments