Skip to content

feat(peerup): consolidate crypto, add distributed consensus and DHT operations#23

Open
veryCrunchy wants to merge 1 commit intomainfrom
peerup-improvements
Open

feat(peerup): consolidate crypto, add distributed consensus and DHT operations#23
veryCrunchy wants to merge 1 commit intomainfrom
peerup-improvements

Conversation

@veryCrunchy
Copy link
Member

  • Move crypto into peerup crate: Ed25519 signing, X25519 ECDH, XChaCha20-Poly1305 AEAD
  • Add distributed consensus, DHT storage, metadata, sync, visibility modules
  • Add trust module for peer trust scoring
  • Add Kademlia DHT put/get/provide operations (node/core/dht.rs)
  • Refactor network event conversions into dedicated modules (kad, mdns, relay, request_response)
  • Remove flat conv files (kad_conv, mdns_conv, relay_conv)
  • Fix P2P resilience: PeerUPEvent::Noop for unhandled events
  • Update gossipsub, node_methods, events to support new architecture

…perations

- Move crypto into peerup crate: Ed25519 signing, X25519 ECDH, XChaCha20-Poly1305 AEAD
- Add distributed consensus, DHT storage, metadata, sync, visibility modules
- Add trust module for peer trust scoring
- Add Kademlia DHT put/get/provide operations (node/core/dht.rs)
- Refactor network event conversions into dedicated modules (kad, mdns, relay, request_response)
- Remove flat conv files (kad_conv, mdns_conv, relay_conv)
- Fix P2P resilience: PeerUPEvent::Noop for unhandled events
- Update gossipsub, node_methods, events to support new architecture
Copilot AI review requested due to automatic review settings February 21, 2026 06:04
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request consolidates cryptographic operations into the peerup crate and adds comprehensive distributed peer-to-peer functionality including consensus, DHT operations, and peer data synchronization. The changes represent a significant architectural evolution from a simple P2P library to a full-featured distributed system framework.

Changes:

  • Moved crypto functionality (Ed25519 signing/verification, X25519 ECDH, XChaCha20-Poly1305 AEAD) into peerup crate
  • Added distributed consensus protocol with rate limiting and Byzantine fault tolerance via signature verification
  • Added DHT operations for public monitor discovery, metadata storage, and peer coordination
  • Introduced peer data storage, sync manager, and visibility types for managing distributed state
  • Refactored network event conversions into dedicated modules and introduced PeerUPEvent::Noop for unhandled events
  • Added trust module placeholder for future peer reputation functionality

Reviewed changes

Copilot reviewed 31 out of 32 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
crates/peerup/src/crypto/keys.rs Ed25519 keypair with X25519 derivation for encryption
crates/peerup/src/crypto/signing.rs Ed25519 signing operations for bytes and JSON
crates/peerup/src/crypto/verification.rs Ed25519 signature verification
crates/peerup/src/crypto/encryption.rs X25519 + XChaCha20-Poly1305 encryption for private data
crates/peerup/src/crypto/signatures.rs Signed result verification for P2P distribution
crates/peerup/src/crypto/mod.rs Crypto module exports
crates/peerup/src/distributed/mod.rs Distributed module structure and configuration
crates/peerup/src/distributed/visibility.rs Monitor visibility types and orchestration scheduling
crates/peerup/src/distributed/sync.rs Sync manager for peer data synchronization
crates/peerup/src/distributed/storage.rs Peer data storage abstraction and in-memory implementation
crates/peerup/src/distributed/metadata.rs DHT-stored metadata for rate limits and trust scores
crates/peerup/src/distributed/messages.rs Message types for peer data queries and sync
crates/peerup/src/distributed/dht.rs DHT-based public monitor discovery
crates/peerup/src/distributed/consensus.rs Consensus protocol with rate limiting and signature verification
crates/peerup/src/trust/mod.rs Placeholder module for trust management
crates/peerup/src/node/core/dht.rs Kademlia DHT put/get/provide operations
crates/peerup/src/node/core/gossipsub.rs GossipSub topic subscription and publishing methods
crates/peerup/src/node/core/node_methods.rs Bootstrap peer handling improvements
crates/peerup/src/node/core/mod.rs Added DHT module export
crates/peerup/src/node/events.rs Updated event handler for probe requests
crates/peerup/src/node/config/types.rs Added distributed peer data configuration options
crates/peerup/src/network/events.rs Added DHT record events and Noop variant
crates/peerup/src/network/conversions/kad.rs Kademlia event conversion with DHT record handling
crates/peerup/src/network/conversions/mdns.rs mDNS event conversion using Noop for empty lists
crates/peerup/src/network/conversions/relay.rs Relay event conversion using Noop for unhandled events
crates/peerup/src/network/conversions/request_response.rs Request/response event conversion using Noop
crates/peerup/src/network/kad_conv.rs Removed flat conversion file
crates/peerup/src/network/mdns_conv.rs Removed flat conversion file
crates/peerup/src/network/relay_conv.rs Removed flat conversion file
crates/peerup/src/lib.rs Updated library documentation and exports
crates/peerup/Cargo.toml Added crypto dependencies

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +10
//! Admin trust management for decentralized authentication.
//!
//! This module provides:
//! - HTTPS bootstrap from trusted sources
//! - Admin key caching in DHT
//! - Trust chain management
//! - Revocation handling

// This module will be expanded to hold trust-related operations
// Currently serves as architectural placeholder for security separation
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The trust module is added but only contains a placeholder comment. While architectural placeholders can be useful, this module is exported from lib.rs and appears in the public API, but provides no actual functionality. This could confuse users of the crate.

Either implement basic trust functionality, or remove the module from the public exports until it's actually implemented. If keeping it, consider adding a clear #[doc(hidden)] or noting in the module documentation that it's a placeholder for future development.

Copilot uses AI. Check for mistakes.
Comment on lines 20 to +30
PeerUPEvent::ProbeRequestReceived { peer, request, channel } => {
info!("Received probe request from {}: {:?}", peer, request);
handle_probe_request(peer, request, channel);
// We cannot send a response here because we don't have swarm access.
// Log the request and drop the channel — the peer will see an inbound
// failure. Production consumers should handle this variant in their
// own swarm event loop where they have mutable access to the behaviour.
warn!(
"ProbeRequestReceived from {} ({:?}) — dropping channel \
(handle in your own event loop to respond)",
peer, request
);
drop(channel);
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment and warning about dropping the response channel is helpful, but the approach of logging at warn level and explicitly dropping the channel in a general-purpose event handler is problematic. If this handler is called in a context where the channel should be handled (like in a service), the warning will fire unnecessarily and the peer will see an inbound failure.

Consider either removing this handler entirely (since the comment says consumers should handle this in their own event loop), or at least log at debug level instead of warn to avoid alarming users when this is expected behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +71
// Ensure we're subscribed to the topic (required for publishing)
if !self.swarm.behaviour().gossipsub.topics().any(|t| t.to_string() == topic_name) {
self.swarm
.behaviour_mut()
.gossipsub
.subscribe(&topic)
.map_err(|e| anyhow::anyhow!("Failed to subscribe to topic {}: {}", topic_name, e))?;
tracing::debug!("Auto-subscribed to topic {}", topic_name);
}
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The publish_to_topic method auto-subscribes to a topic if not already subscribed (line 64-71). While convenient, this behavior is surprising and could lead to unintended subscriptions. In GossipSub, subscribing to a topic means the node will receive all messages on that topic, which has bandwidth and processing implications.

Consider documenting this auto-subscription behavior prominently in the method's documentation, or requiring explicit subscription before publishing. The generic subscribe_to_topic method exists for this purpose.

Copilot uses AI. Check for mistakes.
Comment on lines +121 to +133
// Check that with peer_count, we don't exceed max checks/hour
let divisor = schedule.interval_seconds * peer_count as u64;
if divisor == 0 {
return Err("Invalid schedule: interval cannot be zero".to_string());
}
let checks_per_peer_per_hour = 3600 / divisor;

if checks_per_peer_per_hour > self.max_checks_per_peer_per_hour {
return Err(format!(
"Schedule would cause {} checks/peer/hour (max: {})",
checks_per_peer_per_hour, self.max_checks_per_peer_per_hour
));
}
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rate limit validation has a potential division by zero vulnerability. When peer_count is 1 and schedule.interval_seconds is less than 3600, the calculation 3600 / divisor could produce incorrect results. More critically, if the multiplication on line 122 overflows, divisor could be 0 despite the check on line 123.

Consider checking for overflow explicitly or using checked arithmetic. Also, the logic appears incorrect: with interval_seconds = 60 and peer_count = 1, the divisor is 60 * 1 = 60, and 3600 / 60 = 60 checks per hour per peer, which is correct. However, the comment on line 121 suggests this is meant to prevent exceeding max checks, but the calculation doesn't account for the fact that each peer performs one check per interval_seconds, not per interval_seconds * peer_count.

Copilot uses AI. Check for mistakes.
match query_fn(peer_id.clone(), request).await {
Ok(response) => {
// Store each data item
let mut synced_until = 0;
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indentation of let mut synced_until = 0; is inconsistent with the surrounding code. This line should be indented to align with the other statements in the loop body. While this doesn't affect functionality, it impacts code readability and maintainability.

Suggested change
let mut synced_until = 0;
let mut synced_until = 0;

Copilot uses AI. Check for mistakes.
Comment on lines +171 to +174
// Check if already voted
if self.pending_votes.iter().any(|v| v.voter_peer_id == vote.voter_peer_id) {
return Err("Peer already voted".to_string());
}
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The duplicate vote check on line 172 only compares voter_peer_id strings, but there's no verification that this ID matches the public key that signed the vote. A malicious peer could claim to be another peer by setting voter_peer_id to match a different peer's ID, and the signature check wouldn't catch this unless the application explicitly binds peer IDs to public keys.

Consider either: (1) requiring that voter_peer_id matches the hex-encoded public key used for signing (similar to SignedResult.verify_peer_id()), or (2) documenting clearly that the application layer must maintain and verify peer ID to public key mappings.

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +105
/// Verify the signature on this data
pub fn verify_signature(&self) -> bool {
// TODO: Implement Ed25519 signature verification
// For now, return true (implement in storage layer)
true
}
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verify_signature method in PeerData is not implemented and always returns true. This is a critical security vulnerability as it means peer data signatures are never actually verified before being stored. The sync manager relies on this verification at line 94 of sync.rs to prevent storing malicious or tampered data.

The signature and public_key fields are stored as Strings but should be verified using the crypto module's verify_signature function. Implement actual Ed25519 signature verification here using the crypto utilities available in the crate.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +36
GetRecord(Err(_e)) => PeerUPEvent::DhtGetRecordErr { key: Vec::new() },
PutRecord(Ok(ok)) => {
let key = ok.key.as_ref().to_vec();
PeerUPEvent::DhtPutRecordOk { key }
}
PutRecord(Err(e)) => {
PeerUPEvent::DhtPutRecordErr {
key: Vec::new(),
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the DHT get/put error events, the key field is set to an empty Vec::new(). This loses important debugging information about which key failed. The original key should be preserved and included in the error event so that consumers can properly handle and log failures for specific keys.

Consider capturing the key from the error result or the original query context and including it in the error events.

Suggested change
GetRecord(Err(_e)) => PeerUPEvent::DhtGetRecordErr { key: Vec::new() },
PutRecord(Ok(ok)) => {
let key = ok.key.as_ref().to_vec();
PeerUPEvent::DhtPutRecordOk { key }
}
PutRecord(Err(e)) => {
PeerUPEvent::DhtPutRecordErr {
key: Vec::new(),
GetRecord(Err(e)) => {
let key = e.key.as_ref().to_vec();
PeerUPEvent::DhtGetRecordErr { key }
}
PutRecord(Ok(ok)) => {
let key = ok.key.as_ref().to_vec();
PeerUPEvent::DhtPutRecordOk { key }
}
PutRecord(Err(e)) => {
let key = e.key.as_ref().to_vec();
PeerUPEvent::DhtPutRecordErr {
key,

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +25
pub fn dht_key(domain: &str) -> RecordKey {
RecordKey::new(&format!("/uppe/public-monitor/{}", domain))
}
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DHT key format uses "uppe" as a hardcoded namespace in line 24, but the code is in the generic peerup crate which claims to be application-agnostic. The metadata module correctly uses a configurable namespace parameter, but this DHT module hardcodes "uppe".

Consider making the namespace configurable (e.g., passed to PublicMonitorDHT::new()) to maintain the generic nature of the peerup crate, or document that this module is specifically for the Uppe application.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +37
/// Serialize registry to DHT record
pub fn to_record(&self) -> Result<Record, Box<dyn std::error::Error>> {
let value = serde_json::to_vec(&self)?;
// Key will be set by caller based on specific domain
Ok(Record {
key: RecordKey::new(&[]), // Placeholder
value,
publisher: None,
expires: None,
})
}
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The to_record method creates a Record with an empty placeholder key (RecordKey::new(&[])), which is confusing and error-prone. The comment states "Key will be set by caller", but Record keys are immutable once created. This suggests the method is not actually useful as written.

Either remove this method, or have it accept a domain parameter and create the proper key directly. The current implementation could lead to bugs if someone tries to use the returned Record without realizing the key is invalid.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants