Skip to main content

hopr_crypto_packet/sphinx/
surb.rs

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