Implement more of the KeyStore spec and integrate it in the beacon node
This commit is contained in:
parent
1fc9413c48
commit
17343442ea
|
@ -998,7 +998,7 @@ programMain:
|
|||
of createTestnet:
|
||||
var deposits: seq[Deposit]
|
||||
for i in config.firstValidator.int ..< config.totalValidators.int:
|
||||
let depositFile = config.validatorsDir /
|
||||
let depositFile = config.testnetDepositsDir /
|
||||
validatorFileBaseName(i) & ".deposit.json"
|
||||
try:
|
||||
deposits.add Json.loadFile(depositFile, Deposit)
|
||||
|
@ -1096,15 +1096,13 @@ programMain:
|
|||
node.start()
|
||||
|
||||
of makeDeposits:
|
||||
createDir(config.depositsDir)
|
||||
createDir(config.outValidatorsDir)
|
||||
|
||||
let
|
||||
quickstartDeposits = generateDeposits(
|
||||
config.totalQuickstartDeposits, config.depositsDir, false)
|
||||
|
||||
randomDeposits = generateDeposits(
|
||||
config.totalRandomDeposits, config.depositsDir, true,
|
||||
firstIdx = config.totalQuickstartDeposits)
|
||||
deposits = generateDeposits(
|
||||
config.totalDeposits,
|
||||
config.outValidatorsDir,
|
||||
config.outSecretsDir).tryGet
|
||||
|
||||
if config.web3Url.len > 0 and config.depositContractAddress.len > 0:
|
||||
if config.minDelay > config.maxDelay:
|
||||
|
@ -1121,8 +1119,9 @@ programMain:
|
|||
depositContract = config.depositContractAddress
|
||||
|
||||
waitFor sendDeposits(
|
||||
quickstartDeposits & randomDeposits,
|
||||
deposits,
|
||||
config.web3Url,
|
||||
config.depositContractAddress,
|
||||
config.depositPrivateKey,
|
||||
delayGenerator)
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{.push raises: [Defect].}
|
||||
|
||||
import
|
||||
os, options, strformat, strutils,
|
||||
os, options, strformat,
|
||||
chronicles, confutils, json_serialization,
|
||||
confutils/defs, confutils/std/net,
|
||||
chronicles/options as chroniclesOptions,
|
||||
spec/[crypto]
|
||||
spec/[crypto, keystore]
|
||||
|
||||
export
|
||||
defs, enabledLogLevel, parseCmdArg, completeCmdArg
|
||||
|
@ -39,11 +39,6 @@ type
|
|||
desc: "The Eth1 network tracked by the beacon node."
|
||||
name: "eth1-network" }: Eth1Network
|
||||
|
||||
quickStart* {.
|
||||
defaultValue: false
|
||||
desc: "Run in quickstart mode"
|
||||
name: "quick-start" }: bool
|
||||
|
||||
dataDir* {.
|
||||
defaultValue: config.defaultDataDir()
|
||||
desc: "The directory where nimbus will store all blockchain data."
|
||||
|
@ -60,6 +55,10 @@ type
|
|||
desc: "Address of the deposit contract."
|
||||
name: "deposit-contract" }: string
|
||||
|
||||
nonInteractive* {.
|
||||
desc: "Do not display interative prompts. Quit on missing configuration."
|
||||
name: "non-interactive" }: bool
|
||||
|
||||
case cmd* {.
|
||||
command
|
||||
defaultValue: noCommand }: BNStartUpCmd
|
||||
|
@ -106,6 +105,14 @@ type
|
|||
abbr: "v"
|
||||
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* {.
|
||||
desc: "Json file specifying a recent state snapshot."
|
||||
abbr: "s"
|
||||
|
@ -177,13 +184,12 @@ type
|
|||
name: "dump" }: bool
|
||||
|
||||
of createTestnet:
|
||||
validatorsDir* {.
|
||||
desc: "Directory containing validator descriptors named 'vXXXXXXX.deposit.json'."
|
||||
abbr: "d"
|
||||
testnetDepositsDir* {.
|
||||
desc: "Directory containing validator keystores."
|
||||
name: "validators-dir" }: InputDir
|
||||
|
||||
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
|
||||
|
||||
firstValidator* {.
|
||||
|
@ -209,7 +215,6 @@ type
|
|||
genesisOffset* {.
|
||||
defaultValue: 5
|
||||
desc: "Seconds from now to add to genesis time."
|
||||
abbr: "g"
|
||||
name: "genesis-offset" }: int
|
||||
|
||||
outputGenesis* {.
|
||||
|
@ -231,34 +236,34 @@ type
|
|||
name: "keyfile" }: seq[ValidatorKeyPath]
|
||||
|
||||
of makeDeposits:
|
||||
totalQuickstartDeposits* {.
|
||||
defaultValue: 0
|
||||
desc: "Number of quick-start deposits to generate."
|
||||
name: "quickstart-deposits" }: int
|
||||
totalDeposits* {.
|
||||
defaultValue: 1
|
||||
desc: "Number of deposits to generate."
|
||||
name: "count" }: int
|
||||
|
||||
totalRandomDeposits* {.
|
||||
defaultValue: 0
|
||||
desc: "Number of secure random deposits to generate."
|
||||
name: "random-deposits" }: int
|
||||
|
||||
depositsDir* {.
|
||||
outValidatorsDir* {.
|
||||
defaultValue: "validators"
|
||||
desc: "Folder to write deposits to."
|
||||
name: "deposits-dir" }: string
|
||||
desc: "Output folder for validator keystores and deposits."
|
||||
name: "out-validators-dir" }: string
|
||||
|
||||
outSecretsDir* {.
|
||||
defaultValue: "secrets"
|
||||
desc: "Output folder for randomly generated keystore passphrases."
|
||||
name: "out-secrets-dir" }: string
|
||||
|
||||
depositPrivateKey* {.
|
||||
defaultValue: ""
|
||||
desc: "Private key of the controlling (sending) account",
|
||||
desc: "Private key of the controlling (sending) account.",
|
||||
name: "deposit-private-key" }: string
|
||||
|
||||
minDelay* {.
|
||||
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
|
||||
|
||||
maxDelay* {.
|
||||
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
|
||||
|
||||
ValidatorClientConf* = object
|
||||
|
@ -273,6 +278,10 @@ type
|
|||
abbr: "d"
|
||||
name: "data-dir" }: OutDir
|
||||
|
||||
nonInteractive* {.
|
||||
desc: "Do not display interative prompts. Quit on missing configuration."
|
||||
name: "non-interactive" }: bool
|
||||
|
||||
case cmd* {.
|
||||
command
|
||||
defaultValue: VCNoCommand }: VCStartUpCmd
|
||||
|
@ -290,10 +299,18 @@ type
|
|||
|
||||
validators* {.
|
||||
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"
|
||||
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 =
|
||||
let dataDir = when defined(windows):
|
||||
"AppData" / "Roaming" / "Nimbus"
|
||||
|
@ -315,7 +332,7 @@ func dumpDir*(conf: BeaconNodeConf|ValidatorClientConf): string =
|
|||
conf.dataDir / "dump"
|
||||
|
||||
func localValidatorsDir*(conf: BeaconNodeConf|ValidatorClientConf): string =
|
||||
conf.dataDir / "validators"
|
||||
string conf.validatorsDir.get(InputDir(conf.dataDir / "validators"))
|
||||
|
||||
func databaseDir*(conf: BeaconNodeConf|ValidatorClientConf): string =
|
||||
conf.dataDir / "db"
|
||||
|
@ -328,26 +345,6 @@ func defaultListenAddress*(conf: BeaconNodeConf|ValidatorClientConf): ValidIpAdd
|
|||
func defaultAdminListenAddress*(conf: BeaconNodeConf|ValidatorClientConf): ValidIpAddress =
|
||||
(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,
|
||||
value: TypedInputFile|InputFile|InputDir|OutPath|OutDir|OutFile) =
|
||||
writer.writeValue(string value)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import
|
||||
stew/endians2, stint,
|
||||
./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 =
|
||||
# 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),
|
||||
)
|
||||
|
||||
func makeInteropPrivKey*(i: int): BlsResult[ValidatorPrivKey] =
|
||||
func makeInteropPrivKey*(i: int): ValidatorPrivKey =
|
||||
var bytes: array[32, byte]
|
||||
bytes[0..7] = uint64(i).toBytesLE()
|
||||
|
||||
|
@ -28,19 +28,13 @@ func makeInteropPrivKey*(i: int): BlsResult[ValidatorPrivKey] =
|
|||
privkeyBytes = eth2hash(bytes)
|
||||
key = (UInt256.fromBytesLE(privkeyBytes.data) mod curveOrder).toBytesBE()
|
||||
|
||||
ValidatorPrivKey.fromRaw(key)
|
||||
ValidatorPrivKey.fromRaw(key).get
|
||||
|
||||
const eth1BlockHash* = block:
|
||||
var x: Eth2Digest
|
||||
for v in x.data.mitems: v = 0x42
|
||||
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*(
|
||||
pubkey: ValidatorPubKey, privkey: ValidatorPrivKey, epoch = 0.Epoch,
|
||||
amount: Gwei = MAX_EFFECTIVE_BALANCE.Gwei,
|
||||
|
|
|
@ -26,16 +26,6 @@ func round_step_down*(x: Natural, step: static Natural): int {.inline.} =
|
|||
else:
|
||||
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
|
||||
## Sparse Merkle tree
|
||||
# There is an extra "depth" layer to store leaf nodes
|
||||
|
@ -67,7 +57,7 @@ proc merkleTreeFromLeaves*(
|
|||
# with the zeroHash corresponding to the current depth
|
||||
let nodeHash = withEth2Hash:
|
||||
h.update result.nnznodes[depth-1][^1]
|
||||
h.update ZeroHashes[depth-1]
|
||||
h.update zeroHashes[depth-1]
|
||||
result.nnznodes[depth].add nodeHash
|
||||
|
||||
proc getMerkleProof*[Depth: static int](
|
||||
|
@ -85,7 +75,7 @@ proc getMerkleProof*[Depth: static int](
|
|||
if nodeIdx < tree.nnznodes[depth].len:
|
||||
result[depth] = tree.nnznodes[depth][nodeIdx]
|
||||
else:
|
||||
result[depth] = ZeroHashes[depth]
|
||||
result[depth] = zeroHashes[depth]
|
||||
|
||||
proc attachMerkleProofs*(deposits: var seq[Deposit]) =
|
||||
let deposit_data_roots = mapIt(deposits, it.data.hash_tree_root)
|
||||
|
|
|
@ -69,6 +69,8 @@ type
|
|||
|
||||
BlsResult*[T] = Result[T, cstring]
|
||||
|
||||
RandomSourceDepleted* = object of CatchableError
|
||||
|
||||
func `==`*(a, b: BlsValue): bool =
|
||||
if a.kind != b.kind: return false
|
||||
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 =
|
||||
a == b.blsValue
|
||||
|
||||
template `==`*(a, b: ValidatorPrivKey): bool =
|
||||
blscurve.SecretKey(a) == blscurve.SecretKey(b)
|
||||
|
||||
# API
|
||||
# ----------------------------------------------------------------------
|
||||
# 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:
|
||||
raise (ref ValueError)(msg: $v.error)
|
||||
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)
|
||||
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
json, math, strutils,
|
||||
eth/keyfile/uuid,
|
||||
stew/[results, byteutils],
|
||||
nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, sysrand],
|
||||
./crypto
|
||||
json, math, strutils, strformat,
|
||||
eth/keyfile/uuid, stew/[results, byteutils, bitseqs, bitops2],
|
||||
nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, sysrand], blscurve,
|
||||
datatypes, crypto, digest, helpers
|
||||
|
||||
export results
|
||||
export
|
||||
results
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
|
@ -64,6 +64,22 @@ type
|
|||
|
||||
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
|
||||
saltSize = 32
|
||||
|
||||
|
@ -80,6 +96,108 @@ const
|
|||
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] =
|
||||
var ctx: sha256
|
||||
ctx.init()
|
||||
|
@ -100,12 +218,11 @@ template hexToBytes(data, name: string): untyped =
|
|||
except ValueError:
|
||||
return err "ks: failed to parse " & name
|
||||
|
||||
proc decryptKeystore*(data, passphrase: string): KsResult[seq[byte]] =
|
||||
let ks =
|
||||
try:
|
||||
parseJson(data)
|
||||
except Exception:
|
||||
return err "ks: failed to parse keystore"
|
||||
proc decryptKeystore*(data: KeyStoreContent,
|
||||
password: KeyStorePass): KsResult[ValidatorPrivKey] =
|
||||
# TODO: `parseJson` can raise a general `Exception`
|
||||
let ks = try: parseJson(data.string)
|
||||
except Exception: return err "ks: failed to parse keystore"
|
||||
|
||||
var
|
||||
decKey: seq[byte]
|
||||
|
@ -126,7 +243,7 @@ proc decryptKeystore*(data, passphrase: string): KsResult[seq[byte]] =
|
|||
kdfParams = crypto.kdf.params
|
||||
|
||||
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")
|
||||
cipherMsg = hexToBytes(crypto.cipher.message, "cipher")
|
||||
checksumMsg = hexToBytes(crypto.checksum.message, "checksum")
|
||||
|
@ -151,41 +268,41 @@ proc decryptKeystore*(data, passphrase: string): KsResult[seq[byte]] =
|
|||
aesCipher.decrypt(cipherMsg, secret)
|
||||
aesCipher.clear()
|
||||
|
||||
result = ok secret
|
||||
ValidatorPrivKey.fromRaw(secret)
|
||||
|
||||
proc encryptKeystore*[T: KdfParams](secret: openarray[byte];
|
||||
passphrase: string;
|
||||
path="";
|
||||
salt: openarray[byte] = @[];
|
||||
iv: openarray[byte] = @[];
|
||||
ugly=true): KsResult[string] =
|
||||
proc encryptKeystore*(T: type[KdfParams],
|
||||
privKey: ValidatorPrivkey,
|
||||
password = KeyStorePass "",
|
||||
path = KeyPath "",
|
||||
salt: openarray[byte] = @[],
|
||||
iv: openarray[byte] = @[],
|
||||
ugly = true): KeyStoreContent =
|
||||
var
|
||||
secret = privKey.toRaw[^32..^1]
|
||||
decKey: seq[byte]
|
||||
aesCipher: CTR[aes128]
|
||||
aesIv = newSeq[byte](aes128.sizeBlock)
|
||||
kdfSalt = newSeq[byte](saltSize)
|
||||
cipherMsg = newSeq[byte](secret.len)
|
||||
|
||||
if salt.len == saltSize:
|
||||
if salt.len > 0:
|
||||
doAssert salt.len == saltSize
|
||||
kdfSalt = @salt
|
||||
elif salt.len > 0:
|
||||
return err "ks: invalid salt"
|
||||
elif randomBytes(kdfSalt) != saltSize:
|
||||
return err "ks: no random bytes for salt"
|
||||
else:
|
||||
getRandomBytesOrPanic(kdfSalt)
|
||||
|
||||
if iv.len == aes128.sizeBlock:
|
||||
if iv.len > 0:
|
||||
doAssert iv.len == aes128.sizeBlock
|
||||
aesIv = @iv
|
||||
elif iv.len > 0:
|
||||
return err "ks: invalid iv"
|
||||
elif randomBytes(aesIv) != aes128.sizeBlock:
|
||||
return err "ks: no random bytes for iv"
|
||||
else:
|
||||
getRandomBytesOrPanic(aesIv)
|
||||
|
||||
when T is KdfPbkdf2:
|
||||
decKey = sha256.pbkdf2(passphrase, kdfSalt, pbkdf2Params.c,
|
||||
decKey = sha256.pbkdf2(password.string, kdfSalt, pbkdf2Params.c,
|
||||
pbkdf2Params.dklen)
|
||||
|
||||
var kdf = Kdf[KdfPbkdf2](function: "pbkdf2", params: pbkdf2Params, message: "")
|
||||
kdf.params.salt = kdfSalt.toHex()
|
||||
kdf.params.salt = byteutils.toHex(kdfSalt)
|
||||
else:
|
||||
return
|
||||
|
||||
|
@ -193,29 +310,75 @@ proc encryptKeystore*[T: KdfParams](secret: openarray[byte];
|
|||
aesCipher.encrypt(secret, cipherMsg)
|
||||
aesCipher.clear()
|
||||
|
||||
let pubkey = (? ValidatorPrivkey.fromRaw(secret)).toPubKey()
|
||||
let pubkey = privKey.toPubKey()
|
||||
|
||||
let
|
||||
sum = shaChecksum(decKey.toOpenArray(16, 31), cipherMsg)
|
||||
uuid = uuidGenerate().get
|
||||
|
||||
keystore = Keystore[T](
|
||||
crypto: Crypto[T](
|
||||
kdf: kdf,
|
||||
checksum: Checksum(
|
||||
function: "sha256",
|
||||
message: sum.toHex()
|
||||
message: byteutils.toHex(sum)
|
||||
),
|
||||
cipher: Cipher(
|
||||
function: "aes-128-ctr",
|
||||
params: CipherParams(iv: aesIv.toHex()),
|
||||
message: cipherMsg.toHex()
|
||||
params: CipherParams(iv: byteutils.toHex(aesIv)),
|
||||
message: byteutils.toHex(cipherMsg)
|
||||
)
|
||||
),
|
||||
pubkey: pubkey.toHex(),
|
||||
path: path,
|
||||
uuid: $(? uuidGenerate()),
|
||||
version: 4
|
||||
)
|
||||
pubkey: toHex(pubkey),
|
||||
path: path.string,
|
||||
uuid: $uuid,
|
||||
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))
|
||||
|
|
|
@ -19,3 +19,4 @@ import
|
|||
|
||||
export
|
||||
merkleization, ssz_serialization, types
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ func computeZeroHashes: array[sizeof(Limit) * 8, Eth2Digest] =
|
|||
for i in 1 .. result.high:
|
||||
result[i] = mergeBranches(result[i - 1], result[i - 1])
|
||||
|
||||
const zeroHashes = computeZeroHashes()
|
||||
const zeroHashes* = computeZeroHashes()
|
||||
|
||||
func addChunk(merkleizer: var SszChunksMerkleizer, data: openarray[byte]) =
|
||||
doAssert data.len > 0 and data.len <= bytesPerChunk
|
||||
|
|
|
@ -21,7 +21,7 @@ import
|
|||
eth2_network, eth2_discovery, validator_pool, beacon_node_types,
|
||||
nimbus_binary_common,
|
||||
version, ssz/merkleization,
|
||||
sync_manager,
|
||||
sync_manager, validator_keygen,
|
||||
spec/eth2_apis/validator_callsigs_types,
|
||||
eth2_json_rpc_serialization
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import
|
|||
# Local modules
|
||||
spec/[datatypes, digest, crypto, beaconstate, helpers, validator, network],
|
||||
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,
|
||||
mainchain_monitor, version, ssz/merkleization, interop,
|
||||
attestation_aggregation, sync_manager, sszdump
|
||||
|
|
|
@ -1,76 +1,153 @@
|
|||
import
|
||||
os, strutils,
|
||||
os, strutils, terminal,
|
||||
chronicles, chronos, blscurve, nimcrypto, json_serialization, serialization,
|
||||
web3, stint, eth/keys,
|
||||
spec/[datatypes, digest, crypto], conf, ssz/merkleization, interop, merkle_minimal
|
||||
web3, stint, eth/keys, confutils,
|
||||
spec/[datatypes, digest, crypto, keystore], conf, ssz/merkleization, merkle_minimal
|
||||
|
||||
contract(DepositContract):
|
||||
proc deposit(pubkey: Bytes48, withdrawalCredentials: Bytes32, signature: Bytes96, deposit_data_root: FixedBytes[32])
|
||||
|
||||
const
|
||||
keystoreFileName* = "keystore.json"
|
||||
depositFileName* = "deposit.json"
|
||||
|
||||
type
|
||||
DelayGenerator* = proc(): chronos.Duration {.closure, gcsafe.}
|
||||
|
||||
proc writeTextFile(filename: string, contents: string) =
|
||||
writeFile(filename, contents)
|
||||
# echo "Wrote ", filename
|
||||
|
||||
proc writeFile(filename: string, value: auto) =
|
||||
Json.saveFile(filename, value, pretty = true)
|
||||
# echo "Wrote ", filename
|
||||
{.push raises: [Defect].}
|
||||
|
||||
proc ethToWei(eth: UInt256): UInt256 =
|
||||
eth * 1000000000000000000.u256
|
||||
|
||||
proc generateDeposits*(totalValidators: int,
|
||||
outputDir: string,
|
||||
randomKeys: bool,
|
||||
firstIdx = 0): seq[Deposit] =
|
||||
info "Generating deposits", totalValidators, outputDir, randomKeys
|
||||
for i in 0 ..< totalValidators:
|
||||
let
|
||||
v = validatorFileBaseName(firstIdx + i)
|
||||
depositFn = outputDir / v & ".deposit.json"
|
||||
privKeyFn = outputDir / v & ".privkey"
|
||||
proc loadKeyStore(conf: BeaconNodeConf|ValidatorClientConf,
|
||||
validatorsDir, keyName: string): Option[ValidatorPrivKey] =
|
||||
let
|
||||
keystorePath = validatorsDir / keyName / keystoreFileName
|
||||
keystoreContents = KeyStoreContent:
|
||||
try: readFile(keystorePath)
|
||||
except IOError as err:
|
||||
error "Failed to read keystore", err = err.msg, path = keystorePath
|
||||
return
|
||||
|
||||
if existsFile(depositFn) and existsFile(privKeyFn):
|
||||
try:
|
||||
result.add Json.loadFile(depositFn, Deposit)
|
||||
continue
|
||||
except SerializationError as err:
|
||||
debug "Rewriting unreadable deposit", err = err.formatMsg(depositFn)
|
||||
discard
|
||||
if conf.secretsDir.isSome:
|
||||
let passphrasePath = conf.secretsDir.get / keyName
|
||||
if fileExists(passphrasePath):
|
||||
let
|
||||
passphrase = KeyStorePass:
|
||||
try: readFile(passphrasePath)
|
||||
except IOError as err:
|
||||
error "Failed to read passphrase file", err = err.msg, path = passphrasePath
|
||||
return
|
||||
|
||||
var
|
||||
privkey{.noInit.}: ValidatorPrivKey
|
||||
pubKey{.noInit.}: ValidatorPubKey
|
||||
let res = decryptKeystore(keystoreContents, passphrase)
|
||||
if res.isOk:
|
||||
return res.get.some
|
||||
else:
|
||||
error "Failed to decrypt keystore", keystorePath, passphrasePath
|
||||
return
|
||||
|
||||
if randomKeys:
|
||||
(pubKey, privKey) = crypto.newKeyPair().tryGet()
|
||||
if conf.nonInteractive:
|
||||
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:
|
||||
privKey = makeInteropPrivKey(i).tryGet()
|
||||
pubKey = privKey.toPubKey()
|
||||
prompt = "Keystore decryption failed. Please try again"
|
||||
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
|
||||
# cleanly allows free intermixing of pre-existing and newly generated
|
||||
# deposit and private key files. TODO: only generate new Merkle proof
|
||||
# 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())
|
||||
writeFile(depositFn, result[result.len - 1])
|
||||
ok deposits
|
||||
|
||||
proc sendDeposits*(
|
||||
deposits: seq[Deposit],
|
||||
web3Url, depositContractAddress, privateKey: string,
|
||||
delayGenerator: DelayGenerator = nil) {.async.} =
|
||||
{.pop.}
|
||||
|
||||
proc sendDeposits*(deposits: seq[Deposit],
|
||||
web3Url, depositContractAddress, privateKey: string,
|
||||
delayGenerator: DelayGenerator = nil) {.async.} =
|
||||
var web3 = await newWeb3(web3Url)
|
||||
if privateKey.len != 0:
|
||||
web3.privateKey = PrivateKey.fromHex(privateKey).tryGet()
|
||||
web3.privateKey = PrivateKey.fromHex(privateKey).tryGet
|
||||
else:
|
||||
let accounts = await web3.provider.eth_accounts()
|
||||
if accounts.len == 0:
|
||||
|
@ -79,9 +156,9 @@ proc sendDeposits*(
|
|||
web3.defaultAccount = accounts[0]
|
||||
|
||||
let contractAddress = Address.fromHex(depositContractAddress)
|
||||
let depositContract = web3.contractSender(DepositContract, contractAddress)
|
||||
|
||||
for i, dp in deposits:
|
||||
let depositContract = web3.contractSender(DepositContract, contractAddress)
|
||||
discard await depositContract.deposit(
|
||||
Bytes48(dp.data.pubKey.toRaw()),
|
||||
Bytes32(dp.data.withdrawal_credentials.data),
|
||||
|
@ -91,17 +168,3 @@ proc sendDeposits*(
|
|||
if delayGenerator != nil:
|
||||
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"
|
||||
|
|
|
@ -87,6 +87,7 @@ cli do (skipGoerliKey {.
|
|||
.replace(")", "_")
|
||||
dataDir = buildDir / "data" / dataDirName
|
||||
validatorsDir = dataDir / "validators"
|
||||
secretsDir = dataDir / "secrets"
|
||||
beaconNodeBinary = buildDir / "beacon_node_" & dataDirName
|
||||
var
|
||||
nimFlags = "-d:chronicles_log_level=TRACE " & getEnv("NIM_PARAMS")
|
||||
|
@ -137,7 +138,8 @@ cli do (skipGoerliKey {.
|
|||
mode = Verbose
|
||||
exec replace(&"""{beaconNodeBinary} makeDeposits
|
||||
--random-deposits=1
|
||||
--deposits-dir="{validatorsDir}"
|
||||
--out-validators-dir="{validatorsDir}"
|
||||
--out-secrets-dir="{secretsDir}"
|
||||
--deposit-private-key={privKey}
|
||||
--web3-url={web3Url}
|
||||
{depositContractOpt}
|
||||
|
|
|
@ -114,8 +114,13 @@ fi
|
|||
NETWORK="testnet${TESTNET}"
|
||||
|
||||
rm -rf "${DATA_DIR}"
|
||||
|
||||
DEPOSITS_DIR="${DATA_DIR}/deposits_dir"
|
||||
mkdir -p "${DEPOSITS_DIR}"
|
||||
|
||||
SECRETS_DIR="${DATA_DIR}/secrets"
|
||||
mkdir -p "${SECRETS_DIR}"
|
||||
|
||||
NETWORK_DIR="${DATA_DIR}/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
|
||||
|
||||
./build/beacon_node makeDeposits \
|
||||
--quickstart-deposits=${QUICKSTART_VALIDATORS} \
|
||||
--random-deposits=${RANDOM_VALIDATORS} \
|
||||
--deposits-dir="${DEPOSITS_DIR}"
|
||||
--count=${TOTAL_VALIDATORS} \
|
||||
--out-validators-dir="${DEPOSITS_DIR}" \
|
||||
--out-secrets-dir="${SECRETS_DIR}"
|
||||
|
||||
TOTAL_VALIDATORS="$(( $QUICKSTART_VALIDATORS + $RANDOM_VALIDATORS ))"
|
||||
BOOTSTRAP_IP="127.0.0.1"
|
||||
./build/beacon_node createTestnet \
|
||||
--data-dir="${DATA_DIR}/node0" \
|
||||
--validators-dir="${DEPOSITS_DIR}" \
|
||||
--total-validators=${TOTAL_VALIDATORS} \
|
||||
--last-user-validator=${QUICKSTART_VALIDATORS} \
|
||||
--last-user-validator=${USER_VALIDATORS} \
|
||||
--output-genesis="${NETWORK_DIR}/genesis.ssz" \
|
||||
--output-bootstrap-file="${NETWORK_DIR}/bootstrap_nodes.txt" \
|
||||
--bootstrap-address=${BOOTSTRAP_IP} \
|
||||
|
@ -199,11 +203,12 @@ for NUM_NODE in $(seq 0 $(( ${NUM_NODES} - 1 ))); do
|
|||
fi
|
||||
|
||||
# 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}"
|
||||
mkdir -p "${NODE_DATA_DIR}/validators"
|
||||
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/"
|
||||
done
|
||||
fi
|
||||
|
|
|
@ -46,6 +46,7 @@ ETH2_TESTNETS_ABS=$(cd "$ETH2_TESTNETS"; pwd)
|
|||
NETWORK_DIR_ABS="$ETH2_TESTNETS_ABS/nimbus/$NETWORK"
|
||||
DATA_DIR_ABS=$(mkdir -p "$DATA_DIR"; cd "$DATA_DIR"; pwd)
|
||||
DEPOSITS_DIR_ABS="$DATA_DIR_ABS/deposits"
|
||||
SECRETS_DIR_ABS="$DATA_DIR_ABS/secrets"
|
||||
DEPOSIT_CONTRACT_ADDRESS=""
|
||||
DEPOSIT_CONTRACT_ADDRESS_ARG=""
|
||||
|
||||
|
@ -54,6 +55,7 @@ if [ "$WEB3_URL" != "" ]; then
|
|||
fi
|
||||
|
||||
mkdir -p "$DEPOSITS_DIR_ABS"
|
||||
mkdir -p "$SECRETS_DIR_ABS"
|
||||
|
||||
if [ "$ETH1_PRIVATE_KEY" != "" ]; then
|
||||
make deposit_contract
|
||||
|
@ -82,17 +84,15 @@ echo "Building Docker image..."
|
|||
make build
|
||||
|
||||
../build/beacon_node makeDeposits \
|
||||
--quickstart-deposits=$QUICKSTART_VALIDATORS \
|
||||
--random-deposits=$RANDOM_VALIDATORS \
|
||||
--deposits-dir="$DEPOSITS_DIR_ABS"
|
||||
|
||||
TOTAL_VALIDATORS="$(( $QUICKSTART_VALIDATORS + $RANDOM_VALIDATORS ))"
|
||||
--count=$TOTAL_VALIDATORS \
|
||||
--out-validators-dir="$DEPOSITS_DIR_ABS" \
|
||||
--out-secrets-dir="$SECRETS_DIR_ABS"
|
||||
|
||||
../build/beacon_node createTestnet \
|
||||
--data-dir="$DATA_DIR_ABS" \
|
||||
--validators-dir="$DEPOSITS_DIR_ABS" \
|
||||
--total-validators=$TOTAL_VALIDATORS \
|
||||
--last-user-validator=$QUICKSTART_VALIDATORS \
|
||||
--last-user-validator=$USER_VALIDATORS \
|
||||
--output-genesis="$NETWORK_DIR_ABS/genesis.ssz" \
|
||||
--output-bootstrap-file="$NETWORK_DIR_ABS/bootstrap_nodes.txt" \
|
||||
--bootstrap-address=$BOOTSTRAP_IP \
|
||||
|
@ -116,7 +116,7 @@ if [[ $PUBLISH_TESTNET_RESETS != "0" ]]; then
|
|||
--network=$NETWORK \
|
||||
--deposits-dir="$DEPOSITS_DIR_ABS" \
|
||||
--network-data-dir="$NETWORK_DIR_ABS" \
|
||||
--user-validators=$QUICKSTART_VALIDATORS \
|
||||
--user-validators=$USER_VALIDATORS \
|
||||
--total-validators=$TOTAL_VALIDATORS \
|
||||
> /tmp/reset-network.sh
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
CONST_PRESET=minimal
|
||||
QUICKSTART_VALIDATORS=8
|
||||
RANDOM_VALIDATORS=120
|
||||
USER_VALIDATORS=10
|
||||
TOTAL_VALIDATORS=256
|
||||
BOOTSTRAP_PORT=9000
|
||||
WEB3_URL=wss://goerli.infura.io/ws/v3/809a18497dd74102b5f37d25aae3c85a
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
CONST_PRESET=mainnet
|
||||
QUICKSTART_VALIDATORS=8
|
||||
RANDOM_VALIDATORS=120
|
||||
USER_VALIDATORS=10
|
||||
TOTAL_VALIDATORS=256
|
||||
BOOTSTRAP_PORT=9100
|
||||
WEB3_URL=wss://goerli.infura.io/ws/v3/809a18497dd74102b5f37d25aae3c85a
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ source "$(dirname "$0")/vars.sh"
|
|||
cd "$SIM_ROOT"
|
||||
mkdir -p "$SIMULATION_DIR"
|
||||
mkdir -p "$VALIDATORS_DIR"
|
||||
mkdir -p "$SECRETS_DIR"
|
||||
|
||||
cd "$GIT_ROOT"
|
||||
|
||||
|
@ -118,8 +119,9 @@ if [ ! -f "${LAST_VALIDATOR}" ]; then
|
|||
fi
|
||||
|
||||
$BEACON_NODE_BIN makeDeposits \
|
||||
--quickstart-deposits="${NUM_VALIDATORS}" \
|
||||
--deposits-dir="$VALIDATORS_DIR" \
|
||||
--count="${NUM_VALIDATORS}" \
|
||||
--out-validators-dir="$VALIDATORS_DIR" \
|
||||
--out-secrets-dir="$SECRETS_DIR" \
|
||||
$MAKE_DEPOSITS_WEB3_ARG $DELAY_ARGS \
|
||||
--deposit-contract="${DEPOSIT_CONTRACT_ADDRESS}"
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ MASTER_NODE=$(( TOTAL_NODES - 1 ))
|
|||
SIMULATION_DIR="${SIM_ROOT}/data"
|
||||
METRICS_DIR="${SIM_ROOT}/prometheus"
|
||||
VALIDATORS_DIR="${SIM_ROOT}/validators"
|
||||
SECRETS_DIR="${SIM_ROOT}/secrets"
|
||||
SNAPSHOT_FILE="${SIMULATION_DIR}/state_snapshot.ssz"
|
||||
NETWORK_BOOTSTRAP_FILE="${SIMULATION_DIR}/bootstrap_nodes.txt"
|
||||
BEACON_NODE_BIN="${GIT_ROOT}/build/beacon_node"
|
||||
|
|
|
@ -119,7 +119,7 @@ suiteReport "Interop":
|
|||
timedTest "Mocked start private key":
|
||||
for i, k in privateKeys:
|
||||
let
|
||||
key = makeInteropPrivKey(i)[]
|
||||
key = makeInteropPrivKey(i)
|
||||
v = k.parse(UInt256, 16)
|
||||
|
||||
check:
|
||||
|
@ -144,9 +144,8 @@ suiteReport "Interop":
|
|||
var deposits: seq[Deposit]
|
||||
|
||||
for i in 0..<64:
|
||||
let
|
||||
privKey = makeInteropPrivKey(i)[]
|
||||
deposits.add(makeDeposit(privKey.toPubKey(), privKey))
|
||||
let privKey = makeInteropPrivKey(i)
|
||||
deposits.add makeDeposit(privKey.toPubKey(), privKey)
|
||||
|
||||
const genesis_time = 1570500000
|
||||
var
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
import
|
||||
unittest, ./testutil, json,
|
||||
stew/byteutils,
|
||||
../beacon_chain/spec/keystore
|
||||
../beacon_chain/spec/[crypto, keystore]
|
||||
|
||||
from strutils import replace
|
||||
|
||||
|
@ -79,23 +79,27 @@ const
|
|||
}""" #"
|
||||
|
||||
password = "testpassword"
|
||||
secret = hexToSeqByte("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")
|
||||
secretBytes = hexToSeqByte("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")
|
||||
salt = hexToSeqByte("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
|
||||
iv = hexToSeqByte("264daa3f303d7259501c93d997d84fe6")
|
||||
|
||||
suiteReport "Keystore":
|
||||
setup:
|
||||
let secret = ValidatorPrivKey.fromRaw(secretBytes).get
|
||||
|
||||
timedTest "Pbkdf2 decryption":
|
||||
let decrypt = decryptKeystore(pbkdf2Vector, password)
|
||||
let decrypt = decryptKeystore(KeyStoreContent pbkdf2Vector,
|
||||
KeyStorePass password)
|
||||
check decrypt.isOk
|
||||
check secret == decrypt.get()
|
||||
|
||||
timedTest "Pbkdf2 encryption":
|
||||
let encrypt = encryptKeystore[KdfPbkdf2](secret, password, salt=salt, iv=iv,
|
||||
path="m/12381/60/0/0")
|
||||
check encrypt.isOk
|
||||
|
||||
let encrypt = encryptKeystore(KdfPbkdf2, secret,
|
||||
KeyStorePass password,
|
||||
salt=salt, iv=iv,
|
||||
path = validateKeyPath "m/12381/60/0/0")
|
||||
var
|
||||
encryptJson = parseJson(encrypt.get())
|
||||
encryptJson = parseJson(encrypt.string)
|
||||
pbkdf2Json = parseJson(pbkdf2Vector)
|
||||
encryptJson{"uuid"} = %""
|
||||
pbkdf2Json{"uuid"} = %""
|
||||
|
@ -103,16 +107,27 @@ suiteReport "Keystore":
|
|||
check encryptJson == pbkdf2Json
|
||||
|
||||
timedTest "Pbkdf2 errors":
|
||||
check encryptKeystore[KdfPbkdf2](secret, "", salt = [byte 1]).isErr
|
||||
check encryptKeystore[KdfPbkdf2](secret, "", iv = [byte 1]).isErr
|
||||
expect Defect:
|
||||
echo encryptKeystore(KdfPbkdf2, secret, salt = [byte 1]).string
|
||||
|
||||
check decryptKeystore(pbkdf2Vector, "wrong pass").isErr
|
||||
check decryptKeystore(pbkdf2Vector, "").isErr
|
||||
check decryptKeystore("{\"a\": 0}", "").isErr
|
||||
check decryptKeystore("", "").isErr
|
||||
expect Defect:
|
||||
echo encryptKeystore(KdfPbkdf2, secret, iv = [byte 1]).string
|
||||
|
||||
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 =
|
||||
check decryptKeystore(pbkdf2Vector.replace(remove, ""), password).isErr
|
||||
check decryptKeystore(KeyStoreContent pbkdf2Vector.replace(remove, ""),
|
||||
KeyStorePass password).isErr
|
||||
|
||||
checkVariant "d4e5" # salt
|
||||
checkVariant "18b1" # checksum
|
||||
|
@ -122,4 +137,5 @@ suiteReport "Keystore":
|
|||
var badKdf = parseJson(pbkdf2Vector)
|
||||
badKdf{"crypto", "kdf", "function"} = %"invalid"
|
||||
|
||||
check decryptKeystore($badKdf, password).iserr
|
||||
check decryptKeystore(KeyStoreContent $badKdf,
|
||||
KeyStorePass password).iserr
|
||||
|
|
Loading…
Reference in New Issue