1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
// This file is part of OpenEthereum.
// OpenEthereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// OpenEthereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
//! On-chain randomness generation for authority round
//!
//! This module contains the support code for the on-chain randomness generation used by AuRa. Its
//! core is the finite state machine `RandomnessPhase`, which can be loaded from the blockchain
//! state, then asked to perform potentially necessary transaction afterwards using the `advance()`
//! method.
//!
//! No additional state is kept inside the `RandomnessPhase`, it must be passed in each time.
//!
//! The process of generating random numbers is a simple finite state machine:
//!
//! ```text
//! +
//! |
//! |
//! |
//! +--------------+ +-------v-------+
//! | | | |
//! | BeforeCommit <------------------------------+ Waiting |
//! | | enter commit phase | |
//! +------+-------+ +-------^-------+
//! | |
//! | call |
//! | `commitHash()` | call
//! | | `revealNumber()`
//! | |
//! +------v-------+ +-------+-------+
//! | | | |
//! | Committed +------------------------------> Reveal |
//! | | enter reveal phase | |
//! +--------------+ +---------------+
//! ```
//!
//! Phase transitions are performed by the smart contract and simply queried by the engine.
//!
//! Randomness generation works as follows:
//! * During the commit phase, all validators locally generate a random number, and commit that number's hash to the
//! contract.
//! * During the reveal phase, all validators reveal their local random number to the contract. The contract should
//! verify that it matches the committed hash.
//! * Finally, the XOR of all revealed numbers is used as an on-chain random number.
//!
//! An adversary can only influence that number by either controlling _all_ validators who committed, or, to a lesser
//! extent, by not revealing committed numbers.
//! The length of the commit and reveal phases, as well as any penalties for failure to reveal, are defined by the
//! contract.
//!
//! A typical case of using `RandomnessPhase` is:
//!
//! 1. `RandomnessPhase::load()` the phase from the blockchain data.
//! 2. Call `RandomnessPhase::advance()`.
//!
//! A production implementation of a randomness contract can be found here:
//! https://github.com/poanetwork/posdao-contracts/blob/4fddb108993d4962951717b49222327f3d94275b/contracts/RandomAuRa.sol
use bytes::Bytes;
use crypto::publickey::{ecies, Error as CryptoError};
use derive_more::Display;
use engines::signer::EngineSigner;
use ethabi::Hash;
use ethabi_contract::use_contract;
use ethereum_types::{Address, H256, U256};
use hash::keccak;
use log::{debug, error};
use rand::Rng;
use super::util::{BoundContract, CallError};
/// Random number type expected by the contract: This is generated locally, kept secret during the commit phase, and
/// published in the reveal phase.
pub type RandNumber = H256;
use_contract!(aura_random, "res/contracts/authority_round_random.json");
/// Validated randomness phase state.
#[derive(Debug)]
pub enum RandomnessPhase {
// NOTE: Some states include information already gathered during `load` (e.g. `our_address`,
// `round`) for efficiency reasons.
/// Waiting for the next phase.
///
/// This state indicates either the successful revelation in this round or having missed the
/// window to make a commitment, i.e. having failed to commit during the commit phase.
Waiting,
/// Indicates a commitment is possible, but still missing.
BeforeCommit,
/// Indicates a successful commitment, waiting for the commit phase to end.
Committed,
/// Indicates revealing is expected as the next step.
Reveal { our_address: Address, round: U256 },
}
/// Phase loading error for randomness generation state machine.
///
/// This error usually indicates a bug in either the smart contract, the phase loading function or
/// some state being lost.
///
/// `BadRandNumber` will usually result in punishment by the contract or the other validators.
#[derive(Debug, Display)]
pub enum PhaseError {
/// The smart contract reported that we already revealed something while still being in the
/// commit phase.
#[display(fmt = "Revealed during commit phase")]
RevealedInCommit,
/// Failed to load contract information.
#[display(fmt = "Error loading randomness contract information: {:?}", _0)]
LoadFailed(CallError),
/// Failed to load the stored encrypted random number.
#[display(fmt = "Failed to load random number from the randomness contract")]
BadRandNumber,
/// Failed to encrypt random number.
#[display(fmt = "Failed to encrypt random number: {}", _0)]
Crypto(CryptoError),
/// Failed to get the engine signer's public key.
#[display(fmt = "Failed to get the engine signer's public key")]
MissingPublicKey,
}
impl From<CryptoError> for PhaseError {
fn from(err: CryptoError) -> PhaseError {
PhaseError::Crypto(err)
}
}
impl RandomnessPhase {
/// Determine randomness generation state from the contract.
///
/// Calls various constant contract functions to determine the precise state that needs to be
/// handled (that is, the phase and whether or not the current validator still needs to send
/// commitments or reveal random numbers).
pub fn load(
contract: &BoundContract,
our_address: Address,
) -> Result<RandomnessPhase, PhaseError> {
// Determine the current round and which phase we are in.
let round = contract
.call_const(aura_random::functions::current_collect_round::call())
.map_err(PhaseError::LoadFailed)?;
let is_commit_phase = contract
.call_const(aura_random::functions::is_commit_phase::call())
.map_err(PhaseError::LoadFailed)?;
// Ensure we are not committing or revealing twice.
let committed = contract
.call_const(aura_random::functions::is_committed::call(
round,
our_address,
))
.map_err(PhaseError::LoadFailed)?;
let revealed: bool = contract
.call_const(aura_random::functions::sent_reveal::call(
round,
our_address,
))
.map_err(PhaseError::LoadFailed)?;
// With all the information known, we can determine the actual state we are in.
if is_commit_phase {
if revealed {
return Err(PhaseError::RevealedInCommit);
}
if !committed {
Ok(RandomnessPhase::BeforeCommit)
} else {
Ok(RandomnessPhase::Committed)
}
} else {
if !committed {
// We apparently entered too late to make a commitment, wait until we get a chance again.
return Ok(RandomnessPhase::Waiting);
}
if !revealed {
Ok(RandomnessPhase::Reveal { our_address, round })
} else {
Ok(RandomnessPhase::Waiting)
}
}
}
/// Advance the random seed construction process as far as possible.
///
/// Returns the encoded contract call necessary to advance the randomness contract's state.
///
/// **Warning**: After calling the `advance()` function, wait until the returned transaction has been included in
/// a block before calling it again; otherwise spurious transactions resulting in punishments might be executed.
pub fn advance<R: Rng>(
self,
contract: &BoundContract,
rng: &mut R,
signer: &dyn EngineSigner,
) -> Result<Option<Bytes>, PhaseError> {
match self {
RandomnessPhase::Waiting | RandomnessPhase::Committed => Ok(None),
RandomnessPhase::BeforeCommit => {
// Generate a new random number, but don't reveal it yet. Instead, we publish its hash to the
// randomness contract, together with the number encrypted to ourselves. That way we will later be
// able to decrypt and reveal it, and other parties are able to verify it against the hash.
let number: RandNumber = rng.gen();
let number_hash: Hash = keccak(number.0);
let public = signer.public().ok_or(PhaseError::MissingPublicKey)?;
let cipher = ecies::encrypt(&public, number_hash.as_bytes(), number.as_bytes())?;
debug!(target: "engine", "Randomness contract: committing {}.", number_hash);
// Return the call data for the transaction that commits the hash and the encrypted number.
let (data, _decoder) =
aura_random::functions::commit_hash::call(number_hash, cipher);
Ok(Some(data))
}
RandomnessPhase::Reveal { round, our_address } => {
// Load the hash and encrypted number that we stored in the commit phase.
let call = aura_random::functions::get_commit_and_cipher::call(round, our_address);
let (committed_hash, cipher) =
contract.call_const(call).map_err(PhaseError::LoadFailed)?;
// Decrypt the number and check against the hash.
let number_bytes = signer.decrypt(&committed_hash.0, &cipher)?;
let number = if number_bytes.len() == 32 {
RandNumber::from_slice(&number_bytes)
} else {
// This can only happen if there is a bug in the smart contract,
// or if the entire network goes awry.
error!(target: "engine", "Decrypted random number has the wrong length.");
return Err(PhaseError::BadRandNumber);
};
let number_hash: Hash = keccak(number.0);
if number_hash != committed_hash {
error!(target: "engine", "Decrypted random number doesn't agree with the hash.");
return Err(PhaseError::BadRandNumber);
}
debug!(target: "engine", "Randomness contract: scheduling tx to reveal our random number {} (round={}, our_address={}).", number_hash, round, our_address);
// We are now sure that we have the correct secret and can reveal it. So we return the call data for the
// transaction that stores the revealed random bytes on the contract.
let (data, _decoder) = aura_random::functions::reveal_number::call(number.0);
Ok(Some(data))
}
}
}
}