diff --git a/sn_networking/src/lib.rs b/sn_networking/src/lib.rs index 836029b7ac..87d26f4535 100644 --- a/sn_networking/src/lib.rs +++ b/sn_networking/src/lib.rs @@ -817,14 +817,15 @@ impl Network { /// 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 (random_addr, cid) = { let mut rng = rand::thread_rng(); - let chunk_addr = ChunkAddress::new(XorName::random(&mut rng)); - NetworkAddress::from_chunk_address(chunk_addr) + let cid = XorName::random(&mut rng); + let chunk_addr = ChunkAddress::new(cid); + (NetworkAddress::from_chunk_address(chunk_addr), cid) }; match self.get_closest_peers(&random_addr, true).await { - Ok(closest_peers) => match check_for_sybil_attack(&closest_peers).await { + Ok(closest_peers) => match check_for_sybil_attack(&closest_peers, &cid).await { Ok(is_attack) => info!(">>> Sybil attack detection result: {is_attack}"), Err(err) => error!(">>> Failed to check for sybil attack: {err:?}"), }, diff --git a/sn_networking/src/sybil.rs b/sn_networking/src/sybil.rs index f79a959df8..47fceb1c7a 100644 --- a/sn_networking/src/sybil.rs +++ b/sn_networking/src/sybil.rs @@ -10,6 +10,7 @@ use crate::Result; use libp2p::PeerId; use num::{integer::binomial, pow::Pow}; +use xor_name::{XorName, XOR_NAME_LEN}; // 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. @@ -18,15 +19,16 @@ 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 { +pub(super) async fn check_for_sybil_attack(peers: &[PeerId], cid: &XorName) -> 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 q = num_peers_per_cpl(peers, cid); let n = get_net_size_estimate()?; let p = compute_model_distribution(n); + info!(">>> MODEL DIST WITH {} PEERS: {p}", peers.len()); let kl_divergence = compute_kl_divergence(p, q); let is_attack = kl_divergence > KL_DIVERGENCE_THRESHOLD; @@ -34,9 +36,25 @@ pub(super) async fn check_for_sybil_attack(peers: &[PeerId]) -> Result { } // Formula 6 in page 7 -fn num_peers_per_cpl(peers: &[PeerId]) -> Result { - // TODO! - Ok(0usize) +fn num_peers_per_cpl(peers: &[PeerId], cid: &XorName) -> usize { + let peers_per_cpl = peers.iter().fold(0, |acc, peer| { + let peer_kad_id = XorName::from_content(&peer.to_bytes()); + acc + common_prefix(&peer_kad_id, cid) + }); + + peers_per_cpl / K +} + +// TODO: this is a copy of the private XorName::common_prefix method which could be made public. +/// Returns the length of the common prefix with the `other` name; e. g. +/// the when `other = 11110000` and `self = 11111111` this is 4. +fn common_prefix(lhs: &XorName, rhs: &XorName) -> usize { + for byte_index in 0..XOR_NAME_LEN { + if lhs[byte_index] != rhs[byte_index] { + return (byte_index * 8) + (lhs[byte_index] ^ rhs[byte_index]).leading_zeros() as usize; + } + } + 8 * XOR_NAME_LEN } // Formula 1 and 2 in page ??