Skip to main content

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