mirror of https://github.com/guggero/chantools
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.
226 lines
6.4 KiB
Go
226 lines
6.4 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/btcsuite/btcutil/hdkeychain"
|
|
"github.com/guggero/chantools/lnd"
|
|
)
|
|
|
|
const (
|
|
defaultRecoveryWindow = 2500
|
|
defaultRescanFrom = 500000
|
|
defaultDerivationPath = "m/84'/0'/0'"
|
|
)
|
|
|
|
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."`
|
|
DerivationPath string `long:"derivationpath" description:"The first levels of the derivation path before any internal/external branch. (default m/84'/0'/0')"`
|
|
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
|
|
}
|
|
if c.DerivationPath == "" {
|
|
c.DerivationPath = defaultDerivationPath
|
|
}
|
|
|
|
derivationPath, err := lnd.ParsePath(c.DerivationPath)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing path: %v", err)
|
|
}
|
|
|
|
fmt.Printf("# Wallet dump created by chantools on %s\n",
|
|
time.Now().UTC())
|
|
|
|
// Determine the format.
|
|
var printFn func(*hdkeychain.ExtendedKey, string, 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 (<DerivationPath>/0/i).
|
|
for i := uint32(0); i < c.RecoveryWindow; i++ {
|
|
path := append(derivationPath, 0, i)
|
|
derivedKey, err := lnd.DeriveChildren(extendedKey, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = printFn(derivedKey, c.DerivationPath, 0, i)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Now the internal branch (<DerivationPath>/1/i).
|
|
for i := uint32(0); i < c.RecoveryWindow; i++ {
|
|
path := append(derivationPath, 1, i)
|
|
derivedKey, err := lnd.DeriveChildren(extendedKey, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = printFn(derivedKey, c.DerivationPath, 1, i)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
fmt.Printf("bitcoin-cli rescanblockchain %d\n", c.RescanFrom)
|
|
return nil
|
|
}
|
|
|
|
func printBitcoinCli(hdKey *hdkeychain.ExtendedKey, path string,
|
|
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 \"%s/%d/%d/"+
|
|
"\" false\n", wif.String(), path, branch,
|
|
index)
|
|
return nil
|
|
}
|
|
|
|
func printBitcoinCliWatchOnly(hdKey *hdkeychain.ExtendedKey, path string,
|
|
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 \"%s/%d/%d/"+
|
|
"\" false\n", pubKey.SerializeCompressed(),
|
|
path, branch, index)
|
|
return nil
|
|
}
|
|
|
|
func printBitcoinImportWallet(hdKey *hdkeychain.ExtendedKey, path string,
|
|
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)
|
|
}
|
|
hash160 := btcutil.Hash160(pubKey.SerializeCompressed())
|
|
addrP2PKH, err := btcutil.NewAddressPubKeyHash(hash160, chainParams)
|
|
if err != nil {
|
|
return fmt.Errorf("could not create address: %v", err)
|
|
}
|
|
addrP2WKH, err := btcutil.NewAddressWitnessPubKeyHash(
|
|
hash160, chainParams,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("could not create address: %v", err)
|
|
}
|
|
script, err := txscript.PayToAddrScript(addrP2WKH)
|
|
if err != nil {
|
|
return fmt.Errorf("could not create script: %v", err)
|
|
}
|
|
addrNP2WKH, err := btcutil.NewAddressScriptHash(script, chainParams)
|
|
if err != nil {
|
|
return fmt.Errorf("could not create address: %v", err)
|
|
}
|
|
|
|
fmt.Printf("%s 1970-01-01T00:00:01Z label=%s/%d/%d/ "+
|
|
"# addr=%s,%s,%s\n", wif.String(), path, branch, index,
|
|
addrP2PKH.EncodeAddress(), addrNP2WKH.EncodeAddress(),
|
|
addrP2WKH.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 "testnet3":
|
|
genesisTimestamp =
|
|
chaincfg.TestNet3Params.GenesisBlock.Header.Timestamp
|
|
|
|
case "regtest", "simnet":
|
|
return 0
|
|
|
|
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)
|
|
}
|