forked from btcsuite/btcd
-
Notifications
You must be signed in to change notification settings - Fork 27
/
prompt.go
287 lines (258 loc) · 8.51 KB
/
prompt.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
// Copyright (c) 2015-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package prompt
import (
"bufio"
"bytes"
"encoding/hex"
"fmt"
"os"
"strings"
"github.com/pkt-cash/pktd/btcutil/er"
"github.com/pkt-cash/pktd/btcutil/hdkeychain"
"github.com/pkt-cash/pktd/pktwallet/internal/legacy/keystore"
"github.com/pkt-cash/pktd/pktwallet/wallet/seedwords"
"golang.org/x/crypto/ssh/terminal"
)
// ProvideSeed is used to prompt for the wallet seed which maybe required during
// upgrades.
func ProvideSeed() ([]byte, er.R) {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("Enter existing wallet seed: ")
seedStr, err := reader.ReadString('\n')
if err != nil {
return nil, er.E(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
}
}
// ProvidePrivPassphrase is used to prompt for the private passphrase which
// maybe required during upgrades.
func ProvidePrivPassphrase() ([]byte, er.R) {
prompt := "Enter the private passphrase of your wallet: "
for {
fmt.Print(prompt)
pass, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return nil, er.E(err)
}
fmt.Print("\n")
pass = bytes.TrimSpace(pass)
if len(pass) == 0 {
continue
}
return pass, nil
}
}
// promptList prompts the user with the given prefix, list of valid responses,
// and default list entry to use. The function will repeat the prompt to the
// user until they enter a valid response.
func promptList(reader *bufio.Reader, prefix string, validResponses []string, defaultEntry string) (string, er.R) {
// Setup the prompt according to the parameters.
validStrings := strings.Join(validResponses, "/")
var prompt string
if defaultEntry != "" {
prompt = fmt.Sprintf("%s (%s) [%s]: ", prefix, validStrings,
defaultEntry)
} else {
prompt = fmt.Sprintf("%s (%s): ", prefix, validStrings)
}
// Prompt the user until one of the valid responses is given.
for {
fmt.Print(prompt)
reply, err := reader.ReadString('\n')
if err != nil {
return "", er.E(err)
}
reply = strings.TrimSpace(strings.ToLower(reply))
if reply == "" {
reply = defaultEntry
}
for _, validResponse := range validResponses {
if reply == validResponse {
return reply, nil
}
}
}
}
// promptListBool prompts the user for a boolean (yes/no) with the given prefix.
// The function will repeat the prompt to the user until they enter a valid
// reponse.
func promptListBool(reader *bufio.Reader, prefix string, defaultEntry string) (bool, er.R) {
// Setup the valid responses.
valid := []string{"n", "no", "y", "yes"}
response, err := promptList(reader, prefix, valid, defaultEntry)
if err != nil {
return false, err
}
return response == "yes" || response == "y", nil
}
// promptPass 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 promptPass(reader *bufio.Reader, prefix string, confirm bool) ([]byte, er.R) {
// Prompt the user until they enter a passphrase.
prompt := fmt.Sprintf("%s: ", prefix)
for {
fmt.Print(prompt)
pass, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return nil, er.E(err)
}
fmt.Print("\n")
pass = bytes.TrimSpace(pass)
if len(pass) == 0 {
continue
}
if !confirm {
return pass, nil
}
fmt.Print("Confirm passphrase: ")
confirm, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return nil, er.E(err)
}
fmt.Print("\n")
confirm = bytes.TrimSpace(confirm)
if !bytes.Equal(pass, confirm) {
fmt.Println("The entered passphrases do not match")
continue
}
return pass, nil
}
}
// PrivatePass 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 PrivatePass(reader *bufio.Reader, legacyKeyStore *keystore.Store) ([]byte, er.R) {
// When there is not an existing legacy wallet, simply prompt the user
// for a new private passphase and return it.
if legacyKeyStore == nil {
return promptPass(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 := promptPass(reader, "Enter the private "+
"passphrase for your existing wallet", false)
if err != nil {
return nil, err
}
// Keep prompting the user until the passphrase is correct.
if err := legacyKeyStore.Unlock([]byte(privPass)); err != nil {
if keystore.ErrWrongPassphrase.Is(err) {
fmt.Println(err)
continue
}
return nil, err
}
return privPass, nil
}
}
// 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, passphrase []byte) ([]byte, *seedwords.Seed, er.R) {
// 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, nil, err
}
if !useUserSeed {
seed, err := seedwords.RandomSeed()
if err != nil {
return nil, nil, err
}
fmt.Println("Encrypting your seed...")
seedEnc := seed.Encrypt(passphrase)
words, err := seedEnc.Words("english")
if err != nil {
return nil, nil, err
}
seedEnc.Zero()
fmt.Println("Your wallet generation seed is:")
fmt.Printf("\n%s\n\n", words)
fmt.Println("IMPORTANT: Keep the seed in a safe place.\n" +
"If your wallet is destroyed, you can recover it as long as\n" +
"you have this seed and your wallet passphrase.")
fmt.Println("Please keep in mind that anyone who has access\n" +
"to the seed only needs to guess your wallet passphrase to\n" +
"access your funds.")
fmt.Println("The seed is encrypted using your wallet passphrase\n" +
"YOU MUST REMEMBER YOUR WALLET PASSPHRASE TO RESTORE FROM SEED.\n")
for {
fmt.Print(`Once you have stored the seed in a safe ` +
`and secure location, type "OK" to continue: `)
confirmSeed, err := reader.ReadString('\n')
if err != nil {
return nil, nil, er.E(err)
}
confirmSeed = strings.TrimSpace(confirmSeed)
confirmSeed = strings.Trim(confirmSeed, `"`)
if confirmSeed == "OK" {
break
}
}
return nil, seed, nil
}
for {
fmt.Print("Enter existing wallet seed: ")
seedStr, err := reader.ReadString('\n')
if err != nil {
return nil, nil, er.E(err)
}
seedStr = strings.TrimSpace(strings.ToLower(seedStr))
if seed, err := hex.DecodeString(seedStr); err != nil {
} else if len(seed) < hdkeychain.MinSeedBytes {
} else if len(seed) > hdkeychain.MaxSeedBytes {
} else {
return []byte(seedStr), nil, nil
}
if sw, err := seedwords.SeedFromWords(seedStr); err != nil {
fmt.Printf("Invalid seed specified [%s]", err.Message())
} else if sw.NeedsPassphrase() {
fmt.Println("This seed was taken from a wallet protected by a password.")
for {
pass, err := promptPass(reader, "Enter the wallet password now", false)
if err != nil {
return nil, nil, err
}
fmt.Println("Decrypting your seed...")
if seed, err := sw.Decrypt(pass, false); err != nil {
fmt.Println("The seed did not decrypt properly, please try again.")
} else {
return nil, seed, nil
}
}
} else {
if seed, err := sw.Decrypt(nil, false); err != nil {
return nil, nil, err
} else {
return nil, seed, nil
}
}
}
}