Skip to main content

miden_field_repr/
lib.rs

1//! Serialization/deserialization for felt representation.
2//!
3//! This crate provides traits and utilities for converting Rust types to and from
4//! a sequence of [`Felt`] elements.
5
6#![no_std]
7#![deny(warnings)]
8
9extern crate alloc;
10
11use alloc::vec::Vec;
12
13pub use miden_field::{Felt, Word};
14/// Re-export `DeriveFromFeltRepr` as `FromFeltRepr` for `#[derive(FromFeltRepr)]` ergonomics.
15pub use miden_field_repr_derive::DeriveFromFeltRepr as FromFeltRepr;
16/// Re-export `DeriveToFeltRepr` as `ToFeltRepr` for `#[derive(ToFeltRepr)]` ergonomics.
17pub use miden_field_repr_derive::DeriveToFeltRepr as ToFeltRepr;
18
19/// Error returned when decoding a type from its felt representation.
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[non_exhaustive]
22pub enum FeltReprError {
23    /// Attempted to read beyond the end of the felt slice.
24    UnexpectedEof {
25        /// Current read position.
26        pos: usize,
27        /// Total number of felts available.
28        len: usize,
29    },
30    /// A decoded value did not fit into the target Rust type.
31    ValueOutOfRange {
32        /// Position of the decoded value.
33        pos: usize,
34        /// Total number of felts available.
35        len: usize,
36        /// Name of the target Rust type.
37        ty: &'static str,
38        /// The decoded value.
39        value: u64,
40        /// The maximum supported value for `ty`.
41        max: u64,
42    },
43    /// An `Option<T>` tag was neither `0` nor `1`.
44    InvalidOptionTag {
45        /// Position of the decoded tag.
46        pos: usize,
47        /// Total number of felts available.
48        len: usize,
49        /// The decoded tag.
50        tag: u64,
51    },
52    /// A boolean value was neither `0` nor `1`.
53    InvalidBool {
54        /// Position of the decoded value.
55        pos: usize,
56        /// Total number of felts available.
57        len: usize,
58        /// The decoded value.
59        value: u64,
60    },
61    /// An enum tag was not a valid variant ordinal.
62    UnknownEnumTag {
63        /// Position of the decoded tag.
64        pos: usize,
65        /// Total number of felts available.
66        len: usize,
67        /// Name of the decoded enum type.
68        ty: &'static str,
69        /// The decoded tag.
70        tag: u32,
71    },
72    /// Extra data remained after decoding a value.
73    TrailingData {
74        /// Current read position.
75        pos: usize,
76        /// Total number of felts available.
77        len: usize,
78    },
79    /// A custom decoding error provided by a downstream implementation.
80    Custom(&'static str),
81}
82
83impl core::fmt::Display for FeltReprError {
84    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
85        match self {
86            Self::UnexpectedEof { pos, len } => {
87                write!(f, "unexpected end of input at felt {pos} of {len}")
88            }
89            Self::ValueOutOfRange {
90                pos,
91                len,
92                ty,
93                value,
94                max,
95            } => {
96                write!(f, "value {value} out of range for {ty} at felt {pos} of {len} (max {max})")
97            }
98            Self::InvalidOptionTag { pos, len, tag } => {
99                write!(f, "invalid Option tag at felt {pos} of {len}: {tag}")
100            }
101            Self::InvalidBool { pos, len, value } => {
102                write!(f, "invalid bool value at felt {pos} of {len}: {value}")
103            }
104            Self::UnknownEnumTag { pos, len, ty, tag } => {
105                write!(f, "unknown enum tag for {ty} at felt {pos} of {len}: {tag}")
106            }
107            Self::TrailingData { pos, len } => {
108                write!(f, "trailing data starting at felt {pos} of {len}")
109            }
110            Self::Custom(msg) => f.write_str(msg),
111        }
112    }
113}
114
115/// Convenience alias for results returned by felt-repr decoding APIs.
116pub type FeltReprResult<T> = core::result::Result<T, FeltReprError>;
117
118/// A reader that wraps a slice of `Felt` elements and tracks the current position.
119pub struct FeltReader<'a> {
120    data: &'a [Felt],
121    pos: usize,
122}
123
124impl<'a> FeltReader<'a> {
125    /// Creates a new `FeltReader` from a slice of `Felt` elements.
126    #[inline(always)]
127    pub fn new(data: &'a [Felt]) -> Self {
128        Self { data, pos: 0 }
129    }
130
131    /// Returns the current read position.
132    #[inline(always)]
133    pub fn pos(&self) -> usize {
134        self.pos
135    }
136
137    /// Returns the total number of felts in the underlying slice.
138    #[inline(always)]
139    pub fn len(&self) -> usize {
140        self.data.len()
141    }
142
143    /// Returns `true` if the underlying slice is empty.
144    #[inline(always)]
145    pub fn is_empty(&self) -> bool {
146        self.data.is_empty()
147    }
148
149    /// Returns the number of unread felts remaining.
150    #[inline(always)]
151    pub fn remaining(&self) -> usize {
152        self.data.len().saturating_sub(self.pos)
153    }
154
155    /// Ensures there are no unread felts remaining.
156    #[inline(always)]
157    pub fn ensure_eof(&self) -> FeltReprResult<()> {
158        if self.remaining() != 0 {
159            return Err(FeltReprError::TrailingData {
160                pos: self.pos,
161                len: self.data.len(),
162            });
163        }
164        Ok(())
165    }
166
167    /// Reads the next `Felt` element, advancing the position.
168    #[inline(always)]
169    pub fn read(&mut self) -> FeltReprResult<Felt> {
170        if self.pos >= self.data.len() {
171            return Err(FeltReprError::UnexpectedEof {
172                pos: self.pos,
173                len: self.data.len(),
174            });
175        }
176
177        let felt = self.data[self.pos];
178        self.pos += 1;
179        Ok(felt)
180    }
181
182    /// Reads the next element and decodes it as a `u32`.
183    #[inline(always)]
184    pub fn read_u32(&mut self) -> FeltReprResult<u32> {
185        let pos = self.pos;
186        let len = self.data.len();
187        let value = self.read()?.as_canonical_u64();
188        if value > u32::MAX as u64 {
189            return Err(FeltReprError::ValueOutOfRange {
190                pos,
191                len,
192                ty: "u32",
193                value,
194                max: u32::MAX as u64,
195            });
196        }
197        Ok(value as u32)
198    }
199
200    /// Reads the next element and decodes it as a `u8`.
201    #[inline(always)]
202    pub fn read_u8(&mut self) -> FeltReprResult<u8> {
203        let pos = self.pos;
204        let len = self.data.len();
205        let value = self.read()?.as_canonical_u64();
206        if value > u8::MAX as u64 {
207            return Err(FeltReprError::ValueOutOfRange {
208                pos,
209                len,
210                ty: "u8",
211                value,
212                max: u8::MAX as u64,
213            });
214        }
215        Ok(value as u8)
216    }
217
218    /// Reads the next element and decodes it as a boolean.
219    ///
220    /// Only `0` and `1` are accepted.
221    #[inline(always)]
222    pub fn read_bool(&mut self) -> FeltReprResult<bool> {
223        let pos = self.pos;
224        let len = self.data.len();
225        match self.read()?.as_canonical_u64() {
226            0 => Ok(false),
227            1 => Ok(true),
228            value => Err(FeltReprError::InvalidBool { pos, len, value }),
229        }
230    }
231
232    /// Reads the next element and decodes it as a length prefix.
233    ///
234    /// The length is encoded as a `u32` in a single `Felt`.
235    #[inline(always)]
236    pub fn read_len_u32(&mut self) -> FeltReprResult<usize> {
237        Ok(self.read_u32()? as usize)
238    }
239}
240
241/// A writer that wraps a `Vec<Felt>` and appends elements to it.
242pub struct FeltWriter<'a> {
243    data: &'a mut Vec<Felt>,
244}
245
246impl<'a> FeltWriter<'a> {
247    /// Creates a new `FeltWriter` from a mutable reference to a `Vec<Felt>`.
248    #[inline(always)]
249    pub fn new(data: &'a mut Vec<Felt>) -> Self {
250        Self { data }
251    }
252
253    /// Writes a `Felt` element to the output.
254    #[inline(always)]
255    pub fn write(&mut self, felt: Felt) {
256        self.data.push(felt);
257    }
258}
259
260/// Sums statically known encoded lengths; a variable-length (`None`) entry makes the total
261/// variable.
262///
263/// Used by the `FromFeltRepr` derive to compute [`FromFeltRepr::FIXED_LEN`] for structs (and for
264/// enum variant payloads) as a fold over the field types' lengths.
265#[doc(hidden)]
266pub const fn sum_fixed_len(lens: &[Option<usize>]) -> Option<usize> {
267    let mut total = 0usize;
268    let mut i = 0;
269    while i < lens.len() {
270        match lens[i] {
271            Some(len) => total += len,
272            None => return None,
273        }
274        i += 1;
275    }
276    Some(total)
277}
278
279/// Merges per-variant payload lengths into an enum-wide payload length.
280///
281/// Returns the common length when every variant's payload has the same statically known length,
282/// and `None` (variable length) otherwise. An empty variant list yields `Some(0)`.
283#[doc(hidden)]
284pub const fn uniform_fixed_len(lens: &[Option<usize>]) -> Option<usize> {
285    if lens.is_empty() {
286        return Some(0);
287    }
288    let first = match lens[0] {
289        Some(len) => len,
290        None => return None,
291    };
292    let mut i = 1;
293    while i < lens.len() {
294        match lens[i] {
295            Some(len) => {
296                if len != first {
297                    return None;
298                }
299            }
300            None => return None,
301        }
302        i += 1;
303    }
304    Some(first)
305}
306
307/// Trait for deserialization from felt memory representation.
308pub trait FromFeltRepr: Sized {
309    /// Total encoded length in felts, when statically known.
310    ///
311    /// `None` marks a variable-length encoding: a type containing `Vec` or `Option` fields, or an
312    /// enum whose variants encode to different lengths. When `Some(n)`, `n` must equal the exact
313    /// number of felts consumed by [`Self::from_felt_repr`] (and produced by the matching
314    /// `ToFeltRepr` implementation, if any).
315    ///
316    /// Defaults to `None`, which is always correct for a manual implementation — consumers that
317    /// dispatch on the length (e.g. the tx-script args transport) merely fall back to their
318    /// variable-length path. Override it with the exact count to enable fixed-length dispatch;
319    /// `#[derive(FromFeltRepr)]` computes it automatically.
320    const FIXED_LEN: Option<usize> = None;
321
322    /// Deserializes from a `FeltReader`, consuming the required elements.
323    fn from_felt_repr(reader: &mut FeltReader<'_>) -> FeltReprResult<Self>;
324}
325
326impl FromFeltRepr for Felt {
327    const FIXED_LEN: Option<usize> = Some(1);
328
329    #[inline(always)]
330    fn from_felt_repr(reader: &mut FeltReader<'_>) -> FeltReprResult<Self> {
331        reader.read()
332    }
333}
334
335/// Encodes a `Word` as its 4 felt elements in order.
336impl FromFeltRepr for Word {
337    const FIXED_LEN: Option<usize> = Some(4);
338
339    #[inline(always)]
340    fn from_felt_repr(reader: &mut FeltReader<'_>) -> FeltReprResult<Self> {
341        let a = reader.read()?;
342        let b = reader.read()?;
343        let c = reader.read()?;
344        let d = reader.read()?;
345        Ok(Word::new([a, b, c, d]))
346    }
347}
348
349impl FromFeltRepr for u64 {
350    const FIXED_LEN: Option<usize> = Some(2);
351
352    #[inline(always)]
353    fn from_felt_repr(reader: &mut FeltReader<'_>) -> FeltReprResult<Self> {
354        // Encode u64 as 2 u32 limbs
355        let lo = reader.read_u32()? as u64;
356        let hi = reader.read_u32()? as u64;
357        Ok((hi << 32) | lo)
358    }
359}
360
361impl FromFeltRepr for u32 {
362    const FIXED_LEN: Option<usize> = Some(1);
363
364    #[inline(always)]
365    fn from_felt_repr(reader: &mut FeltReader<'_>) -> FeltReprResult<Self> {
366        reader.read_u32()
367    }
368}
369
370impl FromFeltRepr for u8 {
371    const FIXED_LEN: Option<usize> = Some(1);
372
373    #[inline(always)]
374    fn from_felt_repr(reader: &mut FeltReader<'_>) -> FeltReprResult<Self> {
375        reader.read_u8()
376    }
377}
378
379impl FromFeltRepr for bool {
380    const FIXED_LEN: Option<usize> = Some(1);
381
382    #[inline(always)]
383    fn from_felt_repr(reader: &mut FeltReader<'_>) -> FeltReprResult<Self> {
384        reader.read_bool()
385    }
386}
387
388/// Encodes an `Option<T>` as a 1-felt tag followed by the payload (if present).
389///
390/// Format:
391/// - `None` => `[0]`
392/// - `Some(x)` => `[1, x...]`
393impl<T> FromFeltRepr for Option<T>
394where
395    T: FromFeltRepr,
396{
397    #[inline(always)]
398    fn from_felt_repr(reader: &mut FeltReader<'_>) -> FeltReprResult<Self> {
399        let pos = reader.pos();
400        let len = reader.len();
401        match reader.read()?.as_canonical_u64() {
402            0 => Ok(None),
403            1 => Ok(Some(T::from_felt_repr(reader)?)),
404            tag => Err(FeltReprError::InvalidOptionTag { pos, len, tag }),
405        }
406    }
407}
408
409/// Encodes a `Vec<T>` as a length prefix followed by elements.
410///
411/// Format: `[len, elem0..., elemN-1...]` where `len` is a `u32` encoded in a single `Felt`.
412impl<T> FromFeltRepr for Vec<T>
413where
414    T: FromFeltRepr,
415{
416    #[inline(always)]
417    fn from_felt_repr(reader: &mut FeltReader<'_>) -> FeltReprResult<Self> {
418        let len = reader.read_len_u32()?;
419
420        // Reserve only what the reader can actually supply: a lying length prefix must surface
421        // as a decode error, not an allocation abort.
422        let mut result = match T::FIXED_LEN {
423            Some(width) if width > 0 => match len.checked_mul(width) {
424                Some(total) if total <= reader.remaining() => Vec::with_capacity(len),
425                _ => {
426                    return Err(FeltReprError::UnexpectedEof {
427                        pos: reader.pos(),
428                        len: reader.len(),
429                    });
430                }
431            },
432            _ => Vec::new(),
433        };
434
435        let mut i = 0usize;
436        while i < len {
437            result.push(T::from_felt_repr(reader)?);
438            i += 1;
439        }
440        Ok(result)
441    }
442}
443
444/// Trait for serializing a type into its felt memory representation.
445pub trait ToFeltRepr {
446    /// Writes this value's felt representation to the writer.
447    fn write_felt_repr(&self, writer: &mut FeltWriter<'_>);
448
449    /// Convenience method that allocates and returns a `Vec<Felt>`.
450    fn to_felt_repr(&self) -> Vec<Felt> {
451        // Allocate ahead to avoid reallocations
452        let mut data = Vec::with_capacity(256);
453        self.write_felt_repr(&mut FeltWriter::new(&mut data));
454        data
455    }
456}
457
458impl ToFeltRepr for Felt {
459    #[inline(always)]
460    fn write_felt_repr(&self, writer: &mut FeltWriter<'_>) {
461        writer.write(*self);
462    }
463}
464
465/// Encodes a `Word` as its 4 felt elements in order.
466impl ToFeltRepr for Word {
467    #[inline(always)]
468    fn write_felt_repr(&self, writer: &mut FeltWriter<'_>) {
469        for felt in self.as_elements() {
470            writer.write(*felt);
471        }
472    }
473}
474
475impl ToFeltRepr for u64 {
476    #[inline(always)]
477    fn write_felt_repr(&self, writer: &mut FeltWriter<'_>) {
478        let lo = (*self & 0xffff_ffff) as u32;
479        let hi = (*self >> 32) as u32;
480        writer.write(Felt::new(lo as u64).unwrap());
481        writer.write(Felt::new(hi as u64).unwrap());
482    }
483}
484
485impl ToFeltRepr for u32 {
486    #[inline(always)]
487    fn write_felt_repr(&self, writer: &mut FeltWriter<'_>) {
488        writer.write(Felt::new(*self as u64).unwrap());
489    }
490}
491
492impl ToFeltRepr for u8 {
493    #[inline(always)]
494    fn write_felt_repr(&self, writer: &mut FeltWriter<'_>) {
495        writer.write(Felt::new(*self as u64).unwrap());
496    }
497}
498
499impl ToFeltRepr for bool {
500    #[inline(always)]
501    fn write_felt_repr(&self, writer: &mut FeltWriter<'_>) {
502        writer.write(Felt::new(*self as u64).unwrap());
503    }
504}
505
506/// Encodes an `Option<T>` as a 1-felt tag followed by the payload (if present).
507///
508/// Format:
509/// - `None` => `[0]`
510/// - `Some(x)` => `[1, x...]`
511impl<T> ToFeltRepr for Option<T>
512where
513    T: ToFeltRepr,
514{
515    #[inline(always)]
516    fn write_felt_repr(&self, writer: &mut FeltWriter<'_>) {
517        match self {
518            None => writer.write(Felt::new(0).unwrap()),
519            Some(value) => {
520                writer.write(Felt::new(1).unwrap());
521                value.write_felt_repr(writer);
522            }
523        }
524    }
525}
526
527/// Encodes a `Vec<T>` as a length prefix followed by elements.
528///
529/// Format: `[len, elem0..., elemN-1...]` where `len` is a `u32` encoded in a single `Felt`.
530impl<T> ToFeltRepr for Vec<T>
531where
532    T: ToFeltRepr,
533{
534    #[inline(always)]
535    fn write_felt_repr(&self, writer: &mut FeltWriter<'_>) {
536        let len = self.len();
537        assert!(len <= u32::MAX as usize, "Vec: length out of range");
538        writer.write(Felt::new(len as u64).unwrap());
539
540        let mut i = 0usize;
541        while i < len {
542            self[i].write_felt_repr(writer);
543            i += 1;
544        }
545    }
546}