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))
            }
        }
    }
}