From 09d624ff50e7d9014760defa9ac375fc8c2e15e2 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 14 Oct 2022 22:28:22 -0400 Subject: [PATCH 01/15] feat(net): add ecies crate --- Cargo.lock | 121 ++++- Cargo.toml | 1 + crates/net/ecies/Cargo.toml | 34 ++ crates/net/ecies/src/algorithm.rs | 813 ++++++++++++++++++++++++++++++ crates/net/ecies/src/lib.rs | 13 + crates/net/ecies/src/mac.rs | 57 +++ crates/net/ecies/src/proto.rs | 345 +++++++++++++ crates/net/ecies/src/util.rs | 51 ++ 8 files changed, 1433 insertions(+), 2 deletions(-) create mode 100644 crates/net/ecies/Cargo.toml create mode 100644 crates/net/ecies/src/algorithm.rs create mode 100644 crates/net/ecies/src/lib.rs create mode 100644 crates/net/ecies/src/mac.rs create mode 100644 crates/net/ecies/src/proto.rs create mode 100644 crates/net/ecies/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index e3340651125d..67f5a13976a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe0133578c0986e1fe3dfcd4af1cc5b2dd6c3dbf534d69916ce16a2701d40ba" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.7.6" @@ -190,6 +201,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a90ec2df9600c28a01c56c4784c9207a96d2451833aeceb8cc97e4c9548bb78" +dependencies = [ + "generic-array", +] + [[package]] name = "bs58" version = "0.4.0" @@ -302,6 +322,16 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.4.0" @@ -524,6 +554,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "der" version = "0.6.0" @@ -579,6 +618,18 @@ dependencies = [ "signature", ] +[[package]] +name = "educe" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07b7cc9cd8c08d10db74fca3b20949b9b6199725c04a0cce6d543496098fcac" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.8.0" @@ -624,6 +675,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "enum-ordinalize" +version = "3.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2170fc0efee383079a8bdd05d6ea2a184d2a0f07a1c1dcabdb2fd5e9f24bc36c" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "ethabi" version = "17.2.0" @@ -1177,6 +1242,16 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -2029,6 +2104,37 @@ dependencies = [ "thiserror", ] +[[package]] +name = "reth-ecies" +version = "0.1.0" +dependencies = [ + "aes", + "anyhow", + "block-padding", + "byteorder", + "bytes", + "cipher", + "ctr", + "digest 0.10.5", + "educe", + "futures", + "generic-array", + "hex", + "hmac", + "rand", + "reth-primitives", + "reth-rlp", + "secp256k1", + "sha2", + "sha3", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "typenum", +] + [[package]] name = "reth-eth-wire" version = "0.1.0" @@ -2533,6 +2639,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + [[package]] name = "signature" version = "1.6.3" @@ -2749,7 +2864,9 @@ dependencies = [ "memchr", "mio", "num_cpus", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "winapi", @@ -2779,9 +2896,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" dependencies = [ "futures-core", "pin-project-lite", diff --git a/Cargo.toml b/Cargo.toml index 84ba53c2a14c..0bb846476446 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "crates/executor", "crates/interfaces", "crates/net/p2p", + "crates/net/ecies", "crates/net/eth-wire", "crates/net/rpc", "crates/net/rpc-api", diff --git a/crates/net/ecies/Cargo.toml b/crates/net/ecies/Cargo.toml new file mode 100644 index 000000000000..510d6816be8e --- /dev/null +++ b/crates/net/ecies/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "reth-ecies" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +repository = "https://github.com/foundry-rs/reth" +readme = "README.md" + +[dependencies] +reth-rlp = { path = "../../common/rlp", features = ["derive", "ethereum-types"] } +reth-primitives = { path = "../../primitives" } +anyhow = "1.0.65" +byteorder = "1.4.3" +bytes = "1.2.1" +ctr = "0.9.2" +digest = "0.10.5" +educe = "0.4.19" +futures = "0.3.24" +secp256k1 = { version = "0.24.0", features = ["global-context", "rand-std", "recovery"] } +sha2 = "0.10.6" +sha3 = "0.10.5" +thiserror = "1.0.37" +tokio = { version = "1.21.2", features = ["full"] } +tokio-stream = "0.1.11" +tokio-util = { version = "0.7.4", features = ["codec"] } +tracing = "0.1.37" +hex = "0.4.3" +aes = "0.8.1" +hmac = "0.12.1" +block-padding = "0.3.2" +generic-array = "0.14.6" +cipher = { version = "0.4.3", features = ["block-padding"] } +typenum = "1.15.0" +rand = "0.8.5" diff --git a/crates/net/ecies/src/algorithm.rs b/crates/net/ecies/src/algorithm.rs new file mode 100644 index 000000000000..bee5f625cfe7 --- /dev/null +++ b/crates/net/ecies/src/algorithm.rs @@ -0,0 +1,813 @@ +use crate::{ + mac::{HeaderBytes, MAC}, + util::{hmac_sha256, id2pk, pk2id, sha256}, + proto::ECIESError, +}; +use aes::{cipher::StreamCipher, Aes128, Aes256}; +use anyhow::{format_err, Context}; +use byteorder::{BigEndian, ByteOrder, ReadBytesExt}; +use bytes::{BufMut, Bytes, BytesMut}; +use ctr::Ctr64BE; +use digest::{crypto_common::KeyIvInit, Digest}; +use educe::Educe; +use reth_primitives::{H128, H256, H512 as PeerId}; +use reth_rlp::{Encodable, Rlp, RlpEncodable, RlpMaxEncodedLen}; +use rand::{thread_rng, Rng}; +use secp256k1::{ + ecdsa::{RecoverableSignature, RecoveryId}, + PublicKey, SecretKey, SECP256K1, +}; +use sha2::Sha256; +use sha3::Keccak256; +use std::{convert::TryFrom, io}; + +const PROTOCOL_VERSION: usize = 4; + +pub(crate) const MAX_BODY_SIZE: usize = 19_573_451; + +fn ecdh_x(public_key: &PublicKey, secret_key: &SecretKey) -> H256 { + H256::from_slice(&secp256k1::ecdh::shared_secret_point(public_key, secret_key)[..32]) +} + +fn kdf(secret: H256, s1: &[u8], dest: &mut [u8]) { + // SEC/ISO/Shoup specify counter size SHOULD be equivalent + // to size of hash output, however, it also notes that + // the 4 bytes is okay. NIST specifies 4 bytes. + let mut ctr = 1_u32; + let mut written = 0_usize; + while written < dest.len() { + let mut hasher = Sha256::default(); + let ctrs = [ + (ctr >> 24) as u8, + (ctr >> 16) as u8, + (ctr >> 8) as u8, + ctr as u8, + ]; + hasher.update(&ctrs); + hasher.update(secret.as_bytes()); + hasher.update(s1); + let d = hasher.finalize(); + dest[written..(written + 32)].copy_from_slice(&d); + written += 32; + ctr += 1; + } +} + +#[derive(Educe)] +#[educe(Debug)] +pub struct ECIES { + #[educe(Debug(ignore))] + secret_key: SecretKey, + public_key: PublicKey, + remote_public_key: Option, + + pub(crate) remote_id: Option, + + #[educe(Debug(ignore))] + ephemeral_secret_key: SecretKey, + ephemeral_public_key: PublicKey, + ephemeral_shared_secret: Option, + remote_ephemeral_public_key: Option, + + nonce: H256, + remote_nonce: Option, + + #[educe(Debug(ignore))] + ingress_aes: Option>, + #[educe(Debug(ignore))] + egress_aes: Option>, + ingress_mac: Option, + egress_mac: Option, + + init_msg: Option, + remote_init_msg: Option, + + body_size: Option, +} + +fn split_at_mut(arr: &mut [T], mid: usize) -> Result<(&mut [T], &mut [T]), ECIESError> { + if mid > arr.len() { + return Err(ECIESError::Other(format_err!( + "too short: {mid} > {}", + arr.len() + ))); + } + Ok(arr.split_at_mut(mid)) +} + +impl ECIES { + fn new_static_client( + secret_key: SecretKey, + remote_id: PeerId, + nonce: H256, + ephemeral_secret_key: SecretKey, + ) -> Result { + let public_key = PublicKey::from_secret_key(SECP256K1, &secret_key); + let remote_public_key = id2pk(remote_id)?; + let ephemeral_public_key = PublicKey::from_secret_key(SECP256K1, &ephemeral_secret_key); + + Ok(Self { + secret_key, + public_key, + ephemeral_secret_key, + ephemeral_public_key, + nonce, + + remote_public_key: Some(remote_public_key), + remote_ephemeral_public_key: None, + remote_nonce: None, + ephemeral_shared_secret: None, + init_msg: None, + remote_init_msg: None, + + remote_id: Some(remote_id), + + body_size: None, + egress_aes: None, + ingress_aes: None, + egress_mac: None, + ingress_mac: None, + }) + } + + pub fn new_client(secret_key: SecretKey, remote_id: PeerId) -> Result { + let nonce = H256::random(); + let ephemeral_secret_key = SecretKey::new(&mut secp256k1::rand::thread_rng()); + + Self::new_static_client(secret_key, remote_id, nonce, ephemeral_secret_key) + } + + pub fn new_static_server( + secret_key: SecretKey, + nonce: H256, + ephemeral_secret_key: SecretKey, + ) -> Result { + let public_key = PublicKey::from_secret_key(SECP256K1, &secret_key); + let ephemeral_public_key = PublicKey::from_secret_key(SECP256K1, &ephemeral_secret_key); + + Ok(Self { + secret_key, + public_key, + ephemeral_secret_key, + ephemeral_public_key, + nonce, + + remote_public_key: None, + remote_ephemeral_public_key: None, + remote_nonce: None, + ephemeral_shared_secret: None, + init_msg: None, + remote_init_msg: None, + + remote_id: None, + + body_size: None, + egress_aes: None, + ingress_aes: None, + egress_mac: None, + ingress_mac: None, + }) + } + + pub fn new_server(secret_key: SecretKey) -> Result { + let nonce = H256::random(); + let ephemeral_secret_key = SecretKey::new(&mut secp256k1::rand::thread_rng()); + + Self::new_static_server(secret_key, nonce, ephemeral_secret_key) + } + + pub fn remote_id(&self) -> PeerId { + self.remote_id.unwrap() + } + + fn encrypt_message(&self, data: &[u8], out: &mut BytesMut) { + out.reserve(secp256k1::constants::UNCOMPRESSED_PUBLIC_KEY_SIZE + 16 + data.len() + 32); + + let secret_key = SecretKey::new(&mut secp256k1::rand::thread_rng()); + out.extend_from_slice( + &PublicKey::from_secret_key(SECP256K1, &secret_key).serialize_uncompressed(), + ); + + let x = ecdh_x(&self.remote_public_key.unwrap(), &secret_key); + let mut key = [0_u8; 32]; + kdf(x, &[], &mut key); + + let enc_key = H128::from_slice(&key[0..16]); + let mac_key = sha256(&key[16..32]); + + let iv = H128::random(); + let mut encryptor = Ctr64BE::::new(enc_key.as_ref().into(), iv.as_ref().into()); + + let mut encrypted = data.to_vec(); + encryptor.apply_keystream(&mut encrypted); + + let total_size: u16 = u16::try_from(65 + 16 + data.len() + 32).unwrap(); + + let tag = hmac_sha256( + mac_key.as_ref(), + &[iv.as_bytes(), &encrypted], + &total_size.to_be_bytes(), + ); + + out.extend_from_slice(iv.as_bytes()); + out.extend_from_slice(&encrypted); + out.extend_from_slice(tag.as_ref()); + } + + fn decrypt_message<'a>(&self, data: &'a mut [u8]) -> Result<&'a mut [u8], ECIESError> { + let (auth_data, encrypted) = split_at_mut(data, 2)?; + let (pubkey_bytes, encrypted) = split_at_mut(encrypted, 65)?; + let public_key = PublicKey::from_slice(pubkey_bytes) + .with_context(|| format!("bad public key {}", hex::encode(pubkey_bytes)))?; + let (data_iv, tag_bytes) = split_at_mut(encrypted, encrypted.len() - 32)?; + let (iv, encrypted_data) = split_at_mut(data_iv, 16)?; + let tag = H256::from_slice(tag_bytes); + + let x = ecdh_x(&public_key, &self.secret_key); + let mut key = [0_u8; 32]; + kdf(x, &[], &mut key); + let enc_key = H128::from_slice(&key[0..16]); + let mac_key = sha256(&key[16..32]); + + let check_tag = hmac_sha256(mac_key.as_ref(), &[iv, encrypted_data], auth_data); + if check_tag != tag { + return Err(ECIESError::TagCheckFailed); + } + + let decrypted_data = encrypted_data; + + let mut decryptor = Ctr64BE::::new(enc_key.as_ref().into(), (*iv).into()); + decryptor.apply_keystream(decrypted_data); + + Ok(decrypted_data) + } + + fn create_auth_unencrypted(&self) -> BytesMut { + let x = ecdh_x(&self.remote_public_key.unwrap(), &self.secret_key); + let msg = x ^ self.nonce; + let (rec_id, sig) = SECP256K1 + .sign_ecdsa_recoverable( + &secp256k1::Message::from_slice(msg.as_bytes()).unwrap(), + &self.ephemeral_secret_key, + ) + .serialize_compact(); + + let mut sig_bytes = [0_u8; 65]; + sig_bytes[..64].copy_from_slice(&sig); + sig_bytes[64] = rec_id.to_i32() as u8; + + let id = pk2id(&self.public_key); + + #[derive(RlpEncodable)] + struct S<'a> { + sig_bytes: &'a [u8; 65], + id: &'a PeerId, + nonce: &'a H256, + protocol_version: u8, + } + + let mut out = BytesMut::new(); + S { + sig_bytes: &sig_bytes, + id: &id, + nonce: &self.nonce, + protocol_version: PROTOCOL_VERSION as u8, + } + .encode(&mut out); + + out.resize(out.len() + thread_rng().gen_range(100..=300), 0); + out + } + + #[cfg(test)] + fn create_auth(&mut self) -> BytesMut { + let mut buf = BytesMut::new(); + self.write_auth(&mut buf); + buf + } + + pub fn write_auth(&mut self, buf: &mut BytesMut) { + let unencrypted = self.create_auth_unencrypted(); + + let mut out = buf.split_off(buf.len()); + out.put_u16(0); + + let mut encrypted = out.split_off(out.len()); + self.encrypt_message(&unencrypted, &mut encrypted); + + let len_bytes = u16::try_from(encrypted.len()).unwrap().to_be_bytes(); + out[..len_bytes.len()].copy_from_slice(&len_bytes); + + out.unsplit(encrypted); + + self.init_msg = Some(Bytes::copy_from_slice(&out)); + + buf.unsplit(out); + } + + fn parse_auth_unencrypted(&mut self, data: &[u8]) -> Result<(), ECIESError> { + let mut data = Rlp::new(data)?; + + let sigdata = data + .get_next::<[u8; 65]>()? + .ok_or(ECIESError::InvalidAuthData)?; + let signature = RecoverableSignature::from_compact( + &sigdata[0..64], + RecoveryId::from_i32(sigdata[64] as i32)?, + )?; + let remote_id = data.get_next()?.ok_or(ECIESError::InvalidAuthData)?; + self.remote_id = Some(remote_id); + self.remote_public_key = Some(id2pk(remote_id).context("failed to parse peer id")?); + self.remote_nonce = Some(data.get_next()?.ok_or(ECIESError::InvalidAuthData)?); + + let x = ecdh_x(&self.remote_public_key.unwrap(), &self.secret_key); + self.remote_ephemeral_public_key = Some(SECP256K1.recover_ecdsa( + &secp256k1::Message::from_slice((x ^ self.remote_nonce.unwrap()).as_ref()).unwrap(), + &signature, + )?); + self.ephemeral_shared_secret = Some(ecdh_x( + &self.remote_ephemeral_public_key.unwrap(), + &self.ephemeral_secret_key, + )); + + Ok(()) + } + + pub fn read_auth(&mut self, data: &mut [u8]) -> Result<(), ECIESError> { + self.remote_init_msg = Some(Bytes::copy_from_slice(data)); + let unencrypted = self.decrypt_message(data)?; + self.parse_auth_unencrypted(unencrypted) + } + + fn create_ack_unencrypted(&self) -> impl AsRef<[u8]> { + #[derive(RlpEncodable, RlpMaxEncodedLen)] + struct S { + id: PeerId, + nonce: H256, + protocol_version: u8, + } + + reth_rlp::encode_fixed_size(&S { + id: pk2id(&self.ephemeral_public_key), + nonce: self.nonce, + protocol_version: PROTOCOL_VERSION as u8, + }) + } + + #[cfg(test)] + pub fn create_ack(&mut self) -> BytesMut { + let mut buf = BytesMut::new(); + self.write_ack(&mut buf); + buf + } + + pub fn write_ack(&mut self, out: &mut BytesMut) { + let unencrypted = self.create_ack_unencrypted(); + + let mut buf = out.split_off(out.len()); + + // reserve space for length + buf.put_u16(0); + + // encrypt and append + let mut encrypted = buf.split_off(buf.len()); + self.encrypt_message(unencrypted.as_ref(), &mut encrypted); + let len_bytes = u16::try_from(encrypted.len()).unwrap().to_be_bytes(); + buf.unsplit(encrypted); + + // write length + buf[..len_bytes.len()].copy_from_slice(&len_bytes[..]); + + self.init_msg = Some(buf.clone().freeze()); + out.unsplit(buf); + + self.setup_frame(true); + } + + fn parse_ack_unencrypted(&mut self, data: &[u8]) -> Result<(), ECIESError> { + let mut data = Rlp::new(data)?; + self.remote_ephemeral_public_key = + Some(id2pk(data.get_next()?.ok_or(ECIESError::InvalidAckData)?)?); + self.remote_nonce = Some(data.get_next()?.ok_or(ECIESError::InvalidAckData)?); + + self.ephemeral_shared_secret = Some(ecdh_x( + &self.remote_ephemeral_public_key.unwrap(), + &self.ephemeral_secret_key, + )); + Ok(()) + } + + pub fn read_ack(&mut self, data: &mut [u8]) -> Result<(), ECIESError> { + self.remote_init_msg = Some(Bytes::copy_from_slice(data)); + let unencrypted = self.decrypt_message(data)?; + self.parse_ack_unencrypted(unencrypted)?; + self.setup_frame(false); + Ok(()) + } + + fn setup_frame(&mut self, incoming: bool) { + let mut hasher = Keccak256::new(); + for el in &if incoming { + [self.nonce, self.remote_nonce.unwrap()] + } else { + [self.remote_nonce.unwrap(), self.nonce] + } { + hasher.update(el); + } + let h_nonce = H256::from(hasher.finalize().as_ref()); + + let iv = H128::default(); + let shared_secret: H256 = { + let mut hasher = Keccak256::new(); + hasher.update(self.ephemeral_shared_secret.unwrap().as_ref()); + hasher.update(h_nonce.as_ref()); + H256::from(hasher.finalize().as_ref()) + }; + + let aes_secret: H256 = { + let mut hasher = Keccak256::new(); + hasher.update(self.ephemeral_shared_secret.unwrap().as_ref()); + hasher.update(shared_secret.as_ref()); + H256::from(hasher.finalize().as_ref()) + }; + self.ingress_aes = Some(Ctr64BE::::new( + aes_secret.as_ref().into(), + iv.as_ref().into(), + )); + self.egress_aes = Some(Ctr64BE::::new( + aes_secret.as_ref().into(), + iv.as_ref().into(), + )); + + let mac_secret: H256 = { + let mut hasher = Keccak256::new(); + hasher.update(self.ephemeral_shared_secret.unwrap().as_ref()); + hasher.update(aes_secret.as_ref()); + H256::from(hasher.finalize().as_ref()) + }; + self.ingress_mac = Some(MAC::new(mac_secret)); + self.ingress_mac + .as_mut() + .unwrap() + .update((mac_secret ^ self.nonce).as_ref()); + self.ingress_mac + .as_mut() + .unwrap() + .update(self.remote_init_msg.as_ref().unwrap()); + self.egress_mac = Some(MAC::new(mac_secret)); + self.egress_mac + .as_mut() + .unwrap() + .update((mac_secret ^ self.remote_nonce.unwrap()).as_ref()); + self.egress_mac + .as_mut() + .unwrap() + .update(self.init_msg.as_ref().unwrap()); + } + + #[cfg(test)] + fn create_header(&mut self, size: usize) -> BytesMut { + let mut out = BytesMut::new(); + self.write_header(&mut out, size); + out + } + + pub fn write_header(&mut self, out: &mut BytesMut, size: usize) { + let mut buf = [0; 8]; + BigEndian::write_uint(&mut buf, size as u64, 3); + let mut header = [0_u8; 16]; + header[0..3].copy_from_slice(&buf[0..3]); + header[3..6].copy_from_slice(&[194, 128, 128]); + + let mut header = HeaderBytes::from(header); + self.egress_aes + .as_mut() + .unwrap() + .apply_keystream(&mut header); + self.egress_mac.as_mut().unwrap().update_header(&header); + let tag = self.egress_mac.as_mut().unwrap().digest(); + + out.reserve(ECIES::header_len()); + out.extend_from_slice(&header); + out.extend_from_slice(tag.as_bytes()); + } + + pub fn read_header(&mut self, data: &mut [u8]) -> Result { + let (header_bytes, mac_bytes) = split_at_mut(data, 16)?; + let header = HeaderBytes::from_mut_slice(header_bytes); + let mac = H128::from_slice(&mac_bytes[..16]); + + self.ingress_mac.as_mut().unwrap().update_header(header); + let check_mac = self.ingress_mac.as_mut().unwrap().digest(); + if check_mac != mac { + return Err(ECIESError::TagCheckFailed); + } + + self.ingress_aes.as_mut().unwrap().apply_keystream(header); + if header.as_slice().len() < 3 { + return Err(ECIESError::InvalidHeader); + } + let body_size = usize::try_from(header.as_slice().read_uint::(3)?) + .context("excessive body len")?; + + if body_size > MAX_BODY_SIZE { + return Err(ECIESError::IO(io::Error::new( + io::ErrorKind::InvalidInput, + format!( + "body size ({}) exceeds limit ({} bytes)", + body_size, MAX_BODY_SIZE + ), + ))); + } + + self.body_size = Some(body_size); + + Ok(self.body_size.unwrap()) + } + + pub const fn header_len() -> usize { + 32 + } + + pub fn body_len(&self) -> usize { + let len = self.body_size.unwrap(); + (if len % 16 == 0 { + len + } else { + (len / 16 + 1) * 16 + }) + 16 + } + + #[cfg(test)] + fn create_body(&mut self, data: &[u8]) -> BytesMut { + let mut out = BytesMut::new(); + self.write_body(&mut out, data); + out + } + + pub fn write_body(&mut self, out: &mut BytesMut, data: &[u8]) { + let len = if data.len() % 16 == 0 { + data.len() + } else { + (data.len() / 16 + 1) * 16 + }; + let old_len = out.len(); + out.resize(old_len + len, 0); + + let encrypted = &mut out[old_len..old_len + len]; + encrypted[..data.len()].copy_from_slice(data); + + self.egress_aes.as_mut().unwrap().apply_keystream(encrypted); + self.egress_mac.as_mut().unwrap().update_body(encrypted); + let tag = self.egress_mac.as_mut().unwrap().digest(); + + out.extend_from_slice(tag.as_bytes()); + } + + pub fn read_body<'a>(&mut self, data: &'a mut [u8]) -> Result<&'a mut [u8], ECIESError> { + let (body, mac_bytes) = split_at_mut(data, data.len() - 16)?; + let mac = H128::from_slice(mac_bytes); + self.ingress_mac.as_mut().unwrap().update_body(body); + let check_mac = self.ingress_mac.as_mut().unwrap().digest(); + if check_mac != mac { + return Err(ECIESError::TagCheckFailed); + } + + let size = self.body_size.unwrap(); + self.body_size = None; + let ret = body; + self.ingress_aes.as_mut().unwrap().apply_keystream(ret); + Ok(split_at_mut(ret, size)?.0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + + #[test] + fn ecdh() { + let our_secret_key = SecretKey::from_slice(&hex!( + "202a36e24c3eb39513335ec99a7619bad0e7dc68d69401b016253c7d26dc92f8" + )) + .unwrap(); + let remote_public_key = id2pk(hex!("d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666").into()).unwrap(); + + assert_eq!( + ecdh_x(&remote_public_key, &our_secret_key), + hex!("821ce7e01ea11b111a52b2dafae8a3031a372d83bdf1a78109fa0783c2b9d5d3").into() + ) + } + + #[test] + fn communicate() { + let server_secret_key = SecretKey::new(&mut secp256k1::rand::thread_rng()); + let server_public_key = PublicKey::from_secret_key(SECP256K1, &server_secret_key); + let client_secret_key = SecretKey::new(&mut secp256k1::rand::thread_rng()); + + let mut server_ecies = ECIES::new_server(server_secret_key).unwrap(); + let mut client_ecies = + ECIES::new_client(client_secret_key, pk2id(&server_public_key)).unwrap(); + + // Handshake + let mut auth = client_ecies.create_auth(); + server_ecies.read_auth(&mut auth).unwrap(); + let mut ack = server_ecies.create_ack(); + client_ecies.read_ack(&mut ack).unwrap(); + + let server_to_client_data = [0_u8, 1_u8, 2_u8, 3_u8, 4_u8]; + let client_to_server_data = [5_u8, 6_u8, 7_u8]; + + // Test server to client 1 + let mut header = server_ecies.create_header(server_to_client_data.len()); + assert_eq!(header.len(), ECIES::header_len()); + client_ecies.read_header(&mut *header).unwrap(); + let mut body = server_ecies.create_body(&server_to_client_data); + assert_eq!(body.len(), client_ecies.body_len()); + let ret = client_ecies.read_body(&mut *body).unwrap(); + assert_eq!(ret, server_to_client_data); + + // Test client to server 1 + server_ecies + .read_header(&mut *client_ecies.create_header(client_to_server_data.len())) + .unwrap(); + let mut b = client_ecies.create_body(&client_to_server_data); + let ret = server_ecies.read_body(&mut b).unwrap(); + assert_eq!(ret, client_to_server_data); + + // Test server to client 2 + client_ecies + .read_header(&mut *server_ecies.create_header(server_to_client_data.len())) + .unwrap(); + let mut b = server_ecies.create_body(&server_to_client_data); + let ret = client_ecies.read_body(&mut b).unwrap(); + assert_eq!(ret, server_to_client_data); + + // Test server to client 3 + client_ecies + .read_header(&mut *server_ecies.create_header(server_to_client_data.len())) + .unwrap(); + let mut b = server_ecies.create_body(&server_to_client_data); + let ret = client_ecies.read_body(&mut b).unwrap(); + assert_eq!(ret, server_to_client_data); + + // Test client to server 2 + server_ecies + .read_header(&mut *client_ecies.create_header(client_to_server_data.len())) + .unwrap(); + let mut b = client_ecies.create_body(&client_to_server_data); + let ret = server_ecies.read_body(&mut b).unwrap(); + assert_eq!(ret, client_to_server_data); + + // Test client to server 3 + server_ecies + .read_header(&mut *client_ecies.create_header(client_to_server_data.len())) + .unwrap(); + let mut b = client_ecies.create_body(&client_to_server_data); + let ret = server_ecies.read_body(&mut b).unwrap(); + assert_eq!(ret, client_to_server_data); + } + + fn eip8_test_server_key() -> SecretKey { + SecretKey::from_slice(&hex!( + "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291" + )) + .unwrap() + } + + fn eip8_test_client() -> ECIES { + let client_static_key = SecretKey::from_slice(&hex!( + "49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee" + )) + .unwrap(); + + let client_ephemeral_key = SecretKey::from_slice(&hex!( + "869d6ecf5211f1cc60418a13b9d870b22959d0c16f02bec714c960dd2298a32d" + )) + .unwrap(); + + let client_nonce = H256(hex!( + "7e968bba13b6c50e2c4cd7f241cc0d64d1ac25c7f5952df231ac6a2bda8ee5d6" + )); + + let server_id = pk2id(&PublicKey::from_secret_key( + SECP256K1, + &eip8_test_server_key(), + )); + + ECIES::new_static_client( + client_static_key, + server_id, + client_nonce, + client_ephemeral_key, + ) + .unwrap() + } + + fn eip8_test_server() -> ECIES { + let server_ephemeral_key = SecretKey::from_slice(&hex!( + "e238eb8e04fee6511ab04c6dd3c89ce097b11f25d584863ac2b6d5b35b1847e4" + )) + .unwrap(); + + let server_nonce = H256(hex!( + "559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd" + )); + + ECIES::new_static_server(eip8_test_server_key(), server_nonce, server_ephemeral_key) + .unwrap() + } + + #[test] + /// Test vectors from https://eips.ethereum.org/EIPS/eip-8 + fn eip8_test() { + // EIP-8 format with version 4 and no additional list elements + let auth2 = hex!( + " + 01b304ab7578555167be8154d5cc456f567d5ba302662433674222360f08d5f1534499d3678b513b + 0fca474f3a514b18e75683032eb63fccb16c156dc6eb2c0b1593f0d84ac74f6e475f1b8d56116b84 + 9634a8c458705bf83a626ea0384d4d7341aae591fae42ce6bd5c850bfe0b999a694a49bbbaf3ef6c + da61110601d3b4c02ab6c30437257a6e0117792631a4b47c1d52fc0f8f89caadeb7d02770bf999cc + 147d2df3b62e1ffb2c9d8c125a3984865356266bca11ce7d3a688663a51d82defaa8aad69da39ab6 + d5470e81ec5f2a7a47fb865ff7cca21516f9299a07b1bc63ba56c7a1a892112841ca44b6e0034dee + 70c9adabc15d76a54f443593fafdc3b27af8059703f88928e199cb122362a4b35f62386da7caad09 + c001edaeb5f8a06d2b26fb6cb93c52a9fca51853b68193916982358fe1e5369e249875bb8d0d0ec3 + 6f917bc5e1eafd5896d46bd61ff23f1a863a8a8dcd54c7b109b771c8e61ec9c8908c733c0263440e + 2aa067241aaa433f0bb053c7b31a838504b148f570c0ad62837129e547678c5190341e4f1693956c + 3bf7678318e2d5b5340c9e488eefea198576344afbdf66db5f51204a6961a63ce072c8926c + " + ); + + // EIP-8 format with version 56 and 3 additional list elements (sent from A to B) + let auth3 = hex!( + " + 01b8044c6c312173685d1edd268aa95e1d495474c6959bcdd10067ba4c9013df9e40ff45f5bfd6f7 + 2471f93a91b493f8e00abc4b80f682973de715d77ba3a005a242eb859f9a211d93a347fa64b597bf + 280a6b88e26299cf263b01b8dfdb712278464fd1c25840b995e84d367d743f66c0e54a586725b7bb + f12acca27170ae3283c1073adda4b6d79f27656993aefccf16e0d0409fe07db2dc398a1b7e8ee93b + cd181485fd332f381d6a050fba4c7641a5112ac1b0b61168d20f01b479e19adf7fdbfa0905f63352 + bfc7e23cf3357657455119d879c78d3cf8c8c06375f3f7d4861aa02a122467e069acaf513025ff19 + 6641f6d2810ce493f51bee9c966b15c5043505350392b57645385a18c78f14669cc4d960446c1757 + 1b7c5d725021babbcd786957f3d17089c084907bda22c2b2675b4378b114c601d858802a55345a15 + 116bc61da4193996187ed70d16730e9ae6b3bb8787ebcaea1871d850997ddc08b4f4ea668fbf3740 + 7ac044b55be0908ecb94d4ed172ece66fd31bfdadf2b97a8bc690163ee11f5b575a4b44e36e2bfb2 + f0fce91676fd64c7773bac6a003f481fddd0bae0a1f31aa27504e2a533af4cef3b623f4791b2cca6 + d490 + " + ); + + // EIP-8 format with version 4 and no additional list elements (sent from B to A) + let ack2 = hex!( + " + 01ea0451958701280a56482929d3b0757da8f7fbe5286784beead59d95089c217c9b917788989470 + b0e330cc6e4fb383c0340ed85fab836ec9fb8a49672712aeabbdfd1e837c1ff4cace34311cd7f4de + 05d59279e3524ab26ef753a0095637ac88f2b499b9914b5f64e143eae548a1066e14cd2f4bd7f814 + c4652f11b254f8a2d0191e2f5546fae6055694aed14d906df79ad3b407d94692694e259191cde171 + ad542fc588fa2b7333313d82a9f887332f1dfc36cea03f831cb9a23fea05b33deb999e85489e645f + 6aab1872475d488d7bd6c7c120caf28dbfc5d6833888155ed69d34dbdc39c1f299be1057810f34fb + e754d021bfca14dc989753d61c413d261934e1a9c67ee060a25eefb54e81a4d14baff922180c395d + 3f998d70f46f6b58306f969627ae364497e73fc27f6d17ae45a413d322cb8814276be6ddd13b885b + 201b943213656cde498fa0e9ddc8e0b8f8a53824fbd82254f3e2c17e8eaea009c38b4aa0a3f306e8 + 797db43c25d68e86f262e564086f59a2fc60511c42abfb3057c247a8a8fe4fb3ccbadde17514b7ac + 8000cdb6a912778426260c47f38919a91f25f4b5ffb455d6aaaf150f7e5529c100ce62d6d92826a7 + 1778d809bdf60232ae21ce8a437eca8223f45ac37f6487452ce626f549b3b5fdee26afd2072e4bc7 + 5833c2464c805246155289f4 + " + ); + + // EIP-8 format with version 57 and 3 additional list elements (sent from B to A) + let ack3 = hex!( + " + 01f004076e58aae772bb101ab1a8e64e01ee96e64857ce82b1113817c6cdd52c09d26f7b90981cd7 + ae835aeac72e1573b8a0225dd56d157a010846d888dac7464baf53f2ad4e3d584531fa203658fab0 + 3a06c9fd5e35737e417bc28c1cbf5e5dfc666de7090f69c3b29754725f84f75382891c561040ea1d + dc0d8f381ed1b9d0d4ad2a0ec021421d847820d6fa0ba66eaf58175f1b235e851c7e2124069fbc20 + 2888ddb3ac4d56bcbd1b9b7eab59e78f2e2d400905050f4a92dec1c4bdf797b3fc9b2f8e84a482f3 + d800386186712dae00d5c386ec9387a5e9c9a1aca5a573ca91082c7d68421f388e79127a5177d4f8 + 590237364fd348c9611fa39f78dcdceee3f390f07991b7b47e1daa3ebcb6ccc9607811cb17ce51f1 + c8c2c5098dbdd28fca547b3f58c01a424ac05f869f49c6a34672ea2cbbc558428aa1fe48bbfd6115 + 8b1b735a65d99f21e70dbc020bfdface9f724a0d1fb5895db971cc81aa7608baa0920abb0a565c9c + 436e2fd13323428296c86385f2384e408a31e104670df0791d93e743a3a5194ee6b076fb6323ca59 + 3011b7348c16cf58f66b9633906ba54a2ee803187344b394f75dd2e663a57b956cb830dd7a908d4f + 39a2336a61ef9fda549180d4ccde21514d117b6c6fd07a9102b5efe710a32af4eeacae2cb3b1dec0 + 35b9593b48b9d3ca4c13d245d5f04169b0b1 + " + ); + + eip8_test_server().read_auth(&mut auth2.to_vec()).unwrap(); + eip8_test_server().read_auth(&mut auth3.to_vec()).unwrap(); + + let mut test_client = eip8_test_client(); + let mut test_server = eip8_test_server(); + + test_server + .read_auth(&mut test_client.create_auth()) + .unwrap(); + + test_client.read_ack(&mut test_server.create_ack()).unwrap(); + + test_client.read_ack(&mut ack2.to_vec()).unwrap(); + test_client.read_ack(&mut ack3.to_vec()).unwrap(); + } +} diff --git a/crates/net/ecies/src/lib.rs b/crates/net/ecies/src/lib.rs new file mode 100644 index 000000000000..032821507643 --- /dev/null +++ b/crates/net/ecies/src/lib.rs @@ -0,0 +1,13 @@ +#![warn(missing_docs, unreachable_pub)] +#![deny(unused_must_use, rust_2018_idioms)] +#![doc(test( + no_crate_inject, + attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) +))] + +//! RLPx ECIES framed transport protocol. + +pub mod algorithm; +pub mod mac; +pub mod proto; +pub mod util; diff --git a/crates/net/ecies/src/mac.rs b/crates/net/ecies/src/mac.rs new file mode 100644 index 000000000000..4927860bc7e7 --- /dev/null +++ b/crates/net/ecies/src/mac.rs @@ -0,0 +1,57 @@ +use aes::Aes256Enc; +use block_padding::NoPadding; +use cipher::BlockEncrypt; +use digest::KeyInit; +use reth_primitives::{H128, H256}; +use generic_array::GenericArray; +use sha3::{Digest, Keccak256}; +use typenum::U16; + +pub type HeaderBytes = GenericArray; + +#[derive(Debug)] +pub struct MAC { + secret: H256, + hasher: Keccak256, +} + +impl MAC { + pub fn new(secret: H256) -> Self { + Self { + secret, + hasher: Keccak256::new(), + } + } + + pub fn update(&mut self, data: &[u8]) { + self.hasher.update(data) + } + + pub fn update_header(&mut self, data: &HeaderBytes) { + let aes = Aes256Enc::new_from_slice(self.secret.as_ref()).unwrap(); + let mut encrypted = self.digest().to_fixed_bytes(); + aes.encrypt_padded::(&mut encrypted, H128::len_bytes()) + .unwrap(); + for i in 0..data.len() { + encrypted[i] ^= data[i]; + } + self.hasher.update(encrypted); + } + + pub fn update_body(&mut self, data: &[u8]) { + self.hasher.update(data); + let prev = self.digest(); + let aes = Aes256Enc::new_from_slice(self.secret.as_ref()).unwrap(); + let mut encrypted = self.digest().to_fixed_bytes(); + aes.encrypt_padded::(&mut encrypted, H128::len_bytes()) + .unwrap(); + for i in 0..16 { + encrypted[i] ^= prev[i]; + } + self.hasher.update(encrypted); + } + + pub fn digest(&self) -> H128 { + H128::from_slice(&self.hasher.clone().finalize()[0..16]) + } +} diff --git a/crates/net/ecies/src/proto.rs b/crates/net/ecies/src/proto.rs new file mode 100644 index 000000000000..1d56cdaffa49 --- /dev/null +++ b/crates/net/ecies/src/proto.rs @@ -0,0 +1,345 @@ +use super::algorithm::{ECIES, MAX_BODY_SIZE}; +use reth_primitives::H512 as PeerId; +use anyhow::{bail, Context as _}; +use bytes::{Bytes, BytesMut}; +use futures::{ready, Sink, SinkExt}; +use secp256k1::SecretKey; +use std::{ + fmt::Debug, + io, + pin::Pin, + task::{Context, Poll}, net::SocketAddr, +}; +use tokio::{io::{AsyncRead, AsyncWrite}, net::TcpStream}; +use tokio_stream::{Stream, StreamExt}; +use tokio_util::codec::{Decoder, Encoder, Framed}; +use tracing::{debug, instrument, trace}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum ECIESError { + #[error("IO error")] + IO(#[from] io::Error), + #[error("tag check failure")] + TagCheckFailed, + #[error("invalid auth data")] + InvalidAuthData, + #[error("invalid ack data")] + InvalidAckData, + #[error("invalid body data")] + InvalidHeader, + #[error("other")] + Other(#[from] anyhow::Error), +} + +impl From for io::Error { + fn from(error: ECIESError) -> Self { + Self::new(io::ErrorKind::Other, format!("ECIES error: {:?}", error)) + } +} + +impl From for ECIESError { + fn from(error: secp256k1::Error) -> Self { + Self::Other(error.into()) + } +} + +impl From for ECIESError { + fn from(error: reth_rlp::DecodeError) -> Self { + Self::Other(error.into()) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +/// Current ECIES state of a connection +pub enum ECIESState { + Auth, + Ack, + Header, + Body, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +/// Raw egress values for an ECIES protocol +pub enum EgressECIESValue { + Auth, + Ack, + Message(Bytes), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +/// Raw ingress values for an ECIES protocol +pub enum IngressECIESValue { + AuthReceive(PeerId), + Ack, + Message(Bytes), +} + +/// Tokio codec for ECIES +#[derive(Debug)] +pub struct ECIESCodec { + ecies: ECIES, + state: ECIESState, +} + +impl ECIESCodec { + /// Create a new server codec using the given secret key + pub fn new_server(secret_key: SecretKey) -> Result { + Ok(Self { + ecies: ECIES::new_server(secret_key)?, + state: ECIESState::Auth, + }) + } + + /// Create a new client codec using the given secret key and the server's public id + pub fn new_client(secret_key: SecretKey, remote_id: PeerId) -> Result { + Ok(Self { + ecies: ECIES::new_client(secret_key, remote_id)?, + state: ECIESState::Auth, + }) + } +} + +impl Decoder for ECIESCodec { + type Item = IngressECIESValue; + type Error = io::Error; + + #[instrument(level = "trace", skip_all, fields(peer=&*format!("{:?}", self.ecies.remote_id.map(|s| s.to_string())), state=&*format!("{:?}", self.state)))] + fn decode(&mut self, buf: &mut BytesMut) -> Result, Self::Error> { + loop { + match self.state { + ECIESState::Auth => { + trace!("parsing auth"); + if buf.len() < 2 { + return Ok(None); + } + + let payload_size = u16::from_be_bytes([buf[0], buf[1]]) as usize; + let total_size = payload_size + 2; + + if buf.len() < total_size { + trace!("current len {}, need {}", buf.len(), total_size); + return Ok(None); + } + + self.ecies.read_auth(&mut *buf.split_to(total_size))?; + + self.state = ECIESState::Header; + return Ok(Some(IngressECIESValue::AuthReceive(self.ecies.remote_id()))); + } + ECIESState::Ack => { + trace!("parsing ack with len {}", buf.len()); + if buf.len() < 2 { + return Ok(None); + } + + let payload_size = u16::from_be_bytes([buf[0], buf[1]]) as usize; + let total_size = payload_size + 2; + + if buf.len() < total_size { + trace!("current len {}, need {}", buf.len(), total_size); + return Ok(None); + } + + self.ecies.read_ack(&mut *buf.split_to(total_size))?; + + self.state = ECIESState::Header; + return Ok(Some(IngressECIESValue::Ack)); + } + ECIESState::Header => { + if buf.len() < ECIES::header_len() { + trace!("current len {}, need {}", buf.len(), ECIES::header_len()); + return Ok(None); + } + + self.ecies + .read_header(&mut *buf.split_to(ECIES::header_len()))?; + + self.state = ECIESState::Body; + } + ECIESState::Body => { + if buf.len() < self.ecies.body_len() { + return Ok(None); + } + + let mut data = buf.split_to(self.ecies.body_len()); + let ret = Bytes::copy_from_slice(self.ecies.read_body(&mut *data)?); + + self.state = ECIESState::Header; + return Ok(Some(IngressECIESValue::Message(ret))); + } + } + } + } +} + +impl Encoder for ECIESCodec { + type Error = io::Error; + + #[instrument(level = "trace", skip(self, buf), fields(peer=&*format!("{:?}", self.ecies.remote_id.map(|s| s.to_string())), state=&*format!("{:?}", self.state)))] + fn encode(&mut self, item: EgressECIESValue, buf: &mut BytesMut) -> Result<(), Self::Error> { + match item { + EgressECIESValue::Auth => { + self.state = ECIESState::Ack; + self.ecies.write_auth(buf); + Ok(()) + } + EgressECIESValue::Ack => { + self.state = ECIESState::Header; + self.ecies.write_ack(buf); + Ok(()) + } + EgressECIESValue::Message(data) => { + if data.len() > MAX_BODY_SIZE { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!( + "body size ({}) exceeds limit ({} bytes)", + data.len(), + MAX_BODY_SIZE + ), + )); + } + + self.ecies.write_header(buf, data.len()); + self.ecies.write_body(buf, &data); + Ok(()) + } + } + } +} + +/// `ECIES` stream over TCP exchanging raw bytes +#[derive(Debug)] +pub struct ECIESStream { + stream: Framed, + remote_id: PeerId, +} + + +// This trait is just for instrumenting the stream with a socket addr +pub trait HasRemoteAddr { + fn remote_addr(&self) -> Option; +} + +impl HasRemoteAddr for TcpStream { + fn remote_addr(&self) -> Option { + self.peer_addr().ok() + } +} + +impl ECIESStream +where + Io: AsyncRead + AsyncWrite + Unpin + HasRemoteAddr, +{ + /// Connect to an `ECIES` server + #[instrument(skip(transport, secret_key), fields(peer=&*format!("{:?}", transport.remote_addr())))] + pub async fn connect( + transport: Io, + secret_key: SecretKey, + remote_id: PeerId, + ) -> anyhow::Result { + let ecies = ECIESCodec::new_client(secret_key, remote_id) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "invalid handshake"))?; + + let mut transport = ecies.framed(transport); + + trace!("sending ecies auth ..."); + transport.send(EgressECIESValue::Auth).await?; + + trace!("waiting for ecies ack ..."); + let ack = transport.try_next().await?; + + trace!("parsing ecies ack ..."); + if matches!(ack, Some(IngressECIESValue::Ack)) { + Ok(Self { + stream: transport, + remote_id, + }) + } else { + bail!("invalid handshake: expected ack, got {:?} instead", ack) + } + } + + /// Listen on a just connected ECIES client + #[instrument(skip_all, fields(peer=&*format!("{:?}", transport.remote_addr())))] + pub async fn incoming(transport: Io, secret_key: SecretKey) -> anyhow::Result { + let ecies = ECIESCodec::new_server(secret_key).context("handshake error")?; + + debug!("incoming ecies stream ..."); + let mut transport = ecies.framed(transport); + let ack = transport.try_next().await?; + + debug!("receiving ecies auth"); + let remote_id = match ack { + Some(IngressECIESValue::AuthReceive(remote_id)) => remote_id, + other => { + debug!("expected auth, got {:?} instead", other); + bail!("invalid handshake"); + } + }; + + debug!("sending ecies ack ..."); + transport + .send(EgressECIESValue::Ack) + .await + .context("failed to send ECIES auth")?; + + Ok(Self { + stream: transport, + remote_id, + }) + } + + /// Get the remote id + pub fn remote_id(&self) -> PeerId { + self.remote_id + } +} + +impl Stream for ECIESStream +where + Io: AsyncRead + Unpin, +{ + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match ready!(Pin::new(&mut self.get_mut().stream).poll_next(cx)) { + Some(Ok(IngressECIESValue::Message(body))) => Poll::Ready(Some(Ok(body))), + Some(other) => Poll::Ready(Some(Err(io::Error::new( + io::ErrorKind::Other, + format!( + "ECIES stream protocol error: expected message, received {:?}", + other + ), + )))), + None => Poll::Ready(None), + } + } +} + +impl Sink for ECIESStream +where + Io: AsyncWrite + Unpin, +{ + type Error = io::Error; + + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.get_mut().stream).poll_ready(cx) + } + + fn start_send(self: Pin<&mut Self>, item: Bytes) -> Result<(), Self::Error> { + let this = self.get_mut(); + Pin::new(&mut this.stream).start_send(EgressECIESValue::Message(item))?; + + Ok(()) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.get_mut().stream).poll_flush(cx) + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.get_mut().stream).poll_close(cx) + } +} diff --git a/crates/net/ecies/src/util.rs b/crates/net/ecies/src/util.rs new file mode 100644 index 000000000000..27747f05a55a --- /dev/null +++ b/crates/net/ecies/src/util.rs @@ -0,0 +1,51 @@ +use reth_primitives::{H256, H512 as PeerId}; +use hmac::{Hmac, Mac}; +use secp256k1::PublicKey; +use sha2::Sha256; +use sha3::{Digest, Keccak256}; +use std::fmt::{self, Formatter}; + +pub fn keccak256(data: &[u8]) -> H256 { + H256::from(Keccak256::digest(data).as_ref()) +} + +pub fn sha256(data: &[u8]) -> H256 { + H256::from(Sha256::digest(data).as_ref()) +} + +pub fn hmac_sha256(key: &[u8], input: &[&[u8]], auth_data: &[u8]) -> H256 { + let mut hmac = Hmac::::new_from_slice(key).unwrap(); + for input in input { + hmac.update(input); + } + hmac.update(auth_data); + H256::from_slice(&*hmac.finalize().into_bytes()) +} + +pub fn pk2id(pk: &PublicKey) -> PeerId { + PeerId::from_slice(&pk.serialize_uncompressed()[1..]) +} + +pub fn id2pk(id: PeerId) -> Result { + let mut s = [0_u8; 65]; + s[0] = 4; + s[1..].copy_from_slice(id.as_bytes()); + PublicKey::from_slice(&s) +} + +pub fn hex_debug>(s: &T, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(&hex::encode(&s)) +} + +#[cfg(test)] +mod tests { + use super::*; + use secp256k1::{SecretKey, SECP256K1}; + + #[test] + fn pk2id2pk() { + let prikey = SecretKey::new(&mut secp256k1::rand::thread_rng()); + let pubkey = PublicKey::from_secret_key(SECP256K1, &prikey); + assert_eq!(pubkey, id2pk(pk2id(&pubkey)).unwrap()); + } +} From d9b4b1c45638afdeeb5973edfbceb8e60eae4503 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 14 Oct 2022 22:51:42 -0400 Subject: [PATCH 02/15] cargo fmt --- crates/net/ecies/src/algorithm.rs | 126 +++++++++--------------------- crates/net/ecies/src/mac.rs | 13 +-- crates/net/ecies/src/proto.rs | 68 ++++++---------- crates/net/ecies/src/util.rs | 2 +- 4 files changed, 66 insertions(+), 143 deletions(-) diff --git a/crates/net/ecies/src/algorithm.rs b/crates/net/ecies/src/algorithm.rs index bee5f625cfe7..65b47d27992a 100644 --- a/crates/net/ecies/src/algorithm.rs +++ b/crates/net/ecies/src/algorithm.rs @@ -1,7 +1,7 @@ use crate::{ mac::{HeaderBytes, MAC}, - util::{hmac_sha256, id2pk, pk2id, sha256}, proto::ECIESError, + util::{hmac_sha256, id2pk, pk2id, sha256}, }; use aes::{cipher::StreamCipher, Aes128, Aes256}; use anyhow::{format_err, Context}; @@ -10,9 +10,9 @@ use bytes::{BufMut, Bytes, BytesMut}; use ctr::Ctr64BE; use digest::{crypto_common::KeyIvInit, Digest}; use educe::Educe; +use rand::{thread_rng, Rng}; use reth_primitives::{H128, H256, H512 as PeerId}; use reth_rlp::{Encodable, Rlp, RlpEncodable, RlpMaxEncodedLen}; -use rand::{thread_rng, Rng}; use secp256k1::{ ecdsa::{RecoverableSignature, RecoveryId}, PublicKey, SecretKey, SECP256K1, @@ -37,12 +37,7 @@ fn kdf(secret: H256, s1: &[u8], dest: &mut [u8]) { let mut written = 0_usize; while written < dest.len() { let mut hasher = Sha256::default(); - let ctrs = [ - (ctr >> 24) as u8, - (ctr >> 16) as u8, - (ctr >> 8) as u8, - ctr as u8, - ]; + let ctrs = [(ctr >> 24) as u8, (ctr >> 16) as u8, (ctr >> 8) as u8, ctr as u8]; hasher.update(&ctrs); hasher.update(secret.as_bytes()); hasher.update(s1); @@ -87,10 +82,7 @@ pub struct ECIES { fn split_at_mut(arr: &mut [T], mid: usize) -> Result<(&mut [T], &mut [T]), ECIESError> { if mid > arr.len() { - return Err(ECIESError::Other(format_err!( - "too short: {mid} > {}", - arr.len() - ))); + return Err(ECIESError::Other(format_err!("too short: {mid} > {}", arr.len()))) } Ok(arr.split_at_mut(mid)) } @@ -203,11 +195,8 @@ impl ECIES { let total_size: u16 = u16::try_from(65 + 16 + data.len() + 32).unwrap(); - let tag = hmac_sha256( - mac_key.as_ref(), - &[iv.as_bytes(), &encrypted], - &total_size.to_be_bytes(), - ); + let tag = + hmac_sha256(mac_key.as_ref(), &[iv.as_bytes(), &encrypted], &total_size.to_be_bytes()); out.extend_from_slice(iv.as_bytes()); out.extend_from_slice(&encrypted); @@ -231,7 +220,7 @@ impl ECIES { let check_tag = hmac_sha256(mac_key.as_ref(), &[iv, encrypted_data], auth_data); if check_tag != tag { - return Err(ECIESError::TagCheckFailed); + return Err(ECIESError::TagCheckFailed) } let decrypted_data = encrypted_data; @@ -308,9 +297,7 @@ impl ECIES { fn parse_auth_unencrypted(&mut self, data: &[u8]) -> Result<(), ECIESError> { let mut data = Rlp::new(data)?; - let sigdata = data - .get_next::<[u8; 65]>()? - .ok_or(ECIESError::InvalidAuthData)?; + let sigdata = data.get_next::<[u8; 65]>()?.ok_or(ECIESError::InvalidAuthData)?; let signature = RecoverableSignature::from_compact( &sigdata[0..64], RecoveryId::from_i32(sigdata[64] as i32)?, @@ -325,10 +312,8 @@ impl ECIES { &secp256k1::Message::from_slice((x ^ self.remote_nonce.unwrap()).as_ref()).unwrap(), &signature, )?); - self.ephemeral_shared_secret = Some(ecdh_x( - &self.remote_ephemeral_public_key.unwrap(), - &self.ephemeral_secret_key, - )); + self.ephemeral_shared_secret = + Some(ecdh_x(&self.remote_ephemeral_public_key.unwrap(), &self.ephemeral_secret_key)); Ok(()) } @@ -390,10 +375,8 @@ impl ECIES { Some(id2pk(data.get_next()?.ok_or(ECIESError::InvalidAckData)?)?); self.remote_nonce = Some(data.get_next()?.ok_or(ECIESError::InvalidAckData)?); - self.ephemeral_shared_secret = Some(ecdh_x( - &self.remote_ephemeral_public_key.unwrap(), - &self.ephemeral_secret_key, - )); + self.ephemeral_shared_secret = + Some(ecdh_x(&self.remote_ephemeral_public_key.unwrap(), &self.ephemeral_secret_key)); Ok(()) } @@ -430,14 +413,10 @@ impl ECIES { hasher.update(shared_secret.as_ref()); H256::from(hasher.finalize().as_ref()) }; - self.ingress_aes = Some(Ctr64BE::::new( - aes_secret.as_ref().into(), - iv.as_ref().into(), - )); - self.egress_aes = Some(Ctr64BE::::new( - aes_secret.as_ref().into(), - iv.as_ref().into(), - )); + self.ingress_aes = + Some(Ctr64BE::::new(aes_secret.as_ref().into(), iv.as_ref().into())); + self.egress_aes = + Some(Ctr64BE::::new(aes_secret.as_ref().into(), iv.as_ref().into())); let mac_secret: H256 = { let mut hasher = Keccak256::new(); @@ -446,23 +425,14 @@ impl ECIES { H256::from(hasher.finalize().as_ref()) }; self.ingress_mac = Some(MAC::new(mac_secret)); - self.ingress_mac - .as_mut() - .unwrap() - .update((mac_secret ^ self.nonce).as_ref()); - self.ingress_mac - .as_mut() - .unwrap() - .update(self.remote_init_msg.as_ref().unwrap()); + self.ingress_mac.as_mut().unwrap().update((mac_secret ^ self.nonce).as_ref()); + self.ingress_mac.as_mut().unwrap().update(self.remote_init_msg.as_ref().unwrap()); self.egress_mac = Some(MAC::new(mac_secret)); self.egress_mac .as_mut() .unwrap() .update((mac_secret ^ self.remote_nonce.unwrap()).as_ref()); - self.egress_mac - .as_mut() - .unwrap() - .update(self.init_msg.as_ref().unwrap()); + self.egress_mac.as_mut().unwrap().update(self.init_msg.as_ref().unwrap()); } #[cfg(test)] @@ -480,10 +450,7 @@ impl ECIES { header[3..6].copy_from_slice(&[194, 128, 128]); let mut header = HeaderBytes::from(header); - self.egress_aes - .as_mut() - .unwrap() - .apply_keystream(&mut header); + self.egress_aes.as_mut().unwrap().apply_keystream(&mut header); self.egress_mac.as_mut().unwrap().update_header(&header); let tag = self.egress_mac.as_mut().unwrap().digest(); @@ -500,12 +467,12 @@ impl ECIES { self.ingress_mac.as_mut().unwrap().update_header(header); let check_mac = self.ingress_mac.as_mut().unwrap().digest(); if check_mac != mac { - return Err(ECIESError::TagCheckFailed); + return Err(ECIESError::TagCheckFailed) } self.ingress_aes.as_mut().unwrap().apply_keystream(header); if header.as_slice().len() < 3 { - return Err(ECIESError::InvalidHeader); + return Err(ECIESError::InvalidHeader) } let body_size = usize::try_from(header.as_slice().read_uint::(3)?) .context("excessive body len")?; @@ -513,11 +480,8 @@ impl ECIES { if body_size > MAX_BODY_SIZE { return Err(ECIESError::IO(io::Error::new( io::ErrorKind::InvalidInput, - format!( - "body size ({}) exceeds limit ({} bytes)", - body_size, MAX_BODY_SIZE - ), - ))); + format!("body size ({}) exceeds limit ({} bytes)", body_size, MAX_BODY_SIZE), + ))) } self.body_size = Some(body_size); @@ -531,11 +495,7 @@ impl ECIES { pub fn body_len(&self) -> usize { let len = self.body_size.unwrap(); - (if len % 16 == 0 { - len - } else { - (len / 16 + 1) * 16 - }) + 16 + (if len % 16 == 0 { len } else { (len / 16 + 1) * 16 }) + 16 } #[cfg(test)] @@ -546,11 +506,7 @@ impl ECIES { } pub fn write_body(&mut self, out: &mut BytesMut, data: &[u8]) { - let len = if data.len() % 16 == 0 { - data.len() - } else { - (data.len() / 16 + 1) * 16 - }; + let len = if data.len() % 16 == 0 { data.len() } else { (data.len() / 16 + 1) * 16 }; let old_len = out.len(); out.resize(old_len + len, 0); @@ -570,7 +526,7 @@ impl ECIES { self.ingress_mac.as_mut().unwrap().update_body(body); let check_mac = self.ingress_mac.as_mut().unwrap().digest(); if check_mac != mac { - return Err(ECIESError::TagCheckFailed); + return Err(ECIESError::TagCheckFailed) } let size = self.body_size.unwrap(); @@ -687,22 +643,13 @@ mod tests { )) .unwrap(); - let client_nonce = H256(hex!( - "7e968bba13b6c50e2c4cd7f241cc0d64d1ac25c7f5952df231ac6a2bda8ee5d6" - )); + let client_nonce = + H256(hex!("7e968bba13b6c50e2c4cd7f241cc0d64d1ac25c7f5952df231ac6a2bda8ee5d6")); - let server_id = pk2id(&PublicKey::from_secret_key( - SECP256K1, - &eip8_test_server_key(), - )); + let server_id = pk2id(&PublicKey::from_secret_key(SECP256K1, &eip8_test_server_key())); - ECIES::new_static_client( - client_static_key, - server_id, - client_nonce, - client_ephemeral_key, - ) - .unwrap() + ECIES::new_static_client(client_static_key, server_id, client_nonce, client_ephemeral_key) + .unwrap() } fn eip8_test_server() -> ECIES { @@ -711,9 +658,8 @@ mod tests { )) .unwrap(); - let server_nonce = H256(hex!( - "559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd" - )); + let server_nonce = + H256(hex!("559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd")); ECIES::new_static_server(eip8_test_server_key(), server_nonce, server_ephemeral_key) .unwrap() @@ -801,9 +747,7 @@ mod tests { let mut test_client = eip8_test_client(); let mut test_server = eip8_test_server(); - test_server - .read_auth(&mut test_client.create_auth()) - .unwrap(); + test_server.read_auth(&mut test_client.create_auth()).unwrap(); test_client.read_ack(&mut test_server.create_ack()).unwrap(); diff --git a/crates/net/ecies/src/mac.rs b/crates/net/ecies/src/mac.rs index 4927860bc7e7..cb61ac32bf66 100644 --- a/crates/net/ecies/src/mac.rs +++ b/crates/net/ecies/src/mac.rs @@ -2,8 +2,8 @@ use aes::Aes256Enc; use block_padding::NoPadding; use cipher::BlockEncrypt; use digest::KeyInit; -use reth_primitives::{H128, H256}; use generic_array::GenericArray; +use reth_primitives::{H128, H256}; use sha3::{Digest, Keccak256}; use typenum::U16; @@ -17,10 +17,7 @@ pub struct MAC { impl MAC { pub fn new(secret: H256) -> Self { - Self { - secret, - hasher: Keccak256::new(), - } + Self { secret, hasher: Keccak256::new() } } pub fn update(&mut self, data: &[u8]) { @@ -30,8 +27,7 @@ impl MAC { pub fn update_header(&mut self, data: &HeaderBytes) { let aes = Aes256Enc::new_from_slice(self.secret.as_ref()).unwrap(); let mut encrypted = self.digest().to_fixed_bytes(); - aes.encrypt_padded::(&mut encrypted, H128::len_bytes()) - .unwrap(); + aes.encrypt_padded::(&mut encrypted, H128::len_bytes()).unwrap(); for i in 0..data.len() { encrypted[i] ^= data[i]; } @@ -43,8 +39,7 @@ impl MAC { let prev = self.digest(); let aes = Aes256Enc::new_from_slice(self.secret.as_ref()).unwrap(); let mut encrypted = self.digest().to_fixed_bytes(); - aes.encrypt_padded::(&mut encrypted, H128::len_bytes()) - .unwrap(); + aes.encrypt_padded::(&mut encrypted, H128::len_bytes()).unwrap(); for i in 0..16 { encrypted[i] ^= prev[i]; } diff --git a/crates/net/ecies/src/proto.rs b/crates/net/ecies/src/proto.rs index 1d56cdaffa49..ffd2fc89a4b6 100644 --- a/crates/net/ecies/src/proto.rs +++ b/crates/net/ecies/src/proto.rs @@ -1,20 +1,24 @@ use super::algorithm::{ECIES, MAX_BODY_SIZE}; -use reth_primitives::H512 as PeerId; use anyhow::{bail, Context as _}; use bytes::{Bytes, BytesMut}; use futures::{ready, Sink, SinkExt}; +use reth_primitives::H512 as PeerId; use secp256k1::SecretKey; use std::{ fmt::Debug, io, + net::SocketAddr, pin::Pin, - task::{Context, Poll}, net::SocketAddr, + task::{Context, Poll}, +}; +use thiserror::Error; +use tokio::{ + io::{AsyncRead, AsyncWrite}, + net::TcpStream, }; -use tokio::{io::{AsyncRead, AsyncWrite}, net::TcpStream}; use tokio_stream::{Stream, StreamExt}; use tokio_util::codec::{Decoder, Encoder, Framed}; use tracing::{debug, instrument, trace}; -use thiserror::Error; #[derive(Debug, Error)] pub enum ECIESError { @@ -85,18 +89,12 @@ pub struct ECIESCodec { impl ECIESCodec { /// Create a new server codec using the given secret key pub fn new_server(secret_key: SecretKey) -> Result { - Ok(Self { - ecies: ECIES::new_server(secret_key)?, - state: ECIESState::Auth, - }) + Ok(Self { ecies: ECIES::new_server(secret_key)?, state: ECIESState::Auth }) } /// Create a new client codec using the given secret key and the server's public id pub fn new_client(secret_key: SecretKey, remote_id: PeerId) -> Result { - Ok(Self { - ecies: ECIES::new_client(secret_key, remote_id)?, - state: ECIESState::Auth, - }) + Ok(Self { ecies: ECIES::new_client(secret_key, remote_id)?, state: ECIESState::Auth }) } } @@ -111,7 +109,7 @@ impl Decoder for ECIESCodec { ECIESState::Auth => { trace!("parsing auth"); if buf.len() < 2 { - return Ok(None); + return Ok(None) } let payload_size = u16::from_be_bytes([buf[0], buf[1]]) as usize; @@ -119,18 +117,18 @@ impl Decoder for ECIESCodec { if buf.len() < total_size { trace!("current len {}, need {}", buf.len(), total_size); - return Ok(None); + return Ok(None) } self.ecies.read_auth(&mut *buf.split_to(total_size))?; self.state = ECIESState::Header; - return Ok(Some(IngressECIESValue::AuthReceive(self.ecies.remote_id()))); + return Ok(Some(IngressECIESValue::AuthReceive(self.ecies.remote_id()))) } ECIESState::Ack => { trace!("parsing ack with len {}", buf.len()); if buf.len() < 2 { - return Ok(None); + return Ok(None) } let payload_size = u16::from_be_bytes([buf[0], buf[1]]) as usize; @@ -138,35 +136,34 @@ impl Decoder for ECIESCodec { if buf.len() < total_size { trace!("current len {}, need {}", buf.len(), total_size); - return Ok(None); + return Ok(None) } self.ecies.read_ack(&mut *buf.split_to(total_size))?; self.state = ECIESState::Header; - return Ok(Some(IngressECIESValue::Ack)); + return Ok(Some(IngressECIESValue::Ack)) } ECIESState::Header => { if buf.len() < ECIES::header_len() { trace!("current len {}, need {}", buf.len(), ECIES::header_len()); - return Ok(None); + return Ok(None) } - self.ecies - .read_header(&mut *buf.split_to(ECIES::header_len()))?; + self.ecies.read_header(&mut *buf.split_to(ECIES::header_len()))?; self.state = ECIESState::Body; } ECIESState::Body => { if buf.len() < self.ecies.body_len() { - return Ok(None); + return Ok(None) } let mut data = buf.split_to(self.ecies.body_len()); let ret = Bytes::copy_from_slice(self.ecies.read_body(&mut *data)?); self.state = ECIESState::Header; - return Ok(Some(IngressECIESValue::Message(ret))); + return Ok(Some(IngressECIESValue::Message(ret))) } } } @@ -198,7 +195,7 @@ impl Encoder for ECIESCodec { data.len(), MAX_BODY_SIZE ), - )); + )) } self.ecies.write_header(buf, data.len()); @@ -216,7 +213,6 @@ pub struct ECIESStream { remote_id: PeerId, } - // This trait is just for instrumenting the stream with a socket addr pub trait HasRemoteAddr { fn remote_addr(&self) -> Option; @@ -252,10 +248,7 @@ where trace!("parsing ecies ack ..."); if matches!(ack, Some(IngressECIESValue::Ack)) { - Ok(Self { - stream: transport, - remote_id, - }) + Ok(Self { stream: transport, remote_id }) } else { bail!("invalid handshake: expected ack, got {:?} instead", ack) } @@ -280,15 +273,9 @@ where }; debug!("sending ecies ack ..."); - transport - .send(EgressECIESValue::Ack) - .await - .context("failed to send ECIES auth")?; - - Ok(Self { - stream: transport, - remote_id, - }) + transport.send(EgressECIESValue::Ack).await.context("failed to send ECIES auth")?; + + Ok(Self { stream: transport, remote_id }) } /// Get the remote id @@ -308,10 +295,7 @@ where Some(Ok(IngressECIESValue::Message(body))) => Poll::Ready(Some(Ok(body))), Some(other) => Poll::Ready(Some(Err(io::Error::new( io::ErrorKind::Other, - format!( - "ECIES stream protocol error: expected message, received {:?}", - other - ), + format!("ECIES stream protocol error: expected message, received {:?}", other), )))), None => Poll::Ready(None), } diff --git a/crates/net/ecies/src/util.rs b/crates/net/ecies/src/util.rs index 27747f05a55a..be7c9a512658 100644 --- a/crates/net/ecies/src/util.rs +++ b/crates/net/ecies/src/util.rs @@ -1,5 +1,5 @@ -use reth_primitives::{H256, H512 as PeerId}; use hmac::{Hmac, Mac}; +use reth_primitives::{H256, H512 as PeerId}; use secp256k1::PublicKey; use sha2::Sha256; use sha3::{Digest, Keccak256}; From fc89dd6419216fab0c98d8ec0a69da6a3bd06be5 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 14 Oct 2022 23:08:45 -0400 Subject: [PATCH 03/15] add hex-literal and proptest to dev-dependencies * adds std feature to reth-rlp --- Cargo.lock | 81 ++++++++++++++++++++++++++++++++++++- crates/net/ecies/Cargo.toml | 6 ++- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 67f5a13976a1..c07fd114a678 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -165,6 +165,21 @@ dependencies = [ "shlex", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -736,7 +751,7 @@ dependencies = [ [[package]] name = "ethers-core" version = "0.17.0" -source = "git+https://github.com/rjected/ethers-rs?branch=add-h128#e600580c0348e959d74fdfb0db9d3cdfb67222d7" +source = "git+https://github.com/gakonst/ethers-rs#a07581489a12b1007c3a261dc8df2bbdc4e27918" dependencies = [ "arrayvec", "bytes", @@ -1968,6 +1983,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5" +dependencies = [ + "bit-set", + "bitflags", + "byteorder", + "lazy_static", + "num-traits", + "quick-error 2.0.1", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.21" @@ -2013,6 +2060,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + [[package]] name = "rayon" version = "1.5.3" @@ -2120,7 +2176,9 @@ dependencies = [ "futures", "generic-array", "hex", + "hex-literal", "hmac", + "proptest", "rand", "reth-primitives", "reth-rlp", @@ -2459,6 +2517,18 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error 1.2.3", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.11" @@ -3034,6 +3104,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.3.2" diff --git a/crates/net/ecies/Cargo.toml b/crates/net/ecies/Cargo.toml index 510d6816be8e..38c0bbd66623 100644 --- a/crates/net/ecies/Cargo.toml +++ b/crates/net/ecies/Cargo.toml @@ -7,7 +7,7 @@ repository = "https://github.com/foundry-rs/reth" readme = "README.md" [dependencies] -reth-rlp = { path = "../../common/rlp", features = ["derive", "ethereum-types"] } +reth-rlp = { path = "../../common/rlp", features = ["derive", "ethereum-types", "std"] } reth-primitives = { path = "../../primitives" } anyhow = "1.0.65" byteorder = "1.4.3" @@ -32,3 +32,7 @@ generic-array = "0.14.6" cipher = { version = "0.4.3", features = ["block-padding"] } typenum = "1.15.0" rand = "0.8.5" + +[dev-dependencies] +hex-literal = "0.3.4" +proptest = "1.0.0" From 761cf6a3729eef0a3fbeb83a93bafb3c73b8843a Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 14 Oct 2022 23:29:31 -0400 Subject: [PATCH 04/15] document util --- crates/net/ecies/src/util.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/net/ecies/src/util.rs b/crates/net/ecies/src/util.rs index be7c9a512658..22c9b39bb0c5 100644 --- a/crates/net/ecies/src/util.rs +++ b/crates/net/ecies/src/util.rs @@ -5,14 +5,19 @@ use sha2::Sha256; use sha3::{Digest, Keccak256}; use std::fmt::{self, Formatter}; +/// Hashes the input data with Keccak256. pub fn keccak256(data: &[u8]) -> H256 { H256::from(Keccak256::digest(data).as_ref()) } +/// Hashes the input data with SHA256. pub fn sha256(data: &[u8]) -> H256 { H256::from(Sha256::digest(data).as_ref()) } +/// Produces a HMAC_SHA256 digest of the `input_data` and `auth_data` with the given `key`. +/// This is done by accumulating each slice in `input_data` into the HMAC state, then accumulating +/// the `auth_data` and returning the resulting digest. pub fn hmac_sha256(key: &[u8], input: &[&[u8]], auth_data: &[u8]) -> H256 { let mut hmac = Hmac::::new_from_slice(key).unwrap(); for input in input { @@ -22,17 +27,24 @@ pub fn hmac_sha256(key: &[u8], input: &[&[u8]], auth_data: &[u8]) -> H256 { H256::from_slice(&*hmac.finalize().into_bytes()) } +/// Converts a [secp256k1::PublicKey] to a [PeerId] by stripping the +/// SECP256K1_TAG_PUBKEY_UNCOMPRESSED tag and storing the rest of the slice in the [PeerId]. pub fn pk2id(pk: &PublicKey) -> PeerId { PeerId::from_slice(&pk.serialize_uncompressed()[1..]) } +/// Converts a [PeerId] to a [secp256k1::PublicKey] by prepending the [PeerId] bytes with the +/// SECP256K1_TAG_PUBKEY_UNCOMPRESSED tag. pub fn id2pk(id: PeerId) -> Result { let mut s = [0_u8; 65]; + // SECP256K1_TAG_PUBKEY_UNCOMPRESSED = 0x04 + // see: https://github.com/bitcoin-core/secp256k1/blob/master/include/secp256k1.h#L211 s[0] = 4; s[1..].copy_from_slice(id.as_bytes()); PublicKey::from_slice(&s) } +/// Convenience method for writing byte slices as hex strings. pub fn hex_debug>(s: &T, f: &mut Formatter<'_>) -> fmt::Result { f.write_str(&hex::encode(&s)) } From deb4c74d173907c501efdfe25b293b4e3cac3d4e Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Sat, 15 Oct 2022 00:06:28 -0400 Subject: [PATCH 05/15] document more --- crates/net/ecies/src/algorithm.rs | 11 +++++++++++ crates/net/ecies/src/proto.rs | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/crates/net/ecies/src/algorithm.rs b/crates/net/ecies/src/algorithm.rs index 65b47d27992a..066d561f99de 100644 --- a/crates/net/ecies/src/algorithm.rs +++ b/crates/net/ecies/src/algorithm.rs @@ -88,6 +88,8 @@ fn split_at_mut(arr: &mut [T], mid: usize) -> Result<(&mut [T], &mut [T]), EC } impl ECIES { + /// Create a new client with the given static secret key, remote peer id, nonce, and ephemeral + /// secret key. fn new_static_client( secret_key: SecretKey, remote_id: PeerId, @@ -122,6 +124,7 @@ impl ECIES { }) } + /// Create a new ECIES client with the given static secret key and remote peer ID. pub fn new_client(secret_key: SecretKey, remote_id: PeerId) -> Result { let nonce = H256::random(); let ephemeral_secret_key = SecretKey::new(&mut secp256k1::rand::thread_rng()); @@ -129,6 +132,8 @@ impl ECIES { Self::new_static_client(secret_key, remote_id, nonce, ephemeral_secret_key) } + /// Create a new server with the given static secret key, remote peer id, and ephemeral secret + /// key. pub fn new_static_server( secret_key: SecretKey, nonce: H256, @@ -161,6 +166,7 @@ impl ECIES { }) } + /// Create a new ECIES server with the given static secret key. pub fn new_server(secret_key: SecretKey) -> Result { let nonce = H256::random(); let ephemeral_secret_key = SecretKey::new(&mut secp256k1::rand::thread_rng()); @@ -168,6 +174,7 @@ impl ECIES { Self::new_static_server(secret_key, nonce, ephemeral_secret_key) } + /// Return the contained remote peer ID. pub fn remote_id(&self) -> PeerId { self.remote_id.unwrap() } @@ -275,6 +282,7 @@ impl ECIES { buf } + /// Write an auth message to the given buffer. pub fn write_auth(&mut self, buf: &mut BytesMut) { let unencrypted = self.create_auth_unencrypted(); @@ -318,6 +326,7 @@ impl ECIES { Ok(()) } + /// Read and verify an auth message from the input data. pub fn read_auth(&mut self, data: &mut [u8]) -> Result<(), ECIESError> { self.remote_init_msg = Some(Bytes::copy_from_slice(data)); let unencrypted = self.decrypt_message(data)?; @@ -346,6 +355,7 @@ impl ECIES { buf } + /// Write an ack message to the given buffer. pub fn write_ack(&mut self, out: &mut BytesMut) { let unencrypted = self.create_ack_unencrypted(); @@ -380,6 +390,7 @@ impl ECIES { Ok(()) } + /// Read and verify an ack message from the input data. pub fn read_ack(&mut self, data: &mut [u8]) -> Result<(), ECIESError> { self.remote_init_msg = Some(Bytes::copy_from_slice(data)); let unencrypted = self.decrypt_message(data)?; diff --git a/crates/net/ecies/src/proto.rs b/crates/net/ecies/src/proto.rs index ffd2fc89a4b6..8700319f8163 100644 --- a/crates/net/ecies/src/proto.rs +++ b/crates/net/ecies/src/proto.rs @@ -20,6 +20,7 @@ use tokio_stream::{Stream, StreamExt}; use tokio_util::codec::{Decoder, Encoder, Framed}; use tracing::{debug, instrument, trace}; +/// An error that occurs while reading or writing to an ECIES stream. #[derive(Debug, Error)] pub enum ECIESError { #[error("IO error")] @@ -57,7 +58,13 @@ impl From for ECIESError { #[derive(Clone, Copy, Debug, PartialEq, Eq)] /// Current ECIES state of a connection pub enum ECIESState { + /// The first stage of the ECIES handshake, where each side of the connection sends an auth + /// message containing the ephemeral public key, signature of the public key, nonce, and other + /// metadata. Auth, + + /// The second stage of the ECIES handshake, where each side of the connection sends an ack + /// message containing the nonce and other metadata. Ack, Header, Body, From 49fd89d76f99553f9b2a09075b2ca0bae18cf8e7 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Sat, 15 Oct 2022 13:23:13 -0700 Subject: [PATCH 06/15] chore: allow missing docs in ecies/algorith.rs --- crates/net/ecies/src/algorithm.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/net/ecies/src/algorithm.rs b/crates/net/ecies/src/algorithm.rs index 066d561f99de..9619e256c683 100644 --- a/crates/net/ecies/src/algorithm.rs +++ b/crates/net/ecies/src/algorithm.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] use crate::{ mac::{HeaderBytes, MAC}, proto::ECIESError, From c6b773f79ff57a4a79fe9953597b1e9c628174bb Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Sat, 15 Oct 2022 14:18:59 -0700 Subject: [PATCH 07/15] feat(ecies): improve error handling remove anyhow and strictly type all errors --- Cargo.lock | 1 - crates/net/ecies/Cargo.toml | 29 +++++++------ crates/net/ecies/src/algorithm.rs | 19 ++++----- crates/net/ecies/src/error.rs | 48 +++++++++++++++++++++ crates/net/ecies/src/lib.rs | 3 ++ crates/net/ecies/src/proto.rs | 70 +++++++++---------------------- 6 files changed, 95 insertions(+), 75 deletions(-) create mode 100644 crates/net/ecies/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index c07fd114a678..c8f5aeaac064 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2165,7 +2165,6 @@ name = "reth-ecies" version = "0.1.0" dependencies = [ "aes", - "anyhow", "block-padding", "byteorder", "bytes", diff --git a/crates/net/ecies/Cargo.toml b/crates/net/ecies/Cargo.toml index 38c0bbd66623..bbf14c2f1d14 100644 --- a/crates/net/ecies/Cargo.toml +++ b/crates/net/ecies/Cargo.toml @@ -9,29 +9,34 @@ readme = "README.md" [dependencies] reth-rlp = { path = "../../common/rlp", features = ["derive", "ethereum-types", "std"] } reth-primitives = { path = "../../primitives" } -anyhow = "1.0.65" + +futures = "0.3.24" +thiserror = "1.0.37" +tokio = { version = "1.21.2", features = ["full"] } +tokio-stream = "0.1.11" +tokio-util = { version = "0.7.4", features = ["codec"] } + +educe = "0.4.19" +hex = "0.4.3" +tracing = "0.1.37" + +# HeaderBytes +generic-array = "0.14.6" +typenum = "1.15.0" byteorder = "1.4.3" bytes = "1.2.1" + +# crypto +rand = "0.8.5" ctr = "0.9.2" digest = "0.10.5" -educe = "0.4.19" -futures = "0.3.24" secp256k1 = { version = "0.24.0", features = ["global-context", "rand-std", "recovery"] } sha2 = "0.10.6" sha3 = "0.10.5" -thiserror = "1.0.37" -tokio = { version = "1.21.2", features = ["full"] } -tokio-stream = "0.1.11" -tokio-util = { version = "0.7.4", features = ["codec"] } -tracing = "0.1.37" -hex = "0.4.3" aes = "0.8.1" hmac = "0.12.1" block-padding = "0.3.2" -generic-array = "0.14.6" cipher = { version = "0.4.3", features = ["block-padding"] } -typenum = "1.15.0" -rand = "0.8.5" [dev-dependencies] hex-literal = "0.3.4" diff --git a/crates/net/ecies/src/algorithm.rs b/crates/net/ecies/src/algorithm.rs index 9619e256c683..8204826c7e06 100644 --- a/crates/net/ecies/src/algorithm.rs +++ b/crates/net/ecies/src/algorithm.rs @@ -1,11 +1,10 @@ #![allow(missing_docs)] use crate::{ mac::{HeaderBytes, MAC}, - proto::ECIESError, util::{hmac_sha256, id2pk, pk2id, sha256}, + ECIESError, }; use aes::{cipher::StreamCipher, Aes128, Aes256}; -use anyhow::{format_err, Context}; use byteorder::{BigEndian, ByteOrder, ReadBytesExt}; use bytes::{BufMut, Bytes, BytesMut}; use ctr::Ctr64BE; @@ -81,11 +80,11 @@ pub struct ECIES { body_size: Option, } -fn split_at_mut(arr: &mut [T], mid: usize) -> Result<(&mut [T], &mut [T]), ECIESError> { - if mid > arr.len() { - return Err(ECIESError::Other(format_err!("too short: {mid} > {}", arr.len()))) +fn split_at_mut(arr: &mut [T], idx: usize) -> Result<(&mut [T], &mut [T]), ECIESError> { + if idx > arr.len() { + return Err(ECIESError::OutOfBounds { idx, len: arr.len() }) } - Ok(arr.split_at_mut(mid)) + Ok(arr.split_at_mut(idx)) } impl ECIES { @@ -214,8 +213,7 @@ impl ECIES { fn decrypt_message<'a>(&self, data: &'a mut [u8]) -> Result<&'a mut [u8], ECIESError> { let (auth_data, encrypted) = split_at_mut(data, 2)?; let (pubkey_bytes, encrypted) = split_at_mut(encrypted, 65)?; - let public_key = PublicKey::from_slice(pubkey_bytes) - .with_context(|| format!("bad public key {}", hex::encode(pubkey_bytes)))?; + let public_key = PublicKey::from_slice(pubkey_bytes)?; let (data_iv, tag_bytes) = split_at_mut(encrypted, encrypted.len() - 32)?; let (iv, encrypted_data) = split_at_mut(data_iv, 16)?; let tag = H256::from_slice(tag_bytes); @@ -313,7 +311,7 @@ impl ECIES { )?; let remote_id = data.get_next()?.ok_or(ECIESError::InvalidAuthData)?; self.remote_id = Some(remote_id); - self.remote_public_key = Some(id2pk(remote_id).context("failed to parse peer id")?); + self.remote_public_key = Some(id2pk(remote_id)?); self.remote_nonce = Some(data.get_next()?.ok_or(ECIESError::InvalidAuthData)?); let x = ecdh_x(&self.remote_public_key.unwrap(), &self.secret_key); @@ -486,8 +484,7 @@ impl ECIES { if header.as_slice().len() < 3 { return Err(ECIESError::InvalidHeader) } - let body_size = usize::try_from(header.as_slice().read_uint::(3)?) - .context("excessive body len")?; + let body_size = usize::try_from(header.as_slice().read_uint::(3)?)?; if body_size > MAX_BODY_SIZE { return Err(ECIESError::IO(io::Error::new( diff --git a/crates/net/ecies/src/error.rs b/crates/net/ecies/src/error.rs new file mode 100644 index 000000000000..14e8fb9d93ff --- /dev/null +++ b/crates/net/ecies/src/error.rs @@ -0,0 +1,48 @@ +use thiserror::Error; + +use crate::proto::IngressECIESValue; + +/// An error that occurs while reading or writing to an ECIES stream. +#[derive(Debug, Error)] +pub enum ECIESError { + /// Error during IO + #[error("IO error")] + IO(#[from] std::io::Error), + /// Error when checking the HMAC tag against the tag on the data + #[error("tag check failure")] + TagCheckFailed, + /// Error when parsing AUTH data + #[error("invalid auth data")] + InvalidAuthData, + /// Error when parsing ACK data + #[error("invalid ack data")] + InvalidAckData, + /// Error when reading the header if its length is <3 + #[error("invalid body data")] + InvalidHeader, + /// Error when interacting with secp256k1 + #[error(transparent)] + Secp256k1(#[from] secp256k1::Error), + /// Error when decoding RLP data + #[error(transparent)] + RLPDecoding(#[from] reth_rlp::DecodeError), + /// Error when convering to integer + #[error(transparent)] + FromInt(#[from] std::num::TryFromIntError), + /// Error when trying to split an array beyond its length + #[error("requested {idx} but array len is {len}")] + OutOfBounds { + /// The index you are trying to split at + idx: usize, + /// The length of the array + len: usize, + }, + /// Error when handshaking with a peer (ack / auth) + #[error("invalid handshake: expected {expected:?}, got {msg:?} instead")] + InvalidHandshake { + /// The expected return value from the peer + expected: IngressECIESValue, + /// The actual value returned from the peer + msg: Option, + }, +} diff --git a/crates/net/ecies/src/lib.rs b/crates/net/ecies/src/lib.rs index 032821507643..daedb49f488c 100644 --- a/crates/net/ecies/src/lib.rs +++ b/crates/net/ecies/src/lib.rs @@ -11,3 +11,6 @@ pub mod algorithm; pub mod mac; pub mod proto; pub mod util; + +mod error; +pub use error::ECIESError; diff --git a/crates/net/ecies/src/proto.rs b/crates/net/ecies/src/proto.rs index 8700319f8163..19fe7d5c5eca 100644 --- a/crates/net/ecies/src/proto.rs +++ b/crates/net/ecies/src/proto.rs @@ -1,5 +1,7 @@ -use super::algorithm::{ECIES, MAX_BODY_SIZE}; -use anyhow::{bail, Context as _}; +use crate::{ + algorithm::{ECIES, MAX_BODY_SIZE}, + ECIESError, +}; use bytes::{Bytes, BytesMut}; use futures::{ready, Sink, SinkExt}; use reth_primitives::H512 as PeerId; @@ -11,7 +13,6 @@ use std::{ pin::Pin, task::{Context, Poll}, }; -use thiserror::Error; use tokio::{ io::{AsyncRead, AsyncWrite}, net::TcpStream, @@ -20,41 +21,6 @@ use tokio_stream::{Stream, StreamExt}; use tokio_util::codec::{Decoder, Encoder, Framed}; use tracing::{debug, instrument, trace}; -/// An error that occurs while reading or writing to an ECIES stream. -#[derive(Debug, Error)] -pub enum ECIESError { - #[error("IO error")] - IO(#[from] io::Error), - #[error("tag check failure")] - TagCheckFailed, - #[error("invalid auth data")] - InvalidAuthData, - #[error("invalid ack data")] - InvalidAckData, - #[error("invalid body data")] - InvalidHeader, - #[error("other")] - Other(#[from] anyhow::Error), -} - -impl From for io::Error { - fn from(error: ECIESError) -> Self { - Self::new(io::ErrorKind::Other, format!("ECIES error: {:?}", error)) - } -} - -impl From for ECIESError { - fn from(error: secp256k1::Error) -> Self { - Self::Other(error.into()) - } -} - -impl From for ECIESError { - fn from(error: reth_rlp::DecodeError) -> Self { - Self::Other(error.into()) - } -} - #[derive(Clone, Copy, Debug, PartialEq, Eq)] /// Current ECIES state of a connection pub enum ECIESState { @@ -107,7 +73,7 @@ impl ECIESCodec { impl Decoder for ECIESCodec { type Item = IngressECIESValue; - type Error = io::Error; + type Error = ECIESError; #[instrument(level = "trace", skip_all, fields(peer=&*format!("{:?}", self.ecies.remote_id.map(|s| s.to_string())), state=&*format!("{:?}", self.state)))] fn decode(&mut self, buf: &mut BytesMut) -> Result, Self::Error> { @@ -241,7 +207,7 @@ where transport: Io, secret_key: SecretKey, remote_id: PeerId, - ) -> anyhow::Result { + ) -> Result { let ecies = ECIESCodec::new_client(secret_key, remote_id) .map_err(|_| io::Error::new(io::ErrorKind::Other, "invalid handshake"))?; @@ -251,36 +217,38 @@ where transport.send(EgressECIESValue::Auth).await?; trace!("waiting for ecies ack ..."); - let ack = transport.try_next().await?; + let msg = transport.try_next().await?; trace!("parsing ecies ack ..."); - if matches!(ack, Some(IngressECIESValue::Ack)) { + if matches!(msg, Some(IngressECIESValue::Ack)) { Ok(Self { stream: transport, remote_id }) } else { - bail!("invalid handshake: expected ack, got {:?} instead", ack) + Err(ECIESError::InvalidHandshake { expected: IngressECIESValue::Ack, msg }) } } /// Listen on a just connected ECIES client #[instrument(skip_all, fields(peer=&*format!("{:?}", transport.remote_addr())))] - pub async fn incoming(transport: Io, secret_key: SecretKey) -> anyhow::Result { - let ecies = ECIESCodec::new_server(secret_key).context("handshake error")?; + pub async fn incoming(transport: Io, secret_key: SecretKey) -> Result { + let ecies = ECIESCodec::new_server(secret_key)?; debug!("incoming ecies stream ..."); let mut transport = ecies.framed(transport); - let ack = transport.try_next().await?; + let msg = transport.try_next().await?; debug!("receiving ecies auth"); - let remote_id = match ack { - Some(IngressECIESValue::AuthReceive(remote_id)) => remote_id, + let remote_id = match &msg { + Some(IngressECIESValue::AuthReceive(remote_id)) => *remote_id, other => { - debug!("expected auth, got {:?} instead", other); - bail!("invalid handshake"); + return Err(ECIESError::InvalidHandshake { + expected: IngressECIESValue::AuthReceive(Default::default()), + msg, + }) } }; debug!("sending ecies ack ..."); - transport.send(EgressECIESValue::Ack).await.context("failed to send ECIES auth")?; + transport.send(EgressECIESValue::Ack).await?; Ok(Self { stream: transport, remote_id }) } From a658e3561e4eba5e3e14160a24609f62058e9715 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Sat, 15 Oct 2022 16:27:55 -0700 Subject: [PATCH 08/15] refactor(ecies): movem ingress/egress to lib.rs --- crates/net/ecies/src/error.rs | 2 +- crates/net/ecies/src/lib.rs | 28 +++++++++++++++++++++++++++- crates/net/ecies/src/proto.rs | 24 +++--------------------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/crates/net/ecies/src/error.rs b/crates/net/ecies/src/error.rs index 14e8fb9d93ff..ef25040d2793 100644 --- a/crates/net/ecies/src/error.rs +++ b/crates/net/ecies/src/error.rs @@ -1,6 +1,6 @@ use thiserror::Error; -use crate::proto::IngressECIESValue; +use crate::IngressECIESValue; /// An error that occurs while reading or writing to an ECIES stream. #[derive(Debug, Error)] diff --git a/crates/net/ecies/src/lib.rs b/crates/net/ecies/src/lib.rs index daedb49f488c..9ea37451e67b 100644 --- a/crates/net/ecies/src/lib.rs +++ b/crates/net/ecies/src/lib.rs @@ -10,7 +10,33 @@ pub mod algorithm; pub mod mac; pub mod proto; -pub mod util; +mod util; mod error; pub use error::ECIESError; + +mod codec; + +use reth_primitives::H512 as PeerId; + +#[derive(Clone, Debug, PartialEq, Eq)] +/// Raw egress values for an ECIES protocol +pub enum EgressECIESValue { + /// The AUTH message being sent out + Auth, + /// The ACK message being sent out + Ack, + /// The message being sent out (wrapped bytes) + Message(bytes::Bytes), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +/// Raw ingress values for an ECIES protocol +pub enum IngressECIESValue { + /// Receiving a message from a [`peerId`] + AuthReceive(PeerId), + /// Receiving an ACK message + Ack, + /// Receiving a message + Message(bytes::Bytes), +} diff --git a/crates/net/ecies/src/proto.rs b/crates/net/ecies/src/proto.rs index 19fe7d5c5eca..a78696c1456c 100644 --- a/crates/net/ecies/src/proto.rs +++ b/crates/net/ecies/src/proto.rs @@ -1,8 +1,6 @@ -use crate::{ - algorithm::{ECIES, MAX_BODY_SIZE}, - ECIESError, -}; -use bytes::{Bytes, BytesMut}; +//! The ECIES Stream implementation which wraps over [`AsyncRead`] and [`AsyncWrite`]. +use crate::{ECIESError, EgressECIESValue, IngressECIESValue}; +use bytes::Bytes; use futures::{ready, Sink, SinkExt}; use reth_primitives::H512 as PeerId; use secp256k1::SecretKey; @@ -36,22 +34,6 @@ pub enum ECIESState { Body, } -#[derive(Clone, Debug, PartialEq, Eq)] -/// Raw egress values for an ECIES protocol -pub enum EgressECIESValue { - Auth, - Ack, - Message(Bytes), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -/// Raw ingress values for an ECIES protocol -pub enum IngressECIESValue { - AuthReceive(PeerId), - Ack, - Message(Bytes), -} - /// Tokio codec for ECIES #[derive(Debug)] pub struct ECIESCodec { From 95f11f1fa4644e3a0bc4de73042c406cc74f6b55 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Sat, 15 Oct 2022 16:28:14 -0700 Subject: [PATCH 09/15] chore(ecies): allow missing docs in mac --- crates/net/ecies/src/mac.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/net/ecies/src/mac.rs b/crates/net/ecies/src/mac.rs index cb61ac32bf66..2a834839ffdb 100644 --- a/crates/net/ecies/src/mac.rs +++ b/crates/net/ecies/src/mac.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] use aes::Aes256Enc; use block_padding::NoPadding; use cipher::BlockEncrypt; From 99f69c2385ba9f2d2cd5f92166ad30a3377b4d68 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Sat, 15 Oct 2022 16:28:32 -0700 Subject: [PATCH 10/15] chore(ecies): cleanup utils --- crates/net/ecies/src/util.rs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/crates/net/ecies/src/util.rs b/crates/net/ecies/src/util.rs index 22c9b39bb0c5..1d134dc8572a 100644 --- a/crates/net/ecies/src/util.rs +++ b/crates/net/ecies/src/util.rs @@ -1,24 +1,17 @@ use hmac::{Hmac, Mac}; use reth_primitives::{H256, H512 as PeerId}; use secp256k1::PublicKey; -use sha2::Sha256; -use sha3::{Digest, Keccak256}; -use std::fmt::{self, Formatter}; - -/// Hashes the input data with Keccak256. -pub fn keccak256(data: &[u8]) -> H256 { - H256::from(Keccak256::digest(data).as_ref()) -} +use sha2::{Digest, Sha256}; /// Hashes the input data with SHA256. -pub fn sha256(data: &[u8]) -> H256 { +pub(crate) fn sha256(data: &[u8]) -> H256 { H256::from(Sha256::digest(data).as_ref()) } /// Produces a HMAC_SHA256 digest of the `input_data` and `auth_data` with the given `key`. /// This is done by accumulating each slice in `input_data` into the HMAC state, then accumulating /// the `auth_data` and returning the resulting digest. -pub fn hmac_sha256(key: &[u8], input: &[&[u8]], auth_data: &[u8]) -> H256 { +pub(crate) fn hmac_sha256(key: &[u8], input: &[&[u8]], auth_data: &[u8]) -> H256 { let mut hmac = Hmac::::new_from_slice(key).unwrap(); for input in input { hmac.update(input); @@ -29,13 +22,13 @@ pub fn hmac_sha256(key: &[u8], input: &[&[u8]], auth_data: &[u8]) -> H256 { /// Converts a [secp256k1::PublicKey] to a [PeerId] by stripping the /// SECP256K1_TAG_PUBKEY_UNCOMPRESSED tag and storing the rest of the slice in the [PeerId]. -pub fn pk2id(pk: &PublicKey) -> PeerId { +pub(crate) fn pk2id(pk: &PublicKey) -> PeerId { PeerId::from_slice(&pk.serialize_uncompressed()[1..]) } /// Converts a [PeerId] to a [secp256k1::PublicKey] by prepending the [PeerId] bytes with the /// SECP256K1_TAG_PUBKEY_UNCOMPRESSED tag. -pub fn id2pk(id: PeerId) -> Result { +pub(crate) fn id2pk(id: PeerId) -> Result { let mut s = [0_u8; 65]; // SECP256K1_TAG_PUBKEY_UNCOMPRESSED = 0x04 // see: https://github.com/bitcoin-core/secp256k1/blob/master/include/secp256k1.h#L211 @@ -44,11 +37,6 @@ pub fn id2pk(id: PeerId) -> Result { PublicKey::from_slice(&s) } -/// Convenience method for writing byte slices as hex strings. -pub fn hex_debug>(s: &T, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(&hex::encode(&s)) -} - #[cfg(test)] mod tests { use super::*; From e136130331a4daae1734a721d6f79282c9ef5ab3 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Sat, 15 Oct 2022 16:28:49 -0700 Subject: [PATCH 11/15] refactor(ecies): move ECIES Codec to separate file --- crates/net/ecies/src/codec.rs | 152 ++++++++++++++++++++++++++++++++++ crates/net/ecies/src/proto.rs | 149 ++------------------------------- 2 files changed, 157 insertions(+), 144 deletions(-) create mode 100644 crates/net/ecies/src/codec.rs diff --git a/crates/net/ecies/src/codec.rs b/crates/net/ecies/src/codec.rs new file mode 100644 index 000000000000..caf4445744f5 --- /dev/null +++ b/crates/net/ecies/src/codec.rs @@ -0,0 +1,152 @@ +use crate::{ + algorithm::{ECIES, MAX_BODY_SIZE}, + ECIESError, EgressECIESValue, IngressECIESValue, +}; +use bytes::{Bytes, BytesMut}; +use reth_primitives::H512 as PeerId; +use secp256k1::SecretKey; +use std::{fmt::Debug, io}; +use tokio_util::codec::{Decoder, Encoder}; +use tracing::{instrument, trace}; + +/// Tokio codec for ECIES +#[derive(Debug)] +pub(crate) struct ECIESCodec { + ecies: ECIES, + state: ECIESState, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +/// Current ECIES state of a connection +enum ECIESState { + /// The first stage of the ECIES handshake, where each side of the connection sends an auth + /// message containing the ephemeral public key, signature of the public key, nonce, and other + /// metadata. + Auth, + + /// The second stage of the ECIES handshake, where each side of the connection sends an ack + /// message containing the nonce and other metadata. + Ack, + Header, + Body, +} + +impl ECIESCodec { + /// Create a new server codec using the given secret key + pub(crate) fn new_server(secret_key: SecretKey) -> Result { + Ok(Self { ecies: ECIES::new_server(secret_key)?, state: ECIESState::Auth }) + } + + /// Create a new client codec using the given secret key and the server's public id + pub(crate) fn new_client(secret_key: SecretKey, remote_id: PeerId) -> Result { + Ok(Self { ecies: ECIES::new_client(secret_key, remote_id)?, state: ECIESState::Auth }) + } +} + +impl Decoder for ECIESCodec { + type Item = IngressECIESValue; + type Error = ECIESError; + + #[instrument(level = "trace", skip_all, fields(peer=&*format!("{:?}", self.ecies.remote_id.map(|s| s.to_string())), state=&*format!("{:?}", self.state)))] + fn decode(&mut self, buf: &mut BytesMut) -> Result, Self::Error> { + loop { + match self.state { + ECIESState::Auth => { + trace!("parsing auth"); + if buf.len() < 2 { + return Ok(None) + } + + let payload_size = u16::from_be_bytes([buf[0], buf[1]]) as usize; + let total_size = payload_size + 2; + + if buf.len() < total_size { + trace!("current len {}, need {}", buf.len(), total_size); + return Ok(None) + } + + self.ecies.read_auth(&mut *buf.split_to(total_size))?; + + self.state = ECIESState::Header; + return Ok(Some(IngressECIESValue::AuthReceive(self.ecies.remote_id()))) + } + ECIESState::Ack => { + trace!("parsing ack with len {}", buf.len()); + if buf.len() < 2 { + return Ok(None) + } + + let payload_size = u16::from_be_bytes([buf[0], buf[1]]) as usize; + let total_size = payload_size + 2; + + if buf.len() < total_size { + trace!("current len {}, need {}", buf.len(), total_size); + return Ok(None) + } + + self.ecies.read_ack(&mut *buf.split_to(total_size))?; + + self.state = ECIESState::Header; + return Ok(Some(IngressECIESValue::Ack)) + } + ECIESState::Header => { + if buf.len() < ECIES::header_len() { + trace!("current len {}, need {}", buf.len(), ECIES::header_len()); + return Ok(None) + } + + self.ecies.read_header(&mut *buf.split_to(ECIES::header_len()))?; + + self.state = ECIESState::Body; + } + ECIESState::Body => { + if buf.len() < self.ecies.body_len() { + return Ok(None) + } + + let mut data = buf.split_to(self.ecies.body_len()); + let ret = Bytes::copy_from_slice(self.ecies.read_body(&mut *data)?); + + self.state = ECIESState::Header; + return Ok(Some(IngressECIESValue::Message(ret))) + } + } + } + } +} + +impl Encoder for ECIESCodec { + type Error = io::Error; + + #[instrument(level = "trace", skip(self, buf), fields(peer=&*format!("{:?}", self.ecies.remote_id.map(|s| s.to_string())), state=&*format!("{:?}", self.state)))] + fn encode(&mut self, item: EgressECIESValue, buf: &mut BytesMut) -> Result<(), Self::Error> { + match item { + EgressECIESValue::Auth => { + self.state = ECIESState::Ack; + self.ecies.write_auth(buf); + Ok(()) + } + EgressECIESValue::Ack => { + self.state = ECIESState::Header; + self.ecies.write_ack(buf); + Ok(()) + } + EgressECIESValue::Message(data) => { + if data.len() > MAX_BODY_SIZE { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!( + "body size ({}) exceeds limit ({} bytes)", + data.len(), + MAX_BODY_SIZE + ), + )) + } + + self.ecies.write_header(buf, data.len()); + self.ecies.write_body(buf, &data); + Ok(()) + } + } + } +} diff --git a/crates/net/ecies/src/proto.rs b/crates/net/ecies/src/proto.rs index a78696c1456c..97aeef86e35e 100644 --- a/crates/net/ecies/src/proto.rs +++ b/crates/net/ecies/src/proto.rs @@ -16,150 +16,10 @@ use tokio::{ net::TcpStream, }; use tokio_stream::{Stream, StreamExt}; -use tokio_util::codec::{Decoder, Encoder, Framed}; +use tokio_util::codec::{Decoder, Framed}; use tracing::{debug, instrument, trace}; -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -/// Current ECIES state of a connection -pub enum ECIESState { - /// The first stage of the ECIES handshake, where each side of the connection sends an auth - /// message containing the ephemeral public key, signature of the public key, nonce, and other - /// metadata. - Auth, - - /// The second stage of the ECIES handshake, where each side of the connection sends an ack - /// message containing the nonce and other metadata. - Ack, - Header, - Body, -} - -/// Tokio codec for ECIES -#[derive(Debug)] -pub struct ECIESCodec { - ecies: ECIES, - state: ECIESState, -} - -impl ECIESCodec { - /// Create a new server codec using the given secret key - pub fn new_server(secret_key: SecretKey) -> Result { - Ok(Self { ecies: ECIES::new_server(secret_key)?, state: ECIESState::Auth }) - } - - /// Create a new client codec using the given secret key and the server's public id - pub fn new_client(secret_key: SecretKey, remote_id: PeerId) -> Result { - Ok(Self { ecies: ECIES::new_client(secret_key, remote_id)?, state: ECIESState::Auth }) - } -} - -impl Decoder for ECIESCodec { - type Item = IngressECIESValue; - type Error = ECIESError; - - #[instrument(level = "trace", skip_all, fields(peer=&*format!("{:?}", self.ecies.remote_id.map(|s| s.to_string())), state=&*format!("{:?}", self.state)))] - fn decode(&mut self, buf: &mut BytesMut) -> Result, Self::Error> { - loop { - match self.state { - ECIESState::Auth => { - trace!("parsing auth"); - if buf.len() < 2 { - return Ok(None) - } - - let payload_size = u16::from_be_bytes([buf[0], buf[1]]) as usize; - let total_size = payload_size + 2; - - if buf.len() < total_size { - trace!("current len {}, need {}", buf.len(), total_size); - return Ok(None) - } - - self.ecies.read_auth(&mut *buf.split_to(total_size))?; - - self.state = ECIESState::Header; - return Ok(Some(IngressECIESValue::AuthReceive(self.ecies.remote_id()))) - } - ECIESState::Ack => { - trace!("parsing ack with len {}", buf.len()); - if buf.len() < 2 { - return Ok(None) - } - - let payload_size = u16::from_be_bytes([buf[0], buf[1]]) as usize; - let total_size = payload_size + 2; - - if buf.len() < total_size { - trace!("current len {}, need {}", buf.len(), total_size); - return Ok(None) - } - - self.ecies.read_ack(&mut *buf.split_to(total_size))?; - - self.state = ECIESState::Header; - return Ok(Some(IngressECIESValue::Ack)) - } - ECIESState::Header => { - if buf.len() < ECIES::header_len() { - trace!("current len {}, need {}", buf.len(), ECIES::header_len()); - return Ok(None) - } - - self.ecies.read_header(&mut *buf.split_to(ECIES::header_len()))?; - - self.state = ECIESState::Body; - } - ECIESState::Body => { - if buf.len() < self.ecies.body_len() { - return Ok(None) - } - - let mut data = buf.split_to(self.ecies.body_len()); - let ret = Bytes::copy_from_slice(self.ecies.read_body(&mut *data)?); - - self.state = ECIESState::Header; - return Ok(Some(IngressECIESValue::Message(ret))) - } - } - } - } -} - -impl Encoder for ECIESCodec { - type Error = io::Error; - - #[instrument(level = "trace", skip(self, buf), fields(peer=&*format!("{:?}", self.ecies.remote_id.map(|s| s.to_string())), state=&*format!("{:?}", self.state)))] - fn encode(&mut self, item: EgressECIESValue, buf: &mut BytesMut) -> Result<(), Self::Error> { - match item { - EgressECIESValue::Auth => { - self.state = ECIESState::Ack; - self.ecies.write_auth(buf); - Ok(()) - } - EgressECIESValue::Ack => { - self.state = ECIESState::Header; - self.ecies.write_ack(buf); - Ok(()) - } - EgressECIESValue::Message(data) => { - if data.len() > MAX_BODY_SIZE { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - format!( - "body size ({}) exceeds limit ({} bytes)", - data.len(), - MAX_BODY_SIZE - ), - )) - } - - self.ecies.write_header(buf, data.len()); - self.ecies.write_body(buf, &data); - Ok(()) - } - } - } -} +use crate::codec::ECIESCodec; /// `ECIES` stream over TCP exchanging raw bytes #[derive(Debug)] @@ -168,8 +28,9 @@ pub struct ECIESStream { remote_id: PeerId, } -// This trait is just for instrumenting the stream with a socket addr +/// This trait is just for instrumenting the stream with a socket addr pub trait HasRemoteAddr { + /// Maybe returns a [`SocketAddr`] fn remote_addr(&self) -> Option; } @@ -221,7 +82,7 @@ where debug!("receiving ecies auth"); let remote_id = match &msg { Some(IngressECIESValue::AuthReceive(remote_id)) => *remote_id, - other => { + _ => { return Err(ECIESError::InvalidHandshake { expected: IngressECIESValue::AuthReceive(Default::default()), msg, From 33dca460a2b3a154eff8e9e0ca315004fc75c5ce Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Sat, 15 Oct 2022 16:41:21 -0700 Subject: [PATCH 12/15] refactor(ecies): rename proto to stream --- crates/net/ecies/src/lib.rs | 2 +- crates/net/ecies/src/{proto.rs => stream.rs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename crates/net/ecies/src/{proto.rs => stream.rs} (100%) diff --git a/crates/net/ecies/src/lib.rs b/crates/net/ecies/src/lib.rs index 9ea37451e67b..268ece82d5d2 100644 --- a/crates/net/ecies/src/lib.rs +++ b/crates/net/ecies/src/lib.rs @@ -9,7 +9,7 @@ pub mod algorithm; pub mod mac; -pub mod proto; +pub mod stream; mod util; mod error; diff --git a/crates/net/ecies/src/proto.rs b/crates/net/ecies/src/stream.rs similarity index 100% rename from crates/net/ecies/src/proto.rs rename to crates/net/ecies/src/stream.rs From ed31e6edf6f2bbc39720507880ce8e743e50da88 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Sat, 15 Oct 2022 16:43:32 -0700 Subject: [PATCH 13/15] add test scaffold --- crates/net/ecies/src/stream.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/net/ecies/src/stream.rs b/crates/net/ecies/src/stream.rs index 97aeef86e35e..ba2322d74a56 100644 --- a/crates/net/ecies/src/stream.rs +++ b/crates/net/ecies/src/stream.rs @@ -145,3 +145,13 @@ where Pin::new(&mut self.get_mut().stream).poll_close(cx) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + // TODO: implement test for the proposed + // API: https://github.com/foundry-rs/reth/issues/64#issue-1408708420 + async fn can_write_and_read() {} +} From 84b38b098b1619359baa4e64fe59a16bdaf8479f Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Sat, 15 Oct 2022 20:37:57 -0400 Subject: [PATCH 14/15] implement server/client read/write test --- crates/net/ecies/src/stream.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/crates/net/ecies/src/stream.rs b/crates/net/ecies/src/stream.rs index ba2322d74a56..e45d50ddfe7d 100644 --- a/crates/net/ecies/src/stream.rs +++ b/crates/net/ecies/src/stream.rs @@ -148,10 +148,39 @@ where #[cfg(test)] mod tests { + use secp256k1::{rand, SECP256K1}; + use tokio::net::TcpListener; + + use crate::util::pk2id; + use super::*; #[tokio::test] // TODO: implement test for the proposed // API: https://github.com/foundry-rs/reth/issues/64#issue-1408708420 - async fn can_write_and_read() {} + async fn can_write_and_read() { + let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap(); + let server_key = SecretKey::new(&mut rand::thread_rng()); + + let handle = tokio::spawn(async move { + // roughly based off of the design of tokio::net::TcpListener + let (incoming, _) = listener.accept().await.unwrap(); + let mut stream = ECIESStream::incoming(incoming, server_key).await.unwrap(); + + // use the stream to get the next messagse + let message = stream.next().await.unwrap().unwrap(); + assert_eq!(message, Bytes::from("hello")); + }); + + // create the server pubkey + let server_id = pk2id(&server_key.public_key(SECP256K1)); + + let client_key = SecretKey::new(&mut rand::thread_rng()); + let outgoing = TcpStream::connect("127.0.0.1:8080").await.unwrap(); + let mut client_stream = ECIESStream::connect(outgoing, client_key, server_id).await.unwrap(); + client_stream.send(Bytes::from("hello")).await.unwrap(); + + // make sure the server receives the message and asserts before ending the test + handle.await.unwrap(); + } } From 4e4a655c2334ea44026dd53a8eb33ba86623f917 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Sat, 15 Oct 2022 18:13:19 -0700 Subject: [PATCH 15/15] chore: clippy / fmt --- crates/net/ecies/src/algorithm.rs | 2 +- crates/net/ecies/src/codec.rs | 8 ++++---- crates/net/ecies/src/stream.rs | 3 ++- crates/net/ecies/src/util.rs | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/net/ecies/src/algorithm.rs b/crates/net/ecies/src/algorithm.rs index 8204826c7e06..e1b15915d78e 100644 --- a/crates/net/ecies/src/algorithm.rs +++ b/crates/net/ecies/src/algorithm.rs @@ -38,7 +38,7 @@ fn kdf(secret: H256, s1: &[u8], dest: &mut [u8]) { while written < dest.len() { let mut hasher = Sha256::default(); let ctrs = [(ctr >> 24) as u8, (ctr >> 16) as u8, (ctr >> 8) as u8, ctr as u8]; - hasher.update(&ctrs); + hasher.update(ctrs); hasher.update(secret.as_bytes()); hasher.update(s1); let d = hasher.finalize(); diff --git a/crates/net/ecies/src/codec.rs b/crates/net/ecies/src/codec.rs index caf4445744f5..68c23e0514dc 100644 --- a/crates/net/ecies/src/codec.rs +++ b/crates/net/ecies/src/codec.rs @@ -65,7 +65,7 @@ impl Decoder for ECIESCodec { return Ok(None) } - self.ecies.read_auth(&mut *buf.split_to(total_size))?; + self.ecies.read_auth(&mut buf.split_to(total_size))?; self.state = ECIESState::Header; return Ok(Some(IngressECIESValue::AuthReceive(self.ecies.remote_id()))) @@ -84,7 +84,7 @@ impl Decoder for ECIESCodec { return Ok(None) } - self.ecies.read_ack(&mut *buf.split_to(total_size))?; + self.ecies.read_ack(&mut buf.split_to(total_size))?; self.state = ECIESState::Header; return Ok(Some(IngressECIESValue::Ack)) @@ -95,7 +95,7 @@ impl Decoder for ECIESCodec { return Ok(None) } - self.ecies.read_header(&mut *buf.split_to(ECIES::header_len()))?; + self.ecies.read_header(&mut buf.split_to(ECIES::header_len()))?; self.state = ECIESState::Body; } @@ -105,7 +105,7 @@ impl Decoder for ECIESCodec { } let mut data = buf.split_to(self.ecies.body_len()); - let ret = Bytes::copy_from_slice(self.ecies.read_body(&mut *data)?); + let ret = Bytes::copy_from_slice(self.ecies.read_body(&mut data)?); self.state = ECIESState::Header; return Ok(Some(IngressECIESValue::Message(ret))) diff --git a/crates/net/ecies/src/stream.rs b/crates/net/ecies/src/stream.rs index e45d50ddfe7d..41897f066f99 100644 --- a/crates/net/ecies/src/stream.rs +++ b/crates/net/ecies/src/stream.rs @@ -177,7 +177,8 @@ mod tests { let client_key = SecretKey::new(&mut rand::thread_rng()); let outgoing = TcpStream::connect("127.0.0.1:8080").await.unwrap(); - let mut client_stream = ECIESStream::connect(outgoing, client_key, server_id).await.unwrap(); + let mut client_stream = + ECIESStream::connect(outgoing, client_key, server_id).await.unwrap(); client_stream.send(Bytes::from("hello")).await.unwrap(); // make sure the server receives the message and asserts before ending the test diff --git a/crates/net/ecies/src/util.rs b/crates/net/ecies/src/util.rs index 1d134dc8572a..bd5ee5d97905 100644 --- a/crates/net/ecies/src/util.rs +++ b/crates/net/ecies/src/util.rs @@ -17,7 +17,7 @@ pub(crate) fn hmac_sha256(key: &[u8], input: &[&[u8]], auth_data: &[u8]) -> H256 hmac.update(input); } hmac.update(auth_data); - H256::from_slice(&*hmac.finalize().into_bytes()) + H256::from_slice(&hmac.finalize().into_bytes()) } /// Converts a [secp256k1::PublicKey] to a [PeerId] by stripping the