summaryrefslogtreecommitdiffstats
path: root/network/src/dandelion.rs
diff options
context:
space:
mode:
authorgabrix73 <gabriel1@frozenstar.info>2026-06-01 18:41:36 +0200
committergabrix73 <gabriel1@frozenstar.info>2026-06-01 18:41:36 +0200
commit9f5d864d533ce86459e654f5d78212933c0269ea (patch)
tree4b71e8c7ab4e78da93e5f3164803da4de68c7031 /network/src/dandelion.rs
downloadonioncoin-0.1.0.tar.gz
onioncoin-0.1.0.tar.xz
onioncoin-0.1.0.zip
Initial commit: OnionCoin prototype with Proof-of-Relay consensusHEADv0.1.0main
OnionCoin is a privacy cryptocurrency that rewards Tor relay operators through a unique Proof-of-Contribution consensus mechanism. Core Features: - Proof-of-Relay: 30% of block rewards go to Tor operators - Native .onion node identity (no IP exposure) - Temporal obfuscation protocols - Dandelion++ over Tor propagation - Native inheritance system with dead man's switch Technical Stack: - Rust workspace with 8 crates - Ed25519/X25519 cryptography - arti (Rust Tor client) integration planned - 10 minute block time, 5-10 TPS design Status: Prototype - Consensus logic complete with passing tests (30/33) - Network layer conceptual design complete - Tor integration pending - Testnet launch planned Q3 2026 License: MIT Author: Gabriele Salati (virebent) Contact: g48rix@gmail.com Website: https://www.gabrielesalati.eu Repository: https://git.virebent.art/virebent/onioncoin
Diffstat (limited to 'network/src/dandelion.rs')
-rw-r--r--network/src/dandelion.rs270
1 files changed, 270 insertions, 0 deletions
diff --git a/network/src/dandelion.rs b/network/src/dandelion.rs
new file mode 100644
index 0000000..daca85f
--- /dev/null
+++ b/network/src/dandelion.rs
@@ -0,0 +1,270 @@
+use onioncoin_timing::{DelayStrategy, delay::DelaySequence};
+use rand::Rng;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+
+/// Dandelion++ phases for transaction propagation
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+pub enum DandelionPhase {
+ /// STEM phase: forward to single peer with delays
+ Stem,
+ /// FLUFF phase: broadcast to all peers
+ Fluff,
+}
+
+/// Router for Dandelion++ protocol
+#[derive(Debug)]
+pub struct DandelionRouter<TxId> {
+ /// Current phase for each transaction
+ phases: HashMap<TxId, DandelionPhase>,
+
+ /// Hop count for STEM phase
+ stem_hops: HashMap<TxId, u32>,
+
+ /// Configuration
+ config: DandelionConfig,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct DandelionConfig {
+ /// Minimum STEM hops before transitioning to FLUFF
+ pub min_stem_hops: u32,
+
+ /// Maximum STEM hops before forcing FLUFF
+ pub max_stem_hops: u32,
+
+ /// Probability of transitioning to FLUFF at each hop (0.0 - 1.0)
+ pub fluff_probability: f64,
+
+ /// Enable delays between hops
+ pub use_delays: bool,
+}
+
+impl Default for DandelionConfig {
+ fn default() -> Self {
+ Self {
+ min_stem_hops: 1,
+ max_stem_hops: 4,
+ fluff_probability: 0.25, // 25% chance per hop after min
+ use_delays: true,
+ }
+ }
+}
+
+impl<TxId: std::hash::Hash + Eq + Clone> DandelionRouter<TxId> {
+ pub fn new(config: DandelionConfig) -> Self {
+ Self {
+ phases: HashMap::new(),
+ stem_hops: HashMap::new(),
+ config,
+ }
+ }
+
+ /// Register a new transaction (starts in STEM phase)
+ pub fn register_transaction(&mut self, tx_id: TxId) {
+ self.phases.insert(tx_id.clone(), DandelionPhase::Stem);
+ self.stem_hops.insert(tx_id, 0);
+ }
+
+ /// Process a hop and determine if should transition to FLUFF
+ pub fn process_hop(&mut self, tx_id: &TxId) -> DandelionPhase {
+ let current_phase = self.phases.get(tx_id).copied().unwrap_or(DandelionPhase::Stem);
+
+ if current_phase == DandelionPhase::Fluff {
+ return DandelionPhase::Fluff;
+ }
+
+ // Increment hop count
+ let hops = self.stem_hops.entry(tx_id.clone()).or_insert(0);
+ *hops += 1;
+
+ // Force FLUFF if max hops reached
+ if *hops >= self.config.max_stem_hops {
+ self.phases.insert(tx_id.clone(), DandelionPhase::Fluff);
+ return DandelionPhase::Fluff;
+ }
+
+ // If past min hops, randomly transition to FLUFF
+ if *hops >= self.config.min_stem_hops {
+ let mut rng = rand::thread_rng();
+ if rng.gen_bool(self.config.fluff_probability) {
+ self.phases.insert(tx_id.clone(), DandelionPhase::Fluff);
+ return DandelionPhase::Fluff;
+ }
+ }
+
+ DandelionPhase::Stem
+ }
+
+ /// Get current phase for a transaction
+ pub fn get_phase(&self, tx_id: &TxId) -> Option<DandelionPhase> {
+ self.phases.get(tx_id).copied()
+ }
+
+ /// Force transition to FLUFF phase
+ pub fn force_fluff(&mut self, tx_id: &TxId) {
+ self.phases.insert(tx_id.clone(), DandelionPhase::Fluff);
+ }
+
+ /// Get number of STEM hops for a transaction
+ pub fn get_stem_hops(&self, tx_id: &TxId) -> u32 {
+ self.stem_hops.get(tx_id).copied().unwrap_or(0)
+ }
+
+ /// Clean up old transactions
+ pub fn cleanup(&mut self, tx_ids: &[TxId]) {
+ for tx_id in tx_ids {
+ self.phases.remove(tx_id);
+ self.stem_hops.remove(tx_id);
+ }
+ }
+
+ /// Generate delay sequence for STEM phase
+ pub fn create_stem_delays(&self) -> DelaySequence {
+ DelaySequence::dandelion_stem()
+ }
+}
+
+/// Represents a routing decision for Dandelion++
+#[derive(Debug, Clone)]
+pub struct RoutingDecision {
+ /// Phase to use
+ pub phase: DandelionPhase,
+
+ /// Target peers (1 for STEM, multiple for FLUFF)
+ pub target_peers: TargetPeers,
+
+ /// Delay before forwarding
+ pub delay: Option<DelayStrategy>,
+}
+
+#[derive(Debug, Clone)]
+pub enum TargetPeers {
+ /// Single peer (for STEM)
+ Single(PeerId),
+
+ /// Multiple peers (for FLUFF broadcast)
+ Multiple(Vec<PeerId>),
+
+ /// Random subset of peers
+ RandomSubset { count: usize },
+}
+
+/// Placeholder for peer identifier
+pub type PeerId = String;
+
+impl RoutingDecision {
+ /// Create STEM routing decision
+ pub fn stem(peer: PeerId, delay: DelayStrategy) -> Self {
+ Self {
+ phase: DandelionPhase::Stem,
+ target_peers: TargetPeers::Single(peer),
+ delay: Some(delay),
+ }
+ }
+
+ /// Create FLUFF routing decision
+ pub fn fluff(peers: Vec<PeerId>, delay: Option<DelayStrategy>) -> Self {
+ Self {
+ phase: DandelionPhase::Fluff,
+ target_peers: TargetPeers::Multiple(peers),
+ delay,
+ }
+ }
+
+ /// Create random subset FLUFF
+ pub fn fluff_random(count: usize) -> Self {
+ Self {
+ phase: DandelionPhase::Fluff,
+ target_peers: TargetPeers::RandomSubset { count },
+ delay: None,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_dandelion_registration() {
+ let mut router = DandelionRouter::<u64>::new(DandelionConfig::default());
+ router.register_transaction(1);
+
+ assert_eq!(router.get_phase(&1), Some(DandelionPhase::Stem));
+ assert_eq!(router.get_stem_hops(&1), 0);
+ }
+
+ #[test]
+ fn test_dandelion_hop_progression() {
+ let mut router = DandelionRouter::<u64>::new(DandelionConfig::default());
+ router.register_transaction(1);
+
+ // Process hops
+ for _ in 0..3 {
+ router.process_hop(&1);
+ }
+
+ assert!(router.get_stem_hops(&1) >= 3);
+ }
+
+ #[test]
+ fn test_dandelion_force_fluff() {
+ let config = DandelionConfig {
+ min_stem_hops: 1,
+ max_stem_hops: 10,
+ fluff_probability: 0.0, // Never random transition
+ use_delays: true,
+ };
+
+ let mut router = DandelionRouter::<u64>::new(config);
+ router.register_transaction(1);
+
+ // Should stay in STEM
+ router.process_hop(&1);
+ assert_eq!(router.get_phase(&1), Some(DandelionPhase::Stem));
+
+ // Force FLUFF
+ router.force_fluff(&1);
+ assert_eq!(router.get_phase(&1), Some(DandelionPhase::Fluff));
+ }
+
+ #[test]
+ fn test_dandelion_max_hops() {
+ let config = DandelionConfig {
+ min_stem_hops: 1,
+ max_stem_hops: 3,
+ fluff_probability: 0.0,
+ use_delays: true,
+ };
+
+ let mut router = DandelionRouter::<u64>::new(config);
+ router.register_transaction(1);
+
+ // Process max hops
+ for _ in 0..3 {
+ router.process_hop(&1);
+ }
+
+ // Should transition to FLUFF at max hops
+ assert_eq!(router.get_phase(&1), Some(DandelionPhase::Fluff));
+ }
+
+ #[test]
+ fn test_routing_decision() {
+ let stem_decision = RoutingDecision::stem(
+ "peer1".to_string(),
+ DelayStrategy::dandelion_stem(),
+ );
+
+ assert_eq!(stem_decision.phase, DandelionPhase::Stem);
+ assert!(stem_decision.delay.is_some());
+
+ let fluff_decision = RoutingDecision::fluff(
+ vec!["peer1".to_string(), "peer2".to_string()],
+ None,
+ );
+
+ assert_eq!(fluff_decision.phase, DandelionPhase::Fluff);
+ }
+}