Validator key management API (#2755)
Implements https://github.com/ethereum/beacon-APIs/pull/151
This commit is contained in:
parent
05eb8846bf
commit
65257b82f8
|
@ -8,14 +8,15 @@
|
|||
{.push raises: [Defect].}
|
||||
|
||||
import
|
||||
std/[deques, intsets, streams, tables, hashes],
|
||||
std/[deques, intsets, streams, tables, hashes, options],
|
||||
stew/endians2,
|
||||
./spec/datatypes/[phase0, altair],
|
||||
./spec/keystore,
|
||||
./consensus_object_pools/block_pools_types,
|
||||
./fork_choice/fork_choice_types,
|
||||
./validators/slashing_protection
|
||||
|
||||
export deques, tables, hashes, block_pools_types
|
||||
export deques, tables, hashes, options, block_pools_types
|
||||
|
||||
const
|
||||
ATTESTATION_LOOKBACK* =
|
||||
|
@ -146,22 +147,27 @@ type
|
|||
# Validator Pool
|
||||
#
|
||||
# #############################################
|
||||
ValidatorKind* = enum
|
||||
inProcess
|
||||
remote
|
||||
ValidatorKind* {.pure.} = enum
|
||||
Local, Remote
|
||||
|
||||
ValidatorConnection* = object
|
||||
inStream*: Stream
|
||||
outStream*: Stream
|
||||
pubKeyStr*: string
|
||||
|
||||
ValidatorPrivateItem* = object
|
||||
privateKey*: ValidatorPrivKey
|
||||
description*: Option[string]
|
||||
path*: Option[KeyPath]
|
||||
uuid*: Option[string]
|
||||
version*: Option[uint64]
|
||||
|
||||
AttachedValidator* = ref object
|
||||
pubKey*: ValidatorPubKey
|
||||
|
||||
case kind*: ValidatorKind
|
||||
of inProcess:
|
||||
privKey*: ValidatorPrivKey
|
||||
else:
|
||||
of ValidatorKind.Local:
|
||||
data*: ValidatorPrivateItem
|
||||
of ValidatorKind.Remote:
|
||||
connection*: ValidatorConnection
|
||||
|
||||
# The index at which this validator has been observed in the chain -
|
||||
|
|
|
@ -312,6 +312,12 @@ type
|
|||
defaultValueDesc: "127.0.0.1"
|
||||
name: "rest-address" }: ValidIpAddress
|
||||
|
||||
validatorApiEnabled* {.
|
||||
desc: "Enable the REST (BETA version) validator keystore management " &
|
||||
"API",
|
||||
defaultValue: false,
|
||||
name: "validator-api"}: bool
|
||||
|
||||
inProcessValidators* {.
|
||||
desc: "Disable the push model (the beacon node tells a signing process with the private keys of the validators what to sign and when) and load the validators in the beacon node itself"
|
||||
defaultValue: true # the use of the nimbus_signing_process binary by default will be delayed until async I/O over stdin/stdout is developed for the child process.
|
||||
|
@ -546,6 +552,12 @@ type
|
|||
desc: "A directory containing validator keystore passwords"
|
||||
name: "secrets-dir" }: Option[InputDir]
|
||||
|
||||
validatorApiEnabled* {.
|
||||
desc: "Enable the REST (BETA version) validator keystore management " &
|
||||
"API",
|
||||
defaultValue: false,
|
||||
name: "validator-api"}: bool
|
||||
|
||||
case cmd* {.
|
||||
command
|
||||
defaultValue: VCNoCommand }: VCStartUpCmd
|
||||
|
|
|
@ -1252,6 +1252,8 @@ proc installRestHandlers(restServer: RestServerRef, node: BeaconNode) =
|
|||
restServer.router.installNimbusApiHandlers(node)
|
||||
restServer.router.installNodeApiHandlers(node)
|
||||
restServer.router.installValidatorApiHandlers(node)
|
||||
if node.config.validatorApiEnabled:
|
||||
restServer.router.installValidatorManagementHandlers(node)
|
||||
|
||||
proc installMessageValidators(node: BeaconNode) =
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/p2p-interface.md#attestations-and-aggregation
|
||||
|
@ -1641,13 +1643,13 @@ proc handleValidatorExitCommand(config: BeaconNodeConf) {.async.} =
|
|||
"key '" & validatorKeyAsStr & "'."
|
||||
quit 1
|
||||
|
||||
let signingKey = loadKeystore(
|
||||
let signingItem = loadKeystore(
|
||||
validatorsDir,
|
||||
config.secretsDir,
|
||||
validatorKeyAsStr,
|
||||
config.nonInteractive)
|
||||
|
||||
if signingKey.isNone:
|
||||
if signingItem.isNone:
|
||||
fatal "Unable to continue without decrypted signing key"
|
||||
quit 1
|
||||
|
||||
|
@ -1669,8 +1671,11 @@ proc handleValidatorExitCommand(config: BeaconNodeConf) {.async.} =
|
|||
epoch: exitAtEpoch,
|
||||
validator_index: validatorIdx))
|
||||
|
||||
signedExit.signature = get_voluntary_exit_signature(
|
||||
fork, genesisValidatorsRoot, signedExit.message, signingKey.get).toValidatorSig()
|
||||
signedExit.signature =
|
||||
block:
|
||||
let key = signingItem.get().privateKey
|
||||
get_voluntary_exit_signature(fork, genesisValidatorsRoot,
|
||||
signedExit.message, key).toValidatorSig()
|
||||
|
||||
template ask(prompt: string): string =
|
||||
try:
|
||||
|
|
|
@ -7,23 +7,20 @@
|
|||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
import
|
||||
# Standard library
|
||||
std/[os, strutils, tables],
|
||||
|
||||
# Local modules
|
||||
./spec/[digest, crypto],
|
||||
./validators/keystore_management
|
||||
import std/[os, strutils, tables]
|
||||
import "."/spec/[digest, crypto],
|
||||
"."/validators/keystore_management,
|
||||
"."/beacon_node_types
|
||||
|
||||
{.pop.} # TODO moduletests exceptions
|
||||
|
||||
programMain:
|
||||
var validators: Table[ValidatorPubKey, ValidatorPrivKey]
|
||||
var validators: Table[ValidatorPubKey, ValidatorPrivateItem]
|
||||
# load and send all public keys so the BN knows for which ones to ping us
|
||||
doAssert paramCount() == 2
|
||||
for curr in validatorKeysFromDirs(paramStr(1), paramStr(2)):
|
||||
validators[curr.toPubKey.toPubKey()] = curr
|
||||
echo curr.toPubKey
|
||||
validators[curr.privateKey.toPubKey().toPubKey()] = curr
|
||||
echo curr.privateKey.toPubKey
|
||||
echo "end"
|
||||
|
||||
# simple format: `<pubkey> <eth2digest_to_sign>` => `<signature>`
|
||||
|
@ -31,6 +28,7 @@ programMain:
|
|||
let args = stdin.readLine.split(" ")
|
||||
doAssert args.len == 2
|
||||
|
||||
let privKey = validators[ValidatorPubKey.fromHex(args[0]).get()]
|
||||
let item = validators[ValidatorPubKey.fromHex(args[0]).get()]
|
||||
|
||||
echo blsSign(privKey, Eth2Digest.fromHex(args[1]).data).toValidatorSig()
|
||||
echo blsSign(item.privateKey,
|
||||
Eth2Digest.fromHex(args[1]).data).toValidatorSig()
|
||||
|
|
|
@ -76,14 +76,14 @@ proc initGenesis*(vc: ValidatorClientRef): Future[RestGenesis] {.async.} =
|
|||
proc initValidators*(vc: ValidatorClientRef): Future[bool] {.async.} =
|
||||
info "Initializaing validators", path = vc.config.validatorsDir()
|
||||
var duplicates: seq[ValidatorPubKey]
|
||||
for key in vc.config.validatorKeys():
|
||||
let pubkey = key.toPubKey().toPubKey()
|
||||
for item in vc.config.validatorItems():
|
||||
let pubkey = item.privateKey.toPubKey().toPubKey()
|
||||
if pubkey in duplicates:
|
||||
error "Duplicate validator's key found", validator_pubkey = pubkey
|
||||
return false
|
||||
else:
|
||||
duplicates.add(pubkey)
|
||||
vc.attachedValidators.addLocalValidator(key)
|
||||
vc.attachedValidators.addLocalValidator(item)
|
||||
return true
|
||||
|
||||
proc initClock*(vc: ValidatorClientRef): Future[BeaconClock] {.async.} =
|
||||
|
|
|
@ -14,9 +14,9 @@ import
|
|||
"."/[
|
||||
rest_utils,
|
||||
rest_beacon_api, rest_config_api, rest_debug_api, rest_event_api,
|
||||
rest_nimbus_api, rest_node_api, rest_validator_api]
|
||||
rest_nimbus_api, rest_node_api, rest_validator_api, rest_key_management_api]
|
||||
|
||||
export
|
||||
rest_utils,
|
||||
rest_beacon_api, rest_config_api, rest_debug_api, rest_event_api,
|
||||
rest_nimbus_api, rest_node_api, rest_validator_api
|
||||
rest_nimbus_api, rest_node_api, rest_validator_api, rest_key_management_api
|
||||
|
|
|
@ -0,0 +1,424 @@
|
|||
# Copyright (c) 2021 Status Research & Development GmbH
|
||||
# Licensed and distributed under either of
|
||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import std/[tables, os, sequtils, strutils]
|
||||
import chronos, chronicles, confutils,
|
||||
stew/[base10, results, byteutils, io2], bearssl, blscurve
|
||||
# Local modules
|
||||
import ".."/[conf, version, filepath, beacon_node_types, beacon_node_common]
|
||||
import ".."/spec/[keystore, crypto]
|
||||
import ".."/rpc/rest_utils
|
||||
import ".."/validators/[keystore_management, validator_pool]
|
||||
|
||||
export results
|
||||
|
||||
type
|
||||
ValidatorToggleAction {.pure.} = enum
|
||||
Enable, Disable
|
||||
|
||||
KmResult*[T] = Result[T, cstring]
|
||||
|
||||
StoredValidatorKeyFlag* {.pure.} = enum
|
||||
Valid, NoPassword, NoPermission, Disabled
|
||||
|
||||
StoredValidatorKey* = object
|
||||
name*: string
|
||||
filename*: string
|
||||
flag*: StoredValidatorKeyFlag
|
||||
path*: KeyPath
|
||||
description*: string
|
||||
pubkey*: ValidatorPubKey
|
||||
|
||||
ValidatorListItem* = object
|
||||
pubkey*: ValidatorPubKey
|
||||
status*: string
|
||||
description*: string
|
||||
path*: string
|
||||
|
||||
ValidatorKeystoreItem* = object
|
||||
keystore*: Keystore
|
||||
password*: string
|
||||
|
||||
proc `$`*(s: StoredValidatorKeyFlag): string =
|
||||
case s
|
||||
of StoredValidatorKeyFlag.Valid:
|
||||
"enabled"
|
||||
of StoredValidatorKeyFlag.NoPassword:
|
||||
"failed"
|
||||
of StoredValidatorKeyFlag.NoPermission:
|
||||
"failed"
|
||||
of StoredValidatorKeyFlag.Disabled:
|
||||
"disabled"
|
||||
|
||||
proc init*(t: typedesc[ValidatorListItem],
|
||||
key: StoredValidatorKey): ValidatorListItem {.
|
||||
raises: [Defect].} =
|
||||
ValidatorListItem(pubkey: key.pubkey, status: $key.flag,
|
||||
description: key.description, path: string(key.path))
|
||||
|
||||
proc listValidators*(conf: AnyConf): seq[StoredValidatorKey] {.
|
||||
raises: [Defect].} =
|
||||
var validators: seq[StoredValidatorKey]
|
||||
try:
|
||||
for kind, file in walkDir(conf.validatorsDir()):
|
||||
if kind == pcDir:
|
||||
let keyName = splitFile(file).name
|
||||
let rkey = ValidatorPubKey.fromHex(keyName)
|
||||
if rkey.isErr():
|
||||
# Skip folders which represents invalid public key
|
||||
continue
|
||||
let secretFile = conf.secretsDir() / keyName
|
||||
let keystorePath = conf.validatorsDir() / keyName
|
||||
let keystoreFile = keystorePath / KeystoreFileName
|
||||
let disableFile = keystorePath / DisableFileName
|
||||
|
||||
if not(fileExists(keystoreFile)):
|
||||
# Skip folders which do not have keystore file inside.
|
||||
continue
|
||||
|
||||
let keystore =
|
||||
block:
|
||||
let res = loadKeystoreFile(keystoreFile)
|
||||
if res.isErr():
|
||||
# Skip folders which do not have keystore of proper format.
|
||||
continue
|
||||
res.get()
|
||||
|
||||
let flag =
|
||||
if fileExists(secretFile):
|
||||
if checkSensitiveFilePermissions(secretFile):
|
||||
if not(fileExists(disableFile)):
|
||||
StoredValidatorKeyFlag.Valid
|
||||
else:
|
||||
StoredValidatorKeyFlag.Disabled
|
||||
else:
|
||||
StoredValidatorKeyFlag.NoPermission
|
||||
else:
|
||||
StoredValidatorKeyFlag.NoPassword
|
||||
let item = StoredValidatorKey(name: keyName,
|
||||
filename: keystoreFile, flag: flag,
|
||||
path: keystore.path,
|
||||
description: keystore.description[],
|
||||
pubkey: rkey.get())
|
||||
validators.add(item)
|
||||
validators
|
||||
except OSError:
|
||||
return validators
|
||||
|
||||
func getPubKey*(privkey: ValidatorPrivKey): KmResult[ValidatorPubKey] {.
|
||||
raises: [Defect].} =
|
||||
## Derive a public key from a private key
|
||||
var pubKey: blscurve.PublicKey
|
||||
let ok = publicFromSecret(pubKey, SecretKey privkey)
|
||||
if not(ok):
|
||||
return err("Invalid private key or zero key")
|
||||
ok(ValidatorPubKey(blob: pubKey.exportRaw()))
|
||||
|
||||
proc addValidator(pool: var ValidatorPool,
|
||||
rng: var BrHmacDrbgContext,
|
||||
conf: AnyConf, keystore: Keystore,
|
||||
password: string): KmResult[void] {.
|
||||
raises: [Defect].} =
|
||||
let keypass = KeystorePass.init(password)
|
||||
let privateKey =
|
||||
block:
|
||||
let res = decryptKeystore(keystore, keypass)
|
||||
if res.isOk():
|
||||
res.get()
|
||||
else:
|
||||
return err("Keystore decryption failed")
|
||||
|
||||
let publicKey = ? privateKey.getPubKey()
|
||||
let keyName = publicKey.toHex()
|
||||
|
||||
let secretFile = conf.secretsDir() / keyName
|
||||
let keystorePath = conf.validatorsDir() / keyName
|
||||
let keystoreFile = keystorePath / KeystoreFileName
|
||||
|
||||
if fileExists(keystoreFile) or fileExists(secretFile):
|
||||
return err("Keystore artifacts already exists")
|
||||
|
||||
let plainStorage = createKeystore(kdfScrypt, rng, privateKey, keypass)
|
||||
|
||||
let encodedStorage =
|
||||
try:
|
||||
Json.encode(plainStorage)
|
||||
except SerializationError:
|
||||
error "Could not serialize keystore", key_path = keystoreFile
|
||||
return err("Could not serialize keystore")
|
||||
|
||||
let cleanupSecretsDir =
|
||||
if not(dirExists(conf.secretsDir())):
|
||||
let res = secureCreatePath(conf.secretsDir())
|
||||
if res.isErr():
|
||||
return err("Unable to create data secrets folder")
|
||||
true
|
||||
else:
|
||||
false
|
||||
|
||||
let cleanupValidatorsDir =
|
||||
if not(dirExists(conf.validatorsDir())):
|
||||
let res = secureCreatePath(conf.validatorsDir())
|
||||
if res.isErr():
|
||||
if cleanupSecretsDir: discard io2.removeDir(conf.secretsDir())
|
||||
return err("Unable to create data validators folder")
|
||||
true
|
||||
else:
|
||||
false
|
||||
|
||||
block:
|
||||
let res = secureCreatePath(keystorePath)
|
||||
if res.isErr():
|
||||
if cleanupSecretsDir: discard io2.removeDir(conf.secretsDir())
|
||||
if cleanupValidatorsDir: discard io2.removeDir(conf.validatorsDir())
|
||||
return err("Unable to create folder for keystore")
|
||||
|
||||
block:
|
||||
let res = secureWriteFile(secretFile, keypass.str)
|
||||
if res.isErr():
|
||||
discard io2.removeDir(keystorePath)
|
||||
if cleanupSecretsDir: discard io2.removeDir(conf.secretsDir())
|
||||
if cleanupValidatorsDir: discard io2.removeDir(conf.validatorsDir())
|
||||
return err("Could not store password file")
|
||||
|
||||
block:
|
||||
let res = secureWriteFile(keystoreFile, encodedStorage)
|
||||
if res.isErr():
|
||||
discard io2.removeFile(secretFile)
|
||||
discard io2.removeDir(keystorePath)
|
||||
if cleanupSecretsDir: discard io2.removeDir(conf.secretsDir())
|
||||
if cleanupValidatorsDir: discard io2.removeDir(conf.validatorsDir())
|
||||
return err("Could not store keystore file")
|
||||
|
||||
pool.addLocalValidator(ValidatorPrivateItem.init(privateKey, keystore))
|
||||
ok()
|
||||
|
||||
proc removeValidator(pool: var ValidatorPool, conf: AnyConf,
|
||||
publicKey: ValidatorPubKey): KmResult[void] {.
|
||||
raises: [Defect].} =
|
||||
let keyName = publicKey.toHex()
|
||||
let keystorePath = conf.validatorsDir() / keyName
|
||||
let keystoreFile = keystorePath / KeystoreFileName
|
||||
let secretFile = conf.secretsDir() / keyName
|
||||
try:
|
||||
removeDir(keystorePath, false)
|
||||
except OSError:
|
||||
return err("Could not remove keystore directory")
|
||||
if dirExists(keystorePath):
|
||||
return err("Could not remove keystore directory")
|
||||
let res = io2.removeFile(secretFile)
|
||||
if res.isErr():
|
||||
return err("Could not remove password file")
|
||||
pool.removeValidator(publicKey)
|
||||
ok()
|
||||
|
||||
proc toggleValidator(pool: var ValidatorPool,
|
||||
conf: AnyConf,
|
||||
publicKey: ValidatorPubKey,
|
||||
action: ValidatorToggleAction): KmResult[void] {.
|
||||
raises:[Defect].} =
|
||||
let keyName = publicKey.toHex()
|
||||
let keystorePath = conf.validatorsDir() / keyName
|
||||
let disableFile = keystorePath / DisableFileName
|
||||
let secretFile = conf.secretsDir() / keyName
|
||||
let keystoreFile = keystorePath / KeystoreFileName
|
||||
|
||||
if dirExists(keystorePath) and checkSensitivePathPermissions(keyStorePath):
|
||||
case action
|
||||
of ValidatorToggleAction.Enable:
|
||||
if fileExists(disableFile):
|
||||
if checkSensitivePathPermissions(secretFile) and
|
||||
checkSensitivePathPermissions(keystoreFile):
|
||||
let privateKey =
|
||||
block:
|
||||
let res = loadKeystoreUnsafe(conf.validatorsDir(),
|
||||
conf.secretsDir(), keyName)
|
||||
if res.isErr():
|
||||
return err("Could not decrypt validator's keystore")
|
||||
res.get()
|
||||
let res = io2.removeFile(disableFile)
|
||||
if res.isErr():
|
||||
return err("Could not enable validator's keystore")
|
||||
if isNil(pool.getValidator(publicKey)):
|
||||
pool.addLocalValidator(privateKey)
|
||||
ok()
|
||||
else:
|
||||
err("Could not read validator's keystore")
|
||||
else:
|
||||
# Disable file is already missing.
|
||||
if isNil(pool.getValidator(publicKey)):
|
||||
# If validator pool do not have ``publicKey`` validator we going to
|
||||
# add it.
|
||||
if checkSensitivePathPermissions(secretFile) and
|
||||
checkSensitivePathPermissions(keystoreFile):
|
||||
let privateKey =
|
||||
block:
|
||||
let res = loadKeystoreUnsafe(conf.validatorsDir(),
|
||||
conf.secretsDir(), keyName)
|
||||
if res.isErr():
|
||||
return err("Could not decrypt validator's keystore")
|
||||
res.get()
|
||||
if isNil(pool.getValidator(publicKey)):
|
||||
pool.addLocalValidator(privateKey)
|
||||
ok()
|
||||
else:
|
||||
err("Could not read validator's keystore")
|
||||
else:
|
||||
ok()
|
||||
of ValidatorToggleAction.Disable:
|
||||
if not(fileExists(disableFile)):
|
||||
# Disable file is not present, we first create `.disable` file and in
|
||||
# case of success we removing validator from validators pool.
|
||||
block:
|
||||
let res = secureWriteFile(disableFile, DisableFileContent)
|
||||
if res.isErr():
|
||||
return err("Could not create disable file")
|
||||
pool.removeValidator(publicKey)
|
||||
ok()
|
||||
else:
|
||||
# Disable file is already present.
|
||||
pool.removeValidator(publicKey)
|
||||
ok()
|
||||
else:
|
||||
err("No validator keystore found")
|
||||
|
||||
proc installValidatorManagementHandlers*(router: var RestRouter,
|
||||
node: BeaconNode) =
|
||||
router.api(MethodGet, "/api/nimbus/v1/validators") do (
|
||||
) -> RestApiResponse:
|
||||
let validators = node.config.listValidators().mapIt(
|
||||
ValidatorListItem.init(it)
|
||||
)
|
||||
return RestApiResponse.jsonResponse(validators)
|
||||
|
||||
router.api(MethodPost, "/api/nimbus/v1/validators") do (
|
||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||
let keystores =
|
||||
block:
|
||||
if contentBody.isNone():
|
||||
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
|
||||
let dres = decodeBody(seq[ValidatorKeystoreItem], contentBody.get())
|
||||
if dres.isErr():
|
||||
return RestApiResponse.jsonError(Http400, InvalidKeystoreObjects,
|
||||
$dres.error())
|
||||
dres.get()
|
||||
|
||||
var failures: seq[RestFailureItem]
|
||||
for index, item in keystores.pairs():
|
||||
let res = addValidator(node.attachedValidators[], node.network.rng[],
|
||||
node.config, item.keystore, item.password)
|
||||
if res.isErr():
|
||||
failures.add(RestFailureItem(index: uint64(index),
|
||||
message: $res.error()))
|
||||
if len(failures) > 0:
|
||||
return RestApiResponse.jsonErrorList(Http400, KeystoreAdditionFailure,
|
||||
failures)
|
||||
else:
|
||||
return RestApiResponse.jsonMsgResponse(KeystoreAdditionSuccess)
|
||||
|
||||
router.api(MethodPost, "/api/nimbus/v1/validators/enable") do (
|
||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||
let keys =
|
||||
block:
|
||||
if contentBody.isNone():
|
||||
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
|
||||
let dres = decodeBody(seq[ValidatorPubKey], contentBody.get())
|
||||
if dres.isErr():
|
||||
return RestApiResponse.jsonError(Http400, InvalidValidatorPublicKey,
|
||||
$dres.error())
|
||||
dres.get()
|
||||
|
||||
var failures: seq[RestFailureItem]
|
||||
for index, key in keys.pairs():
|
||||
let res = toggleValidator(node.attachedValidators[], node.config, key,
|
||||
ValidatorToggleAction.Enable)
|
||||
if res.isErr():
|
||||
failures.add(RestFailureItem(index: uint64(index),
|
||||
message: $res.error()))
|
||||
if len(failures) > 0:
|
||||
return RestApiResponse.jsonErrorList(Http400, KeystoreModificationFailure,
|
||||
failures)
|
||||
else:
|
||||
return RestApiResponse.jsonMsgResponse(KeystoreModificationSuccess)
|
||||
|
||||
router.api(MethodPost, "/api/nimbus/v1/validators/disable") do (
|
||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||
let keys =
|
||||
block:
|
||||
if contentBody.isNone():
|
||||
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
|
||||
let dres = decodeBody(seq[ValidatorPubKey], contentBody.get())
|
||||
if dres.isErr():
|
||||
return RestApiResponse.jsonError(Http400, InvalidValidatorPublicKey,
|
||||
$dres.error())
|
||||
dres.get()
|
||||
|
||||
var failures: seq[RestFailureItem]
|
||||
for index, key in keys.pairs():
|
||||
let res = toggleValidator(node.attachedValidators[], node.config, key,
|
||||
ValidatorToggleAction.Disable)
|
||||
if res.isErr():
|
||||
failures.add(RestFailureItem(index: uint64(index),
|
||||
message: $res.error()))
|
||||
if len(failures) > 0:
|
||||
return RestApiResponse.jsonErrorList(Http400, KeystoreModificationFailure,
|
||||
failures)
|
||||
else:
|
||||
return RestApiResponse.jsonMsgResponse(KeystoreModificationSuccess)
|
||||
|
||||
router.api(MethodPost, "/api/nimbus/v1/validators/remove") do (
|
||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||
let keys =
|
||||
block:
|
||||
if contentBody.isNone():
|
||||
return RestApiResponse.jsonError(Http404, EmptyRequestBodyError)
|
||||
let dres = decodeBody(seq[ValidatorPubKey], contentBody.get())
|
||||
if dres.isErr():
|
||||
return RestApiResponse.jsonError(Http400, InvalidValidatorPublicKey,
|
||||
$dres.error())
|
||||
dres.get()
|
||||
|
||||
var failures: seq[RestFailureItem]
|
||||
for index, key in keys.pairs():
|
||||
let res = removeValidator(node.attachedValidators[], node.config, key)
|
||||
if res.isErr():
|
||||
failures.add(RestFailureItem(index: uint64(index),
|
||||
message: $res.error()))
|
||||
if len(failures) > 0:
|
||||
return RestApiResponse.jsonErrorList(Http400, KeystoreRemovalFailure,
|
||||
failures)
|
||||
else:
|
||||
return RestApiResponse.jsonMsgResponse(KeystoreRemovalSuccess)
|
||||
|
||||
router.redirect(
|
||||
MethodGet,
|
||||
"/nimbus/v1/validators",
|
||||
"/api/nimbus/v1/validators"
|
||||
)
|
||||
|
||||
router.redirect(
|
||||
MethodPost,
|
||||
"/nimbus/v1/validators",
|
||||
"/api/nimbus/v1/validators"
|
||||
)
|
||||
|
||||
router.redirect(
|
||||
MethodPost,
|
||||
"/nimbus/v1/validators/enable",
|
||||
"/api/nimbus/v1/validators/enable"
|
||||
)
|
||||
|
||||
router.redirect(
|
||||
MethodPost,
|
||||
"/nimbus/v1/validators/disable",
|
||||
"/api/nimbus/v1/validators/disable"
|
||||
)
|
||||
|
||||
router.redirect(
|
||||
MethodPost,
|
||||
"/nimbus/v1/validators/remove",
|
||||
"/api/nimbus/v1/validators/remove"
|
||||
)
|
|
@ -175,6 +175,22 @@ const
|
|||
"Internal server error"
|
||||
NoImplementationError* =
|
||||
"Not implemented yet"
|
||||
KeystoreAdditionFailure* =
|
||||
"Could not add some keystores"
|
||||
InvalidKeystoreObjects* =
|
||||
"Invalid keystore objects found"
|
||||
KeystoreAdditionSuccess* =
|
||||
"All keystores has been added"
|
||||
KeystoreModificationFailure* =
|
||||
"Could not change keystore(s) state"
|
||||
KeystoreModificationSuccess* =
|
||||
"Keystore(s) state was successfully modified"
|
||||
KeystoreRemovalSuccess* =
|
||||
"Keystore(s) was successfully removed"
|
||||
KeystoreRemovalFailure* =
|
||||
"Could not remove keystore(s)"
|
||||
InvalidValidatorPublicKey* =
|
||||
"Invalid validator's public key(s) found"
|
||||
|
||||
type
|
||||
ValidatorIndexError* {.pure.} = enum
|
||||
|
|
|
@ -43,7 +43,7 @@ type
|
|||
RestAttestationError* = object
|
||||
code*: uint64
|
||||
message*: string
|
||||
failures*: seq[RestAttestationsFailure]
|
||||
failures*: seq[RestFailureItem]
|
||||
|
||||
EncodeTypes* =
|
||||
AttesterSlashing |
|
||||
|
|
|
@ -132,6 +132,10 @@ type
|
|||
slot*: Slot
|
||||
validators*: seq[ValidatorIndex]
|
||||
|
||||
RestFailureItem* = object
|
||||
index*: uint64
|
||||
message*: string
|
||||
|
||||
RestAttestationsFailure* = object
|
||||
index*: uint64
|
||||
message*: string
|
||||
|
|
|
@ -11,12 +11,12 @@ import
|
|||
std/[os, strutils, terminal, wordwrap, unicode],
|
||||
chronicles, chronos, json_serialization, zxcvbn,
|
||||
serialization, blscurve, eth/common/eth_types, eth/keys, confutils, bearssl,
|
||||
../spec/[eth2_merkleization, keystore],
|
||||
../spec/datatypes/base,
|
||||
".."/spec/[eth2_merkleization, keystore],
|
||||
".."/spec/datatypes/base,
|
||||
stew/io2, libp2p/crypto/crypto as lcrypto,
|
||||
nimcrypto/utils as ncrutils,
|
||||
".."/[conf, filepath],
|
||||
../networking/network_metadata
|
||||
".."/[conf, filepath, beacon_node_types],
|
||||
".."/networking/network_metadata
|
||||
|
||||
export
|
||||
keystore
|
||||
|
@ -27,8 +27,11 @@ when defined(windows):
|
|||
{.localPassC: "-fno-lto".} # no LTO for crypto
|
||||
|
||||
const
|
||||
keystoreFileName* = "keystore.json"
|
||||
netKeystoreFileName* = "network_keystore.json"
|
||||
KeystoreFileName* = "keystore.json"
|
||||
NetKeystoreFileName* = "network_keystore.json"
|
||||
DisableFileName* = ".disable"
|
||||
DisableFileContent* = "Please do not remove this file manually. " &
|
||||
"This can lead to slashing of this validator's key."
|
||||
|
||||
type
|
||||
WalletPathPair* = object
|
||||
|
@ -39,6 +42,8 @@ type
|
|||
walletPath*: WalletPathPair
|
||||
seed*: KeySeed
|
||||
|
||||
AnyConf* = BeaconNodeConf | ValidatorClientConf
|
||||
|
||||
const
|
||||
minPasswordLen = 12
|
||||
minPasswordEntropy = 60.0
|
||||
|
@ -53,6 +58,16 @@ proc echoP*(msg: string) =
|
|||
echo ""
|
||||
echo wrapWords(msg, 80)
|
||||
|
||||
proc init*(t: typedesc[ValidatorPrivateItem], privateKey: ValidatorPrivKey,
|
||||
keystore: Keystore): ValidatorPrivateItem =
|
||||
ValidatorPrivateItem(
|
||||
privateKey: privateKey,
|
||||
description: some(keystore.description[]),
|
||||
path: some(keystore.path),
|
||||
uuid: some(keystore.uuid),
|
||||
version: some(uint64(keystore.version))
|
||||
)
|
||||
|
||||
proc checkAndCreateDataDir*(dataDir: string): bool =
|
||||
when defined(posix):
|
||||
let requiredPerms = 0o700
|
||||
|
@ -110,6 +125,44 @@ proc checkAndCreateDataDir*(dataDir: string): bool =
|
|||
|
||||
return true
|
||||
|
||||
proc checkSensitivePathPermissions*(dirFilePath: string): bool =
|
||||
## If ``dirFilePath`` is file, then check if file has only
|
||||
##
|
||||
## - "(600) rwx------" permissions on Posix (Linux, MacOS, BSD)
|
||||
## - current user only ACL on Windows
|
||||
##
|
||||
## If ``dirFilePath`` is directory, then check if directory has only
|
||||
##
|
||||
## - "(700) rwx------" permissions on Posix (Linux, MacOS, BSD)
|
||||
## - current user only ACL on Windows
|
||||
##
|
||||
## Procedure returns ``true`` if directory/file is present and all required
|
||||
## permissions are set.
|
||||
let r1 = isDir(dirFilePath)
|
||||
let r2 = isFile(dirFilePath)
|
||||
if r1 or r2:
|
||||
when defined(windows):
|
||||
let res = checkCurrentUserOnlyACL(dirFilePath)
|
||||
if res.isErr():
|
||||
false
|
||||
else:
|
||||
if res.get() == false:
|
||||
false
|
||||
else:
|
||||
true
|
||||
else:
|
||||
let requiredPermissions = if r1: 0o700 else: 0o600
|
||||
let res = getPermissions(dirFilePath)
|
||||
if res.isErr():
|
||||
false
|
||||
else:
|
||||
if res.get() != requiredPermissions:
|
||||
false
|
||||
else:
|
||||
true
|
||||
else:
|
||||
false
|
||||
|
||||
proc checkSensitiveFilePermissions*(filePath: string): bool =
|
||||
## Check if ``filePath`` has only "(600) rw-------" permissions.
|
||||
## Procedure returns ``false`` if permissions are different and we can't
|
||||
|
@ -202,7 +255,8 @@ proc keyboardCreatePassword(prompt: string,
|
|||
return ok(password)
|
||||
|
||||
proc keyboardGetPassword[T](prompt: string, attempts: int,
|
||||
pred: proc(p: string): KsResult[T] {.gcsafe, raises: [Defect].}): KsResult[T] =
|
||||
pred: proc(p: string): KsResult[T] {.
|
||||
gcsafe, raises: [Defect].}): KsResult[T] =
|
||||
var
|
||||
remainingAttempts = attempts
|
||||
counter = 1
|
||||
|
@ -223,18 +277,51 @@ proc keyboardGetPassword[T](prompt: string, attempts: int,
|
|||
dec(remainingAttempts)
|
||||
err("Failed to decrypt keystore")
|
||||
|
||||
proc loadKeystore*(validatorsDir, secretsDir, keyName: string,
|
||||
nonInteractive: bool): Option[ValidatorPrivKey] =
|
||||
proc loadKeystoreFile*(path: string): KsResult[Keystore] {.
|
||||
raises: [Defect].} =
|
||||
try:
|
||||
ok(Json.loadFile(path, Keystore))
|
||||
except IOError as err:
|
||||
return err("Could not read keystore file")
|
||||
except SerializationError as err:
|
||||
return err("Could not decode keystore file")
|
||||
|
||||
proc loadSecretFile*(path: string): KsResult[KeystorePass] {.
|
||||
raises: [Defect].} =
|
||||
try:
|
||||
ok(KeystorePass.init(readFile(path)))
|
||||
except IOError:
|
||||
return err("Could not read password file")
|
||||
|
||||
proc loadKeystoreUnsafe*(validatorsDir, secretsDir,
|
||||
keyName: string): KsResult[ValidatorPrivateItem] =
|
||||
## Load keystore without any checks on keystore/secret permissions.
|
||||
let
|
||||
keystorePath = validatorsDir / keyName / keystoreFileName
|
||||
keystorePath = validatorsDir / keyName / KeystoreFileName
|
||||
keystore = ? loadKeystoreFile(keystorePath)
|
||||
|
||||
let
|
||||
passphrasePath = secretsDir / keyName
|
||||
passphrase = ? loadSecretFile(passphrasePath)
|
||||
|
||||
let res = decryptKeystore(keystore, passphrase)
|
||||
if res.isOk():
|
||||
ok(ValidatorPrivateItem.init(res.get(), keystore))
|
||||
else:
|
||||
err("Failed to decrypt keystore")
|
||||
|
||||
proc loadKeystore*(validatorsDir, secretsDir, keyName: string,
|
||||
nonInteractive: bool): Option[ValidatorPrivateItem] =
|
||||
let
|
||||
keystorePath = validatorsDir / keyName / KeystoreFileName
|
||||
keystore =
|
||||
try: Json.loadFile(keystorePath, Keystore)
|
||||
except IOError as err:
|
||||
error "Failed to read keystore", err = err.msg, path = keystorePath
|
||||
return
|
||||
except SerializationError as err:
|
||||
error "Invalid keystore", err = err.formatMsg(keystorePath)
|
||||
return
|
||||
block:
|
||||
let res = loadKeystoreFile(keystorePath)
|
||||
if res.isErr():
|
||||
error "Failed to read keystore file", error = res.error(),
|
||||
path = keystorePath
|
||||
return
|
||||
res.get()
|
||||
|
||||
let passphrasePath = secretsDir / keyName
|
||||
if fileExists(passphrasePath):
|
||||
|
@ -242,17 +329,18 @@ proc loadKeystore*(validatorsDir, secretsDir, keyName: string,
|
|||
error "Password file has insecure permissions", key_path = keyStorePath
|
||||
return
|
||||
|
||||
let passphrase = KeystorePass.init:
|
||||
try:
|
||||
readFile(passphrasePath)
|
||||
except IOError as err:
|
||||
error "Failed to read passphrase file", err = err.msg,
|
||||
path = passphrasePath
|
||||
return
|
||||
let passphrase =
|
||||
block:
|
||||
let res = loadSecretFile(passphrasePath)
|
||||
if res.isErr():
|
||||
error "Failed to read passphrase file", err = res.error(),
|
||||
path = passphrasePath
|
||||
return
|
||||
res.get()
|
||||
|
||||
let res = decryptKeystore(keystore, passphrase)
|
||||
if res.isOk:
|
||||
return res.get.some
|
||||
if res.isOk():
|
||||
return some(ValidatorPrivateItem.init(res.get(), keystore))
|
||||
else:
|
||||
error "Failed to decrypt keystore", keystorePath, passphrasePath
|
||||
return
|
||||
|
@ -273,34 +361,65 @@ proc loadKeystore*(validatorsDir, secretsDir, keyName: string,
|
|||
)
|
||||
|
||||
if res.isOk():
|
||||
some(res.get())
|
||||
some(ValidatorPrivateItem.init(res.get(), keystore))
|
||||
else:
|
||||
return
|
||||
|
||||
iterator validatorKeysFromDirs*(validatorsDir, secretsDir: string): ValidatorPrivKey =
|
||||
proc isEnabled*(validatorsDir, keyName: string): bool {.
|
||||
raises: [Defect].} =
|
||||
## Returns ``true`` if specific validator with key ``keyName`` in validators
|
||||
## directory ``validatorsDir`` is not disabled.
|
||||
let keystorePath = validatorsDir / keyName
|
||||
let disableFile = keystorePath / DisableFileName
|
||||
if dirExists(keystorePath):
|
||||
if fileExists(disableFile):
|
||||
false
|
||||
else:
|
||||
true
|
||||
else:
|
||||
false
|
||||
|
||||
proc isEnabled*(conf: AnyConf, keyName: string): bool {.
|
||||
raises: [Defect].} =
|
||||
## Returns ``true`` if specific validator with key ``keyName`` is not
|
||||
## disabled.
|
||||
isEnabled(conf.validatorsDir(), keyName)
|
||||
|
||||
proc isEnabled*(conf: AnyConf, publicKey: ValidatorPubKey): bool {.
|
||||
raises:[Defect].} =
|
||||
## Returns ``true`` if specific validator with public key ``publicKey`` is
|
||||
## not disabled.
|
||||
isEnabled(conf, publicKey.toHex())
|
||||
|
||||
iterator validatorKeysFromDirs*(validatorsDir,
|
||||
secretsDir: string): ValidatorPrivateItem =
|
||||
try:
|
||||
for kind, file in walkDir(validatorsDir):
|
||||
if kind == pcDir:
|
||||
let keyName = splitFile(file).name
|
||||
let key = loadKeystore(validatorsDir, secretsDir, keyName, true)
|
||||
if key.isSome:
|
||||
yield key.get
|
||||
else:
|
||||
quit 1
|
||||
if isEnabled(validatorsDir, keyName):
|
||||
let item = loadKeystore(validatorsDir, secretsDir, keyName, true)
|
||||
if item.isSome():
|
||||
yield item.get()
|
||||
else:
|
||||
quit 1
|
||||
except OSError:
|
||||
quit 1
|
||||
|
||||
iterator validatorKeys*(config: BeaconNodeConf|ValidatorClientConf): ValidatorPrivKey =
|
||||
let validatorsDir = config.validatorsDir
|
||||
iterator validatorItems*(config: AnyConf): ValidatorPrivateItem =
|
||||
let validatorsDir = config.validatorsDir()
|
||||
let secretsDir = config.secretsDir()
|
||||
try:
|
||||
for kind, file in walkDir(validatorsDir):
|
||||
if kind == pcDir:
|
||||
let keyName = splitFile(file).name
|
||||
let key = loadKeystore(validatorsDir, config.secretsDir, keyName, config.nonInteractive)
|
||||
if key.isSome:
|
||||
yield key.get
|
||||
else:
|
||||
quit 1
|
||||
if isEnabled(config, keyName):
|
||||
let item = loadKeystore(validatorsDir, secretsDir, keyName,
|
||||
config.nonInteractive)
|
||||
if item.isSome():
|
||||
yield item.get()
|
||||
else:
|
||||
quit 1
|
||||
except OSError as err:
|
||||
error "Validator keystores directory not accessible",
|
||||
path = validatorsDir, err = err.msg
|
||||
|
@ -411,7 +530,7 @@ proc saveKeystore(rng: var BrHmacDrbgContext,
|
|||
let
|
||||
keyStore = createKeystore(kdfPbkdf2, rng, signingKey,
|
||||
password, signingKeyPath)
|
||||
keystoreFile = validatorDir / keystoreFileName
|
||||
keystoreFile = validatorDir / KeystoreFileName
|
||||
|
||||
var encodedStorage: string
|
||||
try:
|
||||
|
|
|
@ -82,13 +82,12 @@ proc findValidator(validators: auto, pubKey: ValidatorPubKey):
|
|||
else:
|
||||
some(idx.ValidatorIndex)
|
||||
|
||||
proc addLocalValidator(node: BeaconNode,
|
||||
privKey: ValidatorPrivKey) =
|
||||
node.attachedValidators[].addLocalValidator(privKey)
|
||||
proc addLocalValidator(node: BeaconNode, item: ValidatorPrivateItem) =
|
||||
node.attachedValidators[].addLocalValidator(item)
|
||||
|
||||
proc addLocalValidators*(node: BeaconNode) =
|
||||
for validatorKey in node.config.validatorKeys:
|
||||
node.addLocalValidator(validatorKey)
|
||||
for validatorItem in node.config.validatorItems():
|
||||
node.addLocalValidator(validatorItem)
|
||||
|
||||
proc addRemoteValidators*(node: BeaconNode) {.raises: [Defect, OSError, IOError].} =
|
||||
# load all the validators from the child process - loop until `end`
|
||||
|
@ -103,7 +102,7 @@ proc addRemoteValidators*(node: BeaconNode) {.raises: [Defect, OSError, IOError]
|
|||
if pk.isSome():
|
||||
let v = AttachedValidator(pubKey: key,
|
||||
index: index,
|
||||
kind: ValidatorKind.remote,
|
||||
kind: ValidatorKind.Remote,
|
||||
connection: ValidatorConnection(
|
||||
inStream: node.vcProcess.inputStream,
|
||||
outStream: node.vcProcess.outputStream,
|
||||
|
|
|
@ -30,22 +30,17 @@ func init*(T: type ValidatorPool,
|
|||
template count*(pool: ValidatorPool): int =
|
||||
len(pool.validators)
|
||||
|
||||
proc addLocalValidator*(pool: var ValidatorPool,
|
||||
privKey: ValidatorPrivKey,
|
||||
proc addLocalValidator*(pool: var ValidatorPool, item: ValidatorPrivateItem,
|
||||
index: Option[ValidatorIndex]) =
|
||||
let pubKey = privKey.toPubKey().toPubKey()
|
||||
let v = AttachedValidator(kind: inProcess, pubKey: pubKey, index: index,
|
||||
privKey: privKey)
|
||||
let pubKey = item.privateKey.toPubKey().toPubKey()
|
||||
let v = AttachedValidator(kind: ValidatorKind.Local, pubKey: pubKey,
|
||||
index: index, data: item)
|
||||
pool.validators[pubKey] = v
|
||||
notice "Local validator attached", pubKey, validator = shortLog(v)
|
||||
validators.set(pool.count().int64)
|
||||
|
||||
proc addLocalValidator*(pool: var ValidatorPool, privKey: ValidatorPrivKey) =
|
||||
let pubKey = privKey.toPubKey().toPubKey()
|
||||
let v = AttachedValidator(kind: inProcess, pubKey: pubKey, privKey: privKey)
|
||||
pool.validators[pubKey] = v
|
||||
notice "Local validator attached", pubKey, validator = shortLog(v)
|
||||
validators.set(pool.count().int64)
|
||||
proc addLocalValidator*(pool: var ValidatorPool, item: ValidatorPrivateItem) =
|
||||
addLocalValidator(pool, item, none[ValidatorIndex]())
|
||||
|
||||
proc addRemoteValidator*(pool: var ValidatorPool, pubKey: ValidatorPubKey,
|
||||
v: AttachedValidator) =
|
||||
|
@ -61,9 +56,14 @@ proc contains*(pool: ValidatorPool, pubKey: ValidatorPubKey): bool =
|
|||
## Returns ``true`` if validator with key ``pubKey`` present in ``pool``.
|
||||
pool.validators.contains(pubKey)
|
||||
|
||||
proc removeValidator*(pool: var ValidatorPool, pubKey: ValidatorPubKey) =
|
||||
proc removeValidator*(pool: var ValidatorPool, validatorKey: ValidatorPubKey) =
|
||||
## Delete validator with public key ``pubKey`` from ``pool``.
|
||||
pool.validators.del(pubKey)
|
||||
let validator = pool.validators.getOrDefault(validatorKey)
|
||||
if not(isNil(validator)):
|
||||
pool.validators.del(validatorKey)
|
||||
notice "Local or remote validator detached", validatorKey,
|
||||
validator = shortLog(validator)
|
||||
validators.set(pool.count().int64)
|
||||
|
||||
proc updateValidator*(pool: var ValidatorPool, pubKey: ValidatorPubKey,
|
||||
index: ValidatorIndex) =
|
||||
|
@ -101,23 +101,26 @@ proc signWithRemoteValidator(v: AttachedValidator,
|
|||
proc signBlockProposal*(v: AttachedValidator, fork: Fork,
|
||||
genesis_validators_root: Eth2Digest, slot: Slot,
|
||||
blockRoot: Eth2Digest): Future[ValidatorSig] {.async.} =
|
||||
return if v.kind == inProcess:
|
||||
get_block_signature(fork, genesis_validators_root, slot, blockRoot,
|
||||
v.privKey).toValidatorSig()
|
||||
else:
|
||||
let root = compute_block_root(fork, genesis_validators_root, slot,
|
||||
blockRoot)
|
||||
await signWithRemoteValidator(v, root)
|
||||
return
|
||||
case v.kind
|
||||
of ValidatorKind.Local:
|
||||
get_block_signature(fork, genesis_validators_root, slot, blockRoot,
|
||||
v.data.privateKey).toValidatorSig()
|
||||
of ValidatorKind.Remote:
|
||||
let root = compute_block_root(fork, genesis_validators_root, slot,
|
||||
blockRoot)
|
||||
await signWithRemoteValidator(v, root)
|
||||
|
||||
proc signAttestation*(v: AttachedValidator,
|
||||
data: AttestationData,
|
||||
fork: Fork, genesis_validators_root: Eth2Digest):
|
||||
Future[ValidatorSig] {.async.} =
|
||||
return
|
||||
if v.kind == inProcess:
|
||||
case v.kind
|
||||
of ValidatorKind.Local:
|
||||
get_attestation_signature(fork, genesis_validators_root, data,
|
||||
v.privKey).toValidatorSig()
|
||||
else:
|
||||
v.data.privateKey).toValidatorSig()
|
||||
of ValidatorKind.Remote:
|
||||
let root = compute_attestation_root(fork, genesis_validators_root, data)
|
||||
await signWithRemoteValidator(v, root)
|
||||
|
||||
|
@ -142,11 +145,12 @@ proc signAggregateAndProof*(v: AttachedValidator,
|
|||
fork: Fork, genesis_validators_root: Eth2Digest):
|
||||
Future[ValidatorSig] {.async.} =
|
||||
return
|
||||
if v.kind == inProcess:
|
||||
case v.kind
|
||||
of ValidatorKind.Local:
|
||||
get_aggregate_and_proof_signature(fork, genesis_validators_root,
|
||||
aggregate_and_proof,
|
||||
v.privKey).toValidatorSig()
|
||||
else:
|
||||
v.data.privateKey).toValidatorSig()
|
||||
of ValidatorKind.Remote:
|
||||
let root = compute_aggregate_and_proof_root(fork, genesis_validators_root,
|
||||
aggregate_and_proof)
|
||||
await signWithRemoteValidator(v, root)
|
||||
|
@ -161,10 +165,12 @@ proc signSyncCommitteeMessage*(v: AttachedValidator,
|
|||
signing_root = sync_committee_msg_signing_root(
|
||||
fork, slot.epoch, genesis_validators_root, block_root)
|
||||
|
||||
let signature = if v.kind == inProcess:
|
||||
blsSign(v.privkey, signing_root.data).toValidatorSig
|
||||
else:
|
||||
await signWithRemoteValidator(v, signing_root)
|
||||
let signature =
|
||||
case v.kind
|
||||
of ValidatorKind.Local:
|
||||
blsSign(v.data.privateKey, signing_root.data).toValidatorSig
|
||||
of ValidatorKind.Remote:
|
||||
await signWithRemoteValidator(v, signing_root)
|
||||
|
||||
return SyncCommitteeMessage(
|
||||
slot: slot,
|
||||
|
@ -183,10 +189,12 @@ proc getSyncCommitteeSelectionProof*(
|
|||
signing_root = sync_committee_selection_proof_signing_root(
|
||||
fork, genesis_validators_root, slot, subcommittee_index)
|
||||
|
||||
return if v.kind == inProcess:
|
||||
blsSign(v.privkey, signing_root.data).toValidatorSig
|
||||
else:
|
||||
await signWithRemoteValidator(v, signing_root)
|
||||
return
|
||||
case v.kind
|
||||
of ValidatorKind.Local:
|
||||
blsSign(v.data.privateKey, signing_root.data).toValidatorSig
|
||||
of ValidatorKind.Remote:
|
||||
await signWithRemoteValidator(v, signing_root)
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.0-beta.4/specs/altair/validator.md#signature
|
||||
proc sign*(
|
||||
|
@ -198,10 +206,12 @@ proc sign*(
|
|||
signing_root = contribution_and_proof_signing_root(
|
||||
fork, genesis_validators_root, msg.message)
|
||||
|
||||
msg.signature = if v.kind == inProcess:
|
||||
blsSign(v.privkey, signing_root.data).toValidatorSig
|
||||
else:
|
||||
await signWithRemoteValidator(v, signing_root)
|
||||
msg.signature =
|
||||
case v.kind
|
||||
of ValidatorKind.Local:
|
||||
blsSign(v.data.privateKey, signing_root.data).toValidatorSig
|
||||
of ValidatorKind.Remote:
|
||||
await signWithRemoteValidator(v, signing_root)
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.0/specs/phase0/validator.md#randao-reveal
|
||||
func genRandaoReveal*(k: ValidatorPrivKey, fork: Fork,
|
||||
|
@ -214,10 +224,11 @@ proc genRandaoReveal*(v: AttachedValidator, fork: Fork,
|
|||
genesis_validators_root: Eth2Digest, slot: Slot):
|
||||
Future[ValidatorSig] {.async.} =
|
||||
return
|
||||
if v.kind == inProcess:
|
||||
genRandaoReveal(v.privKey, fork, genesis_validators_root,
|
||||
case v.kind
|
||||
of ValidatorKind.Local:
|
||||
genRandaoReveal(v.data.privateKey, fork, genesis_validators_root,
|
||||
slot).toValidatorSig()
|
||||
else:
|
||||
of ValidatorKind.Remote:
|
||||
let root = compute_epoch_root(fork, genesis_validators_root,
|
||||
slot.compute_epoch_at_slot)
|
||||
await signWithRemoteValidator(v, root)
|
||||
|
@ -226,9 +237,10 @@ proc getSlotSig*(v: AttachedValidator, fork: Fork,
|
|||
genesis_validators_root: Eth2Digest, slot: Slot
|
||||
): Future[ValidatorSig] {.async.} =
|
||||
return
|
||||
if v.kind == inProcess:
|
||||
case v.kind
|
||||
of ValidatorKind.Local:
|
||||
get_slot_signature(fork, genesis_validators_root, slot,
|
||||
v.privKey).toValidatorSig()
|
||||
else:
|
||||
v.data.privateKey).toValidatorSig()
|
||||
of ValidatorKind.Remote:
|
||||
let root = compute_slot_root(fork, genesis_validators_root, slot)
|
||||
await signWithRemoteValidator(v, root)
|
||||
|
|
|
@ -216,10 +216,9 @@ suite "Gossip validation - Extra": # Not based on preset config
|
|||
expectedCount = subcommittee.count(pubkey)
|
||||
index = ValidatorIndex(
|
||||
state[].data.validators.mapIt(it.pubkey).find(pubKey))
|
||||
validator = AttachedValidator(
|
||||
pubKey: pubkey,
|
||||
kind: inProcess, privKey: MockPrivKeys[index],
|
||||
index: some(index))
|
||||
privateItem = ValidatorPrivateItem(privateKey: MockPrivKeys[index])
|
||||
validator = AttachedValidator(pubKey: pubkey,
|
||||
kind: ValidatorKind.Local, data: privateItem, index: some(index))
|
||||
msg = waitFor signSyncCommitteeMessage(
|
||||
validator, state[].data.slot,
|
||||
state[].data.fork, state[].data.genesis_validators_root, state[].root)
|
||||
|
|
Loading…
Reference in New Issue