1use 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#[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 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 pub const MAX: Self = Self::new_unchecked(Self::ORDER - 1);
77
78 pub const NUM_BYTES: usize = Goldilocks::NUM_BYTES;
80
81 pub fn new(value: u64) -> Result<Self, FeltFromIntError> {
87 Felt::from_canonical_checked(value).ok_or(FeltFromIntError(value))
88 }
89
90 #[inline]
95 pub const fn new_unchecked(value: u64) -> Self {
96 Self(Goldilocks::new(value))
97 }
98
99 #[inline]
101 pub const fn from_u8(value: u8) -> Self {
102 Self::new_unchecked(value as u64)
103 }
104
105 #[inline]
107 pub const fn from_u16(value: u16) -> Self {
108 Self::new_unchecked(value as u64)
109 }
110
111 #[inline]
113 pub const fn from_u32(value: u32) -> Self {
114 Self::new_unchecked(value as u64)
115 }
116
117 #[inline]
119 pub fn double(&self) -> Self {
120 <Self as PrimeCharacteristicRing>::double(self)
121 }
122
123 #[inline]
125 pub fn square(&self) -> Self {
126 <Self as PrimeCharacteristicRing>::square(self)
127 }
128
129 #[inline]
131 pub fn exp_u64(&self, power: u64) -> Self {
132 <Self as PrimeCharacteristicRing>::exp_u64(self, power)
133 }
134
135 #[inline]
137 pub fn exp_const_u64<const POWER: u64>(&self) -> Self {
138 <Self as PrimeCharacteristicRing>::exp_const_u64::<POWER>(self)
139 }
140
141 #[inline]
144 pub fn as_canonical_u64(&self) -> u64 {
145 <Self as PrimeField64>::as_canonical_u64(self)
146 }
147
148 #[inline]
151 pub fn as_canonical_u64_ct(&self) -> u64 {
152 let raw = raw_felt_u64(*self);
153 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 unsafe { core::mem::transmute_copy(&value) }
170}
171
172#[inline]
178fn felts_as_goldilocks_slice(s: &[Felt]) -> &[Goldilocks] {
179 unsafe { core::slice::from_raw_parts(s.as_ptr().cast::<Goldilocks>(), s.len()) }
181}
182
183#[inline]
189fn felts_as_goldilocks_array<const N: usize>(a: &[Felt; N]) -> &[Goldilocks; N] {
190 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
215impl 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 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
399impl 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
513impl 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 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
567impl 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
671impl 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
704impl 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#[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 pub(super) const CANONICAL_EDGES: [u64; 6] =
750 [0, 1, (1 << 32) - 1, 1 << 32, Felt::ORDER - 2, Felt::ORDER - 1];
751
752 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 pub(super) const NONCANONICAL_EDGES: [u64; 3] = [Felt::ORDER, u64::MAX - 1, u64::MAX];
774
775 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 #[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 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 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}