@@ -4,17 +4,22 @@ import (
44	"bytes" 
55	"encoding/hex" 
66	"fmt" 
7+ 	"github.com/lightningnetwork/lnd/tor" 
8+ 	"io/ioutil" 
79	"net" 
810	"strconv" 
911	"strings" 
1012	"time" 
1113
1214	"github.com/btcsuite/btcd/btcec" 
15+ 	"github.com/btcsuite/btcd/wire" 
1316	"github.com/btcsuite/btcutil" 
17+ 	"github.com/gogo/protobuf/jsonpb" 
1418	"github.com/guggero/chantools/lnd" 
1519	"github.com/lightningnetwork/lnd/chanbackup" 
1620	"github.com/lightningnetwork/lnd/channeldb" 
1721	"github.com/lightningnetwork/lnd/keychain" 
22+ 	"github.com/lightningnetwork/lnd/lnrpc" 
1823	"github.com/lightningnetwork/lnd/lnwire" 
1924	"github.com/spf13/cobra" 
2025)
@@ -23,9 +28,11 @@ type fakeChanBackupCommand struct {
2328	NodeAddr      string 
2429	ChannelPoint  string 
2530	ShortChanID   string 
26- 	Initiator     bool 
2731	Capacity      uint64 
28- 	MultiFile     string 
32+ 
33+ 	FromChannelGraph  string 
34+ 
35+ 	MultiFile  string 
2936
3037	rootKey  * rootKey 
3138	cmd      * cobra.Command 
@@ -47,13 +54,27 @@ output. But to initiate DLP, we would need to have a channel.backup file.
4754Fortunately, if we have enough information about the channel, we can create a 
4855faked/skeleton channel.backup file that at least lets us talk to the other node 
4956and ask them to do their part. Then we can later brute-force the private key for 
50- the transaction output of our part of the funds (see rescueclosed command).` ,
57+ the transaction output of our part of the funds (see rescueclosed command). 
58+ 
59+ There are two versions of this command: The first one is to create a fake 
60+ backup for a single channel where all flags (except --from_channel_graph) need 
61+ to be set. This is the easiest to use since it only relies on data that is 
62+ publicly available (for example on 1ml.com) but involves more manual work. 
63+ The second version of the command only takes the --from_channel_graph and 
64+ --multi_file flags and tries to assemble all channels found in the public 
65+ network graph (must be provided in the JSON format that the  
66+ 'lncli describegraph' command returns) into a fake backup file. This is the 
67+ most convenient way to use this command but requires one to have a fully synced 
68+ lnd node.` ,
5169		Example : `chantools fakechanbackup --rootkey xprvxxxxxxxxxx \ 
5270	--capacity 123456 \ 
5371	--channelpoint f39310xxxxxxxxxx:1 \ 
54- 	--initiator \ 
5572	--remote_node_addr [email protected] :9735 \ 5673	--short_channel_id 566222x300x1 \ 
74+ 	--multi_file fake.backup 
75+ 
76+ chantools fakechanbackup --rootkey xprvxxxxxxxxxx \ 
77+ 	--from_channel_graph lncli_describegraph.json \ 
5778	--multi_file fake.backup` ,
5879		RunE : cc .Execute ,
5980	}
@@ -76,9 +97,10 @@ the transaction output of our part of the funds (see rescueclosed command).`,
7697		& cc .Capacity , "capacity" , 0 , "the channel's capacity in " + 
7798			"satoshis" ,
7899	)
79- 	cc .cmd .Flags ().BoolVar (
80- 		& cc .Initiator , "initiator" , false , "whether our node was the " + 
81- 			"initiator (funder) of the channel" ,
100+ 	cc .cmd .Flags ().StringVar (
101+ 		& cc .FromChannelGraph , "from_channel_graph" , "" , "the full " + 
102+ 			"LN channel graph in the JSON format that the " + 
103+ 			"'lncli describegraph' returns" ,
82104	)
83105	multiFileName  :=  fmt .Sprintf ("results/fake-%s.backup" ,
84106		time .Now ().Format ("2006-01-02-15-04-05" ))
@@ -104,6 +126,21 @@ func (c *fakeChanBackupCommand) Execute(_ *cobra.Command, _ []string) error {
104126		ChainParams : chainParams ,
105127	}
106128
129+ 	if  c .FromChannelGraph  !=  ""  {
130+ 		graphBytes , err  :=  ioutil .ReadFile (c .FromChannelGraph )
131+ 		if  err  !=  nil  {
132+ 			return  fmt .Errorf ("error reading graph JSON file %s: " + 
133+ 				"%v" , c .FromChannelGraph , err )
134+ 		}
135+ 		graph  :=  & lnrpc.ChannelGraph {}
136+ 		err  =  jsonpb .UnmarshalString (string (graphBytes ), graph )
137+ 		if  err  !=  nil  {
138+ 			return  fmt .Errorf ("error parsing graph JSON: %v" , err )
139+ 		}
140+ 
141+ 		return  backupFromGraph (graph , keyRing , multiFile )
142+ 	}
143+ 
107144	// Parse channel point of channel to fake. 
108145	chanOp , err  :=  lnd .ParseOutpoint (c .ChannelPoint )
109146	if  err  !=  nil  {
@@ -160,8 +197,134 @@ func (c *fakeChanBackupCommand) Execute(_ *cobra.Command, _ []string) error {
160197			"be equal to index on --channelpoint" )
161198	}
162199
163- 	// Create some fake channel config. 
164- 	chanCfg  :=  channeldb.ChannelConfig {
200+ 	singles  :=  []chanbackup.Single {newSingle (
201+ 		* chanOp , shortChanID , nodePubkey , []net.Addr {addr },
202+ 		btcutil .Amount (c .Capacity ),
203+ 	)}
204+ 	return  writeBackups (singles , keyRing , multiFile )
205+ }
206+ 
207+ func  backupFromGraph (graph  * lnrpc.ChannelGraph , keyRing  * lnd.HDKeyRing ,
208+ 	multiFile  * chanbackup.MultiFile ) error  {
209+ 
210+ 	// Since we have the master root key, we can find out our local node's 
211+ 	// identity pubkey by just deriving it. 
212+ 	nodePubKey , err  :=  keyRing .NodePubKey ()
213+ 	if  err  !=  nil  {
214+ 		return  fmt .Errorf ("error deriving node pubkey: %v" , err )
215+ 	}
216+ 	nodePubKeyStr  :=  hex .EncodeToString (nodePubKey .SerializeCompressed ())
217+ 
218+ 	// Let's now find all channels in the graph that our node is part of. 
219+ 	channels  :=  lnd .AllNodeChannels (graph , nodePubKeyStr )
220+ 
221+ 	// Let's create a single backup entry for each channel. 
222+ 	singles  :=  make ([]chanbackup.Single , len (channels ))
223+ 	for  idx , channel  :=  range  channels  {
224+ 		var  peerPubKeyStr  string 
225+ 		if  channel .Node1Pub  ==  nodePubKeyStr  {
226+ 			peerPubKeyStr  =  channel .Node2Pub 
227+ 		} else  {
228+ 			peerPubKeyStr  =  channel .Node1Pub 
229+ 		}
230+ 
231+ 		peerPubKeyBytes , err  :=  hex .DecodeString (peerPubKeyStr )
232+ 		if  err  !=  nil  {
233+ 			return  fmt .Errorf ("error parsing hex: %v" , err )
234+ 		}
235+ 		peerPubKey , err  :=  btcec .ParsePubKey (
236+ 			peerPubKeyBytes , btcec .S256 (),
237+ 		)
238+ 		if  err  !=  nil  {
239+ 			return  fmt .Errorf ("error parsing pubkey: %v" , err )
240+ 		}
241+ 
242+ 		peer , err  :=  lnd .FindNode (graph , peerPubKeyStr )
243+ 		if  err  !=  nil  {
244+ 			return  err 
245+ 		}
246+ 		peerAddresses  :=  make ([]net.Addr , len (peer .Addresses ))
247+ 		for  idx , peerAddr  :=  range  peer .Addresses  {
248+ 			var  err  error 
249+ 			if  strings .Contains (peerAddr .Addr , ".onion" ) {
250+ 				peerAddresses [idx ], err  =  tor .ParseAddr (
251+ 					peerAddr .Addr , "" ,
252+ 				)
253+ 				if  err  !=  nil  {
254+ 					return  fmt .Errorf ("error parsing " + 
255+ 						"tor address: %v" , err )
256+ 				}
257+ 
258+ 				continue 
259+ 			}
260+ 			peerAddresses [idx ], err  =  net .ResolveTCPAddr (
261+ 				"tcp" , peerAddr .Addr ,
262+ 			)
263+ 			if  err  !=  nil  {
264+ 				return  fmt .Errorf ("could not parse addr: %s" ,
265+ 					err )
266+ 			}
267+ 		}
268+ 
269+ 		shortChanID  :=  lnwire .NewShortChanIDFromInt (channel .ChannelId )
270+ 		chanOp , err  :=  lnd .ParseOutpoint (channel .ChanPoint )
271+ 		if  err  !=  nil  {
272+ 			return  fmt .Errorf ("error parsing channel point: %v" ,
273+ 				err )
274+ 		}
275+ 
276+ 		singles [idx ] =  newSingle (
277+ 			* chanOp , shortChanID , peerPubKey , peerAddresses ,
278+ 			btcutil .Amount (channel .Capacity ),
279+ 		)
280+ 	}
281+ 
282+ 	return  writeBackups (singles , keyRing , multiFile )
283+ }
284+ 
285+ func  writeBackups (singles  []chanbackup.Single , keyRing  keychain.KeyRing ,
286+ 	multiFile  * chanbackup.MultiFile ) error  {
287+ 
288+ 	newMulti  :=  chanbackup.Multi {
289+ 		Version :       chanbackup .DefaultMultiVersion ,
290+ 		StaticBackups : singles ,
291+ 	}
292+ 	var  packed  bytes.Buffer 
293+ 	err  :=  newMulti .PackToWriter (& packed , keyRing )
294+ 	if  err  !=  nil  {
295+ 		return  fmt .Errorf ("unable to multi-pack backups: %v" , err )
296+ 	}
297+ 
298+ 	return  multiFile .UpdateAndSwap (packed .Bytes ())
299+ }
300+ 
301+ func  newSingle (fundingOutPoint  wire.OutPoint , shortChanID  lnwire.ShortChannelID ,
302+ 	nodePubKey  * btcec.PublicKey , addrs  []net.Addr ,
303+ 	capacity  btcutil.Amount ) chanbackup.Single  {
304+ 
305+ 	return  chanbackup.Single {
306+ 		Version :         chanbackup .DefaultSingleVersion ,
307+ 		IsInitiator :     true ,
308+ 		ChainHash :       * chainParams .GenesisHash ,
309+ 		FundingOutpoint : fundingOutPoint ,
310+ 		ShortChannelID :  shortChanID ,
311+ 		RemoteNodePub :   nodePubKey ,
312+ 		Addresses :       addrs ,
313+ 		Capacity :        capacity ,
314+ 		LocalChanCfg :    fakeChanCfg (nodePubKey ),
315+ 		RemoteChanCfg :   fakeChanCfg (nodePubKey ),
316+ 		ShaChainRootDesc : keychain.KeyDescriptor {
317+ 			PubKey : nodePubKey ,
318+ 			KeyLocator : keychain.KeyLocator {
319+ 				Family : keychain .KeyFamilyRevocationRoot ,
320+ 				Index :  1 ,
321+ 			},
322+ 		},
323+ 	}
324+ }
325+ 
326+ func  fakeChanCfg (nodePubkey  * btcec.PublicKey ) channeldb.ChannelConfig  {
327+ 	return  channeldb.ChannelConfig {
165328		ChannelConstraints : channeldb.ChannelConstraints {
166329			DustLimit :        500 ,
167330			ChanReserve :      5000 ,
@@ -206,34 +369,4 @@ func (c *fakeChanBackupCommand) Execute(_ *cobra.Command, _ []string) error {
206369			},
207370		},
208371	}
209- 
210- 	newMulti  :=  chanbackup.Multi {
211- 		Version : chanbackup .DefaultMultiVersion ,
212- 		StaticBackups : []chanbackup.Single {{
213- 			Version :         chanbackup .DefaultSingleVersion ,
214- 			IsInitiator :     c .Initiator ,
215- 			ChainHash :       * chainParams .GenesisHash ,
216- 			FundingOutpoint : * chanOp ,
217- 			ShortChannelID :  shortChanID ,
218- 			RemoteNodePub :   nodePubkey ,
219- 			Addresses :       []net.Addr {addr },
220- 			Capacity :        btcutil .Amount (c .Capacity ),
221- 			LocalChanCfg :    chanCfg ,
222- 			RemoteChanCfg :   chanCfg ,
223- 			ShaChainRootDesc : keychain.KeyDescriptor {
224- 				PubKey : nodePubkey ,
225- 				KeyLocator : keychain.KeyLocator {
226- 					Family : keychain .KeyFamilyRevocationRoot ,
227- 					Index :  1 ,
228- 				},
229- 			},
230- 		}},
231- 	}
232- 	var  packed  bytes.Buffer 
233- 	err  =  newMulti .PackToWriter (& packed , keyRing )
234- 	if  err  !=  nil  {
235- 		return  fmt .Errorf ("unable to multi-pack backups: %v" , err )
236- 	}
237- 
238- 	return  multiFile .UpdateAndSwap (packed .Bytes ())
239372}
0 commit comments