chie_crypto/
aggregate_mac.rs

1//! Aggregate MAC for efficient multi-message authentication.
2//!
3//! This module provides aggregate Message Authentication Codes (MACs) that allow
4//! authenticating multiple messages with a single verification step. This is particularly
5//! useful for:
6//! - Batch verification of content chunks in P2P networks
7//! - Reducing verification overhead when processing many messages
8//! - Bandwidth-efficient authentication of large datasets
9//!
10//! The aggregate MAC scheme uses BLAKE3 keyed hashing with domain separation:
11//! - Each message is tagged with its index for domain separation
12//! - Individual MACs are combined via XOR aggregation
13//! - Single aggregate tag can verify all messages at once
14//! - Constant-size tags regardless of number of messages
15//!
16//! # Example
17//!
18//! ```
19//! use chie_crypto::aggregate_mac::{AggregateMacKey, AggregateMacBuilder};
20//!
21//! // Generate a key
22//! let key = AggregateMacKey::generate();
23//!
24//! // Authenticate multiple messages
25//! let messages = vec![b"chunk1".as_slice(), b"chunk2".as_slice(), b"chunk3".as_slice()];
26//! let builder = AggregateMacBuilder::new(&key);
27//! let aggregate_tag = builder.authenticate_batch(&messages);
28//!
29//! // Verify all messages at once
30//! let valid = key.verify_batch(&messages, &aggregate_tag);
31//! assert!(valid);
32//! ```
33
34use blake3::Hasher;
35use rand::RngCore;
36use serde::{Deserialize, Serialize};
37use std::fmt;
38use zeroize::{Zeroize, ZeroizeOnDrop};
39
40/// Result type for aggregate MAC operations.
41pub type AggregateMacResult<T> = Result<T, AggregateMacError>;
42
43/// Errors that can occur during aggregate MAC operations.
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum AggregateMacError {
46    /// Empty message list
47    EmptyMessages,
48    /// Invalid tag
49    InvalidTag,
50    /// Serialization failed
51    SerializationFailed,
52    /// Deserialization failed
53    DeserializationFailed,
54}
55
56impl fmt::Display for AggregateMacError {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        match self {
59            AggregateMacError::EmptyMessages => write!(f, "Empty message list"),
60            AggregateMacError::InvalidTag => write!(f, "Invalid tag"),
61            AggregateMacError::SerializationFailed => write!(f, "Serialization failed"),
62            AggregateMacError::DeserializationFailed => write!(f, "Deserialization failed"),
63        }
64    }
65}
66
67impl std::error::Error for AggregateMacError {}
68
69/// Aggregate MAC key.
70///
71/// Used to authenticate multiple messages and verify aggregate tags.
72#[derive(Clone, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
73pub struct AggregateMacKey {
74    key: [u8; 32],
75}
76
77/// Aggregate MAC tag.
78///
79/// Constant-size tag that authenticates multiple messages.
80#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
81pub struct AggregateTag {
82    tag: [u8; 32],
83    count: usize,
84}
85
86/// Builder for creating aggregate MACs.
87pub struct AggregateMacBuilder<'a> {
88    key: &'a AggregateMacKey,
89}
90
91/// Individual MAC tag for a single message.
92#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
93pub struct MacTag {
94    tag: [u8; 32],
95}
96
97impl AggregateMacKey {
98    /// Generate a new random key.
99    pub fn generate() -> Self {
100        let mut key = [0u8; 32];
101        rand::thread_rng().fill_bytes(&mut key);
102        Self { key }
103    }
104
105    /// Create a key from raw bytes.
106    pub fn from_bytes(bytes: [u8; 32]) -> Self {
107        Self { key: bytes }
108    }
109
110    /// Get the raw key bytes.
111    pub fn to_bytes(&self) -> [u8; 32] {
112        self.key
113    }
114
115    /// Authenticate a single message.
116    pub fn authenticate(&self, message: &[u8]) -> MacTag {
117        self.authenticate_with_index(message, 0)
118    }
119
120    /// Authenticate a message with an index for domain separation.
121    pub fn authenticate_with_index(&self, message: &[u8], index: usize) -> MacTag {
122        let mut hasher = Hasher::new_keyed(&self.key);
123        hasher.update(b"AggregateMac-v1:");
124        hasher.update(&index.to_le_bytes());
125        hasher.update(b":");
126        hasher.update(message);
127
128        let hash = hasher.finalize();
129        let mut tag = [0u8; 32];
130        tag.copy_from_slice(hash.as_bytes());
131
132        MacTag { tag }
133    }
134
135    /// Authenticate multiple messages and return an aggregate tag.
136    pub fn authenticate_batch(&self, messages: &[&[u8]]) -> AggregateMacResult<AggregateTag> {
137        if messages.is_empty() {
138            return Err(AggregateMacError::EmptyMessages);
139        }
140
141        let mut aggregate_tag = [0u8; 32];
142
143        for (index, message) in messages.iter().enumerate() {
144            let mac_tag = self.authenticate_with_index(message, index);
145            // XOR aggregation
146            for (i, byte) in aggregate_tag.iter_mut().enumerate() {
147                *byte ^= mac_tag.tag[i];
148            }
149        }
150
151        Ok(AggregateTag {
152            tag: aggregate_tag,
153            count: messages.len(),
154        })
155    }
156
157    /// Verify a single message against a MAC tag.
158    pub fn verify(&self, message: &[u8], tag: &MacTag) -> bool {
159        let expected = self.authenticate(message);
160        constant_time_eq(&expected.tag, &tag.tag)
161    }
162
163    /// Verify a message with an index against a MAC tag.
164    pub fn verify_with_index(&self, message: &[u8], index: usize, tag: &MacTag) -> bool {
165        let expected = self.authenticate_with_index(message, index);
166        constant_time_eq(&expected.tag, &tag.tag)
167    }
168
169    /// Verify multiple messages against an aggregate tag.
170    pub fn verify_batch(&self, messages: &[&[u8]], aggregate_tag: &AggregateTag) -> bool {
171        if messages.is_empty() || messages.len() != aggregate_tag.count {
172            return false;
173        }
174
175        let expected = match self.authenticate_batch(messages) {
176            Ok(tag) => tag,
177            Err(_) => return false,
178        };
179
180        constant_time_eq(&expected.tag, &aggregate_tag.tag)
181    }
182
183    /// Create a builder for aggregate MAC operations.
184    pub fn builder(&self) -> AggregateMacBuilder<'_> {
185        AggregateMacBuilder { key: self }
186    }
187}
188
189impl<'a> AggregateMacBuilder<'a> {
190    /// Create a new builder.
191    pub fn new(key: &'a AggregateMacKey) -> Self {
192        Self { key }
193    }
194
195    /// Authenticate multiple messages.
196    pub fn authenticate_batch(&self, messages: &[&[u8]]) -> AggregateTag {
197        self.key
198            .authenticate_batch(messages)
199            .expect("messages should not be empty")
200    }
201
202    /// Authenticate messages from an iterator.
203    pub fn authenticate_iter<I>(&self, messages: I) -> AggregateMacResult<AggregateTag>
204    where
205        I: IntoIterator<Item = Vec<u8>>,
206    {
207        let messages: Vec<Vec<u8>> = messages.into_iter().collect();
208        let message_refs: Vec<&[u8]> = messages.iter().map(|m| m.as_slice()).collect();
209        self.key.authenticate_batch(&message_refs)
210    }
211
212    /// Verify a batch of messages.
213    pub fn verify_batch(&self, messages: &[&[u8]], tag: &AggregateTag) -> bool {
214        self.key.verify_batch(messages, tag)
215    }
216}
217
218impl AggregateTag {
219    /// Get the number of messages authenticated by this tag.
220    pub fn count(&self) -> usize {
221        self.count
222    }
223
224    /// Serialize the tag to bytes.
225    pub fn to_bytes(&self) -> Vec<u8> {
226        crate::codec::encode(self).unwrap_or_default()
227    }
228
229    /// Deserialize a tag from bytes.
230    pub fn from_bytes(bytes: &[u8]) -> AggregateMacResult<Self> {
231        crate::codec::decode(bytes).map_err(|_| AggregateMacError::DeserializationFailed)
232    }
233}
234
235impl MacTag {
236    /// Serialize the tag to bytes.
237    pub fn to_bytes(&self) -> [u8; 32] {
238        self.tag
239    }
240
241    /// Deserialize a tag from bytes.
242    pub fn from_bytes(bytes: [u8; 32]) -> Self {
243        Self { tag: bytes }
244    }
245}
246
247/// Constant-time equality comparison.
248fn constant_time_eq(a: &[u8; 32], b: &[u8; 32]) -> bool {
249    use subtle::ConstantTimeEq;
250    a.ct_eq(b).into()
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    #[test]
258    fn test_single_message_authentication() {
259        let key = AggregateMacKey::generate();
260        let message = b"test message";
261
262        let tag = key.authenticate(message);
263        assert!(key.verify(message, &tag));
264    }
265
266    #[test]
267    fn test_single_message_wrong_key() {
268        let key1 = AggregateMacKey::generate();
269        let key2 = AggregateMacKey::generate();
270
271        let message = b"test message";
272        let tag = key1.authenticate(message);
273
274        assert!(!key2.verify(message, &tag));
275    }
276
277    #[test]
278    fn test_single_message_tampered() {
279        let key = AggregateMacKey::generate();
280        let message = b"test message";
281
282        let tag = key.authenticate(message);
283
284        let tampered = b"tampered message";
285        assert!(!key.verify(tampered, &tag));
286    }
287
288    #[test]
289    fn test_batch_authentication() {
290        let key = AggregateMacKey::generate();
291        let messages = vec![b"msg1".as_slice(), b"msg2".as_slice(), b"msg3".as_slice()];
292
293        let tag = key.authenticate_batch(&messages).unwrap();
294        assert!(key.verify_batch(&messages, &tag));
295    }
296
297    #[test]
298    fn test_batch_wrong_order() {
299        let key = AggregateMacKey::generate();
300        let messages = vec![b"msg1".as_slice(), b"msg2".as_slice(), b"msg3".as_slice()];
301
302        let tag = key.authenticate_batch(&messages).unwrap();
303
304        // Different order should fail
305        let reordered = vec![b"msg2".as_slice(), b"msg1".as_slice(), b"msg3".as_slice()];
306        assert!(!key.verify_batch(&reordered, &tag));
307    }
308
309    #[test]
310    fn test_batch_tampered_message() {
311        let key = AggregateMacKey::generate();
312        let messages = vec![b"msg1".as_slice(), b"msg2".as_slice(), b"msg3".as_slice()];
313
314        let tag = key.authenticate_batch(&messages).unwrap();
315
316        let tampered = vec![
317            b"msg1".as_slice(),
318            b"TAMPERED".as_slice(),
319            b"msg3".as_slice(),
320        ];
321        assert!(!key.verify_batch(&tampered, &tag));
322    }
323
324    #[test]
325    fn test_batch_wrong_count() {
326        let key = AggregateMacKey::generate();
327        let messages = vec![b"msg1".as_slice(), b"msg2".as_slice(), b"msg3".as_slice()];
328
329        let tag = key.authenticate_batch(&messages).unwrap();
330
331        // Different number of messages should fail
332        let fewer = vec![b"msg1".as_slice(), b"msg2".as_slice()];
333        assert!(!key.verify_batch(&fewer, &tag));
334
335        let more = vec![
336            b"msg1".as_slice(),
337            b"msg2".as_slice(),
338            b"msg3".as_slice(),
339            b"msg4".as_slice(),
340        ];
341        assert!(!key.verify_batch(&more, &tag));
342    }
343
344    #[test]
345    fn test_batch_empty() {
346        let key = AggregateMacKey::generate();
347        let messages: Vec<&[u8]> = vec![];
348
349        assert!(key.authenticate_batch(&messages).is_err());
350    }
351
352    #[test]
353    fn test_indexed_authentication() {
354        let key = AggregateMacKey::generate();
355        let message = b"test message";
356
357        let tag0 = key.authenticate_with_index(message, 0);
358        let tag1 = key.authenticate_with_index(message, 1);
359
360        // Different indices should produce different tags
361        assert_ne!(tag0, tag1);
362
363        assert!(key.verify_with_index(message, 0, &tag0));
364        assert!(key.verify_with_index(message, 1, &tag1));
365
366        // Wrong index should fail
367        assert!(!key.verify_with_index(message, 0, &tag1));
368        assert!(!key.verify_with_index(message, 1, &tag0));
369    }
370
371    #[test]
372    fn test_builder_pattern() {
373        let key = AggregateMacKey::generate();
374        let messages = vec![b"msg1".as_slice(), b"msg2".as_slice()];
375
376        let builder = AggregateMacBuilder::new(&key);
377        let tag = builder.authenticate_batch(&messages);
378
379        assert!(builder.verify_batch(&messages, &tag));
380    }
381
382    #[test]
383    fn test_aggregate_tag_count() {
384        let key = AggregateMacKey::generate();
385        let messages = vec![b"msg1".as_slice(), b"msg2".as_slice(), b"msg3".as_slice()];
386
387        let tag = key.authenticate_batch(&messages).unwrap();
388        assert_eq!(tag.count(), 3);
389    }
390
391    #[test]
392    fn test_tag_serialization() {
393        let key = AggregateMacKey::generate();
394        let message = b"test message";
395
396        let tag = key.authenticate(message);
397        let bytes = tag.to_bytes();
398        let deserialized = MacTag::from_bytes(bytes);
399
400        assert_eq!(tag, deserialized);
401        assert!(key.verify(message, &deserialized));
402    }
403
404    #[test]
405    fn test_aggregate_tag_serialization() {
406        let key = AggregateMacKey::generate();
407        let messages = vec![b"msg1".as_slice(), b"msg2".as_slice()];
408
409        let tag = key.authenticate_batch(&messages).unwrap();
410        let bytes = tag.to_bytes();
411        let deserialized = AggregateTag::from_bytes(&bytes).unwrap();
412
413        assert_eq!(tag, deserialized);
414        assert!(key.verify_batch(&messages, &deserialized));
415    }
416
417    #[test]
418    fn test_key_serialization() {
419        let key = AggregateMacKey::generate();
420        let message = b"test message";
421
422        let bytes = key.to_bytes();
423        let deserialized = AggregateMacKey::from_bytes(bytes);
424
425        let tag = key.authenticate(message);
426        assert!(deserialized.verify(message, &tag));
427    }
428
429    #[test]
430    fn test_large_batch() {
431        let key = AggregateMacKey::generate();
432        let messages: Vec<Vec<u8>> = (0..100)
433            .map(|i| format!("message{}", i).into_bytes())
434            .collect();
435        let message_refs: Vec<&[u8]> = messages.iter().map(|m| m.as_slice()).collect();
436
437        let tag = key.authenticate_batch(&message_refs).unwrap();
438        assert!(key.verify_batch(&message_refs, &tag));
439        assert_eq!(tag.count(), 100);
440    }
441
442    #[test]
443    fn test_deterministic_tags() {
444        let key = AggregateMacKey::from_bytes([0x42; 32]);
445        let message = b"test message";
446
447        let tag1 = key.authenticate(message);
448        let tag2 = key.authenticate(message);
449
450        assert_eq!(tag1, tag2);
451    }
452
453    #[test]
454    fn test_different_message_sizes() {
455        let key = AggregateMacKey::generate();
456        let large_msg = vec![0x42u8; 1000];
457        let messages = vec![
458            b"short".as_slice(),
459            b"medium length message".as_slice(),
460            large_msg.as_slice(),
461        ];
462
463        let tag = key.authenticate_batch(&messages).unwrap();
464        assert!(key.verify_batch(&messages, &tag));
465    }
466
467    #[test]
468    fn test_xor_properties() {
469        let key = AggregateMacKey::generate();
470
471        // Single message
472        let msg1 = vec![b"msg1".as_slice()];
473        let tag1 = key.authenticate_batch(&msg1).unwrap();
474
475        // Same message twice should XOR to zero then XOR with itself
476        let msg2 = vec![b"msg1".as_slice(), b"msg1".as_slice()];
477        let tag2 = key.authenticate_batch(&msg2).unwrap();
478
479        // Tags should be different
480        assert_ne!(tag1.tag, tag2.tag);
481    }
482}