Skip to content

Commit

Permalink
feat(networking): adding API to perform sybil attack check
Browse files Browse the repository at this point in the history
  • Loading branch information
bochaco committed Apr 5, 2024
1 parent 52d9673 commit 01684e6
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 8 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sn_networking/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
34 changes: 27 additions & 7 deletions sn_networking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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::{
Expand All @@ -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};
Expand All @@ -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);
Expand Down Expand Up @@ -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.
Expand Down
71 changes: 71 additions & 0 deletions sn_networking/src/sybil.rs
Original file line number Diff line number Diff line change
@@ -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

Check notice

Code scanning / devskim

A "TODO" or similar was left in source code, possibly indicating incomplete functionality Note

Suspicious comment

const K: usize = 20;
const N: usize = 25; // TODO: replace with network size estimation;

Check notice

Code scanning / devskim

A "TODO" or similar was left in source code, possibly indicating incomplete functionality Note

Suspicious comment

pub(super) async fn check_for_sybil_attack(peers: &[PeerId]) -> Result<bool> {
// TODO: do we go ahead even if we don't have at least K peer ids...?

Check notice

Code scanning / devskim

A "TODO" or similar was left in source code, possibly indicating incomplete functionality Note

Suspicious comment
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<usize> {
// TODO!

Check notice

Code scanning / devskim

A "TODO" or similar was left in source code, possibly indicating incomplete functionality Note

Suspicious comment
Ok(0usize)
}

// Formula 1 and 2 in page ??
fn get_net_size_estimate() -> Result<usize> {
// TODO!

Check notice

Code scanning / devskim

A "TODO" or similar was left in source code, possibly indicating incomplete functionality Note

Suspicious comment
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!

Check notice

Code scanning / devskim

A "TODO" or similar was left in source code, possibly indicating incomplete functionality Note

Suspicious comment
model_dist * peers_per_cpl as f64
}
9 changes: 8 additions & 1 deletion sn_node/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 01684e6

Please sign in to comment.