1use super::sphinx::{
24 KxPublic, MixnodeIndex, PeerId, RawMixnodeIndex, Target, MAX_HOPS, MAX_MIXNODE_INDEX,
25};
26use arrayvec::ArrayVec;
27use either::Either;
28use rand::{seq::SliceRandom, CryptoRng, Rng};
29use std::{
30 cmp::{max, min},
31 fmt,
32};
33
34#[derive(Clone)]
36pub struct Mixnode<X> {
37 pub kx_public: KxPublic,
39 pub peer_id: PeerId,
41 pub extra: X,
43}
44
45enum LocalNode {
46 Mixnode(MixnodeIndex),
48 NonMixnode(Vec<MixnodeIndex>),
51}
52
53#[derive(Debug, thiserror::Error)]
55pub enum TopologyErr {
56 #[error("Bad mixnode index ({0})")]
58 BadMixnodeIndex(MixnodeIndex),
59 #[error("Too few mixnodes; this should have been caught earlier")]
61 TooFewMixnodes,
62 #[error("The local node has not managed to connect to any gateway mixnodes")]
64 NoConnectedGatewayMixnodes,
65}
66
67pub struct Topology<X> {
68 mixnodes: Vec<Mixnode<X>>,
69 local_kx_public: KxPublic,
70 local_node: LocalNode,
71}
72
73impl<X> Topology<X> {
74 pub fn new(
76 rng: &mut impl Rng,
77 mixnodes: Vec<Mixnode<X>>,
78 local_kx_public: &KxPublic,
79 num_gateway_mixnodes: u32,
80 ) -> Self {
81 debug_assert!(mixnodes.len() <= (MAX_MIXNODE_INDEX + 1) as usize);
82
83 let local_node = mixnodes
94 .iter()
95 .position(|mixnode| &mixnode.kx_public == local_kx_public)
96 .map_or_else(
97 || {
98 LocalNode::NonMixnode(
100 rand::seq::index::sample(
101 rng,
102 mixnodes.len(),
103 min(num_gateway_mixnodes as usize, mixnodes.len()),
104 )
105 .iter()
106 .map(|index| {
107 index
108 .try_into()
109 .expect("Topology::new() contract limits size of mixnode set")
110 })
111 .collect(),
112 )
113 },
114 |index| {
115 LocalNode::Mixnode(
117 index
118 .try_into()
119 .expect("Topology::new() contract limits size of mixnode set"),
120 )
121 },
122 );
123
124 Self { mixnodes, local_kx_public: *local_kx_public, local_node }
125 }
126
127 pub fn is_mixnode(&self) -> bool {
128 matches!(self.local_node, LocalNode::Mixnode(_))
129 }
130
131 pub fn reserved_peers(&self) -> impl Iterator<Item = &Mixnode<X>> {
132 let indices = match &self.local_node {
133 LocalNode::Mixnode(local_index) => Either::Left({
134 let num = self.mixnodes.len() as RawMixnodeIndex;
136 (0..local_index.get()).chain((local_index.get() + 1)..num)
137 }),
138 LocalNode::NonMixnode(gateway_indices) =>
139 Either::Right(gateway_indices.iter().map(|index| index.get())),
140 };
141 indices.map(|index| &self.mixnodes[index as usize])
142 }
143
144 pub fn mixnode_index_to_peer_id(&self, index: MixnodeIndex) -> Result<PeerId, TopologyErr> {
145 self.mixnodes
146 .get(index.get() as usize)
147 .map(|mixnode| mixnode.peer_id)
148 .ok_or(TopologyErr::BadMixnodeIndex(index))
149 }
150
151 pub fn target_to_peer_id(&self, target: &Target) -> Result<PeerId, TopologyErr> {
152 match target {
153 Target::MixnodeIndex(index) => self.mixnode_index_to_peer_id(*index),
154 Target::PeerId(peer_id) => Ok(*peer_id),
155 }
156 }
157}
158
159impl<X> fmt::Display for Topology<X> {
160 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
161 match &self.local_node {
162 LocalNode::Mixnode(local_index) => write!(fmt, "Local node is mixnode {local_index}"),
163 LocalNode::NonMixnode(gateway_indices) => {
164 write!(fmt, "Local node is not a mixnode; gateway mixnodes are ")?;
165 for (i, gateway_index) in gateway_indices.iter().enumerate() {
166 if i == 0 {
167 gateway_index.fmt(fmt)?;
168 } else {
169 write!(fmt, ", {gateway_index}")?;
170 }
171 }
172 Ok(())
173 },
174 }
175 }
176}
177
178pub trait NetworkStatus {
180 fn local_peer_id(&self) -> PeerId;
182 fn is_connected(&self, peer_id: &PeerId) -> bool;
184}
185
186const MAX_CONNECTED_GATEWAY_INDICES: usize = 5;
187
188pub enum RouteKind {
189 ToMixnode(MixnodeIndex),
191 FromMixnode(MixnodeIndex),
193 Loop,
195}
196
197struct UsedIndices(ArrayVec<MixnodeIndex, { MAX_HOPS + 1 }>);
198
199impl UsedIndices {
200 fn new() -> Self {
201 Self(ArrayVec::new())
202 }
203
204 fn insert(&mut self, index: MixnodeIndex) {
205 match self.0.iter().position(|used_index| *used_index >= index) {
206 Some(i) =>
207 if self.0[i] != index {
208 self.0.insert(i, index);
209 },
210 None => self.0.push(index),
211 }
212 }
213
214 fn iter(&self) -> impl ExactSizeIterator<Item = MixnodeIndex> + '_ {
215 self.0.iter().copied()
216 }
217
218 fn as_option(&self) -> Option<MixnodeIndex> {
219 debug_assert!(self.0.len() <= 1);
220 self.0.first().copied()
221 }
222}
223
224pub struct RouteGenerator<'topology, X> {
225 topology: &'topology Topology<X>,
226 local_peer_id: PeerId,
227 connected_gateway_indices: ArrayVec<MixnodeIndex, MAX_CONNECTED_GATEWAY_INDICES>,
230}
231
232impl<'topology, X> RouteGenerator<'topology, X> {
233 pub fn new(topology: &'topology Topology<X>, ns: &dyn NetworkStatus) -> Self {
234 let connected_gateway_indices = match &topology.local_node {
235 LocalNode::Mixnode(_) => ArrayVec::new(),
236 LocalNode::NonMixnode(gateway_indices) => gateway_indices
240 .iter()
241 .copied()
242 .filter(|gateway_index| {
243 let mixnode = &topology.mixnodes[gateway_index.get() as usize];
244 ns.is_connected(&mixnode.peer_id)
245 })
246 .take(MAX_CONNECTED_GATEWAY_INDICES)
247 .collect(),
248 };
249
250 Self { topology, local_peer_id: ns.local_peer_id(), connected_gateway_indices }
251 }
252
253 pub fn topology(&self) -> &'topology Topology<X> {
254 self.topology
255 }
256
257 fn choose_mixnode_index(
261 &self,
262 rng: &mut (impl Rng + CryptoRng),
263 exclude_indices: impl ExactSizeIterator<Item = MixnodeIndex>,
264 ) -> Result<MixnodeIndex, TopologyErr> {
265 let num_allowed =
266 self.topology
267 .mixnodes
268 .len()
269 .checked_sub(exclude_indices.len())
270 .expect("No duplicate or invalid indices in exclude_indices") as RawMixnodeIndex;
271 if num_allowed == 0 {
272 return Err(TopologyErr::TooFewMixnodes)
273 }
274
275 let mut chosen = rng.gen_range(0..num_allowed);
276 for exclude_index in exclude_indices {
277 if chosen >= exclude_index.get() {
278 chosen += 1;
279 }
280 }
281 debug_assert!((chosen as usize) < self.topology.mixnodes.len());
284
285 Ok(chosen.try_into().expect("Topology::new() contract limits size of mixnode set"))
286 }
287
288 pub fn choose_destination_index(
290 &self,
291 rng: &mut (impl Rng + CryptoRng),
292 ) -> Result<MixnodeIndex, TopologyErr> {
293 let exclude_index = match self.topology.local_node {
294 LocalNode::Mixnode(local_index) => Some(local_index),
296 LocalNode::NonMixnode(_) => match self.connected_gateway_indices.as_slice() {
299 [gateway_index] => Some(*gateway_index),
300 _ => None,
301 },
302 };
303 self.choose_mixnode_index(rng, exclude_index.iter().copied())
304 }
305
306 fn choose_connected_gateway_index(
307 &self,
308 rng: &mut (impl Rng + CryptoRng),
309 try_exclude_index: Option<MixnodeIndex>,
310 ) -> Result<MixnodeIndex, TopologyErr> {
311 try_exclude_index
312 .and_then(|try_exclude_index| {
313 if !self.connected_gateway_indices.iter().any(|index| *index == try_exclude_index) {
314 return None
316 }
317 let (&first, rest) = self.connected_gateway_indices.split_first()?;
318 let Some(&chosen) = rest.choose(rng) else {
319 return Some(first)
321 };
322 Some(if chosen == try_exclude_index { first } else { chosen })
325 })
326 .or_else(|| self.connected_gateway_indices.choose(rng).copied())
327 .ok_or(TopologyErr::NoConnectedGatewayMixnodes)
328 }
329
330 pub fn gen_route(
333 &self,
334 targets: &mut ArrayVec<Target, { MAX_HOPS - 1 }>,
335 their_kx_publics: &mut ArrayVec<KxPublic, MAX_HOPS>,
336 rng: &mut (impl Rng + CryptoRng),
337 kind: RouteKind,
338 num_hops: usize,
339 ) -> Result<MixnodeIndex, TopologyErr> {
340 let mut used_indices = UsedIndices::new();
342
343 let (from_local, to_local) = match kind {
344 RouteKind::ToMixnode(index) => {
345 used_indices.insert(index);
346 (true, false)
347 },
348 RouteKind::FromMixnode(index) => {
349 used_indices.insert(index);
350 (false, true)
351 },
352 RouteKind::Loop => (true, true),
353 };
354
355 debug_assert!(from_local || to_local);
357 if let LocalNode::Mixnode(index) = self.topology.local_node {
358 used_indices.insert(index);
359 }
360
361 let special_first_index = match self.topology.local_node {
364 LocalNode::NonMixnode(_) if from_local => {
365 let index = self.choose_connected_gateway_index(rng, used_indices.as_option())?;
366 used_indices.insert(index);
367 Some(index)
368 },
369 _ => None,
370 };
371
372 let special_penultimate_index = match self.topology.local_node {
375 LocalNode::NonMixnode(_) if to_local => {
376 let index = self.choose_connected_gateway_index(rng, used_indices.as_option())?;
377 used_indices.insert(index);
378 Some(index)
379 },
380 _ => None,
381 };
382
383 let min_hops = [
384 special_first_index.is_some(),
386 special_first_index.is_some() && (special_first_index == special_penultimate_index),
389 special_penultimate_index.is_some(),
391 true,
393 ]
394 .iter()
395 .map(|need_hop| *need_hop as usize)
396 .sum();
397 let num_hops = max(num_hops, min_hops);
398
399 let mut first_index = None;
400 for i in 0..num_hops {
401 let mut index = match (i, num_hops - i, special_first_index, special_penultimate_index)
404 {
405 (0, _, Some(index), _) => Some(index),
407 (_, 2, _, Some(index)) => Some(index),
409 (_, 1, _, _) => match kind {
411 RouteKind::ToMixnode(index) => Some(index),
412 RouteKind::FromMixnode(_) => None,
413 RouteKind::Loop => None,
414 },
415 _ => {
417 let index = self.choose_mixnode_index(rng, used_indices.iter())?;
418 used_indices.insert(index);
419 Some(index)
420 },
421 };
422
423 their_kx_publics.push(match index {
425 Some(index) => self.topology.mixnodes[index.get() as usize].kx_public,
426 None => self.topology.local_kx_public,
427 });
428
429 if index.is_none() {
431 if let LocalNode::Mixnode(local_index) = self.topology.local_node {
433 index = Some(local_index);
434 }
435 }
436 if i == 0 {
437 debug_assert!(index.is_some());
439 first_index = index;
440 } else {
441 targets.push(match index {
442 Some(index) => Target::MixnodeIndex(index),
443 None => Target::PeerId(self.local_peer_id),
444 });
445 }
446 }
447
448 Ok(first_index.expect("At least one hop"))
449 }
450}