From c630f2c9999a1310807ef4756b048a84bfb9da24 Mon Sep 17 00:00:00 2001 From: Bartek Nowotarski Date: Fri, 22 Sep 2017 01:26:24 +0200 Subject: [PATCH 01/11] stellar-hd-wallet --- glide.lock | 3 + glide.yaml | 1 + tools/stellar-hd-wallet/commands/accounts.go | 66 ++++++++++++++++++++ tools/stellar-hd-wallet/commands/io.go | 25 ++++++++ tools/stellar-hd-wallet/commands/new.go | 59 +++++++++++++++++ tools/stellar-hd-wallet/derive/main.go | 62 ++++++++++++++++++ tools/stellar-hd-wallet/main.go | 24 +++++++ 7 files changed, 240 insertions(+) create mode 100644 tools/stellar-hd-wallet/commands/accounts.go create mode 100644 tools/stellar-hd-wallet/commands/io.go create mode 100644 tools/stellar-hd-wallet/commands/new.go create mode 100644 tools/stellar-hd-wallet/derive/main.go create mode 100644 tools/stellar-hd-wallet/main.go diff --git a/glide.lock b/glide.lock index 37c1e77297..fbd1cf6ae5 100644 --- a/glide.lock +++ b/glide.lock @@ -349,6 +349,8 @@ imports: - suite - name: github.com/tyler-smith/go-bip32 version: 2c9cfd17756470a0b7c3e4b7954bae7d11035504 +- name: github.com/tyler-smith/go-bip39 + version: 8e7a99b3e716f36d3b080a9a70f9eb45abe4edcc - name: github.com/tylerb/graceful version: 7116c7a8115899e80197cd9e0b97998c0f97ed8e repo: https://github.com/tylerb/graceful @@ -403,6 +405,7 @@ imports: version: 1f22c0103821b9390939b6776727195525381532 repo: https://go.googlesource.com/crypto subpackages: + - pbkdf2 - ripemd160 - ssh/terminal - name: golang.org/x/net diff --git a/glide.yaml b/glide.yaml index 434e6335dc..218334599f 100644 --- a/glide.yaml +++ b/glide.yaml @@ -295,3 +295,4 @@ import: subpackages: - socks - package: github.com/btcsuite/websocket +- package: github.com/tyler-smith/go-bip39 diff --git a/tools/stellar-hd-wallet/commands/accounts.go b/tools/stellar-hd-wallet/commands/accounts.go new file mode 100644 index 0000000000..654b3dec5f --- /dev/null +++ b/tools/stellar-hd-wallet/commands/accounts.go @@ -0,0 +1,66 @@ +package commands + +import ( + "fmt" + "log" + "regexp" + "strings" + + "github.com/spf13/cobra" + "github.com/stellar/go/tools/stellar-hd-wallet/derive" + "github.com/tyler-smith/go-bip32" + "github.com/tyler-smith/go-bip39" +) + +var wordsRegexp = regexp.MustCompile(`^[a-z]+$`) +var count, startID uint32 + +var AccountsCmd = &cobra.Command{ + Use: "accounts", + Short: "Display accounts for a given mnemonic code", + Long: "", + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("How many words? ") + wordsCount := readUint() + if wordsCount < 12 { + log.Fatal("Invalid value (min 12)") + } + + words := make([]string, wordsCount) + for i := uint32(0); i < wordsCount; i++ { + fmt.Printf("Enter word #%-4d", i+1) + words[i] = readString() + if !wordsRegexp.MatchString(words[i]) { + fmt.Println("Invalid word, try again.") + i-- + } + } + + fmt.Printf("Enter password (leave empty if none): ") + password := readString() + + mnemonic := strings.Join(words, " ") + fmt.Println("Mnemonic:", mnemonic) + + seed := bip39.NewSeed(mnemonic, password) + masterKey, err := bip32.NewMasterKey(seed) + if err != nil { + log.Fatal(err) + } + + paths, keypairs, err := derive.GetKeyPairs(masterKey, startID, count) + if err != nil { + log.Fatal(err) + } + + fmt.Println("") + for i, keypair := range keypairs { + fmt.Printf("%s %s %s\n", paths[i], keypair.Address(), keypair.Seed()) + } + }, +} + +func init() { + AccountsCmd.Flags().Uint32VarP(&count, "count", "c", 10, "number of accounts to display") + AccountsCmd.Flags().Uint32VarP(&startID, "start", "s", 0, "ID of the first wallet to display") +} diff --git a/tools/stellar-hd-wallet/commands/io.go b/tools/stellar-hd-wallet/commands/io.go new file mode 100644 index 0000000000..2b88e73e99 --- /dev/null +++ b/tools/stellar-hd-wallet/commands/io.go @@ -0,0 +1,25 @@ +package commands + +import ( + "bufio" + "log" + "os" + "strconv" + "strings" +) + +func readString() string { + reader := bufio.NewReader(os.Stdin) + line, _ := reader.ReadString('\n') + return strings.TrimRight(line, "\n") +} + +func readUint() uint32 { + line := readString() + number, err := strconv.Atoi(line) + if err != nil { + log.Fatal("Invalid value") + } + + return uint32(number) +} diff --git a/tools/stellar-hd-wallet/commands/new.go b/tools/stellar-hd-wallet/commands/new.go new file mode 100644 index 0000000000..00df089c8d --- /dev/null +++ b/tools/stellar-hd-wallet/commands/new.go @@ -0,0 +1,59 @@ +package commands + +import ( + "fmt" + "log" + "strings" + + "github.com/spf13/cobra" + "github.com/tyler-smith/go-bip39" +) + +var wordsCount uint32 + +var NewCmd = &cobra.Command{ + Use: "new", + Short: "Generates a new mnemonic code", + Long: "", + Run: func(cmd *cobra.Command, args []string) { + var entSize int + switch wordsCount { + case 12: + entSize = 128 + case 15: + entSize = 160 + case 18: + entSize = 192 + case 21: + entSize = 224 + case 24: + entSize = 256 + default: + log.Fatal("`words` param invalid") + } + + entropy, err := bip39.NewEntropy(entSize) + if err != nil { + log.Fatal(err) + } + + mnemonic, err := bip39.NewMnemonic(entropy) + if err != nil { + log.Fatal(err) + } + + words := strings.Split(mnemonic, " ") + for i := uint32(0); i < wordsCount; i++ { + fmt.Printf("#%-5d %10s", i+1, words[i]) + readString() + } + + fmt.Println("WARNING! Store the words above in a save place!") + fmt.Println("WARNING! If you lose your words, you will lose access to funds in all derived accounts!") + fmt.Println("WARNING! Anyone who has access to these words can spend your funds!") + }, +} + +func init() { + NewCmd.Flags().Uint32VarP(&wordsCount, "words", "w", 24, "number of words to generate") +} diff --git a/tools/stellar-hd-wallet/derive/main.go b/tools/stellar-hd-wallet/derive/main.go new file mode 100644 index 0000000000..2e08724394 --- /dev/null +++ b/tools/stellar-hd-wallet/derive/main.go @@ -0,0 +1,62 @@ +package derive + +import ( + "fmt" + + "github.com/stellar/go/keypair" + "github.com/tyler-smith/go-bip32" +) + +const StellarCoinType = 148 + +// GetKeyPairs generate key pairs using BIP-44 derivation for Stellar Lumens. +// Returns BIP-44 derivation path for each key pair, key pairs and error. +func GetKeyPairs(rootKey *bip32.Key, startID, count uint32) ([]string, []*keypair.Full, error) { + currentKey, err := rootKey.NewChildKey(bip32.FirstHardenedChild + 44) + if err != nil { + return nil, nil, err + } + + derivationPath := []uint32{ + bip32.FirstHardenedChild + StellarCoinType, + bip32.FirstHardenedChild + 0, + 0, + } + + pathString := "m/44'" + for len(derivationPath) > 0 { + if derivationPath[0] >= bip32.FirstHardenedChild { + pathString += fmt.Sprintf("/%d'", derivationPath[0]-bip32.FirstHardenedChild) + } else { + pathString += fmt.Sprintf("/%d", derivationPath[0]) + } + currentKey, err = currentKey.NewChildKey(derivationPath[0]) + if err != nil { + return nil, nil, err + } + derivationPath = derivationPath[1:] + } + + paths := make([]string, count) + keypairs := make([]*keypair.Full, count) + currentID := startID + for i := uint32(0); i < count; i++ { + address, err := currentKey.NewChildKey(currentID) + if err != nil { + return nil, nil, err + } + + var seed [32]byte + copy(seed[:], address.Key[:]) + + paths[i] = fmt.Sprintf("%s/%d", pathString, currentID) + keypairs[i], err = keypair.FromRawSeed(seed) + if err != nil { + return nil, nil, err + } + + currentID++ + } + + return paths, keypairs, nil +} diff --git a/tools/stellar-hd-wallet/main.go b/tools/stellar-hd-wallet/main.go new file mode 100644 index 0000000000..1fa8c91215 --- /dev/null +++ b/tools/stellar-hd-wallet/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "log" + + "github.com/spf13/cobra" + "github.com/stellar/go/tools/stellar-hd-wallet/commands" +) + +var mainCmd = &cobra.Command{ + Use: "stellar-hd-wallet", + Short: "Simple HD wallet for Stellar Lumens", +} + +func init() { + mainCmd.AddCommand(commands.NewCmd) + mainCmd.AddCommand(commands.AccountsCmd) +} + +func main() { + if err := mainCmd.Execute(); err != nil { + log.Fatal(err) + } +} From 867e92fcc8f1d26e1f9d060be400f116e799b4d2 Mon Sep 17 00:00:00 2001 From: Bartek Nowotarski Date: Thu, 9 Nov 2017 15:04:58 +0100 Subject: [PATCH 02/11] Use ed25519 key derivation --- tools/stellar-hd-wallet/commands/accounts.go | 29 ++++++--- tools/stellar-hd-wallet/derive/main.go | 62 -------------------- 2 files changed, 20 insertions(+), 71 deletions(-) delete mode 100644 tools/stellar-hd-wallet/derive/main.go diff --git a/tools/stellar-hd-wallet/commands/accounts.go b/tools/stellar-hd-wallet/commands/accounts.go index 654b3dec5f..6f339f7d38 100644 --- a/tools/stellar-hd-wallet/commands/accounts.go +++ b/tools/stellar-hd-wallet/commands/accounts.go @@ -1,14 +1,15 @@ package commands import ( + "encoding/hex" "fmt" "log" "regexp" "strings" "github.com/spf13/cobra" - "github.com/stellar/go/tools/stellar-hd-wallet/derive" - "github.com/tyler-smith/go-bip32" + "github.com/stellar/go/exp/crypto/derivation" + "github.com/stellar/go/keypair" "github.com/tyler-smith/go-bip39" ) @@ -43,19 +44,29 @@ var AccountsCmd = &cobra.Command{ fmt.Println("Mnemonic:", mnemonic) seed := bip39.NewSeed(mnemonic, password) - masterKey, err := bip32.NewMasterKey(seed) - if err != nil { - log.Fatal(err) - } + fmt.Println("BIP39 Seed:", hex.EncodeToString(seed)) - paths, keypairs, err := derive.GetKeyPairs(masterKey, startID, count) + masterKey, err := derivation.DeriveForPath(derivation.StellarAccountPrefix, seed) if err != nil { log.Fatal(err) } + fmt.Println("m/44'/148' key:", hex.EncodeToString(masterKey.Key)) + fmt.Println("") - for i, keypair := range keypairs { - fmt.Printf("%s %s %s\n", paths[i], keypair.Address(), keypair.Seed()) + + for i := uint32(startID); i < startID+count; i++ { + key, err := masterKey.Derive(derivation.FirstHardenedIndex + i) + if err != nil { + log.Fatal(err) + } + + kp, err := keypair.FromRawSeed(key.RawSeed()) + if err != nil { + log.Fatal(err) + } + + fmt.Println(fmt.Sprintf(derivation.StellarAccountPathFormat, i), kp.Address(), kp.Seed()) } }, } diff --git a/tools/stellar-hd-wallet/derive/main.go b/tools/stellar-hd-wallet/derive/main.go deleted file mode 100644 index 2e08724394..0000000000 --- a/tools/stellar-hd-wallet/derive/main.go +++ /dev/null @@ -1,62 +0,0 @@ -package derive - -import ( - "fmt" - - "github.com/stellar/go/keypair" - "github.com/tyler-smith/go-bip32" -) - -const StellarCoinType = 148 - -// GetKeyPairs generate key pairs using BIP-44 derivation for Stellar Lumens. -// Returns BIP-44 derivation path for each key pair, key pairs and error. -func GetKeyPairs(rootKey *bip32.Key, startID, count uint32) ([]string, []*keypair.Full, error) { - currentKey, err := rootKey.NewChildKey(bip32.FirstHardenedChild + 44) - if err != nil { - return nil, nil, err - } - - derivationPath := []uint32{ - bip32.FirstHardenedChild + StellarCoinType, - bip32.FirstHardenedChild + 0, - 0, - } - - pathString := "m/44'" - for len(derivationPath) > 0 { - if derivationPath[0] >= bip32.FirstHardenedChild { - pathString += fmt.Sprintf("/%d'", derivationPath[0]-bip32.FirstHardenedChild) - } else { - pathString += fmt.Sprintf("/%d", derivationPath[0]) - } - currentKey, err = currentKey.NewChildKey(derivationPath[0]) - if err != nil { - return nil, nil, err - } - derivationPath = derivationPath[1:] - } - - paths := make([]string, count) - keypairs := make([]*keypair.Full, count) - currentID := startID - for i := uint32(0); i < count; i++ { - address, err := currentKey.NewChildKey(currentID) - if err != nil { - return nil, nil, err - } - - var seed [32]byte - copy(seed[:], address.Key[:]) - - paths[i] = fmt.Sprintf("%s/%d", pathString, currentID) - keypairs[i], err = keypair.FromRawSeed(seed) - if err != nil { - return nil, nil, err - } - - currentID++ - } - - return paths, keypairs, nil -} From a3c50f11e45ca35d43f0e1bc7a7f35e6839cb9c8 Mon Sep 17 00:00:00 2001 From: Bartek Nowotarski Date: Fri, 10 Nov 2017 13:15:40 +0100 Subject: [PATCH 03/11] Use 256 bits of entropy --- tools/stellar-hd-wallet/commands/new.go | 28 ++++--------------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/tools/stellar-hd-wallet/commands/new.go b/tools/stellar-hd-wallet/commands/new.go index 00df089c8d..cb5c590303 100644 --- a/tools/stellar-hd-wallet/commands/new.go +++ b/tools/stellar-hd-wallet/commands/new.go @@ -9,30 +9,12 @@ import ( "github.com/tyler-smith/go-bip39" ) -var wordsCount uint32 - var NewCmd = &cobra.Command{ Use: "new", Short: "Generates a new mnemonic code", Long: "", Run: func(cmd *cobra.Command, args []string) { - var entSize int - switch wordsCount { - case 12: - entSize = 128 - case 15: - entSize = 160 - case 18: - entSize = 192 - case 21: - entSize = 224 - case 24: - entSize = 256 - default: - log.Fatal("`words` param invalid") - } - - entropy, err := bip39.NewEntropy(entSize) + entropy, err := bip39.NewEntropy(256) if err != nil { log.Fatal(err) } @@ -43,7 +25,7 @@ var NewCmd = &cobra.Command{ } words := strings.Split(mnemonic, " ") - for i := uint32(0); i < wordsCount; i++ { + for i := 0; i < len(words); i++ { fmt.Printf("#%-5d %10s", i+1, words[i]) readString() } @@ -51,9 +33,7 @@ var NewCmd = &cobra.Command{ fmt.Println("WARNING! Store the words above in a save place!") fmt.Println("WARNING! If you lose your words, you will lose access to funds in all derived accounts!") fmt.Println("WARNING! Anyone who has access to these words can spend your funds!") + fmt.Println("") + fmt.Println("Use: `stellar-hd-wallet accounts` command to see generated accounts.") }, } - -func init() { - NewCmd.Flags().Uint32VarP(&wordsCount, "words", "w", 24, "number of words to generate") -} From 65b6aca5e9ffd31093ec2940b3c1c8bb366b03f1 Mon Sep 17 00:00:00 2001 From: Bartek Nowotarski Date: Fri, 10 Nov 2017 19:19:56 +0100 Subject: [PATCH 04/11] Updates --- tools/stellar-hd-wallet/commands/accounts.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tools/stellar-hd-wallet/commands/accounts.go b/tools/stellar-hd-wallet/commands/accounts.go index 6f339f7d38..3529df63fa 100644 --- a/tools/stellar-hd-wallet/commands/accounts.go +++ b/tools/stellar-hd-wallet/commands/accounts.go @@ -16,6 +16,8 @@ import ( var wordsRegexp = regexp.MustCompile(`^[a-z]+$`) var count, startID uint32 +var allowedNumbers = map[uint32]bool{12: true, 15: true, 18: true, 21: true, 24: true} + var AccountsCmd = &cobra.Command{ Use: "accounts", Short: "Display accounts for a given mnemonic code", @@ -23,8 +25,8 @@ var AccountsCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { fmt.Printf("How many words? ") wordsCount := readUint() - if wordsCount < 12 { - log.Fatal("Invalid value (min 12)") + if _, exist := allowedNumbers[wordsCount]; !exist { + log.Fatal("Invalid value, allowed values: 12, 15, 18, 21, 24") } words := make([]string, wordsCount) @@ -43,7 +45,11 @@ var AccountsCmd = &cobra.Command{ mnemonic := strings.Join(words, " ") fmt.Println("Mnemonic:", mnemonic) - seed := bip39.NewSeed(mnemonic, password) + seed, err := bip39.NewSeedWithErrorChecking(mnemonic, password) + if err != nil { + log.Fatal("Mnemonic or checksum invalid: ", err) + } + fmt.Println("BIP39 Seed:", hex.EncodeToString(seed)) masterKey, err := derivation.DeriveForPath(derivation.StellarAccountPrefix, seed) From 492ae54c1f7f06c9aef42b4c96169f6d138088ae Mon Sep 17 00:00:00 2001 From: Bartek Nowotarski Date: Tue, 14 Nov 2017 16:24:30 +0100 Subject: [PATCH 05/11] Changes --- tools/stellar-hd-wallet/commands/accounts.go | 2 +- tools/stellar-hd-wallet/commands/new.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/stellar-hd-wallet/commands/accounts.go b/tools/stellar-hd-wallet/commands/accounts.go index 3529df63fa..d34f5840d0 100644 --- a/tools/stellar-hd-wallet/commands/accounts.go +++ b/tools/stellar-hd-wallet/commands/accounts.go @@ -47,7 +47,7 @@ var AccountsCmd = &cobra.Command{ seed, err := bip39.NewSeedWithErrorChecking(mnemonic, password) if err != nil { - log.Fatal("Mnemonic or checksum invalid: ", err) + log.Fatal("Invalid words or checksum") } fmt.Println("BIP39 Seed:", hex.EncodeToString(seed)) diff --git a/tools/stellar-hd-wallet/commands/new.go b/tools/stellar-hd-wallet/commands/new.go index cb5c590303..b60de8cfd1 100644 --- a/tools/stellar-hd-wallet/commands/new.go +++ b/tools/stellar-hd-wallet/commands/new.go @@ -26,7 +26,7 @@ var NewCmd = &cobra.Command{ words := strings.Split(mnemonic, " ") for i := 0; i < len(words); i++ { - fmt.Printf("#%-5d %10s", i+1, words[i]) + fmt.Printf("word %02d/24: %10s", i+1, words[i]) readString() } From 34d7d86add119e883ef84232c39e35bb65455a96 Mon Sep 17 00:00:00 2001 From: Bartek Nowotarski Date: Thu, 16 Nov 2017 17:50:06 +0100 Subject: [PATCH 06/11] Use go-bip39 fork, tests --- glide.lock | 4 +- glide.yaml | 3 +- tools/stellar-hd-wallet/commands/accounts.go | 36 +++---- .../commands/accounts_test.go | 97 +++++++++++++++++++ tools/stellar-hd-wallet/commands/io.go | 14 ++- tools/stellar-hd-wallet/commands/new.go | 29 +++--- tools/stellar-hd-wallet/main.go | 2 +- 7 files changed, 150 insertions(+), 35 deletions(-) create mode 100644 tools/stellar-hd-wallet/commands/accounts_test.go diff --git a/glide.lock b/glide.lock index fbd1cf6ae5..712db9c0f7 100644 --- a/glide.lock +++ b/glide.lock @@ -349,8 +349,8 @@ imports: - suite - name: github.com/tyler-smith/go-bip32 version: 2c9cfd17756470a0b7c3e4b7954bae7d11035504 -- name: github.com/tyler-smith/go-bip39 - version: 8e7a99b3e716f36d3b080a9a70f9eb45abe4edcc +- name: github.com/bartekn/go-bip39 + version: a05967ea095d81c8fe4833776774cfaff8e5036c - name: github.com/tylerb/graceful version: 7116c7a8115899e80197cd9e0b97998c0f97ed8e repo: https://github.com/tylerb/graceful diff --git a/glide.yaml b/glide.yaml index 218334599f..29160a80f6 100644 --- a/glide.yaml +++ b/glide.yaml @@ -295,4 +295,5 @@ import: subpackages: - socks - package: github.com/btcsuite/websocket -- package: github.com/tyler-smith/go-bip39 +- package: github.com/bartekn/go-bip39 + version: a05967ea095d81c8fe4833776774cfaff8e5036c diff --git a/tools/stellar-hd-wallet/commands/accounts.go b/tools/stellar-hd-wallet/commands/accounts.go index d34f5840d0..3f07328351 100644 --- a/tools/stellar-hd-wallet/commands/accounts.go +++ b/tools/stellar-hd-wallet/commands/accounts.go @@ -3,14 +3,14 @@ package commands import ( "encoding/hex" "fmt" - "log" "regexp" "strings" + "github.com/bartekn/go-bip39" "github.com/spf13/cobra" "github.com/stellar/go/exp/crypto/derivation" "github.com/stellar/go/keypair" - "github.com/tyler-smith/go-bip39" + "github.com/stellar/go/support/errors" ) var wordsRegexp = regexp.MustCompile(`^[a-z]+$`) @@ -22,58 +22,60 @@ var AccountsCmd = &cobra.Command{ Use: "accounts", Short: "Display accounts for a given mnemonic code", Long: "", - Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("How many words? ") + RunE: func(cmd *cobra.Command, args []string) error { + printf("How many words? ") wordsCount := readUint() if _, exist := allowedNumbers[wordsCount]; !exist { - log.Fatal("Invalid value, allowed values: 12, 15, 18, 21, 24") + return errors.New("Invalid value, allowed values: 12, 15, 18, 21, 24") } words := make([]string, wordsCount) for i := uint32(0); i < wordsCount; i++ { - fmt.Printf("Enter word #%-4d", i+1) + printf("Enter word #%-4d", i+1) words[i] = readString() if !wordsRegexp.MatchString(words[i]) { - fmt.Println("Invalid word, try again.") + println("Invalid word, try again.") i-- } } - fmt.Printf("Enter password (leave empty if none): ") + printf("Enter password (leave empty if none): ") password := readString() mnemonic := strings.Join(words, " ") - fmt.Println("Mnemonic:", mnemonic) + println("Mnemonic:", mnemonic) seed, err := bip39.NewSeedWithErrorChecking(mnemonic, password) if err != nil { - log.Fatal("Invalid words or checksum") + return errors.New("Invalid words or checksum") } - fmt.Println("BIP39 Seed:", hex.EncodeToString(seed)) + println("BIP39 Seed:", hex.EncodeToString(seed)) masterKey, err := derivation.DeriveForPath(derivation.StellarAccountPrefix, seed) if err != nil { - log.Fatal(err) + return errors.Wrap(err, "Error deriving master key") } - fmt.Println("m/44'/148' key:", hex.EncodeToString(masterKey.Key)) + println("m/44'/148' key:", hex.EncodeToString(masterKey.Key)) - fmt.Println("") + println("") for i := uint32(startID); i < startID+count; i++ { key, err := masterKey.Derive(derivation.FirstHardenedIndex + i) if err != nil { - log.Fatal(err) + return errors.Wrap(err, "Error deriving child key") } kp, err := keypair.FromRawSeed(key.RawSeed()) if err != nil { - log.Fatal(err) + return errors.Wrap(err, "Error creating key pair") } - fmt.Println(fmt.Sprintf(derivation.StellarAccountPathFormat, i), kp.Address(), kp.Seed()) + println(fmt.Sprintf(derivation.StellarAccountPathFormat, i), kp.Address(), kp.Seed()) } + + return nil }, } diff --git a/tools/stellar-hd-wallet/commands/accounts_test.go b/tools/stellar-hd-wallet/commands/accounts_test.go new file mode 100644 index 0000000000..ef808303e8 --- /dev/null +++ b/tools/stellar-hd-wallet/commands/accounts_test.go @@ -0,0 +1,97 @@ +package commands + +import ( + "bufio" + "bytes" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAccounts(t *testing.T) { + tests := []struct { + Words string + Passphrase string + Error string + Output string + }{ + { + Words: "illness spike retreat truth genius clock brain pass fit cave bargain toe", + Output: `m/44'/148'/0' GDRXE2BQUC3AZNPVFSCEZ76NJ3WWL25FYFK6RGZGIEKWE4SOOHSUJUJ6 SBGWSG6BTNCKCOB3DIFBGCVMUPQFYPA2G4O34RMTB343OYPXU5DJDVMN +m/44'/148'/1' GBAW5XGWORWVFE2XTJYDTLDHXTY2Q2MO73HYCGB3XMFMQ562Q2W2GJQX SCEPFFWGAG5P2VX5DHIYK3XEMZYLTYWIPWYEKXFHSK25RVMIUNJ7CTIS +m/44'/148'/2' GAY5PRAHJ2HIYBYCLZXTHID6SPVELOOYH2LBPH3LD4RUMXUW3DOYTLXW SDAILLEZCSA67DUEP3XUPZJ7NYG7KGVRM46XA7K5QWWUIGADUZCZWTJP +m/44'/148'/3' GAOD5NRAEORFE34G5D4EOSKIJB6V4Z2FGPBCJNQI6MNICVITE6CSYIAE SBMWLNV75BPI2VB4G27RWOMABVRTSSF7352CCYGVELZDSHCXWCYFKXIX +m/44'/148'/4' GBCUXLFLSL2JE3NWLHAWXQZN6SQC6577YMAU3M3BEMWKYPFWXBSRCWV4 SCPCY3CEHMOP2TADSV2ERNNZBNHBGP4V32VGOORIEV6QJLXD5NMCJUXI +m/44'/148'/5' GBRQY5JFN5UBG5PGOSUOL4M6D7VRMAYU6WW2ZWXBMCKB7GPT3YCBU2XZ SCK27SFHI3WUDOEMJREV7ZJQG34SCBR6YWCE6OLEXUS2VVYTSNGCRS6X +m/44'/148'/6' GBY27SJVFEWR3DUACNBSMJB6T4ZPR4C7ZXSTHT6GMZUDL23LAM5S2PQX SDJ4WDPOQAJYR3YIAJOJP3E6E4BMRB7VZ4QAEGCP7EYVDW6NQD3LRJMZ +m/44'/148'/7' GAY7T23Z34DWLSTEAUKVBPHHBUE4E3EMZBAQSLV6ZHS764U3TKUSNJOF SA3HXJUCE2N27TBIZ5JRBLEBF3TLPQEBINP47E6BTMIWW2RJ5UKR2B3L +m/44'/148'/8' GDJTCF62UUYSAFAVIXHPRBR4AUZV6NYJR75INVDXLLRZLZQ62S44443R SCD5OSHUUC75MSJG44BAT3HFZL2HZMMQ5M4GPDL7KA6HJHV3FLMUJAME +m/44'/148'/9' GBTVYYDIYWGUQUTKX6ZMLGSZGMTESJYJKJWAATGZGITA25ZB6T5REF44 SCJGVMJ66WAUHQHNLMWDFGY2E72QKSI3XGSBYV6BANDFUFE7VY4XNXXR`, + }, + { + Words: "resource asthma orphan phone ice canvas fire useful arch jewel impose vague theory cushion top", + Output: `m/44'/148'/0' GAVXVW5MCK7Q66RIBWZZKZEDQTRXWCZUP4DIIFXCCENGW2P6W4OA34RH SAKS7I2PNDBE5SJSUSU2XLJ7K5XJ3V3K4UDFAHMSBQYPOKE247VHAGDB +m/44'/148'/1' GDFCYVCICATX5YPJUDS22KM2GW5QU2KKSPPPT2IC5AQIU6TP3BZSLR5K SAZ2H5GLAVWCUWNPQMB6I3OHRI63T2ACUUAWSH7NAGYYPXGIOPLPW3Q4 +m/44'/148'/2' GAUA3XK3SGEQFNCBM423WIM5WCZ4CR4ZDPDFCYSFLCTODGGGJMPOHAAE SDVSSLPL76I33DKAI4LFTOAKCHJNCXUERGPCMVFT655Z4GRLWM6ZZTSC +m/44'/148'/3' GAH3S77QXTAPZ77REY6LGFIJ2XWVXFOKXHCFLA6HQTL3POLVZJDHHUDM SCH56YSGOBYVBC6DO3ZI2PY62GBVXT4SEJSXJOBQYGC2GCEZSB5PEVBZ +m/44'/148'/4' GCSCZVGV2Y3EQ2RATJ7TE6PVWTW5OH5SMG754AF6W6YM3KJF7RMNPB4Y SBWBM73VUNBGBMFD4E2BA7Q756AKVEAAVTQH34RYEUFD6X64VYL5KXQ2 +m/44'/148'/5' GDKWYAJE3W6PWCXDZNMFNFQSPTF6BUDANE6OVRYMJKBYNGL62VKKCNCC SAVS4CDQZI6PSA5DPCC42S5WLKYIPKXPCJSFYY4N3VDK25T2XX2BTGVX +m/44'/148'/6' GCDTVB4XDLNX22HI5GUWHBXJFBCPB6JNU6ZON7E57FA3LFURS74CWDJH SDFC7WZT3GDQVQUQMXN7TC7UWDW5E3GSMFPHUT2TSTQ7RKWTRA4PLBAL +m/44'/148'/7' GBTDPL5S4IOUQHDLCZ7I2UXJ2TEHO6DYIQ3F2P5OOP3IS7JSJI4UMHQJ SA6UO2FIYC6AS2MSDECLR6F7NKCJTG67F7R4LV2GYB4HCZYXJZRLPOBB +m/44'/148'/8' GD3KWA24OIM7V3MZKDAVSLN3NBHGKVURNJ72ZCTAJSDTF7RIGFXPW5FQ SBDNHDDICLLMBIDZ2IF2D3LH44OVUGGAVHQVQ6BZQI5IQO6AB6KNJCOV +m/44'/148'/9' GB3C6RRQB3V7EPDXEDJCMTS45LVDLSZQ46PTIGKZUY37DXXEOAKJIWSV SDHRG2J34MGDAYHMOVKVJC6LX2QZMCTIKRO5I4JQ6BJQ36KVL6QUTT72`, + }, + { + Words: "bench hurt jump file august wise shallow faculty impulse spring exact slush thunder author capable act festival slice deposit sauce coconut afford frown better", + Output: `m/44'/148'/0' GC3MMSXBWHL6CPOAVERSJITX7BH76YU252WGLUOM5CJX3E7UCYZBTPJQ SAEWIVK3VLNEJ3WEJRZXQGDAS5NVG2BYSYDFRSH4GKVTS5RXNVED5AX7 +m/44'/148'/1' GB3MTYFXPBZBUINVG72XR7AQ6P2I32CYSXWNRKJ2PV5H5C7EAM5YYISO SBKSABCPDWXDFSZISAVJ5XKVIEWV4M5O3KBRRLSPY3COQI7ZP423FYB4 +m/44'/148'/2' GDYF7GIHS2TRGJ5WW4MZ4ELIUIBINRNYPPAWVQBPLAZXC2JRDI4DGAKU SD5CCQAFRIPB3BWBHQYQ5SC66IB2AVMFNWWPBYGSUXVRZNCIRJ7IHESQ +m/44'/148'/3' GAFLH7DGM3VXFVUID7JUKSGOYG52ZRAQPZHQASVCEQERYC5I4PPJUWBD SBSGSAIKEF7JYQWQSGXKB4SRHNSKDXTEI33WZDRR6UHYQCQ5I6ZGZQPK +m/44'/148'/4' GAXG3LWEXWCAWUABRO6SMAEUKJXLB5BBX6J2KMHFRIWKAMDJKCFGS3NN SBIZH53PIRFTPI73JG7QYA3YAINOAT2XMNAUARB3QOWWVZVBAROHGXWM +m/44'/148'/5' GA6RUD4DZ2NEMAQY4VZJ4C6K6VSEYEJITNSLUQKLCFHJ2JOGC5UCGCFQ SCVM6ZNVRUOP4NMCMMKLTVBEMAF2THIOMHPYSSMPCD2ZU7VDPARQQ6OY +m/44'/148'/6' GCUDW6ZF5SCGCMS3QUTELZ6LSAH6IVVXNRPRLAUNJ2XYLCA7KH7ZCVQS SBSHUZQNC45IAIRSAHMWJEJ35RY7YNW6SMOEBZHTMMG64NKV7Y52ZEO2 +m/44'/148'/7' GBJ646Q524WGBN5X5NOAPIF5VQCR2WZCN6QZIDOSY6VA2PMHJ2X636G4 SC2QO2K2B4EBNBJMBZIKOYSHEX4EZAZNIF4UNLH63AQYV6BE7SMYWC6E +m/44'/148'/8' GDHX4LU6YBSXGYTR7SX2P4ZYZSN24VXNJBVAFOB2GEBKNN3I54IYSRM4 SCGMC5AHAAVB3D4JXQPCORWW37T44XJZUNPEMLRW6DCOEARY3H5MAQST +m/44'/148'/9' GDXOY6HXPIDT2QD352CH7VWX257PHVFR72COWQ74QE3TEV4PK2KCKZX7 SCPA5OX4EYINOPAUEQCPY6TJMYICUS5M7TVXYKWXR3G5ZRAJXY3C37GF`, + }, + { + Words: "cable spray genius state float twenty onion head street palace net private method loan turn phrase state blanket interest dry amazing dress blast tube", + Passphrase: "p4ssphr4se", + Output: `m/44'/148'/0' GDAHPZ2NSYIIHZXM56Y36SBVTV5QKFIZGYMMBHOU53ETUSWTP62B63EQ SAFWTGXVS7ELMNCXELFWCFZOPMHUZ5LXNBGUVRCY3FHLFPXK4QPXYP2X +m/44'/148'/1' GDY47CJARRHHL66JH3RJURDYXAMIQ5DMXZLP3TDAUJ6IN2GUOFX4OJOC SBQPDFUGLMWJYEYXFRM5TQX3AX2BR47WKI4FDS7EJQUSEUUVY72MZPJF +m/44'/148'/2' GCLAQF5H5LGJ2A6ACOMNEHSWYDJ3VKVBUBHDWFGRBEPAVZ56L4D7JJID SAF2LXRW6FOSVQNC4HHIIDURZL4SCGCG7UEGG23ZQG6Q2DKIGMPZV6BZ +m/44'/148'/3' GBC36J4KG7ZSIQ5UOSJFQNUP4IBRN6LVUFAHQWT2ODEQ7Y3ASWC5ZN3B SDCCVBIYZDMXOR4VPC3IYMIPODNEDZCS44LDN7B5ZWECIE57N3BTV4GQ +m/44'/148'/4' GA6NHA4KPH5LFYD6LZH35SIX3DU5CWU3GX6GCKPJPPTQCCQPP627E3CB SA5TRXTO7BG2Z6QTQT3O2LC7A7DLZZ2RBTGUNCTG346PLVSSHXPNDVNT +m/44'/148'/5' GBOWMXTLABFNEWO34UJNSJJNVEF6ESLCNNS36S5SX46UZT2MNYJOLA5L SDEOED2KPHV355YNOLLDLVQB7HDPQVIGKXCAJMA3HTM4325ZHFZSKKUC +m/44'/148'/6' GBL3F5JUZN3SQKZ7SL4XSXEJI2SNSVGO6WZWNJLG666WOJHNDDLEXTSZ SDYNO6TLFNV3IM6THLNGUG5FII4ET2H7NH3KCT6OAHIUSHKR4XBEEI6A +m/44'/148'/7' GA5XPPWXL22HFFL5K5CE37CEPUHXYGSP3NNWGM6IK6K4C3EFHZFKSAND SDXMJXAY45W3WEFWMYEPLPIF4CXAD5ECQ37XKMGY5EKLM472SSRJXCYD +m/44'/148'/8' GDS5I7L7LWFUVSYVAOHXJET2565MGGHJ4VHGVJXIKVKNO5D4JWXIZ3XU SAIZA26BUP55TDCJ4U7I2MSQEAJDPDSZSBKBPWQTD5OQZQSJAGNN2IQB +m/44'/148'/9' GBOSMFQYKWFDHJWCMCZSMGUMWCZOM4KFMXXS64INDHVCJ2A2JAABCYRR SDXDYPDNRMGOF25AWYYKPHFAD3M54IT7LCLG7RWTGR3TS32A4HTUXNOS`, + }, + // Invalid: + { + Words: "illness spike retreat truth genius clock brain pass fit cave bargain illness", + Error: "Invalid words or checksum", + }, + } + + for _, test := range tests { + // Global variables, AFAIK there is no elegant way to pass it to cobra.Command + reader = bufio.NewReader(bytes.NewBufferString(input)) + out = &bytes.Buffer{} + + words := strings.Split(test.Words, " ") + input := fmt.Sprintf("%d\n%s\n%s\n", len(words), strings.Join(words, "\n"), test.Passphrase) + err := AccountsCmd.RunE(nil, []string{}) + if test.Error != "" { + assert.Error(t, err) + assert.Contains(t, err.Error(), test.Error) + } else { + assert.NoError(t, err) + output := out.(*bytes.Buffer).String() + assert.Contains(t, output, test.Output) + } + } +} diff --git a/tools/stellar-hd-wallet/commands/io.go b/tools/stellar-hd-wallet/commands/io.go index 2b88e73e99..49fd0ba000 100644 --- a/tools/stellar-hd-wallet/commands/io.go +++ b/tools/stellar-hd-wallet/commands/io.go @@ -2,14 +2,18 @@ package commands import ( "bufio" + "fmt" + "io" "log" "os" "strconv" "strings" ) +var reader = bufio.NewReader(os.Stdin) +var out io.Writer = os.Stdout + func readString() string { - reader := bufio.NewReader(os.Stdin) line, _ := reader.ReadString('\n') return strings.TrimRight(line, "\n") } @@ -23,3 +27,11 @@ func readUint() uint32 { return uint32(number) } + +func printf(format string, a ...interface{}) { + fmt.Fprintf(out, format, a...) +} + +func println(a ...interface{}) { + fmt.Fprintln(out, a...) +} diff --git a/tools/stellar-hd-wallet/commands/new.go b/tools/stellar-hd-wallet/commands/new.go index b60de8cfd1..301319374b 100644 --- a/tools/stellar-hd-wallet/commands/new.go +++ b/tools/stellar-hd-wallet/commands/new.go @@ -1,39 +1,42 @@ package commands import ( - "fmt" - "log" "strings" + "github.com/bartekn/go-bip39" "github.com/spf13/cobra" - "github.com/tyler-smith/go-bip39" + "github.com/stellar/go/support/errors" ) +const DefaultEntropySize = 256 + var NewCmd = &cobra.Command{ Use: "new", Short: "Generates a new mnemonic code", Long: "", - Run: func(cmd *cobra.Command, args []string) { - entropy, err := bip39.NewEntropy(256) + RunE: func(cmd *cobra.Command, args []string) error { + entropy, err := bip39.NewEntropy(DefaultEntropySize) if err != nil { - log.Fatal(err) + return errors.Wrap(err, "Error generating entropy") } mnemonic, err := bip39.NewMnemonic(entropy) if err != nil { - log.Fatal(err) + return errors.Wrap(err, "Error generating mnemonic code") } words := strings.Split(mnemonic, " ") for i := 0; i < len(words); i++ { - fmt.Printf("word %02d/24: %10s", i+1, words[i]) + printf("word %02d/24: %10s", i+1, words[i]) readString() } - fmt.Println("WARNING! Store the words above in a save place!") - fmt.Println("WARNING! If you lose your words, you will lose access to funds in all derived accounts!") - fmt.Println("WARNING! Anyone who has access to these words can spend your funds!") - fmt.Println("") - fmt.Println("Use: `stellar-hd-wallet accounts` command to see generated accounts.") + println("WARNING! Store the words above in a save place!") + println("WARNING! If you lose your words, you will lose access to funds in all derived accounts!") + println("WARNING! Anyone who has access to these words can spend your funds!") + println("") + println("Use: `stellar-hd-wallet accounts` command to see generated accounts.") + + return nil }, } diff --git a/tools/stellar-hd-wallet/main.go b/tools/stellar-hd-wallet/main.go index 1fa8c91215..8b84393354 100644 --- a/tools/stellar-hd-wallet/main.go +++ b/tools/stellar-hd-wallet/main.go @@ -9,7 +9,7 @@ import ( var mainCmd = &cobra.Command{ Use: "stellar-hd-wallet", - Short: "Simple HD wallet for Stellar Lumens", + Short: "Simple HD wallet for Stellar Lumens. THIS PROGRAM IS STILL EXPERIMENTAL. USE AT YOUR OWN RISK.", } func init() { From 0a3c097b0630f0930290e73bb86ca846b52ef894 Mon Sep 17 00:00:00 2001 From: Bartek Nowotarski Date: Thu, 16 Nov 2017 17:58:57 +0100 Subject: [PATCH 07/11] Fix tests --- tools/stellar-hd-wallet/commands/accounts_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/stellar-hd-wallet/commands/accounts_test.go b/tools/stellar-hd-wallet/commands/accounts_test.go index ef808303e8..5f5407e73a 100644 --- a/tools/stellar-hd-wallet/commands/accounts_test.go +++ b/tools/stellar-hd-wallet/commands/accounts_test.go @@ -78,12 +78,13 @@ m/44'/148'/9' GBOSMFQYKWFDHJWCMCZSMGUMWCZOM4KFMXXS64INDHVCJ2A2JAABCYRR SDXDYPDNR } for _, test := range tests { + words := strings.Split(test.Words, " ") + input := fmt.Sprintf("%d\n%s\n%s\n", len(words), strings.Join(words, "\n"), test.Passphrase) + // Global variables, AFAIK there is no elegant way to pass it to cobra.Command reader = bufio.NewReader(bytes.NewBufferString(input)) out = &bytes.Buffer{} - words := strings.Split(test.Words, " ") - input := fmt.Sprintf("%d\n%s\n%s\n", len(words), strings.Join(words, "\n"), test.Passphrase) err := AccountsCmd.RunE(nil, []string{}) if test.Error != "" { assert.Error(t, err) From 683eaa4ea2b34d1fdb47ba596d2c2a155e663366 Mon Sep 17 00:00:00 2001 From: Bartek Nowotarski Date: Tue, 19 Dec 2017 13:49:37 +0100 Subject: [PATCH 08/11] Update new.go --- tools/stellar-hd-wallet/commands/new.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/stellar-hd-wallet/commands/new.go b/tools/stellar-hd-wallet/commands/new.go index 301319374b..23820cfa30 100644 --- a/tools/stellar-hd-wallet/commands/new.go +++ b/tools/stellar-hd-wallet/commands/new.go @@ -31,7 +31,7 @@ var NewCmd = &cobra.Command{ readString() } - println("WARNING! Store the words above in a save place!") + println("WARNING! Store the words above in a safe place!") println("WARNING! If you lose your words, you will lose access to funds in all derived accounts!") println("WARNING! Anyone who has access to these words can spend your funds!") println("") From 09f1b76c3c3ed4e8dd835ef6d1081cc000c90a1b Mon Sep 17 00:00:00 2001 From: Bartek Nowotarski Date: Thu, 28 Dec 2017 17:07:17 +0100 Subject: [PATCH 09/11] stellar-hd-wallet README CHANGELOG --- tools/stellar-hd-wallet/CHANGELOG.md | 11 +++++++++++ tools/stellar-hd-wallet/README.md | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tools/stellar-hd-wallet/CHANGELOG.md create mode 100644 tools/stellar-hd-wallet/README.md diff --git a/tools/stellar-hd-wallet/CHANGELOG.md b/tools/stellar-hd-wallet/CHANGELOG.md new file mode 100644 index 0000000000..4099077918 --- /dev/null +++ b/tools/stellar-hd-wallet/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this +file. This project adheres to [Semantic Versioning](http://semver.org/). + +As this project is pre 1.0, breaking changes may happen for minor version +bumps. A breaking change will get clearly notified in this log. + +## [v0.0.1] - 2017-12-28 + +Initial release. diff --git a/tools/stellar-hd-wallet/README.md b/tools/stellar-hd-wallet/README.md new file mode 100644 index 0000000000..a5353e400e --- /dev/null +++ b/tools/stellar-hd-wallet/README.md @@ -0,0 +1,23 @@ +# stellar-hd-wallet + +Console tool to generate Stellar HD wallet for a given seed. Implements [SEP-0005](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0005.md). + +This is experimental software. Use at your own risk. + +## Usage + +``` +Simple HD wallet for Stellar Lumens. THIS PROGRAM IS STILL EXPERIMENTAL. USE AT YOUR OWN RISK. + +Usage: + stellar-hd-wallet [command] + +Available Commands: + accounts Display accounts for a given mnemonic code + new Generates a new mnemonic code + +Flags: + -h, --help help for stellar-hd-wallet + +Use "stellar-hd-wallet [command] --help" for more information about a command. +``` From 3727837ea8e5ff39dc8b1f9cebaa10ee2ea3f310 Mon Sep 17 00:00:00 2001 From: Bartek Nowotarski Date: Thu, 28 Dec 2017 17:44:56 +0100 Subject: [PATCH 10/11] More tests --- tools/stellar-hd-wallet/commands/accounts_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tools/stellar-hd-wallet/commands/accounts_test.go b/tools/stellar-hd-wallet/commands/accounts_test.go index 5f5407e73a..d72b9efbcd 100644 --- a/tools/stellar-hd-wallet/commands/accounts_test.go +++ b/tools/stellar-hd-wallet/commands/accounts_test.go @@ -69,6 +69,19 @@ m/44'/148'/6' GBL3F5JUZN3SQKZ7SL4XSXEJI2SNSVGO6WZWNJLG666WOJHNDDLEXTSZ SDYNO6TLF m/44'/148'/7' GA5XPPWXL22HFFL5K5CE37CEPUHXYGSP3NNWGM6IK6K4C3EFHZFKSAND SDXMJXAY45W3WEFWMYEPLPIF4CXAD5ECQ37XKMGY5EKLM472SSRJXCYD m/44'/148'/8' GDS5I7L7LWFUVSYVAOHXJET2565MGGHJ4VHGVJXIKVKNO5D4JWXIZ3XU SAIZA26BUP55TDCJ4U7I2MSQEAJDPDSZSBKBPWQTD5OQZQSJAGNN2IQB m/44'/148'/9' GBOSMFQYKWFDHJWCMCZSMGUMWCZOM4KFMXXS64INDHVCJ2A2JAABCYRR SDXDYPDNRMGOF25AWYYKPHFAD3M54IT7LCLG7RWTGR3TS32A4HTUXNOS`, + }, + { + Words: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + Output: `m/44'/148'/0' GB3JDWCQJCWMJ3IILWIGDTQJJC5567PGVEVXSCVPEQOTDN64VJBDQBYX SBUV3MRWKNS6AYKZ6E6MOUVF2OYMON3MIUASWL3JLY5E3ISDJFELYBRZ +m/44'/148'/1' GDVSYYTUAJ3ACHTPQNSTQBDQ4LDHQCMNY4FCEQH5TJUMSSLWQSTG42MV SCHDCVCWGAKGIMTORV6K5DYYV3BY4WG3RA4M6MCBGJLHUCWU2MC6DL66 +m/44'/148'/2' GBFPWBTN4AXHPWPTQVQBP4KRZ2YVYYOGRMV2PEYL2OBPPJDP7LECEVHR SAPLVTLUXSDLFRDGCCFLPDZMTCEVMP3ZXTM74EBJCVKZKM34LGQPF7K3 +m/44'/148'/3' GCCCOWAKYVFY5M6SYHOW33TSNC7Z5IBRUEU2XQVVT34CIZU7CXZ4OQ4O SDQYXOP2EAUZP4YOEQ5BUJIQ3RDSP5XV4ZFI6C5Y3QCD5Y63LWPXT7PW +m/44'/148'/4' GCQ3J35MKPKJX7JDXRHC5YTXTULFMCBMZ5IC63EDR66QA3LO7264ZL7Q SCT7DUHYZD6DRCETT6M73GWKFJI4D56P3SNWNWNJ7ANLJZS6XIFYYXSB +m/44'/148'/5' GDTA7622ZA5PW7F7JL7NOEFGW62M7GW2GY764EQC2TUJ42YJQE2A3QUL SDTWG5AFDI6GRQNLPWOC7IYS7AKOGMI2GX4OXTBTZHHYPMNZ2PX4ONWU +m/44'/148'/6' GD7A7EACTPTBCYCURD43IEZXGIBCEXNBHN3OFWV2FOX67XKUIGRCTBNU SDJMWY4KFRS4PTA5WBFVCPS2GKYLXOMCLQSBNEIBG7KRGHNQOM25KMCP +m/44'/148'/7' GAF4AGPVLQXFKEWQV3DZU5YEFU6YP7XJHAEEQH4G3R664MSF77FLLRK3 SDOJH5JRCNGT57QTPTJEQGBEBZJPXE7XUDYDB24VTOPP7PH3ALKHAHFG +m/44'/148'/8' GABTYCZJMCP55SS6I46SR76IHETZDLG4L37MLZRZKQDGBLS5RMP65TSX SC6N6GYQ2VA4T7CUP2BWGBRT2P6L2HQSZIUNQRHNDLISF6ND7TW4P4ER +m/44'/148'/9' GAKFARYSPI33KUJE7HYLT47DCX2PFWJ77W3LZMRBPSGPGYPMSDBE7W7X SALJ5LPBTXCFML2CQ7ORP7WJNJOZSVBVRQAAODMVHMUF4P4XXFZB7MKY`, }, // Invalid: { From ee8e4b4083323c83cf70b8732b75df88136cece0 Mon Sep 17 00:00:00 2001 From: Bartek Nowotarski Date: Thu, 28 Dec 2017 19:45:06 +0100 Subject: [PATCH 11/11] Improve tests --- .../commands/accounts_test.go | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/tools/stellar-hd-wallet/commands/accounts_test.go b/tools/stellar-hd-wallet/commands/accounts_test.go index d72b9efbcd..e112378a98 100644 --- a/tools/stellar-hd-wallet/commands/accounts_test.go +++ b/tools/stellar-hd-wallet/commands/accounts_test.go @@ -15,11 +15,11 @@ func TestAccounts(t *testing.T) { Words string Passphrase string Error string - Output string + Want string }{ { Words: "illness spike retreat truth genius clock brain pass fit cave bargain toe", - Output: `m/44'/148'/0' GDRXE2BQUC3AZNPVFSCEZ76NJ3WWL25FYFK6RGZGIEKWE4SOOHSUJUJ6 SBGWSG6BTNCKCOB3DIFBGCVMUPQFYPA2G4O34RMTB343OYPXU5DJDVMN + Want: `m/44'/148'/0' GDRXE2BQUC3AZNPVFSCEZ76NJ3WWL25FYFK6RGZGIEKWE4SOOHSUJUJ6 SBGWSG6BTNCKCOB3DIFBGCVMUPQFYPA2G4O34RMTB343OYPXU5DJDVMN m/44'/148'/1' GBAW5XGWORWVFE2XTJYDTLDHXTY2Q2MO73HYCGB3XMFMQ562Q2W2GJQX SCEPFFWGAG5P2VX5DHIYK3XEMZYLTYWIPWYEKXFHSK25RVMIUNJ7CTIS m/44'/148'/2' GAY5PRAHJ2HIYBYCLZXTHID6SPVELOOYH2LBPH3LD4RUMXUW3DOYTLXW SDAILLEZCSA67DUEP3XUPZJ7NYG7KGVRM46XA7K5QWWUIGADUZCZWTJP m/44'/148'/3' GAOD5NRAEORFE34G5D4EOSKIJB6V4Z2FGPBCJNQI6MNICVITE6CSYIAE SBMWLNV75BPI2VB4G27RWOMABVRTSSF7352CCYGVELZDSHCXWCYFKXIX @@ -32,7 +32,7 @@ m/44'/148'/9' GBTVYYDIYWGUQUTKX6ZMLGSZGMTESJYJKJWAATGZGITA25ZB6T5REF44 SCJGVMJ66 }, { Words: "resource asthma orphan phone ice canvas fire useful arch jewel impose vague theory cushion top", - Output: `m/44'/148'/0' GAVXVW5MCK7Q66RIBWZZKZEDQTRXWCZUP4DIIFXCCENGW2P6W4OA34RH SAKS7I2PNDBE5SJSUSU2XLJ7K5XJ3V3K4UDFAHMSBQYPOKE247VHAGDB + Want: `m/44'/148'/0' GAVXVW5MCK7Q66RIBWZZKZEDQTRXWCZUP4DIIFXCCENGW2P6W4OA34RH SAKS7I2PNDBE5SJSUSU2XLJ7K5XJ3V3K4UDFAHMSBQYPOKE247VHAGDB m/44'/148'/1' GDFCYVCICATX5YPJUDS22KM2GW5QU2KKSPPPT2IC5AQIU6TP3BZSLR5K SAZ2H5GLAVWCUWNPQMB6I3OHRI63T2ACUUAWSH7NAGYYPXGIOPLPW3Q4 m/44'/148'/2' GAUA3XK3SGEQFNCBM423WIM5WCZ4CR4ZDPDFCYSFLCTODGGGJMPOHAAE SDVSSLPL76I33DKAI4LFTOAKCHJNCXUERGPCMVFT655Z4GRLWM6ZZTSC m/44'/148'/3' GAH3S77QXTAPZ77REY6LGFIJ2XWVXFOKXHCFLA6HQTL3POLVZJDHHUDM SCH56YSGOBYVBC6DO3ZI2PY62GBVXT4SEJSXJOBQYGC2GCEZSB5PEVBZ @@ -45,7 +45,7 @@ m/44'/148'/9' GB3C6RRQB3V7EPDXEDJCMTS45LVDLSZQ46PTIGKZUY37DXXEOAKJIWSV SDHRG2J34 }, { Words: "bench hurt jump file august wise shallow faculty impulse spring exact slush thunder author capable act festival slice deposit sauce coconut afford frown better", - Output: `m/44'/148'/0' GC3MMSXBWHL6CPOAVERSJITX7BH76YU252WGLUOM5CJX3E7UCYZBTPJQ SAEWIVK3VLNEJ3WEJRZXQGDAS5NVG2BYSYDFRSH4GKVTS5RXNVED5AX7 + Want: `m/44'/148'/0' GC3MMSXBWHL6CPOAVERSJITX7BH76YU252WGLUOM5CJX3E7UCYZBTPJQ SAEWIVK3VLNEJ3WEJRZXQGDAS5NVG2BYSYDFRSH4GKVTS5RXNVED5AX7 m/44'/148'/1' GB3MTYFXPBZBUINVG72XR7AQ6P2I32CYSXWNRKJ2PV5H5C7EAM5YYISO SBKSABCPDWXDFSZISAVJ5XKVIEWV4M5O3KBRRLSPY3COQI7ZP423FYB4 m/44'/148'/2' GDYF7GIHS2TRGJ5WW4MZ4ELIUIBINRNYPPAWVQBPLAZXC2JRDI4DGAKU SD5CCQAFRIPB3BWBHQYQ5SC66IB2AVMFNWWPBYGSUXVRZNCIRJ7IHESQ m/44'/148'/3' GAFLH7DGM3VXFVUID7JUKSGOYG52ZRAQPZHQASVCEQERYC5I4PPJUWBD SBSGSAIKEF7JYQWQSGXKB4SRHNSKDXTEI33WZDRR6UHYQCQ5I6ZGZQPK @@ -59,7 +59,7 @@ m/44'/148'/9' GDXOY6HXPIDT2QD352CH7VWX257PHVFR72COWQ74QE3TEV4PK2KCKZX7 SCPA5OX4E { Words: "cable spray genius state float twenty onion head street palace net private method loan turn phrase state blanket interest dry amazing dress blast tube", Passphrase: "p4ssphr4se", - Output: `m/44'/148'/0' GDAHPZ2NSYIIHZXM56Y36SBVTV5QKFIZGYMMBHOU53ETUSWTP62B63EQ SAFWTGXVS7ELMNCXELFWCFZOPMHUZ5LXNBGUVRCY3FHLFPXK4QPXYP2X + Want: `m/44'/148'/0' GDAHPZ2NSYIIHZXM56Y36SBVTV5QKFIZGYMMBHOU53ETUSWTP62B63EQ SAFWTGXVS7ELMNCXELFWCFZOPMHUZ5LXNBGUVRCY3FHLFPXK4QPXYP2X m/44'/148'/1' GDY47CJARRHHL66JH3RJURDYXAMIQ5DMXZLP3TDAUJ6IN2GUOFX4OJOC SBQPDFUGLMWJYEYXFRM5TQX3AX2BR47WKI4FDS7EJQUSEUUVY72MZPJF m/44'/148'/2' GCLAQF5H5LGJ2A6ACOMNEHSWYDJ3VKVBUBHDWFGRBEPAVZ56L4D7JJID SAF2LXRW6FOSVQNC4HHIIDURZL4SCGCG7UEGG23ZQG6Q2DKIGMPZV6BZ m/44'/148'/3' GBC36J4KG7ZSIQ5UOSJFQNUP4IBRN6LVUFAHQWT2ODEQ7Y3ASWC5ZN3B SDCCVBIYZDMXOR4VPC3IYMIPODNEDZCS44LDN7B5ZWECIE57N3BTV4GQ @@ -72,7 +72,7 @@ m/44'/148'/9' GBOSMFQYKWFDHJWCMCZSMGUMWCZOM4KFMXXS64INDHVCJ2A2JAABCYRR SDXDYPDNR }, { Words: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", - Output: `m/44'/148'/0' GB3JDWCQJCWMJ3IILWIGDTQJJC5567PGVEVXSCVPEQOTDN64VJBDQBYX SBUV3MRWKNS6AYKZ6E6MOUVF2OYMON3MIUASWL3JLY5E3ISDJFELYBRZ + Want: `m/44'/148'/0' GB3JDWCQJCWMJ3IILWIGDTQJJC5567PGVEVXSCVPEQOTDN64VJBDQBYX SBUV3MRWKNS6AYKZ6E6MOUVF2OYMON3MIUASWL3JLY5E3ISDJFELYBRZ m/44'/148'/1' GDVSYYTUAJ3ACHTPQNSTQBDQ4LDHQCMNY4FCEQH5TJUMSSLWQSTG42MV SCHDCVCWGAKGIMTORV6K5DYYV3BY4WG3RA4M6MCBGJLHUCWU2MC6DL66 m/44'/148'/2' GBFPWBTN4AXHPWPTQVQBP4KRZ2YVYYOGRMV2PEYL2OBPPJDP7LECEVHR SAPLVTLUXSDLFRDGCCFLPDZMTCEVMP3ZXTM74EBJCVKZKM34LGQPF7K3 m/44'/148'/3' GCCCOWAKYVFY5M6SYHOW33TSNC7Z5IBRUEU2XQVVT34CIZU7CXZ4OQ4O SDQYXOP2EAUZP4YOEQ5BUJIQ3RDSP5XV4ZFI6C5Y3QCD5Y63LWPXT7PW @@ -91,21 +91,24 @@ m/44'/148'/9' GAKFARYSPI33KUJE7HYLT47DCX2PFWJ77W3LZMRBPSGPGYPMSDBE7W7X SALJ5LPBT } for _, test := range tests { - words := strings.Split(test.Words, " ") - input := fmt.Sprintf("%d\n%s\n%s\n", len(words), strings.Join(words, "\n"), test.Passphrase) - // Global variables, AFAIK there is no elegant way to pass it to cobra.Command - reader = bufio.NewReader(bytes.NewBufferString(input)) - out = &bytes.Buffer{} + t.Run(fmt.Sprintf("words %s passphrase %s", test.Words, test.Passphrase), func(t *testing.T) { + words := strings.Split(test.Words, " ") + input := fmt.Sprintf("%d\n%s\n%s\n", len(words), strings.Join(words, "\n"), test.Passphrase) - err := AccountsCmd.RunE(nil, []string{}) - if test.Error != "" { - assert.Error(t, err) - assert.Contains(t, err.Error(), test.Error) - } else { - assert.NoError(t, err) - output := out.(*bytes.Buffer).String() - assert.Contains(t, output, test.Output) - } + // Global variables, AFAIK there is no elegant way to pass it to cobra.Command + reader = bufio.NewReader(bytes.NewBufferString(input)) + out = &bytes.Buffer{} + + err := AccountsCmd.RunE(nil, []string{}) + if test.Error != "" { + assert.Error(t, err) + assert.Contains(t, err.Error(), test.Error) + } else { + assert.NoError(t, err) + output := out.(*bytes.Buffer).String() + assert.Contains(t, output, test.Want) + } + }) } }