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