1use alloc::vec::Vec;
17
18use p3_challenger::{
19 CanFinalizeDigest, CanObserve, CanSample, CanSampleBits, FieldChallenger, GrindingChallenger,
20};
21use p3_symmetric::{Hash, MerkleCap};
22
23use super::{
24 BLOCK_LEN, DIGEST_WIDTH, Eidos, PACKED_LANES, PackedBlock, PackedChainingValue, PackedFelt,
25};
26use crate::{
27 Felt, Word, ZERO,
28 field::{BasedVectorSpace, PrimeField64},
29 parallel::*,
30};
31
32const TRANSITION_TAG_BASE: u32 = 1;
34
35const SQUEEZE_TAG: Felt = Felt::new_unchecked((TRANSITION_TAG_BASE + BLOCK_LEN as u32) as u64);
37
38#[derive(Debug, Copy, Clone, Eq, PartialEq)]
39enum EidosChallengerMode {
40 Absorbing,
41 Squeezing,
42}
43
44#[derive(Clone, Debug)]
50pub struct EidosChallenger {
51 cv: Word,
52 buffer: [Felt; BLOCK_LEN],
53 buffer_len: usize,
54 mode: EidosChallengerMode,
55 counter: u32,
56 output_word: Word,
57 output_len: usize,
58}
59
60impl EidosChallenger {
61 pub fn new(initial_cv: Word) -> Self {
66 Self {
67 cv: initial_cv,
68 buffer: [ZERO; BLOCK_LEN],
69 buffer_len: 0,
70 mode: EidosChallengerMode::Absorbing,
71 counter: 0,
72 output_word: Word::default(),
73 output_len: 0,
74 }
75 }
76
77 pub fn cv(&self) -> Word {
79 self.cv
80 }
81
82 pub fn observe_felt(&mut self, value: Felt) {
84 self.enter_absorbing_mode();
85
86 self.buffer[self.buffer_len] = value;
87 self.buffer_len += 1;
88 if self.buffer_len == BLOCK_LEN {
89 self.compress_pending_buffer();
90 }
91 }
92
93 pub fn sample_felt(&mut self) -> Felt {
98 if self.output_len == 0 {
99 self.refill_output_word();
100 }
101
102 let idx = DIGEST_WIDTH - self.output_len;
103 self.output_len -= 1;
104 self.output_word[idx]
105 }
106
107 pub fn sample_bits(&mut self, bits: usize) -> usize {
111 assert!(bits < usize::BITS as usize, "bit count must be valid");
112 assert!((1u64 << bits) < Felt::ORDER_U64);
113
114 let value = self.sample_felt().as_canonical_u64() as usize;
115 value & ((1usize << bits) - 1)
116 }
117
118 pub fn squeeze_word(&mut self) -> Word {
127 assert_eq!(self.output_len, 0, "squeeze_word requires word-aligned output");
128 self.refill_output_word();
129 let output = self.output_word;
130 self.output_len = 0;
131 output
132 }
133
134 fn absorb_full_block(&mut self, block: [Felt; BLOCK_LEN]) {
135 assert_eq!(self.buffer_len, 0, "full-block absorb requires an empty scalar buffer");
136 self.enter_absorbing_mode();
137 self.cv = Eidos::compress(self.cv, block);
138 }
139
140 fn enter_absorbing_mode(&mut self) {
141 self.mode = EidosChallengerMode::Absorbing;
142 self.counter = 0;
143 self.output_len = 0;
144 }
145
146 fn compress_pending_buffer(&mut self) {
147 debug_assert_eq!(self.buffer_len, BLOCK_LEN);
148 self.cv = Eidos::compress(self.cv, self.buffer);
149 self.buffer = [ZERO; BLOCK_LEN];
150 self.buffer_len = 0;
151 }
152
153 fn refill_output_word(&mut self) {
154 match self.mode {
155 EidosChallengerMode::Absorbing => {
156 let tag = transition_tag(self.buffer_len);
157 let block = self.buffer;
158 self.cv = Eidos::compress(tweak_cv(self.cv, tag), block);
159 self.buffer = [ZERO; BLOCK_LEN];
160 self.buffer_len = 0;
161 self.mode = EidosChallengerMode::Squeezing;
162 self.counter = 0;
163 },
164 EidosChallengerMode::Squeezing => {
165 self.counter =
166 self.counter.checked_add(1).expect("squeeze counter exhausted before absorb");
167 self.cv =
168 Eidos::compress(tweak_cv(self.cv, SQUEEZE_TAG), counter_block(self.counter));
169 },
170 }
171
172 self.output_word = self.cv;
173 self.output_len = DIGEST_WIDTH;
174 }
175}
176
177#[derive(Clone, Debug)]
183pub struct MidenEidosChallenger {
184 inner: EidosChallenger,
185}
186
187impl MidenEidosChallenger {
188 pub fn new(transcript_init_cv: Word, relation_digest: Word) -> Self {
190 let mut inner = EidosChallenger::new(transcript_init_cv);
191 inner.absorb_full_block([
192 relation_digest[0],
193 relation_digest[1],
194 relation_digest[2],
195 relation_digest[3],
196 ZERO,
197 ZERO,
198 ZERO,
199 ZERO,
200 ]);
201 Self { inner }
202 }
203}
204
205impl<T> CanObserve<T> for MidenEidosChallenger
206where
207 EidosChallenger: CanObserve<T>,
208{
209 fn observe(&mut self, value: T) {
210 self.inner.observe(value);
211 }
212}
213
214impl<T> CanSample<T> for MidenEidosChallenger
215where
216 EidosChallenger: CanSample<T>,
217{
218 fn sample(&mut self) -> T {
219 self.inner.sample()
220 }
221}
222
223impl CanSampleBits<usize> for MidenEidosChallenger {
224 fn sample_bits(&mut self, bits: usize) -> usize {
225 self.inner.sample_bits(bits)
226 }
227}
228
229impl FieldChallenger<Felt> for MidenEidosChallenger {}
230
231impl GrindingChallenger for MidenEidosChallenger {
232 type Witness = Felt;
233
234 fn grind(&mut self, bits: usize) -> Self::Witness {
235 self.inner.grind(bits)
236 }
237}
238
239impl CanFinalizeDigest for MidenEidosChallenger {
240 type Digest = [Felt; DIGEST_WIDTH];
241
242 fn finalize(self) -> Self::Digest {
243 self.inner.finalize()
244 }
245}
246
247impl CanObserve<Felt> for EidosChallenger {
248 fn observe(&mut self, value: Felt) {
249 self.observe_felt(value);
250 }
251}
252
253impl<const N: usize> CanObserve<[Felt; N]> for EidosChallenger {
254 fn observe(&mut self, values: [Felt; N]) {
255 for value in values {
256 self.observe_felt(value);
257 }
258 }
259}
260
261impl<const N: usize> CanObserve<Hash<Felt, Felt, N>> for EidosChallenger {
262 fn observe(&mut self, values: Hash<Felt, Felt, N>) {
263 for value in values {
264 self.observe_felt(value);
265 }
266 }
267}
268
269impl<const N: usize> CanObserve<Hash<Felt, u64, N>> for EidosChallenger {
270 fn observe(&mut self, values: Hash<Felt, u64, N>) {
271 for value in values {
272 self.observe_felt(Felt::new_unchecked(value));
273 }
274 }
275}
276
277impl<const N: usize> CanObserve<&MerkleCap<Felt, [Felt; N]>> for EidosChallenger {
278 fn observe(&mut self, cap: &MerkleCap<Felt, [Felt; N]>) {
279 for digest in cap.roots() {
280 for &value in digest {
281 self.observe_felt(value);
282 }
283 }
284 }
285}
286
287impl<const N: usize> CanObserve<MerkleCap<Felt, [Felt; N]>> for EidosChallenger {
288 fn observe(&mut self, cap: MerkleCap<Felt, [Felt; N]>) {
289 self.observe(&cap);
290 }
291}
292
293impl<const N: usize> CanObserve<&MerkleCap<Felt, [u64; N]>> for EidosChallenger {
294 fn observe(&mut self, cap: &MerkleCap<Felt, [u64; N]>) {
295 for digest in cap.roots() {
296 for &value in digest {
297 self.observe_felt(Felt::new_unchecked(value));
298 }
299 }
300 }
301}
302
303impl<const N: usize> CanObserve<MerkleCap<Felt, [u64; N]>> for EidosChallenger {
304 fn observe(&mut self, cap: MerkleCap<Felt, [u64; N]>) {
305 self.observe(&cap);
306 }
307}
308
309impl CanObserve<Vec<Vec<Felt>>> for EidosChallenger {
310 fn observe(&mut self, rows: Vec<Vec<Felt>>) {
311 for values in rows {
312 for value in values {
313 self.observe_felt(value);
314 }
315 }
316 }
317}
318
319impl<EF: BasedVectorSpace<Felt>> CanSample<EF> for EidosChallenger {
320 fn sample(&mut self) -> EF {
321 EF::from_basis_coefficients_fn(|_| self.sample_felt())
322 }
323}
324
325impl CanSampleBits<usize> for EidosChallenger {
326 fn sample_bits(&mut self, bits: usize) -> usize {
327 EidosChallenger::sample_bits(self, bits)
328 }
329}
330
331impl FieldChallenger<Felt> for EidosChallenger {}
332
333impl GrindingChallenger for EidosChallenger {
334 type Witness = Felt;
335
336 fn grind(&mut self, bits: usize) -> Self::Witness {
337 assert!(bits < usize::BITS as usize, "bit count must be valid");
338 assert!((1u64 << bits) < Felt::ORDER_U64);
339
340 if bits == 0 {
341 return ZERO;
342 }
343
344 let mask = (1u64 << bits) - 1;
345 let cv = self.cv;
346 let buffer = self.buffer;
347 let buffer_len = self.buffer_len;
348
349 let witness = (0..WITNESS_BATCHES)
353 .into_par_iter()
354 .map(|batch| {
355 let candidates = witness_batch(batch);
356 let accepted = check_witness_packed(cv, buffer, buffer_len, candidates, mask);
357 (0..PACKED_LANES).find(|&lane| accepted[lane]).map(|lane| candidates[lane])
358 })
359 .find_any(Option::is_some)
360 .flatten()
361 .expect("failed to find proof-of-work witness");
362
363 assert!(self.check_witness(bits, witness));
364 witness
365 }
366}
367
368const WITNESS_BATCHES: u64 = Felt::ORDER_U64.div_ceil(PACKED_LANES as u64);
370
371#[inline]
372fn witness_batch(batch: u64) -> PackedFelt {
373 debug_assert!(batch < WITNESS_BATCHES);
374
375 let base = batch * PACKED_LANES as u64;
376 core::array::from_fn(|lane| {
377 let candidate = base + lane as u64;
378 Felt::new_unchecked(candidate.min(Felt::ORDER_U64 - 1))
381 })
382}
383
384fn check_witness_packed(
392 cv: Word,
393 buffer: [Felt; BLOCK_LEN],
394 buffer_len: usize,
395 witnesses: PackedFelt,
396 mask: u64,
397) -> [bool; PACKED_LANES] {
398 debug_assert!(buffer_len < BLOCK_LEN);
399
400 let mut packed_buffer: PackedBlock = core::array::from_fn(|slot| {
402 use core::cmp::Ordering;
403 match slot.cmp(&buffer_len) {
404 Ordering::Less => [buffer[slot]; PACKED_LANES],
405 Ordering::Equal => witnesses,
406 Ordering::Greater => [ZERO; PACKED_LANES],
407 }
408 });
409 let mut packed_cv: PackedChainingValue = core::array::from_fn(|i| [cv[i]; PACKED_LANES]);
410 let mut packed_len = buffer_len + 1;
411
412 if packed_len == BLOCK_LEN {
414 packed_cv = Eidos::compress_packed(packed_cv, packed_buffer);
415 packed_buffer = [[ZERO; PACKED_LANES]; BLOCK_LEN];
416 packed_len = 0;
417 }
418
419 let tag = transition_tag(packed_len);
421 packed_cv[3] = packed_cv[3].map(|word| word + tag);
422 let output = Eidos::compress_packed(packed_cv, packed_buffer);
423
424 core::array::from_fn(|lane| (output[0][lane].as_canonical_u64() & mask) == 0)
426}
427
428impl CanFinalizeDigest for EidosChallenger {
429 type Digest = [Felt; DIGEST_WIDTH];
430
431 fn finalize(mut self) -> Self::Digest {
432 self.output_len = 0;
433 let digest = self.squeeze_word();
434 digest.into()
435 }
436}
437
438fn transition_tag(buffer_len: usize) -> Felt {
439 debug_assert!(buffer_len < BLOCK_LEN);
440 Felt::new_unchecked((TRANSITION_TAG_BASE + buffer_len as u32) as u64)
441}
442
443fn tweak_cv(mut cv: Word, tag: Felt) -> Word {
444 cv[3] += tag;
447 cv
448}
449
450fn counter_block(counter: u32) -> [Felt; BLOCK_LEN] {
451 let mut block = [ZERO; BLOCK_LEN];
452 block[0] = Felt::from_u32(counter);
453 block
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459
460 const TRANSITION_TAG: Felt = Felt::new_unchecked(TRANSITION_TAG_BASE as u64);
461
462 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
463 struct ChallengerSnapshot {
464 cv: Word,
465 mode: EidosChallengerMode,
466 counter: u32,
467 buffer: [Felt; BLOCK_LEN],
468 buffer_len: usize,
469 output_word: Word,
470 output_len: usize,
471 }
472
473 impl ChallengerSnapshot {
474 const SERIALIZED_LEN: usize = 20;
475
476 fn to_canonical_u64s(self) -> [u64; Self::SERIALIZED_LEN] {
477 let mut row = [0u64; Self::SERIALIZED_LEN];
478 row[0] = self.cv[0].as_canonical_u64();
479 row[1] = self.cv[1].as_canonical_u64();
480 row[2] = self.cv[2].as_canonical_u64();
481 row[3] = self.cv[3].as_canonical_u64();
482 row[4] = mode_as_u64(self.mode);
483 row[5] = self.counter as u64;
484 for (i, value) in self.buffer.iter().enumerate() {
485 row[6 + i] = value.as_canonical_u64();
486 }
487 row[14] = self.buffer_len as u64;
488 row[15] = self.output_word[0].as_canonical_u64();
489 row[16] = self.output_word[1].as_canonical_u64();
490 row[17] = self.output_word[2].as_canonical_u64();
491 row[18] = self.output_word[3].as_canonical_u64();
492 row[19] = self.output_len as u64;
493 row
494 }
495 }
496
497 fn felt(value: u64) -> Felt {
498 Felt::new_unchecked(value)
499 }
500
501 fn word(values: [u64; DIGEST_WIDTH]) -> Word {
502 Word::new(values.map(felt))
503 }
504
505 fn snapshot(challenger: &EidosChallenger) -> ChallengerSnapshot {
506 ChallengerSnapshot {
507 cv: challenger.cv,
508 mode: challenger.mode,
509 counter: challenger.counter,
510 buffer: challenger.buffer,
511 buffer_len: challenger.buffer_len,
512 output_word: challenger.output_word,
513 output_len: challenger.output_len,
514 }
515 }
516
517 fn miden_snapshot(challenger: &MidenEidosChallenger) -> ChallengerSnapshot {
518 snapshot(&challenger.inner)
519 }
520
521 const fn mode_as_u64(mode: EidosChallengerMode) -> u64 {
522 match mode {
523 EidosChallengerMode::Absorbing => 0,
524 EidosChallengerMode::Squeezing => 1,
525 }
526 }
527
528 #[test]
529 fn miden_init_absorbs_relation_digest_as_one_full_block() {
530 let init = word([1, 2, 3, 4]);
531 let relation_digest = word([10, 11, 12, 13]);
532
533 let challenger = MidenEidosChallenger::new(init, relation_digest);
534
535 let expected = Eidos::compress(
536 init,
537 [
538 relation_digest[0],
539 relation_digest[1],
540 relation_digest[2],
541 relation_digest[3],
542 ZERO,
543 ZERO,
544 ZERO,
545 ZERO,
546 ],
547 );
548
549 let snapshot = miden_snapshot(&challenger);
550 assert_eq!(snapshot.cv, expected);
551 assert_eq!(snapshot.mode, EidosChallengerMode::Absorbing);
552 assert_eq!(snapshot.counter, 0);
553 assert_eq!(snapshot.buffer_len, 0);
554 assert_eq!(snapshot.output_len, 0);
555 }
556
557 #[test]
558 fn first_squeeze_uses_zero_len_transition_tweak_and_zero_block() {
559 let init = word([1, 2, 3, 4]);
560 let mut challenger = EidosChallenger::new(init);
561
562 let output = challenger.squeeze_word();
563
564 let expected = Eidos::compress(tweak_cv(init, TRANSITION_TAG), [ZERO; BLOCK_LEN]);
565 assert_eq!(output, expected);
566
567 let snapshot = snapshot(&challenger);
568 assert_eq!(snapshot.mode, EidosChallengerMode::Squeezing);
569 assert_eq!(snapshot.counter, 0);
570 assert_eq!(snapshot.output_len, 0);
571 }
572
573 #[test]
574 fn second_squeeze_uses_counter_block_one() {
575 let init = word([1, 2, 3, 4]);
576 let mut challenger = EidosChallenger::new(init);
577
578 let first = challenger.squeeze_word();
579 let second = challenger.squeeze_word();
580
581 let expected = Eidos::compress(tweak_cv(first, SQUEEZE_TAG), counter_block(1));
582 assert_eq!(second, expected);
583 assert_eq!(snapshot(&challenger).counter, 1);
584 }
585
586 #[test]
587 fn scalar_transition_tag_binds_pending_buffer_length() {
588 let init = word([1, 2, 3, 4]);
589 let value = felt(7);
590
591 let mut one = EidosChallenger::new(init);
592 one.observe_felt(value);
593 let one_output = one.squeeze_word();
594
595 let mut two = EidosChallenger::new(init);
596 two.observe_felt(value);
597 two.observe_felt(ZERO);
598 let two_output = two.squeeze_word();
599
600 let expected_one = Eidos::compress(
601 tweak_cv(init, transition_tag(1)),
602 [value, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO],
603 );
604
605 assert_eq!(one_output, expected_one);
606 assert_ne!(one_output, two_output);
607 }
608
609 #[test]
610 fn sample_felt_consumes_squeezed_word_from_the_front() {
611 let init = word([1, 2, 3, 4]);
612
613 let mut word_challenger = EidosChallenger::new(init);
614 let output = word_challenger.squeeze_word();
615
616 let mut scalar_challenger = EidosChallenger::new(init);
617 assert_eq!(scalar_challenger.sample_felt(), output[0]);
618 assert_eq!(scalar_challenger.sample_felt(), output[1]);
619 assert_eq!(scalar_challenger.sample_felt(), output[2]);
620 assert_eq!(scalar_challenger.sample_felt(), output[3]);
621 assert_eq!(snapshot(&scalar_challenger).output_len, 0);
622 }
623
624 #[test]
625 fn sample_bits_returns_requested_width() {
626 let init = word([1, 2, 3, 4]);
627 let mut challenger = EidosChallenger::new(init);
628
629 let sample = challenger.sample_bits(13);
630
631 assert!(sample < (1 << 13));
632 }
633
634 #[test]
635 fn sample_bits_zero_consumes_one_felt() {
636 let init = word([1, 2, 3, 4]);
637 let mut challenger = EidosChallenger::new(init);
638
639 assert_eq!(challenger.sample_bits(0), 0);
640 assert_eq!(snapshot(&challenger).output_len, DIGEST_WIDTH - 1);
641 }
642
643 #[test]
644 fn zero_bit_grinding_and_verification_leave_the_transcript_unchanged() {
645 let mut challenger = EidosChallenger::new(word([1, 2, 3, 4]));
646 challenger.observe_felt(felt(9));
647 let before = snapshot(&challenger);
648
649 assert_eq!(challenger.grind(0), ZERO);
650 assert_eq!(snapshot(&challenger), before);
651
652 let mut verifier = challenger.clone();
653 assert!(verifier.check_witness(0, ZERO));
654 assert_eq!(snapshot(&verifier), before);
655 }
656
657 #[test]
658 fn observe_after_partial_sample_discards_remaining_output() {
659 let init = word([1, 2, 3, 4]);
660 let value = felt(7);
661
662 let mut challenger = EidosChallenger::new(init);
663 let first_output = challenger.squeeze_word();
664 let expected = Eidos::compress(
665 tweak_cv(first_output, transition_tag(1)),
666 [value, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO],
667 );
668
669 let mut challenger = EidosChallenger::new(init);
670 let _ = challenger.sample_felt();
671 challenger.observe_felt(value);
672
673 assert_eq!(challenger.squeeze_word(), expected);
674 }
675
676 #[test]
677 fn finalize_after_partial_sample_uses_fresh_squeeze() {
678 let init = word([1, 2, 3, 4]);
679 let mut challenger = EidosChallenger::new(init);
680
681 let first = challenger.squeeze_word();
682 let expected = Eidos::compress(tweak_cv(first, SQUEEZE_TAG), counter_block(1));
683
684 let mut challenger = EidosChallenger::new(init);
685 let _ = challenger.sample_felt();
686 let finalized = challenger.finalize();
687
688 assert_eq!(Word::new(finalized), expected);
689 }
690
691 #[test]
692 fn snapshot_serialization_order_is_stable() {
693 let mut challenger = EidosChallenger::new(word([1, 2, 3, 4]));
694 challenger.observe_felt(felt(9));
695 challenger.observe_felt(felt(10));
696
697 let row = snapshot(&challenger).to_canonical_u64s();
698
699 assert_eq!(row.len(), ChallengerSnapshot::SERIALIZED_LEN);
700 assert_eq!(row[0..4], [1, 2, 3, 4]);
701 assert_eq!(row[4], 0);
702 assert_eq!(row[5], 0);
703 assert_eq!(row[6..14], [9, 10, 0, 0, 0, 0, 0, 0]);
704 assert_eq!(row[14], 2);
705 assert_eq!(row[15..20], [0, 0, 0, 0, 0]);
706 }
707
708 #[test]
709 fn witness_batches_cover_the_field_boundary_canonically() {
710 let first = witness_batch(0).map(|candidate| candidate.as_canonical_u64());
711 assert_eq!(first, core::array::from_fn(|lane| lane as u64));
712
713 let penultimate =
714 witness_batch(WITNESS_BATCHES - 2).map(|candidate| candidate.as_canonical_u64());
715 let penultimate_base = Felt::ORDER_U64 - 1 - PACKED_LANES as u64;
716 assert_eq!(penultimate, core::array::from_fn(|lane| penultimate_base + lane as u64));
717
718 let last = witness_batch(WITNESS_BATCHES - 1).map(|candidate| candidate.as_canonical_u64());
719 assert_eq!(last, [Felt::ORDER_U64 - 1; PACKED_LANES]);
720 }
721
722 #[test]
727 fn check_witness_packed_matches_scalar_check_witness_across_buffer_lengths() {
728 let bits_values = [1usize, 8, 20];
729
730 for buffer_len in 0..BLOCK_LEN {
731 let mut challenger = EidosChallenger::new(word([5, 6, 7, 8]));
732 for i in 0..buffer_len {
733 challenger.observe_felt(felt(100 + i as u64));
734 }
735 assert_eq!(challenger.buffer_len, buffer_len);
736
737 let witnesses: PackedFelt = core::array::from_fn(|lane| felt(1_000_000 + lane as u64));
738
739 for &bits in &bits_values {
740 let mask = (1u64 << bits) - 1;
741 let accepted = check_witness_packed(
742 challenger.cv,
743 challenger.buffer,
744 challenger.buffer_len,
745 witnesses,
746 mask,
747 );
748
749 for lane in 0..PACKED_LANES {
750 let mut scalar = challenger.clone();
751 let expected = scalar.check_witness(bits, witnesses[lane]);
752 assert_eq!(
753 accepted[lane], expected,
754 "lane {lane} mismatch at buffer_len={buffer_len}, bits={bits}"
755 );
756 }
757 }
758 }
759 }
760
761 #[test]
765 fn grind_finds_a_valid_witness_from_every_buffer_state() {
766 let bits = 4;
767
768 for buffer_len in 0..BLOCK_LEN {
769 let mut challenger = EidosChallenger::new(word([1, 2, 3, 4]));
770 for i in 0..buffer_len {
771 challenger.observe_felt(felt(200 + i as u64));
772 }
773 assert_grind_matches_independent_check(challenger, bits);
774 }
775 }
776
777 #[test]
778 fn grind_from_squeezing_state_matches_independent_check() {
779 let mut challenger = EidosChallenger::new(word([1, 2, 3, 4]));
780 challenger.observe_felt(felt(200));
781 let _ = challenger.sample_felt();
782 assert_eq!(challenger.mode, EidosChallengerMode::Squeezing);
783 assert_ne!(challenger.output_len, 0);
784
785 assert_grind_matches_independent_check(challenger, 4);
786 }
787
788 fn assert_grind_matches_independent_check(mut challenger: EidosChallenger, bits: usize) {
789 let mut verifier = challenger.clone();
790 let witness = challenger.grind(bits);
791
792 assert!(verifier.check_witness(bits, witness));
793 assert_eq!(snapshot(&challenger), snapshot(&verifier));
794 }
795}