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)
+ [filterbackup](#filterbackup)
+ [fixoldbackup](#fixoldbackup)
+ [genimportscript](#genimportscript)
+ [forceclose](#forceclose)
+ [rescueclosed](#rescueclosed)
+ [showrootkey](#showrootkey)
@ -54,16 +55,17 @@ Help Options:
-h, --help Show this help message
Available commands:
derivekey Derive a key with a specific derivation path from the BIP32 HD root key.
dumpbackup Dump the content of a channel.backup file.
dumpchannels Dump all channel information from lnd's channel database.
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).
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.
showrootkey Extract and show the BIP32 HD root key from the 24 word lnd aezeed.
summary Compile a summary about the current state of channels.
sweeptimelock Sweep the force-closed state after the time lock has expired.
derivekey Derive a key with a specific derivation path from the BIP32 HD root key.
dumpbackup Dump the content of a channel.backup file.
dumpchannels Dump all channel information from lnd's channel database.
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).
forceclose Force-close the last state that is in the channel.db provided.
genimportscript Generate a script containing the on-chain keys of an lnd wallet that can be imported into other software like bitcoind.
rescueclosed Try finding the private keys for funds that are in outputs of remotely force-closed channels.
showrootkey Extract and show the BIP32 HD root key from the 24 word lnd aezeed.
summary Compile a summary about the current state of channels.
sweeptimelock Sweep the force-closed state after the time lock has expired.
```
## Commands
@ -213,6 +215,38 @@ chantools --fromsummary results/summary-xxxx-yyyy.json \
--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
```text

@ -28,7 +28,7 @@ func (c *deriveKeyCommand) Execute(_ []string) error {
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
default:
extendedKey, err = rootKeyFromConsole()
extendedKey, _, err = rootKeyFromConsole()
}
if err != nil {
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)
default:
extendedKey, err = rootKeyFromConsole()
extendedKey, _, err = rootKeyFromConsole()
}
if err != nil {
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)
default:
extendedKey, err = rootKeyFromConsole()
extendedKey, _, err = rootKeyFromConsole()
}
if err != nil {
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)
default:
extendedKey, err = rootKeyFromConsole()
extendedKey, _, err = rootKeyFromConsole()
}
if err != nil {
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)
default:
extendedKey, err = rootKeyFromConsole()
extendedKey, _, err = rootKeyFromConsole()
}
if err != nil {
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"
"strings"
"syscall"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil/hdkeychain"
@ -95,13 +96,18 @@ func runCommandParser() error {
"from the BIP32 HD root key.", "", &deriveKeyCommand{},
)
_, _ = parser.AddCommand(
"filterbackup", "Filter an lnd channel.backup file and " +
"filterbackup", "Filter an lnd channel.backup file and "+
"remove certain channels.", "", &filterBackupCommand{},
)
_, _ = parser.AddCommand(
"fixoldbackup", "Fixes an old channel.backup file that is " +
"affected by the lnd issue #3881 (unable to derive " +
"fixoldbackup", "Fixes an old channel.backup file that is "+
"affected by the lnd issue #3881 (unable to derive "+
"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()
return err
@ -158,13 +164,13 @@ func readInput(input string) ([]byte, error) {
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.
fmt.Printf("Input your 24-word mnemonic separated by spaces: ")
reader := bufio.NewReader(os.Stdin)
mnemonicStr, err := reader.ReadString('\n')
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
@ -177,8 +183,8 @@ func rootKeyFromConsole() (*hdkeychain.ExtendedKey, error) {
fmt.Println()
if len(cipherSeedMnemonic) != 24 {
return nil, fmt.Errorf("wrong cipher seed mnemonic "+
"length: got %v words, expecting %v words",
return nil, time.Unix(0, 0), fmt.Errorf("wrong cipher seed "+
"mnemonic length: got %v words, expecting %v words",
len(cipherSeedMnemonic), 24)
}
@ -189,7 +195,7 @@ func rootKeyFromConsole() (*hdkeychain.ExtendedKey, error) {
"your seed doesn't have a passphrase): ")
passphrase, err := terminal.ReadPassword(syscall.Stdin)
if err != nil {
return nil, err
return nil, time.Unix(0, 0), err
}
var mnemonic aezeed.Mnemonic
@ -199,14 +205,15 @@ func rootKeyFromConsole() (*hdkeychain.ExtendedKey, error) {
// mnemonic is wrong, or the passphrase is wrong.
cipherSeed, err := mnemonic.ToCipherSeed(passphrase)
if err != nil {
return nil, fmt.Errorf("failed to decrypt seed with passphrase"+
": %v", err)
return nil, time.Unix(0, 0), fmt.Errorf("failed to decrypt "+
"seed with passphrase: %v", err)
}
rootKey, err := hdkeychain.NewMaster(cipherSeed.Entropy[:], chainParams)
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) {

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

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

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

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

Loading…
Cancel
Save