From fa62a57e95a5111c5c8bf6cbd642ea809469e656 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Fri, 1 Jan 2021 12:56:19 +0100 Subject: [PATCH] multi: add unit tests --- btc/bip39.go | 135 ++++++++++++++++++---------- cmd/chantools/chanbackup_test.go | 37 ++++++++ cmd/chantools/compactdb.go | 4 + cmd/chantools/compactdb_test.go | 46 ++++++++++ cmd/chantools/derivekey.go | 37 +++++--- cmd/chantools/derivekey_test.go | 91 +++++++++++++++++++ cmd/chantools/doc.go | 6 +- cmd/chantools/dumpbackup.go | 9 +- cmd/chantools/dumpbackup_test.go | 4 + cmd/chantools/dumpchannels.go | 9 ++ cmd/chantools/dumpchannels_test.go | 4 + cmd/chantools/genimportscript.go | 2 +- cmd/chantools/removechannel.go | 2 +- cmd/chantools/root.go | 17 +++- cmd/chantools/root_test.go | 117 ++++++++++++++++++++++++ cmd/chantools/showrootkey.go | 34 +++---- cmd/chantools/showrootkey_test.go | 67 ++++++++++++++ cmd/chantools/testdata/channel.db | Bin 0 -> 106496 bytes cmd/chantools/testdata/wallet.db | Bin 0 -> 106496 bytes cmd/chantools/vanitygen.go | 2 +- cmd/chantools/walletinfo.go | 137 +++++++++++++---------------- cmd/chantools/walletinfo_test.go | 32 +++++++ go.mod | 1 + go.sum | 2 + lnd/aezeed.go | 8 +- 25 files changed, 629 insertions(+), 174 deletions(-) create mode 100644 cmd/chantools/chanbackup_test.go create mode 100644 cmd/chantools/compactdb_test.go create mode 100644 cmd/chantools/derivekey_test.go create mode 100644 cmd/chantools/dumpbackup_test.go create mode 100644 cmd/chantools/dumpchannels_test.go create mode 100644 cmd/chantools/root_test.go create mode 100644 cmd/chantools/showrootkey_test.go create mode 100644 cmd/chantools/testdata/channel.db create mode 100644 cmd/chantools/testdata/wallet.db create mode 100644 cmd/chantools/walletinfo_test.go diff --git a/btc/bip39.go b/btc/bip39.go index f9579ba..b368176 100644 --- a/btc/bip39.go +++ b/btc/bip39.go @@ -6,28 +6,42 @@ import ( "encoding/hex" "errors" "fmt" - "os" - "strings" - "syscall" - "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcutil/hdkeychain" "github.com/guggero/chantools/bip39" "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/ssh/terminal" + "os" + "strings" + "syscall" +) + +const ( + BIP39MnemonicEnvName = "SEED_MNEMONIC" + BIP39PassphraseEnvName = "SEED_PASSPHRASE" ) func ReadMnemonicFromTerminal(params *chaincfg.Params) (*hdkeychain.ExtendedKey, error) { - // We'll now prompt the user to enter in their 12 to 24 word mnemonic. - fmt.Printf("Input your 12 to 24 word mnemonic separated by spaces: ") + var err error reader := bufio.NewReader(os.Stdin) - mnemonicStr, err := reader.ReadString('\n') - if err != nil { - return nil, err + + // To automate things with chantools, we also offer reading the seed + // from environment variables. + mnemonicStr := strings.TrimSpace(os.Getenv(BIP39MnemonicEnvName)) + + if mnemonicStr == "" { + // If there's no value in the environment, we'll now prompt the + //user to enter in their 12 to 24 word mnemonic. + fmt.Printf("Input your 12 to 24 word mnemonic separated by " + + "spaces: ") + mnemonicStr, err = reader.ReadString('\n') + if err != nil { + return nil, err + } + fmt.Println() } - fmt.Println() // We'll trim off extra spaces, and ensure the mnemonic is all // lower case. @@ -40,62 +54,87 @@ func ReadMnemonicFromTerminal(params *chaincfg.Params) (*hdkeychain.ExtendedKey, "must be between 12 and 24 words") } - // 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(int(syscall.Stdin)) // nolint - if err != nil { - return nil, err - } - fmt.Println() + // Additionally, the user may have a passphrase, that will also need to + // be provided so the daemon can properly decipher the cipher seed. + // Try the environment variable first. + passphrase := strings.TrimSpace(os.Getenv(BIP39PassphraseEnvName)) - // Check that the mnemonic is valid. - _, err = bip39.EntropyFromMnemonic(mnemonicStr) - if err != nil { - return nil, err - } + // Because we cannot differentiate between an empty and a non-existent + // environment variable, we need a special character that indicates that + // no passphrase should be used. We use a single dash (-) for that as + // that would be too short for a passphrase anyway. + var ( + passphraseBytes []byte + seed []byte + choice string + ) + switch { + // The user indicated in the environment variable that no passphrase + // should be used. We don't set any value. + case passphrase == "-": - var seed []byte - fmt.Printf("Please choose passphrase mode:\n" + - " 0 - Default BIP39\n" + - " 1 - Passphrase to hex\n" + - " 2 - Digital Bitbox (extra round of PBKDF2)\n" + - "\n" + - "Choice [default 0]: ") - choice, err := reader.ReadString('\n') - if err != nil { - return nil, err + // The environment variable didn't contain anything, we'll read the + // passphrase from the terminal. + case passphrase == "": + // 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): ") + passphraseBytes, err = terminal.ReadPassword( + int(syscall.Stdin), // nolint + ) + if err != nil { + return nil, err + } + fmt.Println() + + // Check that the mnemonic is valid. + _, err = bip39.EntropyFromMnemonic(mnemonicStr) + if err != nil { + return nil, err + } + + fmt.Printf("Please choose passphrase mode:\n" + + " 0 - Default BIP39\n" + + " 1 - Passphrase to hex\n" + + " 2 - Digital Bitbox (extra round of PBKDF2)\n" + + "\n" + + "Choice [default 0]: ") + choice, err = reader.ReadString('\n') + if err != nil { + return nil, err + } + fmt.Println() + + // There was a password in the environment, just convert it to bytes. + default: + passphraseBytes = []byte(passphrase) } - fmt.Println() switch strings.TrimSpace(choice) { case "", "0": seed = pbkdf2.Key( []byte(mnemonicStr), append( - []byte("mnemonic"), passphrase..., + []byte("mnemonic"), passphraseBytes..., ), 2048, 64, sha512.New, ) case "1": - passphrase = []byte(hex.EncodeToString(passphrase)) + p := []byte(hex.EncodeToString(passphraseBytes)) seed = pbkdf2.Key( - []byte(mnemonicStr), append( - []byte("mnemonic"), passphrase..., - ), 2048, 64, sha512.New, + []byte(mnemonicStr), append([]byte("mnemonic"), p...), + 2048, 64, sha512.New, ) case "2": - passphrase = pbkdf2.Key( - passphrase, []byte("Digital Bitbox"), 20480, 64, + p := hex.EncodeToString(pbkdf2.Key( + passphraseBytes, []byte("Digital Bitbox"), 20480, 64, sha512.New, - ) - passphrase = []byte(hex.EncodeToString(passphrase)) + )) seed = pbkdf2.Key( - []byte(mnemonicStr), append( - []byte("mnemonic"), passphrase..., - ), 2048, 64, sha512.New, + []byte(mnemonicStr), append([]byte("mnemonic"), p...), + 2048, 64, sha512.New, ) default: diff --git a/cmd/chantools/chanbackup_test.go b/cmd/chantools/chanbackup_test.go new file mode 100644 index 0000000..c16d48d --- /dev/null +++ b/cmd/chantools/chanbackup_test.go @@ -0,0 +1,37 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +const ( + backupContent = "FundingOutpoint: (string) (len=66) \"10279f626196340" + + "58b6133cb7ac6c1693a8e6df7caa91c6263ca3d0bf704ad4d:0\"" +) + +func TestChanBackupAndDumpBackup(t *testing.T) { + h := newHarness(t) + + // Create a channel backup from a channel DB file. + makeBackup := &chanBackupCommand{ + ChannelDB: h.testdataFile("channel.db"), + MultiFile: h.tempFile("extracted.backup"), + rootKey: &rootKey{RootKey: rootKeyAezeed}, + } + + err := makeBackup.Execute(nil, nil) + require.NoError(t, err) + + // Decrypt and dump the channel backup file. + dumpBackup := &dumpBackupCommand{ + MultiFile: makeBackup.MultiFile, + rootKey: &rootKey{RootKey: rootKeyAezeed}, + } + + err = dumpBackup.Execute(nil, nil) + require.NoError(t, err) + + h.assertLogContains(backupContent) +} diff --git a/cmd/chantools/compactdb.go b/cmd/chantools/compactdb.go index 2e3c200..dbb763f 100644 --- a/cmd/chantools/compactdb.go +++ b/cmd/chantools/compactdb.go @@ -64,10 +64,14 @@ func (c *compactDBCommand) Execute(_ *cobra.Command, _ []string) error { if err != nil { return fmt.Errorf("error opening source DB: %v", err) } + defer func() { _ = src.Close() }() + dst, err := c.openDB(c.DestDB, false) if err != nil { return fmt.Errorf("error opening destination DB: %v", err) } + defer func() { _ = dst.Close() }() + err = c.compact(dst, src) if err != nil { return fmt.Errorf("error compacting DB: %v", err) diff --git a/cmd/chantools/compactdb_test.go b/cmd/chantools/compactdb_test.go new file mode 100644 index 0000000..7d44d71 --- /dev/null +++ b/cmd/chantools/compactdb_test.go @@ -0,0 +1,46 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCompactDBAndDumpChannels(t *testing.T) { + h := newHarness(t) + + // Compact the test DB. + compact := &compactDBCommand{ + SourceDB: h.testdataFile("channel.db"), + DestDB: h.tempFile("compacted.db"), + } + + err := compact.Execute(nil, nil) + require.NoError(t, err) + + require.FileExists(t, compact.DestDB) + + // Compacting small DBs actually increases the size slightly. But we + // just want to make sure the contents match. + require.GreaterOrEqual( + t, h.fileSize(compact.DestDB), h.fileSize(compact.SourceDB), + ) + + // Compare the content of the source and destination DB by looking at + // the logged dump. + dump := &dumpChannelsCommand{ + ChannelDB: compact.SourceDB, + } + h.clearLog() + err = dump.Execute(nil, nil) + require.NoError(t, err) + sourceDump := h.getLog() + + h.clearLog() + dump.ChannelDB = compact.DestDB + err = dump.Execute(nil, nil) + require.NoError(t, err) + destDump := h.getLog() + + h.assertLogEqual(sourceDump, destDump) +} diff --git a/cmd/chantools/derivekey.go b/cmd/chantools/derivekey.go index 2e962ad..3c7a837 100644 --- a/cmd/chantools/derivekey.go +++ b/cmd/chantools/derivekey.go @@ -2,15 +2,24 @@ package main import ( "fmt" - "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/hdkeychain" "github.com/guggero/chantools/lnd" "github.com/spf13/cobra" ) +const deriveKeyFormat = ` +Path: %s +Network: %s +Public key: %x +Extended public key (xpub): %v +Address: %v +Legacy address: %v +Private key (WIF): %s +Extended private key (xprv): %s +` + type deriveKeyCommand struct { - BIP39 bool Path string Neuter bool @@ -29,11 +38,6 @@ derivation path from the root key and prints it to the console.`, --path "m/1017'/0'/5'/0/0'" --neuter`, RunE: cc.Execute, } - cc.cmd.Flags().BoolVar( - &cc.BIP39, "bip39", false, "read a classic BIP39 seed and "+ - "passphrase from the terminal instead of asking for "+ - "lnd seed format or providing the --rootkey flag", - ) cc.cmd.Flags().StringVar( &cc.Path, "path", "", "BIP32 derivation path to derive; must "+ "start with \"m/\"", @@ -60,7 +64,6 @@ func (c *deriveKeyCommand) Execute(_ *cobra.Command, _ []string) error { func deriveKey(extendedKey *hdkeychain.ExtendedKey, path string, neuter bool) error { - fmt.Printf("Deriving path %s for network %s.\n", path, chainParams.Name) child, pubKey, wif, err := lnd.DeriveKey(extendedKey, path, chainParams) if err != nil { return fmt.Errorf("could not derive keys: %v", err) @@ -69,8 +72,6 @@ func deriveKey(extendedKey *hdkeychain.ExtendedKey, path string, if err != nil { return fmt.Errorf("could not neuter child key: %v", err) } - fmt.Printf("\nPublic key: %x\n", pubKey.SerializeCompressed()) - fmt.Printf("Extended public key (xpub): %s\n", neutered.String()) // Print the address too. hash160 := btcutil.Hash160(pubKey.SerializeCompressed()) @@ -84,13 +85,21 @@ func deriveKey(extendedKey *hdkeychain.ExtendedKey, path string, if err != nil { return fmt.Errorf("could not create address: %v", err) } - fmt.Printf("Address: %s\n", addrP2WKH) - fmt.Printf("Legacy address: %s\n", addrP2PKH) + privKey, xPriv := "n/a", "n/a" if !neuter { - fmt.Printf("\nPrivate key (WIF): %s\n", wif.String()) - fmt.Printf("Extended private key (xprv): %s\n", child.String()) + privKey, xPriv = wif.String(), child.String() } + result := fmt.Sprintf( + deriveKeyFormat, path, chainParams.Name, + pubKey.SerializeCompressed(), neutered, addrP2WKH, addrP2PKH, + privKey, xPriv, + ) + fmt.Printf(result) + + // For the tests, also log as trace level which is disabled by default. + log.Tracef(result) + return nil } diff --git a/cmd/chantools/derivekey_test.go b/cmd/chantools/derivekey_test.go new file mode 100644 index 0000000..3512c33 --- /dev/null +++ b/cmd/chantools/derivekey_test.go @@ -0,0 +1,91 @@ +package main + +import ( + "github.com/guggero/chantools/btc" + "github.com/guggero/chantools/lnd" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +const ( + testPath = "m/123'/45'/67'/8/9" + keyContent = "bcrt1qnl5qfvpfcmj7y56nugpermluu46x79sfz0ku70" + keyContentBIP39 = "bcrt1q3pae32m7jdqm5ulf80yc3n59xy4s4xm5a28ekr" +) + +func TestDeriveKey(t *testing.T) { + h := newHarness(t) + + // Derive a specific key from the serialized root key. + derive := &deriveKeyCommand{ + Path: testPath, + rootKey: &rootKey{RootKey: rootKeyAezeed}, + } + + err := derive.Execute(nil, nil) + require.NoError(t, err) + + h.assertLogContains(keyContent) +} + +func TestDeriveKeyAezeedNoPassphrase(t *testing.T) { + h := newHarness(t) + + // Derive a specific key from the serialized root key. + derive := &deriveKeyCommand{ + Path: testPath, + rootKey: &rootKey{}, + } + + err := os.Setenv(lnd.MnemonicEnvName, seedAezeedNoPassphrase) + require.NoError(t, err) + err = os.Setenv(lnd.PassphraseEnvName, "-") + require.NoError(t, err) + + err = derive.Execute(nil, nil) + require.NoError(t, err) + + h.assertLogContains(keyContent) +} + +func TestDeriveKeyAezeedWithPassphrase(t *testing.T) { + h := newHarness(t) + + // Derive a specific key from the serialized root key. + derive := &deriveKeyCommand{ + Path: testPath, + rootKey: &rootKey{}, + } + + err := os.Setenv(lnd.MnemonicEnvName, seedAezeedWithPassphrase) + require.NoError(t, err) + err = os.Setenv(lnd.PassphraseEnvName, testPassPhrase) + require.NoError(t, err) + + err = derive.Execute(nil, nil) + require.NoError(t, err) + + h.assertLogContains(keyContent) +} + +func TestDeriveKeySeedBip39(t *testing.T) { + h := newHarness(t) + + // Derive a specific key from the serialized root key. + derive := &deriveKeyCommand{ + Path: testPath, + rootKey: &rootKey{BIP39: true}, + } + + err := os.Setenv(btc.BIP39MnemonicEnvName, seedBip39) + require.NoError(t, err) + err = os.Setenv(btc.BIP39PassphraseEnvName, "-") + require.NoError(t, err) + + err = derive.Execute(nil, nil) + require.NoError(t, err) + + h.assertLogContains(keyContentBIP39) +} diff --git a/cmd/chantools/doc.go b/cmd/chantools/doc.go index b136908..59b2973 100644 --- a/cmd/chantools/doc.go +++ b/cmd/chantools/doc.go @@ -7,8 +7,8 @@ import ( func newDocCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "doc", - Short: "Generate the markdown documentation of all commands", + Use: "doc", + Short: "Generate the markdown documentation of all commands", Hidden: true, RunE: func(cmd *cobra.Command, args []string) error { return doc.GenMarkdownTree(rootCmd, "./doc") @@ -16,4 +16,4 @@ func newDocCommand() *cobra.Command { } return cmd -} \ No newline at end of file +} diff --git a/cmd/chantools/dumpbackup.go b/cmd/chantools/dumpbackup.go index a7bd147..8da3ce3 100644 --- a/cmd/chantools/dumpbackup.go +++ b/cmd/chantools/dumpbackup.go @@ -64,9 +64,14 @@ func dumpChannelBackup(multiFile *chanbackup.MultiFile, if err != nil { return fmt.Errorf("could not extract multi file: %v", err) } - spew.Dump(dump.BackupMulti{ + content := dump.BackupMulti{ Version: multi.Version, StaticBackups: dump.BackupDump(multi, chainParams), - }) + } + spew.Dump(content) + + // For the tests, also log as trace level which is disabled by default. + log.Tracef(spew.Sdump(content)) + return nil } diff --git a/cmd/chantools/dumpbackup_test.go b/cmd/chantools/dumpbackup_test.go new file mode 100644 index 0000000..fdf9a63 --- /dev/null +++ b/cmd/chantools/dumpbackup_test.go @@ -0,0 +1,4 @@ +package main + +// This file is empty for now, the dumpbackup command is covered by the test in +// chanbackup_test.go. diff --git a/cmd/chantools/dumpchannels.go b/cmd/chantools/dumpchannels.go index 3e8185d..4f6ed67 100644 --- a/cmd/chantools/dumpchannels.go +++ b/cmd/chantools/dumpchannels.go @@ -50,6 +50,7 @@ func (c *dumpChannelsCommand) Execute(_ *cobra.Command, _ []string) error { if err != nil { return fmt.Errorf("error opening rescue DB: %v", err) } + defer func() { _ = db.Close() }() if c.Closed { return dumpClosedChannelInfo(db) @@ -69,6 +70,10 @@ func dumpOpenChannelInfo(chanDb *channeldb.DB) error { } spew.Dump(dumpChannels) + + // For the tests, also log as trace level which is disabled by default. + log.Tracef(spew.Sdump(dumpChannels)) + return nil } @@ -84,5 +89,9 @@ func dumpClosedChannelInfo(chanDb *channeldb.DB) error { } spew.Dump(dumpChannels) + + // For the tests, also log as trace level which is disabled by default. + log.Tracef(spew.Sdump(dumpChannels)) + return nil } diff --git a/cmd/chantools/dumpchannels_test.go b/cmd/chantools/dumpchannels_test.go new file mode 100644 index 0000000..b8fbad5 --- /dev/null +++ b/cmd/chantools/dumpchannels_test.go @@ -0,0 +1,4 @@ +package main + +// This file is empty for now, the dumpchannels command is covered by the test +// in compactdb_test.go. diff --git a/cmd/chantools/genimportscript.go b/cmd/chantools/genimportscript.go index ccbb3a6..f9d297a 100644 --- a/cmd/chantools/genimportscript.go +++ b/cmd/chantools/genimportscript.go @@ -96,7 +96,7 @@ func (c *genImportScriptCommand) Execute(_ *cobra.Command, _ []string) error { return fmt.Errorf("error reading root key: %v", err) } - // The btcwallet gives the birthday a slack of 48 hours, let's do the + // The btcwallet gives the birthday a slack of 48 hours, let's do the // same. if !birthday.IsZero() { c.RescanFrom = btc.SeedBirthdayToBlock( diff --git a/cmd/chantools/removechannel.go b/cmd/chantools/removechannel.go index dae7b86..db8fbff 100644 --- a/cmd/chantools/removechannel.go +++ b/cmd/chantools/removechannel.go @@ -26,7 +26,7 @@ func newRemoveChannelCommand() *cobra.Command { Short: "Remove a single channel from the given channel DB", Example: `chantools --channeldb ~/.lnd/data/graph/mainnet/channel.db \ --channel 3149764effbe82718b280de425277e5e7b245a4573aa4a0203ac12cee1c37816:0`, - RunE: cc.Execute, + RunE: cc.Execute, } cc.cmd.Flags().StringVar( &cc.ChannelDB, "channeldb", "", "lnd channel.backup file to "+ diff --git a/cmd/chantools/root.go b/cmd/chantools/root.go index 00b7a3e..cf9b853 100644 --- a/cmd/chantools/root.go +++ b/cmd/chantools/root.go @@ -5,7 +5,7 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/btcsuite/btcutil/hdkeychain" + "github.com/guggero/chantools/btc" "io/ioutil" "os" "strings" @@ -14,6 +14,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btclog" + "github.com/btcsuite/btcutil/hdkeychain" "github.com/guggero/chantools/dataformat" "github.com/guggero/chantools/lnd" "github.com/lightningnetwork/lnd/build" @@ -46,7 +47,7 @@ var rootCmd = &cobra.Command{ funds locked in lnd channels in case lnd itself cannot run properly anymore. Complete documentation is available at https://github.com/guggero/chantools/.`, Version: fmt.Sprintf("v%s, commit %s", version, Commit), - PreRun: func(cmd *cobra.Command, args []string) { + PersistentPreRun: func(cmd *cobra.Command, args []string) { switch { case Testnet: chainParams = &chaincfg.TestNet3Params @@ -109,6 +110,7 @@ func main() { type rootKey struct { RootKey string + BIP39 bool } func newRootKey(cmd *cobra.Command, desc string) *rootKey { @@ -118,6 +120,11 @@ func newRootKey(cmd *cobra.Command, desc string) *rootKey { "to use for "+desc+"; leave empty to prompt for "+ "lnd 24 word aezeed", ) + cmd.Flags().BoolVar( + &r.BIP39, "bip39", false, "read a classic BIP39 seed and "+ + "passphrase from the terminal instead of asking for "+ + "lnd seed format or providing the --rootkey flag", + ) return r } @@ -136,6 +143,10 @@ func (r *rootKey) readWithBirthday() (*hdkeychain.ExtendedKey, time.Time, extendedKey, err := hdkeychain.NewKeyFromString(r.RootKey) return extendedKey, time.Unix(0, 0), err + case r.BIP39: + extendedKey, err := btc.ReadMnemonicFromTerminal(chainParams) + return extendedKey, time.Unix(0, 0), err + default: return lnd.ReadAezeed(chainParams) } @@ -249,7 +260,7 @@ func setupLogging() { if err != nil { panic(err) } - err = build.ParseAndSetDebugLevels("trace", logWriter) + err = build.ParseAndSetDebugLevels("debug", logWriter) if err != nil { panic(err) } diff --git a/cmd/chantools/root_test.go b/cmd/chantools/root_test.go new file mode 100644 index 0000000..afb8ba6 --- /dev/null +++ b/cmd/chantools/root_test.go @@ -0,0 +1,117 @@ +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path" + "regexp" + "testing" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/chanbackup" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/stretchr/testify/require" +) + +const ( + seedAezeedNoPassphrase = "abandon kangaroo tribe spell brass entry " + + "argue buzz muffin total rug title autumn wish use bubble " + + "alarm rent machine hockey fork slam gaze tobacco" + seedAezeedWithPassphrase = "able pause keen exhibit duck olympic " + + "foot donor hire omit earth ribbon rotate cruise door orbit " + + "nephew mixture machine hockey fork scorpion shell door" + testPassPhrase = "testnet3" + seedBip39 = "uncover bargain diesel boss local host over divide " + + "orient cradle good crumble" + + rootKeyAezeed = "tprv8ZgxMBicQKsPejNXQLJKe3dBBs9Zrt53EZrsBzVLQ8rZji3" + + "hVb3wcoRvgrjvTmjPG2ixoGUUkCyC6yBEy9T5gbLdvD2a5VmJbcFd5Q9pkAs" + rootKeyBip39 = "tprv8ZgxMBicQKsPdoVEZRN2MyzEgxGTqJepzhMc66b26zL1siLi" + + "WRQAGh9rAgPPJuQeHWWpgcDcS45yi6KBTFeGkQMEb2RNTrP11evJcB4UVSh" + rootKeyBip39Passphrase = "" +) + +var ( + datePattern = regexp.MustCompile( + "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3} ", + ) + addressPattern = regexp.MustCompile("\\(0x[0-9a-f]{10}\\)") +) + +type harness struct { + t *testing.T + logBuffer *bytes.Buffer + logger btclog.Logger + tempDir string +} + +func newHarness(t *testing.T) *harness { + buf := &bytes.Buffer{} + logBackend := btclog.NewBackend(buf) + tempDir, err := ioutil.TempDir("", "chantools") + require.NoError(t, err) + + h := &harness{ + t: t, + logBuffer: buf, + logger: logBackend.Logger("CHAN"), + tempDir: tempDir, + } + + h.logger.SetLevel(btclog.LevelTrace) + log = h.logger + channeldb.UseLogger(h.logger) + chanbackup.UseLogger(h.logger) + + os.Clearenv() + chainParams = &chaincfg.RegressionNetParams + + return h +} + +func (h *harness) getLog() string { + return h.logBuffer.String() +} + +func (h *harness) clearLog() { + h.logBuffer.Reset() +} + +func (h *harness) assertLogContains(format string, args ...interface{}) { + h.t.Helper() + + require.Contains(h.t, h.logBuffer.String(), fmt.Sprintf(format, args...)) +} + +func (h *harness) assertLogEqual(a, b string) { + // Remove all timestamps and all memory addresses from dumps as those + // are always different. + a = datePattern.ReplaceAllString(a, "") + a = addressPattern.ReplaceAllString(a, "") + + b = datePattern.ReplaceAllString(b, "") + b = addressPattern.ReplaceAllString(b, "") + + require.Equal(h.t, a, b) +} + +func (h *harness) testdataFile(name string) string { + workingDir, err := os.Getwd() + require.NoError(h.t, err) + + return path.Join(workingDir, "testdata", name) +} + +func (h *harness) tempFile(name string) string { + return path.Join(h.tempDir, name) +} + +func (h *harness) fileSize(name string) int64 { + stat, err := os.Stat(name) + require.NoError(h.t, err) + + return stat.Size() +} diff --git a/cmd/chantools/showrootkey.go b/cmd/chantools/showrootkey.go index 6a553ec..d2894ec 100644 --- a/cmd/chantools/showrootkey.go +++ b/cmd/chantools/showrootkey.go @@ -3,14 +3,14 @@ package main import ( "fmt" - "github.com/btcsuite/btcutil/hdkeychain" - "github.com/guggero/chantools/btc" "github.com/spf13/cobra" ) -type showRootKeyCommand struct { - BIP39 bool +const showRootKeyFormat = ` +Your BIP32 HD root key is: %v +` +type showRootKeyCommand struct { rootKey *rootKey cmd *cobra.Command } @@ -27,11 +27,6 @@ commands of this tool.`, Example: `chantools showrootkey`, RunE: cc.Execute, } - cc.cmd.Flags().BoolVar( - &cc.BIP39, "bip39", false, "read a classic BIP39 seed and "+ - "passphrase from the terminal instead of asking for "+ - "lnd seed format or providing the --rootkey flag", - ) cc.rootKey = newRootKey(cc.cmd, "decrypting the backup") @@ -39,23 +34,16 @@ commands of this tool.`, } func (c *showRootKeyCommand) Execute(_ *cobra.Command, _ []string) error { - var ( - extendedKey *hdkeychain.ExtendedKey - err error - ) - - // Check that root key is valid or fall back to terminal input. - switch { - case c.BIP39: - extendedKey, err = btc.ReadMnemonicFromTerminal(chainParams) - - default: - extendedKey, err = c.rootKey.read() - } + extendedKey, err := c.rootKey.read() if err != nil { return fmt.Errorf("error reading root key: %v", err) } - fmt.Printf("\nYour BIP32 HD root key is: %s\n", extendedKey.String()) + result := fmt.Sprintf(showRootKeyFormat, extendedKey) + fmt.Printf(result) + + // For the tests, also log as trace level which is disabled by default. + log.Tracef(result) + return nil } diff --git a/cmd/chantools/showrootkey_test.go b/cmd/chantools/showrootkey_test.go new file mode 100644 index 0000000..fb7b892 --- /dev/null +++ b/cmd/chantools/showrootkey_test.go @@ -0,0 +1,67 @@ +package main + +import ( + "github.com/guggero/chantools/btc" + "os" + "testing" + + "github.com/guggero/chantools/lnd" + "github.com/stretchr/testify/require" +) + +func TestShowRootKey(t *testing.T) { + h := newHarness(t) + + // Derive the root key from the aezeed. + show := &showRootKeyCommand{ + rootKey: &rootKey{}, + } + + err := os.Setenv(lnd.MnemonicEnvName, seedAezeedNoPassphrase) + require.NoError(t, err) + err = os.Setenv(lnd.PassphraseEnvName, "-") + require.NoError(t, err) + + err = show.Execute(nil, nil) + require.NoError(t, err) + + h.assertLogContains(rootKeyAezeed) +} + +func TestShowRootKeyBIP39(t *testing.T) { + h := newHarness(t) + + // Derive the root key from the BIP39 seed. + show := &showRootKeyCommand{ + rootKey: &rootKey{BIP39: true}, + } + + err := os.Setenv(btc.BIP39MnemonicEnvName, seedBip39) + require.NoError(t, err) + err = os.Setenv(btc.BIP39PassphraseEnvName, "-") + require.NoError(t, err) + + err = show.Execute(nil, nil) + require.NoError(t, err) + + h.assertLogContains(rootKeyBip39) +} + +func TestShowRootKeyBIP39WithPassphre(t *testing.T) { + h := newHarness(t) + + // Derive the root key from the BIP39 seed. + show := &showRootKeyCommand{ + rootKey: &rootKey{BIP39: true}, + } + + err := os.Setenv(btc.BIP39MnemonicEnvName, seedBip39) + require.NoError(t, err) + err = os.Setenv(btc.BIP39PassphraseEnvName, testPassPhrase) + require.NoError(t, err) + + err = show.Execute(nil, nil) + require.NoError(t, err) + + h.assertLogContains(rootKeyBip39Passphrase) +} \ No newline at end of file diff --git a/cmd/chantools/testdata/channel.db b/cmd/chantools/testdata/channel.db new file mode 100644 index 0000000000000000000000000000000000000000..54d39f5ff71664a1bd35c582b0ac73200f3a66f2 GIT binary patch literal 106496 zcmeHQ3p`ZY{+}6yR1y)rhA3gALLsM)RC?$kq>$&x6h@3!R7y%uB@rc5R3a3LgcQmv zp_BBc=&ASn_5ZDz*|nR~ZRXr_{`a1H?^-^~p6_12y&m87*z32}+G|lLJR{D(K5p&t zn1+7$=H2MVzfWAg@Qn)phA;p6WZTFv=doTh2swPk3}6N@1DFBK0A>I)fEmCHUH!`NxZmt6kRlo7x zV$3>SxvXZi%SH+PI2YICVcb;%vLH`3O_Z1mMW0;ycUbtga)KN40`+wjQkKOr9xB&7 ztrhp(gJg;E2l8|HH)a4cfEmCHUmkzj z%iy0pTat{aG6Yx{8=y`95M0b+= zI>6-c_VL|`@rWz|d|UvZ1}sJL?+RcP9`Sq!-+c_+L*e&@!3RZ>yb0j`B4oy=M6`eONw==5ky(Ot||)1G&I8ZY)2y02YVg;_mMMLqWJH zDC-gA&hTO{5>rmbharfIS6p7zxXL34KQUbzatpL&eNpFXba-YtkoCvfY}Z zGihhnW$0Z5(d8lxs4b7}eQL*zA;EdOJ~W@!GBaNKTi^8M?brLJ8P;t$*AOnf5B!M` zVUTS((|r+>!SZou2E!9SG(Oa=07_|5VOWV76ahw9Dr_lMXqpj`Pep~8J

}8}9GCBIk^qfcuXs05twpsN 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 K1DFBK!2bciNYo4f literal 0 HcmV?d00001 diff --git a/cmd/chantools/testdata/wallet.db b/cmd/chantools/testdata/wallet.db new file mode 100644 index 0000000000000000000000000000000000000000..bc600b9069946349dc08f982d35babeb7204f5de GIT binary patch literal 106496 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 `, Example: `chantools vanitygen --prefix 022222 --threads 8`, - RunE: cc.Execute, + RunE: cc.Execute, } cc.cmd.Flags().StringVar( &cc.Prefix, "prefix", "", "hex encoded prefix to find in node "+ diff --git a/cmd/chantools/walletinfo.go b/cmd/chantools/walletinfo.go index 8e8c47f..dbcdd29 100644 --- a/cmd/chantools/walletinfo.go +++ b/cmd/chantools/walletinfo.go @@ -1,11 +1,8 @@ package main import ( - "encoding/hex" "fmt" "os" - "os/user" - "path/filepath" "strings" "github.com/btcsuite/btcd/btcec" @@ -15,6 +12,7 @@ import ( "github.com/btcsuite/btcwallet/walletdb" "github.com/guggero/chantools/lnd" "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnwallet" "github.com/spf13/cobra" @@ -27,6 +25,19 @@ import ( const ( passwordEnvName = "WALLET_PASSWORD" + + walletInfoFormat = ` +Identity Pubkey: %x +BIP32 HD extended root key: %s +Wallet scopes: +%s +` + + keyScopeformat = ` +Scope: m/%d'/%d' + Number of internal %s addresses: %d + Number of external %s addresses: %d +` ) var ( @@ -126,13 +137,14 @@ func (c *walletInfoCommand) Execute(_ *cobra.Command, _ []string) error { // Try to load and open the wallet. db, err := walletdb.Open( - "bdb", cleanAndExpandPath(c.WalletDB), false, + "bdb", lncfg.CleanAndExpandPath(c.WalletDB), false, lnd.DefaultOpenTimeout, ) if err != nil { return fmt.Errorf("error opening wallet database: %v", err) } - defer closeWalletDb(db) + defer func() { _ = db.Close() }() + w, err := wallet.Open(db, publicWalletPw, openCallbacks, chainParams, 0) if err != nil { return err @@ -147,21 +159,33 @@ func (c *walletInfoCommand) Execute(_ *cobra.Command, _ []string) error { } // Print the wallet info and if requested the root key. - err = walletInfo(w) + identityKey, scopeInfo, err := walletInfo(w) if err != nil { return err } + rootKey := "n/a" if c.WithRootKey { masterHDPrivKey, err := decryptRootKey(db, privateWalletPw) if err != nil { return err } - fmt.Printf("BIP32 HD extended root key: %s\n", masterHDPrivKey) + rootKey = string(masterHDPrivKey) } + + result := fmt.Sprintf( + walletInfoFormat, identityKey.SerializeCompressed(), rootKey, + scopeInfo, + ) + + fmt.Printf(result) + + // For the tests, also log as trace level which is disabled by default. + log.Tracef(result) + return nil } -func walletInfo(w *wallet.Wallet) error { +func walletInfo(w *wallet.Wallet) (*btcec.PublicKey, string, error) { keyRing := keychain.NewBtcWalletKeyRing(w, chainParams.HDCoinType) idPrivKey, err := keyRing.DerivePrivKey(keychain.KeyDescriptor{ KeyLocator: keychain.KeyLocator{ @@ -170,47 +194,48 @@ func walletInfo(w *wallet.Wallet) error { }, }) if err != nil { - return fmt.Errorf("unable to open key ring for coin type %d: "+ - "%v", chainParams.HDCoinType, err) + return nil, "", fmt.Errorf("unable to open key ring for coin "+ + "type %d: %v", chainParams.HDCoinType, err) } - idPrivKey.Curve = btcec.S256() - fmt.Printf( - "Identity Pubkey: %s\n", - hex.EncodeToString(idPrivKey.PubKey().SerializeCompressed()), - ) - // Print information about the different addresses in use. - printScopeInfo( - "np2wkh", w, - w.Manager.ScopesForExternalAddrType( + // Collect information about the different addresses in use. + scopeNp2wkh, err := printScopeInfo( + "np2wkh", w, w.Manager.ScopesForExternalAddrType( waddrmgr.NestedWitnessPubKey, ), ) - printScopeInfo( - "p2wkh", w, - w.Manager.ScopesForExternalAddrType( + if err != nil { + return nil, "", err + } + scopeP2wkh, err := printScopeInfo( + "p2wkh", w, w.Manager.ScopesForExternalAddrType( waddrmgr.WitnessPubKey, ), ) - return nil + if err != nil { + return nil, "", err + } + + return idPrivKey.PubKey(), scopeNp2wkh + scopeP2wkh, nil } -func printScopeInfo(name string, w *wallet.Wallet, scopes []waddrmgr.KeyScope) { +func printScopeInfo(name string, w *wallet.Wallet, + scopes []waddrmgr.KeyScope) (string, error) { + + scopeInfo := "" for _, scope := range scopes { props, err := w.AccountProperties(scope, defaultAccount) if err != nil { - fmt.Printf("Error fetching account properties: %v", err) + return "", fmt.Errorf("error fetching account "+ + "properties: %v", err) } - fmt.Printf("Scope: %s\n", scope.String()) - fmt.Printf( - " Number of internal (change) %s addresses: %d\n", - name, props.InternalKeyCount, - ) - fmt.Printf( - " Number of external %s addresses: %d\n", name, - props.ExternalKeyCount, + scopeInfo += fmt.Sprintf( + keyScopeformat, scope.Purpose, scope.Coin, name, + props.InternalKeyCount, name, props.ExternalKeyCount, ) } + + return scopeInfo, nil } func decryptRootKey(db walletdb.DB, privPassphrase []byte) ([]byte, error) { @@ -222,18 +247,14 @@ func decryptRootKey(db walletdb.DB, privPassphrase []byte) ([]byte, error) { err := walletdb.View(db, func(tx walletdb.ReadTx) error { ns := tx.ReadBucket(waddrmgrNamespaceKey) if ns == nil { - return fmt.Errorf( - "namespace '%s' does not exist", - waddrmgrNamespaceKey, - ) + return fmt.Errorf("namespace '%s' does not exist", + waddrmgrNamespaceKey) } mainBucket := ns.NestedReadBucket(mainBucketName) if mainBucket == nil { - return fmt.Errorf( - "bucket '%s' does not exist", - mainBucketName, - ) + return fmt.Errorf("bucket '%s' does not exist", + mainBucketName) } val := mainBucket.Get(masterPrivKeyName) @@ -251,6 +272,7 @@ func decryptRootKey(db walletdb.DB, privPassphrase []byte) ([]byte, error) { masterHDPrivEnc = make([]byte, len(val)) copy(masterHDPrivEnc, val) } + return nil }) if err != nil { @@ -276,36 +298,3 @@ func decryptRootKey(db walletdb.DB, privPassphrase []byte) ([]byte, error) { copy(cryptoKeyPriv[:], cryptoKeyPrivBytes) return cryptoKeyPriv.Decrypt(masterHDPrivEnc) } - -func closeWalletDb(db walletdb.DB) { - err := db.Close() - if err != nil { - fmt.Printf("Error closing database: %v", err) - } -} - -// cleanAndExpandPath expands environment variables and leading ~ in the -// passed path, cleans the result, and returns it. -// This function is taken from https://github.com/btcsuite/btcd -func cleanAndExpandPath(path string) string { - if path == "" { - return "" - } - - // Expand initial ~ to OS specific home directory. - if strings.HasPrefix(path, "~") { - var homeDir string - u, err := user.Current() - if err == nil { - homeDir = u.HomeDir - } else { - homeDir = os.Getenv("HOME") - } - - path = strings.Replace(path, "~", homeDir, 1) - } - - // NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%, - // but the variables can still be expanded via POSIX-style $VARIABLE. - return filepath.Clean(os.ExpandEnv(path)) -} diff --git a/cmd/chantools/walletinfo_test.go b/cmd/chantools/walletinfo_test.go new file mode 100644 index 0000000..0800abd --- /dev/null +++ b/cmd/chantools/walletinfo_test.go @@ -0,0 +1,32 @@ +package main + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +const ( + walletContent = "03b99ab108e39e9e4cf565c1b706480180a70a4fdc4828e44c50" + + "4530c056be5b5f" +) + +func TestWalletInfo(t *testing.T) { + h := newHarness(t) + + // Dump the wallet information. + info := &walletInfoCommand{ + WalletDB: h.testdataFile("wallet.db"), + WithRootKey: true, + } + + err := os.Setenv(passwordEnvName, testPassPhrase) + require.NoError(t, err) + + err = info.Execute(nil, nil) + require.NoError(t, err) + + h.assertLogContains(walletContent) + h.assertLogContains(rootKeyAezeed) +} diff --git a/go.mod b/go.mod index 9918da0..3faea38 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/ltcsuite/ltcd v0.0.0-20191228044241-92166e412499 // indirect github.com/miekg/dns v1.1.26 // indirect github.com/spf13/cobra v1.1.1 + github.com/stretchr/testify v1.6.1 go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 ) diff --git a/go.sum b/go.sum index c569685..e8954e5 100644 --- a/go.sum +++ b/go.sum @@ -568,6 +568,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= @@ -858,6 +859,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/lnd/aezeed.go b/lnd/aezeed.go index d0f1f74..8413566 100644 --- a/lnd/aezeed.go +++ b/lnd/aezeed.go @@ -16,8 +16,8 @@ import ( ) const ( - memonicEnvName = "AEZEED_MNEMONIC" - passphraseEnvName = "AEZEED_PASSPHRASE" + MnemonicEnvName = "AEZEED_MNEMONIC" + PassphraseEnvName = "AEZEED_PASSPHRASE" ) var ( @@ -30,7 +30,7 @@ func ReadAezeed(params *chaincfg.Params) (*hdkeychain.ExtendedKey, time.Time, // To automate things with chantools, we also offer reading the seed // from environment variables. - mnemonicStr := strings.TrimSpace(os.Getenv(memonicEnvName)) + mnemonicStr := strings.TrimSpace(os.Getenv(MnemonicEnvName)) // If nothing is set in the environment, read the seed from the // terminal. @@ -70,7 +70,7 @@ func ReadAezeed(params *chaincfg.Params) (*hdkeychain.ExtendedKey, time.Time, // Additionally, the user may have a passphrase, that will also need to // be provided so the daemon can properly decipher the cipher seed. // Try the environment variable first. - passphrase := strings.TrimSpace(os.Getenv(passphraseEnvName)) + passphrase := strings.TrimSpace(os.Getenv(PassphraseEnvName)) // Because we cannot differentiate between an empty and a non-existent // environment variable, we need a special character that indicates that