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 sha3::{Digest, Keccak256};
18
19/// [`Ethereum MAC`](https://github.com/ethereum/devp2p/blob/master/rlpx.md#mac) state.
20///
21/// The ethereum MAC is a cursed MAC construction.
22///
23/// The ethereum MAC is a nonstandard MAC construction that uses AES-256 (without a mode, as a
24/// block cipher) and Keccak-256. However, it only ever encrypts messages that are 128 bits long,
25/// and is not defined as a general MAC.
26#[derive(Debug)]
27pub struct MAC {
28 secret: B256,
29 hasher: Keccak256,
30}
31
32impl MAC {
33 /// Initialize the MAC with the given secret
34 pub fn new(secret: B256) -> Self {
35 Self { secret, hasher: Keccak256::new() }
36 }
37
38 /// Update the internal keccak256 hasher with the given data
39 pub fn update(&mut self, data: &[u8]) {
40 self.hasher.update(data)
41 }
42
43 /// Accumulate the given header bytes into the MAC's internal state.
44 pub fn update_header(&mut self, data: &[u8; 16]) {
45 let aes = Aes256Enc::new_from_slice(self.secret.as_ref()).unwrap();
46 let mut encrypted = self.digest().0;
47
48 aes.encrypt_padded::<NoPadding>(&mut encrypted, B128::len_bytes()).unwrap();
49 for i in 0..data.len() {
50 encrypted[i] ^= data[i];
51 }
52 self.hasher.update(encrypted);
53 }
54
55 /// Accumulate the given message body into the MAC's internal state.
56 pub fn update_body(&mut self, data: &[u8]) {
57 self.hasher.update(data);
58 let prev = self.digest();
59 let aes = Aes256Enc::new_from_slice(self.secret.as_ref()).unwrap();
60 let mut encrypted = self.digest().0;
61
62 aes.encrypt_padded::<NoPadding>(&mut encrypted, B128::len_bytes()).unwrap();
63 for i in 0..16 {
64 encrypted[i] ^= prev[i];
65 }
66 self.hasher.update(encrypted);
67 }
68
69 /// Produce a digest by finalizing the internal keccak256 hasher and returning the first 128
70 /// bits.
71 pub fn digest(&self) -> B128 {
72 B128::from_slice(&self.hasher.clone().finalize()[..16])
73 }
74}