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 }