Implement 'wallets restore' and 'wallets list'; Update 'nextAccount' properly after making deposits multiple times
This commit is contained in:
parent
1d1e0217ca
commit
8809f8d200
|
@ -1161,26 +1161,33 @@ programMain:
|
|||
of deposits:
|
||||
case config.depositsCmd
|
||||
of DepositsCmd.create:
|
||||
var walletData = if config.existingWalletId.isSome:
|
||||
var mnemonic: Mnemonic
|
||||
defer: burnMem(mnemonic)
|
||||
var walletPath: WalletPathPair
|
||||
|
||||
if config.existingWalletId.isSome:
|
||||
let id = config.existingWalletId.get
|
||||
let found = keystore_management.findWallet(config, id)
|
||||
if found.isErr:
|
||||
if found.isOk:
|
||||
walletPath = found.get
|
||||
else:
|
||||
fatal "Unable to find wallet with the specified name/uuid",
|
||||
id, err = found.error
|
||||
quit 1
|
||||
let unlocked = unlockWalletInteractively(found.get)
|
||||
var unlocked = unlockWalletInteractively(walletPath.wallet)
|
||||
if unlocked.isOk:
|
||||
unlocked.get
|
||||
swap(mnemonic, unlocked.get)
|
||||
else:
|
||||
# The failure will be reported in `unlockWalletInteractively`.
|
||||
quit 1
|
||||
else:
|
||||
let walletData = createWalletInteractively(rng[], config)
|
||||
if walletData.isErr:
|
||||
fatal "Unable to create wallet", err = walletData.error
|
||||
var walletRes = createWalletInteractively(rng[], config)
|
||||
if walletRes.isErr:
|
||||
fatal "Unable to create wallet", err = walletRes.error
|
||||
quit 1
|
||||
walletData.get
|
||||
|
||||
defer: burnMem(walletData.mnemonic)
|
||||
else:
|
||||
swap(mnemonic, walletRes.get.mnemonic)
|
||||
walletPath = walletRes.get.walletPath
|
||||
|
||||
createDir(config.outValidatorsDir)
|
||||
createDir(config.outSecretsDir)
|
||||
|
@ -1188,7 +1195,8 @@ programMain:
|
|||
let deposits = generateDeposits(
|
||||
config.runtimePreset,
|
||||
rng[],
|
||||
walletData,
|
||||
mnemonic,
|
||||
walletPath.wallet.nextAccount,
|
||||
config.totalDeposits,
|
||||
config.outValidatorsDir,
|
||||
config.outSecretsDir)
|
||||
|
@ -1208,6 +1216,14 @@ programMain:
|
|||
|
||||
Json.saveFile(depositDataPath, launchPadDeposits)
|
||||
info "Deposit data written", filename = depositDataPath
|
||||
|
||||
walletPath.wallet.nextAccount += deposits.value.len
|
||||
let status = saveWallet(walletPath)
|
||||
if status.isErr:
|
||||
error "Failed to update wallet file after generating deposits",
|
||||
wallet = walletPath.path,
|
||||
error = status.error
|
||||
quit 1
|
||||
except CatchableError as err:
|
||||
error "Failed to create launchpad deposit data file", err = err.msg
|
||||
quit 1
|
||||
|
@ -1225,15 +1241,22 @@ programMain:
|
|||
of wallets:
|
||||
case config.walletsCmd:
|
||||
of WalletsCmd.create:
|
||||
let status = createWalletInteractively(rng[], config)
|
||||
if status.isErr:
|
||||
fatal "Unable to create wallet", err = status.error
|
||||
var walletRes = createWalletInteractively(rng[], config)
|
||||
if walletRes.isErr:
|
||||
fatal "Unable to create wallet", err = walletRes.error
|
||||
quit 1
|
||||
burnMem(walletRes.get.mnemonic)
|
||||
|
||||
of WalletsCmd.list:
|
||||
# TODO
|
||||
discard
|
||||
for kind, walletFile in walkDir(config.walletsDir):
|
||||
if kind != pcFile: continue
|
||||
let walletRes = loadWallet(walletFile)
|
||||
if walletRes.isOk:
|
||||
echo walletRes.get.longName
|
||||
else:
|
||||
warn "Found corrupt wallet file",
|
||||
wallet = walletFile, error = walletRes.error
|
||||
|
||||
of WalletsCmd.restore:
|
||||
# TODO
|
||||
discard
|
||||
restoreWalletInteractively(rng[], config)
|
||||
|
||||
|
|
|
@ -170,7 +170,7 @@ proc main() {.async.} =
|
|||
|
||||
if cfg.cmd == StartUpCommand.generateSimulationDeposits:
|
||||
let
|
||||
walletData = WalletDataForDeposits(mnemonic: generateMnemonic(rng[]))
|
||||
mnemonic = generateMnemonic(rng[])
|
||||
runtimePreset = defaultRuntimePreset
|
||||
|
||||
createDir(string cfg.outValidatorsDir)
|
||||
|
@ -179,8 +179,8 @@ proc main() {.async.} =
|
|||
let deposits = generateDeposits(
|
||||
runtimePreset,
|
||||
rng[],
|
||||
walletData,
|
||||
cfg.simulationDepositsCount,
|
||||
mnemonic,
|
||||
0, cfg.simulationDepositsCount,
|
||||
string cfg.outValidatorsDir,
|
||||
string cfg.outSecretsDir)
|
||||
|
||||
|
|
|
@ -14,9 +14,13 @@ const
|
|||
keystoreFileName* = "keystore.json"
|
||||
|
||||
type
|
||||
WalletDataForDeposits* = object
|
||||
WalletPathPair* = object
|
||||
wallet*: Wallet
|
||||
path*: string
|
||||
|
||||
CreatedWallet* = object
|
||||
walletPath*: WalletPathPair
|
||||
mnemonic*: Mnemonic
|
||||
nextAccount*: Natural
|
||||
|
||||
proc loadKeystore(conf: BeaconNodeConf|ValidatorClientConf,
|
||||
validatorsDir, keyName: string): Option[ValidatorPrivKey] =
|
||||
|
@ -134,22 +138,22 @@ proc saveKeystore(rng: var BrHmacDrbgContext,
|
|||
|
||||
proc generateDeposits*(preset: RuntimePreset,
|
||||
rng: var BrHmacDrbgContext,
|
||||
walletData: WalletDataForDeposits,
|
||||
totalValidators: int,
|
||||
mnemonic: Mnemonic,
|
||||
firstValidatorIdx, totalNewValidators: int,
|
||||
validatorsDir: string,
|
||||
secretsDir: string): Result[seq[DepositData], KeystoreGenerationError] =
|
||||
var deposits: seq[DepositData]
|
||||
|
||||
info "Generating deposits", totalValidators, validatorsDir, secretsDir
|
||||
info "Generating deposits", totalNewValidators, validatorsDir, secretsDir
|
||||
|
||||
let withdrawalKeyPath = makeKeyPath(0, withdrawalKeyKind)
|
||||
# TODO: Explain why we are using an empty password
|
||||
var withdrawalKey = keyFromPath(walletData.mnemonic, KeystorePass"", withdrawalKeyPath)
|
||||
var withdrawalKey = keyFromPath(mnemonic, KeystorePass"", withdrawalKeyPath)
|
||||
defer: burnMem(withdrawalKey)
|
||||
let withdrawalPubKey = withdrawalKey.toPubKey
|
||||
|
||||
for i in 0 ..< totalValidators:
|
||||
let keyStoreIdx = walletData.nextAccount + i
|
||||
for i in 0 ..< totalNewValidators:
|
||||
let keyStoreIdx = firstValidatorIdx + i
|
||||
let signingKeyPath = withdrawalKeyPath.append keyStoreIdx
|
||||
var signingKey = deriveChildKey(withdrawalKey, keyStoreIdx)
|
||||
defer: burnMem(signingKey)
|
||||
|
@ -168,7 +172,7 @@ const
|
|||
mostCommonPasswords = wordListArray(
|
||||
currentSourcePath.parentDir /
|
||||
"../vendor/nimbus-security-resources/passwords/10-million-password-list-top-100000.txt",
|
||||
minWordLength = minPasswordLen)
|
||||
minWordLen = minPasswordLen)
|
||||
|
||||
proc saveWallet*(wallet: Wallet, outWalletPath: string): Result[void, string] =
|
||||
try: createDir splitFile(outWalletPath).dir
|
||||
|
@ -186,6 +190,9 @@ proc saveWallet*(wallet: Wallet, outWalletPath: string): Result[void, string] =
|
|||
|
||||
ok()
|
||||
|
||||
proc saveWallet*(wallet: WalletPathPair): Result[void, string] =
|
||||
saveWallet(wallet.wallet, wallet.path)
|
||||
|
||||
proc readPasswordInput(prompt: string, password: var TaintedString): bool =
|
||||
try:
|
||||
when defined(windows):
|
||||
|
@ -282,58 +289,19 @@ proc importKeystoresFromDir*(rng: var BrHmacDrbgContext,
|
|||
fatal "Failed to access the imported deposits directory"
|
||||
quit 1
|
||||
|
||||
proc createWalletInteractively*(
|
||||
rng: var BrHmacDrbgContext,
|
||||
conf: BeaconNodeConf): Result[WalletDataForDeposits, string] =
|
||||
|
||||
if conf.nonInteractive:
|
||||
return err "not running in interactive mode"
|
||||
|
||||
var mnemonic = generateMnemonic(rng)
|
||||
defer: burnMem(mnemonic)
|
||||
|
||||
template ask(prompt: string): string =
|
||||
try:
|
||||
stdout.write prompt, ": "
|
||||
stdin.readLine()
|
||||
except IOError:
|
||||
return err "failure to read data from stdin"
|
||||
|
||||
template echo80(msg: string) =
|
||||
echo wrapWords(msg, 80)
|
||||
|
||||
echo80 "The generated wallet is uniquely identified by a seed phrase " &
|
||||
"consisting of 24 words. In case you lose your wallet and you " &
|
||||
"need to restore it on a different machine, you must use the " &
|
||||
"words displayed below:"
|
||||
template echo80(msg: string) =
|
||||
echo wrapWords(msg, 80)
|
||||
|
||||
template ask(prompt: string): string =
|
||||
try:
|
||||
echo ""
|
||||
setStyleNoError({styleBright})
|
||||
setForegroundColorNoError fgCyan
|
||||
echo80 $mnemonic
|
||||
resetAttributesNoError()
|
||||
echo ""
|
||||
except IOError, ValueError:
|
||||
return err "failure to write to the standard output"
|
||||
|
||||
echo80 "Please back up the seed phrase now to a safe location as " &
|
||||
"if you are protecting a sensitive password. The seed phrase " &
|
||||
"can be used to withdrawl funds from your wallet."
|
||||
|
||||
echo ""
|
||||
echo "Did you back up your seed recovery phrase?\p" &
|
||||
"(please type 'yes' to continue or press enter to quit)"
|
||||
|
||||
while true:
|
||||
let answer = ask "Answer"
|
||||
if answer == "":
|
||||
return err "aborted wallet creation"
|
||||
elif answer != "yes":
|
||||
echo "To continue, please type 'yes' (without the quotes) or press enter to quit"
|
||||
else:
|
||||
break
|
||||
stdout.write prompt, ": "
|
||||
stdin.readLine()
|
||||
except IOError:
|
||||
return err "failure to read data from stdin"
|
||||
|
||||
proc pickPasswordAndSaveWallet(rng: var BrHmacDrbgContext,
|
||||
config: BeaconNodeConf,
|
||||
mnemonic: Mnemonic): Result[WalletPathPair, string] =
|
||||
echo ""
|
||||
echo80 "When you perform operations with your wallet such as withdrawals " &
|
||||
"and additional deposits, you'll be asked to enter a password. " &
|
||||
|
@ -378,7 +346,7 @@ proc createWalletInteractively*(
|
|||
continue
|
||||
|
||||
var name: WalletName
|
||||
let outWalletName = conf.outWalletName
|
||||
let outWalletName = config.outWalletName
|
||||
if outWalletName.isSome:
|
||||
name = outWalletName.get
|
||||
else:
|
||||
|
@ -396,33 +364,107 @@ proc createWalletInteractively*(
|
|||
continue
|
||||
break
|
||||
|
||||
let wallet = createWallet(kdfPbkdf2, rng, mnemonic,
|
||||
name = name, password = KeystorePass password)
|
||||
let nextAccount = if config.walletsCmd == WalletsCmd.restore:
|
||||
config.restoredDepositsCount
|
||||
else:
|
||||
none Natural
|
||||
|
||||
let outWalletFileFlag = conf.outWalletFile
|
||||
let wallet = createWallet(kdfPbkdf2, rng, mnemonic,
|
||||
name = name,
|
||||
nextAccount = nextAccount,
|
||||
password = KeystorePass password)
|
||||
|
||||
let outWalletFileFlag = config.outWalletFile
|
||||
let outWalletFile = if outWalletFileFlag.isSome:
|
||||
string outWalletFileFlag.get
|
||||
else:
|
||||
conf.walletsDir / addFileExt(string wallet.uuid, "json")
|
||||
config.walletsDir / addFileExt(string wallet.uuid, "json")
|
||||
|
||||
let status = saveWallet(wallet, outWalletFile)
|
||||
if status.isErr:
|
||||
return err("failure to create wallet file due to " & status.error)
|
||||
|
||||
info "Wallet file written", path = outWalletFile
|
||||
return ok WalletDataForDeposits(mnemonic: mnemonic, nextAccount: 0)
|
||||
|
||||
return ok WalletPathPair(wallet: wallet, path: outWalletFile)
|
||||
finally:
|
||||
burnMem(password)
|
||||
burnMem(confirmedPassword)
|
||||
|
||||
proc createWalletInteractively*(
|
||||
rng: var BrHmacDrbgContext,
|
||||
config: BeaconNodeConf): Result[CreatedWallet, string] =
|
||||
|
||||
if config.nonInteractive:
|
||||
return err "not running in interactive mode"
|
||||
|
||||
var mnemonic = generateMnemonic(rng)
|
||||
defer: burnMem(mnemonic)
|
||||
|
||||
echo80 "The generated wallet is uniquely identified by a seed phrase " &
|
||||
"consisting of 24 words. In case you lose your wallet and you " &
|
||||
"need to restore it on a different machine, you must use the " &
|
||||
"words displayed below:"
|
||||
|
||||
try:
|
||||
echo ""
|
||||
setStyleNoError({styleBright})
|
||||
setForegroundColorNoError fgCyan
|
||||
echo80 $mnemonic
|
||||
resetAttributesNoError()
|
||||
echo ""
|
||||
except IOError, ValueError:
|
||||
return err "failure to write to the standard output"
|
||||
|
||||
echo80 "Please back up the seed phrase now to a safe location as " &
|
||||
"if you are protecting a sensitive password. The seed phrase " &
|
||||
"can be used to withdrawl funds from your wallet."
|
||||
|
||||
echo ""
|
||||
echo "Did you back up your seed recovery phrase?\p" &
|
||||
"(please type 'yes' to continue or press enter to quit)"
|
||||
|
||||
while true:
|
||||
let answer = ask "Answer"
|
||||
if answer == "":
|
||||
return err "aborted wallet creation"
|
||||
elif answer != "yes":
|
||||
echo "To continue, please type 'yes' (without the quotes) or press enter to quit"
|
||||
else:
|
||||
break
|
||||
|
||||
let walletPath = ? pickPasswordAndSaveWallet(rng, config, mnemonic)
|
||||
return ok CreatedWallet(walletPath: walletPath, mnemonic: mnemonic)
|
||||
|
||||
proc restoreWalletInteractively*(rng: var BrHmacDrbgContext,
|
||||
config: BeaconNodeConf) =
|
||||
var
|
||||
enteredMnemonic: TaintedString
|
||||
validatedMnemonic: Mnemonic
|
||||
|
||||
defer:
|
||||
burnMem enteredMnemonic
|
||||
burnMem validatedMnemonic
|
||||
|
||||
echo "To restore your wallet, please enter your backed-up seed phrase."
|
||||
while true:
|
||||
if not readPasswordInput("Seedphrase:", enteredMnemonic):
|
||||
fatal "failure to read password from stdin"
|
||||
quit 1
|
||||
|
||||
if validateMnemonic(enteredMnemonic, validatedMnemonic):
|
||||
break
|
||||
else:
|
||||
echo "The entered mnemonic was not valid. Please try again."
|
||||
|
||||
discard pickPasswordAndSaveWallet(rng, config, validatedMnemonic)
|
||||
|
||||
proc loadWallet*(fileName: string): Result[Wallet, string] =
|
||||
try:
|
||||
ok Json.loadFile(fileName, Wallet)
|
||||
except CatchableError as e:
|
||||
err e.msg
|
||||
|
||||
proc unlockWalletInteractively*(wallet: Wallet): Result[WalletDataForDeposits, string] =
|
||||
proc unlockWalletInteractively*(wallet: Wallet): Result[Mnemonic, string] =
|
||||
echo "Please enter the password for unlocking the wallet"
|
||||
|
||||
for i in 1..3:
|
||||
|
@ -434,8 +476,7 @@ proc unlockWalletInteractively*(wallet: Wallet): Result[WalletDataForDeposits, s
|
|||
var secret = decryptCryptoField(wallet.crypto, KeystorePass password)
|
||||
if secret.len > 0:
|
||||
defer: burnMem(secret)
|
||||
return ok WalletDataForDeposits(
|
||||
mnemonic: Mnemonic string.fromBytes(secret))
|
||||
return ok Mnemonic(string.fromBytes(secret))
|
||||
else:
|
||||
echo "Unlocking of the wallet failed. Please try again."
|
||||
finally:
|
||||
|
@ -443,7 +484,7 @@ proc unlockWalletInteractively*(wallet: Wallet): Result[WalletDataForDeposits, s
|
|||
|
||||
return err "failure to unlock wallet"
|
||||
|
||||
proc findWallet*(config: BeaconNodeConf, name: WalletName): Result[Wallet, string] =
|
||||
proc findWallet*(config: BeaconNodeConf, name: WalletName): Result[WalletPathPair, string] =
|
||||
var walletFiles = newSeq[string]()
|
||||
|
||||
try:
|
||||
|
@ -451,15 +492,16 @@ proc findWallet*(config: BeaconNodeConf, name: WalletName): Result[Wallet, strin
|
|||
if kind != pcFile: continue
|
||||
let walletId = splitFile(walletFile).name
|
||||
if cmpIgnoreCase(walletId, name.string) == 0:
|
||||
return loadWallet(walletFile)
|
||||
let wallet = ? loadWallet(walletFile)
|
||||
return ok WalletPathPair(wallet: wallet, path: walletFile)
|
||||
walletFiles.add walletFile
|
||||
except OSError:
|
||||
return err "failure to list wallet directory"
|
||||
|
||||
for walletFile in walletFiles:
|
||||
let wallet = loadWallet(walletFile)
|
||||
if wallet.isOk and cmpIgnoreCase(wallet.get.name.string, name.string) == 0:
|
||||
return wallet
|
||||
let wallet = ? loadWallet(walletFile)
|
||||
if cmpIgnoreCase(wallet.name.string, name.string) == 0:
|
||||
return ok WalletPathPair(wallet: wallet, path: walletFile)
|
||||
|
||||
return err "failure to locate wallet file"
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
math, strutils, strformat, typetraits, bearssl,
|
||||
math, strutils, strformat, typetraits, algorithm,
|
||||
stew/[results, byteutils, bitseqs, bitops2], stew/shims/macros,
|
||||
eth/keyfile/uuid, blscurve, faststreams/textio, json_serialization,
|
||||
bearssl, eth/keyfile/uuid, blscurve, faststreams/textio, json_serialization,
|
||||
nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, utils, scrypt],
|
||||
./datatypes, ./crypto, ./digest, ./signatures
|
||||
|
||||
|
@ -146,6 +146,7 @@ const
|
|||
|
||||
# https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md
|
||||
wordListLen = 2048
|
||||
maxWordLen = 16
|
||||
|
||||
UUID.serializesAsBaseIn Json
|
||||
KeyPath.serializesAsBaseIn Json
|
||||
|
@ -170,6 +171,12 @@ template burnMem*(m: var (SensitiveData|TaintedString)) =
|
|||
# to make its usage less error-prone.
|
||||
utils.burnMem(string m)
|
||||
|
||||
func longName*(wallet: Wallet): string =
|
||||
if wallet.name.string == wallet.uuid.string:
|
||||
wallet.name.string
|
||||
else:
|
||||
wallet.name.string & " (" & wallet.uuid.string & ")"
|
||||
|
||||
proc getRandomBytes*(rng: var BrHmacDrbgContext, n: Natural): seq[byte]
|
||||
{.raises: [Defect].} =
|
||||
result = newSeq[byte](n)
|
||||
|
@ -177,18 +184,23 @@ proc getRandomBytes*(rng: var BrHmacDrbgContext, n: Natural): seq[byte]
|
|||
|
||||
macro wordListArray*(filename: static string,
|
||||
maxWords: static int = 0,
|
||||
minWordLength: static int = 0): untyped =
|
||||
minWordLen: static int = 0,
|
||||
maxWordLen: static int = high(int)): untyped =
|
||||
result = newTree(nnkBracket)
|
||||
var words = slurp(filename).split()
|
||||
for word in words:
|
||||
if word.len >= minWordLength:
|
||||
if word.len >= minWordLen and word.len <= maxWordLen:
|
||||
result.add newCall("cstring", newLit(word))
|
||||
if maxWords > 0 and result.len >= maxWords:
|
||||
return
|
||||
|
||||
const
|
||||
englishWords = wordListArray("english_word_list.txt",
|
||||
maxWords = wordListLen)
|
||||
maxWords = wordListLen,
|
||||
maxWordLen = maxWordLen)
|
||||
|
||||
static:
|
||||
doAssert englishWords.len == wordListLen
|
||||
|
||||
iterator pathNodesImpl(path: string): Natural
|
||||
{.raises: [ValueError].} =
|
||||
|
@ -228,11 +240,15 @@ func getSeed*(mnemonic: Mnemonic, password: KeystorePass): KeySeed =
|
|||
let salt = "mnemonic-" & password.string
|
||||
KeySeed sha512.pbkdf2(mnemonic.string, salt, 2048, 64)
|
||||
|
||||
template add(m: var Mnemonic, s: cstring) =
|
||||
m.string.add s
|
||||
|
||||
proc generateMnemonic*(
|
||||
rng: var BrHmacDrbgContext,
|
||||
words: openarray[cstring] = englishWords,
|
||||
entropyParam: openarray[byte] = @[]): Mnemonic =
|
||||
# https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonic
|
||||
## Generates a valid BIP-0039 mnenomic:
|
||||
## https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonic
|
||||
doAssert words.len == wordListLen
|
||||
|
||||
var entropy: seq[byte]
|
||||
|
@ -252,17 +268,51 @@ proc generateMnemonic*(
|
|||
|
||||
entropy.add byte(checksum.data.getBitsBE(0 ..< checksumBits))
|
||||
|
||||
var res = ""
|
||||
res.add words[entropy.getBitsBE(0..10)]
|
||||
# Make sure the string won't be reallocated as this may
|
||||
# leave partial copies of the mnemonic in memory:
|
||||
result = Mnemonic newStringOfCap(mnemonicWordCount * maxWordLen)
|
||||
result.add words[entropy.getBitsBE(0..10)]
|
||||
|
||||
for i in 1 ..< mnemonicWordCount:
|
||||
let
|
||||
firstBit = i*11
|
||||
lastBit = firstBit + 10
|
||||
res.add " "
|
||||
res.add words[entropy.getBitsBE(firstBit..lastBit)]
|
||||
result.add " "
|
||||
result.add words[entropy.getBitsBE(firstBit..lastBit)]
|
||||
|
||||
Mnemonic res
|
||||
proc cmpIgnoreCase(lhs: cstring, rhs: string): int =
|
||||
# TODO: This is a bit silly.
|
||||
# Nim should have a `cmp` function for C strings.
|
||||
cmpIgnoreCase($lhs, rhs)
|
||||
|
||||
proc validateMnemonic*(inputWords: TaintedString,
|
||||
outputMnemonic: var Mnemonic): bool =
|
||||
## Accept a case-insensitive input string and returns `true`
|
||||
## if it represents a valid mnenomic. The `outputMnemonic`
|
||||
## value will be populated with a normalized lower-case
|
||||
## version of the mnemonic using a single space separator.
|
||||
##
|
||||
## The `outputMnemonic` value may be populated partially
|
||||
## with sensitive data even in case of validator failure.
|
||||
## Make sure to burn the received data after usage.
|
||||
|
||||
let words = inputWords.string.strip.split(Whitespace)
|
||||
if words.len < 12 or words.len > 24 or words.len mod 3 != 0:
|
||||
return false
|
||||
|
||||
# Make sure the string won't be re-allocated as this may
|
||||
# leave partial copies of the mnemonic in memory:
|
||||
outputMnemonic = Mnemonic newStringOfCap(words.len * maxWordLen)
|
||||
|
||||
for word in words:
|
||||
let foundIdx = binarySearch(englishWords, word, cmpIgnoreCase)
|
||||
if foundIdx == -1:
|
||||
return false
|
||||
if outputMnemonic.string.len > 0:
|
||||
outputMnemonic.add " "
|
||||
outputMnemonic.add englishWords[foundIdx]
|
||||
|
||||
return true
|
||||
|
||||
proc deriveChildKey*(parentKey: ValidatorPrivKey,
|
||||
index: Natural): ValidatorPrivKey =
|
||||
|
@ -549,7 +599,6 @@ proc createWallet*(kdfKind: KdfKind,
|
|||
seed = getSeed(mnemonic, KeystorePass"")
|
||||
crypto = createCryptoField(kdfKind, rng, distinctBase seed,
|
||||
password, salt, iv)
|
||||
|
||||
Wallet(
|
||||
uuid: uuid,
|
||||
name: if name.string.len > 0: name
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit eb13845f6537a0af7564d3d4b535d88938eb104a
|
||||
Subproject commit d3182c4dba5cf0eaa11a1c41f065ea3c3b436995
|
Loading…
Reference in New Issue