mirror of
https://github.com/guggero/chantools.git
synced 2026-08-18 13:09:17 +02:00
multi: add unit tests
This commit is contained in:
parent
e6fcb580a3
commit
fa62a57e95
25 changed files with 629 additions and 174 deletions
135
btc/bip39.go
135
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:
|
||||
|
|
|
|||
37
cmd/chantools/chanbackup_test.go
Normal file
37
cmd/chantools/chanbackup_test.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
46
cmd/chantools/compactdb_test.go
Normal file
46
cmd/chantools/compactdb_test.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
91
cmd/chantools/derivekey_test.go
Normal file
91
cmd/chantools/derivekey_test.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
4
cmd/chantools/dumpbackup_test.go
Normal file
4
cmd/chantools/dumpbackup_test.go
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
package main
|
||||
|
||||
// This file is empty for now, the dumpbackup command is covered by the test in
|
||||
// chanbackup_test.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
|
||||
}
|
||||
|
|
|
|||
4
cmd/chantools/dumpchannels_test.go
Normal file
4
cmd/chantools/dumpchannels_test.go
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
package main
|
||||
|
||||
// This file is empty for now, the dumpchannels command is covered by the test
|
||||
// in compactdb_test.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(
|
||||
|
|
|
|||
|
|
@ -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 "+
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
117
cmd/chantools/root_test.go
Normal file
117
cmd/chantools/root_test.go
Normal file
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
67
cmd/chantools/showrootkey_test.go
Normal file
67
cmd/chantools/showrootkey_test.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
BIN
cmd/chantools/testdata/channel.db
vendored
Normal file
BIN
cmd/chantools/testdata/channel.db
vendored
Normal file
Binary file not shown.
BIN
cmd/chantools/testdata/wallet.db
vendored
Normal file
BIN
cmd/chantools/testdata/wallet.db
vendored
Normal file
Binary file not shown.
|
|
@ -51,7 +51,7 @@ phone]
|
|||
</pre>
|
||||
`,
|
||||
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 "+
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
32
cmd/chantools/walletinfo_test.go
Normal file
32
cmd/chantools/walletinfo_test.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
1
go.mod
1
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
|
||||
)
|
||||
|
|
|
|||
2
go.sum
2
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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue