alloy_eips/eip4844/
utils.rs

1//! Utilities for working with EIP-4844 field elements and implementing
2//! [`SidecarCoder`].
3//!
4//! [`SidecarCoder`]: crate::eip4844::builder::SidecarCoder
5
6#[cfg(feature = "kzg")]
7use crate::eip4844::Blob;
8use crate::eip4844::{FIELD_ELEMENT_BYTES_USIZE, USABLE_BITS_PER_FIELD_ELEMENT};
9
10/// Determine whether a slice of bytes can be contained in a field element.
11pub const fn fits_in_fe(data: &[u8]) -> bool {
12    const FIELD_ELEMENT_BYTES_USIZE_PLUS_ONE: usize = FIELD_ELEMENT_BYTES_USIZE + 1;
13
14    match data.len() {
15        FIELD_ELEMENT_BYTES_USIZE_PLUS_ONE.. => false,
16        FIELD_ELEMENT_BYTES_USIZE => data[0] & 0b1100_0000 == 0, // first two bits must be zero
17        _ => true,
18    }
19}
20
21/// Calculate the number of field elements required to store the given
22/// number of bytes.
23pub const fn minimum_fe_for_bytes(bytes: usize) -> usize {
24    (bytes * 8).div_ceil(USABLE_BITS_PER_FIELD_ELEMENT)
25}
26
27/// Calculate the number of field elements required to store the given data.
28pub const fn minimum_fe(data: &[u8]) -> usize {
29    minimum_fe_for_bytes(data.len())
30}
31
32/// Maps a slice of bytes to a blob returning a [`c_kzg::Error`] if the bytes
33/// cannot be mapped. This is a helper for sidecar construction, and mimics the
34/// exact behavior of [`c_kzg::Error`] as of v2.1.1.
35#[cfg(feature = "kzg")]
36pub fn bytes_to_blob<B: AsRef<[u8]>>(blob: B) -> Result<Blob, c_kzg::Error> {
37    let b_ref = blob.as_ref();
38    Blob::try_from(b_ref).map_err(|_| {
39        // mimic c_kzg error
40        c_kzg::Error::InvalidBytesLength(format!(
41            "Invalid byte length. Expected {} got {}",
42            crate::eip4844::BYTES_PER_BLOB,
43            b_ref.len(),
44        ))
45    })
46}
47
48/// Maps a hex string to a blob returning a [`c_kzg::Error`] if the hex
49/// cannot be mapped. This is a helper for sidecar construction, and mimics the
50/// exact behavior of [`c_kzg::Error`] as of v2.1.1.
51#[cfg(feature = "kzg")]
52pub fn hex_to_blob<B: AsRef<str>>(blob: B) -> Result<Blob, c_kzg::Error> {
53    let b_ref = blob.as_ref();
54    alloy_primitives::hex::decode(b_ref)
55        .map_err(|e| c_kzg::Error::InvalidHexFormat(format!("Failed to decode hex: {}", e)))
56        .and_then(bytes_to_blob)
57}
58
59/// A wrapper for a slice of bytes that is a whole, valid field element.
60#[derive(Clone, Copy, Debug)]
61pub struct WholeFe<'a>(&'a [u8]);
62
63impl<'a> WholeFe<'a> {
64    pub(crate) const fn new_unchecked(data: &'a [u8]) -> Self {
65        Self(data)
66    }
67
68    /// Instantiate a new `WholeFe` from a slice of bytes, if it is a valid
69    /// field element.
70    pub const fn new(data: &'a [u8]) -> Option<Self> {
71        if data.len() == FIELD_ELEMENT_BYTES_USIZE && fits_in_fe(data) {
72            Some(Self::new_unchecked(data))
73        } else {
74            None
75        }
76    }
77}
78
79impl AsRef<[u8]> for WholeFe<'_> {
80    fn as_ref(&self) -> &[u8] {
81        self.0
82    }
83}
84
85#[cfg(test)]
86mod test {
87    use crate::eip4844::{FIELD_ELEMENTS_PER_BLOB, USABLE_BYTES_PER_BLOB};
88
89    use super::*;
90    #[test]
91    fn calc_required_fe() {
92        assert_eq!(minimum_fe(&[0u8; 32]), 2);
93        assert_eq!(minimum_fe(&[0u8; 31]), 1);
94        assert_eq!(minimum_fe(&[0u8; 33]), 2);
95        assert_eq!(minimum_fe(&[0u8; 64]), 3);
96        assert_eq!(minimum_fe(&[0u8; 65]), 3);
97        assert_eq!(minimum_fe_for_bytes(USABLE_BYTES_PER_BLOB), FIELD_ELEMENTS_PER_BLOB as usize);
98    }
99
100    #[test]
101    fn calc_is_valid_field_element() {
102        assert!(fits_in_fe(&[0u8; 32]));
103        assert!(!fits_in_fe(&[0u8; 33]));
104
105        assert!(WholeFe::new(&[0u8; 32]).is_some());
106        assert!(WholeFe::new(&[0u8; 33]).is_none());
107        assert!(WholeFe::new(&[0u8; 31]).is_none());
108    }
109}