From 4d66caa6ee56773e9c4955d8f469947a4a9f4354 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 11 Nov 2024 19:41:19 -0600 Subject: [PATCH] Add ValidatorInfo to the PeerContacts. Do basic verification --- Cargo.lock | 1 + dht/Cargo.toml | 1 + dht/src/lib.rs | 132 +++++++++++------ network-libp2p/src/behaviour.rs | 11 +- .../src/connection_pool/behaviour.rs | 4 +- network-libp2p/src/dht.rs | 2 +- network-libp2p/src/discovery/behaviour.rs | 21 ++- network-libp2p/src/discovery/handler.rs | 106 ++++++++++---- network-libp2p/src/discovery/peer_contacts.rs | 136 ++++++++++++++++-- network-libp2p/src/network.rs | 12 +- network-libp2p/src/swarm.rs | 22 ++- network-libp2p/tests/discovery.rs | 8 +- network-libp2p/tests/network.rs | 39 +++-- 13 files changed, 379 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7af3722049..650a744f2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4007,6 +4007,7 @@ dependencies = [ "nimiq-blockchain-proxy", "nimiq-keys", "nimiq-log", + "nimiq-network-interface", "nimiq-network-libp2p", "nimiq-serde", "nimiq-utils", diff --git a/dht/Cargo.toml b/dht/Cargo.toml index ddfd26382b..b63b60ee96 100644 --- a/dht/Cargo.toml +++ b/dht/Cargo.toml @@ -26,6 +26,7 @@ nimiq-blockchain-interface = { workspace = true } nimiq-blockchain-proxy = { workspace = true, features = ["full"] } nimiq-keys = { workspace = true } nimiq-log = { workspace = true, optional = true } +nimiq-network-interface = { workspace = true } nimiq-network-libp2p = { workspace = true } nimiq-serde = { workspace = true } nimiq-utils = { workspace = true } diff --git a/dht/src/lib.rs b/dht/src/lib.rs index afcc7deb30..d7198d16cc 100644 --- a/dht/src/lib.rs +++ b/dht/src/lib.rs @@ -1,9 +1,11 @@ use nimiq_blockchain_proxy::BlockchainProxy; use nimiq_keys::{Address, KeyPair}; +use nimiq_network_interface::network::Network as NetworkInterface; use nimiq_network_libp2p::{ dht::{DhtRecord, DhtVerifierError, Verifier as DhtVerifier}, + discovery::peer_contacts::{ValidatorInfoError, ValidatorRecordVerifier}, libp2p::kad::Record, - PeerId, + Network, PeerId, }; use nimiq_serde::Deserialize; use nimiq_utils::tagged_signing::{TaggedSignable, TaggedSigned}; @@ -17,42 +19,50 @@ impl Verifier { pub fn new(blockchain: BlockchainProxy) -> Self { Self { blockchain } } +} - fn verify_validator_record(&self, record: &Record) -> Result { - // Deserialize the value of the record, which is a ValidatorRecord. If it fails return an error. - let validator_record = - TaggedSigned::, KeyPair>::deserialize_from_vec(&record.value) - .map_err(DhtVerifierError::MalformedValue)?; - - // Make sure the peer who signed the record is also the one presented in the record. - if let Some(publisher) = record.publisher { - if validator_record.record.peer_id != publisher { - return Err(DhtVerifierError::PublisherMismatch( - publisher, - validator_record.record.peer_id, - )); - } - } else { - log::warn!("Validating a dht record without a publisher"); - return Err(DhtVerifierError::PublisherMissing); - } +impl ValidatorRecordVerifier for Verifier { + fn verify_validator_record( + &self, + signed_record: &TaggedSigned< + ValidatorRecord<::PeerId>, + KeyPair, + >, + ) -> Result<(), ValidatorInfoError> { + // // Deserialize the value of the record, which is a ValidatorRecord. If it fails return an error. + // let validator_record = + // TaggedSigned::, KeyPair>::deserialize_from_vec(&record.record.value) + // .map_err(DhtVerifierError::MalformedValue)?; - // Deserialize the key of the record which is an Address. If it fails return an error. - let validator_address = Address::deserialize_from_vec(record.key.as_ref()) - .map_err(DhtVerifierError::MalformedKey)?; + // // Make sure the peer who signed the record is also the one presented in the record. + // if let Some(publisher) = record.publisher { + // if validator_record.record.peer_id != publisher { + // return Err(DhtVerifierError::PublisherMismatch( + // publisher, + // validator_record.record.peer_id, + // )); + // } + // } else { + // log::warn!("Validating a dht record without a publisher"); + // return Err(DhtVerifierError::PublisherMissing); + // } + + // // Deserialize the key of the record which is an Address. If it fails return an error. + // let validator_address = Address::deserialize_from_vec(record.key.as_ref()) + // .map_err(DhtVerifierError::MalformedKey)?; // Make sure the validator address used as key is identical to the one in the record. - if validator_record.record.validator_address != validator_address { - return Err(DhtVerifierError::AddressMismatch( - validator_address, - validator_record.record.validator_address, - )); - } + // if signed_record.record.validator_address != validator_address { + // return Err(DhtVerifierError::AddressMismatch( + // validator_address, + // validator_record.record.validator_address, + // )); + // } // Acquire blockchain read access. For now exclude Light clients. let blockchain = match self.blockchain { BlockchainProxy::Light(ref _light_blockchain) => { - return Err(DhtVerifierError::UnknownTag) + panic!("Light Blockchains cannot verify validator records.") } BlockchainProxy::Full(ref full_blockchain) => full_blockchain, }; @@ -61,27 +71,26 @@ impl Verifier { // Get the staking contract to retrieve the public key for verification. let staking_contract = blockchain_read .get_staking_contract_if_complete(None) - .ok_or(DhtVerifierError::StateIncomplete)?; + .ok_or(ValidatorInfoError::StateIncomplete)?; // Get the public key needed for verification. let data_store = blockchain_read.get_staking_contract_store(); let txn = blockchain_read.read_transaction(); let public_key = staking_contract - .get_validator(&data_store.read(&txn), &validator_address) - .ok_or(DhtVerifierError::UnknownValidator(validator_address))? + .get_validator( + &data_store.read(&txn), + &signed_record.record.validator_address, + ) + .ok_or(ValidatorInfoError::UnknownValidator( + signed_record.record.validator_address.clone(), + ))? .signing_key; // Verify the record. - validator_record + signed_record .verify(&public_key) - .then(|| { - DhtRecord::Validator( - record.publisher.unwrap(), - validator_record.record, - record.clone(), - ) - }) - .ok_or(DhtVerifierError::InvalidSignature) + .then(|| ()) + .ok_or(ValidatorInfoError::InvalidSignature) } } @@ -96,7 +105,46 @@ impl DhtVerifier for Verifier { // Depending on tag perform the verification. match tag { - ValidatorRecord::::TAG => self.verify_validator_record(record), + ValidatorRecord::::TAG => { + // Deserialize the value of the record, which is a ValidatorRecord. If it fails return an error. + let validator_record = + TaggedSigned::, KeyPair>::deserialize_from_vec( + &record.value, + ) + .map_err(DhtVerifierError::MalformedValue)?; + + // Make sure the peer who published the record is also the one signed into the record. + if record.publisher.ok_or(DhtVerifierError::MissingPublisher)? + != validator_record.record.peer_id + { + return Err(DhtVerifierError::PublisherMismatch( + record.publisher.unwrap(), + validator_record.record.peer_id, + )); + } + + // Deserialize the key of the record which is an Address. If it fails return an error. + let validator_address = Address::deserialize_from_vec(record.key.as_ref()) + .map_err(DhtVerifierError::MalformedKey)?; + + // Make sure the address used as key is also the one signed into the record. + if validator_address != validator_record.record.validator_address { + return Err(DhtVerifierError::AddressMismatch( + validator_address, + validator_record.record.validator_address, + )); + } + + self.verify_validator_record(&validator_record) + .map_err(DhtVerifierError::ValidatorInfoError) + .and_then(|_| { + Ok(DhtRecord::Validator( + validator_record.record.peer_id, + validator_record.record, + record.clone(), + )) + }) + } _ => { log::error!(tag, "DHT invalid record tag received"); Err(DhtVerifierError::UnknownTag) diff --git a/network-libp2p/src/behaviour.rs b/network-libp2p/src/behaviour.rs index ac26a382af..cef502500c 100644 --- a/network-libp2p/src/behaviour.rs +++ b/network-libp2p/src/behaviour.rs @@ -12,9 +12,11 @@ use parking_lot::RwLock; use rand::rngs::OsRng; use crate::{ - connection_pool, - connection_pool::behaviour::Config as PoolConfig, - discovery::{self, peer_contacts::PeerContactBook}, + connection_pool::{self, behaviour::Config as PoolConfig}, + discovery::{ + self, + peer_contacts::{PeerContactBook, ValidatorRecordVerifier}, + }, dispatch::codecs::MessageCodec, Config, }; @@ -50,6 +52,7 @@ impl Behaviour { contacts: Arc>, peer_score_params: gossipsub::PeerScoreParams, force_dht_server_mode: bool, + #[cfg(feature = "kad")] verifier: Arc, ) -> Self { let public_key = config.keypair.public(); let peer_id = public_key.to_peer_id(); @@ -68,6 +71,8 @@ impl Behaviour { config.discovery.clone(), config.keypair.clone(), Arc::clone(&contacts), + #[cfg(feature = "kad")] + verifier, ); // Gossipsub behaviour diff --git a/network-libp2p/src/connection_pool/behaviour.rs b/network-libp2p/src/connection_pool/behaviour.rs index fef1be89b7..9e898427d6 100644 --- a/network-libp2p/src/connection_pool/behaviour.rs +++ b/network-libp2p/src/connection_pool/behaviour.rs @@ -534,7 +534,7 @@ impl Behaviour { let own_peer_id = own_contact.peer_id(); contacts - .query(self.required_services) + .query(self.required_services, true) .filter_map(|contact| { let peer_id = contact.peer_id(); if peer_id != own_peer_id @@ -562,7 +562,7 @@ impl Behaviour { let own_peer_id = own_contact.peer_id(); contacts - .query(services) + .query(services, true) .filter_map(|contact| { let peer_id = contact.peer_id(); if peer_id != own_peer_id diff --git a/network-libp2p/src/dht.rs b/network-libp2p/src/dht.rs index 7502d190c1..a0fbe47307 100644 --- a/network-libp2p/src/dht.rs +++ b/network-libp2p/src/dht.rs @@ -5,7 +5,7 @@ use nimiq_serde::DeserializeError; use nimiq_validator_network::validator_record::ValidatorRecord; pub use crate::network_types::DhtRecord; -use crate::Network; +use crate::{discovery::peer_contacts::ValidatorInfoError, Network}; #[derive(Debug)] pub enum DhtVerifierError { diff --git a/network-libp2p/src/discovery/behaviour.rs b/network-libp2p/src/discovery/behaviour.rs index 4711f96389..8aaa2a3bad 100644 --- a/network-libp2p/src/discovery/behaviour.rs +++ b/network-libp2p/src/discovery/behaviour.rs @@ -11,7 +11,7 @@ use libp2p::{ identity::Keypair, swarm::{ behaviour::{ConnectionClosed, ConnectionEstablished}, - CloseConnection, ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, ToSwarm, + ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, ToSwarm, }, Multiaddr, PeerId, }; @@ -22,7 +22,7 @@ use parking_lot::RwLock; use super::{ handler::{Handler, HandlerOutEvent}, - peer_contacts::{PeerContact, PeerContactBook}, + peer_contacts::{PeerContact, PeerContactBook, ValidatorRecordVerifier}, }; #[derive(Clone, Debug)] @@ -115,6 +115,10 @@ pub struct Behaviour { /// Timer to do house-keeping in the peer address book. house_keeping_timer: Interval, + + /// dht verifier TODO + #[cfg(feature = "kad")] + verifier: Arc, } impl Behaviour { @@ -122,6 +126,7 @@ impl Behaviour { config: Config, keypair: Keypair, peer_contact_book: Arc>, + #[cfg(feature = "kad")] verifier: Arc, ) -> Self { let house_keeping_timer = interval(config.house_keeping_interval); peer_contact_book.write().update_own_contact(&keypair); @@ -139,6 +144,8 @@ impl Behaviour { peer_contact_book, events, house_keeping_timer, + #[cfg(feature = "kad")] + verifier, } } @@ -177,6 +184,8 @@ impl NetworkBehaviour for Behaviour { self.keypair.clone(), self.peer_contact_book(), remote_addr.clone(), + #[cfg(feature = "kad")] + Arc::clone(&self.verifier), )) } @@ -194,6 +203,8 @@ impl NetworkBehaviour for Behaviour { self.keypair.clone(), self.peer_contact_book(), addr.clone(), + #[cfg(feature = "kad")] + Arc::clone(&self.verifier), )) } @@ -288,10 +299,8 @@ impl NetworkBehaviour for Behaviour { .push_back(ToSwarm::NewExternalAddrCandidate(observed_address)); } HandlerOutEvent::Update => self.events.push_back(ToSwarm::GenerateEvent(Event::Update)), - HandlerOutEvent::Error(_) => self.events.push_back(ToSwarm::CloseConnection { - peer_id, - connection: CloseConnection::All, - }), + // Errors must not result in a closed connection as light clients are unable to verify ValidatorRecord. + HandlerOutEvent::Error(error) => log::trace!(?error, "Received invalid contact"), } } } diff --git a/network-libp2p/src/discovery/handler.rs b/network-libp2p/src/discovery/handler.rs index 7a04d03c58..5079ef6ed9 100644 --- a/network-libp2p/src/discovery/handler.rs +++ b/network-libp2p/src/discovery/handler.rs @@ -33,7 +33,10 @@ use thiserror::Error; use super::{ behaviour::Config, message_codec::{MessageReader, MessageWriter}, - peer_contacts::{PeerContactBook, SignedPeerContact}, + peer_contacts::{ + PeerContactBook, PeerContactError, SignedPeerContact, ValidatorInfoError, + ValidatorRecordVerifier, + }, protocol::{ChallengeNonce, DiscoveryMessage, DiscoveryProtocol}, }; use crate::{AUTONAT_DIAL_BACK_PROTOCOL, AUTONAT_DIAL_REQUEST_PROTOCOL}; @@ -134,6 +137,11 @@ pub struct Handler { /// The peer contact book peer_contact_book: Arc>, + /// Used to verify PeerContacts. + /// Required as contacts could contain a ValidatorInfo, for which a current verification key is required. + #[cfg(feature = "kad")] + verifier: Arc, + /// The peer address we're connected to (address that got us connected). peer_address: Multiaddr, @@ -179,6 +187,7 @@ impl Handler { keypair: Keypair, peer_contact_book: Arc>, peer_address: Multiaddr, + #[cfg(feature = "kad")] verifier: Arc, ) -> Self { if let Some(peer_contact) = peer_contact_book.write().get(&peer_id) { if let Some(outer_protocol_address) = outer_protocol_address(&peer_address) { @@ -202,6 +211,8 @@ impl Handler { inbound: None, outbound: None, waker: None, + #[cfg(feature = "kad")] + verifier, events: VecDeque::new(), } } @@ -232,7 +243,7 @@ impl Handler { let mut rng = thread_rng(); peer_contact_book - .query(self.services_filter) + .query(self.services_filter, false) .choose_multiple(&mut rng, limit) .into_iter() .map(|c| c.signed().clone()) @@ -287,6 +298,44 @@ pub(crate) fn outer_protocol_address(addr: &Multiaddr) -> Option { .unwrap_or(None) } +fn filter_contact( + timestamp: u64, + #[cfg(feature = "kad")] verifier: Arc, +) -> Box Option> { + Box::new(move |mut peer_contact: SignedPeerContact| { + match peer_contact.verify( + #[cfg(feature = "kad")] + Arc::clone(&verifier), + ) { + // Contacts with too many addresses or an invalid peer signature are rejected + // and removed from the collection + Err(PeerContactError::AdvertisedAddressesExceeded) + | Err(PeerContactError::InvalidSignature) => None, + // If there is a validator record, but the state is incomplete, + // then it cannot be verified and must be checked again at a later time. + Err(PeerContactError::ValidatorRecord(ValidatorInfoError::StateIncomplete)) => { + peer_contact.local_only = true; + Some(peer_contact) + } + // If there is a validator record but either the signature is invalid, or the validator is unknown, + // the head timestamp of the blockchain and the creation timestamp must be compared. + Err(PeerContactError::ValidatorRecord(ValidatorInfoError::InvalidSignature)) + | Err(PeerContactError::ValidatorRecord(ValidatorInfoError::UnknownValidator(_))) => { + // Set it to local only. Some contacts will be discarded by the next condition still. + peer_contact.local_only = true; + + // Retain only contacts which are in the future, as they may still verify then. + if timestamp < peer_contact.inner.timestamp() { + Some(peer_contact) + } else { + None + } + } + Ok(_) => Some(peer_contact), + } + }) +} + impl ConnectionHandler for Handler { type FromBehaviour = (); type ToBehaviour = HandlerOutEvent; @@ -526,7 +575,13 @@ impl ConnectionHandler for Handler { peer_contacts, } => { // Check the peer contact for a valid signature. - if !peer_contact.verify() { + let Some(peer_contact) = filter_contact( + 0, + #[cfg(feature = "kad")] + Arc::clone(&self.verifier), + )( + peer_contact.clone() + ) else { return Poll::Ready( ConnectionHandlerEvent::NotifyBehaviour( HandlerOutEvent::Error( @@ -536,7 +591,7 @@ impl ConnectionHandler for Handler { ), ), ); - } + }; if self.peer_id != peer_contact.peer_id() { return Poll::Ready( @@ -574,19 +629,14 @@ impl ConnectionHandler for Handler { ), ); } - for peer_contact in &peer_contacts { - if !peer_contact.verify() { - return Poll::Ready( - ConnectionHandlerEvent::NotifyBehaviour( - HandlerOutEvent::Error( - Error::InvalidPeerContactSignature { - peer_contact: peer_contact.clone(), - }, - ), - ), - ); - } - } + let peer_contacts: Vec = peer_contacts + .into_iter() + .filter_map(filter_contact( + 0, + #[cfg(feature = "kad")] + Arc::clone(&self.verifier), + )) + .collect(); let mut peer_contact_book = self.peer_contact_book.write(); @@ -685,19 +735,15 @@ impl ConnectionHandler for Handler { ), ); } - for peer_contact in &peer_contacts { - if !peer_contact.verify() { - return Poll::Ready( - ConnectionHandlerEvent::NotifyBehaviour( - HandlerOutEvent::Error( - Error::InvalidPeerContactSignature { - peer_contact: peer_contact.clone(), - }, - ), - ), - ); - } - } + + let peer_contacts: Vec = peer_contacts + .into_iter() + .filter_map(filter_contact( + 0, + #[cfg(feature = "kad")] + Arc::clone(&self.verifier), + )) + .collect(); // Insert the new peer contacts into the peer contact book. self.peer_contact_book.write().insert_all_filtered( diff --git a/network-libp2p/src/discovery/peer_contacts.rs b/network-libp2p/src/discovery/peer_contacts.rs index 2e6c0b1be4..0d2467cbd6 100644 --- a/network-libp2p/src/discovery/peer_contacts.rs +++ b/network-libp2p/src/discovery/peer_contacts.rs @@ -16,7 +16,7 @@ use nimiq_network_interface::{ network::Network as NetworkInterface, peer_info::{PeerInfo, Services}, }; -use nimiq_utils::tagged_signing::{TaggedKeyPair, TaggedSignable, TaggedSignature}; +use nimiq_utils::tagged_signing::{TaggedKeyPair, TaggedSignable, TaggedSignature, TaggedSigned}; use nimiq_validator_network::validator_record::ValidatorRecord; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; @@ -28,6 +28,10 @@ use crate::{utils, Network}; pub enum PeerContactError { #[error("Exceeded number of advertised addresses")] AdvertisedAddressesExceeded, + #[error("Validator Record failed to verify")] + ValidatorRecord(ValidatorInfoError), + #[error("Contact signature is invalid")] + InvalidSignature, } /// The validator info contains all information which is not present in a [PeerContact] @@ -43,10 +47,75 @@ pub struct ValidatorInfo { signature: TaggedSignature::PeerId>, KeyPair>, } +#[derive(Debug)] +pub enum ValidatorInfoError { + StateIncomplete, + InvalidSignature, + UnknownValidator(Address), +} + +impl ValidatorInfo { + pub fn new( + validator_address: Address, + signature: TaggedSignature::PeerId>, KeyPair>, + ) -> Self { + Self { + validator_address, + signature, + } + } + + pub fn verify( + &self, + timestamp: u64, + peer_id: PeerId, + verification_key: &::PublicKey, + ) -> Result<(), ValidatorInfoError> { + // Reconstruct the record + let record = ValidatorRecord { + validator_address: self.validator_address.clone(), + timestamp, + peer_id, + }; + + // Reconstruct the signed record. + let signed_record = TaggedSigned::new(record, self.signature.clone()); + + // Verify the record and return the result. + signed_record + .verify(verification_key) + .then_some(()) + .ok_or(ValidatorInfoError::InvalidSignature) + } +} + +pub trait ValidatorRecordVerifier: Send + Sync { + fn verify_validator_record( + &self, + signed_record: &TaggedSigned< + ValidatorRecord<::PeerId>, + KeyPair, + >, + ) -> Result<(), ValidatorInfoError>; +} + +impl ValidatorRecordVerifier for () { + fn verify_validator_record( + &self, + _signed_record: &TaggedSigned< + ValidatorRecord<::PeerId>, + KeyPair, + >, + ) -> Result<(), ValidatorInfoError> { + Ok(()) + } +} + /// A plain peer contact. This contains: /// /// - A set of multi-addresses for the peer. /// - The peer's public key. +/// - An optional [ValidatorInfo] in case this peer is a running a validator. /// - A bitmask of the services supported by this peer. /// - A timestamp when this contact information was generated. /// @@ -60,6 +129,9 @@ pub struct PeerContact { #[serde(with = "self::serde_public_key")] pub public_key: PublicKey, + /// Optional validator info in case the node is a validator. + pub validator_info: Option, + /// Services supported by this peer. pub services: Services, @@ -91,6 +163,7 @@ impl PeerContact { Ok(Self { addresses, public_key, + validator_info: None, services, validator_info: None, timestamp, @@ -99,7 +172,7 @@ impl PeerContact { /// Derives the peer ID from the public key pub fn peer_id(&self) -> PeerId { - self.public_key.clone().to_peer_id() + self.public_key.to_peer_id() } /// Returns the timestamp of the contact. It is generally set to the time the contact was created. @@ -123,6 +196,7 @@ impl PeerContact { SignedPeerContact { inner: self, signature, + local_only: false, } } @@ -160,10 +234,35 @@ impl PeerContact { /// Verifies whether the lengths of the advertised addresses are within /// the expected limits. This is helpful to verify a received peer contact. - pub fn verify(&self) -> Result<(), PeerContactError> { + pub fn verify( + &self, + #[cfg(feature = "kad")] verifier: Arc, + ) -> Result<(), PeerContactError> { if self.addresses.len() > Self::MAX_ADDRESSES { return Err(PeerContactError::AdvertisedAddressesExceeded); } + + // In case the Contact includes a ValidatorInfo it also needs to be verified. + #[cfg(feature = "kad")] + if let Some(ValidatorInfo { + validator_address, + signature, + }) = self.validator_info.clone() + { + // Reconstruct the record + let record = ValidatorRecord { + validator_address, + timestamp: self.timestamp, + peer_id: self.peer_id(), + }; + + let signed_record = TaggedSigned::new(record, signature); + + verifier + .verify_validator_record(&signed_record) + .map_err(PeerContactError::ValidatorRecord)?; + } + Ok(()) } } @@ -180,17 +279,30 @@ pub struct SignedPeerContact { /// The signature over the serialized peer contact. pub signature: TaggedSignature, + + #[serde(skip)] + pub local_only: bool, } impl SignedPeerContact { /// Verifies that the signature is valid for this peer contact and also does /// intrinsic verification on the inner PeerContact. - pub fn verify(&self) -> bool { - if self.inner.verify().is_err() { - return false; - }; - self.signature + pub fn verify( + &self, + #[cfg(feature = "kad")] verifier: Arc, + ) -> Result<(), PeerContactError> { + // The record signature must be verifid first. + if !self + .signature .tagged_verify(&self.inner, &self.inner.public_key) + { + return Err(PeerContactError::InvalidSignature); + } + + self.inner.verify( + #[cfg(feature = "kad")] + verifier, + ) } /// Gets the public key of this peer contact. @@ -509,11 +621,15 @@ impl PeerContactBook { /// Gets a set of peer contacts given a services filter. /// Every peer contact that matches such services will be returned. - pub fn query(&self, services: Services) -> impl Iterator> + '_ { + pub fn query( + &self, + services: Services, + include_local_only: bool, + ) -> impl Iterator> + '_ { // TODO: This is a naive implementation // TODO: Sort by score? self.peer_contacts.iter().filter_map(move |(_, contact)| { - if contact.matches(services) { + if !contact.contact.local_only || include_local_only && contact.matches(services) { Some(Arc::clone(contact)) } else { None diff --git a/network-libp2p/src/network.rs b/network-libp2p/src/network.rs index e60e8d471f..ec5f0b6c28 100644 --- a/network-libp2p/src/network.rs +++ b/network-libp2p/src/network.rs @@ -38,7 +38,7 @@ use tokio_stream::wrappers::{BroadcastStream, ReceiverStream}; use crate::network_metrics::NetworkMetrics; use crate::{ dht, - discovery::peer_contacts::PeerContactBook, + discovery::peer_contacts::{PeerContactBook, ValidatorRecordVerifier}, network_types::{GossipsubId, NetworkAction, ValidateMessage}, rate_limiting::RateLimitConfig, swarm::{new_swarm, swarm_task}, @@ -79,7 +79,7 @@ impl Network { /// pub async fn new( config: Config, - #[cfg(feature = "kad")] dht_verifier: impl dht::Verifier + 'static, + #[cfg(feature = "kad")] verifier: impl dht::Verifier + ValidatorRecordVerifier + 'static, ) -> Self { let required_services = config.required_services; // TODO: persist to disk @@ -100,11 +100,17 @@ impl Network { // In memory transport we don't have a mechanism that sets the DHT in server mode such as confirming an address // with Autonat. This is because Autonat v1 only works with IP addresses. let force_dht_server_mode = config.memory_transport; + + #[cfg(feature = "kad")] + let verifier = Arc::new(verifier); + let swarm = new_swarm( config, Arc::clone(&contacts), params.clone(), force_dht_server_mode, + #[cfg(feature = "kad")] + (Arc::clone(&verifier) as Arc), ); let local_peer_id = *Swarm::local_peer_id(&swarm); @@ -128,7 +134,7 @@ impl Network { update_scores, Arc::clone(&contacts), #[cfg(feature = "kad")] - dht_verifier, + (Arc::clone(&verifier) as Arc), force_dht_server_mode, dht_quorum, #[cfg(feature = "metrics")] diff --git a/network-libp2p/src/swarm.rs b/network-libp2p/src/swarm.rs index e4096ab9be..ef0c763a37 100644 --- a/network-libp2p/src/swarm.rs +++ b/network-libp2p/src/swarm.rs @@ -45,7 +45,10 @@ use crate::network_metrics::NetworkMetrics; use crate::{ autonat::NatStatus, behaviour, dht, - discovery::{self, peer_contacts::PeerContactBook}, + discovery::{ + self, + peer_contacts::{PeerContactBook, ValidatorRecordVerifier}, + }, network_types::{ DhtBootStrapState, DhtRecord, DhtResults, GossipsubTopicInfo, NetworkAction, TaskState, ValidateMessage, @@ -63,7 +66,7 @@ struct EventInfo<'a> { connected_peers: &'a RwLock>, rate_limiting: &'a mut RateLimits, #[cfg(feature = "kad")] - dht_verifier: &'a dyn dht::Verifier, + dht_verifier: Arc, #[cfg(feature = "metrics")] metrics: &'a Arc, } @@ -73,6 +76,7 @@ pub(crate) fn new_swarm( contacts: Arc>, peer_score_params: gossipsub::PeerScoreParams, force_dht_server_mode: bool, + #[cfg(feature = "kad")] verifier: Arc, ) -> Swarm { let keypair = config.keypair.clone(); let transport = new_transport( @@ -83,8 +87,14 @@ pub(crate) fn new_swarm( ) .unwrap(); - let behaviour = - behaviour::Behaviour::new(config, contacts, peer_score_params, force_dht_server_mode); + let behaviour = behaviour::Behaviour::new( + config, + contacts, + peer_score_params, + force_dht_server_mode, + #[cfg(feature = "kad")] + verifier, + ); // TODO add proper config #[cfg(not(target_family = "wasm"))] @@ -114,7 +124,7 @@ pub(crate) async fn swarm_task( connected_peers: Arc>>, mut update_scores: Interval, contacts: Arc>, - #[cfg(feature = "kad")] dht_verifier: impl dht::Verifier, + #[cfg(feature = "kad")] dht_verifier: Arc, force_dht_server_mode: bool, dht_quorum: NonZeroU8, #[cfg(feature = "metrics")] metrics: Arc, @@ -162,7 +172,7 @@ pub(crate) async fn swarm_task( connected_peers: &connected_peers, rate_limiting: &mut rate_limiting, #[cfg(feature = "kad")] - dht_verifier: &dht_verifier, + dht_verifier: Arc::clone(&dht_verifier), #[cfg( feature = "metrics")] metrics: &metrics, }, ); diff --git a/network-libp2p/tests/discovery.rs b/network-libp2p/tests/discovery.rs index 900dbdce01..8d22baa570 100644 --- a/network-libp2p/tests/discovery.rs +++ b/network-libp2p/tests/discovery.rs @@ -82,8 +82,12 @@ impl TestNode { true, ))); - let behaviour = - discovery::Behaviour::new(config, keypair.clone(), Arc::clone(&peer_contact_book)); + let behaviour = discovery::Behaviour::new( + config, + keypair.clone(), + Arc::clone(&peer_contact_book), + Arc::new(()), + ); let mut swarm = SwarmBuilder::with_existing_identity(keypair) .with_tokio() diff --git a/network-libp2p/tests/network.rs b/network-libp2p/tests/network.rs index 7c371147d2..b097a7485e 100644 --- a/network-libp2p/tests/network.rs +++ b/network-libp2p/tests/network.rs @@ -15,7 +15,10 @@ use nimiq_network_interface::{ }; use nimiq_network_libp2p::{ dht, - discovery::{self, peer_contacts::PeerContact}, + discovery::{ + self, + peer_contacts::{PeerContact, ValidatorInfoError, ValidatorRecordVerifier}, + }, Config, Network, }; use nimiq_serde::{Deserialize, Serialize}; @@ -203,6 +206,26 @@ impl Verifier { } } +impl ValidatorRecordVerifier for Verifier { + fn verify_validator_record( + &self, + signed_record: &TaggedSigned< + ValidatorRecord<::PeerId>, + KeyPair, + >, + ) -> Result<(), ValidatorInfoError> { + let keys = self.keys.read(); + let public_key = keys.get(&signed_record.record.validator_address).ok_or( + ValidatorInfoError::UnknownValidator(signed_record.record.validator_address.clone()), + )?; + + signed_record + .verify(&public_key) + .then(|| ()) + .ok_or(ValidatorInfoError::InvalidSignature) + } +} + impl dht::Verifier for Verifier { fn verify( &self, @@ -228,21 +251,15 @@ impl dht::Verifier for Verifier { let validator_address = Address::deserialize_from_vec(record.key.as_ref()) .map_err(dht::DhtVerifierError::MalformedKey)?; - let keys = self.keys.read(); - let public_key = keys - .get(&validator_address) - .ok_or(dht::DhtVerifierError::UnknownValidator(validator_address))?; - - validator_record - .verify(&public_key) - .then(|| { + self.verify_validator_record(&validator_record) + .map(|_| { dht::DhtRecord::Validator( - record.publisher.unwrap(), + validator_record.record.peer_id, validator_record.record, record.clone(), ) }) - .ok_or(dht::DhtVerifierError::InvalidSignature) + .map_err(dht::DhtVerifierError::ValidatorInfoError) } }