// ReadKeyFileToECPriv returns an extendedkey from a file. // If there's no file there, it'll make one. If there's a password needed, // it'll prompt for one. One stop function. func ReadKeyFileToECPriv( filename string, p *chaincfg.Params) (*hdkeychain.ExtendedKey, error) { key32 := new([32]byte) _, err := os.Stat(filename) if err != nil { if os.IsNotExist(err) { // no key found, generate and save one fmt.Printf("No file %s, generating.\n", filename) rn, err := hdkeychain.GenerateSeed(32) if err != nil { return nil, err } copy(key32[:], rn[:]) err = SaveKeyToFileInteractive(filename, key32) if err != nil { return nil, err } } else { // unknown error, crash fmt.Printf("unknown\n") return nil, err } } key, err := LoadKeyFromFileInteractive(filename) if err != nil { return nil, err } rootpriv, err := hdkeychain.NewMaster(key[:], p) if err != nil { return nil, err } return rootpriv, nil }
// promptConsoleSeed prompts the user whether they want to use an existing // Bitmessage address generation seed. When the user answers no, a seed will be // generated and displayed to the user along with prompting them for // confirmation. When the user answers yes, the user is prompted for it. All // prompts are repeated until the user enters a valid response. func promptConsoleSeed() ([]byte, error) { // Ascertain the wallet generation seed. useUserSeed, err := promptConsoleListBool("\nDo you have an "+ "existing Bitmessage address generation seed you want to use?", "no") if err != nil { return nil, err } if !useUserSeed { seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen) if err != nil { return nil, err } fmt.Println("\nYour address generation seed is:") fmt.Printf("%x\n\n", seed) fmt.Println("IMPORTANT: Please keep in mind that anyone who has" + " access to the seed can also restore your addresses thereby " + "giving them access to all your Bitmessage identities, so it is " + "imperative that you keep it in a secure location.\n") for { fmt.Print(`Once you have stored the seed in a safe ` + `and secure location, enter "OK" to continue: `) confirmSeed, err := consoleReader.ReadString('\n') if err != nil { return nil, err } confirmSeed = strings.TrimSpace(confirmSeed) confirmSeed = strings.Trim(confirmSeed, `"`) if confirmSeed == "OK" { break } } return seed, nil } for { fmt.Print("Enter existing address generation seed: ") seedStr, err := consoleReader.ReadString('\n') if err != nil { return nil, err } seedStr = strings.TrimSpace(strings.ToLower(seedStr)) seed, err := hex.DecodeString(seedStr) if err != nil || len(seed) < hdkeychain.MinSeedBytes || len(seed) > hdkeychain.MaxSeedBytes { fmt.Printf("Invalid seed specified. Must be a "+ "hexadecimal value that is at least %d bits and "+ "at most %d bits\n", hdkeychain.MinSeedBytes*8, hdkeychain.MaxSeedBytes*8) continue } return seed, nil } }
// createSimulationWallet is intended to be called from the rpcclient // and used to create a wallet for actors involved in simulations. func createSimulationWallet(cfg *config) error { // Simulation wallet password is 'password'. privPass := []byte("password") // Public passphrase is the default. pubPass := []byte(defaultPubPassphrase) // Generate a random seed. seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen) if err != nil { return err } netDir := networkDir(cfg.DataDir, activeNet.Params) // Create the wallet. dbPath := filepath.Join(netDir, walletDbName) fmt.Println("Creating the wallet...") // Create the wallet database backed by bolt db. db, err := walletdb.Create("bdb", dbPath) if err != nil { return err } defer db.Close() // Create the address manager. waddrmgrNamespace, err := db.Namespace(waddrmgrNamespaceKey) if err != nil { return err } manager, err := waddrmgr.Create(waddrmgrNamespace, seed, []byte(pubPass), []byte(privPass), activeNet.Params, nil) if err != nil { return err } manager.Close() fmt.Println("The wallet has been created successfully.") return nil }
func TestWalletCreationAndLoad(t *testing.T) { file, err := ioutil.TempFile(os.TempDir(), "wallet.db") if err != nil { t.Fatal(err) } defer os.Remove(file.Name()) privPass := "******" seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen) if err != nil { t.Fatal(err) } wallet, err := CreateWallet(file.Name(), privPass, seed) if err != nil { t.Fatal(err) } if addrs, err := wallet.Addresses(); err != nil { t.Fatal(err) } else if len(addrs) != 0 { t.Fatalf("wallet doesn't start with 0 addresses, len = %d", len(addrs)) } if addrs, err := wallet.GenAddresses(10); err != nil { t.Fatal(err) } else if len(addrs) != 10 { t.Fatalf("generated wrong number of addresses, len = %d", len(addrs)) } if addrs, err := wallet.Addresses(); err != nil { t.Fatal(err) } else if len(addrs) != 10 { t.Fatalf("wallet doesn't have new addresses, len = %d", len(addrs)) } else { for _, addr := range addrs { fmt.Printf("addr %s\n", addr.String()) } } err = wallet.SendBitcoin(map[string]btcutil.Amount{"171RiZZqGzgB25Wxn3MKqo4JsjkMNSJFJe": 0}, 0) if err != nil { t.Fatal(err) } }
// createWallet generates a new wallet. The new wallet will reside at the // provided path. // TODO(roasbeef): maybe pass in config after all for testing purposes? func createWallet(privPass, pubPass, userSeed []byte, dbPath string) error { // TODO(roasbeef): replace with tadge's seed format? hdSeed := userSeed var seedErr error if userSeed == nil { hdSeed, seedErr = hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen) if seedErr != nil { return seedErr } } // Create the wallet. fmt.Println("Creating the wallet...") // Create the wallet database backed by bolt db. db, err := walletdb.Create("bdb", dbPath) if err != nil { return err } // Create the address manager. namespace, err := db.Namespace(waddrmgrNamespaceKey) if err != nil { return err } manager, err := waddrmgr.Create(namespace, hdSeed, []byte(pubPass), []byte(privPass), ActiveNetParams, nil) if err != nil { return err } if err := manager.Close(); err != nil { return err } if err := db.Close(); err != nil { return err } fmt.Println("The lnwallet has been created successfully.") return nil }
// This example demonstrates how to generate a cryptographically random seed // then use it to create a new master node (extended key). func ExampleNewMaster() { // Generate a random seed at the recommended length. seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen) if err != nil { fmt.Println(err) return } // Generate a new master node using the seed. key, err := hdkeychain.NewMaster(seed) if err != nil { fmt.Println(err) return } // Show that the generated master node extended key is private. fmt.Println("Private Extended Key?:", key.IsPrivate()) // Output: // Private Extended Key?: true }
// TestGenenerateSeed ensures the GenerateSeed function works as intended. func TestGenenerateSeed(t *testing.T) { wantErr := errors.New("seed length must be between 128 and 512 bits") tests := []struct { name string length uint8 err error }{ // Test various valid lengths. {name: "16 bytes", length: 16}, {name: "17 bytes", length: 17}, {name: "20 bytes", length: 20}, {name: "32 bytes", length: 32}, {name: "64 bytes", length: 64}, // Test invalid lengths. {name: "15 bytes", length: 15, err: wantErr}, {name: "65 bytes", length: 65, err: wantErr}, } for i, test := range tests { seed, err := hdkeychain.GenerateSeed(test.length) if !reflect.DeepEqual(err, test.err) { t.Errorf("GenerateSeed #%d (%s): unexpected error -- "+ "want %v, got %v", i, test.name, test.err, err) continue } if test.err == nil && len(seed) != int(test.length) { t.Errorf("GenerateSeed #%d (%s): length mismatch -- "+ "got %d, want %d", i, test.name, len(seed), test.length) continue } } }
// TestErrors performs some negative tests for various invalid cases to ensure // the errors are handled properly. func TestErrors(t *testing.T) { // Should get an error when seed has too few bytes. net := &chaincfg.MainNetParams _, err := hdkeychain.NewMaster(bytes.Repeat([]byte{0x00}, 15), net) if err != hdkeychain.ErrInvalidSeedLen { t.Errorf("NewMaster: mismatched error -- got: %v, want: %v", err, hdkeychain.ErrInvalidSeedLen) } // Should get an error when seed has too many bytes. _, err = hdkeychain.NewMaster(bytes.Repeat([]byte{0x00}, 65), net) if err != hdkeychain.ErrInvalidSeedLen { t.Errorf("NewMaster: mismatched error -- got: %v, want: %v", err, hdkeychain.ErrInvalidSeedLen) } // Generate a new key and neuter it to a public extended key. seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen) if err != nil { t.Errorf("GenerateSeed: unexpected error: %v", err) return } extKey, err := hdkeychain.NewMaster(seed, net) if err != nil { t.Errorf("NewMaster: unexpected error: %v", err) return } pubKey, err := extKey.Neuter() if err != nil { t.Errorf("Neuter: unexpected error: %v", err) return } // Deriving a hardened child extended key should fail from a public key. _, err = pubKey.Child(hdkeychain.HardenedKeyStart) if err != hdkeychain.ErrDeriveHardFromPublic { t.Errorf("Child: mismatched error -- got: %v, want: %v", err, hdkeychain.ErrDeriveHardFromPublic) } // NewKeyFromString failure tests. tests := []struct { name string key string err error neuter bool neuterErr error }{ { name: "invalid key length", key: "xpub1234", err: hdkeychain.ErrInvalidKeyLen, }, { name: "bad checksum", key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EBygr15", err: hdkeychain.ErrBadChecksum, }, { name: "pubkey not on curve", key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ1hr9Rwbk95YadvBkQXxzHBSngB8ndpW6QH7zhhsXZ2jHyZqPjk", err: errors.New("pubkey isn't on secp256k1 curve"), }, { name: "unsupported version", key: "xbad4LfUL9eKmA66w2GJdVMqhvDmYGJpTGjWRAtjHqoUY17sGaymoMV9Cm3ocn9Ud6Hh2vLFVC7KSKCRVVrqc6dsEdsTjRV1WUmkK85YEUujAPX", err: nil, neuter: true, neuterErr: chaincfg.ErrUnknownHDKeyID, }, } for i, test := range tests { extKey, err := hdkeychain.NewKeyFromString(test.key) if !reflect.DeepEqual(err, test.err) { t.Errorf("NewKeyFromString #%d (%s): mismatched error "+ "-- got: %v, want: %v", i, test.name, err, test.err) continue } if test.neuter { _, err := extKey.Neuter() if !reflect.DeepEqual(err, test.neuterErr) { t.Errorf("Neuter #%d (%s): mismatched error "+ "-- got: %v, want: %v", i, test.name, err, test.neuterErr) continue } } } }
// Seed prompts the user whether they want to use an existing wallet generation // seed. When the user answers no, a seed will be generated and displayed to // the user along with prompting them for confirmation. When the user answers // yes, a the user is prompted for it. All prompts are repeated until the user // enters a valid response. func Seed(reader *bufio.Reader) ([]byte, error) { // Ascertain the wallet generation seed. useUserSeed, err := promptListBool(reader, "Do you have an "+ "existing wallet seed you want to use?", "no") if err != nil { return nil, err } if !useUserSeed { seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen) if err != nil { return nil, err } fmt.Println("Your wallet generation seed is:") fmt.Printf("%x\n", seed) fmt.Println("IMPORTANT: Keep the seed in a safe place as you\n" + "will NOT be able to restore your wallet without it.") fmt.Println("Please keep in mind that anyone who has access\n" + "to the seed can also restore your wallet thereby\n" + "giving them access to all your funds, so it is\n" + "imperative that you keep it in a secure location.") for { fmt.Print(`Once you have stored the seed in a safe ` + `and secure location, enter "OK" to continue: `) confirmSeed, err := reader.ReadString('\n') if err != nil { return nil, err } confirmSeed = strings.TrimSpace(confirmSeed) confirmSeed = strings.Trim(confirmSeed, `"`) if confirmSeed == "OK" { break } } return seed, nil } for { fmt.Print("Enter existing wallet seed: ") seedStr, err := reader.ReadString('\n') if err != nil { return nil, err } seedStr = strings.TrimSpace(strings.ToLower(seedStr)) seed, err := hex.DecodeString(seedStr) if err != nil || len(seed) < hdkeychain.MinSeedBytes || len(seed) > hdkeychain.MaxSeedBytes { fmt.Printf("Invalid seed specified. Must be a "+ "hexadecimal value that is at least %d bits and "+ "at most %d bits\n", hdkeychain.MinSeedBytes*8, hdkeychain.MaxSeedBytes*8) continue } return seed, nil } }
// CreateNewWallet creates a new wallet using the provided public and private // passphrases. The seed is optional. If non-nil, addresses are derived from // this seed. If nil, a secure random seed is generated. func (l *Loader) CreateNewWallet(pubPassphrase, privPassphrase, seed []byte) (*Wallet, error) { defer l.mu.Unlock() l.mu.Lock() if l.wallet != nil { return nil, ErrLoaded } dbPath := filepath.Join(l.dbDirPath, walletDbName) exists, err := fileExists(dbPath) if err != nil { return nil, err } if exists { return nil, ErrExists } // Create the wallet database backed by bolt db. err = os.MkdirAll(l.dbDirPath, 0700) if err != nil { return nil, err } db, err := walletdb.Create("bdb", dbPath) if err != nil { return nil, err } // If a seed was provided, ensure that it is of valid length. Otherwise, // we generate a random seed for the wallet with the recommended seed // length. if seed != nil { if len(seed) < hdkeychain.MinSeedBytes || len(seed) > hdkeychain.MaxSeedBytes { return nil, hdkeychain.ErrInvalidSeedLen } } else { hdSeed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen) if err != nil { return nil, err } seed = hdSeed } // Create the address manager. addrMgrNamespace, err := db.Namespace(waddrmgrNamespaceKey) if err != nil { return nil, err } _, err = waddrmgr.Create(addrMgrNamespace, seed, pubPassphrase, privPassphrase, l.chainParams, nil) if err != nil { return nil, err } // Create empty transaction manager. txMgrNamespace, err := db.Namespace(wtxmgrNamespaceKey) if err != nil { return nil, err } _, err = wtxmgr.Create(txMgrNamespace) if err != nil { return nil, err } // Open the newly-created wallet. w, err := Open(pubPassphrase, l.chainParams, db, addrMgrNamespace, txMgrNamespace, nil) if err != nil { return nil, err } l.onLoaded(w, db) return w, nil }