1use super::lioness::{lioness_decrypt, lioness_encrypt, LionessKeys};
4use super::packet::{PacketError, SphinxPacket};
5use super::{
6 apply_stream_cipher, compute_mac, derive_keys, PathHop, SphinxError, SphinxHeader,
7 ROUTING_INFO_SIZE, SHIFT_SIZE,
8};
9use curve25519_dalek::montgomery::MontgomeryPoint;
10use curve25519_dalek::scalar::Scalar;
11use rand::Rng;
12use serde::{Deserialize, Serialize};
13use thiserror::Error;
14use x25519_dalek::PublicKey as X25519PublicKey;
15use zeroize::{Zeroize, ZeroizeOnDrop};
16
17fn unpad_iso7816(data: &[u8]) -> Result<Vec<u8>, SurbError> {
19 super::unpad_iso7816_inner(data).ok_or(SurbError::InvalidPadding)
20}
21
22pub const DEFAULT_POW_DIFFICULTY: u32 = 0;
23
24#[derive(Debug, Error)]
25pub enum SurbError {
26 #[error("Return path is empty")]
27 EmptyPath,
28
29 #[error("Sphinx error: {0}")]
30 Sphinx(#[from] SphinxError),
31
32 #[error("Packet error: {0}")]
33 Packet(#[from] PacketError),
34
35 #[error("Message too large: {size} bytes")]
36 MessageTooLarge { size: usize },
37
38 #[error("Invalid ISO 7816-4 padding in decrypted body")]
39 InvalidPadding,
40
41 #[error("Invalid address at hop {index}: {reason}")]
42 InvalidAddress { index: usize, reason: String },
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct Surb {
49 pub id: [u8; 16],
50 pub header: SphinxHeader,
51 pub first_hop: String,
52 pub payload_keys: LionessKeys,
53}
54
55impl Surb {
56 pub fn new(
58 path: &[PathHop],
59 id: [u8; 16],
60 pow_difficulty: u32,
61 ) -> Result<(Self, SurbRecovery), SurbError> {
62 if path.is_empty() {
63 return Err(SurbError::EmptyPath);
64 }
65
66 for (i, hop) in path.iter().enumerate() {
67 if hop.address.is_empty() {
68 return Err(SurbError::InvalidAddress {
69 index: i,
70 reason: "address is empty".into(),
71 });
72 }
73 if hop.address.len() > 255 {
74 return Err(SurbError::InvalidAddress {
75 index: i,
76 reason: format!("address too long ({} bytes, max 255)", hop.address.len()),
77 });
78 }
79 }
80
81 let mut rng = rand::rngs::OsRng;
82
83 let mut ephemeral_public_keys = Vec::with_capacity(path.len());
85 let mut shared_secrets = Vec::with_capacity(path.len());
86 let mut blinding_factors = Vec::with_capacity(path.len());
87
88 let mut current_secret_scalar = Scalar::random(&mut rng);
89 let initial_pk_point =
90 curve25519_dalek::constants::X25519_BASEPOINT * current_secret_scalar;
91 let mut accumulated_blinding = Scalar::ONE;
92
93 for hop in path {
94 let hop_pk_point = MontgomeryPoint(hop.public_key.to_bytes());
95 let shared_point = hop_pk_point * current_secret_scalar;
96 let shared_bytes = shared_point.to_bytes();
97
98 shared_secrets.push(shared_bytes);
99
100 let (_, _, _, blinding) = derive_keys(&shared_bytes);
101 blinding_factors.push(blinding);
102
103 let blinded_point = initial_pk_point * accumulated_blinding;
104 let hop_pk = X25519PublicKey::from(blinded_point.to_bytes());
105 ephemeral_public_keys.push(hop_pk);
106
107 accumulated_blinding *= blinding;
108 current_secret_scalar *= blinding;
109 }
110
111 let mut filler = Vec::new();
112 for secret in shared_secrets.iter().take(path.len().saturating_sub(1)) {
113 let (rho, _, _, _) = derive_keys(secret);
114 let mut keystream = [0u8; ROUTING_INFO_SIZE + SHIFT_SIZE];
115 apply_stream_cipher(&rho, &[0u8; 12], &mut keystream);
116
117 let filler_start_in_keystream = ROUTING_INFO_SIZE - filler.len();
118 for (j, byte) in filler.iter_mut().enumerate() {
119 *byte ^= keystream[filler_start_in_keystream + j];
120 }
121 filler.extend_from_slice(&keystream[ROUTING_INFO_SIZE..ROUTING_INFO_SIZE + SHIFT_SIZE]);
122 }
123
124 if !filler.is_empty() && !path.is_empty() {
125 let (rho, _, _, _) = derive_keys(&shared_secrets[path.len() - 1]);
126 let mut keystream = [0u8; ROUTING_INFO_SIZE];
127 apply_stream_cipher(&rho, &[0u8; 12], &mut keystream);
128 let filler_start = ROUTING_INFO_SIZE - filler.len();
129 for (j, byte) in filler.iter_mut().enumerate() {
130 *byte ^= keystream[filler_start + j];
131 }
132 }
133
134 let mut routing_info = [0u8; ROUTING_INFO_SIZE];
135 let mut next_mac = [0u8; 32];
136 let mut layer_keys: Vec<LionessKeys> = Vec::with_capacity(path.len());
137
138 for i in (0..path.len()).rev() {
139 let shared = shared_secrets[i];
140 let (rho, mu, pi, _) = derive_keys(&shared);
141
142 layer_keys.push(LionessKeys::from_pi(&pi));
143
144 let mut current_routing = [0u8; ROUTING_INFO_SIZE];
145
146 if i == path.len() - 1 {
147 current_routing[0] = 0x01; if !filler.is_empty() {
150 let filler_start = ROUTING_INFO_SIZE - filler.len();
151 current_routing[filler_start..].copy_from_slice(&filler);
152 }
153 } else {
154 current_routing[0] = 0x00;
156 let next_addr_bytes = path[i + 1].address.as_bytes();
157 current_routing[1] = next_addr_bytes.len() as u8;
158 current_routing[2..34].copy_from_slice(&next_mac);
159 current_routing[34..34 + next_addr_bytes.len()].copy_from_slice(next_addr_bytes);
160
161 let remainder_len = ROUTING_INFO_SIZE - SHIFT_SIZE;
162 current_routing[SHIFT_SIZE..].copy_from_slice(&routing_info[0..remainder_len]);
163 }
164
165 apply_stream_cipher(&rho, &[0u8; 12], &mut current_routing);
166 let mac = compute_mac(&mu, ¤t_routing);
167
168 routing_info = current_routing;
169 next_mac = mac;
170 }
171
172 let mut payload_pi = [0u8; 32];
173 rng.fill(&mut payload_pi);
174 let payload_keys = LionessKeys::from_pi(&payload_pi);
175
176 let mut header = SphinxHeader {
177 ephemeral_key: ephemeral_public_keys[0],
178 routing_info,
179 mac: next_mac,
180 nonce: 0,
181 };
182 header.solve_pow(pow_difficulty)?;
183
184 let surb = Surb {
185 id,
186 header,
187 first_hop: path[0].address.clone(),
188 payload_keys: payload_keys.clone(),
189 };
190
191 let recovery = SurbRecovery {
192 id,
193 layer_keys,
194 payload_keys,
195 };
196
197 Ok((surb, recovery))
198 }
199
200 pub fn encapsulate(&self, message: &[u8]) -> Result<SphinxPacket, SurbError> {
202 use super::packet::PACKET_SIZE;
203 use super::HEADER_SIZE as SPHINX_HEADER_SIZE;
204
205 let body_size = PACKET_SIZE - SPHINX_HEADER_SIZE;
206
207 if message.len() >= body_size {
208 return Err(SurbError::MessageTooLarge {
209 size: message.len(),
210 });
211 }
212
213 let mut body = vec![0u8; body_size];
214 body[..message.len()].copy_from_slice(message);
215 body[message.len()] = 0x80;
216
217 lioness_encrypt(&self.payload_keys, &mut body);
218
219 let packet_bytes = self.header.to_bytes(&body);
220
221 if packet_bytes.len() != PACKET_SIZE {
222 let mut final_packet = vec![0u8; PACKET_SIZE];
223 let copy_len = packet_bytes.len().min(PACKET_SIZE);
224 final_packet[..copy_len].copy_from_slice(&packet_bytes[..copy_len]);
225 return SphinxPacket::from_bytes(final_packet).map_err(SurbError::Packet);
226 }
227
228 SphinxPacket::from_bytes(packet_bytes).map_err(SurbError::Packet)
229 }
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
234pub struct SurbRecovery {
235 pub id: [u8; 16],
236 pub layer_keys: Vec<LionessKeys>,
238 pub payload_keys: LionessKeys,
240}
241
242impl SurbRecovery {
243 pub fn decrypt(&self, encrypted_body: &[u8]) -> Result<Vec<u8>, SurbError> {
245 let mut body = encrypted_body.to_vec();
246
247 for keys in &self.layer_keys {
249 lioness_encrypt(keys, &mut body);
250 }
251
252 lioness_decrypt(&self.payload_keys, &mut body);
253
254 let unpadded = unpad_iso7816(&body)?;
255
256 Ok(unpadded)
257 }
258
259 pub fn decrypt_packet(&self, packet: &SphinxPacket) -> Result<Vec<u8>, SurbError> {
261 let packet_bytes = packet.as_bytes();
262 let body_start = super::HEADER_SIZE;
263 let encrypted_body = &packet_bytes[body_start..];
264
265 self.decrypt(encrypted_body)
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272 use x25519_dalek::StaticSecret as X25519SecretKey;
273
274 fn generate_test_path(num_hops: usize) -> (Vec<PathHop>, Vec<X25519SecretKey>) {
275 let mut rng = rand::thread_rng();
276 let secret_keys: Vec<X25519SecretKey> = (0..num_hops)
277 .map(|_| X25519SecretKey::random_from_rng(&mut rng))
278 .collect();
279
280 let path: Vec<PathHop> = secret_keys
281 .iter()
282 .enumerate()
283 .map(|(i, sk)| PathHop {
284 public_key: X25519PublicKey::from(sk),
285 address: format!("node_{}_addr", i),
286 })
287 .collect();
288
289 (path, secret_keys)
290 }
291
292 #[test]
293 fn test_surb_construction() {
294 let (path, _) = generate_test_path(3);
295 let id: [u8; 16] = rand::random();
296
297 let result = Surb::new(&path, id, 0);
298 assert!(result.is_ok());
299
300 let (surb, recovery) = result.unwrap();
301
302 assert_eq!(surb.id, id);
303 assert_eq!(surb.first_hop, "node_0_addr");
304
305 assert_eq!(recovery.id, id);
306 assert_eq!(recovery.layer_keys.len(), 3);
307 assert_eq!(recovery.payload_keys.k1, surb.payload_keys.k1);
308 assert_eq!(recovery.payload_keys.k2, surb.payload_keys.k2);
309 assert_eq!(recovery.payload_keys.k3, surb.payload_keys.k3);
310 assert_eq!(recovery.payload_keys.k4, surb.payload_keys.k4);
311 }
312
313 #[test]
314 fn test_surb_encapsulate() {
315 let (path, _) = generate_test_path(3);
316 let id: [u8; 16] = rand::random();
317
318 let (surb, _recovery) = Surb::new(&path, id, 0).expect("SURB construction failed");
319
320 let message = b"Hello Reply";
321 let result = surb.encapsulate(message);
322 assert!(result.is_ok());
323
324 let packet = result.unwrap();
325 assert_eq!(packet.as_bytes().len(), crate::sphinx::packet::PACKET_SIZE);
326 }
327
328 #[test]
329 fn test_surb_full_roundtrip() {
330 let (path, _secret_keys) = generate_test_path(3);
331 let id: [u8; 16] = rand::random();
332
333 let (surb, recovery) = Surb::new(&path, id, 0).expect("SURB construction failed");
334
335 let message = b"Transaction confirmed: 0x1234567890abcdef";
336 let _packet = surb.encapsulate(message).expect("Encapsulation failed");
337 let body_size = 512;
338 let mut test_body = vec![0u8; body_size];
339 test_body[..message.len()].copy_from_slice(message);
340
341 let original = test_body.clone();
342
343 lioness_encrypt(&surb.payload_keys, &mut test_body);
344 lioness_decrypt(&recovery.payload_keys, &mut test_body);
345
346 assert_eq!(test_body, original);
347 }
348
349 #[test]
350 fn test_surb_empty_path() {
351 let path: Vec<PathHop> = vec![];
352 let id: [u8; 16] = rand::random();
353
354 let result = Surb::new(&path, id, 0);
355 assert!(matches!(result, Err(SurbError::EmptyPath)));
356 }
357
358 #[test]
359 fn test_surb_serialization() {
360 let (path, _) = generate_test_path(2);
361 let id: [u8; 16] = rand::random();
362
363 let (surb, _) = Surb::new(&path, id, 0).expect("SURB construction failed");
364
365 let json = serde_json::to_string(&surb).expect("Serialization failed");
366 let deserialized: Surb = serde_json::from_str(&json).expect("Deserialization failed");
367
368 assert_eq!(deserialized.id, surb.id);
369 assert_eq!(deserialized.first_hop, surb.first_hop);
370 assert_eq!(deserialized.payload_keys.k1, surb.payload_keys.k1);
371 assert_eq!(deserialized.payload_keys.k2, surb.payload_keys.k2);
372 }
373
374 #[test]
376 fn test_decrypt_rejects_random_data() {
377 let (path, _) = generate_test_path(3);
378 let id: [u8; 16] = rand::random();
379
380 let (_, recovery) = Surb::new(&path, id, 0).expect("SURB construction failed");
381
382 let mut rng = rand::thread_rng();
383
384 let random_body: Vec<u8> = (0..32296).map(|_| rand::Rng::gen::<u8>(&mut rng)).collect();
385 let result = recovery.decrypt(&random_body);
386 let mut failures = 0;
387 for _ in 0..100 {
388 let random_data: Vec<u8> = (0..32296).map(|_| rand::Rng::gen::<u8>(&mut rng)).collect();
389 if recovery.decrypt(&random_data).is_err() {
390 failures += 1;
391 }
392 }
393 assert!(
394 failures >= 90,
395 "only {failures}/100 random inputs were rejected -- padding check too permissive",
396 );
397
398 assert!(result.is_err());
399 }
400
401 #[test]
402 fn test_surb_full_roundtrip_with_sphinx() {
403 let (path, secret_keys) = generate_test_path(3);
404 let id: [u8; 16] = rand::random();
405
406 let (surb, recovery) = Surb::new(&path, id, 0).expect("SURB construction failed");
407
408 let message = b"Transaction confirmed: 0x1234567890abcdef";
409 let packet = surb.encapsulate(message).expect("Encapsulation failed");
410
411 let packet_bytes = packet.into_bytes();
412 let (header0, body0) = SphinxHeader::from_bytes(&packet_bytes).expect("Parse failed");
413 let result0 = super::super::into_result(
414 header0
415 .process(&secret_keys[0], body0.to_vec())
416 .expect("Hop 0 failed"),
417 );
418
419 let (next_packet1, body1) = match result0 {
420 super::super::ProcessResult::Forward {
421 next_packet,
422 processed_body,
423 ..
424 } => (next_packet, processed_body),
425 super::super::ProcessResult::Exit { .. } => panic!("Hop 0 should be forward"),
426 };
427
428 let bytes1 = next_packet1.to_bytes(&body1);
429 let (header1, body1_raw) = SphinxHeader::from_bytes(&bytes1).expect("Parse failed");
430 let result1 = super::super::into_result(
431 header1
432 .process(&secret_keys[1], body1_raw.to_vec())
433 .expect("Hop 1 failed"),
434 );
435
436 let (next_packet2, body2) = match result1 {
437 super::super::ProcessResult::Forward {
438 next_packet,
439 processed_body,
440 ..
441 } => (next_packet, processed_body),
442 super::super::ProcessResult::Exit { .. } => panic!("Hop 1 should be forward"),
443 };
444
445 let bytes2 = next_packet2.to_bytes(&body2);
446 let (header2, body2_raw) = SphinxHeader::from_bytes(&bytes2).expect("Parse failed");
447 let result2 = super::super::into_result(
448 header2
449 .process(&secret_keys[2], body2_raw.to_vec())
450 .expect("Hop 2 failed"),
451 );
452
453 let exit_payload = match result2 {
454 super::super::ProcessResult::Exit { payload } => payload,
455 super::super::ProcessResult::Forward { .. } => panic!("Hop 2 should be exit"),
456 };
457
458 let decrypted = recovery
459 .decrypt(&exit_payload)
460 .expect("SURB decryption failed");
461 assert_eq!(decrypted, message);
462 }
463
464 #[test]
466 fn test_decrypt_packet_correct_offset() {
467 use super::super::packet::PACKET_SIZE;
468
469 let (path, _secret_keys) = generate_test_path(3);
470 let id: [u8; 16] = rand::random();
471
472 let (surb, recovery) = Surb::new(&path, id, 0).expect("SURB construction failed");
473 let message = b"decrypt_packet regression test payload";
474 let packet = surb.encapsulate(message).expect("Encapsulation failed");
475
476 let recovery_no_layers = SurbRecovery {
477 id: recovery.id,
478 layer_keys: vec![],
479 payload_keys: recovery.payload_keys.clone(),
480 };
481
482 let result = recovery_no_layers.decrypt_packet(&packet);
483 assert!(result.is_ok(), "decrypt_packet failed: {:?}", result.err());
484 assert_eq!(result.unwrap(), message.as_slice());
485
486 let packet_bytes = packet.as_bytes();
487 let sphinx_header_size = super::super::HEADER_SIZE;
488 assert_eq!(sphinx_header_size, 472);
489 let body_manual = &packet_bytes[sphinx_header_size..];
490 let manual_result = recovery_no_layers.decrypt(body_manual);
491 assert_eq!(manual_result.unwrap(), message.as_slice());
492
493 assert_eq!(packet_bytes.len(), PACKET_SIZE);
494 }
495
496 #[test]
498 fn test_decrypt_packet_wrong_offset_would_fail() {
499 use super::super::packet::PACKET_SIZE;
500
501 let (path, _secret_keys) = generate_test_path(3);
502 let id: [u8; 16] = rand::random();
503
504 let (surb, recovery) = Surb::new(&path, id, 0).expect("SURB construction failed");
505 let message = b"offset regression test";
506 let packet = surb.encapsulate(message).expect("Encapsulation failed");
507
508 let recovery_no_layers = SurbRecovery {
509 id: recovery.id,
510 layer_keys: vec![],
511 payload_keys: recovery.payload_keys.clone(),
512 };
513
514 let packet_bytes = packet.as_bytes();
515 let wrong_offset = super::super::packet::HEADER_SIZE + super::super::packet::NONCE_SIZE;
516 let correct_offset = super::super::HEADER_SIZE;
517
518 assert_eq!(wrong_offset, 1036);
519 assert_eq!(correct_offset, 472);
520 assert_ne!(wrong_offset, correct_offset);
521
522 let wrong_body = &packet_bytes[wrong_offset..];
523 let wrong_result = recovery_no_layers.decrypt(wrong_body);
524 assert!(
525 wrong_result.is_err(),
526 "offset 1036 unexpectedly decrypted Ok({} bytes)",
527 wrong_result.as_ref().map(|v| v.len()).unwrap_or(0)
528 );
529
530 let correct_body = &packet_bytes[correct_offset..];
531 let correct_result = recovery_no_layers.decrypt(correct_body);
532 assert!(
533 correct_result.is_ok(),
534 "offset 472 failed: {:?}",
535 correct_result.err()
536 );
537 assert_eq!(correct_result.unwrap(), message.as_slice());
538
539 let via_method = recovery_no_layers.decrypt_packet(&packet);
540 assert!(via_method.is_ok());
541 assert_eq!(via_method.unwrap(), message.as_slice());
542
543 assert_eq!(packet_bytes.len(), PACKET_SIZE);
544 }
545
546 #[test]
547 fn test_surb_rejects_empty_address() {
548 let mut rng = rand::thread_rng();
549 let sk = X25519SecretKey::random_from_rng(&mut rng);
550 let pk = X25519PublicKey::from(&sk);
551
552 let path = vec![PathHop {
553 public_key: pk,
554 address: String::new(),
555 }];
556 let id: [u8; 16] = rand::random();
557
558 let result = Surb::new(&path, id, 0);
559 assert!(
560 matches!(result, Err(SurbError::InvalidAddress { index: 0, .. })),
561 "got: {result:?}",
562 );
563 }
564
565 #[test]
566 fn test_surb_rejects_oversized_address() {
567 let mut rng = rand::thread_rng();
568 let sk = X25519SecretKey::random_from_rng(&mut rng);
569 let pk = X25519PublicKey::from(&sk);
570
571 let long_address = "x".repeat(256);
572 let path = vec![PathHop {
573 public_key: pk,
574 address: long_address,
575 }];
576 let id: [u8; 16] = rand::random();
577
578 let result = Surb::new(&path, id, 0);
579 assert!(
580 matches!(result, Err(SurbError::InvalidAddress { index: 0, .. })),
581 "got: {result:?}",
582 );
583 }
584
585 #[test]
586 fn test_unpad_iso7816_valid() {
587 use crate::sphinx::unpad_iso7816_inner;
588
589 let mut padded = b"hello".to_vec();
591 padded.push(0x80);
592 padded.extend_from_slice(&[0x00, 0x00, 0x00]);
593
594 let result = unpad_iso7816_inner(&padded);
595 assert_eq!(result, Some(b"hello".to_vec()));
596 }
597
598 #[test]
599 fn test_unpad_iso7816_marker_at_end() {
600 use crate::sphinx::unpad_iso7816_inner;
601
602 let mut padded = b"data".to_vec();
603 padded.push(0x80);
604
605 let result = unpad_iso7816_inner(&padded);
606 assert_eq!(result, Some(b"data".to_vec()));
607 }
608
609 #[test]
610 fn test_unpad_iso7816_invalid_rejected() {
611 use crate::sphinx::unpad_iso7816_inner;
612
613 let corrupted = vec![0x01, 0x02, 0x80, 0xFF];
614 let result = unpad_iso7816_inner(&corrupted);
615 assert_eq!(result, None);
616 }
617
618 #[test]
619 fn test_unpad_iso7816_empty_rejected() {
620 use crate::sphinx::unpad_iso7816_inner;
621
622 let result = unpad_iso7816_inner(&[]);
623 assert_eq!(result, None);
624 }
625
626 #[test]
627 fn test_unpad_iso7816_no_marker_rejected() {
628 use crate::sphinx::unpad_iso7816_inner;
629
630 let no_marker = vec![0x01, 0x02, 0x03, 0x04];
631 let result = unpad_iso7816_inner(&no_marker);
632 assert_eq!(result, None);
633 }
634
635 #[test]
636 fn test_unpad_iso7816_empty_payload() {
637 use crate::sphinx::unpad_iso7816_inner;
638
639 let just_marker = vec![0x80, 0x00, 0x00];
640 let result = unpad_iso7816_inner(&just_marker);
641 assert_eq!(result, Some(vec![]));
642 }
643
644 #[test]
645 fn test_surb_id_hex_roundtrip() {
646 let id: [u8; 16] = rand::random();
647
648 let (path, _) = {
649 let mut rng = rand::thread_rng();
650 let sk = X25519SecretKey::random_from_rng(&mut rng);
651 let pk = X25519PublicKey::from(&sk);
652 let path = vec![PathHop {
653 public_key: pk,
654 address: "node_0".into(),
655 }];
656 (path, ())
657 };
658
659 let (_, recovery) = Surb::new(&path, id, 0).expect("SURB construction must succeed");
660
661 let hex = hex::encode(recovery.id);
662 let decoded = hex::decode(&hex).expect("hex decode");
663 let recovered: [u8; 16] = decoded.try_into().expect("16 bytes");
664
665 assert_eq!(recovery.id, recovered);
666 assert_eq!(hex.len(), 32);
667 }
668}