Skip to content

Commit c389850

Browse files
committed
fakechanbackup: add fakechanbackup command
1 parent 863a5e7 commit c389850

File tree

2 files changed

+241
-1
lines changed

2 files changed

+241
-1
lines changed

cmd/chantools/fakechanbackup.go

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"encoding/hex"
6+
"fmt"
7+
"net"
8+
"strconv"
9+
"strings"
10+
"time"
11+
12+
"github.com/btcsuite/btcd/btcec"
13+
"github.com/btcsuite/btcutil"
14+
"github.com/guggero/chantools/lnd"
15+
"github.com/lightningnetwork/lnd/chanbackup"
16+
"github.com/lightningnetwork/lnd/channeldb"
17+
"github.com/lightningnetwork/lnd/keychain"
18+
"github.com/lightningnetwork/lnd/lnwire"
19+
"github.com/spf13/cobra"
20+
)
21+
22+
type fakeChanBackupCommand struct {
23+
NodeAddr string
24+
ChannelPoint string
25+
ShortChanID string
26+
Initiator bool
27+
Capacity uint64
28+
MultiFile string
29+
30+
rootKey *rootKey
31+
cmd *cobra.Command
32+
}
33+
34+
func newFakeChanBackupCommand() *cobra.Command {
35+
cc := &fakeChanBackupCommand{}
36+
cc.cmd = &cobra.Command{
37+
Use: "fakechanbackup",
38+
Short: "Fake a channel backup file to attempt fund recovery",
39+
Long: `If for any reason a node suffers from data loss and there is no
40+
channel.backup for one or more channels, then the funds in the channel would
41+
theoretically be lost forever.
42+
If the remote node is still online and still knows about the channel, there is
43+
hope. We can initiate DLP (Data Loss Protocol) and ask the remote node to
44+
force-close the channel and to provide us with the per_commit_point that is
45+
needed to derive the private key for our part of the force-close transaction
46+
output. But to initiate DLP, we would need to have a channel.backup file.
47+
Fortunately, if we have enough information about the channel, we can create a
48+
faked/skeleton channel.backup file that at least lets us talk to the other node
49+
and 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).`,
51+
Example: `chantools fakechanbackup --rootkey xprvxxxxxxxxxx \
52+
--capacity 123456 \
53+
--channelpoint f39310xxxxxxxxxx:1 \
54+
--initiator \
55+
--remote_node_addr [email protected]:9735 \
56+
--short_channel_id 566222x300x1 \
57+
--multi_file fake.backup`,
58+
RunE: cc.Execute,
59+
}
60+
cc.cmd.Flags().StringVar(
61+
&cc.NodeAddr, "remote_node_addr", "", "the remote node "+
62+
"connection information in the format pubkey@host:"+
63+
"port",
64+
)
65+
cc.cmd.Flags().StringVar(
66+
&cc.ChannelPoint, "channelpoint", "", "funding transaction "+
67+
"outpoint of the channel to rescue (<txid>:<txindex>) "+
68+
"as it is displayed on 1ml.com",
69+
)
70+
cc.cmd.Flags().StringVar(
71+
&cc.ShortChanID, "short_channel_id", "", "the short channel "+
72+
"ID in the format <blockheight>x<transactionindex>x"+
73+
"<outputindex>",
74+
)
75+
cc.cmd.Flags().Uint64Var(
76+
&cc.Capacity, "capacity", 0, "the channel's capacity in "+
77+
"satoshis",
78+
)
79+
cc.cmd.Flags().BoolVar(
80+
&cc.Initiator, "initiator", false, "whether our node was the "+
81+
"initiator (funder) of the channel",
82+
)
83+
multiFileName := fmt.Sprintf("results/fake-%s.backup",
84+
time.Now().Format("2006-01-02-15-04-05"))
85+
cc.cmd.Flags().StringVar(
86+
&cc.MultiFile, "multi_file", multiFileName, "the fake channel "+
87+
"backup file to create",
88+
)
89+
90+
cc.rootKey = newRootKey(cc.cmd, "encrypting the backup")
91+
92+
return cc.cmd
93+
}
94+
95+
func (c *fakeChanBackupCommand) Execute(_ *cobra.Command, _ []string) error {
96+
extendedKey, err := c.rootKey.read()
97+
if err != nil {
98+
return fmt.Errorf("error reading root key: %v", err)
99+
}
100+
101+
multiFile := chanbackup.NewMultiFile(c.MultiFile)
102+
keyRing := &lnd.HDKeyRing{
103+
ExtendedKey: extendedKey,
104+
ChainParams: chainParams,
105+
}
106+
107+
// Parse channel point of channel to fake.
108+
chanOp, err := lnd.ParseOutpoint(c.ChannelPoint)
109+
if err != nil {
110+
return fmt.Errorf("error parsing channel point: %v", err)
111+
}
112+
113+
// Now parse the remote node info.
114+
splitNodeInfo := strings.Split(c.NodeAddr, "@")
115+
if len(splitNodeInfo) != 2 {
116+
return fmt.Errorf("--remote_node_addr expected in format: " +
117+
"pubkey@host:port")
118+
}
119+
pubKeyBytes, err := hex.DecodeString(splitNodeInfo[0])
120+
if err != nil {
121+
return fmt.Errorf("could not parse pubkey hex string: %s", err)
122+
}
123+
nodePubkey, err := btcec.ParsePubKey(pubKeyBytes, btcec.S256())
124+
if err != nil {
125+
return fmt.Errorf("could not parse pubkey: %s", err)
126+
}
127+
addr, err := net.ResolveTCPAddr("tcp", splitNodeInfo[1])
128+
if err != nil {
129+
return fmt.Errorf("could not parse addr: %s", err)
130+
}
131+
132+
// Parse the short channel ID.
133+
splitChanId := strings.Split(c.ShortChanID, "x")
134+
if len(splitChanId) != 3 {
135+
return fmt.Errorf("--short_channel_id expected in format: " +
136+
"<blockheight>x<transactionindex>x<outputindex>",
137+
)
138+
}
139+
blockHeight, err := strconv.ParseInt(splitChanId[0], 10, 32)
140+
if err != nil {
141+
return fmt.Errorf("could not parse block height: %s", err)
142+
}
143+
txIndex, err := strconv.ParseInt(splitChanId[1], 10, 32)
144+
if err != nil {
145+
return fmt.Errorf("could not parse transaction index: %s", err)
146+
}
147+
chanOutputIdx, err := strconv.ParseInt(splitChanId[2], 10, 32)
148+
if err != nil {
149+
return fmt.Errorf("could not parse output index: %s", err)
150+
}
151+
shortChanID := lnwire.ShortChannelID{
152+
BlockHeight: uint32(blockHeight),
153+
TxIndex: uint32(txIndex),
154+
TxPosition: uint16(chanOutputIdx),
155+
}
156+
157+
// Is the outpoint and/or short channel ID correct?
158+
if uint32(chanOutputIdx) != chanOp.Index {
159+
return fmt.Errorf("output index of --short_channel_id must " +
160+
"be equal to index on --channelpoint")
161+
}
162+
163+
// Create some fake channel config.
164+
chanCfg := channeldb.ChannelConfig{
165+
ChannelConstraints: channeldb.ChannelConstraints{
166+
DustLimit: 500,
167+
ChanReserve: 5000,
168+
MaxPendingAmount: 1,
169+
MinHTLC: 1,
170+
MaxAcceptedHtlcs: 200,
171+
CsvDelay: 144,
172+
},
173+
MultiSigKey: keychain.KeyDescriptor{
174+
PubKey: nodePubkey,
175+
KeyLocator: keychain.KeyLocator{
176+
Family: keychain.KeyFamilyMultiSig,
177+
Index: 0,
178+
},
179+
},
180+
RevocationBasePoint: keychain.KeyDescriptor{
181+
PubKey: nodePubkey,
182+
KeyLocator: keychain.KeyLocator{
183+
Family: keychain.KeyFamilyRevocationBase,
184+
Index: 0,
185+
},
186+
},
187+
PaymentBasePoint: keychain.KeyDescriptor{
188+
PubKey: nodePubkey,
189+
KeyLocator: keychain.KeyLocator{
190+
Family: keychain.KeyFamilyPaymentBase,
191+
Index: 0,
192+
},
193+
},
194+
DelayBasePoint: keychain.KeyDescriptor{
195+
PubKey: nodePubkey,
196+
KeyLocator: keychain.KeyLocator{
197+
Family: keychain.KeyFamilyDelayBase,
198+
Index: 0,
199+
},
200+
},
201+
HtlcBasePoint: keychain.KeyDescriptor{
202+
PubKey: nodePubkey,
203+
KeyLocator: keychain.KeyLocator{
204+
Family: keychain.KeyFamilyHtlcBase,
205+
Index: 0,
206+
},
207+
},
208+
}
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())
239+
}

cmd/chantools/root.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626

2727
const (
2828
defaultAPIURL = "https://blockstream.info/api"
29-
version = "0.8.1"
29+
version = "0.8.2"
3030
na = "n/a"
3131

3232
Commit = ""
@@ -86,6 +86,7 @@ func main() {
8686
newDumpBackupCommand(),
8787
newDumpChannelsCommand(),
8888
newDocCommand(),
89+
newFakeChanBackupCommand(),
8990
newFilterBackupCommand(),
9091
newFixOldBackupCommand(),
9192
newForceCloseCommand(),

0 commit comments

Comments
 (0)