mirror of
https://github.com/guggero/chantools.git
synced 2026-08-16 13:01:09 +02:00
Fall back to aezeed prompt if root key is missing
This commit is contained in:
parent
de4f5256ef
commit
26e1986cef
9 changed files with 141 additions and 93 deletions
10
README.md
10
README.md
|
|
@ -70,7 +70,7 @@ Usage:
|
|||
chantools [OPTIONS] derivekey [derivekey-OPTIONS]
|
||||
|
||||
[derivekey command options]
|
||||
--rootkey= BIP32 HD root key to derive the key from.
|
||||
--rootkey= BIP32 HD root key to derive the key from. Leave empty to prompt for lnd 24 word aezeed.
|
||||
--path= The BIP32 derivation path to derive. Must start with "m/".
|
||||
--neuter Do not output the private key, just the public key.
|
||||
```
|
||||
|
|
@ -93,7 +93,7 @@ Usage:
|
|||
chantools [OPTIONS] dumpbackup [dumpbackup-OPTIONS]
|
||||
|
||||
[dumpbackup command options]
|
||||
--rootkey= BIP32 HD root key of the wallet that was used to create the backup.
|
||||
--rootkey= BIP32 HD root key of the wallet that was used to create the backup. Leave empty to prompt for lnd 24 word aezeed.
|
||||
--multi_file= The lnd channel.backup file to dump.
|
||||
```
|
||||
|
||||
|
|
@ -133,7 +133,7 @@ Usage:
|
|||
chantools [OPTIONS] forceclose [forceclose-OPTIONS]
|
||||
|
||||
[forceclose command options]
|
||||
--rootkey= BIP32 HD root key to use.
|
||||
--rootkey= BIP32 HD root key to use. Leave empty to prompt for lnd 24 word aezeed.
|
||||
--channeldb= The lnd channel.db file to use for force-closing channels.
|
||||
--publish Should the force-closing TX be published to the chain API?
|
||||
```
|
||||
|
|
@ -168,7 +168,7 @@ Usage:
|
|||
chantools [OPTIONS] rescueclosed [rescueclosed-OPTIONS]
|
||||
|
||||
[rescueclosed command options]
|
||||
--rootkey= BIP32 HD root key to use.
|
||||
--rootkey= BIP32 HD root key to use. Leave empty to prompt for lnd 24 word aezeed.
|
||||
--channeldb= The lnd channel.db file to use for rescuing force-closed channels.
|
||||
```
|
||||
|
||||
|
|
@ -230,7 +230,7 @@ Usage:
|
|||
chantools [OPTIONS] sweeptimelock [sweeptimelock-OPTIONS]
|
||||
|
||||
[sweeptimelock command options]
|
||||
--rootkey= BIP32 HD root key to use.
|
||||
--rootkey= BIP32 HD root key to use. Leave empty to prompt for lnd 24 word aezeed.
|
||||
--publish Should the sweep TX be published to the chain API?
|
||||
--sweepaddr= The address the funds should be sweeped to
|
||||
--maxcsvlimit= Maximum CSV limit to use. (default 2000)
|
||||
|
|
|
|||
|
|
@ -17,13 +17,21 @@ type deriveKeyCommand struct {
|
|||
func (c *deriveKeyCommand) Execute(_ []string) error {
|
||||
setupChainParams(cfg)
|
||||
|
||||
// Check that root key is valid.
|
||||
if c.RootKey == "" {
|
||||
return fmt.Errorf("root key is required")
|
||||
var (
|
||||
extendedKey *hdkeychain.ExtendedKey
|
||||
err error
|
||||
)
|
||||
|
||||
// Check that root key is valid or fall back to console input.
|
||||
switch {
|
||||
case c.RootKey != "":
|
||||
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
|
||||
|
||||
default:
|
||||
extendedKey, err = rootKeyFromConsole()
|
||||
}
|
||||
extendedKey, err := hdkeychain.NewKeyFromString(c.RootKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing root key: %v", err)
|
||||
return fmt.Errorf("error reading root key: %v", err)
|
||||
}
|
||||
|
||||
return deriveKey(extendedKey, c.Path, c.Neuter)
|
||||
|
|
|
|||
|
|
@ -11,20 +11,28 @@ import (
|
|||
)
|
||||
|
||||
type dumpBackupCommand struct {
|
||||
RootKey string `long:"rootkey" description:"BIP32 HD root key of the wallet that was used to create the backup."`
|
||||
RootKey string `long:"rootkey" description:"BIP32 HD root key of the wallet that was used to create the backup. Leave empty to prompt for lnd 24 word aezeed."`
|
||||
MultiFile string `long:"multi_file" description:"The lnd channel.backup file to dump."`
|
||||
}
|
||||
|
||||
func (c *dumpBackupCommand) Execute(_ []string) error {
|
||||
setupChainParams(cfg)
|
||||
|
||||
// Check that root key is valid.
|
||||
if c.RootKey == "" {
|
||||
return fmt.Errorf("root key is required")
|
||||
var (
|
||||
extendedKey *hdkeychain.ExtendedKey
|
||||
err error
|
||||
)
|
||||
|
||||
// Check that root key is valid or fall back to console input.
|
||||
switch {
|
||||
case c.RootKey != "":
|
||||
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
|
||||
|
||||
default:
|
||||
extendedKey, err = rootKeyFromConsole()
|
||||
}
|
||||
extendedKey, err := hdkeychain.NewKeyFromString(c.RootKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing root key: %v", err)
|
||||
return fmt.Errorf("error reading root key: %v", err)
|
||||
}
|
||||
|
||||
// Check that we have a backup file.
|
||||
|
|
|
|||
|
|
@ -19,20 +19,29 @@ import (
|
|||
)
|
||||
|
||||
type forceCloseCommand struct {
|
||||
RootKey string `long:"rootkey" description:"BIP32 HD root key to use."`
|
||||
RootKey string `long:"rootkey" description:"BIP32 HD root key to use. Leave empty to prompt for lnd 24 word aezeed."`
|
||||
ChannelDB string `long:"channeldb" description:"The lnd channel.db file to use for force-closing channels."`
|
||||
Publish bool `long:"publish" description:"Should the force-closing TX be published to the chain API?"`
|
||||
}
|
||||
|
||||
func (c *forceCloseCommand) Execute(_ []string) error {
|
||||
// Check that root key is valid.
|
||||
if c.RootKey == "" {
|
||||
return fmt.Errorf("root key is required")
|
||||
var (
|
||||
extendedKey *hdkeychain.ExtendedKey
|
||||
err error
|
||||
)
|
||||
|
||||
// Check that root key is valid or fall back to console input.
|
||||
switch {
|
||||
case c.RootKey != "":
|
||||
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
|
||||
|
||||
default:
|
||||
extendedKey, err = rootKeyFromConsole()
|
||||
}
|
||||
extendedKey, err := hdkeychain.NewKeyFromString(c.RootKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing root key: %v", err)
|
||||
return fmt.Errorf("error reading root key: %v", err)
|
||||
}
|
||||
|
||||
// Check that we have a channel DB.
|
||||
if c.ChannelDB == "" {
|
||||
return fmt.Errorf("rescue DB is required")
|
||||
|
|
@ -43,7 +52,7 @@ func (c *forceCloseCommand) Execute(_ []string) error {
|
|||
}
|
||||
|
||||
// Parse channel entries from any of the possible input files.
|
||||
entries, err := parseInput(cfg)
|
||||
entries, err := parseInputType(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,17 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/btcsuite/btcutil/hdkeychain"
|
||||
"github.com/lightningnetwork/lnd/aezeed"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/guggero/chantools/dataformat"
|
||||
|
|
@ -38,7 +43,7 @@ var (
|
|||
)
|
||||
|
||||
func main() {
|
||||
err := Main()
|
||||
err := runCommandParser()
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -50,7 +55,7 @@ func main() {
|
|||
os.Exit(0)
|
||||
}
|
||||
|
||||
func Main() error {
|
||||
func runCommandParser() error {
|
||||
setupLogging()
|
||||
|
||||
// Parse command line.
|
||||
|
|
@ -93,7 +98,7 @@ func Main() error {
|
|||
return err
|
||||
}
|
||||
|
||||
func parseInput(cfg *config) ([]*dataformat.SummaryEntry, error) {
|
||||
func parseInputType(cfg *config) ([]*dataformat.SummaryEntry, error) {
|
||||
var (
|
||||
content []byte
|
||||
err error
|
||||
|
|
@ -144,6 +149,57 @@ func readInput(input string) ([]byte, error) {
|
|||
return ioutil.ReadFile(input)
|
||||
}
|
||||
|
||||
func rootKeyFromConsole() (*hdkeychain.ExtendedKey, 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
|
||||
}
|
||||
|
||||
// We'll trim off extra spaces, and ensure the mnemonic is all
|
||||
// lower case, then populate our request.
|
||||
mnemonicStr = strings.TrimSpace(mnemonicStr)
|
||||
mnemonicStr = strings.ToLower(mnemonicStr)
|
||||
|
||||
cipherSeedMnemonic := strings.Split(mnemonicStr, " ")
|
||||
|
||||
fmt.Println()
|
||||
|
||||
if len(cipherSeedMnemonic) != 24 {
|
||||
return nil, fmt.Errorf("wrong cipher seed mnemonic "+
|
||||
"length: got %v words, expecting %v words",
|
||||
len(cipherSeedMnemonic), 24)
|
||||
}
|
||||
|
||||
// Additionally, the user may have a passphrase, that will also
|
||||
// need to be provided so the daemon can properly decipher the
|
||||
// cipher seed.
|
||||
fmt.Printf("Input your cipher seed passphrase (press enter if " +
|
||||
"your seed doesn't have a passphrase): ")
|
||||
passphrase, err := terminal.ReadPassword(syscall.Stdin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mnemonic aezeed.Mnemonic
|
||||
copy(mnemonic[:], cipherSeedMnemonic[:])
|
||||
|
||||
// If we're unable to map it back into the ciphertext, then either the
|
||||
// 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)
|
||||
}
|
||||
rootKey, err := hdkeychain.NewMaster(cipherSeed.Entropy[:], chainParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to derive master extended key")
|
||||
}
|
||||
return rootKey, nil
|
||||
}
|
||||
|
||||
func setupChainParams(cfg *config) {
|
||||
if cfg.Testnet {
|
||||
chainParams = &chaincfg.TestNet3Params
|
||||
|
|
|
|||
|
|
@ -32,18 +32,26 @@ type cacheEntry struct {
|
|||
}
|
||||
|
||||
type rescueClosedCommand struct {
|
||||
RootKey string `long:"rootkey" description:"BIP32 HD root key to use."`
|
||||
RootKey string `long:"rootkey" description:"BIP32 HD root key to use. Leave empty to prompt for lnd 24 word aezeed."`
|
||||
ChannelDB string `long:"channeldb" description:"The lnd channel.db file to use for rescuing force-closed channels."`
|
||||
}
|
||||
|
||||
func (c *rescueClosedCommand) Execute(_ []string) error {
|
||||
// Check that root key is valid.
|
||||
if c.RootKey == "" {
|
||||
return fmt.Errorf("root key is required")
|
||||
var (
|
||||
extendedKey *hdkeychain.ExtendedKey
|
||||
err error
|
||||
)
|
||||
|
||||
// Check that root key is valid or fall back to console input.
|
||||
switch {
|
||||
case c.RootKey != "":
|
||||
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
|
||||
|
||||
default:
|
||||
extendedKey, err = rootKeyFromConsole()
|
||||
}
|
||||
extendedKey, err := hdkeychain.NewKeyFromString(c.RootKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing root key: %v", err)
|
||||
return fmt.Errorf("error reading root key: %v", err)
|
||||
}
|
||||
|
||||
// Check that we have a channel DB.
|
||||
|
|
@ -56,7 +64,7 @@ func (c *rescueClosedCommand) Execute(_ []string) error {
|
|||
}
|
||||
|
||||
// Parse channel entries from any of the possible input files.
|
||||
entries, err := parseInput(cfg)
|
||||
entries, err := parseInputType(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,65 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/btcsuite/btcutil/hdkeychain"
|
||||
"github.com/lightningnetwork/lnd/aezeed"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
type showRootKeyCommand struct{}
|
||||
|
||||
func (c *showRootKeyCommand) Execute(_ []string) 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')
|
||||
rootKey, err := rootKeyFromConsole()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We'll trim off extra spaces, and ensure the mnemonic is all
|
||||
// lower case, then populate our request.
|
||||
mnemonicStr = strings.TrimSpace(mnemonicStr)
|
||||
mnemonicStr = strings.ToLower(mnemonicStr)
|
||||
|
||||
cipherSeedMnemonic := strings.Split(mnemonicStr, " ")
|
||||
|
||||
fmt.Println()
|
||||
|
||||
if len(cipherSeedMnemonic) != 24 {
|
||||
return fmt.Errorf("wrong cipher seed mnemonic "+
|
||||
"length: got %v words, expecting %v words",
|
||||
len(cipherSeedMnemonic), 24)
|
||||
}
|
||||
|
||||
// Additionally, the user may have a passphrase, that will also
|
||||
// need to be provided so the daemon can properly decipher the
|
||||
// cipher seed.
|
||||
fmt.Printf("Input your cipher seed passphrase (press enter if " +
|
||||
"your seed doesn't have a passphrase): ")
|
||||
passphrase, err := terminal.ReadPassword(syscall.Stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var mnemonic aezeed.Mnemonic
|
||||
copy(mnemonic[:], cipherSeedMnemonic[:])
|
||||
|
||||
// If we're unable to map it back into the ciphertext, then either the
|
||||
// mnemonic is wrong, or the passphrase is wrong.
|
||||
cipherSeed, err := mnemonic.ToCipherSeed(passphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rootKey, err := hdkeychain.NewMaster(cipherSeed.Entropy[:], chainParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to derive master extended key")
|
||||
return fmt.Errorf("failed to read root key from console: %v",
|
||||
err)
|
||||
}
|
||||
fmt.Printf("\nYour BIP32 HD root key is: %s\n", rootKey.String())
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ type summaryCommand struct{}
|
|||
|
||||
func (c *summaryCommand) Execute(_ []string) error {
|
||||
// Parse channel entries from any of the possible input files.
|
||||
entries, err := parseInput(cfg)
|
||||
entries, err := parseInputType(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,20 +20,28 @@ const (
|
|||
)
|
||||
|
||||
type sweepTimeLockCommand struct {
|
||||
RootKey string `long:"rootkey" description:"BIP32 HD root key to use."`
|
||||
RootKey string `long:"rootkey" description:"BIP32 HD root key to use. Leave empty to prompt for lnd 24 word aezeed."`
|
||||
Publish bool `long:"publish" description:"Should the sweep TX be published to the chain API?"`
|
||||
SweepAddr string `long:"sweepaddr" description:"The address the funds should be sweeped to"`
|
||||
MaxCsvLimit int `long:"maxcsvlimit" description:"Maximum CSV limit to use. (default 2000)"`
|
||||
}
|
||||
|
||||
func (c *sweepTimeLockCommand) Execute(_ []string) error {
|
||||
// Check that root key is valid.
|
||||
if c.RootKey == "" {
|
||||
return fmt.Errorf("root key is required")
|
||||
var (
|
||||
extendedKey *hdkeychain.ExtendedKey
|
||||
err error
|
||||
)
|
||||
|
||||
// Check that root key is valid or fall back to console input.
|
||||
switch {
|
||||
case c.RootKey != "":
|
||||
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
|
||||
|
||||
default:
|
||||
extendedKey, err = rootKeyFromConsole()
|
||||
}
|
||||
extendedKey, err := hdkeychain.NewKeyFromString(c.RootKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing root key: %v", err)
|
||||
return fmt.Errorf("error reading root key: %v", err)
|
||||
}
|
||||
|
||||
// Make sure sweep addr is set.
|
||||
|
|
@ -42,7 +50,7 @@ func (c *sweepTimeLockCommand) Execute(_ []string) error {
|
|||
}
|
||||
|
||||
// Parse channel entries from any of the possible input files.
|
||||
entries, err := parseInput(cfg)
|
||||
entries, err := parseInputType(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue