1use alloc::vec::Vec;
62use core::marker::PhantomData;
63
64use p3_air::BaseAir;
65use p3_challenger::fs::{
66 DomainSeparator, FieldToFieldCodec, FieldUnit, Hierarchy, Interaction, InteractionPattern,
67 Kind, Label, Length, ProverState, VerifierState,
68};
69use p3_challenger::{CanObserve, CanSample, CanSampleBits, GrindingChallenger};
70use p3_field::{ExtensionField, PrimeField64};
71use thiserror::Error;
72
73const VERSION: u8 = 1;
75
76const NAME: &[u8] = b"p3-uni-stark";
78
79const TRACE_COMMITMENT: &str = "trace_commitment";
81
82const PREPROCESSED_COMMITMENT: &str = "preprocessed_commitment";
84
85const PUBLIC_VALUES: &str = "public_values";
87
88const ALPHA: &str = "alpha";
90
91const QUOTIENT_COMMITMENT: &str = "quotient_commitment";
93
94const RANDOM_COMMITMENT: &str = "random_commitment";
96
97const OOD_POW: &str = "ood_pow";
99
100const ZETA: &str = "zeta";
102
103const OPENING_ARGUMENT: &str = "opening_argument";
105
106type Alphabet<F> = FieldUnit<F>;
108
109struct OpeningArgument;
114
115#[derive(Clone, Debug, PartialEq, Eq)]
122pub struct StarkShape {
123 pub log_ext_degree: usize,
125 pub log_degree: usize,
127 pub main_width: usize,
129 pub preprocessed_width: usize,
131 pub num_public_values: usize,
133 pub num_periodic_columns: usize,
135 pub num_quotient_chunks: usize,
137 pub opens_main_next_row: bool,
139 pub opens_preprocessed_next_row: bool,
141 pub has_randomization: bool,
143 pub ood_pow_bits: usize,
145}
146
147impl StarkShape {
148 #[must_use]
170 pub fn new<F, A>(
171 air: &A,
172 preprocessed_width: usize,
173 log_ext_degree: usize,
174 log_degree: usize,
175 num_quotient_chunks: usize,
176 has_randomization: bool,
177 ood_pow_bits: usize,
178 ) -> Self
179 where
180 A: BaseAir<F> + ?Sized,
181 {
182 Self {
183 log_ext_degree,
184 log_degree,
185 main_width: air.width(),
186 preprocessed_width,
187 num_public_values: air.num_public_values(),
188 num_periodic_columns: air.num_periodic_columns(),
189 num_quotient_chunks,
190 opens_main_next_row: !air.main_next_row_columns().is_empty(),
191 opens_preprocessed_next_row: !air.preprocessed_next_row_columns().is_empty(),
192 has_randomization,
193 ood_pow_bits,
194 }
195 }
196
197 #[must_use]
204 pub fn pattern<F, EF>(&self) -> InteractionPattern
205 where
206 F: PrimeField64,
207 EF: ExtensionField<F>,
208 {
209 let mut steps = Vec::with_capacity(9);
211
212 steps.push(Interaction::opaque(
214 Hierarchy::Atomic,
215 Kind::Message,
216 TRACE_COMMITMENT,
217 Length::Scalar,
218 ));
219
220 if self.preprocessed_width > 0 {
222 steps.push(Interaction::opaque(
223 Hierarchy::Atomic,
224 Kind::Message,
225 PREPROCESSED_COMMITMENT,
226 Length::Scalar,
227 ));
228 }
229
230 steps.push(Interaction::algebra::<F, F>(
234 Hierarchy::Atomic,
235 Kind::Public,
236 PUBLIC_VALUES,
237 Length::Fixed(self.num_public_values),
238 ));
239
240 steps.push(Interaction::algebra::<F, EF>(
242 Hierarchy::Atomic,
243 Kind::Challenge,
244 ALPHA,
245 Length::Scalar,
246 ));
247
248 steps.push(Interaction::opaque(
250 Hierarchy::Atomic,
251 Kind::Message,
252 QUOTIENT_COMMITMENT,
253 Length::Scalar,
254 ));
255
256 if self.has_randomization {
258 steps.push(Interaction::opaque(
259 Hierarchy::Atomic,
260 Kind::Message,
261 RANDOM_COMMITMENT,
262 Length::Scalar,
263 ));
264 }
265
266 if self.ood_pow_bits > 0 {
268 steps.push(Interaction::algebra::<F, F>(
269 Hierarchy::Atomic,
270 Kind::Pow,
271 OOD_POW,
272 Length::Fixed(self.ood_pow_bits),
273 ));
274 }
275
276 steps.push(Interaction::algebra::<F, EF>(
278 Hierarchy::Atomic,
279 Kind::Challenge,
280 ZETA,
281 Length::Scalar,
282 ));
283
284 steps.push(Interaction::marker::<OpeningArgument>(
289 Hierarchy::Begin,
290 Kind::Protocol,
291 OPENING_ARGUMENT,
292 ));
293 steps.push(Interaction::marker::<OpeningArgument>(
294 Hierarchy::End,
295 Kind::Protocol,
296 OPENING_ARGUMENT,
297 ));
298
299 InteractionPattern::new(steps).expect("one matched bracket is always well formed")
300 }
301
302 #[must_use]
307 pub fn domain_separator<F, EF>(&self) -> DomainSeparator<Alphabet<F>>
308 where
309 F: PrimeField64,
310 EF: ExtensionField<F>,
311 {
312 let mut separator = DomainSeparator::new(VERSION, NAME, self.pattern::<F, EF>());
313
314 for value in [
325 self.log_ext_degree,
326 self.log_degree,
327 self.main_width,
328 self.preprocessed_width,
329 self.num_periodic_columns,
330 self.num_quotient_chunks,
331 ] {
332 separator.instance(&(value as u64).to_be_bytes());
333 }
334
335 for flag in [self.opens_main_next_row, self.opens_preprocessed_next_row] {
337 separator.instance(&u64::from(flag).to_be_bytes());
338 }
339
340 separator
341 }
342}
343
344pub struct StarkProverTranscript<'a, C, F: PrimeField64, EF> {
351 state: ProverState<&'a mut C, Alphabet<F>>,
353 shape: StarkShape,
355 _ef: PhantomData<EF>,
357}
358
359impl<'a, C, F, EF> StarkProverTranscript<'a, C, F, EF>
360where
361 F: PrimeField64,
362 EF: ExtensionField<F>,
363 C: CanObserve<F> + CanSample<F> + CanSampleBits<usize> + GrindingChallenger<Witness = F>,
364{
365 pub fn new(challenger: &'a mut C, shape: StarkShape) -> Self {
367 let separator = shape.domain_separator::<F, EF>();
368 Self {
369 state: ProverState::new(challenger, &separator),
370 shape,
371 _ef: PhantomData,
372 }
373 }
374
375 pub fn constraint_phase<Com>(
391 &mut self,
392 trace_commitment: Com,
393 preprocessed_commitment: Option<Com>,
394 public_values: &[F],
395 ) -> EF
396 where
397 Com: Clone,
398 C: CanObserve<Com>,
399 {
400 self.state
401 .observe_opaque(TRACE_COMMITMENT, trace_commitment);
402
403 if let Some(commitment) = preprocessed_commitment {
404 self.state
405 .observe_opaque(PREPROCESSED_COMMITMENT, commitment);
406 }
407
408 self.state
409 .add_public_scalars::<F, FieldToFieldCodec<F>>(PUBLIC_VALUES, public_values);
410
411 self.state
412 .challenge_extension::<F, EF, FieldToFieldCodec<F>>(ALPHA)
413 .into_inner()
414 }
415
416 pub fn ood_phase<Com>(
432 &mut self,
433 quotient_commitment: Com,
434 random_commitment: Option<Com>,
435 ) -> (EF, Option<F>)
436 where
437 Com: Clone,
438 C: CanObserve<Com>,
439 {
440 self.state
441 .observe_opaque(QUOTIENT_COMMITMENT, quotient_commitment);
442
443 if let Some(commitment) = random_commitment {
444 self.state.observe_opaque(RANDOM_COMMITMENT, commitment);
445 }
446
447 let witness = (self.shape.ood_pow_bits > 0)
448 .then(|| self.state.observe_pow(OOD_POW, self.shape.ood_pow_bits));
449
450 let zeta = self
451 .state
452 .challenge_extension::<F, EF, FieldToFieldCodec<F>>(ZETA)
453 .into_inner();
454
455 (zeta, witness)
456 }
457
458 pub fn delegate<R>(&mut self, run: impl FnOnce(&mut C) -> R) -> R {
466 self.state
467 .begin_protocol::<OpeningArgument>(OPENING_ARGUMENT);
468 let output = run(self.state.challenger_mut());
469 self.state.end_protocol::<OpeningArgument>(OPENING_ARGUMENT);
470 output
471 }
472
473 pub fn abort(&mut self) {
475 self.state.abort();
476 }
477
478 pub fn finish(self) {
484 assert!(
485 self.state.finalize().is_empty(),
486 "a uni-STARK carries every value in its own proof",
487 );
488 }
489}
490
491pub struct StarkVerifierTranscript<'a, C, F: PrimeField64, EF> {
495 state: VerifierState<'static, &'a mut C, Alphabet<F>>,
499 shape: StarkShape,
501 _ef: PhantomData<EF>,
503}
504
505impl<'a, C, F, EF> StarkVerifierTranscript<'a, C, F, EF>
506where
507 F: PrimeField64,
508 EF: ExtensionField<F>,
509 C: CanObserve<F> + CanSample<F> + CanSampleBits<usize> + GrindingChallenger<Witness = F>,
510{
511 pub fn new(challenger: &'a mut C, shape: StarkShape) -> Self {
513 let separator = shape.domain_separator::<F, EF>();
514 Self {
515 state: VerifierState::new(challenger, &separator, &[]),
516 shape,
517 _ef: PhantomData,
518 }
519 }
520
521 fn optional_commitment<Com>(
528 &mut self,
529 described: bool,
530 label: Label,
531 commitment: Option<Com>,
532 missing: StarkTranscriptFailure,
533 unexpected: StarkTranscriptFailure,
534 ) -> Result<(), StarkTranscriptFailure>
535 where
536 Com: Clone,
537 C: CanObserve<Com>,
538 {
539 match (described, commitment) {
540 (true, Some(commitment)) => {
541 self.state.observe_opaque(label, commitment);
542 Ok(())
543 }
544 (false, None) => Ok(()),
545 (true, None) => {
547 self.state.abort();
548 Err(missing)
549 }
550 (false, Some(_)) => {
551 self.state.abort();
552 Err(unexpected)
553 }
554 }
555 }
556
557 pub fn constraint_phase<Com>(
575 &mut self,
576 trace_commitment: Com,
577 preprocessed_commitment: Option<Com>,
578 public_values: &[F],
579 ) -> Result<EF, StarkTranscriptFailure>
580 where
581 Com: Clone,
582 C: CanObserve<Com>,
583 {
584 self.state
585 .observe_opaque(TRACE_COMMITMENT, trace_commitment);
586
587 self.optional_commitment(
588 self.shape.preprocessed_width > 0,
589 PREPROCESSED_COMMITMENT,
590 preprocessed_commitment,
591 StarkTranscriptFailure::MissingPreprocessedCommitment {
592 width: self.shape.preprocessed_width,
593 },
594 StarkTranscriptFailure::UnexpectedPreprocessedCommitment,
595 )?;
596
597 self.state
598 .observe_public_scalars::<F, FieldToFieldCodec<F>>(PUBLIC_VALUES, public_values);
599
600 Ok(self
601 .state
602 .challenge_extension::<F, EF, FieldToFieldCodec<F>>(ALPHA)
603 .into_inner())
604 }
605
606 pub fn ood_phase<Com>(
620 &mut self,
621 quotient_commitment: Com,
622 random_commitment: Option<Com>,
623 witness: F,
624 ) -> Result<EF, StarkTranscriptFailure>
625 where
626 Com: Clone,
627 C: CanObserve<Com>,
628 {
629 self.state
630 .observe_opaque(QUOTIENT_COMMITMENT, quotient_commitment);
631
632 self.optional_commitment(
633 self.shape.has_randomization,
634 RANDOM_COMMITMENT,
635 random_commitment,
636 StarkTranscriptFailure::MissingRandomCommitment,
637 StarkTranscriptFailure::UnexpectedRandomCommitment,
638 )?;
639
640 if self.shape.ood_pow_bits > 0 {
641 self.state
643 .observe_pow(OOD_POW, self.shape.ood_pow_bits, witness)
644 .map_err(|_| StarkTranscriptFailure::OodPowWitness {
645 bits: self.shape.ood_pow_bits,
646 })?;
647 }
648
649 Ok(self
650 .state
651 .challenge_extension::<F, EF, FieldToFieldCodec<F>>(ZETA)
652 .into_inner())
653 }
654
655 pub fn delegate<R>(&mut self, run: impl FnOnce(&mut C) -> R) -> R {
664 self.state
665 .begin_protocol::<OpeningArgument>(OPENING_ARGUMENT);
666 let output = run(self.state.challenger_mut());
667 self.state.end_protocol::<OpeningArgument>(OPENING_ARGUMENT);
668 output
669 }
670
671 pub fn finish(self) {
677 self.state
678 .finalize()
679 .expect("a uni-STARK reads an empty wire, so no bytes can remain");
680 }
681}
682
683#[derive(Clone, Copy, Debug, PartialEq, Eq, Error)]
685#[non_exhaustive]
686pub enum StarkTranscriptFailure {
687 #[error(
689 "preprocessed commitment step described for a width of {width} arrived with no commitment"
690 )]
691 MissingPreprocessedCommitment {
692 width: usize,
694 },
695 #[error("preprocessed commitment arrived for a run described with a width of 0")]
697 UnexpectedPreprocessedCommitment,
698 #[error("randomization commitment step described by a zero-knowledge PCS arrived with none")]
700 MissingRandomCommitment,
701 #[error("randomization commitment arrived for a run described without one")]
703 UnexpectedRandomCommitment,
704 #[error("out-of-domain PoW witness does not meet the required {bits} bits")]
706 OodPowWitness {
707 bits: usize,
709 },
710}
711
712#[cfg(test)]
713mod tests {
714 use alloc::vec;
715 use core::str::from_utf8;
716
717 use p3_baby_bear::{BabyBear, Poseidon2BabyBear};
718 use p3_challenger::testing::{
719 SeedDigest, assert_seeds_pairwise_distinct, pow_difficulties, seed_digest,
720 };
721 use p3_challenger::{CanSample, DuplexChallenger};
722 use p3_field::PrimeCharacteristicRing;
723 use p3_field::extension::BinomialExtensionField;
724 use p3_security::grinding::{
725 GRINDING_VOCABULARY, GrindingBudget, GrindingSite, GrindingSites, RecordedGrind,
726 ZeroBitConvention, grinding_step,
727 };
728 use rand::SeedableRng;
729 use rand::rngs::SmallRng;
730
731 use super::*;
732
733 type F = BabyBear;
734 type EF = BinomialExtensionField<F, 4>;
735 type Perm = Poseidon2BabyBear<16>;
736 type Ch = DuplexChallenger<F, Perm, 16, 8>;
737
738 struct ShapeAir {
742 width: usize,
744 preprocessed_width: usize,
746 num_public_values: usize,
748 num_periodic_columns: usize,
750 }
751
752 impl BaseAir<F> for ShapeAir {
753 fn width(&self) -> usize {
754 self.width
755 }
756
757 fn preprocessed_width(&self) -> usize {
758 self.preprocessed_width
759 }
760
761 fn num_public_values(&self) -> usize {
762 self.num_public_values
763 }
764
765 fn num_periodic_columns(&self) -> usize {
766 self.num_periodic_columns
767 }
768 }
769
770 fn fresh_challenger() -> Ch {
771 let mut rng = SmallRng::seed_from_u64(0xF21);
773 Ch::new(Perm::new_from_rng_128(&mut rng))
774 }
775
776 fn plain_shape() -> StarkShape {
778 StarkShape {
779 log_ext_degree: 5,
780 log_degree: 5,
781 main_width: 2,
782 preprocessed_width: 0,
783 num_public_values: 1,
784 num_periodic_columns: 0,
785 num_quotient_chunks: 2,
786 opens_main_next_row: true,
787 opens_preprocessed_next_row: false,
788 has_randomization: false,
789 ood_pow_bits: 0,
790 }
791 }
792
793 fn seed_of(shape: &StarkShape) -> SeedDigest {
797 seed_digest(&shape.domain_separator::<F, EF>())
798 }
799
800 fn one_step_from_plain() -> Vec<(&'static str, StarkShape)> {
804 let mut mutations = Vec::new();
805
806 let mut shape = plain_shape();
807 shape.log_ext_degree += 1;
808 mutations.push(("log_ext_degree", shape));
809
810 let mut shape = plain_shape();
811 shape.log_degree += 1;
812 mutations.push(("log_degree", shape));
813
814 let mut shape = plain_shape();
815 shape.main_width += 1;
816 mutations.push(("main_width", shape));
817
818 let mut shape = plain_shape();
819 shape.preprocessed_width += 1;
820 mutations.push(("preprocessed_width", shape));
821
822 let mut shape = plain_shape();
823 shape.num_public_values += 1;
824 mutations.push(("num_public_values", shape));
825
826 let mut shape = plain_shape();
827 shape.num_periodic_columns += 1;
828 mutations.push(("num_periodic_columns", shape));
829
830 let mut shape = plain_shape();
831 shape.num_quotient_chunks += 1;
832 mutations.push(("num_quotient_chunks", shape));
833
834 let mut shape = plain_shape();
835 shape.opens_main_next_row = false;
836 mutations.push(("opens_main_next_row", shape));
837
838 let mut shape = plain_shape();
839 shape.opens_preprocessed_next_row = true;
840 mutations.push(("opens_preprocessed_next_row", shape));
841
842 let mut shape = plain_shape();
843 shape.has_randomization = true;
844 mutations.push(("has_randomization", shape));
845
846 let mut shape = plain_shape();
847 shape.ood_pow_bits += 1;
848 mutations.push(("ood_pow_bits", shape));
849
850 mutations
851 }
852
853 #[test]
854 fn no_two_configurations_of_the_shape_share_a_seed() {
855 let mut seeds = vec![("plain", seed_of(&plain_shape()))];
862 seeds.extend(
863 one_step_from_plain()
864 .iter()
865 .map(|(field, shape)| (*field, seed_of(shape))),
866 );
867
868 assert_seeds_pairwise_distinct(&seeds);
869 }
870
871 #[test]
872 fn two_airs_differing_only_in_shape_seed_differently() {
873 let base = ShapeAir {
879 width: 4,
880 preprocessed_width: 0,
881 num_public_values: 2,
882 num_periodic_columns: 0,
883 };
884
885 let shape_of = |air: &ShapeAir| StarkShape::new::<F, _>(air, 0, 6, 6, 2, false, 0);
886 let baseline = seed_of(&shape_of(&base));
887
888 let wider = ShapeAir { width: 5, ..base };
890 assert_ne!(baseline, seed_of(&shape_of(&wider)));
891
892 let more_public = ShapeAir {
894 num_public_values: 3,
895 ..base
896 };
897 assert_ne!(baseline, seed_of(&shape_of(&more_public)));
898
899 let periodic = ShapeAir {
901 num_periodic_columns: 1,
902 ..base
903 };
904 assert_ne!(baseline, seed_of(&shape_of(&periodic)));
905 }
906
907 #[test]
908 fn the_shape_constructor_reads_the_air_it_is_given() {
909 let air = ShapeAir {
911 width: 7,
912 preprocessed_width: 3,
913 num_public_values: 5,
914 num_periodic_columns: 2,
915 };
916
917 let shape = StarkShape::new::<F, _>(&air, 3, 9, 8, 4, true, 6);
919
920 assert_eq!(
921 shape,
922 StarkShape {
923 log_ext_degree: 9,
924 log_degree: 8,
925 main_width: 7,
926 preprocessed_width: 3,
927 num_public_values: 5,
928 num_periodic_columns: 2,
929 num_quotient_chunks: 4,
930 opens_main_next_row: true,
932 opens_preprocessed_next_row: true,
933 has_randomization: true,
934 ood_pow_bits: 6,
935 }
936 );
937 }
938
939 #[test]
940 fn a_run_with_no_grinding_differs_from_one_with_a_single_bit() {
941 let ungrounded = plain_shape();
943 let mut ground = ungrounded.clone();
944 ground.ood_pow_bits = 1;
945
946 assert_ne!(seed_of(&ungrounded), seed_of(&ground));
947 }
948
949 #[test]
950 fn the_two_sides_agree_on_every_challenge() {
951 let mut shape = plain_shape();
953 shape.preprocessed_width = 3;
954 shape.has_randomization = true;
955 shape.ood_pow_bits = 1;
956
957 let public_values = [F::from_u8(9)];
959
960 let mut prover_challenger = fresh_challenger();
962 let mut prover =
963 StarkProverTranscript::<Ch, F, EF>::new(&mut prover_challenger, shape.clone());
964 let prover_alpha = prover.constraint_phase([F::ONE; 8], Some([F::TWO; 8]), &public_values);
965 let (prover_zeta, witness) = prover.ood_phase([F::from_u8(3); 8], Some([F::from_u8(4); 8]));
966 prover.delegate(|_| ());
967 prover.finish();
968
969 let mut verifier_challenger = fresh_challenger();
971 let mut verifier =
972 StarkVerifierTranscript::<Ch, F, EF>::new(&mut verifier_challenger, shape);
973 let verifier_alpha = verifier
974 .constraint_phase([F::ONE; 8], Some([F::TWO; 8]), &public_values)
975 .expect("the described preprocessed commitment is supplied");
976 let verifier_zeta = verifier
977 .ood_phase(
978 [F::from_u8(3); 8],
979 Some([F::from_u8(4); 8]),
980 witness.expect("a positive difficulty grinds a witness"),
981 )
982 .expect("the witness is the one the prover ground");
983 verifier.delegate(|_| ());
984 verifier.finish();
985
986 assert_eq!(prover_alpha, verifier_alpha);
987 assert_eq!(prover_zeta, verifier_zeta);
988
989 let prover_next: F = prover_challenger.sample();
991 let verifier_next: F = verifier_challenger.sample();
992 assert_eq!(prover_next, verifier_next);
993 }
994
995 #[test]
996 fn a_described_preprocessed_commitment_that_never_arrives_is_rejected() {
997 let mut shape = plain_shape();
999 shape.preprocessed_width = 3;
1000
1001 let mut challenger = fresh_challenger();
1002 let mut transcript = StarkVerifierTranscript::<Ch, F, EF>::new(&mut challenger, shape);
1003
1004 let err = transcript
1006 .constraint_phase([F::ONE; 8], None, &[F::ONE])
1007 .expect_err("a described commitment step with no commitment must error");
1008
1009 assert_eq!(
1010 err,
1011 StarkTranscriptFailure::MissingPreprocessedCommitment { width: 3 }
1012 );
1013 }
1014
1015 #[test]
1016 fn a_preprocessed_commitment_nobody_described_is_rejected() {
1017 let mut challenger = fresh_challenger();
1019 let mut transcript =
1020 StarkVerifierTranscript::<Ch, F, EF>::new(&mut challenger, plain_shape());
1021
1022 let err = transcript
1023 .constraint_phase([F::ONE; 8], Some([F::TWO; 8]), &[F::ONE])
1024 .expect_err("a commitment outside the described sequence must error");
1025
1026 assert_eq!(
1027 err,
1028 StarkTranscriptFailure::UnexpectedPreprocessedCommitment
1029 );
1030 }
1031
1032 #[test]
1033 fn a_missing_randomization_commitment_is_rejected() {
1034 let mut shape = plain_shape();
1036 shape.has_randomization = true;
1037
1038 let mut challenger = fresh_challenger();
1039 let mut transcript = StarkVerifierTranscript::<Ch, F, EF>::new(&mut challenger, shape);
1040 let _alpha = transcript
1041 .constraint_phase([F::ONE; 8], None, &[F::ONE])
1042 .expect("the constraint phase is unaffected by the randomization step");
1043
1044 let err = transcript
1045 .ood_phase([F::TWO; 8], None, F::ZERO)
1046 .expect_err("a described commitment step with no commitment must error");
1047
1048 assert_eq!(err, StarkTranscriptFailure::MissingRandomCommitment);
1049 }
1050
1051 #[test]
1052 fn a_forged_grinding_witness_is_rejected() {
1053 let mut shape = plain_shape();
1055 shape.ood_pow_bits = 16;
1056
1057 let mut challenger = fresh_challenger();
1058 let mut transcript = StarkVerifierTranscript::<Ch, F, EF>::new(&mut challenger, shape);
1059 let _alpha = transcript
1060 .constraint_phase([F::ONE; 8], None, &[F::ONE])
1061 .expect("the constraint phase runs before any grinding step");
1062
1063 let err = transcript
1064 .ood_phase([F::TWO; 8], None, F::ZERO)
1065 .expect_err("a witness below the difficulty must error");
1066
1067 assert_eq!(err, StarkTranscriptFailure::OodPowWitness { bits: 16 });
1068 }
1069
1070 #[test]
1071 fn the_delegation_bracket_leaves_the_sponge_untouched() {
1072 let shape = plain_shape();
1077 let public_values = [F::from_u8(11)];
1078
1079 let mut bracketed_challenger = fresh_challenger();
1080 let mut bracketed =
1081 StarkVerifierTranscript::<Ch, F, EF>::new(&mut bracketed_challenger, shape.clone());
1082 let _alpha = bracketed
1083 .constraint_phase([F::ONE; 8], None, &public_values)
1084 .unwrap();
1085 let bracketed_zeta = bracketed.ood_phase([F::TWO; 8], None, F::ZERO).unwrap();
1086 bracketed.delegate(|_| ());
1087 bracketed.finish();
1088
1089 let mut plain_challenger = fresh_challenger();
1091 let mut plain = StarkProverTranscript::<Ch, F, EF>::new(&mut plain_challenger, shape);
1092 let _alpha = plain.constraint_phase([F::ONE; 8], None, &public_values);
1093 let (plain_zeta, witness) = plain.ood_phase([F::TWO; 8], None);
1094 plain.delegate(|_| ());
1095 plain.finish();
1096
1097 assert_eq!(bracketed_zeta, plain_zeta);
1098 assert_eq!(witness, None);
1099
1100 let bracketed_next: F = bracketed_challenger.sample();
1101 let plain_next: F = plain_challenger.sample();
1102 assert_eq!(bracketed_next, plain_next);
1103 }
1104
1105 #[test]
1106 fn a_public_value_the_verifier_holds_reaches_the_challenges() {
1107 let mut with_seven = fresh_challenger();
1112 let mut seven = StarkVerifierTranscript::<Ch, F, EF>::new(&mut with_seven, plain_shape());
1113 let seven_alpha = seven
1114 .constraint_phase([F::ONE; 8], None, &[F::from_u8(7)])
1115 .unwrap();
1116 let _zeta = seven.ood_phase([F::TWO; 8], None, F::ZERO).unwrap();
1117 seven.delegate(|_| ());
1118 seven.finish();
1119
1120 let mut with_eight = fresh_challenger();
1121 let mut eight = StarkVerifierTranscript::<Ch, F, EF>::new(&mut with_eight, plain_shape());
1122 let eight_alpha = eight
1123 .constraint_phase([F::ONE; 8], None, &[F::from_u8(8)])
1124 .unwrap();
1125 let _zeta = eight.ood_phase([F::TWO; 8], None, F::ZERO).unwrap();
1126 eight.delegate(|_| ());
1127 eight.finish();
1128
1129 assert_ne!(seven_alpha, eight_alpha);
1130 }
1131 fn protocol() -> &'static str {
1133 from_utf8(NAME).expect("the protocol name is ASCII")
1134 }
1135
1136 #[test]
1137 fn the_grinding_vocabulary_maps_the_one_grind_this_protocol_describes() {
1138 let ood = grinding_step(protocol(), OOD_POW).expect("the out-of-domain grind is mapped");
1140 assert_eq!(ood.site, GrindingSite::OutOfDomain);
1141 assert_eq!(ood.zero_bits, ZeroBitConvention::Elided);
1142
1143 assert_eq!(
1145 GRINDING_VOCABULARY
1146 .iter()
1147 .filter(|step| step.protocol == protocol())
1148 .count(),
1149 1,
1150 );
1151 }
1152
1153 #[test]
1154 fn the_out_of_domain_grind_carries_the_difficulty_the_model_credits() {
1155 for ood_pow_bits in [0, 1, 8] {
1162 let shape = StarkShape {
1163 log_ext_degree: 5,
1164 log_degree: 5,
1165 main_width: 2,
1166 preprocessed_width: 0,
1167 num_public_values: 1,
1168 num_periodic_columns: 0,
1169 num_quotient_chunks: 2,
1170 opens_main_next_row: true,
1171 opens_preprocessed_next_row: false,
1172 has_randomization: false,
1173 ood_pow_bits,
1174 };
1175
1176 let recorded: Vec<_> = pow_difficulties(&shape.pattern::<F, EF>())
1177 .into_iter()
1178 .map(|(label, bits)| RecordedGrind::new(protocol(), label, bits))
1179 .collect();
1180
1181 GrindingBudget::from_sites(&GrindingSites {
1182 out_of_domain: ood_pow_bits,
1183 ..GrindingSites::NONE
1184 })
1185 .check(&[protocol()], &recorded)
1186 .unwrap_or_else(|mismatch| panic!("{mismatch}"));
1187 }
1188 }
1189}