diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index ba5b855db9a65cabee3da43a16063e2c51513b0f..d0578a77a91e960b2a7b3e78aaaa87c6167172d2 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -10159,6 +10159,7 @@ dependencies = [ "parity-db", "parking_lot 0.12.1", "sc-client-api", + "sc-keystore", "sp-api", "sp-blockchain", "sp-core", @@ -11592,8 +11593,14 @@ dependencies = [ name = "sp-statement-store" version = "4.0.0-dev" dependencies = [ + "aes-gcm 0.10.2", + "curve25519-dalek 3.2.0", + "ed25519-dalek", + "hkdf", "parity-scale-codec", + "rand 0.8.5", "scale-info", + "sha2 0.10.6", "sp-api", "sp-application-crypto", "sp-core", @@ -11602,6 +11609,7 @@ dependencies = [ "sp-runtime-interface", "sp-std", "thiserror", + "x25519-dalek 2.0.0-pre.1", ] [[package]] diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs index 5fb5e77c0601271be34da38374ca3c8a9efb76b6..99608c04d38b55f6710021d13037d5994f8ac436 100644 --- a/substrate/bin/node/cli/src/service.rs +++ b/substrate/bin/node/cli/src/service.rs @@ -241,6 +241,7 @@ pub fn new_partial( &config.data_path, Default::default(), client.clone(), + keystore_container.local_keystore(), config.prometheus_registry(), &task_manager.spawn_handle(), ) diff --git a/substrate/client/cli/src/runner.rs b/substrate/client/cli/src/runner.rs index c96f494354f9a39e45468746e722e164cf4750da..59f53200a192bce80a0e5b151ce78db26f87c195 100644 --- a/substrate/client/cli/src/runner.rs +++ b/substrate/client/cli/src/runner.rs @@ -400,7 +400,7 @@ mod tests { }, ); - let Some(output) = output else { return } ; + let Some(output) = output else { return }; let stderr = dbg!(String::from_utf8(output.stderr).unwrap()); diff --git a/substrate/client/keystore/src/lib.rs b/substrate/client/keystore/src/lib.rs index 4151f8c4dc1a483d24f4e6a27bb3220e202a90cf..2d353f3ceba5d715d7c9b01e9a1f04e7a9a86c4e 100644 --- a/substrate/client/keystore/src/lib.rs +++ b/substrate/client/keystore/src/lib.rs @@ -26,6 +26,7 @@ use std::io; /// Local keystore implementation mod local; pub use local::LocalKeystore; +pub use sp_keystore::Keystore; /// Keystore error. #[derive(Debug, thiserror::Error)] diff --git a/substrate/client/network/src/discovery.rs b/substrate/client/network/src/discovery.rs index b6cb29584658f14d2026effb34001c41029fec86..d9fad538443463a5a6474cc3b90e8e954203f086 100644 --- a/substrate/client/network/src/discovery.rs +++ b/substrate/client/network/src/discovery.rs @@ -548,7 +548,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { addresses: &[Multiaddr], effective_role: Endpoint, ) -> Result<Vec<Multiaddr>, ConnectionDenied> { - let Some(peer_id) = maybe_peer else { return Ok(Vec::new()); }; + let Some(peer_id) = maybe_peer else { return Ok(Vec::new()) }; let mut list = self .permanent_addresses diff --git a/substrate/client/network/src/protocol_controller.rs b/substrate/client/network/src/protocol_controller.rs index 9a77881c63320dccf7c8b68cb5e748bb8522e7d9..5b421e1485d69e8e6c3f9c493210f20a8bebb522 100644 --- a/substrate/client/network/src/protocol_controller.rs +++ b/substrate/client/network/src/protocol_controller.rs @@ -658,9 +658,7 @@ impl ProtocolController { /// disconnected, `Ok(false)` if it wasn't found, `Err(PeerId)`, if the peer found, but not in /// connected state. fn drop_reserved_peer(&mut self, peer_id: &PeerId) -> Result<bool, PeerId> { - let Some(state) = self.reserved_nodes.get_mut(peer_id) else { - return Ok(false) - }; + let Some(state) = self.reserved_nodes.get_mut(peer_id) else { return Ok(false) }; if let PeerState::Connected(direction) = state { trace!( @@ -678,9 +676,7 @@ impl ProtocolController { /// Try dropping the peer as a regular peer. Return `true` if the peer was found and /// disconnected, `false` if it wasn't found. fn drop_regular_peer(&mut self, peer_id: &PeerId) -> bool { - let Some(direction) = self.nodes.remove(peer_id) else { - return false - }; + let Some(direction) = self.nodes.remove(peer_id) else { return false }; trace!( target: LOG_TARGET, diff --git a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs index bb3599c0e4ae59325b7c25831509c7767999ca3c..a61406b8d53b83769224ec93e10c5e5ce7096e45 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs @@ -162,7 +162,8 @@ where }, }; // Keep track of the subscription. - let Some(rx_stop) = self.subscriptions.insert_subscription(sub_id.clone(), with_runtime) else { + let Some(rx_stop) = self.subscriptions.insert_subscription(sub_id.clone(), with_runtime) + else { // Inserting the subscription can only fail if the JsonRPSee // generated a duplicate subscription ID. debug!(target: LOG_TARGET, "[follow][id={:?}] Subscription already accepted", sub_id); diff --git a/substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs b/substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs index cb6af8bd590b3a5a0252ff91acf39a510e252a5a..799978be532ae20632aec2b77a5920c2e34996de 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs @@ -339,9 +339,7 @@ where let mut events = Vec::new(); // Nothing to be done if no finalized hashes are provided. - let Some(first_hash) = finalized_block_hashes.get(0) else { - return Ok(Default::default()) - }; + let Some(first_hash) = finalized_block_hashes.get(0) else { return Ok(Default::default()) }; // Find the parent header. let Some(first_header) = self.client.header(*first_hash)? else { diff --git a/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs b/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs index adab64a01c875372f9ed538f29489c0f41c04586..be8b8f46a28440d7408696f5550895ba641c501f 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs @@ -328,9 +328,7 @@ impl<Block: BlockT, BE: Backend<Block>> SubscriptionsInner<Block, BE> { /// Remove the subscription ID with associated pinned blocks. pub fn remove_subscription(&mut self, sub_id: &str) { - let Some(mut sub) = self.subs.remove(sub_id) else { - return - }; + let Some(mut sub) = self.subs.remove(sub_id) else { return }; // The `Stop` event can be generated only once. sub.stop(); diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index c987c2471907d65d125470eaa40737b136007c87..0961967f9ca20a039e12b560b3ba6cb6cea24659 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -237,7 +237,7 @@ pub async fn build_system_rpc_future< // Answer incoming RPC requests. let Some(req) = rpc_rx.next().await else { debug!("RPC requests stream has terminated, shutting down the system RPC future."); - return; + return }; match req { diff --git a/substrate/client/statement-store/Cargo.toml b/substrate/client/statement-store/Cargo.toml index bb2d7aa2feaa53e6ee7ecf706c827130bd8b9d7e..8668dbfa8ba03a2e6e514507ba8089b6b9233d77 100644 --- a/substrate/client/statement-store/Cargo.toml +++ b/substrate/client/statement-store/Cargo.toml @@ -24,6 +24,7 @@ sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-core = { version = "21.0.0", path = "../../primitives/core" } sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } sc-client-api = { version = "4.0.0-dev", path = "../api" } +sc-keystore = { version = "4.0.0-dev", path = "../../client/keystore" } [dev-dependencies] tempfile = "3.1.0" diff --git a/substrate/client/statement-store/src/lib.rs b/substrate/client/statement-store/src/lib.rs index 3b42641d9c5ab1fbd907f7805451a858b7e3e695..da0af08b45402a9e2df83dbd6f9f91e4d4d99311 100644 --- a/substrate/client/statement-store/src/lib.rs +++ b/substrate/client/statement-store/src/lib.rs @@ -54,9 +54,10 @@ pub use sp_statement_store::{Error, StatementStore, MAX_TOPICS}; use metrics::MetricsLink as PrometheusMetrics; use parking_lot::RwLock; use prometheus_endpoint::Registry as PrometheusRegistry; +use sc_keystore::LocalKeystore; use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; -use sp_core::{hexdisplay::HexDisplay, traits::SpawnNamed, Decode, Encode}; +use sp_core::{crypto::UncheckedFrom, hexdisplay::HexDisplay, traits::SpawnNamed, Decode, Encode}; use sp_runtime::traits::Block as BlockT; use sp_statement_store::{ runtime_api::{ @@ -201,6 +202,7 @@ pub struct Store { + Send + Sync, >, + keystore: Arc<LocalKeystore>, // Used for testing time_override: Option<u64>, metrics: PrometheusMetrics, @@ -479,6 +481,7 @@ impl Store { path: &std::path::Path, options: Options, client: Arc<Client>, + keystore: Arc<LocalKeystore>, prometheus: Option<&PrometheusRegistry>, task_spawner: &dyn SpawnNamed, ) -> Result<Arc<Store>> @@ -493,7 +496,7 @@ impl Store { + 'static, Client::Api: ValidateStatement<Block>, { - let store = Arc::new(Self::new(path, options, client, prometheus)?); + let store = Arc::new(Self::new(path, options, client, keystore, prometheus)?); // Perform periodic statement store maintenance let worker_store = store.clone(); @@ -518,6 +521,7 @@ impl Store { path: &std::path::Path, options: Options, client: Arc<Client>, + keystore: Arc<LocalKeystore>, prometheus: Option<&PrometheusRegistry>, ) -> Result<Store> where @@ -566,6 +570,7 @@ impl Store { db, index: RwLock::new(Index::new(options)), validate_fn, + keystore, time_override: None, metrics: PrometheusMetrics::new(prometheus), }; @@ -768,7 +773,45 @@ impl StatementStore for Store { /// Return the decrypted data of all known statements whose decryption key is identified as /// `dest`. The key must be available to the client. fn posted_clear(&self, match_all_topics: &[Topic], dest: [u8; 32]) -> Result<Vec<Vec<u8>>> { - self.collect_statements(Some(dest), match_all_topics, |statement| statement.into_data()) + self.collect_statements(Some(dest), match_all_topics, |statement| { + if let (Some(key), Some(_)) = (statement.decryption_key(), statement.data()) { + let public: sp_core::ed25519::Public = UncheckedFrom::unchecked_from(key); + let public: sp_statement_store::ed25519::Public = public.into(); + match self.keystore.key_pair::<sp_statement_store::ed25519::Pair>(&public) { + Err(e) => { + log::debug!( + target: LOG_TARGET, + "Keystore error: {:?}, for statement {:?}", + e, + HexDisplay::from(&statement.hash()) + ); + None + }, + Ok(None) => { + log::debug!( + target: LOG_TARGET, + "Keystore is missing key for statement {:?}", + HexDisplay::from(&statement.hash()) + ); + None + }, + Ok(Some(pair)) => match statement.decrypt_private(&pair.into_inner()) { + Ok(r) => r, + Err(e) => { + log::debug!( + target: LOG_TARGET, + "Decryption error: {:?}, for statement {:?}", + e, + HexDisplay::from(&statement.hash()) + ); + None + }, + }, + } + } else { + None + } + }) } /// Submit a statement to the store. Validates the statement and returns validation result. @@ -887,6 +930,7 @@ impl StatementStore for Store { #[cfg(test)] mod tests { use crate::Store; + use sc_keystore::Keystore; use sp_core::Pair; use sp_statement_store::{ runtime_api::{InvalidStatement, ValidStatement, ValidateStatement}, @@ -916,6 +960,7 @@ mod tests { RuntimeApi { _inner: self.clone() }.into() } } + sp_api::mock_impl_runtime_apis! { impl ValidateStatement<Block> for RuntimeApi { fn validate_statement( @@ -983,7 +1028,8 @@ mod tests { let client = std::sync::Arc::new(TestClient); let mut path: std::path::PathBuf = temp_dir.path().into(); path.push("db"); - let store = Store::new(&path, Default::default(), client, None).unwrap(); + let keystore = std::sync::Arc::new(sc_keystore::LocalKeystore::in_memory()); + let store = Store::new(&path, Default::default(), client, keystore, None).unwrap(); (store, temp_dir) // return order is important. Store must be dropped before TempDir } @@ -1086,12 +1132,13 @@ mod tests { assert_eq!(store.statements().unwrap().len(), 3); assert_eq!(store.broadcasts(&[]).unwrap().len(), 3); assert_eq!(store.statement(&statement1.hash()).unwrap(), Some(statement1.clone())); + let keystore = store.keystore.clone(); drop(store); let client = std::sync::Arc::new(TestClient); let mut path: std::path::PathBuf = temp.path().into(); path.push("db"); - let store = Store::new(&path, Default::default(), client, None).unwrap(); + let store = Store::new(&path, Default::default(), client, keystore, None).unwrap(); assert_eq!(store.statements().unwrap().len(), 3); assert_eq!(store.broadcasts(&[]).unwrap().len(), 3); assert_eq!(store.statement(&statement1.hash()).unwrap(), Some(statement1)); @@ -1196,7 +1243,6 @@ mod tests { statement(2, 4, None, 1000).hash(), statement(3, 4, Some(3), 300).hash(), statement(3, 5, None, 500).hash(), - //statement(4, 6, None, 100).hash(), ]; expected_statements.sort(); let mut statements: Vec<_> = @@ -1220,13 +1266,31 @@ mod tests { store.set_time(DEFAULT_PURGE_AFTER_SEC + 1); store.maintain(); assert_eq!(store.index.read().expired.len(), 0); + let keystore = store.keystore.clone(); drop(store); let client = std::sync::Arc::new(TestClient); let mut path: std::path::PathBuf = temp.path().into(); path.push("db"); - let store = Store::new(&path, Default::default(), client, None).unwrap(); + let store = Store::new(&path, Default::default(), client, keystore, None).unwrap(); assert_eq!(store.statements().unwrap().len(), 0); assert_eq!(store.index.read().expired.len(), 0); } + + #[test] + fn posted_clear_decrypts() { + let (store, _temp) = test_store(); + let public = store + .keystore + .ed25519_generate_new(sp_core::crypto::key_types::STATEMENT, None) + .unwrap(); + let statement1 = statement(1, 1, None, 100); + let mut statement2 = statement(1, 2, None, 0); + let plain = b"The most valuable secret".to_vec(); + statement2.encrypt(&plain, &public).unwrap(); + store.submit(statement1, StatementSource::Network); + store.submit(statement2, StatementSource::Network); + let posted_clear = store.posted_clear(&[], public.into()).unwrap(); + assert_eq!(posted_clear, vec![plain]); + } } diff --git a/substrate/frame/glutton/src/lib.rs b/substrate/frame/glutton/src/lib.rs index ae3e4f87f0e80cf3669f061dc258fd468c2ad59d..838bd9b67d2965ae2bf66ec5102213b441883f4e 100644 --- a/substrate/frame/glutton/src/lib.rs +++ b/substrate/frame/glutton/src/lib.rs @@ -271,9 +271,7 @@ pub mod pallet { /// /// Tries to come as close to the limit as possible. pub(crate) fn waste_at_most_proof_size(meter: &mut WeightMeter) { - let Ok(n) = Self::calculate_proof_size_iters(&meter) else { - return; - }; + let Ok(n) = Self::calculate_proof_size_iters(&meter) else { return }; meter.defensive_saturating_accrue(T::WeightInfo::waste_proof_size_some(n)); @@ -303,9 +301,7 @@ pub mod pallet { /// /// Tries to come as close to the limit as possible. pub(crate) fn waste_at_most_ref_time(meter: &mut WeightMeter) { - let Ok(n) = Self::calculate_ref_time_iters(&meter) else { - return; - }; + let Ok(n) = Self::calculate_ref_time_iters(&meter) else { return }; meter.defensive_saturating_accrue(T::WeightInfo::waste_ref_time_iter(n)); let clobber = Self::waste_ref_time_iter(vec![0u8; 64], n); diff --git a/substrate/frame/support/procedural/src/benchmark.rs b/substrate/frame/support/procedural/src/benchmark.rs index 18d672b9b0508ddc3daf65efb9ba916f20385875..9f28e7129d2e1b1e2d38b4f55de5a795677d2824 100644 --- a/substrate/frame/support/procedural/src/benchmark.rs +++ b/substrate/frame/support/procedural/src/benchmark.rs @@ -166,11 +166,11 @@ fn ensure_valid_return_type(item_fn: &ItemFn) -> Result<()> { if let ReturnType::Type(_, typ) = &item_fn.sig.output { let non_unit = |span| return Err(Error::new(span, "expected `()`")); let Type::Path(TypePath { path, qself: _ }) = &**typ else { - return Err(Error::new( + return Err(Error::new( typ.span(), "Only `Result<(), BenchmarkError>` or a blank return type is allowed on benchmark function definitions", )) - }; + }; let seg = path .segments .last() @@ -780,7 +780,7 @@ fn expand_benchmark( let call_name = match *expr_call.func { Expr::Path(expr_path) => { // normal function call - let Some(segment) = expr_path.path.segments.last() else { return call_err(); }; + let Some(segment) = expr_path.path.segments.last() else { return call_err() }; segment.ident.to_string() }, Expr::Infer(_) => { diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs index 544f63be2bfa14dc2076831bdb6609ad3c8ad342..b78360d5fc052200e0fcd09942f9882406785109 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs @@ -100,9 +100,7 @@ pub fn expand_outer_enum( let enum_name_ident = Ident::new(enum_ty.struct_name(), Span::call_site()); for pallet_decl in pallet_decls { - let Some(pallet_entry) = pallet_decl.find_part(enum_name_str) else { - continue - }; + let Some(pallet_entry) = pallet_decl.find_part(enum_name_str) else { continue }; let path = &pallet_decl.path; let pallet_name = &pallet_decl.name; diff --git a/substrate/primitives/api/proc-macro/src/runtime_metadata.rs b/substrate/primitives/api/proc-macro/src/runtime_metadata.rs index d027e86fac4ef36c219a531a22fbc05d96a9b632..41849401291e633924e6f530ffc5bc3474ba6283 100644 --- a/substrate/primitives/api/proc-macro/src/runtime_metadata.rs +++ b/substrate/primitives/api/proc-macro/src/runtime_metadata.rs @@ -88,9 +88,7 @@ pub fn generate_decl_runtime_metadata(decl: &ItemTrait) -> TokenStream2 { let mut where_clause = Vec::new(); for item in &decl.items { // Collect metadata for methods only. - let syn::TraitItem::Fn(method) = item else { - continue - }; + let syn::TraitItem::Fn(method) = item else { continue }; // Collect metadata only for the latest methods. let is_changed_in = @@ -103,9 +101,7 @@ pub fn generate_decl_runtime_metadata(decl: &ItemTrait) -> TokenStream2 { let signature = &method.sig; for input in &signature.inputs { // Exclude `self` from metadata collection. - let syn::FnArg::Typed(typed) = input else { - continue - }; + let syn::FnArg::Typed(typed) = input else { continue }; let pat = &typed.pat; let name = quote!(#pat).to_string(); @@ -153,9 +149,7 @@ pub fn generate_decl_runtime_metadata(decl: &ItemTrait) -> TokenStream2 { // The trait generics where already extended with `Block: BlockT`. let mut generics = decl.generics.clone(); for generic_param in generics.params.iter_mut() { - let syn::GenericParam::Type(ty) = generic_param else { - continue - }; + let syn::GenericParam::Type(ty) = generic_param else { continue }; // Default type parameters are not allowed in functions. ty.eq_token = None; diff --git a/substrate/primitives/api/proc-macro/src/utils.rs b/substrate/primitives/api/proc-macro/src/utils.rs index 7b580ee6f099e31c4f4568e3e2b47b5595de64c5..c9389154bbf40ff780f97e5d096dcaee7f1c1ce2 100644 --- a/substrate/primitives/api/proc-macro/src/utils.rs +++ b/substrate/primitives/api/proc-macro/src/utils.rs @@ -266,9 +266,7 @@ pub fn get_doc_literals(attrs: &[syn::Attribute]) -> Vec<syn::Lit> { attrs .iter() .filter_map(|attr| { - let syn::Meta::NameValue(meta) = &attr.meta else { - return None - }; + let syn::Meta::NameValue(meta) = &attr.meta else { return None }; let Ok(lit) = syn::parse2::<syn::Lit>(meta.value.to_token_stream()) else { unreachable!("non-lit doc attribute values do not exist"); }; diff --git a/substrate/primitives/application-crypto/src/lib.rs b/substrate/primitives/application-crypto/src/lib.rs index 46f59719f9c294bc70523badac30c8ea9b203313..95a25c11a717d2e88d8f3f62203b509603f573a8 100644 --- a/substrate/primitives/application-crypto/src/lib.rs +++ b/substrate/primitives/application-crypto/src/lib.rs @@ -179,6 +179,13 @@ macro_rules! app_crypto_pair { impl $crate::AppPair for Pair { type Generic = $pair; } + + impl Pair { + /// Convert into wrapped generic key pair type. + pub fn into_inner(self) -> $pair { + self.0 + } + } }; } diff --git a/substrate/primitives/arithmetic/fuzzer/src/multiply_by_rational_with_rounding.rs b/substrate/primitives/arithmetic/fuzzer/src/multiply_by_rational_with_rounding.rs index e9a3208a738efdee8a7333380658954b3ffd9f87..5f3f675c971f21503944a5474bd053faf7dd4873 100644 --- a/substrate/primitives/arithmetic/fuzzer/src/multiply_by_rational_with_rounding.rs +++ b/substrate/primitives/arithmetic/fuzzer/src/multiply_by_rational_with_rounding.rs @@ -52,9 +52,7 @@ fn check<N>(f: N, n: N, d: N, r: Rounding) where N: MultiplyRational + Into<u128> + Copy + core::fmt::Debug, { - let Some(got) = f.multiply_rational(n, d, r) else { - return; - }; + let Some(got) = f.multiply_rational(n, d, r) else { return }; let (ae, be, ce) = (Fraction::from(f.into()), Fraction::from(n.into()), Fraction::from(d.into())); diff --git a/substrate/primitives/blockchain/src/backend.rs b/substrate/primitives/blockchain/src/backend.rs index e9278be1d5d3c4c1d213fbf61db350831c604572..8208f9128e714511c2a3604d7d754e4d87d62530 100644 --- a/substrate/primitives/blockchain/src/backend.rs +++ b/substrate/primitives/blockchain/src/backend.rs @@ -196,9 +196,7 @@ pub trait Backend<Block: BlockT>: base_hash: Block::Hash, import_lock: &RwLock<()>, ) -> Result<Option<Block::Hash>> { - let Some(base_header) = self.header(base_hash)? else { - return Ok(None) - }; + let Some(base_header) = self.header(base_hash)? else { return Ok(None) }; let leaves = { // ensure no blocks are imported during this code block. diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index f0da73e8ec7891241950773c6f995385ce8fa0e7..f02489a09f05eee1b72fdf1c155fed7df798d657 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -57,7 +57,6 @@ sp-runtime-interface = { version = "17.0.0", default-features = false, path = ". w3f-bls = { version = "0.1.3", default-features = false, optional = true} [dev-dependencies] -rand = "0.8.5" criterion = "0.4.0" serde_json = "1.0" sp-core-hashing-proc-macro = { version = "9.0.0", path = "./hashing/proc-macro" } diff --git a/substrate/primitives/core/src/ed25519.rs b/substrate/primitives/core/src/ed25519.rs index 9d948a5bcea66915c64bced01c56e04cbe7d04ca..d46f2b5043d61a9d3ddb3f6f4d4a53bf642ee930 100644 --- a/substrate/primitives/core/src/ed25519.rs +++ b/substrate/primitives/core/src/ed25519.rs @@ -422,12 +422,8 @@ impl TraitPair for Pair { /// /// Returns true if the signature is good. fn verify<M: AsRef<[u8]>>(sig: &Self::Signature, message: M, public: &Self::Public) -> bool { - let Ok(public) = VerificationKey::try_from(public.as_slice()) else { - return false - }; - let Ok(signature) = ed25519_zebra::Signature::try_from(sig.as_ref()) else { - return false - }; + let Ok(public) = VerificationKey::try_from(public.as_slice()) else { return false }; + let Ok(signature) = ed25519_zebra::Signature::try_from(sig.as_ref()) else { return false }; public.verify(&signature, message.as_ref()).is_ok() } diff --git a/substrate/primitives/core/src/sr25519.rs b/substrate/primitives/core/src/sr25519.rs index 1c040f6ccf7be9be102487d003e07e85c53dfa4b..bcd64ed1cc3cf4d4a4ed79301e5dcc44d1acb925 100644 --- a/substrate/primitives/core/src/sr25519.rs +++ b/substrate/primitives/core/src/sr25519.rs @@ -505,12 +505,8 @@ impl TraitPair for Pair { } fn verify<M: AsRef<[u8]>>(sig: &Self::Signature, message: M, pubkey: &Self::Public) -> bool { - let Ok(signature) = schnorrkel::Signature::from_bytes(sig.as_ref()) else { - return false - }; - let Ok(public) = PublicKey::from_bytes(pubkey.as_ref()) else { - return false - }; + let Ok(signature) = schnorrkel::Signature::from_bytes(sig.as_ref()) else { return false }; + let Ok(public) = PublicKey::from_bytes(pubkey.as_ref()) else { return false }; public.verify_simple(SIGNING_CTX, message.as_ref(), &signature).is_ok() } diff --git a/substrate/primitives/io/src/lib.rs b/substrate/primitives/io/src/lib.rs index 750b5d592463742cfc55b942034da21be1c80e60..bd20ee0c917aac13feabe73485cc1366c461b472 100644 --- a/substrate/primitives/io/src/lib.rs +++ b/substrate/primitives/io/src/lib.rs @@ -776,9 +776,7 @@ pub trait Crypto { return false }; - let Ok(sig) = ed25519_dalek::Signature::from_bytes(&sig.0) else { - return false - }; + let Ok(sig) = ed25519_dalek::Signature::from_bytes(&sig.0) else { return false }; public_key.verify(msg, &sig).is_ok() } else { diff --git a/substrate/primitives/statement-store/Cargo.toml b/substrate/primitives/statement-store/Cargo.toml index c0e1a494ab23af28a31003e0fb3f4b6095eb1131..02ee6f4c15521d1fcc7d8702566c1e50b0d03b54 100644 --- a/substrate/primitives/statement-store/Cargo.toml +++ b/substrate/primitives/statement-store/Cargo.toml @@ -24,6 +24,15 @@ sp-runtime-interface = { version = "17.0.0", default-features = false, path = ". sp-externalities = { version = "0.19.0", default-features = false, path = "../externalities" } thiserror = { version = "1.0", optional = true } +# ECIES dependencies +ed25519-dalek = { version = "1.0", optional = true } +x25519-dalek = { version = "2.0.0-pre.1", optional = true } +curve25519-dalek = { version = "3.2", optional = true } +aes-gcm = { version = "0.10", optional = true } +hkdf = { version = "0.12.0", optional = true } +sha2 = { version = "0.10.0", optional = true } +rand = { version = "0.8.5", features = ["small_rng"], optional = true } + [features] default = ["std"] std = [ @@ -36,6 +45,14 @@ std = [ "sp-api/std", "sp-application-crypto/std", "thiserror", + + "ed25519-dalek", + "x25519-dalek", + "curve25519-dalek", + "aes-gcm", + "hkdf", + "sha2", + "rand", ] serde = [ "scale-info/serde", diff --git a/substrate/primitives/statement-store/src/ecies.rs b/substrate/primitives/statement-store/src/ecies.rs new file mode 100644 index 0000000000000000000000000000000000000000..4afd583e03c80165f3f6716054d6181489105f0e --- /dev/null +++ b/substrate/primitives/statement-store/src/ecies.rs @@ -0,0 +1,174 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// tag::description[] +//! ECIES encryption scheme using x25519 key exchange and AEAD. +// end::description[] + +use aes_gcm::{aead::Aead, AeadCore, KeyInit}; +use rand::rngs::OsRng; +use sha2::Digest; +use sp_core::crypto::Pair; + +/// x25519 secret key. +pub type SecretKey = x25519_dalek::StaticSecret; +/// x25519 public key. +pub type PublicKey = x25519_dalek::PublicKey; + +/// Encryption or decryption error. +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum Error { + /// Generic AES encryption error. + #[error("Encryption error")] + Encryption, + /// Generic AES decryption error. + #[error("Decryption error")] + Decryption, + /// Error reading key data. Not enough data in the buffer. + #[error("Bad cypher text")] + BadData, +} + +const NONCE_LEN: usize = 12; +const PK_LEN: usize = 32; +const AES_KEY_LEN: usize = 32; + +fn aes_encrypt(key: &[u8; AES_KEY_LEN], nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> { + let enc = aes_gcm::Aes256Gcm::new(key.into()); + + enc.encrypt(nonce.into(), aes_gcm::aead::Payload { msg: plaintext, aad: b"" }) + .map_err(|_| Error::Encryption) +} + +fn aes_decrypt(key: &[u8; AES_KEY_LEN], nonce: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Error> { + let dec = aes_gcm::Aes256Gcm::new(key.into()); + dec.decrypt(nonce.into(), aes_gcm::aead::Payload { msg: ciphertext, aad: b"" }) + .map_err(|_| Error::Decryption) +} + +fn kdf(shared_secret: &[u8]) -> [u8; AES_KEY_LEN] { + let hkdf = hkdf::Hkdf::<sha2::Sha256>::new(None, shared_secret); + let mut aes_key = [0u8; AES_KEY_LEN]; + hkdf.expand(b"", &mut aes_key) + .expect("There's always enough data for derivation. qed."); + aes_key +} + +/// Encrypt `plaintext` with the given public x25519 public key. Decryption can be performed with +/// the matching secret key. +pub fn encrypt_x25519(pk: &PublicKey, plaintext: &[u8]) -> Result<Vec<u8>, Error> { + let ephemeral_sk = x25519_dalek::StaticSecret::new(OsRng); + let ephemeral_pk = x25519_dalek::PublicKey::from(&ephemeral_sk); + + let mut shared_secret = ephemeral_sk.diffie_hellman(pk).to_bytes().to_vec(); + shared_secret.extend_from_slice(ephemeral_pk.as_bytes()); + + let aes_key = kdf(&shared_secret); + + let nonce = aes_gcm::Aes256Gcm::generate_nonce(OsRng); + let ciphertext = aes_encrypt(&aes_key, &nonce, plaintext)?; + + let mut out = Vec::with_capacity(ciphertext.len() + PK_LEN + NONCE_LEN); + out.extend_from_slice(ephemeral_pk.as_bytes()); + out.extend_from_slice(nonce.as_slice()); + out.extend_from_slice(ciphertext.as_slice()); + + Ok(out) +} + +/// Encrypt `plaintext` with the given ed25519 public key. Decryption can be performed with the +/// matching secret key. +pub fn encrypt_ed25519(pk: &sp_core::ed25519::Public, plaintext: &[u8]) -> Result<Vec<u8>, Error> { + let ed25519 = curve25519_dalek::edwards::CompressedEdwardsY(pk.0); + let x25519 = ed25519.decompress().ok_or(Error::BadData)?.to_montgomery(); + let montgomery = x25519_dalek::PublicKey::from(x25519.to_bytes()); + encrypt_x25519(&montgomery, plaintext) +} + +/// Decrypt with the given x25519 secret key. +pub fn decrypt_x25519(sk: &SecretKey, encrypted: &[u8]) -> Result<Vec<u8>, Error> { + if encrypted.len() < PK_LEN + NONCE_LEN { + return Err(Error::BadData) + } + let mut ephemeral_pk: [u8; PK_LEN] = Default::default(); + ephemeral_pk.copy_from_slice(&encrypted[0..PK_LEN]); + let ephemeral_pk = PublicKey::from(ephemeral_pk); + + let mut shared_secret = sk.diffie_hellman(&ephemeral_pk).to_bytes().to_vec(); + shared_secret.extend_from_slice(ephemeral_pk.as_bytes()); + + let aes_key = kdf(&shared_secret); + + let nonce = &encrypted[PK_LEN..PK_LEN + NONCE_LEN]; + aes_decrypt(&aes_key, &nonce, &encrypted[PK_LEN + NONCE_LEN..]) +} + +/// Decrypt with the given ed25519 key pair. +pub fn decrypt_ed25519(pair: &sp_core::ed25519::Pair, encrypted: &[u8]) -> Result<Vec<u8>, Error> { + let raw = pair.to_raw_vec(); + let hash: [u8; 32] = sha2::Sha512::digest(&raw).as_slice()[..32] + .try_into() + .map_err(|_| Error::Decryption)?; + let secret = x25519_dalek::StaticSecret::from(hash); + decrypt_x25519(&secret, encrypted) +} + +#[cfg(test)] +mod test { + use super::*; + use rand::rngs::OsRng; + use sp_core::crypto::Pair; + + #[test] + fn basic_x25519_encryption() { + let sk = SecretKey::new(OsRng); + let pk = PublicKey::from(&sk); + + let plain_message = b"An important secret message"; + let encrypted = encrypt_x25519(&pk, plain_message).unwrap(); + + let decrypted = decrypt_x25519(&sk, &encrypted).unwrap(); + assert_eq!(plain_message, decrypted.as_slice()); + } + + #[test] + fn basic_ed25519_encryption() { + let (pair, _) = sp_core::ed25519::Pair::generate(); + let pk = pair.into(); + + let plain_message = b"An important secret message"; + let encrypted = encrypt_ed25519(&pk, plain_message).unwrap(); + + let decrypted = decrypt_ed25519(&pair, &encrypted).unwrap(); + assert_eq!(plain_message, decrypted.as_slice()); + } + + #[test] + fn fails_on_bad_data() { + let sk = SecretKey::new(OsRng); + let pk = PublicKey::from(&sk); + + let plain_message = b"An important secret message"; + let encrypted = encrypt_x25519(&pk, plain_message).unwrap(); + + assert_eq!(decrypt_x25519(&sk, &[]), Err(Error::BadData)); + assert_eq!( + decrypt_x25519(&sk, &encrypted[0..super::PK_LEN + super::NONCE_LEN - 1]), + Err(Error::BadData) + ); + } +} diff --git a/substrate/primitives/statement-store/src/lib.rs b/substrate/primitives/statement-store/src/lib.rs index e5c642d24e2b30973d37441d09a7d8a3e6fa472e..67e7a7b3896b5df81c27539c52135669778882ae 100644 --- a/substrate/primitives/statement-store/src/lib.rs +++ b/substrate/primitives/statement-store/src/lib.rs @@ -49,6 +49,8 @@ pub use store_api::{ Error, NetworkPriority, Result, StatementSource, StatementStore, SubmitResult, }; +#[cfg(feature = "std")] +mod ecies; pub mod runtime_api; #[cfg(feature = "std")] mod store_api; @@ -61,12 +63,17 @@ mod sr25519 { pub type Public = app_sr25519::Public; } -mod ed25519 { +/// Statement-store specific ed25519 crypto primitives. +pub mod ed25519 { mod app_ed25519 { use sp_application_crypto::{app_crypto, ed25519, key_types::STATEMENT}; app_crypto!(ed25519, STATEMENT); } + /// Statement-store specific ed25519 public key. pub type Public = app_ed25519::Public; + /// Statement-store specific ed25519 key pair. + #[cfg(feature = "std")] + pub type Pair = app_ed25519::Pair; } mod ecdsa { @@ -507,6 +514,28 @@ impl Statement { } output } + + /// Encrypt give data with given key and store both in the statements. + #[cfg(feature = "std")] + pub fn encrypt( + &mut self, + data: &[u8], + key: &sp_core::ed25519::Public, + ) -> core::result::Result<(), ecies::Error> { + let encrypted = ecies::encrypt_ed25519(key, data)?; + self.data = Some(encrypted); + self.decryption_key = Some((*key).into()); + Ok(()) + } + + /// Decrypt data (if any) with the given private key. + #[cfg(feature = "std")] + pub fn decrypt_private( + &self, + key: &sp_core::ed25519::Pair, + ) -> core::result::Result<Option<Vec<u8>>, ecies::Error> { + self.data.as_ref().map(|d| ecies::decrypt_ed25519(key, d)).transpose() + } } #[cfg(test)] @@ -615,4 +644,18 @@ mod test { statement.remove_proof(); assert_eq!(statement.verify_signature(), SignatureVerificationResult::NoSignature); } + + #[test] + fn encrypt_decrypt() { + let mut statement = Statement::new(); + let (pair, _) = sp_core::ed25519::Pair::generate(); + let plain = b"test data".to_vec(); + + //let sr25519_kp = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap(); + statement.encrypt(&plain, &pair.public()).unwrap(); + assert_ne!(plain.as_slice(), statement.data().unwrap().as_slice()); + + let decrypted = statement.decrypt_private(&pair).unwrap(); + assert_eq!(decrypted, Some(plain)); + } } diff --git a/substrate/utils/wasm-builder/src/lib.rs b/substrate/utils/wasm-builder/src/lib.rs index 8405b5a0bda9e8759d56495cae37c1d42ca80e9f..c9011f97be711d9338c3cd572f4c105a910ca581 100644 --- a/substrate/utils/wasm-builder/src/lib.rs +++ b/substrate/utils/wasm-builder/src/lib.rs @@ -222,7 +222,7 @@ fn get_rustup_command() -> Option<CargoCommand> { continue } - let Some(cargo_version) = cmd.version() else { continue; }; + let Some(cargo_version) = cmd.version() else { continue }; versions.push((cargo_version, rustup_version.to_string())); }