BABE Randomness using PreRuntime digests (#2929)

* Initial work on exposing pre-runtime digests

This provides the primitive API, as well as exposing it from BABE.

* Initial work on using pre-digests in runtimes

This includes both code to expose them from `srml_system`, as well as
using it in (currently dead) code in `srml_babe`.

* Bump `{spec,impl}_version`

* Add `u64_backend` feature to curve25519-dalek

Otherwise, it errors out at compile-time.

* Bump `Cargo.lock`

* Do not depend on the schnorrkel crate in the runtime

The schnorrkel crate does not work on `#![no_std]`, but the runtime only
needs constants from it.  This adds our own definitions of those
constants, and checks them for correctness at compile-time.

* Actually implement storage of VRF outputs

* Trivial formatting change

* Provide a `hash_randomness` function in BABE

for processing VRF outputs.

* Implement a basic randomness generating function

It just XORs the VRF outputs together.

* Actually implement on-chain randomness

Blake2b is used for hashing.

* Update dependencies

* Run `cargo update` where needed

* Re-add a newline at EOF

* Remove broken and unsafe code

XOR is not a hash function, and must not be used as such.  The
implementation was also needlessly unsafe.

* Run `cargo update` where needed

* Remove spurious dependency

* Document security guarantees of BABE randomness

* Add a `RandomnessBeacon` trait

* Document `RandomnessBeacon::random`

* Fix silly compile error (unexpected type arguments)

* Fix BABE randomness

* Implement `FindAuthor` for `babe::Module`

* Apply suggestions from code review

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>
Co-Authored-By: Robert Habermeier <rphmeier@gmail.com>

* Respond to suggestions from code review and fix bugs

* Store an authority index, not the authority itself.
* Avoid unnecessary decoding.
* Implement relative slots and BABE randomness fully and correctly.

* Remove spurious dependency

* Fix error reported by rust-analyzer

* Update Cargo.lock files

* `wrapping_add` → `checked_add`

The epoch index will not overflow.  Panic if it does.

* Move randomness documentation to trait

* Fix compile error in test suite

* Explain 2^64 limit

Co-Authored-By: Robert Habermeier <rphmeier@gmail.com>
This commit is contained in:
DemiMarie-parity
2019-07-03 08:49:08 -04:00
committed by Gavin Wood
parent dcb1a590e2
commit 81d8a5d01d
18 changed files with 1810 additions and 1916 deletions
+101 -13
View File
@@ -17,13 +17,14 @@
//! Consensus extension module for BABE consensus.
#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unsafe_code)]
#![forbid(unused_must_use, unsafe_code, unused_variables, dead_code)]
pub use timestamp;
use rstd::{result, prelude::*};
use srml_support::{decl_storage, decl_module, StorageValue};
use srml_support::{decl_storage, decl_module, StorageValue, traits::FindAuthor};
use timestamp::{OnTimestampSet, Trait};
use primitives::{generic::DigestItem, traits::{SaturatedConversion, Saturating}};
use primitives::{generic::DigestItem, traits::{SaturatedConversion, Saturating, RandomnessBeacon}};
use primitives::ConsensusEngineId;
#[cfg(feature = "std")]
use timestamp::TimestampInherentData;
use parity_codec::{Encode, Decode};
@@ -31,8 +32,7 @@ use inherents::{RuntimeString, InherentIdentifier, InherentData, ProvideInherent
#[cfg(feature = "std")]
use inherents::{InherentDataProviders, ProvideInherentData};
use babe_primitives::BABE_ENGINE_ID;
pub use babe_primitives::AuthorityId;
pub use babe_primitives::{AuthorityId, VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH, PUBLIC_KEY_LENGTH};
/// The BABE inherent identifier.
pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"babeslot";
@@ -67,6 +67,7 @@ pub struct InherentDataProvider {
#[cfg(feature = "std")]
impl InherentDataProvider {
/// Constructs `Self`
pub fn new(slot_duration: u64) -> Self {
Self {
slot_duration
@@ -106,6 +107,9 @@ impl ProvideInherentData for InherentDataProvider {
}
}
/// The length of the BABE randomness
pub const RANDOMNESS_LENGTH: usize = 32;
decl_storage! {
trait Store for Module<T: Trait> as Babe {
/// The last timestamp.
@@ -113,11 +117,75 @@ decl_storage! {
/// The current authorities set.
Authorities get(authorities): Vec<AuthorityId>;
/// The epoch randomness.
///
/// # Security
///
/// This MUST NOT be used for gambling, as it can be influenced by a
/// malicious validator in the short term. It MAY be used in many
/// cryptographic protocols, however, so long as one remembers that this
/// (like everything else on-chain) it is public. For example, it can be
/// used where a number is needed that cannot have been chosen by an
/// adversary, for purposes such as public-coin zero-knowledge proofs.
EpochRandomness get(epoch_randomness): [u8; 32];
/// The randomness under construction
UnderConstruction: [u8; 32];
/// The randomness for the next epoch
NextEpochRandomness: [u8; 32];
/// The current epoch
EpochIndex get(epoch_index): u64;
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin { }
/// The BABE SRML module
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
/// Initialization
fn on_initialize() {
for i in Self::get_inherent_digests()
.logs
.iter()
.filter_map(|s| s.as_pre_runtime())
.filter_map(|(id, mut data)| if id == BABE_ENGINE_ID {
<[u8; VRF_OUTPUT_LENGTH]>::decode(&mut data)
} else {
None
}) {
Self::deposit_vrf_output(&i);
}
}
}
}
impl<T: Trait> RandomnessBeacon for Module<T> {
fn random() -> [u8; 32] {
Self::epoch_randomness()
}
}
/// A BABE public key
pub type BabeKey = [u8; PUBLIC_KEY_LENGTH];
impl<T: Trait> FindAuthor<u64> for Module<T> {
fn find_author<'a, I>(digests: I) -> Option<u64> where
I: 'a + IntoIterator<Item=(ConsensusEngineId, &'a [u8])>
{
for (id, mut data) in digests.into_iter() {
if id == BABE_ENGINE_ID {
let (_, _, i): (
[u8; VRF_OUTPUT_LENGTH],
[u8; VRF_PROOF_LENGTH],
u64,
) = Decode::decode(&mut data)?;
return Some(i)
}
}
return None
}
}
impl<T: Trait> Module<T> {
@@ -127,25 +195,31 @@ impl<T: Trait> Module<T> {
// the majority of their slot.
<timestamp::Module<T>>::minimum_period().saturating_mul(2.into())
}
}
impl<T: Trait> OnTimestampSet<T::Moment> for Module<T> {
fn on_timestamp_set(_moment: T::Moment) { }
}
impl<T: Trait> Module<T> {
fn change_authorities(new: Vec<AuthorityId>) {
Authorities::put(&new);
let log: DigestItem<T::Hash> = DigestItem::Consensus(BABE_ENGINE_ID, new.encode());
<system::Module<T>>::deposit_log(log.into());
}
fn deposit_vrf_output(vrf_output: &[u8; VRF_OUTPUT_LENGTH]) {
UnderConstruction::mutate(|z| z.iter_mut().zip(vrf_output).for_each(|(x, y)| *x^=y))
}
fn get_inherent_digests() -> system::DigestOf<T> {
<system::Module<T>>::digest()
}
}
impl<T: Trait> OnTimestampSet<T::Moment> for Module<T> {
fn on_timestamp_set(_moment: T::Moment) { }
}
impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
type Key = AuthorityId;
fn on_new_session<'a, I: 'a>(changed: bool, validators: I)
where I: Iterator<Item=(&'a T::AccountId, AuthorityId)>
where I: Iterator<Item=(&'a T::AccountId, AuthorityId)>
{
// instant changes
if changed {
@@ -155,6 +229,20 @@ impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
Self::change_authorities(next_authorities);
}
}
let rho = UnderConstruction::get();
UnderConstruction::put([0; 32]);
let last_epoch_randomness = EpochRandomness::get();
let epoch_index = EpochIndex::get()
.checked_add(1)
.expect("epoch indices will never reach 2^64 before the death of the universe; qed");
EpochIndex::put(epoch_index);
EpochRandomness::put(NextEpochRandomness::get());
let mut s = [0; 72];
s[..32].copy_from_slice(&last_epoch_randomness);
s[32..40].copy_from_slice(&epoch_index.to_le_bytes());
s[40..].copy_from_slice(&rho);
NextEpochRandomness::put(runtime_io::blake2_256(&s))
}
fn on_disabled(_i: usize) {
// ignore?