Implement more of the KeyStore spec and integrate it in the beacon node

This commit is contained in:
Zahary Karadjov 2020-06-01 22:48:20 +03:00 committed by zah
parent 1fc9413c48
commit 17343442ea
20 changed files with 476 additions and 225 deletions

View File

@ -998,7 +998,7 @@ programMain:
of createTestnet: of createTestnet:
var deposits: seq[Deposit] var deposits: seq[Deposit]
for i in config.firstValidator.int ..< config.totalValidators.int: for i in config.firstValidator.int ..< config.totalValidators.int:
let depositFile = config.validatorsDir / let depositFile = config.testnetDepositsDir /
validatorFileBaseName(i) & ".deposit.json" validatorFileBaseName(i) & ".deposit.json"
try: try:
deposits.add Json.loadFile(depositFile, Deposit) deposits.add Json.loadFile(depositFile, Deposit)
@ -1096,15 +1096,13 @@ programMain:
node.start() node.start()
of makeDeposits: of makeDeposits:
createDir(config.depositsDir) createDir(config.outValidatorsDir)
let let
quickstartDeposits = generateDeposits( deposits = generateDeposits(
config.totalQuickstartDeposits, config.depositsDir, false) config.totalDeposits,
config.outValidatorsDir,
randomDeposits = generateDeposits( config.outSecretsDir).tryGet
config.totalRandomDeposits, config.depositsDir, true,
firstIdx = config.totalQuickstartDeposits)
if config.web3Url.len > 0 and config.depositContractAddress.len > 0: if config.web3Url.len > 0 and config.depositContractAddress.len > 0:
if config.minDelay > config.maxDelay: if config.minDelay > config.maxDelay:
@ -1121,8 +1119,9 @@ programMain:
depositContract = config.depositContractAddress depositContract = config.depositContractAddress
waitFor sendDeposits( waitFor sendDeposits(
quickstartDeposits & randomDeposits, deposits,
config.web3Url, config.web3Url,
config.depositContractAddress, config.depositContractAddress,
config.depositPrivateKey, config.depositPrivateKey,
delayGenerator) delayGenerator)

View File

@ -1,11 +1,11 @@
{.push raises: [Defect].} {.push raises: [Defect].}
import import
os, options, strformat, strutils, os, options, strformat,
chronicles, confutils, json_serialization, chronicles, confutils, json_serialization,
confutils/defs, confutils/std/net, confutils/defs, confutils/std/net,
chronicles/options as chroniclesOptions, chronicles/options as chroniclesOptions,
spec/[crypto] spec/[crypto, keystore]
export export
defs, enabledLogLevel, parseCmdArg, completeCmdArg defs, enabledLogLevel, parseCmdArg, completeCmdArg
@ -39,11 +39,6 @@ type
desc: "The Eth1 network tracked by the beacon node." desc: "The Eth1 network tracked by the beacon node."
name: "eth1-network" }: Eth1Network name: "eth1-network" }: Eth1Network
quickStart* {.
defaultValue: false
desc: "Run in quickstart mode"
name: "quick-start" }: bool
dataDir* {. dataDir* {.
defaultValue: config.defaultDataDir() defaultValue: config.defaultDataDir()
desc: "The directory where nimbus will store all blockchain data." desc: "The directory where nimbus will store all blockchain data."
@ -60,6 +55,10 @@ type
desc: "Address of the deposit contract." desc: "Address of the deposit contract."
name: "deposit-contract" }: string name: "deposit-contract" }: string
nonInteractive* {.
desc: "Do not display interative prompts. Quit on missing configuration."
name: "non-interactive" }: bool
case cmd* {. case cmd* {.
command command
defaultValue: noCommand }: BNStartUpCmd defaultValue: noCommand }: BNStartUpCmd
@ -106,6 +105,14 @@ type
abbr: "v" abbr: "v"
name: "validator" }: seq[ValidatorKeyPath] name: "validator" }: seq[ValidatorKeyPath]
validatorsDir* {.
desc: "A directory containing validator keystores"
name: "validators-dir" }: Option[InputDir]
secretsDir* {.
desc: "A directory containing validator keystore passwords"
name: "secrets-dir" }: Option[InputDir]
stateSnapshot* {. stateSnapshot* {.
desc: "Json file specifying a recent state snapshot." desc: "Json file specifying a recent state snapshot."
abbr: "s" abbr: "s"
@ -177,13 +184,12 @@ type
name: "dump" }: bool name: "dump" }: bool
of createTestnet: of createTestnet:
validatorsDir* {. testnetDepositsDir* {.
desc: "Directory containing validator descriptors named 'vXXXXXXX.deposit.json'." desc: "Directory containing validator keystores."
abbr: "d"
name: "validators-dir" }: InputDir name: "validators-dir" }: InputDir
totalValidators* {. totalValidators* {.
desc: "The number of validators in the newly created chain." desc: "The number of validator deposits in the newly created chain."
name: "total-validators" }: uint64 name: "total-validators" }: uint64
firstValidator* {. firstValidator* {.
@ -209,7 +215,6 @@ type
genesisOffset* {. genesisOffset* {.
defaultValue: 5 defaultValue: 5
desc: "Seconds from now to add to genesis time." desc: "Seconds from now to add to genesis time."
abbr: "g"
name: "genesis-offset" }: int name: "genesis-offset" }: int
outputGenesis* {. outputGenesis* {.
@ -231,34 +236,34 @@ type
name: "keyfile" }: seq[ValidatorKeyPath] name: "keyfile" }: seq[ValidatorKeyPath]
of makeDeposits: of makeDeposits:
totalQuickstartDeposits* {. totalDeposits* {.
defaultValue: 0 defaultValue: 1
desc: "Number of quick-start deposits to generate." desc: "Number of deposits to generate."
name: "quickstart-deposits" }: int name: "count" }: int
totalRandomDeposits* {. outValidatorsDir* {.
defaultValue: 0
desc: "Number of secure random deposits to generate."
name: "random-deposits" }: int
depositsDir* {.
defaultValue: "validators" defaultValue: "validators"
desc: "Folder to write deposits to." desc: "Output folder for validator keystores and deposits."
name: "deposits-dir" }: string name: "out-validators-dir" }: string
outSecretsDir* {.
defaultValue: "secrets"
desc: "Output folder for randomly generated keystore passphrases."
name: "out-secrets-dir" }: string
depositPrivateKey* {. depositPrivateKey* {.
defaultValue: "" defaultValue: ""
desc: "Private key of the controlling (sending) account", desc: "Private key of the controlling (sending) account.",
name: "deposit-private-key" }: string name: "deposit-private-key" }: string
minDelay* {. minDelay* {.
defaultValue: 0.0 defaultValue: 0.0
desc: "Minimum possible delay between making two deposits (in seconds)" desc: "Minimum possible delay between making two deposits (in seconds)."
name: "min-delay" }: float name: "min-delay" }: float
maxDelay* {. maxDelay* {.
defaultValue: 0.0 defaultValue: 0.0
desc: "Maximum possible delay between making two deposits (in seconds)" desc: "Maximum possible delay between making two deposits (in seconds)."
name: "max-delay" }: float name: "max-delay" }: float
ValidatorClientConf* = object ValidatorClientConf* = object
@ -273,6 +278,10 @@ type
abbr: "d" abbr: "d"
name: "data-dir" }: OutDir name: "data-dir" }: OutDir
nonInteractive* {.
desc: "Do not display interative prompts. Quit on missing configuration."
name: "non-interactive" }: bool
case cmd* {. case cmd* {.
command command
defaultValue: VCNoCommand }: VCStartUpCmd defaultValue: VCNoCommand }: VCStartUpCmd
@ -290,10 +299,18 @@ type
validators* {. validators* {.
required required
desc: "Path to a validator private key, as generated by makeDeposits." desc: "Path to a validator key store, as generated by makeDeposits."
abbr: "v" abbr: "v"
name: "validator" }: seq[ValidatorKeyPath] name: "validator" }: seq[ValidatorKeyPath]
validatorsDir* {.
desc: "A directory containing validator keystores"
name: "validators-dir" }: Option[InputDir]
secretsDir* {.
desc: "A directory containing validator keystore passwords"
name: "secrets-dir" }: Option[InputDir]
proc defaultDataDir*(conf: BeaconNodeConf|ValidatorClientConf): string = proc defaultDataDir*(conf: BeaconNodeConf|ValidatorClientConf): string =
let dataDir = when defined(windows): let dataDir = when defined(windows):
"AppData" / "Roaming" / "Nimbus" "AppData" / "Roaming" / "Nimbus"
@ -315,7 +332,7 @@ func dumpDir*(conf: BeaconNodeConf|ValidatorClientConf): string =
conf.dataDir / "dump" conf.dataDir / "dump"
func localValidatorsDir*(conf: BeaconNodeConf|ValidatorClientConf): string = func localValidatorsDir*(conf: BeaconNodeConf|ValidatorClientConf): string =
conf.dataDir / "validators" string conf.validatorsDir.get(InputDir(conf.dataDir / "validators"))
func databaseDir*(conf: BeaconNodeConf|ValidatorClientConf): string = func databaseDir*(conf: BeaconNodeConf|ValidatorClientConf): string =
conf.dataDir / "db" conf.dataDir / "db"
@ -328,26 +345,6 @@ func defaultListenAddress*(conf: BeaconNodeConf|ValidatorClientConf): ValidIpAdd
func defaultAdminListenAddress*(conf: BeaconNodeConf|ValidatorClientConf): ValidIpAddress = func defaultAdminListenAddress*(conf: BeaconNodeConf|ValidatorClientConf): ValidIpAddress =
(static ValidIpAddress.init("127.0.0.1")) (static ValidIpAddress.init("127.0.0.1"))
iterator validatorKeys*(conf: BeaconNodeConf|ValidatorClientConf): ValidatorPrivKey =
for validatorKeyFile in conf.validators:
try:
yield validatorKeyFile.load
except CatchableError as err:
warn "Failed to load validator private key",
file = validatorKeyFile.string, err = err.msg
try:
for kind, file in walkDir(conf.localValidatorsDir):
if kind in {pcFile, pcLinkToFile} and
cmpIgnoreCase(".privkey", splitFile(file).ext) == 0:
try:
yield ValidatorPrivKey.init(readFile(file).string)
except CatchableError as err:
warn "Failed to load a validator private key", file, err = err.msg
except OSError as err:
warn "Cannot load validator keys",
dir = conf.localValidatorsDir, err = err.msg
template writeValue*(writer: var JsonWriter, template writeValue*(writer: var JsonWriter,
value: TypedInputFile|InputFile|InputDir|OutPath|OutDir|OutFile) = value: TypedInputFile|InputFile|InputDir|OutPath|OutDir|OutFile) =
writer.writeValue(string value) writer.writeValue(string value)

View File

@ -3,7 +3,7 @@
import import
stew/endians2, stint, stew/endians2, stint,
./extras, ./ssz/merkleization, ./extras, ./ssz/merkleization,
spec/[crypto, datatypes, digest, helpers] spec/[crypto, datatypes, digest, helpers, keystore]
func get_eth1data_stub*(deposit_count: uint64, current_epoch: Epoch): Eth1Data = func get_eth1data_stub*(deposit_count: uint64, current_epoch: Epoch): Eth1Data =
# https://github.com/ethereum/eth2.0-pm/blob/e596c70a19e22c7def4fd3519e20ae4022349390/interop/mocked_eth1data/README.md # https://github.com/ethereum/eth2.0-pm/blob/e596c70a19e22c7def4fd3519e20ae4022349390/interop/mocked_eth1data/README.md
@ -16,7 +16,7 @@ func get_eth1data_stub*(deposit_count: uint64, current_epoch: Epoch): Eth1Data =
block_hash: hash_tree_root(hash_tree_root(voting_period).data), block_hash: hash_tree_root(hash_tree_root(voting_period).data),
) )
func makeInteropPrivKey*(i: int): BlsResult[ValidatorPrivKey] = func makeInteropPrivKey*(i: int): ValidatorPrivKey =
var bytes: array[32, byte] var bytes: array[32, byte]
bytes[0..7] = uint64(i).toBytesLE() bytes[0..7] = uint64(i).toBytesLE()
@ -28,19 +28,13 @@ func makeInteropPrivKey*(i: int): BlsResult[ValidatorPrivKey] =
privkeyBytes = eth2hash(bytes) privkeyBytes = eth2hash(bytes)
key = (UInt256.fromBytesLE(privkeyBytes.data) mod curveOrder).toBytesBE() key = (UInt256.fromBytesLE(privkeyBytes.data) mod curveOrder).toBytesBE()
ValidatorPrivKey.fromRaw(key) ValidatorPrivKey.fromRaw(key).get
const eth1BlockHash* = block: const eth1BlockHash* = block:
var x: Eth2Digest var x: Eth2Digest
for v in x.data.mitems: v = 0x42 for v in x.data.mitems: v = 0x42
x x
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/deposit-contract.md#withdrawal-credentials
func makeWithdrawalCredentials*(k: ValidatorPubKey): Eth2Digest =
var bytes = eth2hash(k.toRaw())
bytes.data[0] = BLS_WITHDRAWAL_PREFIX.uint8
bytes
func makeDeposit*( func makeDeposit*(
pubkey: ValidatorPubKey, privkey: ValidatorPrivKey, epoch = 0.Epoch, pubkey: ValidatorPubKey, privkey: ValidatorPrivKey, epoch = 0.Epoch,
amount: Gwei = MAX_EFFECTIVE_BALANCE.Gwei, amount: Gwei = MAX_EFFECTIVE_BALANCE.Gwei,

View File

@ -26,16 +26,6 @@ func round_step_down*(x: Natural, step: static Natural): int {.inline.} =
else: else:
result = x - x mod step result = x - x mod step
let ZeroHashes = block:
# hashes for a merkle tree full of zeros for leafs
var zh = @[Eth2Digest()]
for i in 1 ..< DEPOSIT_CONTRACT_TREE_DEPTH:
let nodehash = withEth2Hash:
h.update zh[i-1]
h.update zh[i-1]
zh.add nodehash
zh
type SparseMerkleTree*[Depth: static int] = object type SparseMerkleTree*[Depth: static int] = object
## Sparse Merkle tree ## Sparse Merkle tree
# There is an extra "depth" layer to store leaf nodes # There is an extra "depth" layer to store leaf nodes
@ -67,7 +57,7 @@ proc merkleTreeFromLeaves*(
# with the zeroHash corresponding to the current depth # with the zeroHash corresponding to the current depth
let nodeHash = withEth2Hash: let nodeHash = withEth2Hash:
h.update result.nnznodes[depth-1][^1] h.update result.nnznodes[depth-1][^1]
h.update ZeroHashes[depth-1] h.update zeroHashes[depth-1]
result.nnznodes[depth].add nodeHash result.nnznodes[depth].add nodeHash
proc getMerkleProof*[Depth: static int]( proc getMerkleProof*[Depth: static int](
@ -85,7 +75,7 @@ proc getMerkleProof*[Depth: static int](
if nodeIdx < tree.nnznodes[depth].len: if nodeIdx < tree.nnznodes[depth].len:
result[depth] = tree.nnznodes[depth][nodeIdx] result[depth] = tree.nnznodes[depth][nodeIdx]
else: else:
result[depth] = ZeroHashes[depth] result[depth] = zeroHashes[depth]
proc attachMerkleProofs*(deposits: var seq[Deposit]) = proc attachMerkleProofs*(deposits: var seq[Deposit]) =
let deposit_data_roots = mapIt(deposits, it.data.hash_tree_root) let deposit_data_roots = mapIt(deposits, it.data.hash_tree_root)

View File

@ -69,6 +69,8 @@ type
BlsResult*[T] = Result[T, cstring] BlsResult*[T] = Result[T, cstring]
RandomSourceDepleted* = object of CatchableError
func `==`*(a, b: BlsValue): bool = func `==`*(a, b: BlsValue): bool =
if a.kind != b.kind: return false if a.kind != b.kind: return false
if a.kind == Real: if a.kind == Real:
@ -82,6 +84,9 @@ template `==`*[N, T](a: BlsValue[N, T], b: T): bool =
template `==`*[N, T](a: T, b: BlsValue[N, T]): bool = template `==`*[N, T](a: T, b: BlsValue[N, T]): bool =
a == b.blsValue a == b.blsValue
template `==`*(a, b: ValidatorPrivKey): bool =
blscurve.SecretKey(a) == blscurve.SecretKey(b)
# API # API
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#bls-signatures # https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#bls-signatures
@ -341,3 +346,17 @@ func init*(T: typedesc[ValidatorSig], data: array[RawSigSize, byte]): T {.noInit
if v.isErr: if v.isErr:
raise (ref ValueError)(msg: $v.error) raise (ref ValueError)(msg: $v.error)
return v[] return v[]
proc getRandomBytes*(n: Natural): seq[byte]
{.raises: [RandomSourceDepleted, Defect].} =
result = newSeq[byte](n)
if randomBytes(result) != result.len:
raise newException(RandomSourceDepleted, "Failed to generate random bytes")
proc getRandomBytesOrPanic*(output: var openarray[byte]) =
doAssert randomBytes(output) == output.len
proc getRandomBytesOrPanic*(n: Natural): seq[byte] =
result = newSeq[byte](n)
getRandomBytesOrPanic(result)

View File

@ -6,13 +6,13 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
json, math, strutils, json, math, strutils, strformat,
eth/keyfile/uuid, eth/keyfile/uuid, stew/[results, byteutils, bitseqs, bitops2],
stew/[results, byteutils], nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, sysrand], blscurve,
nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, sysrand], datatypes, crypto, digest, helpers
./crypto
export results export
results
{.push raises: [Defect].} {.push raises: [Defect].}
@ -64,6 +64,22 @@ type
KsResult*[T] = Result[T, cstring] KsResult*[T] = Result[T, cstring]
Eth2KeyKind* = enum
signingKeyKind # Also known as voting key
withdrawalKeyKind
Mnemonic* = distinct string
KeyPath* = distinct string
KeyStorePass* = distinct string
KeyStoreContent* = distinct JsonString
KeySeed* = distinct seq[byte]
Credentials* = object
mnemonic*: Mnemonic
keyStore*: KeyStoreContent
signingKey*: ValidatorPrivKey
withdrawalKey*: ValidatorPrivKey
const const
saltSize = 32 saltSize = 32
@ -80,6 +96,108 @@ const
prf: "hmac-sha256" prf: "hmac-sha256"
) )
# https://eips.ethereum.org/EIPS/eip-2334
eth2KeyPurpose = 12381
eth2CoinType* = 3600
# https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md
wordListLen = 2048
englishWords = split slurp("english_word_list.txt")
iterator pathNodesImpl(path: string): Natural
{.raises: [ValueError].} =
for elem in path.split("/"):
if elem == "m": continue
yield parseInt(elem)
func append*(path: KeyPath, pathNode: Natural): KeyPath =
KeyPath(path.string & "/" & $pathNode)
func validateKeyPath*(path: TaintedString): KeyPath
{.raises: [ValueError].} =
for elem in pathNodesImpl(path.string): discard elem
KeyPath path
iterator pathNodes(path: KeyPath): Natural =
try:
for elem in pathNodesImpl(path.string):
yield elem
except ValueError:
doAssert false, "Make sure you've validated the key path with `validateKeyPath`"
func makeKeyPath*(validatorIdx: Natural,
keyType: Eth2KeyKind): KeyPath =
# https://eips.ethereum.org/EIPS/eip-2334
let use = case keyType
of withdrawalKeyKind: "0"
of signingKeyKind: "0/0"
try:
KeyPath &"m/{eth2KeyPurpose}/{eth2CoinType}/{validatorIdx}/{use}"
except ValueError:
raiseAssert "All values above can be converted successfully to strings"
func getSeed*(mnemonic: Mnemonic, password: KeyStorePass): KeySeed =
# https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#from-mnemonic-to-seed
let salt = "mnemonic-" & password.string
KeySeed sha512.pbkdf2(mnemonic.string, salt, 2048, 64)
proc generateMnemonic*(words: openarray[string],
entropyParam: openarray[byte] = @[]): Mnemonic =
# https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonic
doAssert words.len == wordListLen
var entropy: seq[byte]
if entropyParam.len == 0:
entropy = getRandomBytesOrPanic(32)
else:
doAssert entropyParam.len >= 128 and
entropyParam.len <= 256 and
entropyParam.len mod 32 == 0
entropy = @entropyParam
let
checksumBits = entropy.len div 4 # ranges from 4 to 8
mnemonicWordCount = 12 + (checksumBits - 4) * 3
checksum = sha256.digest(entropy)
entropy.add byte(checksum.data.getBitsBE(0 ..< checksumBits))
var res = 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)]
Mnemonic res
proc deriveChildKey*(parentKey: ValidatorPrivKey,
index: Natural): ValidatorPrivKey =
doAssert derive_child_secretKey(SecretKey result,
SecretKey parentKey,
uint32 index)
proc deriveMasterKey*(seed: KeySeed): ValidatorPrivKey =
doAssert derive_master_secretKey(SecretKey result,
seq[byte] seed)
proc deriveMasterKey*(mnemonic: Mnemonic,
password: KeyStorePass): ValidatorPrivKey =
deriveMasterKey(getSeed(mnemonic, password))
proc deriveChildKey*(masterKey: ValidatorPrivKey,
path: KeyPath): ValidatorPrivKey =
result = masterKey
for idx in pathNodes(path):
result = deriveChildKey(result, idx)
proc keyFromPath*(mnemonic: Mnemonic,
password: KeyStorePass,
path: KeyPath): ValidatorPrivKey =
deriveChildKey(deriveMasterKey(mnemonic, password), path)
proc shaChecksum(key, cipher: openarray[byte]): array[32, byte] = proc shaChecksum(key, cipher: openarray[byte]): array[32, byte] =
var ctx: sha256 var ctx: sha256
ctx.init() ctx.init()
@ -100,12 +218,11 @@ template hexToBytes(data, name: string): untyped =
except ValueError: except ValueError:
return err "ks: failed to parse " & name return err "ks: failed to parse " & name
proc decryptKeystore*(data, passphrase: string): KsResult[seq[byte]] = proc decryptKeystore*(data: KeyStoreContent,
let ks = password: KeyStorePass): KsResult[ValidatorPrivKey] =
try: # TODO: `parseJson` can raise a general `Exception`
parseJson(data) let ks = try: parseJson(data.string)
except Exception: except Exception: return err "ks: failed to parse keystore"
return err "ks: failed to parse keystore"
var var
decKey: seq[byte] decKey: seq[byte]
@ -126,7 +243,7 @@ proc decryptKeystore*(data, passphrase: string): KsResult[seq[byte]] =
kdfParams = crypto.kdf.params kdfParams = crypto.kdf.params
salt = hexToBytes(kdfParams.salt, "salt") salt = hexToBytes(kdfParams.salt, "salt")
decKey = sha256.pbkdf2(passphrase, salt, kdfParams.c, kdfParams.dklen) decKey = sha256.pbkdf2(password.string, salt, kdfParams.c, kdfParams.dklen)
iv = hexToBytes(crypto.cipher.params.iv, "iv") iv = hexToBytes(crypto.cipher.params.iv, "iv")
cipherMsg = hexToBytes(crypto.cipher.message, "cipher") cipherMsg = hexToBytes(crypto.cipher.message, "cipher")
checksumMsg = hexToBytes(crypto.checksum.message, "checksum") checksumMsg = hexToBytes(crypto.checksum.message, "checksum")
@ -151,41 +268,41 @@ proc decryptKeystore*(data, passphrase: string): KsResult[seq[byte]] =
aesCipher.decrypt(cipherMsg, secret) aesCipher.decrypt(cipherMsg, secret)
aesCipher.clear() aesCipher.clear()
result = ok secret ValidatorPrivKey.fromRaw(secret)
proc encryptKeystore*[T: KdfParams](secret: openarray[byte]; proc encryptKeystore*(T: type[KdfParams],
passphrase: string; privKey: ValidatorPrivkey,
path=""; password = KeyStorePass "",
salt: openarray[byte] = @[]; path = KeyPath "",
iv: openarray[byte] = @[]; salt: openarray[byte] = @[],
ugly=true): KsResult[string] = iv: openarray[byte] = @[],
ugly = true): KeyStoreContent =
var var
secret = privKey.toRaw[^32..^1]
decKey: seq[byte] decKey: seq[byte]
aesCipher: CTR[aes128] aesCipher: CTR[aes128]
aesIv = newSeq[byte](aes128.sizeBlock) aesIv = newSeq[byte](aes128.sizeBlock)
kdfSalt = newSeq[byte](saltSize) kdfSalt = newSeq[byte](saltSize)
cipherMsg = newSeq[byte](secret.len) cipherMsg = newSeq[byte](secret.len)
if salt.len == saltSize: if salt.len > 0:
doAssert salt.len == saltSize
kdfSalt = @salt kdfSalt = @salt
elif salt.len > 0: else:
return err "ks: invalid salt" getRandomBytesOrPanic(kdfSalt)
elif randomBytes(kdfSalt) != saltSize:
return err "ks: no random bytes for salt"
if iv.len == aes128.sizeBlock: if iv.len > 0:
doAssert iv.len == aes128.sizeBlock
aesIv = @iv aesIv = @iv
elif iv.len > 0: else:
return err "ks: invalid iv" getRandomBytesOrPanic(aesIv)
elif randomBytes(aesIv) != aes128.sizeBlock:
return err "ks: no random bytes for iv"
when T is KdfPbkdf2: when T is KdfPbkdf2:
decKey = sha256.pbkdf2(passphrase, kdfSalt, pbkdf2Params.c, decKey = sha256.pbkdf2(password.string, kdfSalt, pbkdf2Params.c,
pbkdf2Params.dklen) pbkdf2Params.dklen)
var kdf = Kdf[KdfPbkdf2](function: "pbkdf2", params: pbkdf2Params, message: "") var kdf = Kdf[KdfPbkdf2](function: "pbkdf2", params: pbkdf2Params, message: "")
kdf.params.salt = kdfSalt.toHex() kdf.params.salt = byteutils.toHex(kdfSalt)
else: else:
return return
@ -193,29 +310,75 @@ proc encryptKeystore*[T: KdfParams](secret: openarray[byte];
aesCipher.encrypt(secret, cipherMsg) aesCipher.encrypt(secret, cipherMsg)
aesCipher.clear() aesCipher.clear()
let pubkey = (? ValidatorPrivkey.fromRaw(secret)).toPubKey() let pubkey = privKey.toPubKey()
let let
sum = shaChecksum(decKey.toOpenArray(16, 31), cipherMsg) sum = shaChecksum(decKey.toOpenArray(16, 31), cipherMsg)
uuid = uuidGenerate().get
keystore = Keystore[T]( keystore = Keystore[T](
crypto: Crypto[T]( crypto: Crypto[T](
kdf: kdf, kdf: kdf,
checksum: Checksum( checksum: Checksum(
function: "sha256", function: "sha256",
message: sum.toHex() message: byteutils.toHex(sum)
), ),
cipher: Cipher( cipher: Cipher(
function: "aes-128-ctr", function: "aes-128-ctr",
params: CipherParams(iv: aesIv.toHex()), params: CipherParams(iv: byteutils.toHex(aesIv)),
message: cipherMsg.toHex() message: byteutils.toHex(cipherMsg)
) )
), ),
pubkey: pubkey.toHex(), pubkey: toHex(pubkey),
path: path, path: path.string,
uuid: $(? uuidGenerate()), uuid: $uuid,
version: 4 version: 4)
)
KeyStoreContent if ugly: $(%keystore)
else: pretty(%keystore, indent=4)
proc restoreCredentials*(mnemonic: Mnemonic,
password = KeyStorePass ""): Credentials =
let
withdrawalKeyPath = makeKeyPath(0, withdrawalKeyKind)
withdrawalKey = keyFromPath(mnemonic, password, withdrawalKeyPath)
signingKeyPath = withdrawalKeyPath.append 0
signingKey = deriveChildKey(withdrawalKey, 0)
Credentials(
mnemonic: mnemonic,
keyStore: encryptKeystore(KdfPbkdf2, signingKey, password, signingKeyPath),
signingKey: signingKey,
withdrawalKey: withdrawalKey)
proc generateCredentials*(entropy: openarray[byte] = @[],
password = KeyStorePass ""): Credentials =
let mnemonic = generateMnemonic(englishWords, entropy)
restoreCredentials(mnemonic, password)
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/deposit-contract.md#withdrawal-credentials
proc makeWithdrawalCredentials*(k: ValidatorPubKey): Eth2Digest =
var bytes = eth2hash(k.toRaw())
bytes.data[0] = BLS_WITHDRAWAL_PREFIX.uint8
bytes
proc prepareDeposit*(credentials: Credentials,
amount = MAX_EFFECTIVE_BALANCE.Gwei): Deposit =
let
withdrawalPubKey = credentials.withdrawalKey.toPubKey
signingPubKey = credentials.signingKey.toPubKey
var
ret = Deposit(
data: DepositData(
amount: amount,
pubkey: signingPubKey,
withdrawal_credentials: makeWithdrawalCredentials(withdrawalPubKey)))
let domain = compute_domain(DOMAIN_DEPOSIT)
let signing_root = compute_signing_root(ret.getDepositMessage, domain)
ret.data.signature = bls_sign(credentials.signingKey, signing_root.data)
ret
result = ok(if ugly: $(%keystore)
else: pretty(%keystore, indent=4))

View File

@ -19,3 +19,4 @@ import
export export
merkleization, ssz_serialization, types merkleization, ssz_serialization, types

View File

@ -72,7 +72,7 @@ func computeZeroHashes: array[sizeof(Limit) * 8, Eth2Digest] =
for i in 1 .. result.high: for i in 1 .. result.high:
result[i] = mergeBranches(result[i - 1], result[i - 1]) result[i] = mergeBranches(result[i - 1], result[i - 1])
const zeroHashes = computeZeroHashes() const zeroHashes* = computeZeroHashes()
func addChunk(merkleizer: var SszChunksMerkleizer, data: openarray[byte]) = func addChunk(merkleizer: var SszChunksMerkleizer, data: openarray[byte]) =
doAssert data.len > 0 and data.len <= bytesPerChunk doAssert data.len > 0 and data.len <= bytesPerChunk

View File

@ -21,7 +21,7 @@ import
eth2_network, eth2_discovery, validator_pool, beacon_node_types, eth2_network, eth2_discovery, validator_pool, beacon_node_types,
nimbus_binary_common, nimbus_binary_common,
version, ssz/merkleization, version, ssz/merkleization,
sync_manager, sync_manager, validator_keygen,
spec/eth2_apis/validator_callsigs_types, spec/eth2_apis/validator_callsigs_types,
eth2_json_rpc_serialization eth2_json_rpc_serialization

View File

@ -20,7 +20,7 @@ import
# Local modules # Local modules
spec/[datatypes, digest, crypto, beaconstate, helpers, validator, network], spec/[datatypes, digest, crypto, beaconstate, helpers, validator, network],
conf, time, validator_pool, state_transition, conf, time, validator_pool, state_transition,
attestation_pool, block_pool, eth2_network, attestation_pool, block_pool, eth2_network, validator_keygen,
beacon_node_common, beacon_node_types, nimbus_binary_common, beacon_node_common, beacon_node_types, nimbus_binary_common,
mainchain_monitor, version, ssz/merkleization, interop, mainchain_monitor, version, ssz/merkleization, interop,
attestation_aggregation, sync_manager, sszdump attestation_aggregation, sync_manager, sszdump

View File

@ -1,76 +1,153 @@
import import
os, strutils, os, strutils, terminal,
chronicles, chronos, blscurve, nimcrypto, json_serialization, serialization, chronicles, chronos, blscurve, nimcrypto, json_serialization, serialization,
web3, stint, eth/keys, web3, stint, eth/keys, confutils,
spec/[datatypes, digest, crypto], conf, ssz/merkleization, interop, merkle_minimal spec/[datatypes, digest, crypto, keystore], conf, ssz/merkleization, merkle_minimal
contract(DepositContract): contract(DepositContract):
proc deposit(pubkey: Bytes48, withdrawalCredentials: Bytes32, signature: Bytes96, deposit_data_root: FixedBytes[32]) proc deposit(pubkey: Bytes48, withdrawalCredentials: Bytes32, signature: Bytes96, deposit_data_root: FixedBytes[32])
const
keystoreFileName* = "keystore.json"
depositFileName* = "deposit.json"
type type
DelayGenerator* = proc(): chronos.Duration {.closure, gcsafe.} DelayGenerator* = proc(): chronos.Duration {.closure, gcsafe.}
proc writeTextFile(filename: string, contents: string) = {.push raises: [Defect].}
writeFile(filename, contents)
# echo "Wrote ", filename
proc writeFile(filename: string, value: auto) =
Json.saveFile(filename, value, pretty = true)
# echo "Wrote ", filename
proc ethToWei(eth: UInt256): UInt256 = proc ethToWei(eth: UInt256): UInt256 =
eth * 1000000000000000000.u256 eth * 1000000000000000000.u256
proc generateDeposits*(totalValidators: int, proc loadKeyStore(conf: BeaconNodeConf|ValidatorClientConf,
outputDir: string, validatorsDir, keyName: string): Option[ValidatorPrivKey] =
randomKeys: bool, let
firstIdx = 0): seq[Deposit] = keystorePath = validatorsDir / keyName / keystoreFileName
info "Generating deposits", totalValidators, outputDir, randomKeys keystoreContents = KeyStoreContent:
for i in 0 ..< totalValidators: try: readFile(keystorePath)
let except IOError as err:
v = validatorFileBaseName(firstIdx + i) error "Failed to read keystore", err = err.msg, path = keystorePath
depositFn = outputDir / v & ".deposit.json" return
privKeyFn = outputDir / v & ".privkey"
if existsFile(depositFn) and existsFile(privKeyFn): if conf.secretsDir.isSome:
try: let passphrasePath = conf.secretsDir.get / keyName
result.add Json.loadFile(depositFn, Deposit) if fileExists(passphrasePath):
continue let
except SerializationError as err: passphrase = KeyStorePass:
debug "Rewriting unreadable deposit", err = err.formatMsg(depositFn) try: readFile(passphrasePath)
discard except IOError as err:
error "Failed to read passphrase file", err = err.msg, path = passphrasePath
return
var let res = decryptKeystore(keystoreContents, passphrase)
privkey{.noInit.}: ValidatorPrivKey if res.isOk:
pubKey{.noInit.}: ValidatorPubKey return res.get.some
else:
error "Failed to decrypt keystore", keystorePath, passphrasePath
return
if randomKeys: if conf.nonInteractive:
(pubKey, privKey) = crypto.newKeyPair().tryGet() error "Unable to load validator key store. Please ensure matching passphrase exists in the secrets dir",
keyName, validatorsDir, secretsDir = conf.secretsDir
return
var remainingAttempts = 3
var prompt = "Please enter passphrase for key \"" & validatorsDir/keyName & "\""
while remainingAttempts > 0:
let passphrase = KeyStorePass:
try: readPasswordFromStdin(prompt)
except IOError:
error "STDIN not readable. Cannot obtain KeyStore password"
return
let decrypted = decryptKeystore(keystoreContents, passphrase)
if decrypted.isOk:
return decrypted.get.some
else: else:
privKey = makeInteropPrivKey(i).tryGet() prompt = "Keystore decryption failed. Please try again"
pubKey = privKey.toPubKey() dec remainingAttempts
let dp = makeDeposit(pubKey, privKey) iterator validatorKeys*(conf: BeaconNodeConf|ValidatorClientConf): ValidatorPrivKey =
for validatorKeyFile in conf.validators:
try:
yield validatorKeyFile.load
except CatchableError as err:
error "Failed to load validator private key",
file = validatorKeyFile.string, err = err.msg
quit 1
result.add(dp) let validatorsDir = conf.localValidatorsDir
try:
for kind, file in walkDir(validatorsDir):
if kind == pcDir:
let keyName = splitFile(file).name
let key = loadKeyStore(conf, validatorsDir, keyName)
if key.isSome:
yield key.get
else:
quit 1
except OSError as err:
error "Validator keystores directory not accessible",
path = validatorsDir, err = err.msg
quit 1
type
GenerateDepositsError = enum
RandomSourceDepleted,
FailedToCreateValidatoDir
FailedToCreateSecretFile
FailedToCreateKeystoreFile
FailedToCreateDepositFile
proc generateDeposits*(totalValidators: int,
validatorsDir: string,
secretsDir: string): Result[seq[Deposit], GenerateDepositsError] =
var deposits: seq[Deposit]
info "Generating deposits", totalValidators, validatorsDir, secretsDir
for i in 0 ..< totalValidators:
let password = KeyStorePass getRandomBytesOrPanic(32).toHex
let credentials = generateCredentials(password = password)
let
keyName = $(credentials.signingKey.toPubKey)
validatorDir = validatorsDir / keyName
passphraseFile = secretsDir / keyName
depositFile = validatorDir / depositFileName
keystoreFile = validatorDir / keystoreFileName
if existsDir(validatorDir) and existsFile(depositFile):
continue
try: createDir validatorDir
except OSError, IOError: return err FailedToCreateValidatoDir
try: writeFile(secretsDir / keyName, password.string)
except IOError: return err FailedToCreateSecretFile
try: writeFile(keystoreFile, credentials.keyStore.string)
except IOError: return err FailedToCreateKeystoreFile
deposits.add credentials.prepareDeposit()
# Does quadratic additional work, but fast enough, and otherwise more # Does quadratic additional work, but fast enough, and otherwise more
# cleanly allows free intermixing of pre-existing and newly generated # cleanly allows free intermixing of pre-existing and newly generated
# deposit and private key files. TODO: only generate new Merkle proof # deposit and private key files. TODO: only generate new Merkle proof
# for the most recent deposit if this becomes bottleneck. # for the most recent deposit if this becomes bottleneck.
attachMerkleProofs(result) attachMerkleProofs(deposits)
try: Json.saveFile(depositFile, deposits[^1], pretty = true)
except: return err FailedToCreateDepositFile
writeTextFile(privKeyFn, privKey.toHex()) ok deposits
writeFile(depositFn, result[result.len - 1])
proc sendDeposits*( {.pop.}
deposits: seq[Deposit],
web3Url, depositContractAddress, privateKey: string,
delayGenerator: DelayGenerator = nil) {.async.} =
proc sendDeposits*(deposits: seq[Deposit],
web3Url, depositContractAddress, privateKey: string,
delayGenerator: DelayGenerator = nil) {.async.} =
var web3 = await newWeb3(web3Url) var web3 = await newWeb3(web3Url)
if privateKey.len != 0: if privateKey.len != 0:
web3.privateKey = PrivateKey.fromHex(privateKey).tryGet() web3.privateKey = PrivateKey.fromHex(privateKey).tryGet
else: else:
let accounts = await web3.provider.eth_accounts() let accounts = await web3.provider.eth_accounts()
if accounts.len == 0: if accounts.len == 0:
@ -79,9 +156,9 @@ proc sendDeposits*(
web3.defaultAccount = accounts[0] web3.defaultAccount = accounts[0]
let contractAddress = Address.fromHex(depositContractAddress) let contractAddress = Address.fromHex(depositContractAddress)
let depositContract = web3.contractSender(DepositContract, contractAddress)
for i, dp in deposits: for i, dp in deposits:
let depositContract = web3.contractSender(DepositContract, contractAddress)
discard await depositContract.deposit( discard await depositContract.deposit(
Bytes48(dp.data.pubKey.toRaw()), Bytes48(dp.data.pubKey.toRaw()),
Bytes32(dp.data.withdrawal_credentials.data), Bytes32(dp.data.withdrawal_credentials.data),
@ -91,17 +168,3 @@ proc sendDeposits*(
if delayGenerator != nil: if delayGenerator != nil:
await sleepAsync(delayGenerator()) await sleepAsync(delayGenerator())
when isMainModule:
import confutils
cli do (totalValidators: int = 125000,
outputDir: string = "validators",
randomKeys: bool = false,
web3Url: string = "",
depositContractAddress: string = ""):
let deposits = generateDeposits(totalValidators, outputDir, randomKeys)
if web3Url.len() > 0 and depositContractAddress.len() > 0:
echo "Sending deposits to eth1..."
waitFor sendDeposits(deposits, web3Url, depositContractAddress, "")
echo "Done"

View File

@ -87,6 +87,7 @@ cli do (skipGoerliKey {.
.replace(")", "_") .replace(")", "_")
dataDir = buildDir / "data" / dataDirName dataDir = buildDir / "data" / dataDirName
validatorsDir = dataDir / "validators" validatorsDir = dataDir / "validators"
secretsDir = dataDir / "secrets"
beaconNodeBinary = buildDir / "beacon_node_" & dataDirName beaconNodeBinary = buildDir / "beacon_node_" & dataDirName
var var
nimFlags = "-d:chronicles_log_level=TRACE " & getEnv("NIM_PARAMS") nimFlags = "-d:chronicles_log_level=TRACE " & getEnv("NIM_PARAMS")
@ -137,7 +138,8 @@ cli do (skipGoerliKey {.
mode = Verbose mode = Verbose
exec replace(&"""{beaconNodeBinary} makeDeposits exec replace(&"""{beaconNodeBinary} makeDeposits
--random-deposits=1 --random-deposits=1
--deposits-dir="{validatorsDir}" --out-validators-dir="{validatorsDir}"
--out-secrets-dir="{secretsDir}"
--deposit-private-key={privKey} --deposit-private-key={privKey}
--web3-url={web3Url} --web3-url={web3Url}
{depositContractOpt} {depositContractOpt}

View File

@ -114,8 +114,13 @@ fi
NETWORK="testnet${TESTNET}" NETWORK="testnet${TESTNET}"
rm -rf "${DATA_DIR}" rm -rf "${DATA_DIR}"
DEPOSITS_DIR="${DATA_DIR}/deposits_dir" DEPOSITS_DIR="${DATA_DIR}/deposits_dir"
mkdir -p "${DEPOSITS_DIR}" mkdir -p "${DEPOSITS_DIR}"
SECRETS_DIR="${DATA_DIR}/secrets"
mkdir -p "${SECRETS_DIR}"
NETWORK_DIR="${DATA_DIR}/network_dir" NETWORK_DIR="${DATA_DIR}/network_dir"
mkdir -p "${NETWORK_DIR}" mkdir -p "${NETWORK_DIR}"
@ -134,17 +139,16 @@ NETWORK_NIM_FLAGS=$(scripts/load-testnet-nim-flags.sh ${NETWORK})
$MAKE LOG_LEVEL="${LOG_LEVEL}" NIMFLAGS="-d:insecure -d:testnet_servers_image ${NETWORK_NIM_FLAGS}" beacon_node $MAKE LOG_LEVEL="${LOG_LEVEL}" NIMFLAGS="-d:insecure -d:testnet_servers_image ${NETWORK_NIM_FLAGS}" beacon_node
./build/beacon_node makeDeposits \ ./build/beacon_node makeDeposits \
--quickstart-deposits=${QUICKSTART_VALIDATORS} \ --count=${TOTAL_VALIDATORS} \
--random-deposits=${RANDOM_VALIDATORS} \ --out-validators-dir="${DEPOSITS_DIR}" \
--deposits-dir="${DEPOSITS_DIR}" --out-secrets-dir="${SECRETS_DIR}"
TOTAL_VALIDATORS="$(( $QUICKSTART_VALIDATORS + $RANDOM_VALIDATORS ))"
BOOTSTRAP_IP="127.0.0.1" BOOTSTRAP_IP="127.0.0.1"
./build/beacon_node createTestnet \ ./build/beacon_node createTestnet \
--data-dir="${DATA_DIR}/node0" \ --data-dir="${DATA_DIR}/node0" \
--validators-dir="${DEPOSITS_DIR}" \ --validators-dir="${DEPOSITS_DIR}" \
--total-validators=${TOTAL_VALIDATORS} \ --total-validators=${TOTAL_VALIDATORS} \
--last-user-validator=${QUICKSTART_VALIDATORS} \ --last-user-validator=${USER_VALIDATORS} \
--output-genesis="${NETWORK_DIR}/genesis.ssz" \ --output-genesis="${NETWORK_DIR}/genesis.ssz" \
--output-bootstrap-file="${NETWORK_DIR}/bootstrap_nodes.txt" \ --output-bootstrap-file="${NETWORK_DIR}/bootstrap_nodes.txt" \
--bootstrap-address=${BOOTSTRAP_IP} \ --bootstrap-address=${BOOTSTRAP_IP} \
@ -199,11 +203,12 @@ for NUM_NODE in $(seq 0 $(( ${NUM_NODES} - 1 ))); do
fi fi
# Copy validators to individual nodes. # Copy validators to individual nodes.
# The first $NODES_WITH_VALIDATORS nodes split them equally between them, after skipping the first $QUICKSTART_VALIDATORS. # The first $NODES_WITH_VALIDATORS nodes split them equally between them, after skipping the first $USER_VALIDATORS.
NODE_DATA_DIR="${DATA_DIR}/node${NUM_NODE}" NODE_DATA_DIR="${DATA_DIR}/node${NUM_NODE}"
mkdir -p "${NODE_DATA_DIR}/validators" mkdir -p "${NODE_DATA_DIR}/validators"
if [[ $NUM_NODE -lt $NODES_WITH_VALIDATORS ]]; then if [[ $NUM_NODE -lt $NODES_WITH_VALIDATORS ]]; then
for KEYFILE in $(ls ${DEPOSITS_DIR}/*.privkey | tail -n +$(( $QUICKSTART_VALIDATORS + ($VALIDATORS_PER_NODE * $NUM_NODE) + 1 )) | head -n $VALIDATORS_PER_NODE); do # TODO: There are no longer privkey files
for KEYFILE in $(ls ${DEPOSITS_DIR}/*.privkey | tail -n +$(( $USER_VALIDATORS + ($VALIDATORS_PER_NODE * $NUM_NODE) + 1 )) | head -n $VALIDATORS_PER_NODE); do
cp -a "$KEYFILE" "${NODE_DATA_DIR}/validators/" cp -a "$KEYFILE" "${NODE_DATA_DIR}/validators/"
done done
fi fi

View File

@ -46,6 +46,7 @@ ETH2_TESTNETS_ABS=$(cd "$ETH2_TESTNETS"; pwd)
NETWORK_DIR_ABS="$ETH2_TESTNETS_ABS/nimbus/$NETWORK" NETWORK_DIR_ABS="$ETH2_TESTNETS_ABS/nimbus/$NETWORK"
DATA_DIR_ABS=$(mkdir -p "$DATA_DIR"; cd "$DATA_DIR"; pwd) DATA_DIR_ABS=$(mkdir -p "$DATA_DIR"; cd "$DATA_DIR"; pwd)
DEPOSITS_DIR_ABS="$DATA_DIR_ABS/deposits" DEPOSITS_DIR_ABS="$DATA_DIR_ABS/deposits"
SECRETS_DIR_ABS="$DATA_DIR_ABS/secrets"
DEPOSIT_CONTRACT_ADDRESS="" DEPOSIT_CONTRACT_ADDRESS=""
DEPOSIT_CONTRACT_ADDRESS_ARG="" DEPOSIT_CONTRACT_ADDRESS_ARG=""
@ -54,6 +55,7 @@ if [ "$WEB3_URL" != "" ]; then
fi fi
mkdir -p "$DEPOSITS_DIR_ABS" mkdir -p "$DEPOSITS_DIR_ABS"
mkdir -p "$SECRETS_DIR_ABS"
if [ "$ETH1_PRIVATE_KEY" != "" ]; then if [ "$ETH1_PRIVATE_KEY" != "" ]; then
make deposit_contract make deposit_contract
@ -82,17 +84,15 @@ echo "Building Docker image..."
make build make build
../build/beacon_node makeDeposits \ ../build/beacon_node makeDeposits \
--quickstart-deposits=$QUICKSTART_VALIDATORS \ --count=$TOTAL_VALIDATORS \
--random-deposits=$RANDOM_VALIDATORS \ --out-validators-dir="$DEPOSITS_DIR_ABS" \
--deposits-dir="$DEPOSITS_DIR_ABS" --out-secrets-dir="$SECRETS_DIR_ABS"
TOTAL_VALIDATORS="$(( $QUICKSTART_VALIDATORS + $RANDOM_VALIDATORS ))"
../build/beacon_node createTestnet \ ../build/beacon_node createTestnet \
--data-dir="$DATA_DIR_ABS" \ --data-dir="$DATA_DIR_ABS" \
--validators-dir="$DEPOSITS_DIR_ABS" \ --validators-dir="$DEPOSITS_DIR_ABS" \
--total-validators=$TOTAL_VALIDATORS \ --total-validators=$TOTAL_VALIDATORS \
--last-user-validator=$QUICKSTART_VALIDATORS \ --last-user-validator=$USER_VALIDATORS \
--output-genesis="$NETWORK_DIR_ABS/genesis.ssz" \ --output-genesis="$NETWORK_DIR_ABS/genesis.ssz" \
--output-bootstrap-file="$NETWORK_DIR_ABS/bootstrap_nodes.txt" \ --output-bootstrap-file="$NETWORK_DIR_ABS/bootstrap_nodes.txt" \
--bootstrap-address=$BOOTSTRAP_IP \ --bootstrap-address=$BOOTSTRAP_IP \
@ -116,7 +116,7 @@ if [[ $PUBLISH_TESTNET_RESETS != "0" ]]; then
--network=$NETWORK \ --network=$NETWORK \
--deposits-dir="$DEPOSITS_DIR_ABS" \ --deposits-dir="$DEPOSITS_DIR_ABS" \
--network-data-dir="$NETWORK_DIR_ABS" \ --network-data-dir="$NETWORK_DIR_ABS" \
--user-validators=$QUICKSTART_VALIDATORS \ --user-validators=$USER_VALIDATORS \
--total-validators=$TOTAL_VALIDATORS \ --total-validators=$TOTAL_VALIDATORS \
> /tmp/reset-network.sh > /tmp/reset-network.sh

View File

@ -1,5 +1,5 @@
CONST_PRESET=minimal CONST_PRESET=minimal
QUICKSTART_VALIDATORS=8 USER_VALIDATORS=10
RANDOM_VALIDATORS=120 TOTAL_VALIDATORS=256
BOOTSTRAP_PORT=9000 BOOTSTRAP_PORT=9000
WEB3_URL=wss://goerli.infura.io/ws/v3/809a18497dd74102b5f37d25aae3c85a WEB3_URL=wss://goerli.infura.io/ws/v3/809a18497dd74102b5f37d25aae3c85a

View File

@ -1,6 +1,6 @@
CONST_PRESET=mainnet CONST_PRESET=mainnet
QUICKSTART_VALIDATORS=8 USER_VALIDATORS=10
RANDOM_VALIDATORS=120 TOTAL_VALIDATORS=256
BOOTSTRAP_PORT=9100 BOOTSTRAP_PORT=9100
WEB3_URL=wss://goerli.infura.io/ws/v3/809a18497dd74102b5f37d25aae3c85a WEB3_URL=wss://goerli.infura.io/ws/v3/809a18497dd74102b5f37d25aae3c85a

View File

@ -9,6 +9,7 @@ source "$(dirname "$0")/vars.sh"
cd "$SIM_ROOT" cd "$SIM_ROOT"
mkdir -p "$SIMULATION_DIR" mkdir -p "$SIMULATION_DIR"
mkdir -p "$VALIDATORS_DIR" mkdir -p "$VALIDATORS_DIR"
mkdir -p "$SECRETS_DIR"
cd "$GIT_ROOT" cd "$GIT_ROOT"
@ -118,8 +119,9 @@ if [ ! -f "${LAST_VALIDATOR}" ]; then
fi fi
$BEACON_NODE_BIN makeDeposits \ $BEACON_NODE_BIN makeDeposits \
--quickstart-deposits="${NUM_VALIDATORS}" \ --count="${NUM_VALIDATORS}" \
--deposits-dir="$VALIDATORS_DIR" \ --out-validators-dir="$VALIDATORS_DIR" \
--out-secrets-dir="$SECRETS_DIR" \
$MAKE_DEPOSITS_WEB3_ARG $DELAY_ARGS \ $MAKE_DEPOSITS_WEB3_ARG $DELAY_ARGS \
--deposit-contract="${DEPOSIT_CONTRACT_ADDRESS}" --deposit-contract="${DEPOSIT_CONTRACT_ADDRESS}"

View File

@ -28,6 +28,7 @@ MASTER_NODE=$(( TOTAL_NODES - 1 ))
SIMULATION_DIR="${SIM_ROOT}/data" SIMULATION_DIR="${SIM_ROOT}/data"
METRICS_DIR="${SIM_ROOT}/prometheus" METRICS_DIR="${SIM_ROOT}/prometheus"
VALIDATORS_DIR="${SIM_ROOT}/validators" VALIDATORS_DIR="${SIM_ROOT}/validators"
SECRETS_DIR="${SIM_ROOT}/secrets"
SNAPSHOT_FILE="${SIMULATION_DIR}/state_snapshot.ssz" SNAPSHOT_FILE="${SIMULATION_DIR}/state_snapshot.ssz"
NETWORK_BOOTSTRAP_FILE="${SIMULATION_DIR}/bootstrap_nodes.txt" NETWORK_BOOTSTRAP_FILE="${SIMULATION_DIR}/bootstrap_nodes.txt"
BEACON_NODE_BIN="${GIT_ROOT}/build/beacon_node" BEACON_NODE_BIN="${GIT_ROOT}/build/beacon_node"

View File

@ -119,7 +119,7 @@ suiteReport "Interop":
timedTest "Mocked start private key": timedTest "Mocked start private key":
for i, k in privateKeys: for i, k in privateKeys:
let let
key = makeInteropPrivKey(i)[] key = makeInteropPrivKey(i)
v = k.parse(UInt256, 16) v = k.parse(UInt256, 16)
check: check:
@ -144,9 +144,8 @@ suiteReport "Interop":
var deposits: seq[Deposit] var deposits: seq[Deposit]
for i in 0..<64: for i in 0..<64:
let let privKey = makeInteropPrivKey(i)
privKey = makeInteropPrivKey(i)[] deposits.add makeDeposit(privKey.toPubKey(), privKey)
deposits.add(makeDeposit(privKey.toPubKey(), privKey))
const genesis_time = 1570500000 const genesis_time = 1570500000
var var

View File

@ -10,7 +10,7 @@
import import
unittest, ./testutil, json, unittest, ./testutil, json,
stew/byteutils, stew/byteutils,
../beacon_chain/spec/keystore ../beacon_chain/spec/[crypto, keystore]
from strutils import replace from strutils import replace
@ -79,23 +79,27 @@ const
}""" #" }""" #"
password = "testpassword" password = "testpassword"
secret = hexToSeqByte("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f") secretBytes = hexToSeqByte("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")
salt = hexToSeqByte("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") salt = hexToSeqByte("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
iv = hexToSeqByte("264daa3f303d7259501c93d997d84fe6") iv = hexToSeqByte("264daa3f303d7259501c93d997d84fe6")
suiteReport "Keystore": suiteReport "Keystore":
setup:
let secret = ValidatorPrivKey.fromRaw(secretBytes).get
timedTest "Pbkdf2 decryption": timedTest "Pbkdf2 decryption":
let decrypt = decryptKeystore(pbkdf2Vector, password) let decrypt = decryptKeystore(KeyStoreContent pbkdf2Vector,
KeyStorePass password)
check decrypt.isOk check decrypt.isOk
check secret == decrypt.get() check secret == decrypt.get()
timedTest "Pbkdf2 encryption": timedTest "Pbkdf2 encryption":
let encrypt = encryptKeystore[KdfPbkdf2](secret, password, salt=salt, iv=iv, let encrypt = encryptKeystore(KdfPbkdf2, secret,
path="m/12381/60/0/0") KeyStorePass password,
check encrypt.isOk salt=salt, iv=iv,
path = validateKeyPath "m/12381/60/0/0")
var var
encryptJson = parseJson(encrypt.get()) encryptJson = parseJson(encrypt.string)
pbkdf2Json = parseJson(pbkdf2Vector) pbkdf2Json = parseJson(pbkdf2Vector)
encryptJson{"uuid"} = %"" encryptJson{"uuid"} = %""
pbkdf2Json{"uuid"} = %"" pbkdf2Json{"uuid"} = %""
@ -103,16 +107,27 @@ suiteReport "Keystore":
check encryptJson == pbkdf2Json check encryptJson == pbkdf2Json
timedTest "Pbkdf2 errors": timedTest "Pbkdf2 errors":
check encryptKeystore[KdfPbkdf2](secret, "", salt = [byte 1]).isErr expect Defect:
check encryptKeystore[KdfPbkdf2](secret, "", iv = [byte 1]).isErr echo encryptKeystore(KdfPbkdf2, secret, salt = [byte 1]).string
check decryptKeystore(pbkdf2Vector, "wrong pass").isErr expect Defect:
check decryptKeystore(pbkdf2Vector, "").isErr echo encryptKeystore(KdfPbkdf2, secret, iv = [byte 1]).string
check decryptKeystore("{\"a\": 0}", "").isErr
check decryptKeystore("", "").isErr check decryptKeystore(KeyStoreContent pbkdf2Vector,
KeyStorePass "wrong pass").isErr
check decryptKeystore(KeyStoreContent pbkdf2Vector,
KeyStorePass "").isErr
check decryptKeystore(KeyStoreContent "{\"a\": 0}",
KeyStorePass "").isErr
check decryptKeystore(KeyStoreContent "",
KeyStorePass "").isErr
template checkVariant(remove): untyped = template checkVariant(remove): untyped =
check decryptKeystore(pbkdf2Vector.replace(remove, ""), password).isErr check decryptKeystore(KeyStoreContent pbkdf2Vector.replace(remove, ""),
KeyStorePass password).isErr
checkVariant "d4e5" # salt checkVariant "d4e5" # salt
checkVariant "18b1" # checksum checkVariant "18b1" # checksum
@ -122,4 +137,5 @@ suiteReport "Keystore":
var badKdf = parseJson(pbkdf2Vector) var badKdf = parseJson(pbkdf2Vector)
badKdf{"crypto", "kdf", "function"} = %"invalid" badKdf{"crypto", "kdf", "function"} = %"invalid"
check decryptKeystore($badKdf, password).iserr check decryptKeystore(KeyStoreContent $badKdf,
KeyStorePass password).iserr