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
6// Re-export serialization traits from miden-serde-utils
7#[cfg(feature = "std")]
8pub use miden_serde_utils::ReadAdapter;
9pub use miden_serde_utils::{
10    BudgetedReader, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
11    SliceReader,
12};
13use p3_field::{PrimeCharacteristicRing, RawDataSerializable, integers::QuotientMap};
14use p3_maybe_rayon::prelude::*;
15use thiserror::Error;
16
17use crate::{Felt, Word, field::PrimeField64};
18
19// CONSTANTS
20// ================================================================================================
21
22/// The number of byte chunks that can be safely embedded in a field element
23const BINARY_CHUNK_SIZE: usize = 7;
24
25// RE-EXPORTS
26// ================================================================================================
27
28pub use k256::elliptic_curve::zeroize;
29
30// UTILITY FUNCTIONS
31// ================================================================================================
32
33/// Converts a [Word] into hex.
34pub fn word_to_hex(w: &Word) -> Result<String, fmt::Error> {
35    let mut s = String::new();
36
37    for byte in w.iter().flat_map(|&e| e.to_bytes()) {
38        write!(s, "{byte:02x}")?;
39    }
40
41    Ok(s)
42}
43
44/// Renders an array of bytes as hex into a String.
45pub fn bytes_to_hex_string<const N: usize>(data: [u8; N]) -> String {
46    let mut s = String::with_capacity(N + 2);
47
48    s.push_str("0x");
49    for byte in data.iter() {
50        write!(s, "{byte:02x}").expect("formatting hex failed");
51    }
52
53    s
54}
55
56/// Defines errors which can occur during parsing of hexadecimal strings.
57#[derive(Debug, Error)]
58pub enum HexParseError {
59    #[error("expected hex data to have length {expected}, including the 0x prefix, found {actual}")]
60    InvalidLength { expected: usize, actual: usize },
61    #[error("hex encoded data must start with 0x prefix")]
62    MissingPrefix,
63    #[error("hex encoded data must contain only characters [0-9a-fA-F]")]
64    InvalidChar,
65    #[error("hex encoded values of a Digest must be inside the field modulus")]
66    OutOfRange,
67}
68
69/// Parses a hex string into an array of bytes of known size.
70pub fn hex_to_bytes<const N: usize>(value: &str) -> Result<[u8; N], HexParseError> {
71    let expected: usize = (N * 2) + 2;
72    if value.len() != expected {
73        return Err(HexParseError::InvalidLength { expected, actual: value.len() });
74    }
75
76    if !value.starts_with("0x") {
77        return Err(HexParseError::MissingPrefix);
78    }
79
80    let mut data = value.bytes().skip(2).map(|v| match v {
81        b'0'..=b'9' => Ok(v - b'0'),
82        b'a'..=b'f' => Ok(v - b'a' + 10),
83        b'A'..=b'F' => Ok(v - b'A' + 10),
84        _ => Err(HexParseError::InvalidChar),
85    });
86
87    let mut decoded = [0u8; N];
88    for byte in decoded.iter_mut() {
89        // These `unwrap` calls are okay because the length was checked above
90        let high: u8 = data.next().unwrap()?;
91        let low: u8 = data.next().unwrap()?;
92        *byte = (high << 4) + low;
93    }
94
95    Ok(decoded)
96}
97
98// CONVERSIONS BETWEEN BYTES AND ELEMENTS
99// ================================================================================================
100
101/// Converts a sequence of bytes into vector field elements with padding. This guarantees that no
102/// two sequences or bytes map to the same sequence of field elements.
103///
104/// Packs bytes into chunks of `BINARY_CHUNK_SIZE` and adds padding to the final chunk using a `1`
105/// bit followed by zeros. This ensures the original bytes can be recovered during decoding without
106/// any ambiguity.
107///
108/// Note that by the endianness of the conversion as well as the fact that we are packing at most
109/// `56 = 7 * 8` bits in each field element, the padding above with `1` should never overflow the
110/// field size.
111///
112/// # Arguments
113/// * `bytes` - Byte slice to encode
114///
115/// # Returns
116/// Vector of `Felt` elements with the last element containing padding
117pub fn bytes_to_elements_with_padding(bytes: &[u8]) -> Vec<Felt> {
118    if bytes.is_empty() {
119        return vec![];
120    }
121
122    // determine the number of field elements needed to encode `bytes` when each field element
123    // represents at most 7 bytes.
124    let num_field_elem = bytes.len().div_ceil(BINARY_CHUNK_SIZE);
125
126    // initialize a buffer to receive the little-endian elements.
127    let mut buf = [0_u8; 8];
128
129    // iterate the chunks of bytes, creating a field element from each chunk
130    let last_chunk_idx = num_field_elem - 1;
131
132    bytes
133        .chunks(BINARY_CHUNK_SIZE)
134        .enumerate()
135        .map(|(current_chunk_idx, chunk)| {
136            // copy the chunk into the buffer
137            if current_chunk_idx != last_chunk_idx {
138                buf[..BINARY_CHUNK_SIZE].copy_from_slice(chunk);
139            } else {
140                // on the last iteration, we pad `buf` with a 1 followed by as many 0's as are
141                // needed to fill it
142                buf.fill(0);
143                buf[..chunk.len()].copy_from_slice(chunk);
144                buf[chunk.len()] = 1;
145            }
146
147            Felt::new(u64::from_le_bytes(buf))
148        })
149        .collect()
150}
151
152/// Converts a sequence of padded field elements back to the original bytes.
153///
154/// Reconstructs the original byte sequence by removing the padding added by `bytes_to_felts`.
155/// The padding consists of a `1` bit followed by zeros in the final field element.
156///
157/// Note that by the endianness of the conversion as well as the fact that we are packing at most
158/// `56 = 7 * 8` bits in each field element, the padding above with `1` should never overflow the
159/// field size.
160///
161/// # Arguments
162/// * `felts` - Slice of field elements with padding in the last element
163///
164/// # Returns
165/// * `Some(Vec<u8>)` - The original byte sequence with padding removed
166/// * `None` - If no padding marker (`1` bit) is found
167pub fn padded_elements_to_bytes(felts: &[Felt]) -> Option<Vec<u8>> {
168    let number_felts = felts.len();
169    if number_felts == 0 {
170        return Some(vec![]);
171    }
172
173    let mut result = Vec::with_capacity(number_felts * BINARY_CHUNK_SIZE);
174    for felt in felts.iter().take(number_felts - 1) {
175        let felt_bytes = felt.as_canonical_u64().to_le_bytes();
176        result.extend_from_slice(&felt_bytes[..BINARY_CHUNK_SIZE]);
177    }
178
179    // handle the last field element
180    let felt_bytes = felts[number_felts - 1].as_canonical_u64().to_le_bytes();
181    let pos = felt_bytes.iter().rposition(|entry| *entry == 1_u8)?;
182
183    result.extend_from_slice(&felt_bytes[..pos]);
184    Some(result)
185}
186
187/// Converts field elements to raw byte representation.
188///
189/// Each `Felt` is converted to its full `NUM_BYTES` representation, in little-endian form
190/// and canonical form, without any padding removal or validation. This is the inverse
191/// of `bytes_to_elements_exact`.
192///
193/// # Arguments
194/// * `felts` - Slice of field elements to convert
195///
196/// # Returns
197/// Vector containing the raw bytes from all field elements
198pub fn elements_to_bytes(felts: &[Felt]) -> Vec<u8> {
199    let number_felts = felts.len();
200    let mut result = Vec::with_capacity(number_felts * Felt::NUM_BYTES);
201    for felt in felts.iter().take(number_felts) {
202        let felt_bytes = felt.as_canonical_u64().to_le_bytes();
203        result.extend_from_slice(&felt_bytes);
204    }
205
206    result
207}
208
209/// Converts bytes to field elements with validation.
210///
211/// This function validates that:
212/// - The input bytes length is divisible by `Felt::NUM_BYTES`
213/// - All `Felt::NUM_BYTES`-byte sequences represent valid field elements
214///
215/// # Arguments
216/// * `bytes` - Byte slice that must be a multiple of `Felt::NUM_BYTES` in length
217///
218/// # Returns
219/// `Option<Vec<Felt>>` - Vector of `Felt` elements if all validations pass, or None otherwise
220pub fn bytes_to_elements_exact(bytes: &[u8]) -> Option<Vec<Felt>> {
221    // Check that the length is divisible by NUM_BYTES
222    if !bytes.len().is_multiple_of(Felt::NUM_BYTES) {
223        return None;
224    }
225
226    let mut result = Vec::with_capacity(bytes.len() / Felt::NUM_BYTES);
227
228    for chunk in bytes.chunks_exact(Felt::NUM_BYTES) {
229        let chunk_array: [u8; Felt::NUM_BYTES] =
230            chunk.try_into().expect("should succeed given the length check above");
231
232        let value = u64::from_le_bytes(chunk_array);
233
234        // Validate that the value represents a valid field element
235        let felt = Felt::from_canonical_checked(value)?;
236        result.push(felt);
237    }
238
239    Some(result)
240}
241
242/// Converts bytes to field elements using u32 packing in little-endian format.
243///
244/// Each field element contains a u32 value representing up to 4 bytes. If the byte length
245/// is not a multiple of 4, the final field element is zero-padded.
246///
247/// # Arguments
248/// - `bytes`: The byte slice to convert
249///
250/// # Returns
251/// A vector of field elements, each containing 4 bytes packed in little-endian order.
252///
253/// # Examples
254/// ```rust
255/// # use miden_crypto::{Felt, utils::bytes_to_packed_u32_elements};
256///
257/// let bytes = vec![0x01, 0x02, 0x03, 0x04, 0x05];
258/// let felts = bytes_to_packed_u32_elements(&bytes);
259/// assert_eq!(felts, vec![Felt::new(0x04030201), Felt::new(0x00000005)]);
260/// ```
261pub fn bytes_to_packed_u32_elements(bytes: &[u8]) -> Vec<Felt> {
262    const BYTES_PER_U32: usize = core::mem::size_of::<u32>();
263
264    bytes
265        .chunks(BYTES_PER_U32)
266        .map(|chunk| {
267            // Pack up to 4 bytes into a u32 in little-endian format
268            let mut packed = [0u8; BYTES_PER_U32];
269            packed[..chunk.len()].copy_from_slice(chunk);
270            Felt::from_u32(u32::from_le_bytes(packed))
271        })
272        .collect()
273}
274
275// VECTOR FUNCTIONS (ported from Winterfell's winter-utils)
276// ================================================================================================
277
278/// Returns a vector of the specified length with un-initialized memory.
279///
280/// This is usually faster than requesting a vector with initialized memory and is useful when we
281/// overwrite all contents of the vector immediately after memory allocation.
282///
283/// # Safety
284/// Using values from the returned vector before initializing them will lead to undefined behavior.
285#[expect(clippy::uninit_vec)]
286pub unsafe fn uninit_vector<T>(length: usize) -> Vec<T> {
287    let mut vector = Vec::with_capacity(length);
288    unsafe {
289        vector.set_len(length);
290    }
291    vector
292}
293
294// GROUPING / UN-GROUPING FUNCTIONS (ported from Winterfell's winter-utils)
295// ================================================================================================
296
297/// Transmutes a slice of `n` elements into a slice of `n` / `N` elements, each of which is
298/// an array of `N` elements.
299///
300/// This function just re-interprets the underlying memory and is thus zero-copy.
301/// # Panics
302/// Panics if `n` is not divisible by `N`.
303pub fn group_slice_elements<T, const N: usize>(source: &[T]) -> &[[T; N]] {
304    let (chunks, remainder) = source.as_chunks::<N>();
305    assert!(remainder.is_empty(), "source length must be divisible by {N}");
306    chunks
307}
308
309/// Transmutes a slice of `n` arrays each of length `N`, into a slice of `N` * `n` elements.
310///
311/// This function just re-interprets the underlying memory and is thus zero-copy.
312pub fn flatten_slice_elements<T, const N: usize>(source: &[[T; N]]) -> &[T] {
313    // SAFETY: [T; N] has the same alignment and memory layout as an array of T.
314    // p3-util's as_base_slice handles the conversion safely.
315    unsafe { p3_util::as_base_slice(source) }
316}
317
318/// Transmutes a vector of `n` arrays each of length `N`, into a vector of `N` * `n` elements.
319///
320/// This function just re-interprets the underlying memory and is thus zero-copy.
321pub fn flatten_vector_elements<T, const N: usize>(source: Vec<[T; N]>) -> Vec<T> {
322    // SAFETY: [T; N] has the same alignment and memory layout as an array of T.
323    // p3-util's flatten_to_base handles the conversion without reallocations.
324    unsafe { p3_util::flatten_to_base(source) }
325}
326
327// TRANSPOSING (ported from Winterfell's winter-utils)
328// ================================================================================================
329
330/// Transposes a slice of `n` elements into a matrix with `N` columns and `n`/`N` rows.
331///
332/// When `concurrent` feature is enabled, the slice will be transposed using multiple threads.
333///
334/// # Panics
335/// Panics if `n` is not divisible by `N`.
336pub fn transpose_slice<T: Copy + Send + Sync, const N: usize>(source: &[T]) -> Vec<[T; N]> {
337    let row_count = source.len() / N;
338    assert_eq!(
339        row_count * N,
340        source.len(),
341        "source length must be divisible by {}, but was {}",
342        N,
343        source.len()
344    );
345
346    let mut result: Vec<[T; N]> = unsafe { uninit_vector(row_count) };
347    result.par_iter_mut().enumerate().for_each(|(i, element)| {
348        for j in 0..N {
349            element[j] = source[i + j * row_count]
350        }
351    });
352    result
353}