From f30d277b78406d1a542dd3beee0de1ef37269269 Mon Sep 17 00:00:00 2001 From: "Sam H. Smith" Date: Thu, 14 Mar 2024 23:33:20 +0100 Subject: [PATCH] [feature] #4285: Verifiable Random Function in Sumeragi Signed-off-by: Sam H. Smith --- core/benches/blocks/common.rs | 3 +- core/benches/kura.rs | 4 +- core/benches/validation.rs | 7 +++- core/src/block.rs | 24 +++++++++-- core/src/smartcontracts/isi/query.rs | 8 ++-- core/src/sumeragi/main_loop.rs | 21 +++++++--- core/src/sumeragi/network_topology.rs | 29 +++++++++++-- crypto/src/lib.rs | 59 +++++++++++++++++++++++++++ data_model/src/block.rs | 4 +- docs/source/references/schema.json | 12 ++++++ 10 files changed, 150 insertions(+), 21 deletions(-) diff --git a/core/benches/blocks/common.rs b/core/benches/blocks/common.rs index ef489686047..4ec4c78ddaa 100644 --- a/core/benches/blocks/common.rs +++ b/core/benches/blocks/common.rs @@ -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}, @@ -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(); diff --git a/core/benches/kura.rs b/core/benches/kura.rs index e8e0e6b75c5..6d5f2a510c4 100644 --- a/core/benches/kura.rs +++ b/core/benches/kura.rs @@ -13,7 +13,7 @@ use iroha_core::{ sumeragi::network_topology::Topology, wsv::World, }; -use iroha_crypto::KeyPair; +use iroha_crypto::{KeyPair, VRFState}; use iroha_data_model::{prelude::*, transaction::TransactionLimits}; use iroha_primitives::unique_vec::UniqueVec; use tokio::{fs, runtime::Runtime}; @@ -52,7 +52,7 @@ async fn measure_block_size_for_n_executors(n_executors: u32) { let mut wsv = WorldStateView::new(World::new(), kura, query_handle); let topology = Topology::new(UniqueVec::new()); let mut block = BlockBuilder::new(vec![tx], topology, Vec::new()) - .chain(0, &mut wsv) + .chain(0, VRFState::generate_new_random_state(), &mut wsv) .sign(&KeyPair::random()); for _ in 1..n_executors { diff --git a/core/benches/validation.rs b/core/benches/validation.rs index 814e565fce0..dc54fe38905 100644 --- a/core/benches/validation.rs +++ b/core/benches/validation.rs @@ -12,6 +12,7 @@ use iroha_core::{ tx::TransactionExecutor, wsv::World, }; +use iroha_crypto::VRFState; use iroha_data_model::{isi::InstructionBox, prelude::*, transaction::TransactionLimits}; use iroha_primitives::unique_vec::UniqueVec; @@ -174,7 +175,11 @@ fn sign_blocks(criterion: &mut Criterion) { let mut count = 0; - let block = BlockBuilder::new(vec![transaction], topology, Vec::new()).chain(0, &mut wsv); + let block = BlockBuilder::new(vec![transaction], topology, Vec::new()).chain( + 0, + VRFState::generate_new_random_state(), + &mut wsv, + ); let _ = criterion.bench_function("sign_block", |b| { b.iter_batched( diff --git a/core/src/block.rs b/core/src/block.rs index 7a044a6abdc..23bfac15983 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -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::*, @@ -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 @@ -137,6 +139,7 @@ mod pending { previous_height: u64, previous_block_hash: Option>, view_change_index: u64, + vrf_state: VRFState, transactions: &[TransactionValue], ) -> BlockHeader { BlockHeader { @@ -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()) @@ -191,6 +195,7 @@ mod pending { pub fn chain( self, view_change_index: u64, + vrf_state: VRFState, wsv: &mut WorldStateView, ) -> BlockBuilder { let transactions = Self::categorize_transactions(self.0.transactions, wsv); @@ -200,6 +205,7 @@ mod pending { wsv.height(), wsv.latest_block_hash(), view_change_index, + vrf_state, &transactions, ), transactions, @@ -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() @@ -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(), @@ -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 @@ -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 @@ -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 diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index 9560c23c1b5..bd715e4a9eb 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -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, }; @@ -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"); @@ -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"); @@ -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"); diff --git a/core/src/sumeragi/main_loop.rs b/core/src/sumeragi/main_loop.rs index 90a4a5bef66..a48af1a22bf 100644 --- a/core/src/sumeragi/main_loop.rs +++ b/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, @@ -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(); @@ -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, ); @@ -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(); @@ -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(); @@ -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"); @@ -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()) diff --git a/core/src/sumeragi/network_topology.rs b/core/src/sumeragi/network_topology.rs index dfa22fd9cc5..cb0be72ec99 100644 --- a/core/src/sumeragi/network_topology.rs +++ b/core/src/sumeragi/network_topology.rs @@ -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. /// @@ -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, + created_with_vrf_state: VRFState, } /// Topology with at least one peer @@ -41,8 +43,13 @@ impl Topology { pub fn new(peers: UniqueVec) -> 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 { @@ -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) { + fn update_topology(&mut self, block_signees: &[PublicKey], new_peers: UniqueVec) { self.lift_up_peers(block_signees); self.rotate_set_a(); self.update_peer_list(new_peers); @@ -181,7 +188,23 @@ impl Topology { view_change_index: u64, new_peers: UniqueVec, ) -> 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 = block.commit_topology().clone().into(); + shuffle_peers.shuffle(&mut rng); + let mut ordered_peers = UniqueVec::new(); + ordered_peers.extend(shuffle_peers); + Topology { + ordered_peers, + created_with_vrf_state, + } + }; let block_signees = block .signatures() .into_iter() diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index f1662780479..abe70c2d044 100755 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -718,6 +718,65 @@ impl From for PrivateKeySerialized { } } +ffi::ffi_item! { +/// 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. + #[must_use] + 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); diff --git a/data_model/src/block.rs b/data_model/src/block.rs index 93ce5bec045..42046c0ac7d 100644 --- a/data_model/src/block.rs +++ b/data_model/src/block.rs @@ -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; @@ -59,6 +59,8 @@ pub mod model { pub timestamp_ms: u64, /// Hash of the previous block in the chain. pub previous_block_hash: Option>, + /// VRF State + pub vrf_state: VRFState, /// Hash of merkle tree root of transactions' hashes. pub transactions_hash: Option>>, /// Value of view change index. Used to resolve soft forks. diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index 6e729ccd1d7..a1f9853d705 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -600,6 +600,10 @@ "name": "previous_block_hash", "type": "Option>" }, + { + "name": "vrf_state", + "type": "VRFState" + }, { "name": "transactions_hash", "type": "Option>>" @@ -4490,6 +4494,14 @@ } ] }, + "VRFState": { + "Struct": [ + { + "name": "signature", + "type": "Signature" + } + ] + }, "ValidationFail": { "Enum": [ {