diff --git a/beacon_chain/beacon_node.nim b/beacon_chain/beacon_node.nim index 547b5189c..e313f46c5 100644 --- a/beacon_chain/beacon_node.nim +++ b/beacon_chain/beacon_node.nim @@ -131,7 +131,6 @@ proc addLocalValidators*(node: BeaconNode) = let privKey = validator.privKey pubKey = privKey.pubKey() - randao = validator.randao let idx = node.beaconState.validator_registry.findIt(it.pubKey == pubKey) if idx == -1: @@ -139,7 +138,7 @@ proc addLocalValidators*(node: BeaconNode) = else: debug "Attaching validator", validator = shortValidatorKey(node, idx), idx, pubKey - node.attachedValidators.addLocalValidator(idx, pubKey, privKey, randao) + node.attachedValidators.addLocalValidator(idx, pubKey, privKey) info "Local validators attached ", count = node.attachedValidators.count @@ -216,7 +215,7 @@ proc proposeBlock(node: BeaconNode, var newBlock = BeaconBlock( slot: slot, parent_root: node.headBlockRoot, - randao_reveal: validator.genRandaoReveal(state), + randao_reveal: validator.genRandaoReveal(state, state.slot), eth1_data: node.mainchainMonitor.getBeaconBlockRef(), signature: ValidatorSig(), # we need the rest of the block first! body: blockBody) diff --git a/beacon_chain/conf.nim b/beacon_chain/conf.nim index c106643e9..e42d56282 100644 --- a/beacon_chain/conf.nim +++ b/beacon_chain/conf.nim @@ -2,7 +2,7 @@ import os, options, confutils/defs, chronicles/options as chroniclesOptions, json_serialization, - spec/[crypto, datatypes], randao, time + spec/[crypto, datatypes], time export json_serialization @@ -20,7 +20,6 @@ type PrivateValidatorData* = object privKey*: ValidatorPrivKey - randao*: Randao BeaconNodeConf* = object logLevel* {. @@ -81,13 +80,9 @@ proc readFileBytes(path: string): seq[byte] = proc loadPrivKey*(p: ValidatorKeyPath): ValidatorPrivKey = ValidatorPrivKey.init(readFileBytes(string(p) & ".privkey")) -proc loadRandao*(p: ValidatorKeyPath): Randao = - initRandao(readFileBytes(string(p) & ".randao")) - proc parseCmdArg*(T: type ValidatorKeyPath, input: TaintedString): T = result = T(input) discard loadPrivKey(result) - discard loadRandao(result) template mustBeFilePath(input: TaintedString) = if not fileExists(string input): diff --git a/beacon_chain/randao.nim b/beacon_chain/randao.nim deleted file mode 100644 index 5329d4108..000000000 --- a/beacon_chain/randao.nim +++ /dev/null @@ -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 diff --git a/beacon_chain/spec/helpers.nim b/beacon_chain/spec/helpers.nim index e6a5ec703..9bc4979fd 100644 --- a/beacon_chain/spec/helpers.nim +++ b/beacon_chain/spec/helpers.nim @@ -54,18 +54,6 @@ func get_new_recent_block_roots*(old_block_roots: seq[Eth2Digest], for _ in 0 ..< min(d, old_block_roots.len): 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! # https://github.com/ethereum/eth2.0-specs/blob/v0.2.0/specs/core/0_beacon-chain.md#integer_squareroot diff --git a/beacon_chain/state_transition.nim b/beacon_chain/state_transition.nim index 8d39a82bb..9e176a05a 100644 --- a/beacon_chain/state_transition.nim +++ b/beacon_chain/state_transition.nim @@ -39,60 +39,49 @@ func flatten[T](v: openArray[seq[T]]): seq[T] = # TODO not in nim - doh. 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 ## 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 ## process. - ## - ## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#proposer-signature - var blck_without_sig = blck - blck_without_sig.signature = ValidatorSig() + var blck_without_signature = blck + blck_without_signature.signature = ValidatorSig() let signed_data = ProposalSignedData( slot: state.slot, 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) bls_verify( state.validator_registry[proposer_index].pubkey, - proposal_hash.data, blck.signature, - get_domain(state.fork, slot_to_epoch(state.slot), DOMAIN_PROPOSAL)) + proposal_root.data, blck.signature, + 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( 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 proposer_index = get_beacon_proposer_index(state, state.slot) proposer = addr state.validator_registry[proposer_index] if skipValidation notin flags: - # Check that proposer commit and reveal match - # TODO re-enable if appropriate - #if expected != proposer.randao_commitment: - # notice "Randao reveal mismatch", reveal = blck.randao_reveal, - # layers = proposer.randao_layers, - # commitment = proposer.randao_commitment, - # expected - # return false - discard + if not bls_verify( + proposer.pubkey, + int_to_bytes32(get_current_epoch(state)), + blck.randao_reveal, + get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO)): + + notice "Randao mismatch", proposer_pubkey = proposer.pubkey, + message = get_current_epoch(state), + signature = blck.randao_reveal, + slot = state.slot, + blck_slot = blck.slot + return false # Update state and proposer now that we're alright let @@ -102,7 +91,7 @@ proc processRandao( for i, b in state.latest_randao_mixes[mix].data: state.latest_randao_mixes[mix].data[i] = b xor rr[i] - return true + true func processDepositRoot(state: var BeaconState, blck: BeaconBlock) = ## 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: 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) = ## 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 ## chain at that time. In case the proposer is missing, it may happen that ## 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.latest_block_roots[(state.slot - 1) mod LATEST_BLOCK_ROOTS_LENGTH] = previous_block_root @@ -380,6 +368,7 @@ proc processBlock( # TODO when there's a failure, we should reset the state! # 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): notice "Unexpected block slot number", blockSlot = blck.slot, diff --git a/beacon_chain/validator_keygen.nim b/beacon_chain/validator_keygen.nim index bc8aabe05..b6e2029f4 100644 --- a/beacon_chain/validator_keygen.nim +++ b/beacon_chain/validator_keygen.nim @@ -1,7 +1,7 @@ import os, ospaths, strutils, strformat, chronos, nimcrypto, json_serialization, confutils, - spec/[datatypes, digest, crypto], conf, randao, time, ssz, + spec/[datatypes, digest, crypto], conf, time, ssz, ../tests/testutil proc writeFile(filename: string, value: auto) = @@ -9,17 +9,13 @@ proc writeFile(filename: string, value: auto) = echo "Wrote ", filename proc genSingleValidator(path: string): (ValidatorPubKey, - ValidatorPrivKey, - Eth2Digest) = + ValidatorPrivKey) = var v: PrivateValidatorData v.privKey = newPrivKey() - if randomBytes(v.randao.seed.data) != sizeof(v.randao.seed.data): - raise newException(Exception, "Could not generate randao seed") - writeFile(path, v) assert v.privKey != ValidatorPrivKey(), "Private key shouldn't be zero" - return (v.privKey.pubKey(), v.privKey, v.randao.initialCommitment) + return (v.privKey.pubKey(), v.privKey) # TODO: Make these more comprehensive and find them a new home type @@ -41,7 +37,7 @@ cli do (validators: int, var startupData: ChainStartupData for i in 1 .. validators: - let (pubKey, privKey, randaoCommitment) = + let (pubKey, privKey) = genSingleValidator(outputDir / &"validator-{i:02}.json") let diff --git a/beacon_chain/validator_pool.nim b/beacon_chain/validator_pool.nim index cd351e14f..2cb857679 100644 --- a/beacon_chain/validator_pool.nim +++ b/beacon_chain/validator_pool.nim @@ -1,7 +1,7 @@ import tables, random, chronos, - spec/[datatypes, crypto, digest, helpers], randao, ssz + spec/[datatypes, crypto, digest, helpers], ssz type ValidatorKind = enum @@ -15,7 +15,6 @@ type case kind: ValidatorKind of inProcess: privKey: ValidatorPrivKey - randaoSecret: Randao else: connection: ValidatorConnection @@ -31,12 +30,10 @@ template count*(pool: ValidatorPool): int = proc addLocalValidator*(pool: var ValidatorPool, idx: int, pubKey: ValidatorPubKey, - privKey: ValidatorPrivKey, - randaoSecret: Randao) = + privKey: ValidatorPrivKey) = let v = AttachedValidator(idx: idx, kind: inProcess, - privKey: privKey, - randaoSecret: randaoSecret) + privKey: privKey) pool.validators[pubKey] = v proc getValidator*(pool: ValidatorPool, @@ -74,25 +71,13 @@ proc signAttestation*(v: AttachedValidator, # send RPC discard -proc randaoReveal*(v: AttachedValidator, commitment: Eth2Digest): Future[Eth2Digest] {.async.} = - 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): +func genRandaoReveal*(k: ValidatorPrivKey, state: BeaconState, slot: SlotNumber): ValidatorSig = - # https://github.com/ethereum/eth2.0-specs/blob/v0.1/specs/core/0_beacon-chain.md#randao - bls_sign(k, int_to_bytes32(get_current_epoch(state)), - get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO)) + # https://github.com/ethereum/eth2.0-specs/blob/v0.2.0/specs/core/0_beacon-chain.md#randao + assert slot > state.slot + 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 = - genRandaoReveal(v.privKey, state) + genRandaoReveal(v.privKey, state, slot) diff --git a/tests/testutil.nim b/tests/testutil.nim index 3ed2c8fb2..17fded892 100644 --- a/tests/testutil.nim +++ b/tests/testutil.nim @@ -100,7 +100,7 @@ proc addBlock*( slot: state.slot + 1, parent_root: previous_block_root, 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 signature: ValidatorSig(), # we need the rest of the block first! body: body