Add genimportscript command

pull/3/head
Oliver Gugger 4 years ago
parent aac8a2a728
commit 305a9dab94
No known key found for this signature in database
GPG Key ID: 8E4256593F177720

@ -10,6 +10,7 @@
+ [dumpchannels](#dumpchannels) + [dumpchannels](#dumpchannels)
+ [filterbackup](#filterbackup) + [filterbackup](#filterbackup)
+ [fixoldbackup](#fixoldbackup) + [fixoldbackup](#fixoldbackup)
+ [genimportscript](#genimportscript)
+ [forceclose](#forceclose) + [forceclose](#forceclose)
+ [rescueclosed](#rescueclosed) + [rescueclosed](#rescueclosed)
+ [showrootkey](#showrootkey) + [showrootkey](#showrootkey)
@ -54,16 +55,17 @@ Help Options:
-h, --help Show this help message -h, --help Show this help message
Available commands: Available commands:
derivekey Derive a key with a specific derivation path from the BIP32 HD root key. derivekey Derive a key with a specific derivation path from the BIP32 HD root key.
dumpbackup Dump the content of a channel.backup file. dumpbackup Dump the content of a channel.backup file.
dumpchannels Dump all channel information from lnd's channel database. dumpchannels Dump all channel information from lnd's channel database.
filterbackup Filter an lnd channel.backup file and remove certain channels. filterbackup Filter an lnd channel.backup file and remove certain channels.
fixoldbackup Fixes an old channel.backup file that is affected by the lnd issue #3881 (unable to derive shachain root key). fixoldbackup Fixes an old channel.backup file that is affected by the lnd issue #3881 (unable to derive shachain root key).
forceclose Force-close the last state that is in the channel.db provided. forceclose Force-close the last state that is in the channel.db provided.
rescueclosed Try finding the private keys for funds that are in outputs of remotely force-closed channels. genimportscript Generate a script containing the on-chain keys of an lnd wallet that can be imported into other software like bitcoind.
showrootkey Extract and show the BIP32 HD root key from the 24 word lnd aezeed. rescueclosed Try finding the private keys for funds that are in outputs of remotely force-closed channels.
summary Compile a summary about the current state of channels. showrootkey Extract and show the BIP32 HD root key from the 24 word lnd aezeed.
sweeptimelock Sweep the force-closed state after the time lock has expired. summary Compile a summary about the current state of channels.
sweeptimelock Sweep the force-closed state after the time lock has expired.
``` ```
## Commands ## Commands
@ -213,6 +215,38 @@ chantools --fromsummary results/summary-xxxx-yyyy.json \
--publish --publish
``` ```
### genimportscript
```text
Usage:
chantools [OPTIONS] genimportscript [genimportscript-OPTIONS]
[genimportscript command options]
--rootkey= BIP32 HD root key to use. Leave empty to prompt for lnd 24 word aezeed.
--format= The format of the generated import script. Currently supported are: bitcoin-cli, bitcoin-cli-watchonly.
--recoverywindow= The number of keys to scan per internal/external branch. The output will consist of double this amount of keys. (default 2500)
--rescanfrom= The block number to rescan from. Will be set automatically from the wallet birthday if the lnd 24 word aezeed is entered. (default 500000)
```
Generates a script that contains all on-chain private (or public) keys derived
from an lnd 24 word aezeed wallet. That script can then be imported into other
software like bitcoind.
The following script formats are currently supported:
* `bitcoin-cli`: Creates a list of `bitcoin-cli importprivkey` commands that can
be used in combination with a `bitcoind` full node to recover the funds locked
in those private keys.
* `bitcoin-cli-watchonly`: Does the same as `bitcoin-cli` but with the
`bitcoin-cli importpubkey` command. That means, only the public keys are
imported into `bitcoind` to watch the UTXOs of those keys. The funds cannot be
spent that way as they are watch-only.
Example command:
```bash
chantools genimportscript --format bitcoin-cli --recoverywindow 5000
```
### rescueclosed ### rescueclosed
```text ```text

@ -28,7 +28,7 @@ func (c *deriveKeyCommand) Execute(_ []string) error {
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey) extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
default: default:
extendedKey, err = rootKeyFromConsole() extendedKey, _, err = rootKeyFromConsole()
} }
if err != nil { if err != nil {
return fmt.Errorf("error reading root key: %v", err) return fmt.Errorf("error reading root key: %v", err)

@ -30,7 +30,7 @@ func (c *dumpBackupCommand) Execute(_ []string) error {
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey) extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
default: default:
extendedKey, err = rootKeyFromConsole() extendedKey, _, err = rootKeyFromConsole()
} }
if err != nil { if err != nil {
return fmt.Errorf("error reading root key: %v", err) return fmt.Errorf("error reading root key: %v", err)

@ -32,7 +32,7 @@ func (c *filterBackupCommand) Execute(_ []string) error {
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey) extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
default: default:
extendedKey, err = rootKeyFromConsole() extendedKey, _, err = rootKeyFromConsole()
} }
if err != nil { if err != nil {
return fmt.Errorf("error reading root key: %v", err) return fmt.Errorf("error reading root key: %v", err)

@ -30,7 +30,7 @@ func (c *fixOldBackupCommand) Execute(_ []string) error {
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey) extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
default: default:
extendedKey, err = rootKeyFromConsole() extendedKey, _, err = rootKeyFromConsole()
} }
if err != nil { if err != nil {
return fmt.Errorf("error reading root key: %v", err) return fmt.Errorf("error reading root key: %v", err)

@ -36,7 +36,7 @@ func (c *forceCloseCommand) Execute(_ []string) error {
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey) extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
default: default:
extendedKey, err = rootKeyFromConsole() extendedKey, _, err = rootKeyFromConsole()
} }
if err != nil { if err != nil {
return fmt.Errorf("error reading root key: %v", err) return fmt.Errorf("error reading root key: %v", err)

@ -0,0 +1,159 @@
package main
import (
"fmt"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/guggero/chantools/btc"
)
const (
defaultRecoveryWindow = 2500
defaultRescanFrom = 500000
)
type genImportScriptCommand struct {
RootKey string `long:"rootkey" description:"BIP32 HD root key to use. Leave empty to prompt for lnd 24 word aezeed."`
Format string `long:"format" description:"The format of the generated import script. Currently supported are: bitcoin-cli, bitcoin-cli-watchonly."`
RecoveryWindow uint32 `long:"recoverywindow" description:"The number of keys to scan per internal/external branch. The output will consist of double this amount of keys. (default 2500)"`
RescanFrom uint32 `long:"rescanfrom" description:"The block number to rescan from. Will be set automatically from the wallet birthday if the lnd 24 word aezeed is entered. (default 500000)"`
}
func (c *genImportScriptCommand) Execute(_ []string) error {
var (
extendedKey *hdkeychain.ExtendedKey
err error
birthday time.Time
)
// Check that root key is valid or fall back to console input.
switch {
case c.RootKey != "":
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
if err != nil {
return fmt.Errorf("error reading root key: %v", err)
}
default:
extendedKey, birthday, err = rootKeyFromConsole()
if err != nil {
return fmt.Errorf("error reading root key: %v", err)
}
// The btcwallet gives the birthday a slack of 48 hours, let's
// do the same.
c.RescanFrom = seedBirthdayToBlock(birthday.Add(-48 * time.Hour))
}
// Set default values.
if c.RecoveryWindow == 0 {
c.RecoveryWindow = defaultRecoveryWindow
}
if c.RescanFrom == 0 {
c.RescanFrom = defaultRescanFrom
}
// Determine the format.
printFn := printBitcoinCli
if c.Format == "bitcoin-cli-watchonly" {
printFn = printBitcoinCliWatchOnly
}
fmt.Println("# Paste the following lines into a command line window.")
// External branch first (m/84'/<coinType>'/0'/0/x).
for i := uint32(0); i < c.RecoveryWindow; i++ {
derivedKey, err := btc.DeriveChildren(extendedKey, []uint32{
btc.HardenedKeyStart + uint32(84),
btc.HardenedKeyStart + chainParams.HDCoinType,
btc.HardenedKeyStart + uint32(0),
0,
i,
})
if err != nil {
return err
}
err = printFn(derivedKey, 0, i)
if err != nil {
return err
}
}
// Now the internal branch (m/84'/<coinType>'/0'/1/x).
for i := uint32(0); i < c.RecoveryWindow; i++ {
derivedKey, err := btc.DeriveChildren(extendedKey, []uint32{
btc.HardenedKeyStart + uint32(84),
btc.HardenedKeyStart + chainParams.HDCoinType,
btc.HardenedKeyStart + uint32(0),
1,
i,
})
if err != nil {
return err
}
err = printFn(derivedKey, 1, i)
if err != nil {
return err
}
}
fmt.Printf("bitcoin-cli rescanblockchain %d\n", c.RescanFrom)
return nil
}
func printBitcoinCli(derivedKey *hdkeychain.ExtendedKey, branch,
index uint32) error {
privKey, err := derivedKey.ECPrivKey()
if err != nil {
return fmt.Errorf("could not derive private key: %v",
err)
}
wif, err := btcutil.NewWIF(privKey, chainParams, true)
if err != nil {
return fmt.Errorf("could not encode WIF: %v", err)
}
fmt.Printf("bitcoin-cli importprivkey %s \"m/84'/%d'/0'/%d/%d/"+
"\" false\n", wif.String(), chainParams.HDCoinType, branch,
index)
return nil
}
func printBitcoinCliWatchOnly(derivedKey *hdkeychain.ExtendedKey, branch,
index uint32) error {
pubKey, err := derivedKey.ECPubKey()
if err != nil {
return fmt.Errorf("could not derive private key: %v",
err)
}
fmt.Printf("bitcoin-cli importpubkey %x \"m/84'/%d'/0'/%d/%d/"+
"\" false\n", pubKey.SerializeCompressed(),
chainParams.HDCoinType, branch, index)
return nil
}
func seedBirthdayToBlock(birthdayTimestamp time.Time) uint32 {
var genesisTimestamp time.Time
switch chainParams.Name {
case "mainnet":
genesisTimestamp =
chaincfg.MainNetParams.GenesisBlock.Header.Timestamp
case "testnet":
genesisTimestamp =
chaincfg.TestNet3Params.GenesisBlock.Header.Timestamp
default:
panic(fmt.Errorf("unimplemented network %v", chainParams.Name))
}
// With the timestamps retrieved, we can estimate a block height by
// taking the difference between them and dividing by the average block
// time (10 minutes).
return uint32(birthdayTimestamp.Sub(genesisTimestamp).Seconds() / 600)
}

@ -9,6 +9,7 @@ import (
"os" "os"
"strings" "strings"
"syscall" "syscall"
"time"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil/hdkeychain" "github.com/btcsuite/btcutil/hdkeychain"
@ -95,13 +96,18 @@ func runCommandParser() error {
"from the BIP32 HD root key.", "", &deriveKeyCommand{}, "from the BIP32 HD root key.", "", &deriveKeyCommand{},
) )
_, _ = parser.AddCommand( _, _ = parser.AddCommand(
"filterbackup", "Filter an lnd channel.backup file and " + "filterbackup", "Filter an lnd channel.backup file and "+
"remove certain channels.", "", &filterBackupCommand{}, "remove certain channels.", "", &filterBackupCommand{},
) )
_, _ = parser.AddCommand( _, _ = parser.AddCommand(
"fixoldbackup", "Fixes an old channel.backup file that is " + "fixoldbackup", "Fixes an old channel.backup file that is "+
"affected by the lnd issue #3881 (unable to derive " + "affected by the lnd issue #3881 (unable to derive "+
"shachain root key).", "", &fixOldBackupCommand{}) "shachain root key).", "", &fixOldBackupCommand{})
_, _ = parser.AddCommand(
"genimportscript", "Generate a script containing the on-chain "+
"keys of an lnd wallet that can be imported into "+
"other software like bitcoind.", "",
&genImportScriptCommand{})
_, err := parser.Parse() _, err := parser.Parse()
return err return err
@ -158,13 +164,13 @@ func readInput(input string) ([]byte, error) {
return ioutil.ReadFile(input) return ioutil.ReadFile(input)
} }
func rootKeyFromConsole() (*hdkeychain.ExtendedKey, error) { func rootKeyFromConsole() (*hdkeychain.ExtendedKey, time.Time, error) {
// We'll now prompt the user to enter in their 24-word mnemonic. // We'll now prompt the user to enter in their 24-word mnemonic.
fmt.Printf("Input your 24-word mnemonic separated by spaces: ") fmt.Printf("Input your 24-word mnemonic separated by spaces: ")
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
mnemonicStr, err := reader.ReadString('\n') mnemonicStr, err := reader.ReadString('\n')
if err != nil { if err != nil {
return nil, err return nil, time.Unix(0, 0), err
} }
// We'll trim off extra spaces, and ensure the mnemonic is all // We'll trim off extra spaces, and ensure the mnemonic is all
@ -177,8 +183,8 @@ func rootKeyFromConsole() (*hdkeychain.ExtendedKey, error) {
fmt.Println() fmt.Println()
if len(cipherSeedMnemonic) != 24 { if len(cipherSeedMnemonic) != 24 {
return nil, fmt.Errorf("wrong cipher seed mnemonic "+ return nil, time.Unix(0, 0), fmt.Errorf("wrong cipher seed "+
"length: got %v words, expecting %v words", "mnemonic length: got %v words, expecting %v words",
len(cipherSeedMnemonic), 24) len(cipherSeedMnemonic), 24)
} }
@ -189,7 +195,7 @@ func rootKeyFromConsole() (*hdkeychain.ExtendedKey, error) {
"your seed doesn't have a passphrase): ") "your seed doesn't have a passphrase): ")
passphrase, err := terminal.ReadPassword(syscall.Stdin) passphrase, err := terminal.ReadPassword(syscall.Stdin)
if err != nil { if err != nil {
return nil, err return nil, time.Unix(0, 0), err
} }
var mnemonic aezeed.Mnemonic var mnemonic aezeed.Mnemonic
@ -199,14 +205,15 @@ func rootKeyFromConsole() (*hdkeychain.ExtendedKey, error) {
// mnemonic is wrong, or the passphrase is wrong. // mnemonic is wrong, or the passphrase is wrong.
cipherSeed, err := mnemonic.ToCipherSeed(passphrase) cipherSeed, err := mnemonic.ToCipherSeed(passphrase)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to decrypt seed with passphrase"+ return nil, time.Unix(0, 0), fmt.Errorf("failed to decrypt "+
": %v", err) "seed with passphrase: %v", err)
} }
rootKey, err := hdkeychain.NewMaster(cipherSeed.Entropy[:], chainParams) rootKey, err := hdkeychain.NewMaster(cipherSeed.Entropy[:], chainParams)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to derive master extended key") return nil, time.Unix(0, 0), fmt.Errorf("failed to derive " +
"master extended key")
} }
return rootKey, nil return rootKey, cipherSeed.BirthdayTime(), nil
} }
func setupChainParams(cfg *config) { func setupChainParams(cfg *config) {

@ -48,7 +48,7 @@ func (c *rescueClosedCommand) Execute(_ []string) error {
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey) extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
default: default:
extendedKey, err = rootKeyFromConsole() extendedKey, _, err = rootKeyFromConsole()
} }
if err != nil { if err != nil {
return fmt.Errorf("error reading root key: %v", err) return fmt.Errorf("error reading root key: %v", err)

@ -7,7 +7,7 @@ import (
type showRootKeyCommand struct{} type showRootKeyCommand struct{}
func (c *showRootKeyCommand) Execute(_ []string) error { func (c *showRootKeyCommand) Execute(_ []string) error {
rootKey, err := rootKeyFromConsole() rootKey, _, err := rootKeyFromConsole()
if err != nil { if err != nil {
return fmt.Errorf("failed to read root key from console: %v", return fmt.Errorf("failed to read root key from console: %v",
err) err)

@ -38,7 +38,7 @@ func (c *sweepTimeLockCommand) Execute(_ []string) error {
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey) extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
default: default:
extendedKey, err = rootKeyFromConsole() extendedKey, _, err = rootKeyFromConsole()
} }
if err != nil { if err != nil {
return fmt.Errorf("error reading root key: %v", err) return fmt.Errorf("error reading root key: %v", err)

@ -3,10 +3,10 @@ package dump
import ( import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/btcsuite/btcd/chaincfg"
"net" "net"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/chanbackup"

Loading…
Cancel
Save