Skip to main content

hopr_crypto_packet/sphinx/
surb.rs

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