mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-09 13:56:23 +00:00
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:
|
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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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))
|
|
||||||
|
@ -19,3 +19,4 @@ import
|
|||||||
|
|
||||||
export
|
export
|
||||||
merkleization, ssz_serialization, types
|
merkleization, ssz_serialization, types
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
|
||||||
firstIdx = 0): seq[Deposit] =
|
|
||||||
info "Generating deposits", totalValidators, outputDir, randomKeys
|
|
||||||
for i in 0 ..< totalValidators:
|
|
||||||
let
|
let
|
||||||
v = validatorFileBaseName(firstIdx + i)
|
keystorePath = validatorsDir / keyName / keystoreFileName
|
||||||
depositFn = outputDir / v & ".deposit.json"
|
keystoreContents = KeyStoreContent:
|
||||||
privKeyFn = outputDir / v & ".privkey"
|
try: readFile(keystorePath)
|
||||||
|
except IOError as err:
|
||||||
|
error "Failed to read keystore", err = err.msg, path = keystorePath
|
||||||
|
return
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
if randomKeys:
|
|
||||||
(pubKey, privKey) = crypto.newKeyPair().tryGet()
|
|
||||||
else:
|
else:
|
||||||
privKey = makeInteropPrivKey(i).tryGet()
|
error "Failed to decrypt keystore", keystorePath, passphrasePath
|
||||||
pubKey = privKey.toPubKey()
|
return
|
||||||
|
|
||||||
let dp = makeDeposit(pubKey, privKey)
|
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
|
||||||
|
|
||||||
result.add(dp)
|
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:
|
||||||
|
prompt = "Keystore decryption failed. Please try again"
|
||||||
|
dec remainingAttempts
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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],
|
|
||||||
|
proc sendDeposits*(deposits: seq[Deposit],
|
||||||
web3Url, depositContractAddress, privateKey: string,
|
web3Url, depositContractAddress, privateKey: string,
|
||||||
delayGenerator: DelayGenerator = nil) {.async.} =
|
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"
|
|
||||||
|
@ -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}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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}"
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user