Skip to main content

miden_field/native/
mod.rs

1//! Off-chain implementation of [`crate::Felt`].
2
3use alloc::{format, vec, vec::Vec};
4use core::{
5    array, fmt,
6    hash::{Hash, Hasher},
7    iter::{Product, Sum},
8    mem::{align_of, size_of},
9    ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign},
10};
11
12use miden_serde_utils::{
13    ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
14};
15use num_bigint::BigUint;
16use p3_challenger::UniformSamplingField;
17use p3_field::{
18    Field, InjectiveMonomial, Packable, PermutationMonomial, PrimeCharacteristicRing, PrimeField,
19    PrimeField64, RawDataSerializable, TwoAdicField,
20    extension::{
21        Binomial, BinomiallyExtendable, ExtensionAlgebra, HasTwoAdicBinomialExtension,
22        binomial_mul, binomial_square,
23    },
24    impl_raw_serializable_primefield64,
25    integers::QuotientMap,
26    quotient_map_large_iint, quotient_map_large_uint, quotient_map_small_int,
27};
28use p3_goldilocks::Goldilocks;
29use p3_util::flatten_to_base;
30use rand::{
31    Rng,
32    distr::{Distribution, StandardUniform},
33};
34use subtle::{ConditionallySelectable, ConstantTimeLess};
35
36#[cfg(any(
37    all(target_arch = "x86_64", target_feature = "avx2"),
38    all(target_arch = "aarch64", target_feature = "neon"),
39    all(target_arch = "wasm32", target_feature = "simd128"),
40))]
41mod packed;
42#[cfg(any(
43    all(target_arch = "x86_64", target_feature = "avx2"),
44    all(target_arch = "aarch64", target_feature = "neon"),
45    all(target_arch = "wasm32", target_feature = "simd128"),
46))]
47pub use packed::PackedFelt;
48
49#[cfg(test)]
50mod tests;
51
52// FELT
53// ================================================================================================
54
55/// A `Felt` backed by Plonky3's Goldilocks field element.
56///
57/// Round-trip equality is safe even though `Felt` may hold non-canonical internal values:
58/// `PartialEq for Felt` delegates to `Goldilocks::eq`, which compares `as_canonical_u64()` on
59/// both sides, so canonicalization by serialization is invisible to equality.
60#[derive(Copy, Clone, Default, serde::Serialize, serde::Deserialize)]
61#[repr(transparent)]
62#[cfg_attr(
63    all(feature = "arbitrary", test),
64    miden_test_serialization_macros::serialization_test
65)]
66pub struct Felt(Goldilocks);
67
68impl Felt {
69    /// Order of the field.
70    pub const ORDER: u64 = <Goldilocks as PrimeField64>::ORDER_U64;
71
72    pub const ZERO: Self = Self(Goldilocks::ZERO);
73    pub const ONE: Self = Self(Goldilocks::ONE);
74
75    /// The largest valid field element, equal to `ORDER - 1`.
76    pub const MAX: Self = Self::new_unchecked(Self::ORDER - 1);
77
78    /// The number of bytes which this field element occupies in memory.
79    pub const NUM_BYTES: usize = Goldilocks::NUM_BYTES;
80
81    /// Constructs a new field element from the provided `value`.
82    ///
83    /// # Errors
84    ///
85    /// - [`FeltFromIntError`] if the provided `value` is not a valid input.
86    pub fn new(value: u64) -> Result<Self, FeltFromIntError> {
87        Felt::from_canonical_checked(value).ok_or(FeltFromIntError(value))
88    }
89
90    /// Creates a new field element from any `u64` without performing reduction.
91    ///
92    /// Any `u64` value is accepted. No reduction is performed since Goldilocks uses a
93    /// non-canonical internal representation.
94    #[inline]
95    pub const fn new_unchecked(value: u64) -> Self {
96        Self(Goldilocks::new(value))
97    }
98
99    /// Constructs a field element from a `u8`.
100    #[inline]
101    pub const fn from_u8(value: u8) -> Self {
102        Self::new_unchecked(value as u64)
103    }
104
105    /// Constructs a field element from a `u16`.
106    #[inline]
107    pub const fn from_u16(value: u16) -> Self {
108        Self::new_unchecked(value as u64)
109    }
110
111    /// Constructs a field element from a `u32`.
112    #[inline]
113    pub const fn from_u32(value: u32) -> Self {
114        Self::new_unchecked(value as u64)
115    }
116
117    /// The elementary function `double(a) = 2*a`.
118    #[inline]
119    pub fn double(&self) -> Self {
120        <Self as PrimeCharacteristicRing>::double(self)
121    }
122
123    /// The elementary function `square(a) = a^2`.
124    #[inline]
125    pub fn square(&self) -> Self {
126        <Self as PrimeCharacteristicRing>::square(self)
127    }
128
129    /// Exponentiation by a `u64` power.
130    #[inline]
131    pub fn exp_u64(&self, power: u64) -> Self {
132        <Self as PrimeCharacteristicRing>::exp_u64(self, power)
133    }
134
135    /// Exponentiation by a small constant power.
136    #[inline]
137    pub fn exp_const_u64<const POWER: u64>(&self) -> Self {
138        <Self as PrimeCharacteristicRing>::exp_const_u64::<POWER>(self)
139    }
140
141    /// Return the representative of element in canonical form which lies in the range
142    /// `0 <= x < ORDER`.
143    #[inline]
144    pub fn as_canonical_u64(&self) -> u64 {
145        <Self as PrimeField64>::as_canonical_u64(self)
146    }
147
148    /// Constant-time equivalent of `as_canonical_u64()` using the same reduction logic as
149    /// Plonky3's Goldilocks implementation.
150    #[inline]
151    pub fn as_canonical_u64_ct(&self) -> u64 {
152        let raw = raw_felt_u64(*self);
153        // Mirrors Goldilocks::as_canonical_u64: conditional subtraction of ORDER.
154        // A single subtraction is sufficient for any u64 value since 2*ORDER > u64::MAX.
155        let reduced = raw.wrapping_sub(Self::ORDER);
156        let reduce = !raw.ct_lt(&Self::ORDER);
157        u64::conditional_select(&raw, &reduced, reduce)
158    }
159}
160
161#[inline]
162fn raw_felt_u64(value: Felt) -> u64 {
163    const _: () = {
164        assert!(size_of::<Felt>() == size_of::<u64>());
165        assert!(align_of::<Felt>() == align_of::<u64>());
166        assert!(2u128 * (Felt::ORDER as u128) > u64::MAX as u128);
167    };
168    // SAFETY: Felt is repr(transparent) over Goldilocks, which is repr(transparent) over u64.
169    unsafe { core::mem::transmute_copy(&value) }
170}
171
172/// Reinterprets a `Felt` slice as `Goldilocks`.
173///
174/// # Safety
175///
176/// `Felt` is `#[repr(transparent)]` over `Goldilocks`, so the element layout matches.
177#[inline]
178fn felts_as_goldilocks_slice(s: &[Felt]) -> &[Goldilocks] {
179    // SAFETY: `Felt` is `#[repr(transparent)]` over `Goldilocks`, so the element layout matches.
180    unsafe { core::slice::from_raw_parts(s.as_ptr().cast::<Goldilocks>(), s.len()) }
181}
182
183/// Reinterprets a `Felt` array as `Goldilocks`.
184///
185/// # Safety
186///
187/// `Felt` is `#[repr(transparent)]` over `Goldilocks`, so `[Felt; N]` matches `[Goldilocks; N]`.
188#[inline]
189fn felts_as_goldilocks_array<const N: usize>(a: &[Felt; N]) -> &[Goldilocks; N] {
190    // SAFETY: same layout as `felts_as_goldilocks_slice`, for a fixed `N`.
191    unsafe { &*(a as *const [Felt; N] as *const [Goldilocks; N]) }
192}
193
194impl fmt::Display for Felt {
195    #[inline]
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        fmt::Display::fmt(&self.0, f)
198    }
199}
200
201impl fmt::Debug for Felt {
202    #[inline]
203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204        fmt::Debug::fmt(&self.0, f)
205    }
206}
207
208impl Hash for Felt {
209    #[inline]
210    fn hash<H: Hasher>(&self, state: &mut H) {
211        state.write_u64(self.as_canonical_u64());
212    }
213}
214
215// FIELD
216// ================================================================================================
217
218impl Field for Felt {
219    #[cfg(all(target_arch = "x86_64", target_feature = "avx2", not(target_feature = "avx512f")))]
220    type Packing = PackedFelt;
221
222    #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
223    type Packing = PackedFelt;
224
225    #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
226    type Packing = PackedFelt;
227
228    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
229    type Packing = PackedFelt;
230
231    #[cfg(not(any(
232        all(target_arch = "x86_64", target_feature = "avx2", not(target_feature = "avx512f")),
233        all(target_arch = "x86_64", target_feature = "avx512f"),
234        target_arch = "aarch64",
235        all(target_arch = "wasm32", target_feature = "simd128"),
236    )))]
237    type Packing = Self;
238
239    const GENERATOR: Self = Self(Goldilocks::GENERATOR);
240
241    #[inline]
242    fn is_zero(&self) -> bool {
243        self.0.is_zero()
244    }
245
246    #[inline]
247    fn try_inverse(&self) -> Option<Self> {
248        self.0.try_inverse().map(Self)
249    }
250
251    #[inline]
252    fn order() -> BigUint {
253        <Goldilocks as Field>::order()
254    }
255}
256
257impl Packable for Felt {}
258
259impl PrimeCharacteristicRing for Felt {
260    type PrimeSubfield = Goldilocks;
261
262    const ZERO: Self = Self(Goldilocks::ZERO);
263    const ONE: Self = Self(Goldilocks::ONE);
264    const TWO: Self = Self(Goldilocks::TWO);
265    const NEG_ONE: Self = Self(Goldilocks::NEG_ONE);
266
267    #[inline]
268    fn from_prime_subfield(f: Self::PrimeSubfield) -> Self {
269        Self(f)
270    }
271
272    #[inline]
273    fn from_bool(value: bool) -> Self {
274        Self::new_unchecked(value.into())
275    }
276
277    #[inline]
278    fn halve(&self) -> Self {
279        Self(self.0.halve())
280    }
281
282    #[inline]
283    fn mul_2exp_u64(&self, exp: u64) -> Self {
284        Self(self.0.mul_2exp_u64(exp))
285    }
286
287    #[inline]
288    fn div_2exp_u64(&self, exp: u64) -> Self {
289        Self(self.0.div_2exp_u64(exp))
290    }
291
292    #[inline]
293    fn exp_u64(&self, power: u64) -> Self {
294        self.0.exp_u64(power).into()
295    }
296
297    #[inline]
298    fn sum_array<const N: usize>(input: &[Self]) -> Self {
299        assert_eq!(N, input.len());
300        let g = felts_as_goldilocks_slice(input);
301        Self(Goldilocks::sum_array::<N>(g))
302    }
303
304    #[inline]
305    fn dot_product<const N: usize>(lhs: &[Self; N], rhs: &[Self; N]) -> Self {
306        let lhs_g = felts_as_goldilocks_array(lhs);
307        let rhs_g = felts_as_goldilocks_array(rhs);
308        Self(Goldilocks::dot_product(lhs_g, rhs_g))
309    }
310
311    #[inline]
312    fn zero_vec(len: usize) -> Vec<Self> {
313        // SAFETY:
314        // Due to `#[repr(transparent)]`, Felt, Goldilocks and u64 have the same size,
315        // alignment and memory layout making `flatten_to_base` safe.
316        // This will create a vector of Felt elements with value set to 0.
317        unsafe { flatten_to_base(vec![0u64; len]) }
318    }
319}
320
321quotient_map_small_int!(Felt, u64, [u8, u16, u32]);
322quotient_map_small_int!(Felt, i64, [i8, i16, i32]);
323
324quotient_map_large_uint!(
325    Felt,
326    u64,
327    Felt::ORDER_U64,
328    "`[0, 2^64 - 2^32]`",
329    "`[0, 2^64 - 1]`",
330    [u128]
331);
332quotient_map_large_iint!(
333    Felt,
334    i64,
335    "`[-(2^63 - 2^31), 2^63 - 2^31]`",
336    "`[1 + 2^32 - 2^64, 2^64 - 1]`",
337    [(i128, u128)]
338);
339
340impl QuotientMap<u64> for Felt {
341    #[inline]
342    fn from_int(int: u64) -> Self {
343        Goldilocks::from_int(int).into()
344    }
345
346    #[inline]
347    fn from_canonical_checked(int: u64) -> Option<Self> {
348        Goldilocks::from_canonical_checked(int).map(From::from)
349    }
350
351    #[inline(always)]
352    unsafe fn from_canonical_unchecked(int: u64) -> Self {
353        Goldilocks::new(int).into()
354    }
355}
356
357impl QuotientMap<i64> for Felt {
358    #[inline]
359    fn from_int(int: i64) -> Self {
360        Goldilocks::from_int(int).into()
361    }
362
363    #[inline]
364    fn from_canonical_checked(int: i64) -> Option<Self> {
365        Goldilocks::from_canonical_checked(int).map(From::from)
366    }
367
368    #[inline(always)]
369    unsafe fn from_canonical_unchecked(int: i64) -> Self {
370        unsafe { Goldilocks::from_canonical_unchecked(int).into() }
371    }
372}
373
374impl PrimeField for Felt {
375    #[inline]
376    fn as_canonical_biguint(&self) -> BigUint {
377        <Goldilocks as PrimeField>::as_canonical_biguint(&self.0)
378    }
379}
380
381impl PrimeField64 for Felt {
382    const ORDER_U64: u64 = <Goldilocks as PrimeField64>::ORDER_U64;
383
384    #[inline]
385    fn as_canonical_u64(&self) -> u64 {
386        self.0.as_canonical_u64()
387    }
388}
389
390impl TwoAdicField for Felt {
391    const TWO_ADICITY: usize = <Goldilocks as TwoAdicField>::TWO_ADICITY;
392
393    #[inline]
394    fn two_adic_generator(bits: usize) -> Self {
395        Self(<Goldilocks as TwoAdicField>::two_adic_generator(bits))
396    }
397}
398
399// EXTENSION FIELDS
400// ================================================================================================
401
402impl ExtensionAlgebra<Self, 2, Binomial<Self>> for Felt {
403    #[inline]
404    fn ext_mul(a: &[Self; 2], b: &[Self; 2], res: &mut [Self; 2]) {
405        binomial_mul::<Self, Self, Self, 2>(a, b, res, <Self as BinomiallyExtendable<2>>::W);
406    }
407
408    #[inline]
409    fn ext_square(a: &[Self; 2], res: &mut [Self; 2]) {
410        binomial_square::<Self, Self, 2>(a, res, <Self as BinomiallyExtendable<2>>::W);
411    }
412}
413
414impl BinomiallyExtendable<2> for Felt {
415    fn binomial_algebra_id() -> Vec<u8> {
416        <Goldilocks as BinomiallyExtendable<2>>::binomial_algebra_id()
417    }
418
419    const W: Self = Self(<Goldilocks as BinomiallyExtendable<2>>::W);
420
421    const DTH_ROOT: Self = Self(<Goldilocks as BinomiallyExtendable<2>>::DTH_ROOT);
422
423    const EXT_GENERATOR: [Self; 2] = [
424        Self(<Goldilocks as BinomiallyExtendable<2>>::EXT_GENERATOR[0]),
425        Self(<Goldilocks as BinomiallyExtendable<2>>::EXT_GENERATOR[1]),
426    ];
427}
428
429impl HasTwoAdicBinomialExtension<2> for Felt {
430    const EXT_TWO_ADICITY: usize = <Goldilocks as HasTwoAdicBinomialExtension<2>>::EXT_TWO_ADICITY;
431
432    #[inline]
433    fn ext_two_adic_generator(bits: usize) -> [Self; 2] {
434        let [a, b] = <Goldilocks as HasTwoAdicBinomialExtension<2>>::ext_two_adic_generator(bits);
435        [Self(a), Self(b)]
436    }
437}
438
439impl ExtensionAlgebra<Self, 5, Binomial<Self>> for Felt {
440    #[inline]
441    fn ext_mul(a: &[Self; 5], b: &[Self; 5], res: &mut [Self; 5]) {
442        binomial_mul::<Self, Self, Self, 5>(a, b, res, <Self as BinomiallyExtendable<5>>::W);
443    }
444
445    #[inline]
446    fn ext_square(a: &[Self; 5], res: &mut [Self; 5]) {
447        binomial_square::<Self, Self, 5>(a, res, <Self as BinomiallyExtendable<5>>::W);
448    }
449}
450
451impl BinomiallyExtendable<5> for Felt {
452    fn binomial_algebra_id() -> Vec<u8> {
453        <Goldilocks as BinomiallyExtendable<5>>::binomial_algebra_id()
454    }
455
456    const W: Self = Self(<Goldilocks as BinomiallyExtendable<5>>::W);
457
458    const DTH_ROOT: Self = Self(<Goldilocks as BinomiallyExtendable<5>>::DTH_ROOT);
459
460    const EXT_GENERATOR: [Self; 5] = [
461        Self(<Goldilocks as BinomiallyExtendable<5>>::EXT_GENERATOR[0]),
462        Self(<Goldilocks as BinomiallyExtendable<5>>::EXT_GENERATOR[1]),
463        Self(<Goldilocks as BinomiallyExtendable<5>>::EXT_GENERATOR[2]),
464        Self(<Goldilocks as BinomiallyExtendable<5>>::EXT_GENERATOR[3]),
465        Self(<Goldilocks as BinomiallyExtendable<5>>::EXT_GENERATOR[4]),
466    ];
467}
468
469impl HasTwoAdicBinomialExtension<5> for Felt {
470    const EXT_TWO_ADICITY: usize = <Goldilocks as HasTwoAdicBinomialExtension<5>>::EXT_TWO_ADICITY;
471
472    #[inline]
473    fn ext_two_adic_generator(bits: usize) -> [Self; 5] {
474        let ext_generator =
475            <Goldilocks as HasTwoAdicBinomialExtension<5>>::ext_two_adic_generator(bits);
476        [
477            Self(ext_generator[0]),
478            Self(ext_generator[1]),
479            Self(ext_generator[2]),
480            Self(ext_generator[3]),
481            Self(ext_generator[4]),
482        ]
483    }
484}
485
486impl RawDataSerializable for Felt {
487    impl_raw_serializable_primefield64!();
488}
489
490impl Distribution<Felt> for StandardUniform {
491    #[inline]
492    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Felt {
493        let inner = <StandardUniform as Distribution<Goldilocks>>::sample(self, rng);
494        Felt(inner)
495    }
496}
497
498impl UniformSamplingField for Felt {
499    const MAX_SINGLE_SAMPLE_BITS: usize =
500        <Goldilocks as UniformSamplingField>::MAX_SINGLE_SAMPLE_BITS;
501    const SAMPLING_BITS_M: [u64; 64] = <Goldilocks as UniformSamplingField>::SAMPLING_BITS_M;
502}
503
504impl InjectiveMonomial<7> for Felt {}
505
506impl PermutationMonomial<7> for Felt {
507    #[inline]
508    fn injective_exp_root_n(&self) -> Self {
509        Self(self.0.injective_exp_root_n())
510    }
511}
512
513// CONVERSIONS
514// ================================================================================================
515
516impl From<u8> for Felt {
517    fn from(int: u8) -> Self {
518        Self::from_u8(int)
519    }
520}
521
522impl From<u16> for Felt {
523    fn from(int: u16) -> Self {
524        Self::from_u16(int)
525    }
526}
527
528impl From<u32> for Felt {
529    fn from(int: u32) -> Self {
530        Self::from_u32(int)
531    }
532}
533
534impl TryFrom<u64> for Felt {
535    type Error = FeltFromIntError;
536
537    fn try_from(int: u64) -> Result<Felt, Self::Error> {
538        Felt::new(int)
539    }
540}
541
542#[derive(Debug, thiserror::Error)]
543#[error("integer {0} is equal to or exceeds the felt modulus {modulus}", modulus = Felt::ORDER)]
544pub struct FeltFromIntError(u64);
545
546impl FeltFromIntError {
547    /// Returns the integer for which the conversion failed.
548    pub fn as_u64(&self) -> u64 {
549        self.0
550    }
551}
552
553impl From<Goldilocks> for Felt {
554    #[inline]
555    fn from(value: Goldilocks) -> Self {
556        Self(value)
557    }
558}
559
560impl From<Felt> for Goldilocks {
561    #[inline]
562    fn from(value: Felt) -> Self {
563        value.0
564    }
565}
566
567// ARITHMETIC OPERATIONS
568// ================================================================================================
569
570impl Add for Felt {
571    type Output = Self;
572
573    #[inline]
574    fn add(self, other: Self) -> Self {
575        Self(self.0 + other.0)
576    }
577}
578
579impl AddAssign for Felt {
580    #[inline]
581    fn add_assign(&mut self, other: Self) {
582        *self = *self + other;
583    }
584}
585
586impl Sub for Felt {
587    type Output = Self;
588
589    #[inline]
590    fn sub(self, other: Self) -> Self {
591        Self(self.0 - other.0)
592    }
593}
594
595impl SubAssign for Felt {
596    #[inline]
597    fn sub_assign(&mut self, other: Self) {
598        *self = *self - other;
599    }
600}
601
602impl Mul for Felt {
603    type Output = Self;
604
605    #[inline]
606    fn mul(self, other: Self) -> Self {
607        Self(self.0 * other.0)
608    }
609}
610
611impl MulAssign for Felt {
612    #[inline]
613    fn mul_assign(&mut self, other: Self) {
614        *self = *self * other;
615    }
616}
617
618impl Div for Felt {
619    type Output = Self;
620
621    #[inline]
622    fn div(self, other: Self) -> Self {
623        Self(self.0 / other.0)
624    }
625}
626
627impl DivAssign for Felt {
628    #[inline]
629    fn div_assign(&mut self, other: Self) {
630        *self = *self / other;
631    }
632}
633
634impl Neg for Felt {
635    type Output = Self;
636
637    #[inline]
638    fn neg(self) -> Self {
639        Self(-self.0)
640    }
641}
642
643impl Sum for Felt {
644    #[inline]
645    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
646        Self(iter.map(|x| x.0).sum())
647    }
648}
649
650impl<'a> Sum<&'a Felt> for Felt {
651    #[inline]
652    fn sum<I: Iterator<Item = &'a Felt>>(iter: I) -> Self {
653        Self(iter.map(|x| x.0).sum())
654    }
655}
656
657impl Product for Felt {
658    #[inline]
659    fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
660        Self(iter.map(|x| x.0).product())
661    }
662}
663
664impl<'a> Product<&'a Felt> for Felt {
665    #[inline]
666    fn product<I: Iterator<Item = &'a Felt>>(iter: I) -> Self {
667        Self(iter.map(|x| x.0).product())
668    }
669}
670
671// EQUALITY AND COMPARISON OPERATIONS
672// ================================================================================================
673
674impl PartialEq for Felt {
675    #[inline]
676    fn eq(&self, other: &Self) -> bool {
677        self.0 == other.0
678    }
679}
680
681impl PartialEq<Goldilocks> for Felt {
682    #[inline]
683    fn eq(&self, other: &Goldilocks) -> bool {
684        self.0 == *other
685    }
686}
687
688impl Eq for Felt {}
689
690impl PartialOrd for Felt {
691    #[inline]
692    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
693        Some(self.cmp(other))
694    }
695}
696
697impl Ord for Felt {
698    #[inline]
699    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
700        self.0.cmp(&other.0)
701    }
702}
703
704// SERIALIZATION
705// ================================================================================================
706
707impl Serializable for Felt {
708    fn write_into<W: ByteWriter>(&self, target: &mut W) {
709        target.write_u64(self.as_canonical_u64());
710    }
711
712    fn get_size_hint(&self) -> usize {
713        size_of::<u64>()
714    }
715}
716
717impl Deserializable for Felt {
718    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
719        let value = source.read_u64()?;
720        Self::from_canonical_checked(value).ok_or_else(|| {
721            DeserializationError::InvalidValue(format!("value {value} is not a valid felt"))
722        })
723    }
724}
725
726// ARBITRARY (proptest)
727// ================================================================================================
728
729#[cfg(all(any(test, feature = "arbitrary"), not(all(target_family = "wasm", miden))))]
730mod arbitrary {
731    use proptest::prelude::*;
732
733    use super::Felt;
734
735    impl Arbitrary for Felt {
736        type Parameters = ();
737        type Strategy = BoxedStrategy<Self>;
738
739        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
740            prop_oneof![4 => arb_felt_canonical(), 1 => arb_felt_noncanonical()].boxed()
741        }
742    }
743
744    /// Boundary values of the canonical regime, hit by construction: zero,
745    /// one, both sides of the 2^32 seam, and the two elements adjacent to
746    /// the modulus. The edge index derives from `r / 8` — a component
747    /// independent of the branch gate `r % 8` (the run-#84 correlation
748    /// lesson).
749    pub(super) const CANONICAL_EDGES: [u64; 6] =
750        [0, 1, (1 << 32) - 1, 1 << 32, Felt::ORDER - 2, Felt::ORDER - 1];
751
752    /// Generates field elements with canonical representations: ~7/8 of
753    /// draws are uniform over `[0, ORDER)` and ~1/8 are boundary values
754    /// hit by construction (uniform sampling leaves them measure-zero,
755    /// which under-covers every wire test's Felt fields). The branch gate
756    /// and the value draw are INDEPENDENT components — deriving both from
757    /// one `u64` would remove every canonical value congruent to the gate
758    /// class from the generator's range (the run-#84 correlation lesson at
759    /// full depth).
760    pub fn arb_felt_canonical() -> impl Strategy<Value = Felt> {
761        (any::<u64>(), any::<u64>()).prop_map(|(gate, value)| {
762            if gate % 8 == 7 {
763                Felt::new_unchecked(CANONICAL_EDGES[(gate / 8) as usize % CANONICAL_EDGES.len()])
764            } else {
765                Felt::new_unchecked(value % Felt::ORDER)
766            }
767        })
768    }
769
770    /// Boundary values of the non-canonical regime: the first and last
771    /// non-canonical representations (ORDER aliases canonical zero;
772    /// u64::MAX is the largest).
773    pub(super) const NONCANONICAL_EDGES: [u64; 3] = [Felt::ORDER, u64::MAX - 1, u64::MAX];
774
775    /// Generates field elements with non-canonical representations: ~3/4
776    /// uniform over `[ORDER, u64::MAX]` and ~1/4 boundary values, with the
777    /// same independent gate/value components as the canonical adapter.
778    pub fn arb_felt_noncanonical() -> impl Strategy<Value = Felt> {
779        (any::<u64>(), any::<u64>()).prop_map(|(gate, value)| {
780            if gate % 4 == 3 {
781                Felt::new_unchecked(
782                    NONCANONICAL_EDGES[(gate / 4) as usize % NONCANONICAL_EDGES.len()],
783                )
784            } else {
785                Felt::new_unchecked(Felt::ORDER + value % (u64::MAX - Felt::ORDER + 1))
786            }
787        })
788    }
789}
790
791#[cfg(all(any(test, feature = "arbitrary"), not(all(target_family = "wasm", miden))))]
792pub use arbitrary::{arb_felt_canonical, arb_felt_noncanonical};
793
794#[cfg(all(test, not(all(target_family = "wasm", miden))))]
795mod arbitrary_boundary_tests {
796    use proptest::{
797        strategy::{Strategy, ValueTree},
798        test_runner::TestRunner,
799    };
800
801    use super::{
802        Felt,
803        arbitrary::{
804            CANONICAL_EDGES, NONCANONICAL_EDGES, arb_felt_canonical, arb_felt_noncanonical,
805        },
806    };
807
808    /// The boundary values are reachable BY CONSTRUCTION — pin it
809    /// empirically so a future refactor of the branch gating cannot
810    /// silently drop edges (the run-#84 correlation regression).
811    #[test]
812    fn boundary_values_are_reached_by_construction() {
813        let mut runner = TestRunner::deterministic();
814        let samples = 10_000;
815
816        let mut canonical_hits = [0usize; CANONICAL_EDGES.len()];
817        for _ in 0..samples {
818            let felt = arb_felt_canonical().new_tree(&mut runner).unwrap().current();
819            for (i, edge) in CANONICAL_EDGES.iter().enumerate() {
820                if super::raw_felt_u64(felt) == *edge {
821                    canonical_hits[i] += 1;
822                }
823            }
824        }
825        for (edge, hits) in CANONICAL_EDGES.iter().zip(canonical_hits) {
826            assert!(hits > 0, "canonical boundary {edge} never sampled");
827        }
828
829        let mut noncanonical_hits = [0usize; NONCANONICAL_EDGES.len()];
830        for _ in 0..samples {
831            let felt = arb_felt_noncanonical().new_tree(&mut runner).unwrap().current();
832            for (i, edge) in NONCANONICAL_EDGES.iter().enumerate() {
833                if super::raw_felt_u64(felt) == *edge {
834                    noncanonical_hits[i] += 1;
835                }
836            }
837        }
838        for (edge, hits) in NONCANONICAL_EDGES.iter().zip(noncanonical_hits) {
839            assert!(hits > 0, "non-canonical boundary {edge} never sampled");
840        }
841
842        // The canonical adapter never leaves the canonical regime.
843        for _ in 0..samples {
844            let felt = arb_felt_canonical().new_tree(&mut runner).unwrap().current();
845            assert!(
846                super::raw_felt_u64(felt) < Felt::ORDER,
847                "canonical adapter emitted a non-canonical element",
848            );
849        }
850
851        // Every residue class mod 8 must occur among NON-boundary draws —
852        // pinning that the gate and the value are independent components
853        // (deriving both from one u64 removes the gate class from the
854        // generator's range, the run-#430 regression).
855        let mut residue_seen = [false; 8];
856        for _ in 0..samples {
857            let felt = arb_felt_canonical().new_tree(&mut runner).unwrap().current();
858            let raw = super::raw_felt_u64(felt);
859            if !CANONICAL_EDGES.contains(&raw) {
860                residue_seen[(raw % 8) as usize] = true;
861            }
862        }
863        assert!(
864            residue_seen.iter().all(|&seen| seen),
865            "a residue class mod 8 is missing from non-boundary draws: {residue_seen:?}",
866        );
867    }
868}