hopr_crypto_sphinx/
surb.rs

1use std::fmt::Formatter;
2
3use hopr_crypto_random::Randomizable;
4use hopr_crypto_types::prelude::*;
5use hopr_primitive_types::prelude::*;
6use subtle::ConstantTimeEq;
7use typenum::Unsigned;
8
9use crate::{
10    routing::{RoutingInfo, SphinxHeaderSpec},
11    shared_keys::{Alpha, GroupElement, SharedKeys, SharedSecret, SphinxSuite},
12};
13
14/// Single Use Reply Block
15///
16/// This is delivered to the recipient, so they are able to send reply messages back
17/// anonymously (via the return path inside that SURB).
18///
19/// [`SURB`] is always created in a pair with [`ReplyOpener`], so that the sending
20/// party knows how to decrypt the data.
21///
22/// The SURB sent to the receiving party must be accompanied
23/// by a `Pseudonym`, and once the receiving party uses that SURB to send a reply, it
24/// must be accompanied by the same `Pseudonym`.
25/// Upon receiving such a reply, the reply recipient (= sender of the SURB)
26/// uses the `Pseudonym` to find the `ReplyOpener` created with the SURB to read the reply.
27///
28/// Always use [`create_surb`] to create the [`SURB`] and [`ReplyOpener`] pair.
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct SURB<S: SphinxSuite, H: SphinxHeaderSpec> {
31    /// ID of the first relayer.
32    pub first_relayer: H::KeyId,
33    /// Alpha value for the header.
34    pub alpha: Alpha<<S::G as GroupElement<S::E>>::AlphaLen>,
35    /// Sphinx routing header.
36    pub header: RoutingInfo<H>,
37    /// Encryption key to use to encrypt the data for the SURB's creator.
38    pub sender_key: SecretKey16,
39    /// Additional data for the SURB receiver.
40    pub additional_data_receiver: H::SurbReceiverData,
41}
42
43impl<S: SphinxSuite, H: SphinxHeaderSpec> SURB<S, H> {
44    /// Size of the SURB in bytes.
45    pub const SIZE: usize = H::KEY_ID_SIZE.get()
46        + <S::G as GroupElement<S::E>>::AlphaLen::USIZE
47        + RoutingInfo::<H>::SIZE
48        + SecretKey16::LENGTH
49        + H::SURB_RECEIVER_DATA_SIZE;
50
51    /// Serializes SURB into wire format.
52    pub fn into_boxed(self) -> Box<[u8]> {
53        let alpha_len = <S::G as GroupElement<S::E>>::AlphaLen::USIZE;
54
55        let mut ret = vec![0u8; Self::SIZE];
56        ret[..H::KEY_ID_SIZE.get()].copy_from_slice(self.first_relayer.as_ref());
57        ret[H::KEY_ID_SIZE.get()..H::KEY_ID_SIZE.get() + alpha_len].copy_from_slice(self.alpha.as_ref());
58        ret[H::KEY_ID_SIZE.get() + alpha_len..H::KEY_ID_SIZE.get() + alpha_len + RoutingInfo::<H>::SIZE]
59            .copy_from_slice(self.header.as_ref());
60        ret[H::KEY_ID_SIZE.get() + alpha_len + RoutingInfo::<H>::SIZE
61            ..H::KEY_ID_SIZE.get() + alpha_len + RoutingInfo::<H>::SIZE + SecretKey16::LENGTH]
62            .copy_from_slice(self.sender_key.as_ref());
63        ret[H::KEY_ID_SIZE.get() + alpha_len + RoutingInfo::<H>::SIZE + SecretKey16::LENGTH
64            ..H::KEY_ID_SIZE.get()
65                + alpha_len
66                + RoutingInfo::<H>::SIZE
67                + SecretKey16::LENGTH
68                + H::SURB_RECEIVER_DATA_SIZE]
69            .copy_from_slice(self.additional_data_receiver.as_ref());
70
71        ret.into_boxed_slice()
72    }
73
74    /// Computes Keccak256 hash of the SURB.
75    ///
76    /// The given `context` is appended to the input.
77    pub fn get_hash(&self, context: &[u8]) -> Hash {
78        Hash::create(&[
79            self.first_relayer.as_ref(),
80            self.alpha.as_ref(),
81            self.sender_key.as_ref(),
82            self.header.as_ref(),
83            context,
84        ])
85    }
86}
87
88impl<S: SphinxSuite, H: SphinxHeaderSpec> Clone for SURB<S, H>
89where
90    H::KeyId: Clone,
91    H::SurbReceiverData: Clone,
92{
93    fn clone(&self) -> Self {
94        Self {
95            first_relayer: self.first_relayer.clone(),
96            alpha: self.alpha.clone(),
97            header: self.header.clone(),
98            sender_key: self.sender_key.clone(),
99            additional_data_receiver: self.additional_data_receiver.clone(),
100        }
101    }
102}
103
104impl<S: SphinxSuite, H: SphinxHeaderSpec> std::fmt::Debug for SURB<S, H>
105where
106    H::KeyId: std::fmt::Debug,
107    H::SurbReceiverData: std::fmt::Debug,
108{
109    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
110        f.debug_struct("SURB")
111            .field("first_relayer", &self.first_relayer)
112            .field("alpha", &self.alpha)
113            .field("header", &self.header)
114            .field("sender_key", &"<redacted>")
115            .field("additional_data_receiver", &self.additional_data_receiver)
116            .finish()
117    }
118}
119
120impl<S: SphinxSuite, H: SphinxHeaderSpec> PartialEq for SURB<S, H>
121where
122    H::KeyId: PartialEq,
123    H::SurbReceiverData: PartialEq,
124{
125    fn eq(&self, other: &Self) -> bool {
126        self.first_relayer.eq(&other.first_relayer)
127            && self.alpha.eq(&other.alpha)
128            && self.header.eq(&other.header)
129            && self.sender_key.ct_eq(&other.sender_key).into()
130            && self.additional_data_receiver.eq(&other.additional_data_receiver)
131    }
132}
133
134impl<S: SphinxSuite, H: SphinxHeaderSpec> Eq for SURB<S, H>
135where
136    H::KeyId: Eq,
137    H::SurbReceiverData: Eq,
138{
139}
140
141impl<'a, S: SphinxSuite, H: SphinxHeaderSpec> TryFrom<&'a [u8]> for SURB<S, H> {
142    type Error = GeneralError;
143
144    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
145        let alpha = <S::G as GroupElement<S::E>>::AlphaLen::USIZE;
146
147        if value.len() == Self::SIZE {
148            Ok(Self {
149                first_relayer: value[0..H::KEY_ID_SIZE.get()]
150                    .try_into()
151                    .map_err(|_| GeneralError::ParseError("SURB.first_relayer".into()))?,
152                alpha: Alpha::<<S::G as GroupElement<S::E>>::AlphaLen>::from_slice(
153                    &value[H::KEY_ID_SIZE.get()..H::KEY_ID_SIZE.get() + alpha],
154                )
155                .clone(),
156                header: value[H::KEY_ID_SIZE.get() + alpha..H::KEY_ID_SIZE.get() + alpha + RoutingInfo::<H>::SIZE]
157                    .try_into()
158                    .map_err(|_| GeneralError::ParseError("SURB.header".into()))?,
159                sender_key: value[H::KEY_ID_SIZE.get() + alpha + RoutingInfo::<H>::SIZE
160                    ..H::KEY_ID_SIZE.get() + alpha + RoutingInfo::<H>::SIZE + SecretKey16::LENGTH]
161                    .try_into()
162                    .map_err(|_| GeneralError::ParseError("SURB.sender_key".into()))?,
163                additional_data_receiver: value
164                    [H::KEY_ID_SIZE.get() + alpha + RoutingInfo::<H>::SIZE + SecretKey16::LENGTH..]
165                    .try_into()
166                    .map_err(|_| GeneralError::ParseError("SURB.additional_data_receiver".into()))?,
167            })
168        } else {
169            Err(GeneralError::ParseError("SURB::SIZE".into()))
170        }
171    }
172}
173
174/// Entry stored locally by the [`SURB`] creator to allow decryption
175/// of received responses.
176#[derive(Clone)]
177#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
178pub struct ReplyOpener {
179    /// Encryption key the other party should use to encrypt the data for us.
180    pub sender_key: SecretKey16,
181    /// Shared secrets for nodes along the return path.
182    pub shared_secrets: Vec<SharedSecret>,
183}
184
185impl std::fmt::Debug for ReplyOpener {
186    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
187        f.debug_struct("ReplyOpener")
188            .field("sender_key", &"<redacted>")
189            .field("shared_secrets", &format!("{} <redacted>", self.shared_secrets.len()))
190            .finish()
191    }
192}
193
194/// Creates a pair of [`SURB`] and [`ReplyOpener`].
195///
196/// The former is sent to the other party, the latter is kept locally.
197pub fn create_surb<S: SphinxSuite, H: SphinxHeaderSpec>(
198    shared_keys: SharedKeys<S::E, S::G>,
199    path: &[H::KeyId],
200    additional_data_relayer: &[H::RelayerData],
201    receiver_data: H::PacketReceiverData,
202    additional_data_receiver: H::SurbReceiverData,
203) -> hopr_crypto_types::errors::Result<(SURB<S, H>, ReplyOpener)>
204where
205    H::KeyId: Copy,
206{
207    let header = RoutingInfo::<H>::new(
208        path,
209        &shared_keys.secrets,
210        additional_data_relayer,
211        &receiver_data,
212        true,
213        false,
214    )?;
215
216    let sender_key = SecretKey16::random();
217
218    let surb = SURB {
219        sender_key: sender_key.clone(),
220        header,
221        first_relayer: *path.first().ok_or(CryptoError::InvalidInputValue("path is empty"))?,
222        additional_data_receiver,
223        alpha: shared_keys.alpha,
224    };
225
226    let reply_opener = ReplyOpener {
227        sender_key: sender_key.clone(),
228        shared_secrets: shared_keys.secrets,
229    };
230
231    Ok((surb, reply_opener))
232}
233
234#[cfg(test)]
235mod tests {
236    use hopr_crypto_random::Randomizable;
237
238    use super::*;
239    use crate::{ec_groups::X25519Suite, tests::*};
240
241    #[allow(type_alias_bounds)]
242    pub type HeaderSpec<S: SphinxSuite> = TestSpec<<S::P as Keypair>::Public, 4, 66>;
243
244    fn generate_surbs<S: SphinxSuite>(keypairs: Vec<S::P>) -> anyhow::Result<(SURB<S, HeaderSpec<S>>, ReplyOpener)>
245    where
246        <<S as SphinxSuite>::P as Keypair>::Public: Copy,
247        for<'a> &'a Alpha<<<S as SphinxSuite>::G as GroupElement<<S as SphinxSuite>::E>>::AlphaLen>:
248            From<&'a <<S as SphinxSuite>::P as Keypair>::Public>,
249    {
250        let pub_keys = keypairs.iter().map(|kp| *kp.public()).collect::<Vec<_>>();
251        let shares = S::new_shared_keys(&pub_keys)?;
252
253        Ok(create_surb::<S, HeaderSpec<S>>(
254            shares,
255            &pub_keys,
256            &[Default::default(); 4],
257            SimplePseudonym::random(),
258            Default::default(),
259        )?)
260    }
261
262    #[test]
263    fn surb_x25519_serialize_deserialize() -> anyhow::Result<()> {
264        let (surb_1, _) = generate_surbs::<X25519Suite>((0..3).map(|_| OffchainKeypair::random()).collect())?;
265
266        let surb_1_enc = surb_1.into_boxed();
267
268        let surb_2 = SURB::<X25519Suite, HeaderSpec<X25519Suite>>::try_from(surb_1_enc.as_ref())?;
269
270        assert_eq!(surb_1_enc, surb_2.into_boxed());
271
272        Ok(())
273    }
274}