From b44f48882fb27e4dda1c48b906e24dd58ab90b93 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 1 Oct 2014 12:15:32 -0500 Subject: [PATCH] Add conversion from legacy keystore if it exists. --- walletsetup.go | 151 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 145 insertions(+), 6 deletions(-) diff --git a/walletsetup.go b/walletsetup.go index fca7aa5..40d1f2f 100644 --- a/walletsetup.go +++ b/walletsetup.go @@ -24,7 +24,10 @@ import ( "path/filepath" "strings" + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/hdkeychain" + "github.com/btcsuite/btcwallet/legacy/keystore" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/walletdb" _ "github.com/btcsuite/btcwallet/walletdb/bdb" @@ -80,7 +83,7 @@ func promptConsoleListBool(reader *bufio.Reader, prefix string, defaultEntry str // promptConsolePass prompts the user for a passphrase with the given prefix. // The function will ask the user to confirm the passphrase and will repeat // the prompts until they enter a matching response. -func promptConsolePass(reader *bufio.Reader, prefix string) (string, error) { +func promptConsolePass(reader *bufio.Reader, prefix string, confirm bool) (string, error) { // Prompt the user until they enter a passphrase. prompt := fmt.Sprintf("%s: ", prefix) for { @@ -94,6 +97,10 @@ func promptConsolePass(reader *bufio.Reader, prefix string) (string, error) { continue } + if !confirm { + return pass, nil + } + fmt.Print("Confirm passphrase: ") confirm, err := reader.ReadString('\n') if err != nil { @@ -109,6 +116,47 @@ func promptConsolePass(reader *bufio.Reader, prefix string) (string, error) { } } +// promptConsolePrivatePass prompts the user for a private passphrase with +// varying behavior depending on whether the passed legacy keystore exists. +// When it does, the user is prompted for the existing passphrase which is then +// used to unlock it. On the other hand, when the legacy keystore is nil, the +// user is prompted for a new private passphrase. All prompts are repeated +// until the user enters a valid response. +func promptConsolePrivatePass(reader *bufio.Reader, legacyKeyStore *keystore.Store) (string, error) { + // When there is not an existing legacy wallet, simply prompt the user + // for a new private passphase and return it. + if legacyKeyStore == nil { + return promptConsolePass(reader, "Enter the private "+ + "passphrase for your new wallet", true) + } + + // At this point, there is an existing legacy wallet, so prompt the user + // for the existing private passphrase and ensure it properly unlocks + // the legacy wallet so all of the addresses can later be imported. + fmt.Println("You have an existing legacy wallet. All addresses from " + + "your existing legacy wallet will be imported into the new " + + "wallet format.") + for { + privPass, err := promptConsolePass(reader, "Enter the private "+ + "passphrase for your existing wallet", false) + if err != nil { + return "", err + } + + // Keep prompting the user until the passphrase is correct. + if err := legacyKeyStore.Unlock([]byte(privPass)); err != nil { + if err == keystore.ErrWrongPassphrase { + fmt.Println(err) + continue + } + + return "", err + } + + return privPass, nil + } +} + // promptConsolePublicPass prompts the user whether they want to add an // additional layer of encryption to the wallet. When the user answers yes and // there is already a public passphrase provided via the passed config, it @@ -145,7 +193,7 @@ func promptConsolePublicPass(reader *bufio.Reader, privPass string, cfg *config) for { pubPass, err = promptConsolePass(reader, "Enter the public "+ - "passphrase for your new wallet") + "passphrase for your new wallet", true) if err != nil { return "", err } @@ -240,14 +288,85 @@ func promptConsoleSeed(reader *bufio.Reader) ([]byte, error) { } } +// convertLegacyKeystore converts all of the addresses in the passed legacy +// key store to the new waddrmgr.Manager format. Both the legacy keystore and +// the new manager must be unlocked. +func convertLegacyKeystore(legacyKeyStore *keystore.Store, manager *waddrmgr.Manager) error { + netParams := legacyKeyStore.Net() + blockStamp := waddrmgr.BlockStamp{ + Height: 0, + Hash: *netParams.GenesisHash, + } + for _, walletAddr := range legacyKeyStore.ActiveAddresses() { + switch addr := walletAddr.(type) { + case keystore.PubKeyAddress: + privKey, err := addr.PrivKey() + if err != nil { + fmt.Printf("WARN: Failed to obtain private key "+ + "for address %v: %v\n", addr.Address(), + err) + continue + } + + wif, err := btcutil.NewWIF((*btcec.PrivateKey)(privKey), + netParams, addr.Compressed()) + if err != nil { + fmt.Printf("WARN: Failed to create wallet "+ + "import format for address %v: %v\n", + addr.Address(), err) + continue + } + + _, err = manager.ImportPrivateKey(wif, &blockStamp) + if err != nil { + fmt.Printf("WARN: Failed to import private "+ + "key for address %v: %v\n", + addr.Address(), err) + continue + } + + case keystore.ScriptAddress: + _, err := manager.ImportScript(addr.Script(), &blockStamp) + if err != nil { + fmt.Printf("WARN: Failed to import "+ + "pay-to-script-hash script for "+ + "address %v: %v\n", addr.Address(), err) + continue + } + + default: + fmt.Printf("WARN: Skipping unrecognized legacy "+ + "keystore type: %T\n", addr) + continue + } + } + + return nil +} + // createWallet prompts the user for information needed to generate a new wallet // and generates the wallet accordingly. The new wallet will reside at the // provided path. func createWallet(cfg *config) error { - // Start by prompting for the private passphrase. + // When there is a legacy keystore, open it now to ensure any errors + // don't end up exiting the process after the user has spent time + // entering a bunch of information. + netDir := networkDir(cfg.DataDir, activeNet.Params) + keystorePath := filepath.Join(netDir, keystore.Filename) + var legacyKeyStore *keystore.Store + if fileExists(keystorePath) { + var err error + legacyKeyStore, err = keystore.OpenDir(netDir) + if err != nil { + return err + } + } + + // Start by prompting for the private passphrase. When there is an + // existing keystore, the user will be promped for that passphrase, + // otherwise they will be prompted for a new one. reader := bufio.NewReader(os.Stdin) - privPass, err := promptConsolePass(reader, "Enter the private passphrase "+ - "for your new wallet") + privPass, err := promptConsolePrivatePass(reader, legacyKeyStore) if err != nil { return err } @@ -269,7 +388,6 @@ func createWallet(cfg *config) error { } // Create the wallet. - netDir := networkDir(cfg.DataDir, activeNet.Params) dbPath := filepath.Join(netDir, walletDbName) fmt.Println("Creating the wallet...") @@ -290,6 +408,27 @@ func createWallet(cfg *config) error { return err } + // Import the addresses in the legacy keystore to the new wallet if + // any exist. + if legacyKeyStore != nil { + fmt.Println("Importing addresses from existing wallet...") + if err := manager.Unlock([]byte(privPass)); err != nil { + return err + } + if err := convertLegacyKeystore(legacyKeyStore, manager); err != nil { + return err + } + + legacyKeyStore.Lock() + legacyKeyStore = nil + + // Remove the legacy key store. + if err := os.Remove(keystorePath); err != nil { + fmt.Printf("WARN: Failed to remove legacy wallet "+ + "from'%s'\n", keystorePath) + } + } + manager.Close() fmt.Println("The wallet has been created successfully.") return nil