Skip to content

Commit

Permalink
[feature] hyperledger#4285: Verifiable Random Function in Sumeragi
Browse files Browse the repository at this point in the history
Signed-off-by: Sam H. Smith <sam.henning.smith@protonmail.com>
  • Loading branch information
SamHSmith committed Mar 14, 2024
1 parent 7014f53 commit c43c25f
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 18 deletions.
3 changes: 2 additions & 1 deletion core/benches/blocks/common.rs
Expand Up @@ -8,6 +8,7 @@ use iroha_core::{
sumeragi::network_topology::Topology,
wsv::World,
};
use iroha_crypto::VRFState;
use iroha_data_model::{
account::Account,
asset::{AssetDefinition, AssetDefinitionId},
Expand Down Expand Up @@ -40,7 +41,7 @@ pub fn create_block(
topology.clone(),
Vec::new(),
)
.chain(0, wsv)
.chain(0, VRFState::generate_new_random_state(), wsv)
.sign(key_pair)
.commit(&topology)
.unwrap();
Expand Down
24 changes: 20 additions & 4 deletions core/src/block.rs
Expand Up @@ -7,7 +7,7 @@
use std::error::Error as _;

use iroha_config::parameters::defaults::chain_wide::DEFAULT_CONSENSUS_ESTIMATION;
use iroha_crypto::{HashOf, KeyPair, MerkleTree, SignatureOf, SignaturesOf};
use iroha_crypto::{HashOf, KeyPair, MerkleTree, SignatureOf, SignaturesOf, VRFState};
use iroha_data_model::{
block::*,
events::prelude::*,
Expand Down Expand Up @@ -66,6 +66,8 @@ pub enum BlockValidationError {
SignatureVerification(#[from] SignatureVerificationError),
/// Received view change index is too large
ViewChangeIndexTooLarge,
/// Block has an invalid VRF state
InvalidVRF,
}

/// Error during signature verification
Expand Down Expand Up @@ -137,6 +139,7 @@ mod pending {
previous_height: u64,
previous_block_hash: Option<HashOf<SignedBlock>>,
view_change_index: u64,
vrf_state: VRFState,
transactions: &[TransactionValue],
) -> BlockHeader {
BlockHeader {
Expand All @@ -151,6 +154,7 @@ mod pending {
height: previous_height + 1,
view_change_index,
previous_block_hash,
vrf_state,
transactions_hash: transactions
.iter()
.map(|value| value.as_ref().hash())
Expand Down Expand Up @@ -191,6 +195,7 @@ mod pending {
pub fn chain(
self,
view_change_index: u64,
vrf_state: VRFState,
wsv: &mut WorldStateView,
) -> BlockBuilder<Chained> {
let transactions = Self::categorize_transactions(self.0.transactions, wsv);
Expand All @@ -200,6 +205,7 @@ mod pending {
wsv.height(),
wsv.latest_block_hash(),
view_change_index,
vrf_state,
&transactions,
),
transactions,
Expand Down Expand Up @@ -278,6 +284,15 @@ mod valid {
));
}

let leader_pk = &topology.ordered_peers[0].public_key;
if !block
.header()
.vrf_state
.verify(topology.get_vrf_state(), leader_pk)
{
return Err((block, BlockValidationError::InvalidVRF));
}

if topology
.filter_signatures_by_roles(&[Role::Leader], block.signatures())
.is_empty()
Expand Down Expand Up @@ -447,6 +462,7 @@ mod valid {
height: 2,
view_change_index: 0,
previous_block_hash: None,
vrf_state: VRFState::generate_new_random_state(),
transactions_hash: None,
},
transactions: Vec::new(),
Expand Down Expand Up @@ -724,7 +740,7 @@ mod tests {
let transactions = vec![tx.clone(), tx];
let topology = Topology::new(UniqueVec::new());
let valid_block = BlockBuilder::new(transactions, topology, Vec::new())
.chain(0, &mut wsv)
.chain(0, VRFState::generate_new_random_state(), &mut wsv)
.sign(&alice_keys);

// The first transaction should be confirmed
Expand Down Expand Up @@ -785,7 +801,7 @@ mod tests {
let transactions = vec![tx0, tx, tx2];
let topology = Topology::new(UniqueVec::new());
let valid_block = BlockBuilder::new(transactions, topology, Vec::new())
.chain(0, &mut wsv)
.chain(0, VRFState::generate_new_random_state(), &mut wsv)
.sign(&alice_keys);

// The first transaction should fail
Expand Down Expand Up @@ -841,7 +857,7 @@ mod tests {
let transactions = vec![tx_fail, tx_accept];
let topology = Topology::new(UniqueVec::new());
let valid_block = BlockBuilder::new(transactions, topology, Vec::new())
.chain(0, &mut wsv)
.chain(0, VRFState::generate_new_random_state(), &mut wsv)
.sign(&alice_keys);

// The first transaction should be rejected
Expand Down
8 changes: 4 additions & 4 deletions core/src/smartcontracts/isi/query.rs
Expand Up @@ -173,7 +173,7 @@ impl ValidQuery for QueryBox {
mod tests {
use std::str::FromStr as _;

use iroha_crypto::{Hash, HashOf, KeyPair};
use iroha_crypto::{Hash, HashOf, KeyPair, VRFState};
use iroha_data_model::{
metadata::MetadataValueBox, query::error::FindError, transaction::TransactionLimits,
};
Expand Down Expand Up @@ -294,7 +294,7 @@ mod tests {

let topology = Topology::new(UniqueVec::new());
let first_block = BlockBuilder::new(transactions.clone(), topology.clone(), Vec::new())
.chain(0, &mut wsv)
.chain(0, VRFState::generate_new_random_state(), &mut wsv)
.sign(&ALICE_KEYS)
.commit(&topology)
.expect("Block is valid");
Expand All @@ -304,7 +304,7 @@ mod tests {

for _ in 1u64..blocks {
let block = BlockBuilder::new(transactions.clone(), topology.clone(), Vec::new())
.chain(0, &mut wsv)
.chain(0, VRFState::generate_new_random_state(), &mut wsv)
.sign(&ALICE_KEYS)
.commit(&topology)
.expect("Block is valid");
Expand Down Expand Up @@ -435,7 +435,7 @@ mod tests {

let topology = Topology::new(UniqueVec::new());
let vcb = BlockBuilder::new(vec![va_tx.clone()], topology.clone(), Vec::new())
.chain(0, &mut wsv)
.chain(0, VRFState::generate_new_random_state(), &mut wsv)
.sign(&ALICE_KEYS)
.commit(&topology)
.expect("Block is valid");
Expand Down
21 changes: 16 additions & 5 deletions core/src/sumeragi/main_loop.rs
@@ -1,7 +1,7 @@
//! The main event loop that powers sumeragi.
use std::sync::mpsc;

use iroha_crypto::HashOf;
use iroha_crypto::{HashOf, VRFState};
use iroha_data_model::{
block::*, events::pipeline::PipelineEvent, peer::PeerId,
transaction::error::TransactionRejectionReason,
Expand Down Expand Up @@ -291,8 +291,9 @@ impl Sumeragi {
.expect("Genesis invalid");

let mut new_wsv = self.wsv.clone();
// Here is the only place in sumeragi it is okay to generate a random vrf state.
let genesis = BlockBuilder::new(transactions, self.current_topology.clone(), vec![])
.chain(0, &mut new_wsv)
.chain(0, VRFState::generate_new_random_state(), &mut new_wsv)
.sign(&self.key_pair);

let genesis_msg = BlockCreated::from(genesis.clone()).into();
Expand Down Expand Up @@ -334,6 +335,7 @@ impl Sumeragi {
role=%self.current_topology.role(&self.peer_id),
block_height=%block.as_ref().header().height(),
block_hash=%block.as_ref().hash(),
vrf_state=%block.as_ref().header().vrf_state,
"{}", Strategy::LOG_MESSAGE,
);

Expand Down Expand Up @@ -638,6 +640,15 @@ impl Sumeragi {
info!(%addr, txns=%transactions.len(), "Creating block...");
let create_block_start_time = Instant::now();

let old_vrf_state: VRFState = self
.wsv
.latest_block_ref()
.expect("Genesis committed")
.header()
.vrf_state
.clone();
let new_vrf_state: VRFState = old_vrf_state.perform_vrf(&self.key_pair);

// TODO: properly process triggers!
let mut new_wsv = self.wsv.clone();
let event_recommendations = Vec::new();
Expand All @@ -646,7 +657,7 @@ impl Sumeragi {
self.current_topology.clone(),
event_recommendations,
)
.chain(current_view_change_index, &mut new_wsv)
.chain(current_view_change_index, new_vrf_state, &mut new_wsv)
.sign(&self.key_pair);

let created_in = create_block_start_time.elapsed();
Expand Down Expand Up @@ -1224,7 +1235,7 @@ mod tests {

// Creating a block of two identical transactions and validating it
let block = BlockBuilder::new(vec![tx.clone(), tx], topology.clone(), Vec::new())
.chain(0, &mut wsv)
.chain(0, VRFState::generate_new_random_state(), &mut wsv)
.sign(leader_key_pair);

let genesis = block.commit(topology).expect("Block is valid");
Expand Down Expand Up @@ -1262,7 +1273,7 @@ mod tests {

// Creating a block of two identical transactions and validating it
let block = BlockBuilder::new(vec![tx1, tx2], topology.clone(), Vec::new())
.chain(0, &mut wsv.clone())
.chain(0, VRFState::generate_new_random_state(), &mut wsv.clone())
.sign(leader_key_pair);

(wsv, kura, block.into())
Expand Down
29 changes: 26 additions & 3 deletions core/src/sumeragi/network_topology.rs
Expand Up @@ -2,10 +2,11 @@

use derive_more::Display;
use indexmap::IndexSet;
use iroha_crypto::{PublicKey, SignatureOf};
use iroha_crypto::{HashOf, PublicKey, SignatureOf, VRFState};
use iroha_data_model::{block::SignedBlock, prelude::PeerId};
use iroha_logger::trace;
use iroha_primitives::unique_vec::UniqueVec;
use rand::{prelude::SliceRandom, rngs::StdRng, SeedableRng};

/// The ordering of the peers which defines their roles in the current round of consensus.
///
Expand All @@ -22,6 +23,7 @@ use iroha_primitives::unique_vec::UniqueVec;
pub struct Topology {
/// Current order of peers. The roles of peers are defined based on this order.
pub(crate) ordered_peers: UniqueVec<PeerId>,
created_with_vrf_state: VRFState,
}

/// Topology with at least one peer
Expand All @@ -41,8 +43,13 @@ impl Topology {
pub fn new(peers: UniqueVec<PeerId>) -> Self {
Topology {
ordered_peers: peers,
created_with_vrf_state: VRFState::generate_new_random_state(),
}
}
/// Get the VRF state that this topology was created with.
pub fn get_vrf_state(&self) -> VRFState {
self.created_with_vrf_state.clone()
}

/// True, if the topology contains at least one peer and thus requires consensus
pub fn is_non_empty(&self) -> Option<NonEmptyTopology> {
Expand Down Expand Up @@ -169,7 +176,7 @@ impl Topology {
}

/// Perform sequence of actions after block committed.
pub fn update_topology(&mut self, block_signees: &[PublicKey], new_peers: UniqueVec<PeerId>) {
fn update_topology(&mut self, block_signees: &[PublicKey], new_peers: UniqueVec<PeerId>) {
self.lift_up_peers(block_signees);
self.rotate_set_a();
self.update_peer_list(new_peers);
Expand All @@ -181,7 +188,23 @@ impl Topology {
view_change_index: u64,
new_peers: UniqueVec<PeerId>,
) -> Self {
let mut topology = Topology::new(block.commit_topology().clone());
let created_with_vrf_state = block.header().vrf_state.clone();
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(
HashOf::new(&created_with_vrf_state).as_ref()[0..8]
.try_into()
.expect("Cannot fail"),
));

let mut topology = {
let mut shuffle_peers: Vec<PeerId> = block.commit_topology().clone().into();
shuffle_peers.shuffle(&mut rng);
let mut ordered_peers = UniqueVec::new();
ordered_peers.extend(shuffle_peers.into_iter());
Topology {
ordered_peers,
created_with_vrf_state,
}
};
let block_signees = block
.signatures()
.into_iter()
Expand Down
56 changes: 56 additions & 0 deletions crypto/src/lib.rs
Expand Up @@ -718,6 +718,62 @@ impl From<PrivateKey> for PrivateKeySerialized {
}
}

/// Random State of Verifiable Random Function
#[derive(
Debug,
PartialEq,
Ord,
PartialOrd,
Eq,
Clone,
Hash,
Decode,
Encode,
Serialize,
Deserialize,
IntoSchema,
)]
pub struct VRFState {
signature: Signature,
}

impl fmt::Display for VRFState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use fmt::Debug;
self.signature.fmt(f)
}
}

impl VRFState {
/// Generate an initial random state to seed the VRF.
#[cfg(feature = "std")]
pub fn generate_new_random_state() -> Self {
let key_pair = KeyPair::random();
let (public, _) = key_pair.clone().into_parts();
let (_, bytes) = public.to_bytes();
Self {
signature: Signature::new(&key_pair, &bytes),
}
}
/// Generate an initial random state to seed the VRF.
pub fn perform_vrf(&self, key_pair: &KeyPair) -> Self {
Self {
signature: Signature::new(key_pair, HashOf::new(&self).as_ref()),
}
}
/// Verify the vrf using old state and expected public key. In the context
/// of Sumeragi the expected public key is the public key of the peer that
/// created the block. In other words the peer with the Leader role.
pub fn verify(&self, old_state: Self, expected_key: &PublicKey) -> bool {
if self.signature.public_key() != expected_key {
return false;
}
self.signature
.verify(HashOf::new(&old_state).as_ref())
.is_ok()
}
}

/// A session key derived from a key exchange. Will usually be used for a symmetric encryption afterwards
pub struct SessionKey(ConstVec<u8>);

Expand Down
4 changes: 3 additions & 1 deletion data_model/src/block.rs
Expand Up @@ -12,7 +12,7 @@ use derive_more::Display;
use getset::Getters;
#[cfg(all(feature = "std", feature = "transparent_api"))]
use iroha_crypto::KeyPair;
use iroha_crypto::{HashOf, MerkleTree, SignaturesOf};
use iroha_crypto::{HashOf, MerkleTree, SignaturesOf, VRFState};
use iroha_data_model_derive::model;
use iroha_macro::FromVariant;
use iroha_primitives::unique_vec::UniqueVec;
Expand Down Expand Up @@ -59,6 +59,8 @@ pub mod model {
pub timestamp_ms: u64,
/// Hash of the previous block in the chain.
pub previous_block_hash: Option<HashOf<SignedBlock>>,
/// VRF State
pub vrf_state: VRFState,
/// Hash of merkle tree root of transactions' hashes.
pub transactions_hash: Option<HashOf<MerkleTree<SignedTransaction>>>,
/// Value of view change index. Used to resolve soft forks.
Expand Down

0 comments on commit c43c25f

Please sign in to comment.