reth_ecies/
mac.rs

1//! # Ethereum MAC Module
2//!
3//! This module provides the implementation of the Ethereum MAC (Message Authentication Code)
4//! construction, as specified in the Ethereum `RLPx` protocol.
5//!
6//! The Ethereum MAC is a nonstandard MAC construction that utilizes AES-256 (as a block cipher)
7//! and Keccak-256. It is specifically designed for messages of 128 bits in length and is not
8//! intended for general MAC use.
9//!
10//! For more information, refer to the [Ethereum MAC specification](https://github.com/ethereum/devp2p/blob/master/rlpx.md#mac).
11
12use aes::Aes256Enc;
13use alloy_primitives::{B128, B256};
14use block_padding::NoPadding;
15use cipher::BlockEncrypt;
16use digest::KeyInit;
17use generic_array::GenericArray;
18use sha3::{Digest, Keccak256};
19use typenum::U16;
20
21/// Type alias for a fixed-size array of 16 bytes used as headers.
22///
23/// This type is defined as [`GenericArray<u8, U16>`] and is commonly employed in Ethereum `RLPx`
24/// protocol-related structures for headers. It represents 16 bytes of data used in various
25/// cryptographic operations, such as MAC (Message Authentication Code) computation.
26pub type HeaderBytes = GenericArray<u8, U16>;
27
28/// [`Ethereum MAC`](https://github.com/ethereum/devp2p/blob/master/rlpx.md#mac) state.
29///
30/// The ethereum MAC is a cursed MAC construction.
31///
32/// The ethereum MAC is a nonstandard MAC construction that uses AES-256 (without a mode, as a
33/// block cipher) and Keccak-256. However, it only ever encrypts messages that are 128 bits long,
34/// and is not defined as a general MAC.
35#[derive(Debug)]
36pub struct MAC {
37    secret: B256,
38    hasher: Keccak256,
39}
40
41impl MAC {
42    /// Initialize the MAC with the given secret
43    pub fn new(secret: B256) -> Self {
44        Self { secret, hasher: Keccak256::new() }
45    }
46
47    /// Update the internal keccak256 hasher with the given data
48    pub fn update(&mut self, data: &[u8]) {
49        self.hasher.update(data)
50    }
51
52    /// Accumulate the given [`HeaderBytes`] into the MAC's internal state.
53    pub fn update_header(&mut self, data: &HeaderBytes) {
54        let aes = Aes256Enc::new_from_slice(self.secret.as_ref()).unwrap();
55        let mut encrypted = self.digest().0;
56
57        aes.encrypt_padded::<NoPadding>(&mut encrypted, B128::len_bytes()).unwrap();
58        for i in 0..data.len() {
59            encrypted[i] ^= data[i];
60        }
61        self.hasher.update(encrypted);
62    }
63
64    /// Accumulate the given message body into the MAC's internal state.
65    pub fn update_body(&mut self, data: &[u8]) {
66        self.hasher.update(data);
67        let prev = self.digest();
68        let aes = Aes256Enc::new_from_slice(self.secret.as_ref()).unwrap();
69        let mut encrypted = self.digest().0;
70
71        aes.encrypt_padded::<NoPadding>(&mut encrypted, B128::len_bytes()).unwrap();
72        for i in 0..16 {
73            encrypted[i] ^= prev[i];
74        }
75        self.hasher.update(encrypted);
76    }
77
78    /// Produce a digest by finalizing the internal keccak256 hasher and returning the first 128
79    /// bits.
80    pub fn digest(&self) -> B128 {
81        B128::from_slice(&self.hasher.clone().finalize()[..16])
82    }
83}