diff --git a/README.md b/README.md index a6cdac9..f3d5c98 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,36 @@ A console bitcoin wallet application written in golang. +![GoWallet Account View](https://raw.githubusercontent.com/aiportal/gowallet/master/_doc/account.png) + **GoWallet uses a secret phrase and a salt phrase to generate your safe wallets.** Project location: https://github.com/aiportal/gowallet -Secret at least 16 characters, containing uppercase letters, lowercase letters, numbers, and special characters. -salt at least 6 characters. -Secret and salt allow the use of hexadecimal notation similar to '\xff' or '\xFF' to represent a character. +**GoWallet is a safe brain wallet for bitcoin.** + Secret phrase at least 16 characters. + Salt phrase at least 6 characters. + + Secret phrases should contain uppercase letters, lowercase letters, numbers, and special characters. + Both secret phrases and salt phrases can use hexadecimal notation such as \xff or \xFF to represent a character. + +**It is recommended that use a more complex secret and put it on paper.** +**It's also recommended that keep your salt in mind.** + +Donations are welcome at [1BTCzvzTn7QYBwFkRRkXGcVPodwrYoQyAq](https://blockchain.info/address/1BTCzvzTn7QYBwFkRRkXGcVPodwrYoQyAq) + +![GoWallet Encryption Process](https://raw.githubusercontent.com/aiportal/gowallet/master/_doc/encryption.png) + -**It is advisable to use more complex secret and to write secret on paper.** -**It is also recommended that salt be memorized in the brain.** +#### Advanced usage -Donations are welcome at 1Brn37oiWcDoTVqeP1EzbVtCz3dJ7W1Z57 +You can export bulk wallets using the command line. -![GoWallet Encryption Process](https://raw.githubusercontent.com/aiportal/gowallet/master/GoWallet.png) \ No newline at end of file + -n or -number uint +          + Number of wallets to generate. + -v or -vanity string +          + Find vanity wallet address matching. (prefix) + -e or -export string +          + Export wallets(child number, private key and address) in WIF format. diff --git a/gowallet.go b/gowallet.go index dac0426..85dbe53 100644 --- a/gowallet.go +++ b/gowallet.go @@ -4,124 +4,93 @@ import ( "flag" "fmt" "os" - "gowallet/address" + "gowallet/view" + "gowallet/wallet" ) -const goWalletTip = ` -GoWallet uses a secret phrase and a salt phrase to generate your safe wallets. -Project location: https://github.com/aiportal/gowallet - -Secret at least 16 characters, containing uppercase letters, lowercase letters, numbers, and special characters. -salt at least 6 characters. -Secret and salt allow the use of hexadecimal notation similar to '\xff' or '\xFF' to represent a character. - -It is advisable to use more complex secret and to write secret on paper. -It is also recommended that salt be memorized in the brain.` - -const debug = true -const trace = false - - func main() { - vanity, number, export := parseParams() - - var passPhrase string - if _, err := os.Stat("./gowallet.wlt"); os.IsNotExist(err) { - // New wallets. - var seed []byte - if !debug { - secret, salt, err := address.InputBrainWalletSecret(goWalletTip) - if err != nil { - println(err.Error()) - return - } - if trace { - println("your secret is: " + secret) - println("your salt is: " + salt) - } - passPhrase = salt - seed, err = address.GenerateBrainWalletSeed(secret, salt) - if err != nil { - println(err.Error()) - return - } - } else { - seed, err = address.GenerateBrainWalletSeed("https://github.com/aiportal", "gowallet") - if err != nil { - println(err.Error()) - return - } - passPhrase = "gowallet" - } - - accountKey, accountPub, err := address.GenerateAccount(seed[:], 0) + number, vanity, export := parseParams() + if number > 0 { + err := generateWallets(uint32(number), vanity, export) if err != nil { println(err.Error()) return } - fmt.Println("") - fmt.Println("Main account: ") - // fmt.Printf(" key: %s\n", accountKey) - fmt.Printf(" pub: %s\n", accountPub) + } else { + view.ShowSplashView(view.SplashStartView) - if vanity == "" { - wallets, err := address.GenerateWallets(accountKey, uint32(number)) + var ws []*wallet.Wallet + if !wallet.IsFileExists() { + var err error + ws, err = createWallets(1, 10) if err != nil { - println(err.Error()) + fmt.Println(err.Error()) return } - for i, w := range wallets { - encrypt, err := address.EncryptKey(w[0], passPhrase) - if err != nil { - println(err.Error()) - encrypt = w[0] - } - fmt.Printf("wallet(%d): \n", i) - fmt.Printf(" private: %s\n", encrypt) - fmt.Printf(" address: %s\n", w[1]) - } - if export != "" { - err := exportWallets(export, wallets) - if err != nil { - println(err.Error()) - return - } - } + // save wallets + wf := wallet.NewWalletFile(ws) + wf.Save() } else { - wallets, err := address.SearchVanities(accountKey, vanity, uint32(number), - func(i uint32, count uint32, n uint32) { - fmt.Printf("processed:%d / %d, found: %d \n", i, count, n) - }) + wf, err := wallet.LoadWalletFile() if err != nil { - println(err.Error()) + fmt.Println(err.Error()) return } - for _, w := range wallets { - fmt.Printf("wallet(%s): \n", w[2]) - fmt.Printf(" private: %s\n", w[0]) - fmt.Printf(" address: %s\n", w[1]) - } - if export != "" { - err := exportWallets(export, wallets) - if err != nil { - println(err.Error()) - return - } - } + ws = wf.Wallets } - } else { - // Open wallets file. + + showUI(ws) + } +} + +func showUI(ws []*wallet.Wallet) { + + accountView := view.NewAccountView(ws) + accountView.Show() + + for accountView.Data != nil { + cmd := accountView.Data.(string) + if cmd == "quit" { + break + } + tipView := view.NewTipView(cmd) + if tipView != nil { + tipView.Show() + } + accountView.Show() } } +// create wallets by secret and salt +func createWallets(start, count uint32) (ws []*wallet.Wallet, err error) { + view.ShowSplashView(view.SplashCreateView) + + // create wallets + wp, err := view.InputNewParameters(3) + if err != nil { + return + } + //wp := view.WalletParam{Secret:"https://github.com/aiportal", Salt:"gowallet"} + + wa, err := wallet.NewWalletAccount(wp.SecretBytes(), wp.SaltBytes()) + if err != nil { + return + } + ws, err = wa.GenerateWallets(start, count) + if err != nil { + return + } + return +} + //Parse command line parameters -func parseParams() (vanity string, number uint, export string) { +func parseParams() (number uint, vanity, export string) { - flag.StringVar(&vanity, "vanity", "", "Find vanity wallet address matching. (prefix or regular)") - flag.StringVar(&vanity, "v", "", "Find vanity wallet address matching. (prefix or regular)") + flag.UintVar(&number, "number", 0, "Number of wallets to generate.") + flag.UintVar(&number, "n", 0, "Number of wallets to generate.") - flag.UintVar(&number, "number", 1, "Number of wallets to generate. (default 1)") - flag.UintVar(&number, "n", 1, "Number of wallets to generate. (default 1)") + flag.StringVar(&vanity, "vanity", "", "Find vanity wallet address matching. (prefix)") + flag.StringVar(&vanity, "v", "", "Find vanity wallet address matching. (prefix)") flag.StringVar(&export, "export", "", "Export wallets in WIF format.") flag.StringVar(&export, "e", "", "Export wallets in WIF format.") @@ -130,21 +99,53 @@ func parseParams() (vanity string, number uint, export string) { return } -// Export wallets -func exportWallets(filename string, wallets [][]string) (err error) { - f, err := os.Create(filename) +func generateWallets(number uint32, vanity, export string) (err error) { + + view.ShowSplashView(view.SplashStartView) + view.ShowSplashView(view.SplashCreateView) + wp, err := view.InputNewParameters(3) if err != nil { return } - defer f.Close() - for i, w := range wallets { - if len(w) > 2 { - f.WriteString(fmt.Sprintf("wallet(%s): \n", w[2])) - } else { - f.WriteString(fmt.Sprintf("wallet(%d): \n", i)) + wa, err := wallet.NewWalletAccount(wp.SecretBytes(), wp.SaltBytes()) + if err != nil { + return + } + var ws []*wallet.Wallet + if vanity == "" { + ws, err = wa.GenerateWallets(0, uint32(number)) + if err != nil { + return + } + } else { + var patterns []string + patterns, err = wa.NormalizeVanities([]string{vanity}) + if err != nil { + return + } + ws, err = wa.FindVanities(patterns, func(i, c, n uint32) bool { + fmt.Printf("progress: %d, %d, %d\n", i, c, n) + return (n >= number) + }) + } + if export == "" { + for _, w := range ws { + fmt.Printf("wallet (%d): \n", w.No) + fmt.Println(" " + w.Private) + fmt.Println(" " + w.Address) + } + } else { + var f *os.File + f, err = os.Create(export) + if err != nil { + return + } + defer f.Close() + for _, w := range ws { + f.WriteString(fmt.Sprintf("wallet(%d): \r\n", w.No)) + f.WriteString(fmt.Sprintf(" private: %s\r\n", w.Private)) + f.WriteString(fmt.Sprintf(" address: %s\r\n", w.Address)) } - f.WriteString(fmt.Sprintf(" private: %s\n", w[0])) - f.WriteString(fmt.Sprintf(" address: %s\n", w[1])) } return } diff --git a/wallet/file.go b/wallet/file.go new file mode 100644 index 0000000..bf20ea2 --- /dev/null +++ b/wallet/file.go @@ -0,0 +1,90 @@ +package wallet + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/user" +) + +const WalletFileName = ".gowallet.w02" // version 0.2 + +// check if user wallet file exists +func IsFileExists() bool { + u, err := user.Current() + if err != nil { + return false + } + filename := fmt.Sprintf("%s/%s", u.HomeDir, WalletFileName) + f, err := os.Stat(filename) + if err != nil { + return false + } + if f.IsDir() { + return false + } + return true +} + +type WalletFile struct { + Wallets []*Wallet +} + +func NewWalletFile(ws []*Wallet) *WalletFile { + wf := new(WalletFile) + wf.Wallets = make([]*Wallet, len(ws)) + for i, v := range ws { + w := NewWallet(v.No, "", v.Address) + wf.Wallets[i] = w + } + return wf +} + +func LoadWalletFile() (wf *WalletFile, err error) { + u, err := user.Current() + if err != nil { + return + } + path := fmt.Sprintf("%s/%s", u.HomeDir, WalletFileName) + _, err = os.Stat(path) + if err != nil { + return + } + bs, err := ioutil.ReadFile(path) + if err != nil { + return + } + wf = new(WalletFile) + err = json.Unmarshal(bs, wf) + if err != nil { + return + } + return +} + +func (wf *WalletFile) Save() (err error) { + u, err := user.Current() + if err != nil { + return + } + path := fmt.Sprintf("%s/%s", u.HomeDir, WalletFileName) + + bs, err := json.Marshal(wf) + if err != nil { + return + } + err = ioutil.WriteFile(path, bs, os.ModeExclusive) + if err != nil { + return + } + return +} +// +//func (wf *WalletFile) Wallets() (ws []*Wallet) { +// ws = make([]*Wallet, len(wf.wallets)) +// for i, v := range wf.wallets { +// ws[i] = v +// } +// return +//} \ No newline at end of file