mixnet/core/sphinx/
delay.rs

1// Copyright 2022 Parity Technologies (UK) Ltd.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation
6// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7// and/or sell copies of the Software, and to permit persons to whom the
8// Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19// DEALINGS IN THE SOFTWARE.
20
21//! Unitless delay type.
22
23use arrayref::array_mut_ref;
24use rand::Rng;
25use rand_chacha::{rand_core::SeedableRng, ChaChaRng};
26use std::{
27	cmp::Ordering,
28	ops::{Add, AddAssign},
29	time::Duration,
30};
31
32pub const DELAY_SEED_SIZE: usize = 16;
33pub type DelaySeed = [u8; DELAY_SEED_SIZE];
34
35/// Unitless delay. Can be converted to a [`Duration`] with [`to_duration`](Self::to_duration).
36#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
37pub struct Delay(f64);
38
39impl Delay {
40	/// Returns a delay of zero time.
41	pub fn zero() -> Self {
42		Self(0.0)
43	}
44
45	/// Returns a random delay sampled from an exponential distribution with mean 1. `seed`
46	/// provides the entropy.
47	pub fn exp(seed: &DelaySeed) -> Self {
48		// The algorithm for sampling from an exponential distribution consumes a variable amount
49		// of random data; possibly more random data than is in seed. So it is not sufficient to
50		// just use the random data in seed directly; we really do need to seed an RNG with it.
51		let mut double_seed = [0; 32];
52		*array_mut_ref![double_seed, 0, 16] = *seed;
53		*array_mut_ref![double_seed, 16, 16] = *seed;
54		let mut rng = ChaChaRng::from_seed(double_seed);
55		let delay: f64 = rng.sample(rand_distr::Exp1);
56		// Cap at 10x the mean; this is about the 99.995th percentile. This avoids potential panics
57		// in to_duration() due to overflow.
58		Self(delay.min(10.0))
59	}
60
61	/// Convert the unitless delay into a [`Duration`] by multiplying by `unit`. For delays
62	/// calculated by different parties to match, they must all agree on `unit`!
63	pub fn to_duration(self, unit: Duration) -> Duration {
64		unit.mul_f64(self.0)
65	}
66}
67
68// Delays are never NaN
69impl Eq for Delay {}
70
71#[allow(clippy::derive_ord_xor_partial_ord)]
72impl Ord for Delay {
73	fn cmp(&self, other: &Self) -> Ordering {
74		self.partial_cmp(other).expect("Delays are never NaN")
75	}
76}
77
78impl Add for Delay {
79	type Output = Self;
80
81	fn add(self, other: Self) -> Self {
82		Self(self.0 + other.0)
83	}
84}
85
86impl AddAssign for Delay {
87	fn add_assign(&mut self, other: Self) {
88		self.0 += other.0;
89	}
90}
91
92#[cfg(test)]
93mod tests {
94	use super::*;
95
96	#[test]
97	fn portable_deterministic_exp() {
98		assert_eq!(
99			Delay::exp(&[
100				0xdc, 0x18, 0x0e, 0xe6, 0x71, 0x1e, 0xcf, 0x2d, 0xad, 0x0c, 0xde, 0xd1, 0xd4, 0x94,
101				0xbd, 0x3b
102			]),
103			Delay(2.953842296445717)
104		);
105		assert_eq!(
106			Delay::exp(&[
107				0x0a, 0xcc, 0x48, 0xbd, 0xa2, 0x30, 0x9a, 0x48, 0xc8, 0x78, 0x61, 0x0d, 0xf8, 0xc2,
108				0x8d, 0x99
109			]),
110			Delay(1.278588765412407)
111		);
112		assert_eq!(
113			Delay::exp(&[
114				0x17, 0x4c, 0x40, 0x2f, 0x8f, 0xda, 0xa6, 0x46, 0x45, 0xe7, 0x1c, 0xb0, 0x1e, 0xff,
115				0xf8, 0xfc
116			]),
117			Delay(0.7747915675800142)
118		);
119		assert_eq!(
120			Delay::exp(&[
121				0xca, 0xe8, 0x07, 0x72, 0x17, 0x28, 0xf7, 0x09, 0xd8, 0x7d, 0x3e, 0xa2, 0x03, 0x7d,
122				0x4f, 0x03
123			]),
124			Delay(0.8799379598933348)
125		);
126		assert_eq!(
127			Delay::exp(&[
128				0x61, 0x56, 0x54, 0x41, 0xd0, 0x25, 0xdf, 0xe7, 0xb9, 0xc8, 0x6a, 0x56, 0xdd, 0x27,
129				0x09, 0xa6
130			]),
131			Delay(10.0)
132		);
133	}
134}