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.
160 lines
4.4 KiB
Go
160 lines
4.4 KiB
Go
4 years ago
|
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)
|
||
|
}
|