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#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct SURB<S: SphinxSuite, H: SphinxHeaderSpec> {
31 pub first_relayer: H::KeyId,
33 pub alpha: Alpha<<S::G as GroupElement<S::E>>::AlphaLen>,
35 pub header: RoutingInfo<H>,
37 pub sender_key: SecretKey16,
39 pub additional_data_receiver: H::SurbReceiverData,
41}
42
43impl<S: SphinxSuite, H: SphinxHeaderSpec> SURB<S, H> {
44 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 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 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#[derive(Clone)]
177#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
178pub struct ReplyOpener {
179 pub sender_key: SecretKey16,
181 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
194pub 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}