hopr_crypto_packet/sphinx/
surb.rs1use std::fmt::Formatter;
2
3use hopr_types::{
4 crypto::prelude::*,
5 crypto_random::Randomizable,
6 primitive::{
7 hybrid_array::{Array, typenum::Unsigned},
8 prelude::*,
9 },
10};
11use subtle::ConstantTimeEq;
12
13use super::{
14 routing::{RoutingInfo, SphinxHeaderSpec},
15 shared_keys::{Alpha, GroupElement, SharedKeys, SharedSecret, SphinxSuite},
16};
17
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34pub struct SURB<S: SphinxSuite, H: SphinxHeaderSpec> {
35 pub first_relayer: H::KeyId,
37 pub alpha: Alpha<<S::G as GroupElement<S::E>>::AlphaLen>,
39 pub header: RoutingInfo<H>,
41 pub sender_key: SecretKey16,
43 pub additional_data_receiver: H::SurbReceiverData,
45}
46
47impl<S: SphinxSuite, H: SphinxHeaderSpec> SURB<S, H> {
48 pub const SIZE: usize = H::KEY_ID_SIZE.get()
50 + <S::G as GroupElement<S::E>>::AlphaLen::USIZE
51 + RoutingInfo::<H>::SIZE
52 + SecretKey16::LENGTH
53 + H::SURB_RECEIVER_DATA_SIZE;
54
55 pub fn into_boxed(self) -> Box<[u8]> {
57 let alpha_len = <S::G as GroupElement<S::E>>::AlphaLen::USIZE;
58
59 let mut ret = vec![0u8; Self::SIZE];
60 ret[..H::KEY_ID_SIZE.get()].copy_from_slice(self.first_relayer.as_ref());
61 ret[H::KEY_ID_SIZE.get()..H::KEY_ID_SIZE.get() + alpha_len].copy_from_slice(self.alpha.as_ref());
62 ret[H::KEY_ID_SIZE.get() + alpha_len..H::KEY_ID_SIZE.get() + alpha_len + RoutingInfo::<H>::SIZE]
63 .copy_from_slice(self.header.as_ref());
64 ret[H::KEY_ID_SIZE.get() + alpha_len + RoutingInfo::<H>::SIZE
65 ..H::KEY_ID_SIZE.get() + alpha_len + RoutingInfo::<H>::SIZE + SecretKey16::LENGTH]
66 .copy_from_slice(self.sender_key.as_ref());
67 ret[H::KEY_ID_SIZE.get() + alpha_len + RoutingInfo::<H>::SIZE + SecretKey16::LENGTH
68 ..H::KEY_ID_SIZE.get()
69 + alpha_len
70 + RoutingInfo::<H>::SIZE
71 + SecretKey16::LENGTH
72 + H::SURB_RECEIVER_DATA_SIZE]
73 .copy_from_slice(self.additional_data_receiver.as_ref());
74
75 ret.into_boxed_slice()
76 }
77
78 pub fn get_hash(&self, context: &[u8]) -> Hash {
82 Hash::create(&[
83 self.first_relayer.as_ref(),
84 self.alpha.as_ref(),
85 self.sender_key.as_ref(),
86 self.header.as_ref(),
87 context,
88 ])
89 }
90}
91
92impl<S: SphinxSuite, H: SphinxHeaderSpec> Clone for SURB<S, H>
93where
94 H::KeyId: Clone,
95 H::SurbReceiverData: Clone,
96{
97 fn clone(&self) -> Self {
98 Self {
99 first_relayer: self.first_relayer.clone(),
100 alpha: self.alpha.clone(),
101 header: self.header.clone(),
102 sender_key: self.sender_key.clone(),
103 additional_data_receiver: self.additional_data_receiver.clone(),
104 }
105 }
106}
107
108impl<S: SphinxSuite, H: SphinxHeaderSpec> std::fmt::Debug for SURB<S, H>
109where
110 H::KeyId: std::fmt::Debug,
111 H::SurbReceiverData: std::fmt::Debug,
112{
113 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
114 f.debug_struct("SURB")
115 .field("first_relayer", &self.first_relayer)
116 .field("alpha", &self.alpha)
117 .field("header", &self.header)
118 .field("sender_key", &"<redacted>")
119 .field("additional_data_receiver", &self.additional_data_receiver)
120 .finish()
121 }
122}
123
124impl<S: SphinxSuite, H: SphinxHeaderSpec> PartialEq for SURB<S, H>
125where
126 H::KeyId: PartialEq,
127 H::SurbReceiverData: PartialEq,
128{
129 fn eq(&self, other: &Self) -> bool {
130 self.first_relayer.eq(&other.first_relayer)
131 && self.alpha.eq(&other.alpha)
132 && self.header.eq(&other.header)
133 && self.sender_key.ct_eq(&other.sender_key).into()
134 && self.additional_data_receiver.eq(&other.additional_data_receiver)
135 }
136}
137
138impl<S: SphinxSuite, H: SphinxHeaderSpec> Eq for SURB<S, H>
139where
140 H::KeyId: Eq,
141 H::SurbReceiverData: Eq,
142{
143}
144
145impl<'a, S: SphinxSuite, H: SphinxHeaderSpec> TryFrom<&'a [u8]> for SURB<S, H> {
146 type Error = GeneralError;
147
148 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
149 let alpha = <S::G as GroupElement<S::E>>::AlphaLen::USIZE;
150
151 if value.len() == Self::SIZE {
152 Ok(Self {
153 first_relayer: value[0..H::KEY_ID_SIZE.get()]
154 .try_into()
155 .map_err(|_| GeneralError::ParseError("SURB.first_relayer".into()))?,
156 alpha: Array::<u8, <S::G as GroupElement<S::E>>::AlphaLen>::try_from(
157 &value[H::KEY_ID_SIZE.get()..H::KEY_ID_SIZE.get() + alpha],
158 )
159 .map_err(|_| GeneralError::ParseError("SURB.alpha".into()))?,
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 use hopr_types::crypto_random::Randomizable;
241
242 use super::{super::tests::*, *};
243
244 #[allow(type_alias_bounds)]
245 pub type HeaderSpec<S: SphinxSuite> = TestSpec<<S::P as Keypair>::Public, 4, 66>;
246
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 = "ed25519")]
268 use crate::sphinx::ec_groups::Ed25519Suite as CurrentSuite;
269 #[cfg(all(feature = "secp256k1", not(feature = "ed25519")))]
270 use crate::sphinx::ec_groups::Secp256k1Suite as CurrentSuite;
271 #[cfg(all(feature = "x25519", not(any(feature = "ed25519", feature = "secp256k1"))))]
272 use crate::sphinx::ec_groups::X25519Suite as CurrentSuite;
273
274 #[test]
275 fn surb_serialize_deserialize() -> anyhow::Result<()> {
276 let (surb_1, _) = generate_surbs::<CurrentSuite>((0..3).map(|_| OffchainKeypair::random()).collect())?;
277
278 let surb_1_enc = surb_1.into_boxed();
279
280 let surb_2 = SURB::<CurrentSuite, HeaderSpec<CurrentSuite>>::try_from(surb_1_enc.as_ref())?;
281
282 assert_eq!(surb_1_enc, surb_2.into_boxed());
283
284 Ok(())
285 }
286}