chie_crypto/
onion.rs

1//! Onion encryption for privacy-preserving P2P routing.
2//!
3//! This module provides layered encryption similar to Tor's onion routing,
4//! where data is encrypted in multiple layers. Each intermediate node can
5//! decrypt only one layer to learn the next hop, preserving privacy.
6//!
7//! # Example
8//!
9//! ```
10//! use chie_crypto::onion::{OnionBuilder, OnionLayer};
11//! use chie_crypto::KeyPair;
12//!
13//! // Create routing path with 3 hops
14//! let hop1 = KeyPair::generate();
15//! let hop2 = KeyPair::generate();
16//! let hop3 = KeyPair::generate();
17//!
18//! let data = b"Secret message to route through network";
19//!
20//! // Build onion with layers
21//! let onion = OnionBuilder::new(data)
22//!     .add_layer(hop1.public_key())
23//!     .add_layer(hop2.public_key())
24//!     .add_layer(hop3.public_key())
25//!     .build()
26//!     .unwrap();
27//!
28//! // Each hop peels one layer
29//! let (layer1, next_onion) = onion.peel_layer(&hop3).unwrap();
30//! let (layer2, next_onion) = next_onion.unwrap().peel_layer(&hop2).unwrap();
31//! let (layer3, final_packet) = next_onion.unwrap().peel_layer(&hop1).unwrap();
32//!
33//! // Final packet contains the data
34//! assert_eq!(data, final_packet.unwrap().data());
35//! ```
36
37use crate::encryption::{decrypt, encrypt};
38use crate::signing::{KeyPair, PublicKey};
39use blake3;
40use rand::RngCore;
41use serde::{Deserialize, Serialize};
42use thiserror::Error;
43
44/// Error types for onion encryption operations.
45#[derive(Debug, Error)]
46pub enum OnionError {
47    #[error("No layers to build onion")]
48    NoLayers,
49
50    #[error("Encryption failed")]
51    EncryptionFailed,
52
53    #[error("Decryption failed - invalid key or corrupted data")]
54    DecryptionFailed,
55
56    #[error("Serialization error: {0}")]
57    SerializationError(String),
58
59    #[error("Invalid onion structure")]
60    InvalidStructure,
61}
62
63pub type OnionResult<T> = Result<T, OnionError>;
64
65/// An onion-encrypted packet with multiple layers.
66///
67/// Each layer is encrypted with a different public key. Only the holder
68/// of the corresponding private key can decrypt their layer.
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct OnionPacket {
71    /// The encrypted payload (contains either next layer or final data)
72    ciphertext: Vec<u8>,
73    /// Nonce for this layer's encryption
74    nonce: [u8; 12],
75    /// Ephemeral public key hint (for key derivation)
76    ephemeral_hint: [u8; 32],
77}
78
79impl OnionPacket {
80    /// Get the ciphertext (for final layer data extraction).
81    pub fn data(&self) -> &[u8] {
82        &self.ciphertext
83    }
84
85    /// Serialize to bytes.
86    pub fn to_bytes(&self) -> OnionResult<Vec<u8>> {
87        crate::codec::encode(self).map_err(|e| OnionError::SerializationError(e.to_string()))
88    }
89
90    /// Deserialize from bytes.
91    pub fn from_bytes(bytes: &[u8]) -> OnionResult<Self> {
92        crate::codec::decode(bytes).map_err(|e| OnionError::SerializationError(e.to_string()))
93    }
94
95    /// Peel one layer of the onion using the provided keypair.
96    ///
97    /// Returns a tuple of (layer_info, next_packet_or_data):
98    /// - If there are more layers, next_packet_or_data is Some(OnionPacket)
99    /// - If this is the last layer, next_packet_or_data is None and the data is returned
100    pub fn peel_layer(&self, keypair: &KeyPair) -> OnionResult<(OnionLayer, Option<OnionPacket>)> {
101        // Derive decryption key using public key and ephemeral hint
102        // This matches the encryption key derivation in build()
103        let mut hasher = blake3::Hasher::new();
104        hasher.update(b"CHIE-ONION-V1");
105        hasher.update(&keypair.public_key());
106        hasher.update(&self.ephemeral_hint);
107        let decryption_key = *hasher.finalize().as_bytes();
108
109        // Decrypt this layer
110        let decrypted = decrypt(&self.ciphertext, &decryption_key, &self.nonce)
111            .map_err(|_| OnionError::DecryptionFailed)?;
112
113        // Try to deserialize as OnionLayerPayload
114        let payload: OnionLayerPayload =
115            crate::codec::decode(&decrypted).map_err(|_| OnionError::InvalidStructure)?;
116
117        match payload {
118            OnionLayerPayload::Intermediate {
119                next_hop,
120                next_packet,
121            } => Ok((OnionLayer::Intermediate { next_hop }, Some(next_packet))),
122            OnionLayerPayload::Final { data } => Ok((
123                OnionLayer::Final,
124                Some(OnionPacket {
125                    ciphertext: data,
126                    nonce: [0; 12],
127                    ephemeral_hint: [0; 32],
128                }),
129            )),
130        }
131    }
132}
133
134/// Information about a peeled onion layer.
135#[derive(Debug, Clone)]
136pub enum OnionLayer {
137    /// Intermediate layer with next hop information
138    Intermediate {
139        /// Public key of the next hop
140        next_hop: PublicKey,
141    },
142    /// Final layer (contains the actual data)
143    Final,
144}
145
146/// Internal payload structure for onion layers.
147#[derive(Debug, Clone, Serialize, Deserialize)]
148enum OnionLayerPayload {
149    /// Intermediate layer pointing to next hop
150    Intermediate {
151        next_hop: PublicKey,
152        next_packet: OnionPacket,
153    },
154    /// Final layer containing the actual data
155    Final { data: Vec<u8> },
156}
157
158/// Builder for creating multi-layer onion packets.
159pub struct OnionBuilder {
160    data: Vec<u8>,
161    layers: Vec<PublicKey>,
162}
163
164impl OnionBuilder {
165    /// Create a new onion builder with the data to encrypt.
166    pub fn new(data: &[u8]) -> Self {
167        Self {
168            data: data.to_vec(),
169            layers: Vec::new(),
170        }
171    }
172
173    /// Add a layer to the onion (layers are added from innermost to outermost).
174    ///
175    /// The first layer added will be the first to decrypt (innermost).
176    /// The last layer added will be the last to decrypt (outermost).
177    pub fn add_layer(mut self, pubkey: PublicKey) -> Self {
178        self.layers.push(pubkey);
179        self
180    }
181
182    /// Add multiple layers at once.
183    pub fn add_layers(mut self, pubkeys: &[PublicKey]) -> Self {
184        self.layers.extend_from_slice(pubkeys);
185        self
186    }
187
188    /// Build the onion packet with all layers.
189    pub fn build(self) -> OnionResult<OnionPacket> {
190        if self.layers.is_empty() {
191            return Err(OnionError::NoLayers);
192        }
193
194        // Start with the final payload (the actual data)
195        let mut current_payload = OnionLayerPayload::Final { data: self.data };
196
197        // Wrap in layers from inside out
198        // We iterate through layers and create encrypted packets
199        for (i, pubkey) in self.layers.iter().enumerate() {
200            // Serialize current payload
201            let payload_bytes = crate::codec::encode(&current_payload)
202                .map_err(|e| OnionError::SerializationError(e.to_string()))?;
203
204            // Generate ephemeral hint for key derivation
205            let mut ephemeral_hint = [0u8; 32];
206            rand::thread_rng().fill_bytes(&mut ephemeral_hint);
207
208            // Derive encryption key for current layer
209            let mut hasher = blake3::Hasher::new();
210            hasher.update(b"CHIE-ONION-V1");
211            hasher.update(pubkey);
212            hasher.update(&ephemeral_hint);
213            let encryption_key = *hasher.finalize().as_bytes();
214
215            // Generate nonce
216            let mut nonce = [0u8; 12];
217            rand::thread_rng().fill_bytes(&mut nonce);
218
219            // Encrypt the payload
220            let ciphertext = encrypt(&payload_bytes, &encryption_key, &nonce)
221                .map_err(|_| OnionError::EncryptionFailed)?;
222
223            let packet = OnionPacket {
224                ciphertext,
225                nonce,
226                ephemeral_hint,
227            };
228
229            // If this is the last (outermost) layer, return the packet
230            if i == self.layers.len() - 1 {
231                return Ok(packet);
232            }
233
234            // Otherwise, wrap this packet for the next layer
235            // The next layer should know to forward to the current layer
236            current_payload = OnionLayerPayload::Intermediate {
237                next_hop: *pubkey,
238                next_packet: packet,
239            };
240        }
241
242        Err(OnionError::InvalidStructure)
243    }
244}
245
246/// Multi-hop onion route for P2P communication.
247pub struct OnionRoute {
248    /// The complete routing path (public keys)
249    path: Vec<PublicKey>,
250}
251
252impl OnionRoute {
253    /// Create a new onion route with the specified path.
254    pub fn new(path: Vec<PublicKey>) -> Self {
255        Self { path }
256    }
257
258    /// Get the route length (number of hops).
259    pub fn length(&self) -> usize {
260        self.path.len()
261    }
262
263    /// Get the path of public keys.
264    pub fn path(&self) -> &[PublicKey] {
265        &self.path
266    }
267
268    /// Encrypt data for this route.
269    pub fn encrypt(&self, data: &[u8]) -> OnionResult<OnionPacket> {
270        let mut builder = OnionBuilder::new(data);
271        for pubkey in &self.path {
272            builder = builder.add_layer(*pubkey);
273        }
274        builder.build()
275    }
276}
277
278/// Create an onion packet with the given data and routing path.
279///
280/// This is a convenience function that creates an OnionBuilder and builds the packet.
281pub fn create_onion(data: &[u8], path: &[PublicKey]) -> OnionResult<OnionPacket> {
282    let mut builder = OnionBuilder::new(data);
283    for pubkey in path {
284        builder = builder.add_layer(*pubkey);
285    }
286    builder.build()
287}
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292    use crate::signing::KeyPair;
293
294    #[test]
295    fn test_onion_single_layer() {
296        let data = b"Single layer test";
297        let keypair = KeyPair::generate();
298
299        let onion = OnionBuilder::new(data)
300            .add_layer(keypair.public_key())
301            .build()
302            .unwrap();
303
304        let (layer, next) = onion.peel_layer(&keypair).unwrap();
305        assert!(matches!(layer, OnionLayer::Final));
306
307        let final_data = next.unwrap();
308        assert_eq!(data, &final_data.ciphertext[..]);
309    }
310
311    #[test]
312    fn test_onion_two_layers() {
313        let data = b"Two layer test";
314        let keypair1 = KeyPair::generate();
315        let keypair2 = KeyPair::generate();
316
317        let onion = OnionBuilder::new(data)
318            .add_layer(keypair1.public_key())
319            .add_layer(keypair2.public_key())
320            .build()
321            .unwrap();
322
323        // Peel outer layer (keypair2)
324        let (layer1, next1) = onion.peel_layer(&keypair2).unwrap();
325        if let OnionLayer::Intermediate { next_hop } = layer1 {
326            assert_eq!(next_hop, keypair1.public_key());
327        } else {
328            panic!("Expected intermediate layer");
329        }
330
331        // Peel inner layer (keypair1)
332        let onion2 = next1.unwrap();
333        let (layer2, final_data) = onion2.peel_layer(&keypair1).unwrap();
334        assert!(matches!(layer2, OnionLayer::Final));
335
336        let data_packet = final_data.unwrap();
337        assert_eq!(data, &data_packet.ciphertext[..]);
338    }
339
340    #[test]
341    fn test_onion_three_layers() {
342        let data = b"Three layer test message";
343        let keypair1 = KeyPair::generate();
344        let keypair2 = KeyPair::generate();
345        let keypair3 = KeyPair::generate();
346
347        let onion = OnionBuilder::new(data)
348            .add_layer(keypair1.public_key())
349            .add_layer(keypair2.public_key())
350            .add_layer(keypair3.public_key())
351            .build()
352            .unwrap();
353
354        // Peel layer 3
355        let (layer3, next3) = onion.peel_layer(&keypair3).unwrap();
356        if let OnionLayer::Intermediate { next_hop } = layer3 {
357            assert_eq!(next_hop, keypair2.public_key());
358        } else {
359            panic!("Expected intermediate layer");
360        }
361
362        // Peel layer 2
363        let onion2 = next3.unwrap();
364        let (layer2, next2) = onion2.peel_layer(&keypair2).unwrap();
365        if let OnionLayer::Intermediate { next_hop } = layer2 {
366            assert_eq!(next_hop, keypair1.public_key());
367        } else {
368            panic!("Expected intermediate layer");
369        }
370
371        // Peel layer 1
372        let onion1 = next2.unwrap();
373        let (layer1, final_data) = onion1.peel_layer(&keypair1).unwrap();
374        assert!(matches!(layer1, OnionLayer::Final));
375
376        let data_packet = final_data.unwrap();
377        assert_eq!(data, &data_packet.ciphertext[..]);
378    }
379
380    #[test]
381    fn test_onion_wrong_key() {
382        let data = b"Test data";
383        let keypair1 = KeyPair::generate();
384        let keypair2 = KeyPair::generate();
385        let wrong_keypair = KeyPair::generate();
386
387        let onion = OnionBuilder::new(data)
388            .add_layer(keypair1.public_key())
389            .add_layer(keypair2.public_key())
390            .build()
391            .unwrap();
392
393        // Try to decrypt with wrong key
394        let result = onion.peel_layer(&wrong_keypair);
395        assert!(matches!(result, Err(OnionError::DecryptionFailed)));
396    }
397
398    #[test]
399    fn test_onion_no_layers() {
400        let data = b"Test";
401        let result = OnionBuilder::new(data).build();
402        assert!(matches!(result, Err(OnionError::NoLayers)));
403    }
404
405    #[test]
406    fn test_onion_serialization() {
407        let data = b"Serialization test";
408        let keypair1 = KeyPair::generate();
409        let keypair2 = KeyPair::generate();
410
411        let onion = OnionBuilder::new(data)
412            .add_layer(keypair1.public_key())
413            .add_layer(keypair2.public_key())
414            .build()
415            .unwrap();
416
417        // Serialize and deserialize
418        let bytes = onion.to_bytes().unwrap();
419        let deserialized = OnionPacket::from_bytes(&bytes).unwrap();
420
421        // Verify deserialized onion works
422        let (_, next) = deserialized.peel_layer(&keypair2).unwrap();
423        let onion2 = next.unwrap();
424        let (_, final_data) = onion2.peel_layer(&keypair1).unwrap();
425
426        assert_eq!(data, &final_data.unwrap().ciphertext[..]);
427    }
428
429    #[test]
430    fn test_onion_route() {
431        let data = b"Route test";
432        let keypair1 = KeyPair::generate();
433        let keypair2 = KeyPair::generate();
434        let keypair3 = KeyPair::generate();
435
436        let path = vec![
437            keypair1.public_key(),
438            keypair2.public_key(),
439            keypair3.public_key(),
440        ];
441
442        let route = OnionRoute::new(path.clone());
443        assert_eq!(route.length(), 3);
444        assert_eq!(route.path(), &path[..]);
445
446        let onion = route.encrypt(data).unwrap();
447
448        // Peel all layers
449        let (_, next) = onion.peel_layer(&keypair3).unwrap();
450        let (_, next) = next.unwrap().peel_layer(&keypair2).unwrap();
451        let (_, final_data) = next.unwrap().peel_layer(&keypair1).unwrap();
452
453        assert_eq!(data, &final_data.unwrap().ciphertext[..]);
454    }
455
456    #[test]
457    fn test_create_onion_convenience() {
458        let data = b"Convenience function test";
459        let keypair1 = KeyPair::generate();
460        let keypair2 = KeyPair::generate();
461
462        let path = vec![keypair1.public_key(), keypair2.public_key()];
463
464        let onion = create_onion(data, &path).unwrap();
465
466        let (_, next) = onion.peel_layer(&keypair2).unwrap();
467        let (_, final_data) = next.unwrap().peel_layer(&keypair1).unwrap();
468
469        assert_eq!(data, &final_data.unwrap().ciphertext[..]);
470    }
471
472    #[test]
473    fn test_large_data() {
474        let data = vec![0x42u8; 10_000]; // 10KB
475        let keypair1 = KeyPair::generate();
476        let keypair2 = KeyPair::generate();
477
478        let onion = OnionBuilder::new(&data)
479            .add_layer(keypair1.public_key())
480            .add_layer(keypair2.public_key())
481            .build()
482            .unwrap();
483
484        let (_, next) = onion.peel_layer(&keypair2).unwrap();
485        let (_, final_data) = next.unwrap().peel_layer(&keypair1).unwrap();
486
487        assert_eq!(data, final_data.unwrap().ciphertext);
488    }
489
490    #[test]
491    fn test_add_layers_batch() {
492        let data = b"Batch layers test";
493        let keypairs: Vec<KeyPair> = (0..5).map(|_| KeyPair::generate()).collect();
494        let pubkeys: Vec<PublicKey> = keypairs.iter().map(|kp| kp.public_key()).collect();
495
496        let onion = OnionBuilder::new(data)
497            .add_layers(&pubkeys)
498            .build()
499            .unwrap();
500
501        // Peel all layers
502        let mut current = Some(onion);
503        for keypair in keypairs.iter().rev() {
504            if let Some(pkt) = current {
505                let (_, next) = pkt.peel_layer(keypair).unwrap();
506                current = next;
507            }
508        }
509
510        let final_data = current.unwrap();
511        assert_eq!(data, &final_data.ciphertext[..]);
512    }
513}