From 5c86b3ae59b05424e7bee940b570d151e7aa033e 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 --- Cargo.lock | 52 ++++++++++++ cli/src/lib.rs | 5 +- .../extra_functional/offline_peers.rs | 4 +- config/iroha_test_config.toml | 12 +-- config/src/parameters/user.rs | 11 +++ config/tests/fixtures.rs | 36 ++++---- config/tests/fixtures/base.toml | 6 +- config/tests/fixtures/base_trusted_peers.toml | 2 +- config/tests/fixtures/full.env | 6 +- config/tests/fixtures/full.toml | 6 +- .../tests/fixtures/minimal_file_and_env.toml | 8 +- core/Cargo.toml | 1 + core/benches/blocks/common.rs | 2 +- core/benches/kura.rs | 2 +- core/benches/validation.rs | 7 +- core/src/block.rs | 28 ++++++- core/src/smartcontracts/isi/query.rs | 6 +- core/src/smartcontracts/isi/world.rs | 4 + core/src/sumeragi/main_loop.rs | 26 ++++-- core/src/sumeragi/mod.rs | 1 + core/src/sumeragi/network_topology.rs | 83 ++++++++----------- core/src/sumeragi/vrf.rs | 22 +++++ core/test_network/src/lib.rs | 7 +- data_model/src/block.rs | 2 + data_model/src/events/pipeline.rs | 1 + data_model/src/isi.rs | 2 + docs/source/references/schema.json | 8 ++ tools/swarm/src/compose.rs | 9 +- 28 files changed, 250 insertions(+), 109 deletions(-) create mode 100644 core/src/sumeragi/vrf.rs diff --git a/Cargo.lock b/Cargo.lock index b09ce7122a0..37fa7e59de1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1541,6 +1541,28 @@ dependencies = [ "once_cell", ] +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -2464,6 +2486,12 @@ dependencies = [ "digest", ] +[[package]] +name = "hmac-sha256" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3688e69b38018fec1557254f64c8dc2cc8ec502890182f395dbb0aa997aa5735" + [[package]] name = "home" version = "0.5.5" @@ -2849,6 +2877,7 @@ dependencies = [ "thiserror", "tokio", "uuid", + "vrf", "wasmtime", ] @@ -5417,6 +5446,18 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + [[package]] name = "tap" version = "1.0.1" @@ -6129,6 +6170,17 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vrf" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff9943db5840ba292776c3778fedf9b97e11166d8222eceb2cb330f1ea08945" +dependencies = [ + "failure", + "hmac-sha256", + "openssl", +] + [[package]] name = "w3f-bls" version = "0.1.3" diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 0787ea2207c..04d82f938af 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -622,14 +622,15 @@ mod tests { use assertables::{assert_contains, assert_contains_as_result}; use iroha_config::parameters::user::RootPartial as PartialUserConfig; - use iroha_crypto::KeyPair; + use iroha_crypto::{Algorithm, KeyPair}; use iroha_primitives::addr::socket_addr; use path_absolutize::Absolutize as _; use super::*; fn config_factory() -> PartialUserConfig { - let (pubkey, privkey) = KeyPair::random().into_parts(); + let (pubkey, privkey) = + KeyPair::random_with_algorithm(Algorithm::Secp256k1).into_parts(); let mut base = PartialUserConfig::default(); diff --git a/client/tests/integration/extra_functional/offline_peers.rs b/client/tests/integration/extra_functional/offline_peers.rs index 988b0271acb..f12adafe560 100644 --- a/client/tests/integration/extra_functional/offline_peers.rs +++ b/client/tests/integration/extra_functional/offline_peers.rs @@ -7,7 +7,7 @@ use iroha_client::{ }, }; use iroha_config::parameters::actual::Root as Config; -use iroha_crypto::KeyPair; +use iroha_crypto::{Algorithm, KeyPair}; use iroha_primitives::addr::socket_addr; use test_network::*; use tokio::runtime::Runtime; @@ -53,7 +53,7 @@ fn register_offline_peer() -> Result<()> { check_status(&peer_clients, 1); let address = socket_addr!(128.0.0.2:8085); - let key_pair = KeyPair::random(); + let key_pair = KeyPair::random_with_algorithm(Algorithm::Secp256k1); let public_key = key_pair.public_key().clone(); let peer_id = PeerId::new(address, public_key); let register_peer = Register::peer(DataModelPeer::new(peer_id)); diff --git a/config/iroha_test_config.toml b/config/iroha_test_config.toml index ae71d8b8223..997ddcaafcf 100644 --- a/config/iroha_test_config.toml +++ b/config/iroha_test_config.toml @@ -1,6 +1,6 @@ chain_id = "00000000-0000-0000-0000-000000000000" -public_key = "ed01201C61FAF8FE94E253B93114240394F79A607B7FA55F9E5A41EBEC74B88055768B" -private_key = { algorithm = "ed25519", payload = "282ED9F3CF92811C3818DBC4AE594ED59DC1A2F78E4241E31924E101D6B1FB831C61FAF8FE94E253B93114240394F79A607B7FA55F9E5A41EBEC74B88055768B" } +public_key = "e70121039B861E76EBC90B3348142E3ED6C82DE4F6223A003E19159397D93008CEDB2CF3" +private_key = { algorithm = "secp256k1", payload = "D9CAA39CD8DF8E20BDE9E21CB2A8DFF3DC3F152954E651D96F03E1EC94DFE581" } [network] address = "127.0.0.1:1337" @@ -15,19 +15,19 @@ address = "127.0.0.1:8080" [[sumeragi.trusted_peers]] address = "127.0.0.1:1337" -public_key = "ed01201C61FAF8FE94E253B93114240394F79A607B7FA55F9E5A41EBEC74B88055768B" +public_key = "e7012102E7A461547D04FF0EA7E9A473D67B51A58C41E53F7D9583096052492ABDB430D4" [[sumeragi.trusted_peers]] address = "127.0.0.1:1338" -public_key = "ed0120CC25624D62896D3A0BFD8940F928DC2ABF27CC57CEFEB442AA96D9081AAE58A1" +public_key = "e7012103F8DE33E3B492CE14346BCD330A0044FE8C03BFCA03168B513722CCB00BE42734" [[sumeragi.trusted_peers]] address = "127.0.0.1:1339" -public_key = "ed0120FACA9E8AA83225CB4D16D67F27DD4F93FC30FFA11ADC1F5C88FD5495ECC91020" +public_key = "e701210390292D847084F39975E805DF70CEEBDF1FEC61C1547C77DC0B2A353C29DD3B97" [[sumeragi.trusted_peers]] address = "127.0.0.1:1340" -public_key = "ed01208E351A70B6A603ED285D666B8D689B680865913BA03CE29FB7D13A166C4E7F1F" +public_key = "e701210360E53577CDA472A8873ABFF96376E0A82A5123D718F91499D284A45D57A322EC" [logger] format = "pretty" diff --git a/config/src/parameters/user.rs b/config/src/parameters/user.rs index 35774e882a6..ab7349f02ef 100644 --- a/config/src/parameters/user.rs +++ b/config/src/parameters/user.rs @@ -142,6 +142,12 @@ impl Root { let key_pair = KeyPair::new(self.public_key, self.private_key) .wrap_err("failed to construct a key pair from `iroha.public_key` and `iroha.private_key` configuration parameters") + .map(|key| { + if key.algorithm() != iroha_crypto::Algorithm::Secp256k1 { + emitter.emit(eyre!("Peer key pair must use algorithm Secp256k1. Problematic public key = {}", key.public_key())); + } + key + }) .map_or_else(|err| { emitter.emit(err); None @@ -426,6 +432,11 @@ impl Sumeragi { } = self; let trusted_peers = construct_unique_vec(trusted_peers.unwrap_or(vec![]))?; + for peer in &trusted_peers { + if peer.public_key.algorithm() != iroha_crypto::Algorithm::Secp256k1 { + return Err(eyre!("Only Secp256k1 key pairs are allowed in the trusted peers. Problematic public key = {}", peer.public_key)); + } + } Ok(actual::Sumeragi { trusted_peers, diff --git a/config/tests/fixtures.rs b/config/tests/fixtures.rs index 1f95e1650aa..d23dcd69125 100644 --- a/config/tests/fixtures.rs +++ b/config/tests/fixtures.rs @@ -59,20 +59,20 @@ fn minimal_config_snapshot() -> Result<()> { ), key_pair: KeyPair { public_key: PublicKey( - ed25519( - "ed01208BA62848CF767D72E7F7F4B9D2D7BA07FEE33760F79ABE5597A51520E292A0CB", + secp256k1( + "e701210309DC6B5DFAB2603E5577DF96D34314CED6A07389A047E772649FA877BCCA17B2", ), ), - private_key: ed25519( - "8F4C15E5D664DA3F13778801D23D4E89B76E94C1B94B389544168B6CB894F84F8BA62848CF767D72E7F7F4B9D2D7BA07FEE33760F79ABE5597A51520E292A0CB", + private_key: secp256k1( + "7D7175A0C3F141A482C3DE3D23225686B6896C66D6430BBBC05867834DE3FA48", ), }, p2p_address: 127.0.0.1:1337, }, genesis: Partial { public_key: PublicKey( - ed25519( - "ed01208BA62848CF767D72E7F7F4B9D2D7BA07FEE33760F79ABE5597A51520E292A0CB", + secp256k1( + "e701210309DC6B5DFAB2603E5577DF96D34314CED6A07389A047E772649FA877BCCA17B2", ), ), }, @@ -91,8 +91,8 @@ fn minimal_config_snapshot() -> Result<()> { PeerId { address: 127.0.0.1:1338, public_key: PublicKey( - ed25519( - "ed01208BA62848CF767D72E7F7F4B9D2D7BA07FEE33760F79ABE5597A51520E292A0CB", + secp256k1( + "e701210309DC6B5DFAB2603E5577DF96D34314CED6A07389A047E772649FA877BCCA17B2", ), ), }, @@ -303,27 +303,27 @@ fn full_envs_set_is_consumed() -> Result<()> { ), public_key: Some( PublicKey( - ed25519( - "ed01208BA62848CF767D72E7F7F4B9D2D7BA07FEE33760F79ABE5597A51520E292A0CB", + secp256k1( + "e701210309DC6B5DFAB2603E5577DF96D34314CED6A07389A047E772649FA877BCCA17B2", ), ), ), private_key: Some( - ed25519( - "8F4C15E5D664DA3F13778801D23D4E89B76E94C1B94B389544168B6CB894F84F8BA62848CF767D72E7F7F4B9D2D7BA07FEE33760F79ABE5597A51520E292A0CB", + secp256k1( + "7D7175A0C3F141A482C3DE3D23225686B6896C66D6430BBBC05867834DE3FA48", ), ), genesis: GenesisPartial { public_key: Some( PublicKey( - ed25519( - "ed01208BA62848CF767D72E7F7F4B9D2D7BA07FEE33760F79ABE5597A51520E292A0CB", + secp256k1( + "e701210309DC6B5DFAB2603E5577DF96D34314CED6A07389A047E772649FA877BCCA17B2", ), ), ), private_key: Some( - ed25519( - "8F4C15E5D664DA3F13778801D23D4E89B76E94C1B94B389544168B6CB894F84F8BA62848CF767D72E7F7F4B9D2D7BA07FEE33760F79ABE5597A51520E292A0CB", + secp256k1( + "7D7175A0C3F141A482C3DE3D23225686B6896C66D6430BBBC05867834DE3FA48", ), ), file: None, @@ -350,8 +350,8 @@ fn full_envs_set_is_consumed() -> Result<()> { port: 1339, }, public_key: PublicKey( - ed25519( - "ed0120312C1B7B5DE23D366ADCF23CD6DB92CE18B2AA283C7D9F5033B969C2DC2B92F4", + secp256k1( + "e701210309DC6B5DFAB2603E5577DF96D34314CED6A07389A047E772649FA877BCCA17B2", ), ), }, diff --git a/config/tests/fixtures/base.toml b/config/tests/fixtures/base.toml index 052b9a6df5b..52445ed49b0 100644 --- a/config/tests/fixtures/base.toml +++ b/config/tests/fixtures/base.toml @@ -1,7 +1,7 @@ chain_id = "0" -public_key = "ed01208BA62848CF767D72E7F7F4B9D2D7BA07FEE33760F79ABE5597A51520E292A0CB" -private_key.algorithm = "ed25519" -private_key.payload = "8f4c15e5d664da3f13778801d23d4e89b76e94c1b94b389544168b6cb894f84f8ba62848cf767d72e7f7f4b9d2d7ba07fee33760f79abe5597a51520e292a0cb" +public_key = "e7012103DD678497624AF3F3A3F59F4F0AD3861751E7F3B16CA402E864A1499DD6358C55" +private_key.algorithm = "secp256k1" +private_key.payload = "BB52C74DC56A2E9028F3BC5B42ECEC6CDDF19DBD3CD11F2A23A0477670944E52" [network] address = "127.0.0.1:1337" diff --git a/config/tests/fixtures/base_trusted_peers.toml b/config/tests/fixtures/base_trusted_peers.toml index 1314cd70026..646b4e3ad0e 100644 --- a/config/tests/fixtures/base_trusted_peers.toml +++ b/config/tests/fixtures/base_trusted_peers.toml @@ -1,3 +1,3 @@ [[sumeragi.trusted_peers]] address = "127.0.0.1:1338" -public_key = "ed01208BA62848CF767D72E7F7F4B9D2D7BA07FEE33760F79ABE5597A51520E292A0CB" +public_key = "e7012103756A6D80129A39E94D40E91826D96FE9DAE6F0EE153ADE3BE1246E019394F445" diff --git a/config/tests/fixtures/full.env b/config/tests/fixtures/full.env index 35b61e9e5f7..f46b3830f21 100644 --- a/config/tests/fixtures/full.env +++ b/config/tests/fixtures/full.env @@ -1,7 +1,7 @@ CHAIN_ID=0-0 -PUBLIC_KEY=ed01208BA62848CF767D72E7F7F4B9D2D7BA07FEE33760F79ABE5597A51520E292A0CB -PRIVATE_KEY_ALGORITHM=ed25519 -PRIVATE_KEY_PAYLOAD=8f4c15e5d664da3f13778801d23d4e89b76e94c1b94b389544168b6cb894f84f8ba62848cf767d72e7f7f4b9d2d7ba07fee33760f79abe5597a51520e292a0cb +PUBLIC_KEY=e7012103DD678497624AF3F3A3F59F4F0AD3861751E7F3B16CA402E864A1499DD6358C55 +PRIVATE_KEY_ALGORITHM=secp256k1 +PRIVATE_KEY_PAYLOAD=BB52C74DC56A2E9028F3BC5B42ECEC6CDDF19DBD3CD11F2A23A0477670944E52 P2P_ADDRESS=127.0.0.1:5432 GENESIS_PUBLIC_KEY=ed01208BA62848CF767D72E7F7F4B9D2D7BA07FEE33760F79ABE5597A51520E292A0CB GENESIS_PRIVATE_KEY_ALGORITHM=ed25519 diff --git a/config/tests/fixtures/full.toml b/config/tests/fixtures/full.toml index aae107333d0..1740c320b07 100644 --- a/config/tests/fixtures/full.toml +++ b/config/tests/fixtures/full.toml @@ -1,8 +1,8 @@ # This config has ALL fields specified (except `extends`) chain_id = "0" -public_key = "ed01208BA62848CF767D72E7F7F4B9D2D7BA07FEE33760F79ABE5597A51520E292A0CB" -private_key = { algorithm = "ed25519", payload = "8f4c15e5d664da3f13778801d23d4e89b76e94c1b94b389544168b6cb894f84f8ba62848cf767d72e7f7f4b9d2d7ba07fee33760f79abe5597a51520e292a0cb" } +public_key = "e7012103DD678497624AF3F3A3F59F4F0AD3861751E7F3B16CA402E864A1499DD6358C55" +private_key = { algorithm = "secp256k1", payload = "BB52C74DC56A2E9028F3BC5B42ECEC6CDDF19DBD3CD11F2A23A0477670944E52" } [genesis] file = "genesis.json" @@ -30,7 +30,7 @@ output_new_blocks = true [[sumeragi.trusted_peers]] address = "localhost:8081" -public_key = "ed01208BA62848CF767D72E7F7F4B9D2D7BA07FEE33760F79ABE5597A51520E292A0CB" +public_key = "e7012103DD678497624AF3F3A3F59F4F0AD3861751E7F3B16CA402E864A1499DD6358C55" [sumeragi.debug] force_soft_fork = true diff --git a/config/tests/fixtures/minimal_file_and_env.toml b/config/tests/fixtures/minimal_file_and_env.toml index 1cb37d5e2a6..9c43d805826 100644 --- a/config/tests/fixtures/minimal_file_and_env.toml +++ b/config/tests/fixtures/minimal_file_and_env.toml @@ -1,14 +1,14 @@ extends = "base_trusted_peers.toml" chain_id = "0" -public_key = "ed01208BA62848CF767D72E7F7F4B9D2D7BA07FEE33760F79ABE5597A51520E292A0CB" -private_key.algorithm = "ed25519" -private_key.payload = "8f4c15e5d664da3f13778801d23d4e89b76e94c1b94b389544168b6cb894f84f8ba62848cf767d72e7f7f4b9d2d7ba07fee33760f79abe5597a51520e292a0cb" +public_key = "e7012103756A6D80129A39E94D40E91826D96FE9DAE6F0EE153ADE3BE1246E019394F445" +private_key.algorithm = "secp256k1" +private_key.payload = "1D39F2378F01F87F32AA830B7ABC4262EFEB44C67D7EA7BE277F1EA97C470CAE" [network] address = "127.0.0.1:1337" [genesis] -public_key = "ed01208BA62848CF767D72E7F7F4B9D2D7BA07FEE33760F79ABE5597A51520E292A0CB" +public_key = "e7012103756A6D80129A39E94D40E91826D96FE9DAE6F0EE153ADE3BE1246E019394F445" # `torii.address` should be in ENV diff --git a/core/Cargo.toml b/core/Cargo.toml index fea0db275ef..673052d9fe0 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -66,6 +66,7 @@ nonzero_ext = { workspace = true } uuid = { version = "1.4.1", features = ["v4"] } indexmap = "2.1.0" +vrf = "0.2.4" [dev-dependencies] criterion = { workspace = true } diff --git a/core/benches/blocks/common.rs b/core/benches/blocks/common.rs index d88514f7c9f..2c1588f07e6 100644 --- a/core/benches/blocks/common.rs +++ b/core/benches/blocks/common.rs @@ -40,7 +40,7 @@ pub fn create_block( topology.clone(), Vec::new(), ) - .chain(0, state) + .chain(0, Vec::new(), state) .sign(key_pair) .unpack(|_| {}) .commit(&topology) diff --git a/core/benches/kura.rs b/core/benches/kura.rs index 521e242f60e..d7a131f2ff7 100644 --- a/core/benches/kura.rs +++ b/core/benches/kura.rs @@ -54,7 +54,7 @@ async fn measure_block_size_for_n_executors(n_executors: u32) { let mut block = { let mut state_block = state.block(); BlockBuilder::new(vec![tx], topology, Vec::new()) - .chain(0, &mut state_block) + .chain(0, Vec::new(), &mut state_block) .sign(&KeyPair::random()) .unpack(|_| {}) }; diff --git a/core/benches/validation.rs b/core/benches/validation.rs index d7e5459f090..00efbc003ff 100644 --- a/core/benches/validation.rs +++ b/core/benches/validation.rs @@ -179,8 +179,11 @@ fn sign_blocks(criterion: &mut Criterion) { let mut count = 0; let mut state_block = state.block(); - let block = - BlockBuilder::new(vec![transaction], topology, Vec::new()).chain(0, &mut state_block); + let block = BlockBuilder::new(vec![transaction], topology, Vec::new()).chain( + 0, + Vec::new(), + &mut state_block, + ); let _ = criterion.bench_function("sign_block", |b| { b.iter_batched( diff --git a/core/src/block.rs b/core/src/block.rs index 28a1369e1a0..6d5864ec321 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -20,7 +20,11 @@ use thiserror::Error; pub(crate) use self::event::WithEvents; pub use self::{chained::Chained, commit::CommittedBlock, valid::ValidBlock}; -use crate::{prelude::*, sumeragi::network_topology::Topology, tx::AcceptTransactionFail}; +use crate::{ + prelude::*, + sumeragi::{network_topology::Topology, vrf::verify_vrf}, + tx::AcceptTransactionFail, +}; /// Error during transaction validation #[derive(Debug, displaydoc::Display, Error)] @@ -74,6 +78,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 @@ -148,11 +154,13 @@ mod pending { previous_height: u64, prev_block_hash: Option>, view_change_index: u64, + vrf_state: Vec, transactions: &[TransactionValue], ) -> BlockHeader { BlockHeader { height: previous_height + 1, previous_block_hash: prev_block_hash, + vrf_state, transactions_hash: transactions .iter() .map(|value| value.as_ref().hash()) @@ -206,6 +214,7 @@ mod pending { pub fn chain( self, view_change_index: u64, + vrf_state: Vec, state: &mut StateBlock<'_>, ) -> BlockBuilder { let transactions = Self::categorize_transactions(self.0.transactions, state); @@ -215,6 +224,7 @@ mod pending { state.height(), state.latest_block_hash(), view_change_index, + vrf_state, &transactions, ), transactions, @@ -320,6 +330,15 @@ mod valid { ))); } + let leader_pk = &topology.ordered_peers[0].public_key; + if !verify_vrf( + &topology.get_vrf_state(), + &block.header().vrf_state, + leader_pk, + ) { + return WithEvents::new(Err((block, BlockValidationError::InvalidVRF))); + } + if topology .filter_signatures_by_roles(&[Role::Leader], block.signatures()) .is_empty() @@ -491,6 +510,7 @@ mod valid { .as_millis() .try_into() .expect("Time should fit into u64"), + vrf_state: Vec::new(), }, transactions: Vec::new(), commit_topology: UniqueVec::new(), @@ -842,7 +862,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 state_block) + .chain(0, Vec::new(), &mut state_block) .sign(&alice_keys) .unpack(|_| {}); @@ -917,7 +937,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 state_block) + .chain(0, Vec::new(), &mut state_block) .sign(&alice_keys) .unpack(|_| {}); @@ -987,7 +1007,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 state_block) + .chain(0, Vec::new(), &mut state_block) .sign(&alice_keys) .unpack(|_| {}); diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index daf7faae917..e3e4418876c 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -314,7 +314,7 @@ mod tests { let topology = Topology::new(UniqueVec::new()); let first_block = BlockBuilder::new(transactions.clone(), topology.clone(), Vec::new()) - .chain(0, &mut state_block) + .chain(0, Vec::new(), &mut state_block) .sign(&ALICE_KEYS) .unpack(|_| {}) .commit(&topology) @@ -326,7 +326,7 @@ mod tests { for _ in 1u64..blocks { let block = BlockBuilder::new(transactions.clone(), topology.clone(), Vec::new()) - .chain(0, &mut state_block) + .chain(0, Vec::new(), &mut state_block) .sign(&ALICE_KEYS) .unpack(|_| {}) .commit(&topology) @@ -468,7 +468,7 @@ mod tests { let topology = Topology::new(UniqueVec::new()); let vcb = BlockBuilder::new(vec![va_tx.clone()], topology.clone(), Vec::new()) - .chain(0, &mut state_block) + .chain(0, Vec::new(), &mut state_block) .sign(&ALICE_KEYS) .unpack(|_| {}) .commit(&topology) diff --git a/core/src/smartcontracts/isi/world.rs b/core/src/smartcontracts/isi/world.rs index d2146ae2c22..a3f785e787a 100644 --- a/core/src/smartcontracts/isi/world.rs +++ b/core/src/smartcontracts/isi/world.rs @@ -36,6 +36,10 @@ pub mod isi { ) -> Result<(), Error> { let peer_id = self.object.id; + if peer_id.public_key().algorithm() != iroha_crypto::Algorithm::Secp256k1 { + return Err(InvalidParameterError::KeyAlgorithmNotSupported.into()); + } + let world = &mut state_transaction.world; if !world.trusted_peers_ids.push(peer_id.clone()) { return Err(RepetitionError { diff --git a/core/src/sumeragi/main_loop.rs b/core/src/sumeragi/main_loop.rs index 12fcde7c4ad..d378ca8441c 100644 --- a/core/src/sumeragi/main_loop.rs +++ b/core/src/sumeragi/main_loop.rs @@ -6,7 +6,7 @@ use iroha_data_model::{block::*, events::pipeline::PipelineEventBox, peer::PeerI use iroha_p2p::UpdateTopology; use tracing::{span, Level}; -use super::{view_change::ProofBuilder, *}; +use super::{view_change::ProofBuilder, vrf::perform_vrf, *}; use crate::{block::*, sumeragi::tracing::instrument}; /// `Sumeragi` is the implementation of the consensus. @@ -270,8 +270,13 @@ impl Sumeragi { .expect("Genesis invalid"); let mut state_block = state.block(); + // Here is the only place in sumeragi it is okay to have a bogus unchecked vrf state. let genesis = BlockBuilder::new(transactions, self.current_topology.clone(), vec![]) - .chain(0, &mut state_block) + .chain( + 0, + self.current_topology.get_vrf_state().clone(), + &mut state_block, + ) .sign(&self.key_pair) .unpack(|e| self.send_event(e)); @@ -315,6 +320,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=%HashOf::new(&block.as_ref().header().vrf_state), "{}", Strategy::LOG_MESSAGE, ); @@ -651,6 +657,16 @@ impl Sumeragi { info!(%addr, txns=%transactions.len(), "Creating block..."); let create_block_start_time = Instant::now(); + let new_vrf_state: Vec = perform_vrf( + &state + .view() + .latest_block_ref() + .expect("Genesis committed") + .header() + .vrf_state, + &self.key_pair, + ); + // TODO: properly process triggers! let mut state_block = state.block(); let event_recommendations = Vec::new(); @@ -659,7 +675,7 @@ impl Sumeragi { self.current_topology.clone(), event_recommendations, ) - .chain(current_view_change_index, &mut state_block) + .chain(current_view_change_index, new_vrf_state, &mut state_block) .sign(&self.key_pair) .unpack(|e| self.send_event(e)); @@ -1208,7 +1224,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 state_block) + .chain(0, Vec::new(), &mut state_block) .sign(leader_key_pair) .unpack(|_| {}); @@ -1253,7 +1269,7 @@ mod tests { // Creating a block of two identical transactions and validating it BlockBuilder::new(vec![tx1, tx2], topology.clone(), Vec::new()) - .chain(0, &mut state_block) + .chain(0, Vec::new(), &mut state_block) .sign(leader_key_pair) .unpack(|_| {}) }; diff --git a/core/src/sumeragi/mod.rs b/core/src/sumeragi/mod.rs index f59a7ee6259..ee3681156d3 100644 --- a/core/src/sumeragi/mod.rs +++ b/core/src/sumeragi/mod.rs @@ -28,6 +28,7 @@ pub mod main_loop; pub mod message; pub mod network_topology; pub mod view_change; +pub mod vrf; use parking_lot::Mutex; diff --git a/core/src/sumeragi/network_topology.rs b/core/src/sumeragi/network_topology.rs index dfa22fd9cc5..c41814a8b24 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}; 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: Vec, } /// Topology with at least one peer @@ -39,10 +41,18 @@ pub struct ConsensusTopology<'topology> { impl Topology { /// Create a new topology. pub fn new(peers: UniqueVec) -> Self { + for peer in &peers { + assert!(peer.public_key.algorithm() == iroha_crypto::Algorithm::Secp256k1); + } Topology { ordered_peers: peers, + created_with_vrf_state: (0..128).map(|_| rand::random::()).collect(), } } + /// Get the VRF state that this topology was created with. + pub fn get_vrf_state(&self) -> &Vec { + &self.created_with_vrf_state + } /// True, if the topology contains at least one peer and thus requires consensus pub fn is_non_empty(&self) -> Option { @@ -132,7 +142,7 @@ impl Topology { } /// Add or remove peers from the topology. - pub fn update_peer_list(&mut self, new_peers: UniqueVec) { + fn update_peer_list(&mut self, new_peers: UniqueVec) { self.modify_peers_directly(|peers| peers.retain(|peer| new_peers.contains(peer))); self.ordered_peers.extend(new_peers); } @@ -153,35 +163,36 @@ impl Topology { } } - /// Re-arrange the set of peers after each successful block commit. - pub fn rotate_set_a(&mut self) { - let rotate_at = self.min_votes_for_commit(); - if rotate_at > 0 { - self.modify_peers_directly(|peers| peers[..rotate_at].rotate_left(1)); - } - } - /// Pull peers up in the topology to the top of the a set while preserving local order. - pub fn lift_up_peers(&mut self, to_lift_up: &[PublicKey]) { + fn lift_up_peers(&mut self, to_lift_up: &[PublicKey]) { self.modify_peers_directly(|peers| { peers.sort_by_cached_key(|peer| !to_lift_up.contains(&peer.public_key)); }); } - /// Perform sequence of actions after block committed. - pub 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); - } - /// Recreate topology for given block and view change index pub fn recreate_topology( block: &SignedBlock, 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() @@ -189,31 +200,16 @@ impl Topology { .cloned() .collect::>(); - topology.update_topology(&block_signees, new_peers); + // This causes the signees to be the new A set. But it does not change + // their order relative to each other and so the randomness from the + // vrf is preserved. + topology.lift_up_peers(&block_signees); + + topology.update_peer_list(new_peers); // Rotate all once for every view_change topology.rotate_all_n(view_change_index); - { - // FIXME: This is a hack to prevent consensus from running amock due to - // a bug in the implementation by reverting to predictable ordering - - let view_change_limit: usize = view_change_index - .saturating_sub(10) - .try_into() - .expect("u64 must fit into usize"); - - if view_change_limit > 1 { - iroha_logger::error!("Restarting consensus(internal bug). Report to developers"); - let mut peers: Vec<_> = topology.ordered_peers.iter().cloned().collect(); - - peers.sort(); - let peers_count = peers.len(); - peers.rotate_right(view_change_limit % peers_count); - topology = Topology::new(peers.into_iter().collect()); - } - } - topology } @@ -313,13 +309,6 @@ mod tests { .collect() } - #[test] - fn rotate_set_a() { - let mut topology = topology(); - topology.rotate_set_a(); - assert_eq!(extract_ports(&topology), vec![1, 2, 3, 4, 0, 5, 6]) - } - #[test] fn lift_up_peers() { let mut topology = topology(); diff --git a/core/src/sumeragi/vrf.rs b/core/src/sumeragi/vrf.rs new file mode 100644 index 00000000000..528599cf364 --- /dev/null +++ b/core/src/sumeragi/vrf.rs @@ -0,0 +1,22 @@ +//! The verifiable random function used by sumeragi. +use ::vrf::{ + openssl::{CipherSuite, ECVRF}, + VRF, +}; +use iroha_crypto::{KeyPair, PublicKey}; + +/// Perform the verifiable random function +pub fn perform_vrf(old_state: &Vec, kp: &KeyPair) -> Vec { + assert!(kp.algorithm() == iroha_crypto::Algorithm::Secp256k1); + let mut ctx = ECVRF::from_suite(CipherSuite::SECP256K1_SHA256_TAI).expect("Cannot fail"); + ctx.prove(&kp.private_key().to_bytes().1, old_state.as_ref()) + .expect("Is not allowed to fail") +} +/// Verify the verifiable random function +pub fn verify_vrf(old_state: &Vec, new_state: &Vec, pk: &PublicKey) -> bool { + assert!(pk.algorithm() == iroha_crypto::Algorithm::Secp256k1); + + let mut ctx = ECVRF::from_suite(CipherSuite::SECP256K1_SHA256_TAI).expect("Cannot fail"); + ctx.verify(&pk.to_bytes().1, new_state.as_ref(), old_state.as_ref()) + .is_ok() +} diff --git a/core/test_network/src/lib.rs b/core/test_network/src/lib.rs index 2c966af2b23..bc76c15bfbd 100644 --- a/core/test_network/src/lib.rs +++ b/core/test_network/src/lib.rs @@ -14,7 +14,7 @@ use iroha_client::{ }; use iroha_config::parameters::actual::Root as Config; pub use iroha_core::state::StateReadOnly; -use iroha_crypto::KeyPair; +use iroha_crypto::{Algorithm, KeyPair}; use iroha_data_model::{query::QueryOutputBox, ChainId}; use iroha_genesis::{GenesisNetwork, RawGenesisBlockFile}; use iroha_logger::InstrumentFutures; @@ -489,7 +489,7 @@ impl Peer { /// - `api_address` /// * If keypair generation fails pub fn new() -> Result { - let key_pair = KeyPair::random(); + let key_pair = KeyPair::random_with_algorithm(Algorithm::Secp256k1); let p2p_address = local_unique_port()?; let api_address = local_unique_port()?; let id = PeerId::new(p2p_address.clone(), key_pair.public_key().clone()); @@ -780,7 +780,8 @@ impl TestConfig for Config { ) .merge(RootPartial::from_env(&StdEnv).expect("test env variables should parse properly")); - let (public_key, private_key) = KeyPair::random().into_parts(); + let (public_key, private_key) = + KeyPair::random_with_algorithm(Algorithm::Secp256k1).into_parts(); layer.public_key.set(public_key); layer.private_key.set(private_key); diff --git a/data_model/src/block.rs b/data_model/src/block.rs index ab92e076a3f..606e31dcc3d 100644 --- a/data_model/src/block.rs +++ b/data_model/src/block.rs @@ -65,6 +65,8 @@ mod model { /// Creation timestamp (unix time in milliseconds). #[getset(skip)] pub timestamp_ms: u64, + /// VRF State + pub vrf_state: Vec, /// Value of view change index. Used to resolve soft forks. #[getset(skip)] pub view_change_index: u64, diff --git a/data_model/src/events/pipeline.rs b/data_model/src/events/pipeline.rs index 2e6c8c08dae..f5e2dae6a20 100644 --- a/data_model/src/events/pipeline.rs +++ b/data_model/src/events/pipeline.rs @@ -349,6 +349,7 @@ mod tests { previous_block_hash: None, transactions_hash: None, timestamp_ms: 0, + vrf_state: Vec::new(), view_change_index: 0, consensus_estimation_ms: 0, } diff --git a/data_model/src/isi.rs b/data_model/src/isi.rs index 2e9a73504e3..5cec6449555 100644 --- a/data_model/src/isi.rs +++ b/data_model/src/isi.rs @@ -1547,6 +1547,8 @@ pub mod error { /// /// i.e. too long [`AccountId`] NameLength, + /// Key Algorithm not supported for this instruction + KeyAlgorithmNotSupported, } /// Repetition of of `{instruction_type}` for id `{id}` diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index e9196b814df..704d3d920e5 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -640,6 +640,10 @@ "name": "timestamp_ms", "type": "u64" }, + { + "name": "vrf_state", + "type": "Vec" + }, { "name": "view_change_index", "type": "u64" @@ -2007,6 +2011,10 @@ { "tag": "NameLength", "discriminant": 1 + }, + { + "tag": "KeyAlgorithmNotSupported", + "discriminant": 2 } ] }, diff --git a/tools/swarm/src/compose.rs b/tools/swarm/src/compose.rs index a33237f34b9..334c03b48df 100644 --- a/tools/swarm/src/compose.rs +++ b/tools/swarm/src/compose.rs @@ -471,6 +471,13 @@ fn generate_key_pair(base_seed: Option<&[u8]>, additional_seed: &[u8]) -> KeyPai }) } +fn generate_key_pair_for_peer(base_seed: Option<&[u8]>, additional_seed: &[u8]) -> KeyPair { + base_seed.map_or_else(KeyPair::random, |base| { + let seed: Vec<_> = base.iter().chain(additional_seed).copied().collect(); + KeyPair::from_seed(seed, Algorithm::Secp256k1) + }) +} + mod peer_generator { use std::{collections::BTreeMap, num::NonZeroU16}; @@ -512,7 +519,7 @@ mod peer_generator { .map(|i| { let service_name = format!("{BASE_SERVICE_NAME}{i}"); - let key_pair = super::generate_key_pair(base_seed, service_name.as_bytes()); + let key_pair = super::generate_key_pair_for_peer(base_seed, service_name.as_bytes()); let peer = Peer { name: service_name.clone(),