Skip to content

Commit

Permalink
feat: Simple randomness beacon (#2177)
Browse files Browse the repository at this point in the history
feat: Simple randomness beacon

This implements a simple biasable randomness beacon. The work on the
unbiasable randomness beacon is here:
https://github.com/nearprotocol/nearcore/commits/rnd_integration

The biasable randomness beacon in this commit just uses the VRF created
by the current block proposer, thus it has 1 bit of influence (the block
proposer can choose not to create a block), which is sufficient for the
block producers rotation, but should be used with caution for other use
cases.

Test plan
---------
No new tests added as part of the change.
  • Loading branch information
SkidanovAlex committed Feb 25, 2020
1 parent 60f2da2 commit d4a07fa
Show file tree
Hide file tree
Showing 16 changed files with 186 additions and 4 deletions.
16 changes: 16 additions & 0 deletions chain/chain/src/chain.rs
Expand Up @@ -303,6 +303,7 @@ impl Chain {
runtime_adapter.add_validator_proposals(
CryptoHash::default(),
genesis.hash(),
genesis.header.inner_rest.random_value,
genesis.header.inner_lite.height,
0,
vec![],
Expand Down Expand Up @@ -709,6 +710,7 @@ impl Chain {
self.runtime_adapter.add_validator_proposals(
header.prev_hash,
header.hash(),
header.inner_rest.random_value,
header.inner_lite.height,
self.store.get_block_height(&header.inner_rest.last_quorum_pre_commit)?,
header.inner_rest.validator_proposals.clone(),
Expand Down Expand Up @@ -2524,6 +2526,7 @@ impl<'a> ChainUpdate<'a> {
let prev_prev_hash = prev.prev_hash;
let prev_gas_price = prev.inner_rest.gas_price;
let prev_epoch_id = prev.inner_lite.epoch_id.clone();
let prev_random_value = prev.inner_rest.random_value;

// Block is an orphan if we do not know about the previous full block.
if !is_next && !self.chain_store_update.block_exists(&prev_hash)? {
Expand Down Expand Up @@ -2559,6 +2562,18 @@ impl<'a> ChainUpdate<'a> {
FinalityGadget::process_approval(me, approval, &mut self.chain_store_update)?;
}

self.runtime_adapter.verify_block_vrf(
&block.header.inner_lite.epoch_id,
block.header.inner_lite.height,
&prev_random_value,
block.vrf_value,
block.vrf_proof,
)?;

if block.header.inner_rest.random_value != hash(block.vrf_value.0.as_ref()) {
return Err(ErrorKind::InvalidRandomnessBeaconOutput.into());
}

// We need to know the last approval on the previous block to later compute the reference
// block for the current block. If it is not known by now, transfer it from the block
// before it
Expand Down Expand Up @@ -2635,6 +2650,7 @@ impl<'a> ChainUpdate<'a> {
self.runtime_adapter.add_validator_proposals(
block.header.prev_hash,
block.hash(),
block.header.inner_rest.random_value,
block.header.inner_lite.height,
last_finalized_height,
block.header.inner_rest.validator_proposals.clone(),
Expand Down
4 changes: 4 additions & 0 deletions chain/chain/src/error.rs
Expand Up @@ -143,6 +143,9 @@ pub enum ErrorKind {
/// Invalid shard id
#[fail(display = "Invalid state request: {}", _0)]
InvalidStateRequest(String),
/// Invalid VRF proof, or incorrect random_output in the header
#[fail(display = "Invalid Randomness Beacon Output")]
InvalidRandomnessBeaconOutput,
/// Someone is not a validator. Usually happens in signature verification
#[fail(display = "Not A Validator")]
NotAValidator,
Expand Down Expand Up @@ -250,6 +253,7 @@ impl Error {
| ErrorKind::InvalidRent
| ErrorKind::InvalidShardId(_)
| ErrorKind::InvalidStateRequest(_)
| ErrorKind::InvalidRandomnessBeaconOutput
| ErrorKind::NotAValidator => true,
}
}
Expand Down
12 changes: 12 additions & 0 deletions chain/chain/src/test_utils.rs
Expand Up @@ -265,6 +265,17 @@ impl RuntimeAdapter for KeyValueRuntime {
Ok(())
}

fn verify_block_vrf(
&self,
_epoch_id: &EpochId,
_block_height: BlockHeight,
_prev_random_value: &CryptoHash,
_vrf_value: near_crypto::vrf::Value,
_vrf_proof: near_crypto::vrf::Proof,
) -> Result<(), Error> {
Ok(())
}

fn verify_validator_signature(
&self,
_epoch_id: &EpochId,
Expand Down Expand Up @@ -454,6 +465,7 @@ impl RuntimeAdapter for KeyValueRuntime {
&self,
_parent_hash: CryptoHash,
_current_hash: CryptoHash,
_rng_seed: CryptoHash,
_height: BlockHeight,
_last_finalized_height: BlockHeight,
_proposals: Vec<ValidatorStake>,
Expand Down
9 changes: 9 additions & 0 deletions chain/chain/src/types.rs
Expand Up @@ -118,6 +118,14 @@ pub trait RuntimeAdapter: Send + Sync {

/// Verify block producer validity
fn verify_block_signature(&self, header: &BlockHeader) -> Result<(), Error>;
fn verify_block_vrf(
&self,
epoch_id: &EpochId,
block_height: BlockHeight,
prev_random_value: &CryptoHash,
vrf_value: near_crypto::vrf::Value,
vrf_proof: near_crypto::vrf::Proof,
) -> Result<(), Error>;

/// Validates a given signed transaction on top of the given state root.
/// Returns an option of `InvalidTxError`, it contains `Some(InvalidTxError)` if there is
Expand Down Expand Up @@ -300,6 +308,7 @@ pub trait RuntimeAdapter: Send + Sync {
&self,
parent_hash: CryptoHash,
current_hash: CryptoHash,
rng_seed: CryptoHash,
height: BlockHeight,
last_finalized_height: BlockHeight,
proposals: Vec<ValidatorStake>,
Expand Down
2 changes: 1 addition & 1 deletion core/crypto/src/lib.rs
Expand Up @@ -9,7 +9,7 @@ mod traits;
#[macro_use]
mod util;

mod key_conversion;
pub mod key_conversion;
mod key_file;
pub mod randomness;
mod signature;
Expand Down
14 changes: 14 additions & 0 deletions core/crypto/src/signature.rs
Expand Up @@ -148,6 +148,13 @@ impl PublicKey {
PublicKey::SECP256K1(_) => KeyType::SECP256K1,
}
}

pub fn unwrap_as_ed25519(&self) -> &ED25519PublicKey {
match self {
PublicKey::ED25519(key) => &key,
PublicKey::SECP256K1(_) => panic!(),
}
}
}

impl Hash for PublicKey {
Expand Down Expand Up @@ -373,6 +380,13 @@ impl SecretKey {
}
}
}

pub fn unwrap_as_ed25519(&self) -> &ED25519SecretKey {
match self {
SecretKey::ED25519(key) => &key,
SecretKey::SECP256K1(_) => panic!(),
}
}
}

impl std::fmt::Display for SecretKey {
Expand Down
12 changes: 12 additions & 0 deletions core/crypto/src/signer.rs
@@ -1,6 +1,7 @@
use std::path::Path;
use std::sync::Arc;

use crate::key_conversion::convert_secret_key;
use crate::key_file::KeyFile;
use crate::{KeyType, PublicKey, SecretKey, Signature};

Expand All @@ -13,6 +14,8 @@ pub trait Signer: Sync + Send {
signature.verify(data, &self.public_key())
}

fn compute_vrf_with_proof(&self, _data: &[u8]) -> (crate::vrf::Value, crate::vrf::Proof);

/// Used by test infrastructure, only implement if make sense for testing otherwise raise `unimplemented`.
fn write_to_file(&self, _path: &Path) {
unimplemented!();
Expand All @@ -30,6 +33,10 @@ impl Signer for EmptySigner {
fn sign(&self, _data: &[u8]) -> Signature {
Signature::empty(KeyType::ED25519)
}

fn compute_vrf_with_proof(&self, _data: &[u8]) -> (crate::vrf::Value, crate::vrf::Proof) {
unimplemented!()
}
}

/// Signer that keeps secret key in memory.
Expand Down Expand Up @@ -64,6 +71,11 @@ impl Signer for InMemorySigner {
self.secret_key.sign(data)
}

fn compute_vrf_with_proof(&self, data: &[u8]) -> (crate::vrf::Value, crate::vrf::Proof) {
let secret_key = convert_secret_key(self.secret_key.unwrap_as_ed25519());
secret_key.compute_vrf_with_proof(&data)
}

fn write_to_file(&self, path: &Path) {
KeyFile::from(self).write_to_file(path);
}
Expand Down
16 changes: 16 additions & 0 deletions core/crypto/src/traits.rs
Expand Up @@ -150,6 +150,22 @@ macro_rules! value_type {
}
}

impl borsh::BorshSerialize for $ty {
#[inline]
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
writer.write_all(&self.0)
}
}

impl borsh::BorshDeserialize for $ty {
#[inline]
fn deserialize<R: std::io::Read>(reader: &mut R) -> Result<Self, std::io::Error> {
let mut data = [0u8; $l];
reader.read_exact(&mut data)?;
Ok($ty(data))
}
}

common_conversions_fixed!($ty, $l, |s| &s.0, $what);
};
}
22 changes: 22 additions & 0 deletions core/primitives/src/block.rs
Expand Up @@ -46,6 +46,8 @@ pub struct BlockHeaderInnerRest {
pub chunks_included: u64,
/// Root hash of the challenges in the given block.
pub challenges_root: MerkleHash,
/// The output of the randomness beacon
pub random_value: CryptoHash,
/// Score.
pub score: BlockScore,
/// Validator proposals.
Expand Down Expand Up @@ -107,6 +109,7 @@ impl BlockHeaderInnerRest {
chunk_tx_root: MerkleHash,
chunks_included: u64,
challenges_root: MerkleHash,
random_value: CryptoHash,
score: BlockScore,
validator_proposals: Vec<ValidatorStake>,
chunk_mask: Vec<bool>,
Expand All @@ -126,6 +129,7 @@ impl BlockHeaderInnerRest {
chunk_tx_root,
chunks_included,
challenges_root,
random_value,
score,
validator_proposals,
chunk_mask,
Expand Down Expand Up @@ -261,6 +265,7 @@ impl BlockHeader {
timestamp: u64,
chunks_included: u64,
challenges_root: MerkleHash,
random_value: CryptoHash,
score: BlockScore,
validator_proposals: Vec<ValidatorStake>,
chunk_mask: Vec<bool>,
Expand Down Expand Up @@ -293,6 +298,7 @@ impl BlockHeader {
chunk_tx_root,
chunks_included,
challenges_root,
random_value,
score,
validator_proposals,
chunk_mask,
Expand Down Expand Up @@ -337,6 +343,7 @@ impl BlockHeader {
chunk_tx_root,
chunks_included,
challenges_root,
CryptoHash::default(),
0.into(),
vec![],
vec![],
Expand Down Expand Up @@ -387,6 +394,10 @@ pub struct Block {
pub header: BlockHeader,
pub chunks: Vec<ShardChunkHeader>,
pub challenges: Challenges,

// Data to confirm the correctness of randomness beacon output
pub vrf_value: near_crypto::vrf::Value,
pub vrf_proof: near_crypto::vrf::Proof,
}

pub fn genesis_chunks(
Expand Down Expand Up @@ -449,6 +460,9 @@ impl Block {
),
chunks,
challenges,

vrf_value: near_crypto::vrf::Value([0; 32]),
vrf_proof: near_crypto::vrf::Proof([0; 64]),
}
}

Expand Down Expand Up @@ -509,6 +523,10 @@ impl Block {
let time =
if now <= prev.inner_lite.timestamp { prev.inner_lite.timestamp + 1 } else { now };

let (vrf_value, vrf_proof) =
signer.compute_vrf_with_proof(prev.inner_rest.random_value.as_ref());
let random_value = hash(vrf_value.0.as_ref());

Block {
header: BlockHeader::new(
height,
Expand All @@ -521,6 +539,7 @@ impl Block {
time,
Block::compute_chunks_included(&chunks, height),
Block::compute_challenges_root(&challenges),
random_value,
score,
validator_proposals,
chunk_mask,
Expand All @@ -540,6 +559,9 @@ impl Block {
),
chunks,
challenges,

vrf_value,
vrf_proof,
}
}

Expand Down
5 changes: 5 additions & 0 deletions core/primitives/src/errors.rs
Expand Up @@ -328,6 +328,8 @@ pub enum ActionErrorKind {
#[serde(with = "u128_dec_format")]
balance: Balance,
},
/// An attempt to stake with a key that is not convertable to ristretto
UnsuitableStakingKey { public_key: PublicKey },
/// An error occurred during a `FunctionCall` Action.
FunctionCallError(FunctionCallError),
/// Error occurs when a new `ActionReceipt` created by the `FunctionCall` action fails
Expand Down Expand Up @@ -592,6 +594,9 @@ impl Display for ActionErrorKind {
"Account {:?} tries to stake {}, but has staked {} and only has {}",
account_id, stake, locked, balance
),
ActionErrorKind::UnsuitableStakingKey { public_key } => {
write!(f, "The staking key must be ED25519. {} is provided instead.", public_key)
}
ActionErrorKind::CreateAccountNotAllowed { account_id, predecessor_id } => write!(
f,
"The new account_id {:?} can't be created by {:?}",
Expand Down
19 changes: 19 additions & 0 deletions core/primitives/src/validator_signer.rs
Expand Up @@ -58,6 +58,11 @@ pub trait ValidatorSigner: Sync + Send {
epoch_id: &EpochId,
) -> Signature;

fn compute_vrf_with_proof(
&self,
data: &[u8],
) -> (near_crypto::vrf::Value, near_crypto::vrf::Proof);

/// Used by test infrastructure, only implement if make sense for testing otherwise raise `unimplemented`.
fn write_to_file(&self, path: &Path);
}
Expand Down Expand Up @@ -124,6 +129,13 @@ impl ValidatorSigner for EmptyValidatorSigner {
Signature::default()
}

fn compute_vrf_with_proof(
&self,
_data: &[u8],
) -> (near_crypto::vrf::Value, near_crypto::vrf::Proof) {
unimplemented!()
}

fn write_to_file(&self, _path: &Path) {
unimplemented!()
}
Expand Down Expand Up @@ -227,6 +239,13 @@ impl ValidatorSigner for InMemoryValidatorSigner {
self.signer.sign(hash.as_ref())
}

fn compute_vrf_with_proof(
&self,
data: &[u8],
) -> (near_crypto::vrf::Value, near_crypto::vrf::Proof) {
self.signer.compute_vrf_with_proof(data)
}

fn write_to_file(&self, path: &Path) {
self.signer.write_to_file(path);
}
Expand Down
3 changes: 3 additions & 0 deletions core/primitives/src/views.rs
Expand Up @@ -275,6 +275,7 @@ pub struct BlockHeaderView {
pub chunks_included: u64,
pub challenges_root: CryptoHash,
pub timestamp: u64,
pub random_value: CryptoHash,
pub score: u64,
pub validator_proposals: Vec<ValidatorStakeView>,
pub chunk_mask: Vec<bool>,
Expand Down Expand Up @@ -311,6 +312,7 @@ impl From<BlockHeader> for BlockHeaderView {
challenges_root: header.inner_rest.challenges_root,
outcome_root: header.inner_lite.outcome_root,
timestamp: header.inner_lite.timestamp,
random_value: header.inner_rest.random_value,
score: header.inner_rest.score.to_num(),
validator_proposals: header
.inner_rest
Expand Down Expand Up @@ -367,6 +369,7 @@ impl From<BlockHeaderView> for BlockHeader {
chunk_tx_root: view.chunk_tx_root,
chunks_included: view.chunks_included,
challenges_root: view.challenges_root,
random_value: view.random_value,
score: view.score.into(),
validator_proposals: view
.validator_proposals
Expand Down

0 comments on commit d4a07fa

Please sign in to comment.