Repository: bitmaelum/bitmaelum-suite
Pull request #49 (2020-10-10 12:14:53)

Console seed phrase


Patch:
diff --git a/cmd/bm-client/handlers/create_account.go b/cmd/bm-client/handlers/create_account.go
index e9f86c1..4a96b5b 100644
--- a/cmd/bm-client/handlers/create_account.go
+++ b/cmd/bm-client/handlers/create_account.go
@@ -59,7 +59,7 @@ func CreateAccount(vault *vault.Vault, bmAddr, name, token string) {
 
 	fmt.Printf("* Checking if the account is already present in the vault: ")
 	var info *internal.AccountInfo
-	var seedToShow string
+	var mnemonicToShow string
 	if vault.HasAccount(*addr) {
 		fmt.Printf("\n  X account already present in the vault. Strange, but let's continue...\n")
 		info, err = vault.GetAccountInfo(*addr)
@@ -72,13 +72,13 @@ func CreateAccount(vault *vault.Vault, bmAddr, name, token string) {
 		fmt.Printf("not found. This is a good thing.\n")
 
 		fmt.Printf("* Generating your secret key to send and read mail: ")
-		seed, privKey, pubKey, err := bmcrypto.GenerateKeypairWithSeed()
+		mnemonic, privKey, pubKey, err := bmcrypto.GenerateKeypairWithMnemonic()
 		if err != nil {
 			fmt.Print(err)
 			fmt.Println("")
 			os.Exit(1)
 		}
-		seedToShow = seed
+		mnemonicToShow = mnemonic
 		fmt.Printf("done.\n")
 
 		fmt.Printf("* Doing some work to let people know this is not a fake account: ")
@@ -157,7 +157,7 @@ func CreateAccount(vault *vault.Vault, bmAddr, name, token string) {
 	fmt.Printf("\n")
 	fmt.Printf("* All done")
 
-	if len(seedToShow) > 0 {
+	if len(mnemonicToShow) > 0 {
 		fmt.Print(`
 *****************************************************************************
 !IMPORTANT!IMPORTANT!IMPORTANT!IMPORTANT!IMPORTANT!IMPORTANT!IMPORTANT!IMPORT
@@ -168,7 +168,7 @@ If, for any reason, you lose this key, you will need to use the following
 words in order to recreate the key:
 
 `)
-		fmt.Print(internal.WordWrap(seedToShow, 78))
+		fmt.Print(internal.WordWrap(mnemonicToShow, 78))
 		fmt.Print(`
 
 Write these words down and store them in a secure environment. They are the 
diff --git a/cmd/bm-client/handlers/create_organisation.go b/cmd/bm-client/handlers/create_organisation.go
index 66bd529..ee4beab 100644
--- a/cmd/bm-client/handlers/create_organisation.go
+++ b/cmd/bm-client/handlers/create_organisation.go
@@ -38,7 +38,7 @@ func CreateOrganisation(vault *vault.Vault, orgAddr, fullName string, orgValidat
 	}
 	fmt.Printf("not found. This is a good thing.\n")
 
-	var seed string
+	var mnemonic string
 
 	fmt.Printf("* Checking if the organisation is already present in the vault: ")
 	var info *internal.OrganisationInfo
@@ -55,7 +55,7 @@ func CreateOrganisation(vault *vault.Vault, orgAddr, fullName string, orgValidat
 			privKey *bmcrypto.PrivKey
 			pubKey  *bmcrypto.PubKey
 		)
-		seed, privKey, pubKey, err = bmcrypto.GenerateKeypairWithSeed()
+		mnemonic, privKey, pubKey, err = bmcrypto.GenerateKeypairWithMnemonic()
 		if err != nil {
 			fmt.Print(err)
 			fmt.Println("")
@@ -103,7 +103,7 @@ func CreateOrganisation(vault *vault.Vault, orgAddr, fullName string, orgValidat
 	fmt.Printf("\n")
 	fmt.Printf("* All done")
 
-	if len(seed) > 0 {
+	if len(mnemonic) > 0 {
 		fmt.Print(`
 *****************************************************************************
 !IMPORTANT!IMPORTANT!IMPORTANT!IMPORTANT!IMPORTANT!IMPORTANT!IMPORTANT!IMPORT
@@ -114,7 +114,7 @@ If, for any reason, you lose this key, you will need to use the following
 words in order to recreate the key:
 
 `)
-		fmt.Print(internal.WordWrap(seed, 78))
+		fmt.Print(internal.WordWrap(mnemonic, 78))
 		fmt.Print(`
 
 Write these words down and store them in a secure environment. They are the 
diff --git a/cmd/bm-config/cmd/generate_routing_id.go b/cmd/bm-config/cmd/generate_routing_id.go
index b1239a8..91d90b4 100644
--- a/cmd/bm-config/cmd/generate_routing_id.go
+++ b/cmd/bm-config/cmd/generate_routing_id.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/bitmaelum/bitmaelum-suite/internal"
 	"github.com/bitmaelum/bitmaelum-suite/internal/config"
+	"github.com/bitmaelum/bitmaelum-suite/internal/console"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 )
@@ -29,8 +30,18 @@ This command creates a new routing file if one does not exist.`,
 			logrus.Fatalf("Routing file %s already exist. I will not overwrite this file.", config.Server.Server.RoutingFile)
 		}
 
-		// Generate new routing
-		seed, r, err := config.GenerateRouting()
+		var (
+			mnemonic string
+			r        *config.RoutingConfig
+		)
+		if ok, _ := cmd.Flags().GetBool("mnemonic"); ok {
+			// ask for mnemonic
+			mnemonic = console.AskMnemonicPhrase()
+			r, err = config.GenerateRoutingFromMnemonic(mnemonic)
+		} else {
+			// Generate new routing
+			mnemonic, r, err = config.GenerateRouting()
+		}
 		if err != nil {
 			logrus.Fatalf("Error while generating routing file: %v", err)
 		}
@@ -53,7 +64,7 @@ for any reason, you lose this key, you will need to use the following words
 in order to recreate the key:
 
 `)
-		fmt.Print(internal.WordWrap(seed, 78))
+		fmt.Print(internal.WordWrap(mnemonic, 78))
 		fmt.Print(`
 
 Write these words down and store them in a secure environment. They are the 
@@ -64,4 +75,6 @@ ONLY way to recover your private key in case you lose it.
 
 func init() {
 	rootCmd.AddCommand(initRoutingConfigCmd)
+
+	initRoutingConfigCmd.Flags().Bool("mnemonic", false, "Ask for your mnemonic phrase")
 }
diff --git a/go.mod b/go.mod
index e5864c1..6216632 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,9 @@ go 1.13
 
 require (
 	github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee
+	github.com/chzyer/logex v1.1.10 // indirect
+	github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
+	github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
 	github.com/coreos/go-semver v0.3.0
 	github.com/davecgh/go-spew v1.1.1
 	github.com/ernesto-jimenez/httplogger v0.0.0-20150224132909-86cc44f6150a
diff --git a/go.sum b/go.sum
index 18bf45a..78b0c13 100644
--- a/go.sum
+++ b/go.sum
@@ -22,6 +22,12 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
diff --git a/internal/config/routing.go b/internal/config/routing.go
index 5c9251d..9f89c61 100644
--- a/internal/config/routing.go
+++ b/internal/config/routing.go
@@ -58,15 +58,30 @@ func SaveRouting(p string, routing *RoutingConfig) error {
 	return afero.WriteFile(fs, p, data, 0600)
 }
 
+// GenerateRoutingFromMnemonic generates a new routing file from the given seed
+func GenerateRoutingFromMnemonic(mnemonic string) (*RoutingConfig, error) {
+	privKey, pubKey, err := bmcrypto.GenerateKeypairFromMnemonic(mnemonic)
+	if err != nil {
+		return nil, err
+	}
+
+	id := hex.EncodeToString(pubKey.K.(ed25519.PublicKey))
+	return &RoutingConfig{
+		RoutingID:  hash.New(id).String(),
+		PrivateKey: *privKey,
+		PublicKey:  *pubKey,
+	}, nil
+}
+
 // GenerateRouting generates a new routing structure
 func GenerateRouting() (string, *RoutingConfig, error) {
-	seed, privKey, pubKey, err := bmcrypto.GenerateKeypairWithSeed()
+	mnemonic, privKey, pubKey, err := bmcrypto.GenerateKeypairWithMnemonic()
 	if err != nil {
 		return "", nil, err
 	}
 
 	id := hex.EncodeToString(pubKey.K.(ed25519.PublicKey))
-	return seed, &RoutingConfig{
+	return mnemonic, &RoutingConfig{
 		RoutingID:  hash.New(id).String(),
 		PrivateKey: *privKey,
 		PublicKey:  *pubKey,
diff --git a/internal/console/mnemonic_phrase.go b/internal/console/mnemonic_phrase.go
new file mode 100644
index 0000000..af1c8ba
--- /dev/null
+++ b/internal/console/mnemonic_phrase.go
@@ -0,0 +1,18 @@
+package console
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+	"strings"
+)
+
+// AskMnemonicPhrase asks for the mnemonic phrase
+func AskMnemonicPhrase() string {
+	fmt.Print("Please enter your mnemonic words:")
+
+	reader := bufio.NewReader(os.Stdin)
+	mnemonic, _ := reader.ReadString('\n')
+
+	return strings.TrimSpace(strings.ToLower(mnemonic))
+}
diff --git a/internal/console/password.go b/internal/console/password.go
new file mode 100644
index 0000000..57ca325
--- /dev/null
+++ b/internal/console/password.go
@@ -0,0 +1,58 @@
+package console
+
+import (
+	"bytes"
+
+	"github.com/chzyer/readline"
+	"github.com/zalando/go-keyring"
+)
+
+const (
+	service string = "bitmaelum"
+	user    string = "vault"
+)
+
+var (
+	// Readline instance. Override for mocking purposes
+	readliner, _ = readline.NewEx(&readline.Config{
+		DisableAutoSaveHistory: true,
+	})
+	// Override for mocking purposes
+	kr keyring.Keyring
+)
+
+// StorePassword will store the given password into the keychain if possible
+func StorePassword(pwd string) error {
+	if kr != nil {
+		return kr.Set(service, user, pwd)
+	}
+
+	return nil
+}
+
+// AskDoublePassword will ask for a password (and confirmation) on the commandline
+func AskDoublePassword() ([]byte, error) {
+	for {
+		p1, _ := readliner.ReadPassword("Please enter your vault password: ")
+		p2, _ := readliner.ReadPassword("Please retype your vault password: ")
+
+		if bytes.Equal(p1, p2) {
+			return p1, nil
+		}
+
+		_, _ = readliner.Stdout().Write([]byte("Passwords do not match. Please type again.\n"))
+	}
+}
+
+// AskPassword will ask for a password (without confirmation) on the commandline
+func AskPassword() (string, bool) {
+	if kr != nil {
+		pwd, err := kr.Get(service, user)
+		if err == nil {
+			return pwd, true
+		}
+	}
+
+	b, _ := readliner.ReadPassword("Please enter your vault password: ")
+	return string(b), false
+}
diff --git a/internal/password/password.go b/internal/password/password.go
deleted file mode 100644
index 05e64bc..0000000
--- a/internal/password/password.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package password
-
-import (
-	"bytes"
-	"fmt"
-	"os"
-
-	"github.com/zalando/go-keyring"
-	"golang.org/x/crypto/ssh/terminal"
-)
-
-const (
-	service string = "bitmaelum"
-	user    string = "vault"
-)
-
-// StorePassword will store the given password into the keychain if possible
-func StorePassword(pwd string) error {
-	return keyring.Set(service, user, pwd)
-}
-
-// AskDoublePassword will ask for a password (and confirmation) on the commandline
-func AskDoublePassword() []byte {
-	for {
-		fmt.Printf("Please enter your vault password: ")
-		p1, _ := terminal.ReadPassword(int(os.Stdin.Fd()))
-		fmt.Printf("\n")
-
-		fmt.Printf("Please retype your vault password: ")
-		p2, _ := terminal.ReadPassword(int(os.Stdin.Fd()))
-		fmt.Printf("\n")
-
-		if bytes.Equal(p1, p2) {
-			return p1
-		}
-
-		fmt.Printf("Passwords do not match. Please type again.\n")
-	}
-}
-
-// AskPassword will ask for a password (without confirmation) on the commandline
-func AskPassword() (string, bool) {
-	pwd, err := keyring.Get(service, user)
-	if err == nil {
-		return pwd, true
-	}
-
-	fmt.Printf("Please enter your vault password: ")
-	b, _ := terminal.ReadPassword(int(os.Stdin.Fd()))
-	fmt.Printf("\n")
-
-	return string(b), false
-}
diff --git a/internal/vault/vault.go b/internal/vault/vault.go
index fc5847c..8e2adaf 100644
--- a/internal/vault/vault.go
+++ b/internal/vault/vault.go
@@ -17,7 +17,7 @@ import (
 
 	"github.com/bitmaelum/bitmaelum-suite/internal"
 	"github.com/bitmaelum/bitmaelum-suite/internal/config"
-	"github.com/bitmaelum/bitmaelum-suite/internal/password"
+	"github.com/bitmaelum/bitmaelum-suite/internal/console"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/address"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/afero"
@@ -321,7 +321,7 @@ func OpenVault() *Vault {
 	fromVault := false
 
 	if VaultPassword == "" {
-		VaultPassword, fromVault = password.AskPassword()
+		VaultPassword, fromVault = console.AskPassword()
 	}
 
 	// Unlock vault
@@ -334,7 +334,7 @@ func OpenVault() *Vault {
 
 	// If the password was correct and not already read from the vault, store it in the vault
 	if !fromVault {
-		_ = password.StorePassword(VaultPassword)
+		_ = console.StorePassword(VaultPassword)
 	}
 
 	return v
diff --git a/pkg/bmcrypto/encrypt.go b/pkg/bmcrypto/encrypt.go
index 51a4e4a..d7032f8 100644
--- a/pkg/bmcrypto/encrypt.go
+++ b/pkg/bmcrypto/encrypt.go
@@ -18,7 +18,7 @@ func Encrypt(pubKey PubKey, message []byte) ([]byte, string, string, error) {
 	case KeyTypeRSA:
 		encryptedMessage, err := encryptRsa(pubKey.K.(*rsa.PublicKey), message)
 		return encryptedMessage, "", "rsa+aes256gcm", err
-  	// TODO: Implement KeyTypeECDSA
+		// TODO: Implement KeyTypeECDSA
 	case KeyTypeED25519:
 		return encryptED25519(pubKey, message)
 	}
diff --git a/pkg/bmcrypto/seed.go b/pkg/bmcrypto/mnemonic.go
similarity index 65%
rename from pkg/bmcrypto/seed.go
rename to pkg/bmcrypto/mnemonic.go
index e17b9b5..cade506 100644
--- a/pkg/bmcrypto/seed.go
+++ b/pkg/bmcrypto/mnemonic.go
@@ -9,9 +9,9 @@ import (
 	"golang.org/x/crypto/hkdf"
 )
 
-// GenerateKeypairFromSeed generates a keypair based on the given seed
-func GenerateKeypairFromSeed(seed string) (*PrivKey, *PubKey, error) {
-	e, err := bip39.MnemonicToByteArray(seed, true)
+// GenerateKeypairFromMnemonic generates a keypair based on the given mnemonic
+func GenerateKeypairFromMnemonic(mnemonic string) (*PrivKey, *PubKey, error) {
+	e, err := bip39.MnemonicToByteArray(mnemonic, true)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -19,16 +19,16 @@ func GenerateKeypairFromSeed(seed string) (*PrivKey, *PubKey, error) {
 	return genKey(e)
 }
 
-// GenerateKeypairWithSeed generates a seed, and a keypair that can be generated through the same seed again.
-func GenerateKeypairWithSeed() (string, *PrivKey, *PubKey, error) {
+// GenerateKeypairWithMnemonic generates a mnemonic, and a keypair that can be generated through the same mnemonic again.
+func GenerateKeypairWithMnemonic() (string, *PrivKey, *PubKey, error) {
 	// Generate large enough random string
 	e, err := bip39.NewEntropy(192)
 	if err != nil {
 		return "", nil, nil, err
 	}
 
-	// Generate seed words
-	seed, err := bip39.NewMnemonic(e)
+	// Generate Mnemonic words
+	mnemonic, err := bip39.NewMnemonic(e)
 	if err != nil {
 		return "", nil, nil, err
 	}
@@ -38,7 +38,7 @@ func GenerateKeypairWithSeed() (string, *PrivKey, *PubKey, error) {
 		return "", nil, nil, err
 	}
 
-	return seed, privKey, pubKey, nil
+	return mnemonic, privKey, pubKey, nil
 }
 
 func genKey(e []byte) (*PrivKey, *PubKey, error) {
diff --git a/tools/resolve/main.go b/tools/resolve/main.go
index 77b46ba..b0f55c5 100644
--- a/tools/resolve/main.go
+++ b/tools/resolve/main.go
@@ -6,6 +6,7 @@ import (
 	"github.com/bitmaelum/bitmaelum-suite/internal"
 	"github.com/bitmaelum/bitmaelum-suite/internal/config"
 	"github.com/bitmaelum/bitmaelum-suite/internal/container"
+	"github.com/bitmaelum/bitmaelum-suite/pkg/address"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/hash"
 	"github.com/sirupsen/logrus"
 )
@@ -35,7 +36,11 @@ func main() {
 }
 
 func resolveAddress(addr string) {
-	addrHash := hash.New(addr)
+	a, err := address.NewAddress(addr)
+	if err != nil {
+		logrus.Fatal(err)
+	}
+	addrHash := a.Hash()
 
 	svc := container.GetResolveService()
 	info, err := svc.ResolveAddress(addrHash)
@@ -43,10 +48,10 @@ func resolveAddress(addr string) {
 		logrus.Fatal(err)
 	}
 
-	fmt.Printf("BitMaelum address '%s'", addr)
-	fmt.Printf("Hash: %s\n", info.Hash)
-	fmt.Printf("RoutingID: %s\n", info.RoutingID)
-	fmt.Printf("Public key:\n%s\n", info.PublicKey.String())
+	fmt.Printf("Address   : %s\n", addr)
+	fmt.Printf("Hash      : %s\n", info.Hash)
+	fmt.Printf("RoutingID : %s\n", info.RoutingID)
+	fmt.Printf("Pub key   : %s\n", info.PublicKey.String())
 }
 
 func resolveRouting(routingID string) {
@@ -56,10 +61,9 @@ func resolveRouting(routingID string) {
 		logrus.Fatal(err)
 	}
 
-	fmt.Printf("BitMaelum routing '%s'\n", routingID)
-	fmt.Printf("Hash: %s\n", info.Hash)
-	fmt.Printf("Public key:\n%s\n", info.PublicKey.String())
-	fmt.Printf("Routing:\n%s\n", info.Routing)
+	fmt.Printf("Routing ID : %s\n", routingID)
+	fmt.Printf("Pub key    : %s\n", info.PublicKey.String())
+	fmt.Printf("Routing    : %s\n", info.Routing)
 }
 
 func resolveOrganisation(orgAddr string) {
@@ -71,7 +75,7 @@ func resolveOrganisation(orgAddr string) {
 		logrus.Fatal(err)
 	}
 
-	fmt.Printf("BitMaelum organisation '%s'\n", orgAddr)
-	fmt.Printf("Hash: %s\n", info.Hash)
-	fmt.Printf("Public key:\n%s\n", info.PublicKey.String())
+	fmt.Printf("Org Name : %s\n", orgAddr)
+	fmt.Printf("Hash     : %s\n", info.Hash)
+	fmt.Printf("Pub key  : %s\n", info.PublicKey.String())
 }


Test patch:
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 21ce76c..1c46d76 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -86,6 +86,15 @@ func TestLoadClientConfig(t *testing.T) {
 	assert.Error(t, err)
 }
 
+func TestGenerateRoutingFromMnemonic(t *testing.T) {
+	r, err := GenerateRoutingFromMnemonic("cluster puppy wash ceiling skate search great angry drift rose undo fragile boring fence stumble shuffle cable praise")
+	assert.NoError(t, err)
+
+	assert.Equal(t, "f5f1dc4eff7237ac0e061a9e8982b7b913fc479138189cc8d6ba5131dee1bde9", r.RoutingID)
+	assert.Equal(t, "ed25519 MC4CAQAwBQYDK2VwBCIEIDLOvf5iUAPWeNIYlbyDffgv+VA2xnS1s1mUYIOmW8XK", r.PrivateKey.String())
+	assert.Equal(t, "ed25519 MCowBQYDK2VwAyEAndS2/G3uasbaYO0+89rNzvNJ3gfOi/An1t5xvETeNoc=", r.PublicKey.String())
+}
+
 func init() {
 	// Setup mock
 	_, hook = test.NewNullLogger()
diff --git a/internal/console/password_test.go b/internal/console/password_test.go
new file mode 100644
index 0000000..2099bf6
--- /dev/null
+++ b/internal/console/password_test.go
@@ -0,0 +1,136 @@
+package console
+
+import (
+	"bytes"
+	"errors"
+	"testing"
+
+	"github.com/chzyer/readline"
+	"github.com/stretchr/testify/assert"
+)
+
+type MockBuffer struct {
+	Out string
+	In  []string
+}
+
+func (cb *MockBuffer) Read(p []byte) (n int, err error) {
+	buf := bytes.NewBufferString(cb.In[0])
+	n, err = buf.Read(p)
+
+	if len(cb.In) > 1 {
+		cb.In = cb.In[1:]
+	}
+
+	return
+}
+
+func (cb *MockBuffer) Write(p []byte) (n int, err error) {
+	cb.Out += string(p)
+
+	return len(p), nil
+}
+
+func (cb *MockBuffer) Close() error {
+	return nil
+}
+
+func TestAskDoublePassword(t *testing.T) {
+	mb := &MockBuffer{In: []string{"secret\n", "secret\n"}}
+	readliner, _ = readline.NewEx(&readline.Config{
+		Stdin:  mb,
+		Stdout: mb,
+		Stderr: mb,
+	})
+	defer func() {
+		_ = readliner.Close()
+	}()
+
+	b, err := AskDoublePassword()
+	assert.NoError(t, err)
+	assert.Equal(t, "secret", string(b))
+	// @TODO: Somehow on linux, the prompts are send to mb.Out. It works on windows though
+	// assert.Contains(t, mb.Out, "Please enter your vault password")
+	// assert.Contains(t, mb.Out, "Please retype your vault password")
+
+	mb = &MockBuffer{In: []string{"secret\n", "false\n", "secret\n", "secret\n"}}
+	readliner, _ = readline.NewEx(&readline.Config{
+		Stdin:  mb,
+		Stdout: mb,
+		Stderr: mb,
+	})
+	defer func() {
+		_ = readliner.Close()
+	}()
+
+	b, err = AskDoublePassword()
+	assert.NoError(t, err)
+	assert.Equal(t, "secret", string(b))
+	// @TODO: Somehow on linux, the prompts are send to mb.Out. It works on windows though
+	// assert.Contains(t, mb.Out, "Please enter your vault password")
+	// assert.Contains(t, mb.Out, "Please retype your vault password")
+	assert.Contains(t, mb.Out, "Passwords do not match. Please type again.")
+}
+
+type mockKeyring struct {
+	p map[string]string
+}
+
+func (m *mockKeyring) Set(service, user, password string) error {
+	m.p[service+":"+user] = password
+	return nil
+}
+
+func (m *mockKeyring) Get(service, user string) (string, error) {
+	v, ok := m.p[service+":"+user]
+	if !ok {
+		return "", errors.New("not found")
+	}
+
+	return v, nil
+}
+
+func (m *mockKeyring) Delete(service, user string) error {
+	delete(m.p, service+":"+user)
+	return nil
+}
+
+func TestAskPassword(t *testing.T) {
+	mb := &MockBuffer{In: []string{"secret\n"}}
+	readliner, _ = readline.NewEx(&readline.Config{
+		Stdin:  mb,
+		Stdout: mb,
+		Stderr: mb,
+	})
+
+	kr = nil
+	s, ok := AskPassword()
+	assert.Equal(t, "secret", s)
+	assert.False(t, ok)
+
+	kr = &mockKeyring{
+		p: make(map[string]string),
+	}
+	_ = kr.Set(service, user, "stored-in-keyring")
+
+	mb = &MockBuffer{In: []string{"not-stored\n"}}
+	readliner, _ = readline.NewEx(&readline.Config{
+		Stdin:  mb,
+		Stdout: mb,
+		Stderr: mb,
+	})
+
+	s, ok = AskPassword()
+	assert.Equal(t, "stored-in-keyring", s)
+	assert.True(t, ok)
+
+	_ = kr.Delete(service, user)
+	s, ok = AskPassword()
+	assert.Equal(t, "not-stored", s)
+	assert.False(t, ok)
+
+	_ = StorePassword("foobar")
+	s, ok = AskPassword()
+	assert.Equal(t, "foobar", s)
+	assert.True(t, ok)
+}
diff --git a/internal/vault/vault_test.go b/internal/vault/vault_test.go
index 6a037b2..1545980 100644
--- a/internal/vault/vault_test.go
+++ b/internal/vault/vault_test.go
@@ -86,3 +86,31 @@ func Test_FindShortRoutingId(t *testing.T) {
 	assert.Equal(t, "", v.FindShortRoutingID("12345"))
 	assert.Equal(t, "", v.FindShortRoutingID("1"))
 }
+
+func TestVault_ChangePassword(t *testing.T) {
+	v, _ := New("", []byte("foobar"))
+	assert.Equal(t, []byte("foobar"), v.password)
+
+	v.ChangePassword("secret")
+	assert.Equal(t, []byte("secret"), v.password)
+}
+
+func TestGetAccountOrDefault(t *testing.T) {
+	var acc *internal.AccountInfo
+
+	v, _ := New("", []byte{})
+	acc = &internal.AccountInfo{Address: "example1!", RoutingID: "123456780000"}
+	v.AddAccount(*acc)
+	acc = &internal.AccountInfo{Address: "example2!", RoutingID: "123456780001"}
+	v.AddAccount(*acc)
+	acc = &internal.AccountInfo{Address: "example3!", RoutingID: "123456780002", Default: true}
+	v.AddAccount(*acc)
+	acc = &internal.AccountInfo{Address: "example4!", RoutingID: "154353535335"}
+	v.AddAccount(*acc)
+
+	acc = GetAccountOrDefault(v, "")
+	assert.Equal(t, "example3!", acc.Address)
+
+	acc = GetAccountOrDefault(v, "example2!")
+	assert.Equal(t, "example2!", acc.Address)
+}
diff --git a/pkg/bmcrypto/seed_test.go b/pkg/bmcrypto/mnemonic_test.go
similarity index 54%
rename from pkg/bmcrypto/seed_test.go
rename to pkg/bmcrypto/mnemonic_test.go
index 9169ace..4624d3b 100644
--- a/pkg/bmcrypto/seed_test.go
+++ b/pkg/bmcrypto/mnemonic_test.go
@@ -6,11 +6,11 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestGenerateKeypairFromSeed(t *testing.T) {
-	s, priv1, pub1, err := GenerateKeypairWithSeed()
+func TestGenerateKeypairFromMnemonic(t *testing.T) {
+	s, priv1, pub1, err := GenerateKeypairWithMnemonic()
 	assert.NoError(t, err)
 
-	priv2, pub2, err := GenerateKeypairFromSeed(s)
+	priv2, pub2, err := GenerateKeypairFromMnemonic(s)
 	assert.NoError(t, err)
 
 	assert.Equal(t, priv1, priv2)