hopr_crypto_packet/sphinx/
surb.rs1use 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#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct SURB<S: SphinxSuite, H: SphinxHeaderSpec> {
29 pub first_relayer: H::KeyId,
31 pub alpha: Alpha<<S::G as GroupElement<S::E>>::AlphaLen>,
33 pub header: RoutingInfo<H>,
35 pub sender_key: SecretKey16,
37 pub additional_data_receiver: H::SurbReceiverData,
39}
40
41impl<S: SphinxSuite, H: SphinxHeaderSpec> SURB<S, H> {
42 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 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 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#[derive(Clone)]
175#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
176pub struct ReplyOpener {
177 pub sender_key: SecretKey16,
179 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
192pub 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}