You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
chantools/cmd/chantools/genimportscript.go

209 lines
5.6 KiB
Go

package main
import (
"fmt"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/guggero/chantools/lnd"
)
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, bitcoin-importwallet."`
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 {
setupChainParams(cfg)
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
}
fmt.Printf("# Wallet dump created by chantools on %s\n",
time.Now().UTC())
// Determine the format.
var printFn func(*hdkeychain.ExtendedKey, uint32, uint32) error
switch c.Format {
default:
fallthrough
case "bitcoin-cli":
printFn = printBitcoinCli
fmt.Println("# Paste the following lines into a command line " +
"window.")
case "bitcoin-cli-watchonly":
printFn = printBitcoinCliWatchOnly
fmt.Println("# Paste the following lines into a command line " +
"window.")
case "bitcoin-importwallet":
printFn = printBitcoinImportWallet
fmt.Println("# Save this output to a file and use the " +
"importwallet command of bitcoin core.")
}
// External branch first (m/84'/<coinType>'/0'/0/x).
for i := uint32(0); i < c.RecoveryWindow; i++ {
derivedKey, err := lnd.DeriveChildren(extendedKey, []uint32{
lnd.HardenedKeyStart + uint32(84),
lnd.HardenedKeyStart + chainParams.HDCoinType,
lnd.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 := lnd.DeriveChildren(extendedKey, []uint32{
lnd.HardenedKeyStart + uint32(84),
lnd.HardenedKeyStart + chainParams.HDCoinType,
lnd.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(hdKey *hdkeychain.ExtendedKey, branch,
index uint32) error {
privKey, err := hdKey.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(hdKey *hdkeychain.ExtendedKey, branch,
index uint32) error {
pubKey, err := hdKey.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 printBitcoinImportWallet(hdKey *hdkeychain.ExtendedKey, branch,
index uint32) error {
privKey, err := hdKey.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)
}
pubKey, err := hdKey.ECPubKey()
if err != nil {
return fmt.Errorf("could not derive private key: %v",
err)
}
addrPubkey, err := btcutil.NewAddressPubKey(
pubKey.SerializeCompressed(), chainParams,
)
if err != nil {
return fmt.Errorf("could not create address: %v", err)
}
addr := addrPubkey.AddressPubKeyHash()
fmt.Printf("%s 1970-01-01T00:00:01Z label=m/84'/%d'/0'/%d/%d/ "+
"# addr=%s", wif.String(), chainParams.HDCoinType, branch,
index, addr.EncodeAddress(),
)
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)
}