hopr_crypto_packet/sphinx/
surb.rs1use 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#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31pub struct SURB<S: SphinxSuite, H: SphinxHeaderSpec> {
32 pub first_relayer: H::KeyId,
34 pub alpha: Alpha<<S::G as GroupElement<S::E>>::AlphaLen>,
36 pub header: RoutingInfo<H>,
38 pub sender_key: SecretKey16,
40 pub additional_data_receiver: H::SurbReceiverData,
42}
43
44impl<S: SphinxSuite, H: SphinxHeaderSpec> SURB<S, H> {
45 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 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 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#[derive(Clone)]
181#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
182pub struct ReplyOpener {
183 pub sender_key: SecretKey16,
185 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
198pub 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}