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