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