1use std::fmt::Formatter;
2
3use hopr_crypto_sphinx::prelude::SharedSecret;
4use hopr_crypto_types::{
5 crypto_traits::Randomizable,
6 prelude::{SecretKey, sample_secp256k1_field_element},
7 types::{Challenge, HalfKey, HalfKeyChallenge, Response},
8};
9use hopr_primitive_types::prelude::*;
10
11use crate::errors::{PacketError, Result};
12
13const HASH_KEY_OWN_KEY: &str = "HASH_KEY_OWN_KEY";
14const HASH_KEY_ACK_KEY: &str = "HASH_KEY_ACK_KEY";
15
16fn derive_own_key_share(secret: &SecretKey) -> HalfKey {
19 sample_secp256k1_field_element(secret.as_ref(), HASH_KEY_OWN_KEY).expect("failed to sample own key share")
20}
21
22pub fn derive_ack_key_share(secret: &SecretKey) -> HalfKey {
25 sample_secp256k1_field_element(secret.as_ref(), HASH_KEY_ACK_KEY).expect("failed to sample ack key share")
26}
27
28#[derive(Clone, Copy, PartialEq, Eq)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub struct ProofOfRelayValues(#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] [u8; Self::SIZE]);
34
35impl ProofOfRelayValues {
36 fn new(chain_len: u8, ack_challenge: &HalfKeyChallenge, ticket_challenge: &EthereumChallenge) -> Self {
37 let mut ret = [0u8; Self::SIZE];
38 ret[0] = chain_len;
39 ret[1..1 + HalfKeyChallenge::SIZE].copy_from_slice(ack_challenge.as_ref());
40 ret[1 + HalfKeyChallenge::SIZE..].copy_from_slice(ticket_challenge.as_ref());
41 Self(ret)
42 }
43
44 pub fn chain_length(&self) -> u8 {
47 self.0[0]
48 }
49
50 pub fn acknowledgement_challenge(&self) -> HalfKeyChallenge {
55 HalfKeyChallenge::new(&self.0[1..1 + HalfKeyChallenge::SIZE])
56 }
57
58 pub fn ticket_challenge(&self) -> EthereumChallenge {
61 EthereumChallenge::new(&self.0[1 + HalfKeyChallenge::SIZE..])
62 }
63}
64
65impl std::fmt::Debug for ProofOfRelayValues {
66 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
67 f.debug_tuple("ProofOfRelayValues")
68 .field(&self.chain_length())
69 .field(&hex::encode(&self.0[1..1 + HalfKeyChallenge::SIZE]))
70 .field(&hex::encode(&self.0[1 + HalfKeyChallenge::SIZE..]))
71 .finish()
72 }
73}
74
75impl AsRef<[u8]> for ProofOfRelayValues {
76 fn as_ref(&self) -> &[u8] {
77 &self.0
78 }
79}
80
81impl<'a> TryFrom<&'a [u8]> for ProofOfRelayValues {
82 type Error = GeneralError;
83
84 fn try_from(value: &'a [u8]) -> std::result::Result<Self, Self::Error> {
85 value
86 .try_into()
87 .map(Self)
88 .map_err(|_| GeneralError::ParseError("ProofOfRelayValues".into()))
89 }
90}
91
92impl BytesRepresentable for ProofOfRelayValues {
93 const SIZE: usize = 1 + HalfKeyChallenge::SIZE + EthereumChallenge::SIZE;
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
100#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
101pub struct SurbReceiverInfo(#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] [u8; Self::SIZE]);
102
103impl SurbReceiverInfo {
104 pub fn new(pov: ProofOfRelayValues, share: [u8; 32]) -> Self {
105 let mut ret = [0u8; Self::SIZE];
106 ret[0..ProofOfRelayValues::SIZE].copy_from_slice(&pov.0);
107 ret[ProofOfRelayValues::SIZE..ProofOfRelayValues::SIZE + 32].copy_from_slice(&share);
109 Self(ret)
110 }
111
112 pub fn proof_of_relay_values(&self) -> ProofOfRelayValues {
113 ProofOfRelayValues::try_from(&self.0[0..ProofOfRelayValues::SIZE])
114 .expect("SurbReceiverInfo always contains valid ProofOfRelayValues")
115 }
116}
117
118impl AsRef<[u8]> for SurbReceiverInfo {
119 fn as_ref(&self) -> &[u8] {
120 &self.0
121 }
122}
123
124impl<'a> TryFrom<&'a [u8]> for SurbReceiverInfo {
125 type Error = GeneralError;
126
127 fn try_from(value: &'a [u8]) -> std::result::Result<Self, Self::Error> {
128 value
129 .try_into()
130 .map(Self)
131 .map_err(|_| GeneralError::ParseError("SurbReceiverInfo".into()))
132 }
133}
134
135impl BytesRepresentable for SurbReceiverInfo {
136 const SIZE: usize = ProofOfRelayValues::SIZE + 32;
137}
138
139#[derive(Clone, PartialEq, Eq)]
142pub struct ProofOfRelayString([u8; Self::SIZE]);
143
144impl ProofOfRelayString {
145 fn new(next_ticket_challenge: &EthereumChallenge, hint: &HalfKeyChallenge) -> Self {
146 let mut ret = [0u8; Self::SIZE];
147 ret[0..EthereumChallenge::SIZE].copy_from_slice(next_ticket_challenge.as_ref());
148 ret[EthereumChallenge::SIZE..].copy_from_slice(hint.as_ref());
149 Self(ret)
150 }
151
152 pub fn next_ticket_challenge(&self) -> EthereumChallenge {
154 EthereumChallenge::new(&self.0[0..EthereumChallenge::SIZE])
155 }
156
157 pub fn acknowledgement_challenge_or_hint(&self) -> HalfKeyChallenge {
160 HalfKeyChallenge::new(&self.0[EthereumChallenge::SIZE..])
161 }
162}
163
164impl std::fmt::Debug for ProofOfRelayString {
165 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
166 f.debug_tuple("ProofOfRelayString")
167 .field(&hex::encode(&self.0[0..EthereumChallenge::SIZE]))
168 .field(&hex::encode(&self.0[EthereumChallenge::SIZE..]))
169 .finish()
170 }
171}
172
173impl TryFrom<&[u8]> for ProofOfRelayString {
174 type Error = GeneralError;
175
176 fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
177 value
178 .try_into()
179 .map(Self)
180 .map_err(|_| GeneralError::ParseError("ProofOfRelayString".into()))
181 }
182}
183
184impl AsRef<[u8]> for ProofOfRelayString {
185 fn as_ref(&self) -> &[u8] {
186 &self.0
187 }
188}
189impl BytesRepresentable for ProofOfRelayString {
190 const SIZE: usize = EthereumChallenge::SIZE + HalfKeyChallenge::SIZE;
191}
192
193#[derive(Clone)]
196pub struct ProofOfRelayOutput {
197 pub own_key: HalfKey,
198 pub next_ticket_challenge: EthereumChallenge,
199 pub ack_challenge: HalfKeyChallenge,
200}
201
202pub fn pre_verify(
210 secret: &SharedSecret,
211 pors: &ProofOfRelayString,
212 challenge: &EthereumChallenge,
213) -> Result<ProofOfRelayOutput> {
214 let own_key = derive_own_key_share(secret);
215 let own_share = own_key.to_challenge();
216
217 if Challenge::from_hint_and_share(&own_share, &pors.acknowledgement_challenge_or_hint())?
218 .to_ethereum_challenge()
219 .eq(challenge)
220 {
221 Ok(ProofOfRelayOutput {
222 next_ticket_challenge: pors.next_ticket_challenge(),
223 ack_challenge: pors.acknowledgement_challenge_or_hint(),
224 own_key,
225 })
226 } else {
227 Err(PacketError::PoRVerificationError)
228 }
229}
230
231pub fn generate_proof_of_relay(secrets: &[SharedSecret]) -> Result<(Vec<ProofOfRelayString>, ProofOfRelayValues)> {
233 let mut last_ack_key_share = None;
234 let mut por_strings = Vec::with_capacity(secrets.len());
235 let mut por_values = None;
236
237 for i in 0..secrets.len() {
238 let hint = last_ack_key_share
239 .unwrap_or_else(|| {
240 derive_ack_key_share(&secrets[i]) })
242 .to_challenge();
243
244 let s1 = derive_own_key_share(&secrets[i]); let s2 = derive_ack_key_share(secrets.get(i + 1).unwrap_or(&SharedSecret::random()));
246
247 let next_ticket_challenge = Response::from_half_keys(&s1, &s2)? .to_challenge()
249 .to_ethereum_challenge();
250
251 if i > 0 {
252 por_strings.push(ProofOfRelayString::new(&next_ticket_challenge, &hint));
253 } else {
254 por_values = Some(ProofOfRelayValues::new(
255 secrets.len() as u8,
256 &hint,
257 &next_ticket_challenge,
258 ));
259 }
260 last_ack_key_share = Some(s2);
261 }
262
263 Ok((
264 por_strings,
265 por_values.ok_or(PacketError::LogicError("no shared secrets".into()))?,
266 ))
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 impl ProofOfRelayValues {
274 fn create(
275 secret_b: &SharedSecret,
276 secret_c: Option<&SharedSecret>,
277 chain_length: u8,
278 ) -> hopr_crypto_types::errors::Result<(Self, HalfKey)> {
279 let s0 = derive_own_key_share(secret_b);
280 let s1 = derive_ack_key_share(secret_c.unwrap_or(&SharedSecret::random()));
281
282 let ack_challenge = derive_ack_key_share(secret_b).to_challenge();
283 let ticket_challenge = Response::from_half_keys(&s0, &s1)?
284 .to_challenge()
285 .to_ethereum_challenge();
286
287 Ok((Self::new(chain_length, &ack_challenge, &ticket_challenge), s0))
288 }
289 }
290
291 impl ProofOfRelayString {
292 fn create(secret_c: &SharedSecret, secret_d: Option<&SharedSecret>) -> hopr_crypto_types::errors::Result<Self> {
294 let s0 = derive_ack_key_share(secret_c); let s1 = derive_own_key_share(secret_c); let s2 = derive_ack_key_share(secret_d.unwrap_or(&SharedSecret::random())); let next_ticket_challenge = Response::from_half_keys(&s1, &s2)? .to_challenge()
300 .to_ethereum_challenge();
301
302 let hint = s0.to_challenge();
303
304 Ok(Self::new(&next_ticket_challenge, &hint))
305 }
306
307 fn from_shared_secrets(secrets: &[SharedSecret]) -> hopr_crypto_types::errors::Result<Vec<ProofOfRelayString>> {
310 (1..secrets.len())
311 .map(|i| ProofOfRelayString::create(&secrets[i], secrets.get(i + 1)))
312 .collect()
313 }
314 }
315
316 fn validate_por_half_keys(ethereum_challenge: &EthereumChallenge, own_key: &HalfKey, ack: &HalfKey) -> bool {
318 Response::from_half_keys(own_key, ack)
319 .map(|response| validate_por_response(ethereum_challenge, &response))
320 .unwrap_or(false)
321 }
322
323 fn validate_por_response(ethereum_challenge: &EthereumChallenge, response: &Response) -> bool {
325 response.to_challenge().to_ethereum_challenge().eq(ethereum_challenge)
326 }
327
328 fn validate_por_hint(ethereum_challenge: &EthereumChallenge, own_share: &HalfKeyChallenge, ack: &HalfKey) -> bool {
330 Challenge::from_own_share_and_half_key(own_share, ack)
331 .map(|c| c.to_ethereum_challenge().eq(ethereum_challenge))
332 .unwrap_or(false)
333 }
334
335 #[test]
336 fn test_generate_proof_of_relay() -> anyhow::Result<()> {
337 for hops in 0..=3 {
338 let secrets = (0..=hops).map(|_| SharedSecret::random()).collect::<Vec<_>>();
339
340 let por_strings = ProofOfRelayString::from_shared_secrets(&secrets)?;
341 let por_values = ProofOfRelayValues::create(&secrets[0], secrets.get(1), secrets.len() as u8)?.0;
342
343 let (gen_por_strings, gen_por_values) = generate_proof_of_relay(&secrets)?;
344
345 if hops > 0 {
347 assert_eq!(por_values, gen_por_values);
348 }
349
350 assert_eq!(por_strings.len(), gen_por_strings.len());
351
352 for i in 0..por_strings.len() {
353 assert_eq!(
354 por_strings[i].acknowledgement_challenge_or_hint(),
355 gen_por_strings[i].acknowledgement_challenge_or_hint()
356 );
357
358 if i != por_strings.len() - 1 {
360 assert_eq!(
361 por_strings[i].next_ticket_challenge(),
362 gen_por_strings[i].next_ticket_challenge()
363 );
364 }
365 }
366 }
367
368 Ok(())
369 }
370
371 #[test]
372 fn test_por_preverify_validate() -> anyhow::Result<()> {
373 const AMOUNT: usize = 4;
374
375 let secrets = (0..AMOUNT).map(|_| SharedSecret::random()).collect::<Vec<_>>();
376
377 let first_challenge = ProofOfRelayValues::create(&secrets[0], Some(&secrets[1]), secrets.len() as u8)?.0;
379
380 let first_por_string = ProofOfRelayString::create(&secrets[1], Some(&secrets[2]))?;
382
383 let second_por_string = ProofOfRelayString::create(&secrets[2], Some(&secrets[3]))?;
385
386 let first_challenge_eth = first_challenge.ticket_challenge();
388 let first_result = pre_verify(&secrets[0], &first_por_string, &first_challenge_eth)
389 .expect("First challenge must be plausible");
390
391 let expected_hkc = derive_ack_key_share(&secrets[1]).to_challenge();
392 assert_eq!(expected_hkc, first_result.ack_challenge);
393
394 let expected_pors = ProofOfRelayString::try_from(first_por_string.as_ref())?;
396 assert_eq!(
397 expected_pors.next_ticket_challenge(),
398 first_result.next_ticket_challenge,
399 "Forward logic must extract correct challenge for the next downstream node"
400 );
401
402 let first_ack = derive_ack_key_share(&secrets[1]);
404 assert!(
405 validate_por_half_keys(&first_challenge.ticket_challenge(), &first_result.own_key, &first_ack),
406 "Acknowledgement must solve the challenge"
407 );
408
409 let first_result_challenge_eth = first_result.next_ticket_challenge;
411 let second_result = pre_verify(&secrets[1], &second_por_string, &first_result_challenge_eth)
412 .expect("Second challenge must be plausible");
413
414 let second_ack = derive_ack_key_share(&secrets[2]);
415 assert!(
416 validate_por_half_keys(&first_result.next_ticket_challenge, &second_result.own_key, &second_ack),
417 "Second acknowledgement must solve the challenge"
418 );
419
420 assert!(
421 validate_por_hint(
422 &first_result.next_ticket_challenge,
423 &second_result.own_key.to_challenge(),
424 &second_ack
425 ),
426 "Second acknowledgement must solve the challenge"
427 );
428
429 Ok(())
430 }
431
432 #[test]
433 fn test_challenge_and_response_solving() -> anyhow::Result<()> {
434 const AMOUNT: usize = 2;
435 let secrets = (0..AMOUNT).map(|_| SharedSecret::random()).collect::<Vec<_>>();
436
437 let (first_challenge, own_key) =
438 ProofOfRelayValues::create(&secrets[0], Some(&secrets[1]), secrets.len() as u8)?;
439 let ack = derive_ack_key_share(&secrets[1]);
440
441 assert!(
442 validate_por_half_keys(&first_challenge.ticket_challenge(), &own_key, &ack),
443 "Challenge must be solved"
444 );
445
446 assert!(
447 validate_por_response(
448 &first_challenge.ticket_challenge(),
449 &Response::from_half_keys(&own_key, &ack)?
450 ),
451 "Returned response must solve the challenge"
452 );
453
454 Ok(())
455 }
456}