Skip to content

Commit 863a5e7

Browse files
committed
rescueclosed: add manual brute force method
1 parent e36bf5e commit 863a5e7

File tree

1 file changed

+106
-16
lines changed

1 file changed

+106
-16
lines changed

cmd/chantools/rescueclosed.go

Lines changed: 106 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"crypto/subtle"
5+
"encoding/hex"
56
"encoding/json"
67
"errors"
78
"fmt"
@@ -32,7 +33,9 @@ type cacheEntry struct {
3233
}
3334

3435
type 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
5154
that contains the latest commit point. Normally you would use SCB to get the
5255
funds from those channels. But this method can help if the other node doesn't
5356
know 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

94143
func 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+
171261
func addrInCache(addr string, perCommitPoint *btcec.PublicKey) (string, error) {
172262
targetPubKeyHash, scriptHash, err := lnd.DecodeAddressHash(
173263
addr, chainParams,

0 commit comments

Comments
 (0)