diff --git a/README.md b/README.md index ad83f7d..4e207d9 100644 --- a/README.md +++ b/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) diff --git a/cmd/chantools/derivekey.go b/cmd/chantools/derivekey.go index 1fa809b..1df66d2 100644 --- a/cmd/chantools/derivekey.go +++ b/cmd/chantools/derivekey.go @@ -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) diff --git a/cmd/chantools/dumpbackup.go b/cmd/chantools/dumpbackup.go index 695da1e..ac5d142 100644 --- a/cmd/chantools/dumpbackup.go +++ b/cmd/chantools/dumpbackup.go @@ -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. diff --git a/cmd/chantools/forceclose.go b/cmd/chantools/forceclose.go index b7dedb7..2179fec 100644 --- a/cmd/chantools/forceclose.go +++ b/cmd/chantools/forceclose.go @@ -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 } diff --git a/cmd/chantools/main.go b/cmd/chantools/main.go index 5518581..82773c3 100644 --- a/cmd/chantools/main.go +++ b/cmd/chantools/main.go @@ -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 diff --git a/cmd/chantools/rescueclosed.go b/cmd/chantools/rescueclosed.go index 530fdd4..43e881a 100644 --- a/cmd/chantools/rescueclosed.go +++ b/cmd/chantools/rescueclosed.go @@ -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 } diff --git a/cmd/chantools/showrootkey.go b/cmd/chantools/showrootkey.go index 6bcd62d..2d03c32 100644 --- a/cmd/chantools/showrootkey.go +++ b/cmd/chantools/showrootkey.go @@ -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') - 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) + rootKey, err := rootKeyFromConsole() 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 diff --git a/cmd/chantools/summary.go b/cmd/chantools/summary.go index 0358a0b..556e0c7 100644 --- a/cmd/chantools/summary.go +++ b/cmd/chantools/summary.go @@ -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 } diff --git a/cmd/chantools/sweeptimelock.go b/cmd/chantools/sweeptimelock.go index 6d95696..28bfaaa 100644 --- a/cmd/chantools/sweeptimelock.go +++ b/cmd/chantools/sweeptimelock.go @@ -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 }