miden_crypto/utils/mod.rs
1//! Utilities used in this crate which can also be generally useful downstream.
2
3use alloc::{boxed::Box, string::String, vec::Vec};
4use core::{
5 fmt::{self, Write},
6 mem::{ManuallyDrop, MaybeUninit},
7};
8
9// Re-export serialization traits from miden-serde-utils
10#[cfg(feature = "std")]
11pub use miden_serde_utils::ReadAdapter;
12pub use miden_serde_utils::{
13 BudgetedReader, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
14 SliceReader,
15};
16use p3_maybe_rayon::prelude::*;
17
18use crate::{Felt, Word, field::QuotientMap};
19
20// CONSTANTS
21// ================================================================================================
22
23/// Number of data bytes packed into each Felt by the padded byte encoding.
24pub(crate) const BINARY_CHUNK_SIZE: usize = 7;
25
26// RE-EXPORTS
27// ================================================================================================
28
29pub use k256::elliptic_curve::zeroize;
30
31// UTILITY FUNCTIONS
32// ================================================================================================
33
34/// Converts a [Word] into hex.
35pub fn word_to_hex(w: &Word) -> Result<String, fmt::Error> {
36 let mut s = String::new();
37
38 for byte in w.iter().flat_map(|&e| e.to_bytes()) {
39 write!(s, "{byte:02x}")?;
40 }
41
42 Ok(s)
43}
44
45/// Writes `bytes` into the formatter as a `0x`-prefixed, lowercase hexadecimal string.
46///
47/// Useful for `Display` implementations that want compact, canonical hex output (e.g. in `tracing`
48/// logs) without allocating an intermediate `String`. This mirrors the format produced by
49/// [`bytes_to_hex_string`], which only accepts fixed-size arrays.
50pub fn write_hex(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
51 write!(f, "0x")?;
52 for byte in bytes {
53 write!(f, "{byte:02x}")?;
54 }
55 Ok(())
56}
57
58pub use miden_field::utils::{HexParseError, bytes_to_hex_string, hex_to_bytes};
59
60/// Reads a fixed-size byte array from `source`, wrapped in [`Zeroizing`](zeroize::Zeroizing) so
61/// the buffer is wiped on drop regardless of how the calling function exits, including `?` early
62/// returns on malformed input.
63///
64/// Deserialization of sensitive material (secret keys, seeds) should read its bytes through this
65/// helper instead of calling `read_array` and remembering a manual `zeroize()` before every
66/// return path.
67///
68/// Note: `ByteReader::read_array` returns the array by value, so the single move into the
69/// wrapper is not wiped. This is the same residual a manual `zeroize()` call has; the helper
70/// closes the error-path leak and removes the per-call-site cleanup, but a fully airtight read
71/// would require `ByteReader` to expose reading into a caller-provided `&mut [u8]`.
72pub(crate) fn read_sensitive_array<const N: usize, R: ByteReader>(
73 source: &mut R,
74) -> Result<zeroize::Zeroizing<[u8; N]>, DeserializationError> {
75 Ok(zeroize::Zeroizing::new(source.read_array()?))
76}
77
78// CONVERSIONS BETWEEN BYTES AND ELEMENTS
79// ================================================================================================
80
81/// Converts a sequence of bytes into vector field elements with padding. This guarantees that no
82/// two sequences or bytes map to the same sequence of field elements.
83///
84/// Packs bytes into chunks of `BINARY_CHUNK_SIZE` and adds padding to the final chunk using a `1`
85/// bit followed by zeros. This ensures the original bytes can be recovered during decoding without
86/// any ambiguity.
87///
88/// Note that by the endianness of the conversion as well as the fact that we are packing at most
89/// `56 = 7 * 8` bits in each field element, the padding above with `1` should never overflow the
90/// field size.
91///
92/// # Arguments
93/// * `bytes` - Byte slice to encode
94///
95/// # Returns
96///
97/// Returns `bytes.len().div_ceil(BINARY_CHUNK_SIZE)` Felts. Empty input yields an empty vector.
98/// The padding marker fits in the final Felt, even when the final data chunk has seven bytes.
99pub fn bytes_to_elements_with_padding(bytes: &[u8]) -> Vec<Felt> {
100 if bytes.is_empty() {
101 return vec![];
102 }
103
104 // determine the number of field elements needed to encode `bytes` when each field element
105 // represents at most 7 bytes.
106 let num_field_elem = bytes.len().div_ceil(BINARY_CHUNK_SIZE);
107
108 // initialize a buffer to receive the little-endian elements.
109 let mut buf = [0_u8; 8];
110
111 // iterate the chunks of bytes, creating a field element from each chunk
112 let last_chunk_idx = num_field_elem - 1;
113
114 bytes
115 .chunks(BINARY_CHUNK_SIZE)
116 .enumerate()
117 .map(|(current_chunk_idx, chunk)| {
118 // copy the chunk into the buffer
119 if current_chunk_idx != last_chunk_idx {
120 buf[..BINARY_CHUNK_SIZE].copy_from_slice(chunk);
121 } else {
122 // on the last iteration, we pad `buf` with a 1 followed by as many 0's as are
123 // needed to fill it
124 buf.fill(0);
125 buf[..chunk.len()].copy_from_slice(chunk);
126 buf[chunk.len()] = 1;
127 }
128
129 Felt::new_unchecked(u64::from_le_bytes(buf))
130 })
131 .collect()
132}
133
134/// Converts a sequence of padded field elements back to the original bytes.
135///
136/// Reconstructs the original byte sequence by removing the padding added by
137/// `bytes_to_elements_with_padding`.
138/// The padding consists of a `1` bit followed by zeros in the final field element.
139/// Any bytes after the last `1` marker in the final field element are ignored and are not
140/// validated to be zero.
141///
142/// Note that by the endianness of the conversion as well as the fact that we are packing at most
143/// `56 = 7 * 8` bits in each field element, the padding above with `1` should never overflow the
144/// field size.
145///
146/// # Arguments
147/// * `felts` - Slice of field elements with padding in the last element
148///
149/// # Returns
150/// * `Some(Vec<u8>)` - The original byte sequence with padding removed
151/// * `None` - If no padding marker (`1` bit) is found
152pub fn padded_elements_to_bytes(felts: &[Felt]) -> Option<Vec<u8>> {
153 let number_felts = felts.len();
154 if number_felts == 0 {
155 return Some(vec![]);
156 }
157
158 let mut result = Vec::with_capacity(number_felts * BINARY_CHUNK_SIZE);
159 for felt in felts.iter().take(number_felts - 1) {
160 let felt_bytes = felt.as_canonical_u64().to_le_bytes();
161 result.extend_from_slice(&felt_bytes[..BINARY_CHUNK_SIZE]);
162 }
163
164 // handle the last field element
165 let felt_bytes = felts[number_felts - 1].as_canonical_u64().to_le_bytes();
166 let pos = felt_bytes.iter().rposition(|entry| *entry == 1_u8)?;
167
168 result.extend_from_slice(&felt_bytes[..pos]);
169 Some(result)
170}
171
172/// Converts field elements to raw byte representation.
173///
174/// Each `Felt` is converted to its full `NUM_BYTES` representation, in little-endian form
175/// and canonical form, without any padding removal or validation. This is the inverse
176/// of `bytes_to_elements_exact`.
177///
178/// # Arguments
179/// * `felts` - Slice of field elements to convert
180///
181/// # Returns
182/// Vector containing the raw bytes from all field elements
183pub fn elements_to_bytes(felts: &[Felt]) -> Vec<u8> {
184 let number_felts = felts.len();
185 let mut result = Vec::with_capacity(number_felts * Felt::NUM_BYTES);
186 for felt in felts.iter().take(number_felts) {
187 let felt_bytes = felt.as_canonical_u64().to_le_bytes();
188 result.extend_from_slice(&felt_bytes);
189 }
190
191 result
192}
193
194/// Converts bytes to field elements with validation.
195///
196/// This function validates that:
197/// - The input bytes length is divisible by `Felt::NUM_BYTES`
198/// - All `Felt::NUM_BYTES`-byte sequences represent valid field elements
199///
200/// # Arguments
201/// * `bytes` - Byte slice that must be a multiple of `Felt::NUM_BYTES` in length
202///
203/// # Returns
204/// `Option<Vec<Felt>>` - Vector of `Felt` elements if all validations pass, or None otherwise
205pub fn bytes_to_elements_exact(bytes: &[u8]) -> Option<Vec<Felt>> {
206 // Check that the length is divisible by NUM_BYTES
207 if !bytes.len().is_multiple_of(Felt::NUM_BYTES) {
208 return None;
209 }
210
211 let mut result = Vec::with_capacity(bytes.len() / Felt::NUM_BYTES);
212
213 for chunk in bytes.as_chunks::<{ Felt::NUM_BYTES }>().0 {
214 let value = u64::from_le_bytes(*chunk);
215
216 // Validate that the value represents a valid field element
217 let felt = Felt::from_canonical_checked(value)?;
218 result.push(felt);
219 }
220
221 Some(result)
222}
223
224/// Converts bytes to field elements using u32 packing in little-endian format.
225///
226/// Each field element contains a u32 value representing up to 4 bytes. If the byte length
227/// is not a multiple of 4, the final field element is zero-padded.
228///
229/// # Arguments
230/// - `bytes`: The byte slice to convert
231///
232/// # Returns
233/// A vector of field elements, each containing 4 bytes packed in little-endian order.
234///
235/// # Examples
236/// ```rust
237/// # use miden_crypto::{Felt, utils::bytes_to_packed_u32_elements};
238///
239/// let bytes = vec![0x01, 0x02, 0x03, 0x04, 0x05];
240/// let felts = bytes_to_packed_u32_elements(&bytes);
241/// assert_eq!(felts, vec![Felt::new_unchecked(0x04030201), Felt::new_unchecked(0x00000005)]);
242/// ```
243pub fn bytes_to_packed_u32_elements(bytes: &[u8]) -> Vec<Felt> {
244 const BYTES_PER_U32: usize = size_of::<u32>();
245
246 bytes
247 .chunks(BYTES_PER_U32)
248 .map(|chunk| {
249 // Pack up to 4 bytes into a u32 in little-endian format
250 let mut packed = [0u8; BYTES_PER_U32];
251 packed[..chunk.len()].copy_from_slice(chunk);
252 Felt::from_u32(u32::from_le_bytes(packed))
253 })
254 .collect()
255}
256
257// VECTOR FUNCTIONS (ported from Winterfell's winter-utils)
258// ================================================================================================
259
260/// Returns a vector of the specified length with un-initialized memory.
261///
262/// This is usually faster than requesting a vector with initialized memory and is useful when we
263/// overwrite all contents of the vector immediately after memory allocation.
264pub fn uninit_vector<T>(length: usize) -> Vec<MaybeUninit<T>> {
265 Vec::from(Box::new_uninit_slice(length))
266}
267
268/// Converts a fully-initialized `Vec<MaybeUninit<T>>` into `Vec<T>`.
269///
270/// # Safety
271/// All elements must be initialized before calling this function.
272pub unsafe fn assume_init_vec<T>(v: Vec<MaybeUninit<T>>) -> Vec<T> {
273 let mut v = ManuallyDrop::new(v);
274 let ptr = v.as_mut_ptr();
275 let len = v.len();
276 let cap = v.capacity();
277 // SAFETY: caller guarantees all elements are initialized.
278 unsafe { Vec::from_raw_parts(ptr.cast::<T>(), len, cap) }
279}
280
281// GROUPING / UN-GROUPING FUNCTIONS (ported from Winterfell's winter-utils)
282// ================================================================================================
283
284/// Transmutes a slice of `n` elements into a slice of `n` / `N` elements, each of which is
285/// an array of `N` elements.
286///
287/// This function just re-interprets the underlying memory and is thus zero-copy.
288/// # Panics
289/// Panics if `n` is not divisible by `N`.
290pub fn group_slice_elements<T, const N: usize>(source: &[T]) -> &[[T; N]] {
291 let (chunks, remainder) = source.as_chunks::<N>();
292 assert!(remainder.is_empty(), "source length must be divisible by {N}");
293 chunks
294}
295
296/// Transmutes a slice of `n` arrays each of length `N`, into a slice of `N` * `n` elements.
297///
298/// This function just re-interprets the underlying memory and is thus zero-copy.
299pub fn flatten_slice_elements<T, const N: usize>(source: &[[T; N]]) -> &[T] {
300 // SAFETY: [T; N] has the same alignment and memory layout as an array of T.
301 // p3-util's as_base_slice handles the conversion safely.
302 unsafe { p3_util::as_base_slice(source) }
303}
304
305/// Transmutes a vector of `n` arrays each of length `N`, into a vector of `N` * `n` elements.
306///
307/// This function just re-interprets the underlying memory and is thus zero-copy.
308pub fn flatten_vector_elements<T, const N: usize>(source: Vec<[T; N]>) -> Vec<T> {
309 // SAFETY: [T; N] has the same alignment and memory layout as an array of T.
310 // p3-util's flatten_to_base handles the conversion without reallocations.
311 unsafe { p3_util::flatten_to_base(source) }
312}
313
314// TRANSPOSING (ported from Winterfell's winter-utils)
315// ================================================================================================
316
317/// Transposes a slice of `n` elements into a matrix with `N` columns and `n`/`N` rows.
318///
319/// When `concurrent` feature is enabled, the slice will be transposed using multiple threads.
320/// Uses uninit_vector for ~31% speedup at 1024x1024 (benches/transpose.rs).
321///
322/// # Panics
323/// Panics if `n` is not divisible by `N`.
324pub fn transpose_slice<T: Copy + Send + Sync, const N: usize>(source: &[T]) -> Vec<[T; N]> {
325 let row_count = source.len() / N;
326 assert_eq!(
327 row_count * N,
328 source.len(),
329 "source length must be divisible by {}, but was {}",
330 N,
331 source.len()
332 );
333
334 let mut result = uninit_vector::<[T; N]>(row_count);
335 result.par_iter_mut().enumerate().for_each(|(i, slot)| {
336 let row = core::array::from_fn(|j| source[i + j * row_count]);
337 slot.write(row);
338 });
339 // SAFETY: all rows are written above.
340 unsafe { assume_init_vec(result) }
341}