From 01684e63cd58b410d303eeed2102a9331b237dc7 Mon Sep 17 00:00:00 2001 From: bochaco Date: Fri, 5 Apr 2024 16:13:02 -0300 Subject: [PATCH] feat(networking): adding API to perform sybil attack check --- Cargo.lock | 1 + sn_networking/Cargo.toml | 1 + sn_networking/src/lib.rs | 34 ++++++++++++++---- sn_networking/src/sybil.rs | 71 ++++++++++++++++++++++++++++++++++++++ sn_node/src/node.rs | 9 ++++- 5 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 sn_networking/src/sybil.rs diff --git a/Cargo.lock b/Cargo.lock index a7e77508a4..62cc876dd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5424,6 +5424,7 @@ dependencies = [ "itertools 0.12.1", "libp2p", "libp2p-identity", + "num", "prometheus-client", "quickcheck", "rand", diff --git a/sn_networking/Cargo.toml b/sn_networking/Cargo.toml index ef985f527c..027d682209 100644 --- a/sn_networking/Cargo.toml +++ b/sn_networking/Cargo.toml @@ -44,6 +44,7 @@ libp2p = { version = "0.53", features = [ "yamux", "websocket", ] } +num = "0.4.1" prometheus-client = { version = "0.22", optional = true } rand = { version = "~0.8.5", features = ["small_rng"] } rayon = "1.8.0" diff --git a/sn_networking/src/lib.rs b/sn_networking/src/lib.rs index 31a6d4523f..836029b7ac 100644 --- a/sn_networking/src/lib.rs +++ b/sn_networking/src/lib.rs @@ -26,6 +26,7 @@ mod record_store; mod record_store_api; mod replication_fetcher; mod spends; +mod sybil; pub mod target_arch; mod transfers; mod transport; @@ -42,7 +43,7 @@ pub use self::{ transfers::{get_raw_signed_spends_from_record, get_signed_spend_from_record}, }; -use self::{cmd::SwarmCmd, error::Result}; +use self::{cmd::SwarmCmd, error::Result, sybil::check_for_sybil_attack}; use backoff::{Error as BackoffError, ExponentialBackoff}; use futures::future::select_all; use libp2p::{ @@ -55,7 +56,7 @@ use rand::Rng; use sn_protocol::{ error::Error as ProtocolError, messages::{ChunkProof, Cmd, Nonce, Query, QueryResponse, Request, Response}, - storage::{RecordType, RetryStrategy}, + storage::{ChunkAddress, RecordType, RetryStrategy}, NetworkAddress, PrettyPrintKBucketKey, PrettyPrintRecordKey, }; use sn_transfers::{MainPubkey, NanoTokens, PaymentQuote, QuotingMetrics}; @@ -64,13 +65,15 @@ use std::{ path::PathBuf, sync::Arc, }; -use tokio::sync::{ - mpsc::{self, Sender}, - oneshot, +use tokio::{ + sync::{ + mpsc::{self, Sender}, + oneshot, + }, + time::Duration, }; - -use tokio::time::Duration; use tracing::trace; +use xor_name::XorName; /// The type of quote for a selected payee. pub type PayeeQuote = (PeerId, MainPubkey, PaymentQuote); @@ -812,6 +815,23 @@ impl Network { Ok(closest_peers.into_iter().cloned().collect()) } + /// Using a random address, check if there is a sybil attack around it + pub async fn perform_sybil_attack_check(&self) { + let random_addr = { + let mut rng = rand::thread_rng(); + let chunk_addr = ChunkAddress::new(XorName::random(&mut rng)); + NetworkAddress::from_chunk_address(chunk_addr) + }; + + match self.get_closest_peers(&random_addr, true).await { + Ok(closest_peers) => match check_for_sybil_attack(&closest_peers).await { + Ok(is_attack) => info!(">>> Sybil attack detection result: {is_attack}"), + Err(err) => error!(">>> Failed to check for sybil attack: {err:?}"), + }, + Err(err) => error!(">>> Failed to get closes peer to check for sybil attack: {err:?}"), + } + } + /// Send a `Request` to the provided set of peers and wait for their responses concurrently. /// If `get_all_responses` is true, we wait for the responses from all the peers. /// NB TODO: Will return an error if the request timeouts. diff --git a/sn_networking/src/sybil.rs b/sn_networking/src/sybil.rs new file mode 100644 index 0000000000..f79a959df8 --- /dev/null +++ b/sn_networking/src/sybil.rs @@ -0,0 +1,71 @@ +// Copyright 2024 MaidSafe.net limited. +// +// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. +// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed +// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. Please review the Licences for the specific language governing +// permissions and limitations relating to use of the SAFE Network Software. + +use crate::Result; + +use libp2p::PeerId; +use num::{integer::binomial, pow::Pow}; + +// Threshold to determine if there is an attack using Kullback-Liebler (KL) divergence +// between model peer ids distribution vs. actual distribution around any point in the address space. +const KL_DIVERGENCE_THRESHOLD: f64 = 10f64; // TODO: find a good value + +const K: usize = 20; +const N: usize = 25; // TODO: replace with network size estimation; + +pub(super) async fn check_for_sybil_attack(peers: &[PeerId]) -> Result { + // TODO: do we go ahead even if we don't have at least K peer ids...? + info!( + ">>> CHECKING SYBIL ATTACK WITH {} PEERS: {peers:?}", + peers.len() + ); + let q = num_peers_per_cpl(peers)? / K; + let n = get_net_size_estimate()?; + let p = compute_model_distribution(n); + let kl_divergence = compute_kl_divergence(p, q); + + let is_attack = kl_divergence > KL_DIVERGENCE_THRESHOLD; + Ok(is_attack) +} + +// Formula 6 in page 7 +fn num_peers_per_cpl(peers: &[PeerId]) -> Result { + // TODO! + Ok(0usize) +} + +// Formula 1 and 2 in page ?? +fn get_net_size_estimate() -> Result { + // TODO! + Ok(N) +} + +// Formula 3 in page 7 +fn distrib_j_th_largest_prefix_length(j: usize, x: usize) -> f64 { + (0..j).fold(0f64, |acc, i| { + acc + binomial(N, i) as f64 + * (1f64 - 0.5.pow((x + 1) as f64)).pow((N - i) as f64) + * 0.5.pow(((x + 1) * i) as f64) + }) +} + +// Formula 4 in page 7 +fn compute_model_distribution(x: usize) -> f64 { + let model_dist = (1..K + 1).fold(0f64, |acc, j| { + acc + distrib_j_th_largest_prefix_length(j, x) + - distrib_j_th_largest_prefix_length(j, x - 1) + }); + + model_dist / K as f64 +} + +// Formula 5 in page 7 +fn compute_kl_divergence(model_dist: f64, peers_per_cpl: usize) -> f64 { + // TODO! + model_dist * peers_per_cpl as f64 +} diff --git a/sn_node/src/node.rs b/sn_node/src/node.rs index 8dd1314cfe..1711383be8 100644 --- a/sn_node/src/node.rs +++ b/sn_node/src/node.rs @@ -251,14 +251,21 @@ impl Node { _ = bad_nodes_check_interval.tick() => { let start = std::time::Instant::now(); trace!("Periodic bad_nodes check triggered"); - let network = self.network.clone(); self.record_metrics(Marker::IntervalBadNodesCheckTriggered); + let network = self.network.clone(); let _handle = spawn(async move { Self::try_bad_nodes_check(network, rolling_index).await; trace!("Periodic bad_nodes check took {:?}", start.elapsed()); }); + // we also spawn a task to check for sybil peers + let network = self.network.clone(); + let _handle = spawn(async move { + network.perform_sybil_attack_check().await; + info!(">>> Checking for sybil peers took {:?}", start.elapsed()); + }); + if rolling_index == 511 { rolling_index = 0; } else {