decred.org/dcrwallet/v3@v3.1.0/walletsetup.go (about)

     1  // Copyright (c) 2014-2015 The btcsuite developers
     2  // Copyright (c) 2015-2018 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package main
     7  
     8  import (
     9  	"bufio"
    10  	"context"
    11  	"fmt"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  
    16  	"decred.org/dcrwallet/v3/errors"
    17  	"decred.org/dcrwallet/v3/internal/loader"
    18  	"decred.org/dcrwallet/v3/internal/prompt"
    19  	"decred.org/dcrwallet/v3/wallet"
    20  	_ "decred.org/dcrwallet/v3/wallet/drivers/bdb"
    21  	"decred.org/dcrwallet/v3/walletseed"
    22  	"github.com/decred/dcrd/chaincfg/v3"
    23  	"github.com/decred/dcrd/dcrutil/v4"
    24  	"github.com/decred/dcrd/hdkeychain/v3"
    25  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    26  	"github.com/decred/dcrd/wire"
    27  )
    28  
    29  // networkDir returns the directory name of a network directory to hold wallet
    30  // files.
    31  func networkDir(dataDir string, chainParams *chaincfg.Params) string {
    32  	netname := chainParams.Name
    33  	// Be cautious of v2+ testnets being named only "testnet".
    34  	switch chainParams.Net {
    35  	case 0x48e7a065: // testnet2
    36  		netname = "testnet2"
    37  	case wire.TestNet3:
    38  		netname = "testnet3"
    39  	}
    40  	return filepath.Join(dataDir, netname)
    41  }
    42  
    43  // createWallet prompts the user for information needed to generate a new wallet
    44  // and generates the wallet accordingly.  The new wallet will reside at the
    45  // provided path. The bool passed back gives whether or not the wallet was
    46  // restored from seed, while the []byte passed is the private password required
    47  // to do the initial sync.
    48  func createWallet(ctx context.Context, cfg *config) error {
    49  	dbDir := networkDir(cfg.AppDataDir.Value, activeNet.Params)
    50  	stakeOptions := &loader.StakeOptions{
    51  		VotingEnabled: cfg.EnableVoting,
    52  		VotingAddress: cfg.TBOpts.votingAddress,
    53  	}
    54  	loader := loader.NewLoader(activeNet.Params, dbDir, stakeOptions,
    55  		cfg.GapLimit, cfg.WatchLast, cfg.AllowHighFees, cfg.RelayFee.Amount,
    56  		cfg.AccountGapLimit, cfg.DisableCoinTypeUpgrades, cfg.ManualTickets,
    57  		cfg.MixSplitLimit)
    58  
    59  	var privPass, pubPass, seed []byte
    60  	var imported bool
    61  	var err error
    62  	c := make(chan struct{}, 1)
    63  	go func() {
    64  		defer func() { c <- struct{}{} }()
    65  		r := bufio.NewReader(os.Stdin)
    66  
    67  		// Start by prompting for the private passphrase.  This function
    68  		// prompts whether any configured private passphrase should be
    69  		// used.
    70  		privPass, err = prompt.PrivatePass(r, []byte(cfg.Pass))
    71  		if err != nil {
    72  			return
    73  		}
    74  
    75  		// Ascertain the public passphrase.  This will either be a value
    76  		// specified by the user or the default hard-coded public passphrase if
    77  		// the user does not want the additional public data encryption.
    78  		// This function also prompts whether the configured public data
    79  		// passphrase should be used.
    80  		pubPass, err = prompt.PublicPass(r, privPass,
    81  			[]byte(wallet.InsecurePubPassphrase), []byte(cfg.WalletPass))
    82  		if err != nil {
    83  			return
    84  		}
    85  
    86  		// Ascertain the wallet generation seed.  This will either be an
    87  		// automatically generated value the user has already confirmed or a
    88  		// value the user has entered which has already been validated.
    89  		// There is no config flag to set the seed.
    90  		seed, imported, err = prompt.Seed(r)
    91  	}()
    92  	select {
    93  	case <-ctx.Done():
    94  		return ctx.Err()
    95  	case <-c:
    96  		if err != nil {
    97  			return err
    98  		}
    99  	}
   100  
   101  	fmt.Println("Creating the wallet...")
   102  	w, err := loader.CreateNewWallet(ctx, pubPass, privPass, seed)
   103  	if err != nil {
   104  		return err
   105  	}
   106  
   107  	// Upgrade to the SLIP0044 cointype if this is a new (rather than
   108  	// user-provided) seed, and also unconditionally on simnet (to prevent
   109  	// the mining address printed below from ever becoming invalid if a
   110  	// cointype upgrade occurred later).
   111  	if !imported || cfg.SimNet {
   112  		err := w.UpgradeToSLIP0044CoinType(ctx)
   113  		if err != nil {
   114  			return err
   115  		}
   116  	}
   117  
   118  	// Display a mining address when creating a simnet wallet.
   119  	if cfg.SimNet {
   120  		xpub, err := w.AccountXpub(ctx, 0)
   121  		if err != nil {
   122  			return err
   123  		}
   124  		branch, err := xpub.Child(0)
   125  		if err != nil {
   126  			return err
   127  		}
   128  		child, err := branch.Child(0)
   129  		if err != nil {
   130  			return err
   131  		}
   132  		pkh := dcrutil.Hash160(child.SerializedPubKey())
   133  		addr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(pkh,
   134  			chaincfg.SimNetParams())
   135  		if err != nil {
   136  			return err
   137  		}
   138  		fmt.Println("Mining address:", addr)
   139  	}
   140  
   141  	err = loader.UnloadWallet()
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	fmt.Println("The wallet has been created successfully.")
   147  	return nil
   148  }
   149  
   150  // createSimulationWallet is intended to be called from the rpcclient
   151  // and used to create a wallet for actors involved in simulations.
   152  func createSimulationWallet(ctx context.Context, cfg *config) error {
   153  	// Simulation wallet password is 'password'.
   154  	privPass := wallet.SimulationPassphrase
   155  
   156  	// Public passphrase is the default.
   157  	pubPass := []byte(wallet.InsecurePubPassphrase)
   158  
   159  	// Generate a random seed.
   160  	seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen)
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	netDir := networkDir(cfg.AppDataDir.Value, activeNet.Params)
   166  
   167  	// Write the seed to disk, so that we can restore it later
   168  	// if need be, for testing purposes.
   169  	seedStr := walletseed.EncodeMnemonic(seed)
   170  	err = os.WriteFile(filepath.Join(netDir, "seed"), []byte(seedStr), 0644)
   171  	if err != nil {
   172  		return err
   173  	}
   174  
   175  	// Create the wallet.
   176  	dbPath := filepath.Join(netDir, walletDbName)
   177  	fmt.Println("Creating the wallet...")
   178  
   179  	// Create the wallet database backed by bolt db.
   180  	db, err := wallet.CreateDB("bdb", dbPath)
   181  	if err != nil {
   182  		return err
   183  	}
   184  	defer db.Close()
   185  
   186  	// Create the wallet.
   187  	err = wallet.Create(ctx, db, pubPass, privPass, seed, activeNet.Params)
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	fmt.Println("The wallet has been created successfully.")
   193  	return nil
   194  }
   195  
   196  // promptHDPublicKey prompts the user for an extended public key.
   197  func promptHDPublicKey(reader *bufio.Reader) (string, error) {
   198  	fmt.Print("Enter HD wallet public key: ")
   199  	keyString, err := reader.ReadString('\n')
   200  	if err != nil {
   201  		return "", err
   202  	}
   203  
   204  	keyStringTrimmed := strings.TrimSpace(keyString)
   205  	return keyStringTrimmed, nil
   206  }
   207  
   208  // createWatchingOnlyWallet creates a watching only wallet using the passed
   209  // extended public key.
   210  func createWatchingOnlyWallet(ctx context.Context, cfg *config) error {
   211  	// Get the public key.
   212  	reader := bufio.NewReader(os.Stdin)
   213  	pubKeyString, err := promptHDPublicKey(reader)
   214  	if err != nil {
   215  		return err
   216  	}
   217  
   218  	// Ask if the user wants to encrypt the wallet with a password.
   219  	pubPass, err := prompt.PublicPass(reader, []byte{},
   220  		[]byte(wallet.InsecurePubPassphrase), []byte(cfg.WalletPass))
   221  	if err != nil {
   222  		return err
   223  	}
   224  
   225  	netDir := networkDir(cfg.AppDataDir.Value, activeNet.Params)
   226  
   227  	// Create the wallet.
   228  	dbPath := filepath.Join(netDir, walletDbName)
   229  	fmt.Println("Creating the wallet...")
   230  
   231  	// Create the wallet database backed by bolt db.
   232  	db, err := wallet.CreateDB("bdb", dbPath)
   233  	if err != nil {
   234  		return err
   235  	}
   236  	defer db.Close()
   237  
   238  	err = wallet.CreateWatchOnly(ctx, db, pubKeyString, pubPass, activeNet.Params)
   239  	if err != nil {
   240  		errOS := os.Remove(dbPath)
   241  		if errOS != nil {
   242  			fmt.Println(errOS)
   243  		}
   244  		return err
   245  	}
   246  
   247  	fmt.Println("The watching only wallet has been created successfully.")
   248  	return nil
   249  }
   250  
   251  // checkCreateDir checks that the path exists and is a directory.
   252  // If path does not exist, it is created.
   253  func checkCreateDir(path string) error {
   254  	if fi, err := os.Stat(path); err != nil {
   255  		if os.IsNotExist(err) {
   256  			// Attempt data directory creation
   257  			if err = os.MkdirAll(path, 0700); err != nil {
   258  				return errors.Errorf("cannot create directory: %s", err)
   259  			}
   260  		} else {
   261  			return errors.Errorf("error checking directory: %s", err)
   262  		}
   263  	} else {
   264  		if !fi.IsDir() {
   265  			return errors.Errorf("path '%s' is not a directory", path)
   266  		}
   267  	}
   268  
   269  	return nil
   270  }