complete switching to v0.2.0 RANDAO scheme, with verification enabled
This commit is contained in:
parent
c2a52d7fc5
commit
8fbf87aa8c
|
@ -131,7 +131,6 @@ proc addLocalValidators*(node: BeaconNode) =
|
||||||
let
|
let
|
||||||
privKey = validator.privKey
|
privKey = validator.privKey
|
||||||
pubKey = privKey.pubKey()
|
pubKey = privKey.pubKey()
|
||||||
randao = validator.randao
|
|
||||||
|
|
||||||
let idx = node.beaconState.validator_registry.findIt(it.pubKey == pubKey)
|
let idx = node.beaconState.validator_registry.findIt(it.pubKey == pubKey)
|
||||||
if idx == -1:
|
if idx == -1:
|
||||||
|
@ -139,7 +138,7 @@ proc addLocalValidators*(node: BeaconNode) =
|
||||||
else:
|
else:
|
||||||
debug "Attaching validator", validator = shortValidatorKey(node, idx),
|
debug "Attaching validator", validator = shortValidatorKey(node, idx),
|
||||||
idx, pubKey
|
idx, pubKey
|
||||||
node.attachedValidators.addLocalValidator(idx, pubKey, privKey, randao)
|
node.attachedValidators.addLocalValidator(idx, pubKey, privKey)
|
||||||
|
|
||||||
info "Local validators attached ", count = node.attachedValidators.count
|
info "Local validators attached ", count = node.attachedValidators.count
|
||||||
|
|
||||||
|
@ -216,7 +215,7 @@ proc proposeBlock(node: BeaconNode,
|
||||||
var newBlock = BeaconBlock(
|
var newBlock = BeaconBlock(
|
||||||
slot: slot,
|
slot: slot,
|
||||||
parent_root: node.headBlockRoot,
|
parent_root: node.headBlockRoot,
|
||||||
randao_reveal: validator.genRandaoReveal(state),
|
randao_reveal: validator.genRandaoReveal(state, state.slot),
|
||||||
eth1_data: node.mainchainMonitor.getBeaconBlockRef(),
|
eth1_data: node.mainchainMonitor.getBeaconBlockRef(),
|
||||||
signature: ValidatorSig(), # we need the rest of the block first!
|
signature: ValidatorSig(), # we need the rest of the block first!
|
||||||
body: blockBody)
|
body: blockBody)
|
||||||
|
|
|
@ -2,7 +2,7 @@ import
|
||||||
os, options,
|
os, options,
|
||||||
confutils/defs, chronicles/options as chroniclesOptions,
|
confutils/defs, chronicles/options as chroniclesOptions,
|
||||||
json_serialization,
|
json_serialization,
|
||||||
spec/[crypto, datatypes], randao, time
|
spec/[crypto, datatypes], time
|
||||||
|
|
||||||
export
|
export
|
||||||
json_serialization
|
json_serialization
|
||||||
|
@ -20,7 +20,6 @@ type
|
||||||
|
|
||||||
PrivateValidatorData* = object
|
PrivateValidatorData* = object
|
||||||
privKey*: ValidatorPrivKey
|
privKey*: ValidatorPrivKey
|
||||||
randao*: Randao
|
|
||||||
|
|
||||||
BeaconNodeConf* = object
|
BeaconNodeConf* = object
|
||||||
logLevel* {.
|
logLevel* {.
|
||||||
|
@ -81,13 +80,9 @@ proc readFileBytes(path: string): seq[byte] =
|
||||||
proc loadPrivKey*(p: ValidatorKeyPath): ValidatorPrivKey =
|
proc loadPrivKey*(p: ValidatorKeyPath): ValidatorPrivKey =
|
||||||
ValidatorPrivKey.init(readFileBytes(string(p) & ".privkey"))
|
ValidatorPrivKey.init(readFileBytes(string(p) & ".privkey"))
|
||||||
|
|
||||||
proc loadRandao*(p: ValidatorKeyPath): Randao =
|
|
||||||
initRandao(readFileBytes(string(p) & ".randao"))
|
|
||||||
|
|
||||||
proc parseCmdArg*(T: type ValidatorKeyPath, input: TaintedString): T =
|
proc parseCmdArg*(T: type ValidatorKeyPath, input: TaintedString): T =
|
||||||
result = T(input)
|
result = T(input)
|
||||||
discard loadPrivKey(result)
|
discard loadPrivKey(result)
|
||||||
discard loadRandao(result)
|
|
||||||
|
|
||||||
template mustBeFilePath(input: TaintedString) =
|
template mustBeFilePath(input: TaintedString) =
|
||||||
if not fileExists(string input):
|
if not fileExists(string input):
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
import spec/[digest, helpers]
|
|
||||||
|
|
||||||
type Randao* = object
|
|
||||||
seed*: Eth2Digest
|
|
||||||
|
|
||||||
const MaxRandaoLevels = 10000 # TODO: This number is arbitrary
|
|
||||||
|
|
||||||
proc initRandao*(seed: Eth2Digest): Randao =
|
|
||||||
result.seed = seed
|
|
||||||
|
|
||||||
proc initRandao*(bytes: openarray[byte]): Randao =
|
|
||||||
if bytes.len != sizeof(Eth2Digest):
|
|
||||||
raise newException(Exception, "Wrong randao size")
|
|
||||||
var s: Eth2Digest
|
|
||||||
s.data[0 .. ^1] = bytes
|
|
||||||
initRandao(s)
|
|
||||||
|
|
||||||
proc initialCommitment*(r: Randao): Eth2Digest =
|
|
||||||
repeatHash(r.seed, MaxRandaoLevels)
|
|
||||||
|
|
||||||
proc reveal*(r: Randao, commitment: Eth2Digest): Eth2Digest =
|
|
||||||
if commitment == r.seed:
|
|
||||||
raise newException(Exception, "Randao: cannot reveal for seed")
|
|
||||||
result = r.seed
|
|
||||||
for i in 0 .. MaxRandaoLevels:
|
|
||||||
let h = eth2hash(result.data)
|
|
||||||
if h == commitment:
|
|
||||||
return
|
|
||||||
result = h
|
|
||||||
|
|
||||||
raise newException(Exception, "Randao: commitment not found")
|
|
||||||
|
|
||||||
when isMainModule:
|
|
||||||
import times, nimcrypto
|
|
||||||
var seed: Eth2Digest
|
|
||||||
let r = initRandao(seed)
|
|
||||||
|
|
||||||
var s = epochTime()
|
|
||||||
var ic = r.initialCommitment()
|
|
||||||
var e = epochTime()
|
|
||||||
echo "initialCommitment: ", ic
|
|
||||||
echo "Took time: ", e - s
|
|
||||||
s = epochTime()
|
|
||||||
let rev = r.reveal(ic)
|
|
||||||
e = epochTime()
|
|
||||||
echo "reveal: ", rev
|
|
||||||
echo "Took time: ", e - s
|
|
||||||
|
|
||||||
echo r.reveal(eth2hash([1.byte, 2, 3])) # Should raise
|
|
|
@ -54,18 +54,6 @@ func get_new_recent_block_roots*(old_block_roots: seq[Eth2Digest],
|
||||||
for _ in 0 ..< min(d, old_block_roots.len):
|
for _ in 0 ..< min(d, old_block_roots.len):
|
||||||
result.add parent_hash
|
result.add parent_hash
|
||||||
|
|
||||||
# TODO remove; cascades through randao.nim, validator_pool.nim, etc
|
|
||||||
func repeat_hash*(v: Eth2Digest, n: SomeInteger): Eth2Digest =
|
|
||||||
# Spec version:
|
|
||||||
# if n == 0: v
|
|
||||||
# else: repeat_hash(eth2hash(v.data), n - 1)
|
|
||||||
# Nim is pretty bad at recursion though (max 2k levels / no tco), so:
|
|
||||||
result = v
|
|
||||||
var n = n
|
|
||||||
while n != 0:
|
|
||||||
result = eth2hash(result.data)
|
|
||||||
dec n
|
|
||||||
|
|
||||||
func ceil_div8*(v: int): int = (v + 7) div 8 # TODO use a proper bitarray!
|
func ceil_div8*(v: int): int = (v + 7) div 8 # TODO use a proper bitarray!
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.2.0/specs/core/0_beacon-chain.md#integer_squareroot
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.2.0/specs/core/0_beacon-chain.md#integer_squareroot
|
||||||
|
|
|
@ -39,60 +39,49 @@ func flatten[T](v: openArray[seq[T]]): seq[T] =
|
||||||
# TODO not in nim - doh.
|
# TODO not in nim - doh.
|
||||||
for x in v: result.add x
|
for x in v: result.add x
|
||||||
|
|
||||||
proc verifyProposerSignature(state: BeaconState, blck: BeaconBlock): bool =
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.2.0/specs/core/0_beacon-chain.md#proposer-signature
|
||||||
|
func verifyProposerSignature(state: BeaconState, blck: BeaconBlock): bool =
|
||||||
## When creating a block, the proposer will sign a version of the block that
|
## When creating a block, the proposer will sign a version of the block that
|
||||||
## doesn't contain the data (chicken and egg), then add the signature to that
|
## doesn't contain the data (chicken and egg), then add the signature to that
|
||||||
## block. Here, we check that the signature is correct by repeating the same
|
## block. Here, we check that the signature is correct by repeating the same
|
||||||
## process.
|
## process.
|
||||||
##
|
var blck_without_signature = blck
|
||||||
## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#proposer-signature
|
blck_without_signature.signature = ValidatorSig()
|
||||||
var blck_without_sig = blck
|
|
||||||
blck_without_sig.signature = ValidatorSig()
|
|
||||||
|
|
||||||
let
|
let
|
||||||
signed_data = ProposalSignedData(
|
signed_data = ProposalSignedData(
|
||||||
slot: state.slot,
|
slot: state.slot,
|
||||||
shard: BEACON_CHAIN_SHARD_NUMBER,
|
shard: BEACON_CHAIN_SHARD_NUMBER,
|
||||||
block_root: hash_tree_root_final(blck_without_sig)
|
block_root: hash_tree_root_final(blck_without_signature)
|
||||||
)
|
)
|
||||||
proposal_hash = hash_tree_root_final(signed_data)
|
proposal_root = hash_tree_root_final(signed_data)
|
||||||
proposer_index = get_beacon_proposer_index(state, state.slot)
|
proposer_index = get_beacon_proposer_index(state, state.slot)
|
||||||
|
|
||||||
bls_verify(
|
bls_verify(
|
||||||
state.validator_registry[proposer_index].pubkey,
|
state.validator_registry[proposer_index].pubkey,
|
||||||
proposal_hash.data, blck.signature,
|
proposal_root.data, blck.signature,
|
||||||
get_domain(state.fork, slot_to_epoch(state.slot), DOMAIN_PROPOSAL))
|
get_domain(state.fork, get_current_epoch(state), DOMAIN_PROPOSAL))
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.2.0/specs/core/0_beacon-chain.md#randao
|
||||||
proc processRandao(
|
proc processRandao(
|
||||||
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool =
|
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool =
|
||||||
## When a validator signs up, they will include a hash number together with
|
|
||||||
## the deposit - the randao_commitment. The commitment is formed by hashing
|
|
||||||
## a secret value N times.
|
|
||||||
## The first time the proposer proposes a block, they will hash their secret
|
|
||||||
## value N-1 times, and provide the reuslt as "reveal" - now everyone else can
|
|
||||||
## verify that the reveal matches the commitment by hashing it once.
|
|
||||||
## The next time the proposer proposes, they will reveal the secret value
|
|
||||||
## hashed N-2 times and so on, and everyone will verify that it matches N-1.
|
|
||||||
## The previous reveal has now become the commitment!
|
|
||||||
##
|
|
||||||
## Effectively, the block proposer can only reveal N-1 times, so better pick
|
|
||||||
## a large N!
|
|
||||||
##
|
|
||||||
## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#randao
|
|
||||||
let
|
let
|
||||||
proposer_index = get_beacon_proposer_index(state, state.slot)
|
proposer_index = get_beacon_proposer_index(state, state.slot)
|
||||||
proposer = addr state.validator_registry[proposer_index]
|
proposer = addr state.validator_registry[proposer_index]
|
||||||
|
|
||||||
if skipValidation notin flags:
|
if skipValidation notin flags:
|
||||||
# Check that proposer commit and reveal match
|
if not bls_verify(
|
||||||
# TODO re-enable if appropriate
|
proposer.pubkey,
|
||||||
#if expected != proposer.randao_commitment:
|
int_to_bytes32(get_current_epoch(state)),
|
||||||
# notice "Randao reveal mismatch", reveal = blck.randao_reveal,
|
blck.randao_reveal,
|
||||||
# layers = proposer.randao_layers,
|
get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO)):
|
||||||
# commitment = proposer.randao_commitment,
|
|
||||||
# expected
|
notice "Randao mismatch", proposer_pubkey = proposer.pubkey,
|
||||||
# return false
|
message = get_current_epoch(state),
|
||||||
discard
|
signature = blck.randao_reveal,
|
||||||
|
slot = state.slot,
|
||||||
|
blck_slot = blck.slot
|
||||||
|
return false
|
||||||
|
|
||||||
# Update state and proposer now that we're alright
|
# Update state and proposer now that we're alright
|
||||||
let
|
let
|
||||||
|
@ -102,7 +91,7 @@ proc processRandao(
|
||||||
for i, b in state.latest_randao_mixes[mix].data:
|
for i, b in state.latest_randao_mixes[mix].data:
|
||||||
state.latest_randao_mixes[mix].data[i] = b xor rr[i]
|
state.latest_randao_mixes[mix].data[i] = b xor rr[i]
|
||||||
|
|
||||||
return true
|
true
|
||||||
|
|
||||||
func processDepositRoot(state: var BeaconState, blck: BeaconBlock) =
|
func processDepositRoot(state: var BeaconState, blck: BeaconBlock) =
|
||||||
## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#eth1-data
|
## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#eth1-data
|
||||||
|
@ -359,13 +348,12 @@ proc process_ejections(state: var BeaconState) =
|
||||||
state.validator_balances[index] < EJECTION_BALANCE:
|
state.validator_balances[index] < EJECTION_BALANCE:
|
||||||
exit_validator(state, index.ValidatorIndex)
|
exit_validator(state, index.ValidatorIndex)
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.2.0/specs/core/0_beacon-chain.md#per-slot-processing
|
||||||
func processSlot(state: var BeaconState, previous_block_root: Eth2Digest) =
|
func processSlot(state: var BeaconState, previous_block_root: Eth2Digest) =
|
||||||
## Time on the beacon chain moves in slots. Every time we make it to a new
|
## Time on the beacon chain moves in slots. Every time we make it to a new
|
||||||
## slot, a proposer creates a block to represent the state of the beacon
|
## slot, a proposer creates a block to represent the state of the beacon
|
||||||
## chain at that time. In case the proposer is missing, it may happen that
|
## chain at that time. In case the proposer is missing, it may happen that
|
||||||
## the no block is produced during the slot.
|
## the no block is produced during the slot.
|
||||||
##
|
|
||||||
## https://github.com/ethereum/eth2.0-specs/blob/v0.1/specs/core/0_beacon-chain.md#per-slot-processing
|
|
||||||
state.slot += 1
|
state.slot += 1
|
||||||
state.latest_block_roots[(state.slot - 1) mod LATEST_BLOCK_ROOTS_LENGTH] =
|
state.latest_block_roots[(state.slot - 1) mod LATEST_BLOCK_ROOTS_LENGTH] =
|
||||||
previous_block_root
|
previous_block_root
|
||||||
|
@ -380,6 +368,7 @@ proc processBlock(
|
||||||
# TODO when there's a failure, we should reset the state!
|
# TODO when there's a failure, we should reset the state!
|
||||||
# TODO probably better to do all verification first, then apply state changes
|
# TODO probably better to do all verification first, then apply state changes
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.2.0/specs/core/0_beacon-chain.md#slot-1
|
||||||
if not (blck.slot == state.slot):
|
if not (blck.slot == state.slot):
|
||||||
notice "Unexpected block slot number",
|
notice "Unexpected block slot number",
|
||||||
blockSlot = blck.slot,
|
blockSlot = blck.slot,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import
|
import
|
||||||
tables, random,
|
tables, random,
|
||||||
chronos,
|
chronos,
|
||||||
spec/[datatypes, crypto, digest, helpers], randao, ssz
|
spec/[datatypes, crypto, digest, helpers], ssz
|
||||||
|
|
||||||
type
|
type
|
||||||
ValidatorKind = enum
|
ValidatorKind = enum
|
||||||
|
@ -15,7 +15,6 @@ type
|
||||||
case kind: ValidatorKind
|
case kind: ValidatorKind
|
||||||
of inProcess:
|
of inProcess:
|
||||||
privKey: ValidatorPrivKey
|
privKey: ValidatorPrivKey
|
||||||
randaoSecret: Randao
|
|
||||||
else:
|
else:
|
||||||
connection: ValidatorConnection
|
connection: ValidatorConnection
|
||||||
|
|
||||||
|
@ -31,12 +30,10 @@ template count*(pool: ValidatorPool): int =
|
||||||
proc addLocalValidator*(pool: var ValidatorPool,
|
proc addLocalValidator*(pool: var ValidatorPool,
|
||||||
idx: int,
|
idx: int,
|
||||||
pubKey: ValidatorPubKey,
|
pubKey: ValidatorPubKey,
|
||||||
privKey: ValidatorPrivKey,
|
privKey: ValidatorPrivKey) =
|
||||||
randaoSecret: Randao) =
|
|
||||||
let v = AttachedValidator(idx: idx,
|
let v = AttachedValidator(idx: idx,
|
||||||
kind: inProcess,
|
kind: inProcess,
|
||||||
privKey: privKey,
|
privKey: privKey)
|
||||||
randaoSecret: randaoSecret)
|
|
||||||
pool.validators[pubKey] = v
|
pool.validators[pubKey] = v
|
||||||
|
|
||||||
proc getValidator*(pool: ValidatorPool,
|
proc getValidator*(pool: ValidatorPool,
|
||||||
|
@ -74,25 +71,13 @@ proc signAttestation*(v: AttachedValidator,
|
||||||
# send RPC
|
# send RPC
|
||||||
discard
|
discard
|
||||||
|
|
||||||
proc randaoReveal*(v: AttachedValidator, commitment: Eth2Digest): Future[Eth2Digest] {.async.} =
|
func genRandaoReveal*(k: ValidatorPrivKey, state: BeaconState, slot: SlotNumber):
|
||||||
if v.kind == inProcess:
|
|
||||||
result = v.randaoSecret.reveal(commitment)
|
|
||||||
else:
|
|
||||||
# TODO:
|
|
||||||
# send RPC
|
|
||||||
discard
|
|
||||||
|
|
||||||
# TODO move elsewhere when something else wants this utility function
|
|
||||||
func int_to_bytes32(x: uint64) : array[32, byte] =
|
|
||||||
for i in 0 ..< 8:
|
|
||||||
result[31 - i] = byte((x shr i*8) and 0xff)
|
|
||||||
|
|
||||||
func genRandaoReveal*(k: ValidatorPrivKey, state: BeaconState):
|
|
||||||
ValidatorSig =
|
ValidatorSig =
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.1/specs/core/0_beacon-chain.md#randao
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.2.0/specs/core/0_beacon-chain.md#randao
|
||||||
bls_sign(k, int_to_bytes32(get_current_epoch(state)),
|
assert slot > state.slot
|
||||||
get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO))
|
bls_sign(k, int_to_bytes32(slot_to_epoch(slot)),
|
||||||
|
get_domain(state.fork, slot_to_epoch(slot), DOMAIN_RANDAO))
|
||||||
|
|
||||||
func genRandaoReveal*(v: AttachedValidator, state: BeaconState):
|
func genRandaoReveal*(v: AttachedValidator, state: BeaconState, slot: SlotNumber):
|
||||||
ValidatorSig =
|
ValidatorSig =
|
||||||
genRandaoReveal(v.privKey, state)
|
genRandaoReveal(v.privKey, state, slot)
|
||||||
|
|
|
@ -100,7 +100,7 @@ proc addBlock*(
|
||||||
slot: state.slot + 1,
|
slot: state.slot + 1,
|
||||||
parent_root: previous_block_root,
|
parent_root: previous_block_root,
|
||||||
state_root: Eth2Digest(), # we need the new state first
|
state_root: Eth2Digest(), # we need the new state first
|
||||||
randao_reveal: privKey.genRandaoReveal(state),
|
randao_reveal: privKey.genRandaoReveal(state, state.slot + 1),
|
||||||
eth1_data: Eth1Data(), # TODO
|
eth1_data: Eth1Data(), # TODO
|
||||||
signature: ValidatorSig(), # we need the rest of the block first!
|
signature: ValidatorSig(), # we need the rest of the block first!
|
||||||
body: body
|
body: body
|
||||||
|
|
Loading…
Reference in New Issue