miden_crypto/utils/
mod.rs

1//! Utilities used in this crate which can also be generally useful downstream.
2
3use alloc::{string::String, vec::Vec};
4use core::fmt::{self, Write};
5
6use thiserror::Error;
7#[cfg(feature = "std")]
8pub use winter_utils::ReadAdapter;
9pub use winter_utils::{
10    ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SliceReader,
11    uninit_vector,
12};
13
14use crate::{Felt, FieldElement, StarkField, Word};
15
16// CONSTANTS
17// ================================================================================================
18
19/// The number of byte chunks that can be safely embedded in a field element
20const BINARY_CHUNK_SIZE: usize = 7;
21
22// UTILITY FUNCTIONS
23// ================================================================================================
24
25/// Converts a [Word] into hex.
26pub fn word_to_hex(w: &Word) -> Result<String, fmt::Error> {
27    let mut s = String::new();
28
29    for byte in w.iter().flat_map(|e| e.to_bytes()) {
30        write!(s, "{byte:02x}")?;
31    }
32
33    Ok(s)
34}
35
36/// Renders an array of bytes as hex into a String.
37pub fn bytes_to_hex_string<const N: usize>(data: [u8; N]) -> String {
38    let mut s = String::with_capacity(N + 2);
39
40    s.push_str("0x");
41    for byte in data.iter() {
42        write!(s, "{byte:02x}").expect("formatting hex failed");
43    }
44
45    s
46}
47
48/// Defines errors which can occur during parsing of hexadecimal strings.
49#[derive(Debug, Error)]
50pub enum HexParseError {
51    #[error("expected hex data to have length {expected}, including the 0x prefix, found {actual}")]
52    InvalidLength { expected: usize, actual: usize },
53    #[error("hex encoded data must start with 0x prefix")]
54    MissingPrefix,
55    #[error("hex encoded data must contain only characters [0-9a-fA-F]")]
56    InvalidChar,
57    #[error("hex encoded values of a Digest must be inside the field modulus")]
58    OutOfRange,
59}
60
61/// Parses a hex string into an array of bytes of known size.
62pub fn hex_to_bytes<const N: usize>(value: &str) -> Result<[u8; N], HexParseError> {
63    let expected: usize = (N * 2) + 2;
64    if value.len() != expected {
65        return Err(HexParseError::InvalidLength { expected, actual: value.len() });
66    }
67
68    if !value.starts_with("0x") {
69        return Err(HexParseError::MissingPrefix);
70    }
71
72    let mut data = value.bytes().skip(2).map(|v| match v {
73        b'0'..=b'9' => Ok(v - b'0'),
74        b'a'..=b'f' => Ok(v - b'a' + 10),
75        b'A'..=b'F' => Ok(v - b'A' + 10),
76        _ => Err(HexParseError::InvalidChar),
77    });
78
79    let mut decoded = [0u8; N];
80    for byte in decoded.iter_mut() {
81        // These `unwrap` calls are okay because the length was checked above
82        let high: u8 = data.next().unwrap()?;
83        let low: u8 = data.next().unwrap()?;
84        *byte = (high << 4) + low;
85    }
86
87    Ok(decoded)
88}
89
90// CONVERSIONS BETWEEN BYTES AND ELEMENTS
91// ================================================================================================
92
93/// Converts a sequence of bytes into vector field elements with padding. This guarantees that no
94/// two sequences or bytes map to the same sequence of field elements.
95///
96/// Packs bytes into chunks of `BINARY_CHUNK_SIZE` and adds padding to the final chunk using a `1`
97/// bit followed by zeros. This ensures the original bytes can be recovered during decoding without
98/// any ambiguity.
99///
100/// Note that by the endianness of the conversion as well as the fact that we are packing at most
101/// `56 = 7 * 8` bits in each field element, the padding above with `1` should never overflow the
102/// field size.
103///
104/// # Arguments
105/// * `bytes` - Byte slice to encode
106///
107/// # Returns
108/// Vector of `Felt` elements with the last element containing padding
109pub fn bytes_to_elements_with_padding(bytes: &[u8]) -> Vec<Felt> {
110    if bytes.is_empty() {
111        return vec![];
112    }
113
114    // determine the number of field elements needed to encode `bytes` when each field element
115    // represents at most 7 bytes.
116    let num_field_elem = bytes.len().div_ceil(BINARY_CHUNK_SIZE);
117
118    // initialize a buffer to receive the little-endian elements.
119    let mut buf = [0_u8; 8];
120
121    // iterate the chunks of bytes, creating a field element from each chunk
122    let last_chunk_idx = num_field_elem - 1;
123
124    bytes
125        .chunks(BINARY_CHUNK_SIZE)
126        .enumerate()
127        .map(|(current_chunk_idx, chunk)| {
128            // copy the chunk into the buffer
129            if current_chunk_idx != last_chunk_idx {
130                buf[..BINARY_CHUNK_SIZE].copy_from_slice(chunk);
131            } else {
132                // on the last iteration, we pad `buf` with a 1 followed by as many 0's as are
133                // needed to fill it
134                buf.fill(0);
135                buf[..chunk.len()].copy_from_slice(chunk);
136                buf[chunk.len()] = 1;
137            }
138
139            Felt::new(u64::from_le_bytes(buf))
140        })
141        .collect()
142}
143
144/// Converts a sequence of padded field elements back to the original bytes.
145///
146/// Reconstructs the original byte sequence by removing the padding added by `bytes_to_felts`.
147/// The padding consists of a `1` bit followed by zeros in the final field element.
148///
149/// Note that by the endianness of the conversion as well as the fact that we are packing at most
150/// `56 = 7 * 8` bits in each field element, the padding above with `1` should never overflow the
151/// field size.
152///
153/// # Arguments
154/// * `felts` - Slice of field elements with padding in the last element
155///
156/// # Returns
157/// * `Some(Vec<u8>)` - The original byte sequence with padding removed
158/// * `None` - If no padding marker (`1` bit) is found
159pub fn padded_elements_to_bytes(felts: &[Felt]) -> Option<Vec<u8>> {
160    let number_felts = felts.len();
161    if number_felts == 0 {
162        return Some(vec![]);
163    }
164
165    let mut result = Vec::with_capacity(number_felts * BINARY_CHUNK_SIZE);
166    for felt in felts.iter().take(number_felts - 1) {
167        let felt_bytes = felt.as_int().to_le_bytes();
168        result.extend_from_slice(&felt_bytes[..BINARY_CHUNK_SIZE]);
169    }
170
171    // handle the last field element
172    let felt_bytes = felts[number_felts - 1].as_int().to_le_bytes();
173    let pos = felt_bytes.iter().rposition(|entry| *entry == 1_u8)?;
174
175    result.extend_from_slice(&felt_bytes[..pos]);
176    Some(result)
177}
178
179/// Converts field elements to raw byte representation.
180///
181/// Each `Felt` is converted to its full `ELEMENT_BYTES` representation, in little-endian form
182/// and canonical form, without any padding removal or validation. This is the inverse
183/// of `bytes_to_elements_exact`.
184///
185/// # Arguments
186/// * `felts` - Slice of field elements to convert
187///
188/// # Returns
189/// Vector containing the raw bytes from all field elements
190pub fn elements_to_bytes(felts: &[Felt]) -> Vec<u8> {
191    let number_felts = felts.len();
192    let mut result = Vec::with_capacity(number_felts * Felt::ELEMENT_BYTES);
193    for felt in felts.iter().take(number_felts) {
194        let felt_bytes = felt.as_int().to_le_bytes();
195        result.extend_from_slice(&felt_bytes);
196    }
197
198    result
199}
200
201/// Converts bytes to field elements with validation.
202///
203/// This function validates that:
204/// - The input bytes length is divisible by `Felt::ELEMENT_BYTES`
205/// - All `Felt::ELEMENT_BYTES`-byte sequences represent valid field elements
206///
207/// # Arguments
208/// * `bytes` - Byte slice that must be a multiple of `Felt::ELEMENT_BYTES` in length
209///
210/// # Returns
211/// `Option<Vec<Felt>>` - Vector of `Felt` elements if all validations pass, or None otherwise
212pub fn bytes_to_elements_exact(bytes: &[u8]) -> Option<Vec<Felt>> {
213    // Check that the length is divisible by ELEMENT_BYTES
214    if !bytes.len().is_multiple_of(Felt::ELEMENT_BYTES) {
215        return None;
216    }
217
218    let mut result = Vec::with_capacity(bytes.len() / Felt::ELEMENT_BYTES);
219
220    for chunk in bytes.chunks_exact(Felt::ELEMENT_BYTES) {
221        let chunk_array: [u8; Felt::ELEMENT_BYTES] =
222            chunk.try_into().expect("should succeed given the length check above");
223
224        let value = u64::from_le_bytes(chunk_array);
225
226        // Validate that the value represents a valid field element
227        if value >= Felt::MODULUS {
228            return None;
229        }
230
231        result.push(Felt::new(value));
232    }
233
234    Some(result)
235}
236
237/// Converts bytes to field elements using u32 packing in little-endian format.
238///
239/// Each field element contains a u32 value representing up to 4 bytes. If the byte length
240/// is not a multiple of 4, the final field element is zero-padded.
241///
242/// # Arguments
243/// - `bytes`: The byte slice to convert
244///
245/// # Returns
246/// A vector of field elements, each containing 4 bytes packed in little-endian order.
247///
248/// # Examples
249/// ```rust
250/// # use miden_crypto::{Felt, utils::bytes_to_packed_u32_elements};
251///
252/// let bytes = vec![0x01, 0x02, 0x03, 0x04, 0x05];
253/// let felts = bytes_to_packed_u32_elements(&bytes);
254/// assert_eq!(felts, vec![Felt::new(0x04030201), Felt::new(0x00000005)]);
255/// ```
256pub fn bytes_to_packed_u32_elements(bytes: &[u8]) -> Vec<Felt> {
257    const BYTES_PER_U32: usize = core::mem::size_of::<u32>();
258
259    bytes
260        .chunks(BYTES_PER_U32)
261        .map(|chunk| {
262            // Pack up to 4 bytes into a u32 in little-endian format
263            let mut packed = [0u8; BYTES_PER_U32];
264            packed[..chunk.len()].copy_from_slice(chunk);
265            Felt::from(u32::from_le_bytes(packed))
266        })
267        .collect()
268}