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#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29pub struct SURB<S: SphinxSuite, H: SphinxHeaderSpec> {
30 pub first_relayer: H::KeyId,
32 pub alpha: Alpha<<S::G as GroupElement<S::E>>::AlphaLen>,
34 pub header: RoutingInfo<H>,
36 pub sender_key: SecretKey16,
38 pub additional_data_receiver: H::SurbReceiverData,
40}
41
42impl<S: SphinxSuite, H: SphinxHeaderSpec> SURB<S, H> {
43 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 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 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#[derive(Clone)]
155#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
156pub struct ReplyOpener {
157 pub sender_key: SecretKey16,
159 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
172pub 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}