@@ -7,9 +7,9 @@ use math::field::element::FieldElement;
77use math:: field:: traits:: { IsFFTField , IsField , IsSubFieldOf } ;
88use math:: polynomial:: Polynomial ;
99
10- /// TransitionConstraint represents the behaviour that a transition constraint
10+ /// TransitionConstraintEvaluator represents the behaviour that a transition constraint
1111/// over the computation that wants to be proven must comply with.
12- pub trait TransitionConstraint < F , E > : Send + Sync
12+ pub trait TransitionConstraintEvaluator < F , E > : Send + Sync
1313where
1414 F : IsSubFieldOf < E > + IsFFTField + Send + Sync ,
1515 E : IsField + Send + Sync ,
3030 /// the evaluation.
3131 /// Once computed, the evaluation should be inserted in the `transition_evaluations`
3232 /// vector, in the index corresponding to the constraint as given by `constraint_idx()`.
33- fn evaluate (
33+ fn evaluate_verifier (
3434 & self ,
3535 evaluation_context : & TransitionEvaluationContext < F , E > ,
3636 transition_evaluations : & mut [ FieldElement < E > ] ,
8484 0
8585 }
8686
87+ /// Prover-optimized evaluation that writes base-field constraints to `base_evals`
88+ /// and extension-field constraints to `ext_evals`.
89+ ///
90+ /// Constraints with `constraint_idx() < base_evals.len()` are "base" constraints
91+ /// and MUST override this to write `FieldElement<F>` into `base_evals[constraint_idx()]`.
92+ /// Extension constraints (LogUp etc.) use the default, which asserts the index is
93+ /// in the extension range and delegates to `evaluate()`.
94+ fn evaluate_prover (
95+ & self ,
96+ evaluation_context : & TransitionEvaluationContext < F , E > ,
97+ base_evals : & mut [ FieldElement < F > ] ,
98+ ext_evals : & mut [ FieldElement < E > ] ,
99+ ) {
100+ debug_assert ! (
101+ self . constraint_idx( ) >= base_evals. len( ) ,
102+ "Base constraint idx {} must override evaluate_prover()" ,
103+ self . constraint_idx( ) ,
104+ ) ;
105+ self . evaluate_verifier ( evaluation_context, ext_evals) ;
106+ }
107+
87108 /// Method for calculating the end exemptions polynomial.
88109 ///
89110 /// This polynomial is used to compute zerofiers of the constraint, and the default
@@ -267,3 +288,145 @@ where
267288 * end_exemptions_poly. evaluate ( z)
268289 }
269290}
291+
292+ // =============================================================================
293+ // User-facing TransitionConstraint trait + adapter
294+ // =============================================================================
295+
296+ use crate :: table:: TableView ;
297+
298+ /// User-facing trait for defining transition constraints.
299+ ///
300+ /// Implement `evaluate()` to define the polynomial identity; the verifier and
301+ /// prover evaluation paths are auto-generated via `.boxed()`.
302+ ///
303+ /// The `evaluate` method is generic over its field types so the same polynomial
304+ /// works for both the prover (`TableView<F, E>`) and verifier (`TableView<E, E>`).
305+ pub trait TransitionConstraint < F , E > : Send + Sync
306+ where
307+ F : IsSubFieldOf < E > + IsFFTField + Send + Sync ,
308+ E : IsField + Send + Sync ,
309+ {
310+ /// The degree of the constraint as a multivariate polynomial.
311+ fn degree ( & self ) -> usize ;
312+
313+ /// Unique index in `[0, N)` where N is the total number of transition constraints.
314+ fn constraint_idx ( & self ) -> usize ;
315+
316+ /// Number of exempted rows at the end of the trace.
317+ fn end_exemptions ( & self ) -> usize {
318+ 0
319+ }
320+
321+ /// Evaluate the constraint polynomial on a trace step.
322+ ///
323+ /// Generic over the field so the same polynomial works for both
324+ /// prover (FF=F, returns FieldElement<F>) and verifier (FF=E, returns FieldElement<E>).
325+ fn evaluate < FF , EE > ( & self , step : & TableView < FF , EE > ) -> FieldElement < FF >
326+ where
327+ FF : IsSubFieldOf < EE > ,
328+ EE : IsField ;
329+
330+ /// Periodicity (default 1 = every row).
331+ fn period ( & self ) -> usize {
332+ 1
333+ }
334+
335+ /// Offset for periodic application (default 0).
336+ fn offset ( & self ) -> usize {
337+ 0
338+ }
339+
340+ /// Exemptions period (default None).
341+ fn exemptions_period ( & self ) -> Option < usize > {
342+ None
343+ }
344+
345+ /// Offset for periodic exemptions (default None).
346+ fn periodic_exemptions_offset ( & self ) -> Option < usize > {
347+ None
348+ }
349+
350+ /// Wrap into a boxed `TransitionConstraintEvaluator` for the evaluator.
351+ ///
352+ /// The adapter auto-generates `evaluate_verifier()` and `evaluate_prover()`
353+ /// from the generic `evaluate()`.
354+ fn boxed ( self ) -> Box < dyn TransitionConstraintEvaluator < F , E > >
355+ where
356+ Self : Sized + ' static ,
357+ {
358+ Box :: new ( TransitionConstraintAdapter ( self ) )
359+ }
360+ }
361+
362+ /// Adapter: implements `TransitionConstraintEvaluator` for any `TransitionConstraint`.
363+ ///
364+ /// Auto-generates `evaluate_verifier()` (E×E path) and `evaluate_prover()` (F path)
365+ /// from the user's generic `evaluate()`.
366+ pub struct TransitionConstraintAdapter < T > ( pub T ) ;
367+
368+ impl < T , F , E > TransitionConstraintEvaluator < F , E > for TransitionConstraintAdapter < T >
369+ where
370+ T : TransitionConstraint < F , E > + ' static ,
371+ F : IsSubFieldOf < E > + IsFFTField + Send + Sync ,
372+ E : IsField + Send + Sync ,
373+ {
374+ fn degree ( & self ) -> usize {
375+ self . 0 . degree ( )
376+ }
377+ fn constraint_idx ( & self ) -> usize {
378+ self . 0 . constraint_idx ( )
379+ }
380+ fn end_exemptions ( & self ) -> usize {
381+ self . 0 . end_exemptions ( )
382+ }
383+ fn period ( & self ) -> usize {
384+ self . 0 . period ( )
385+ }
386+ fn offset ( & self ) -> usize {
387+ self . 0 . offset ( )
388+ }
389+ fn exemptions_period ( & self ) -> Option < usize > {
390+ self . 0 . exemptions_period ( )
391+ }
392+ fn periodic_exemptions_offset ( & self ) -> Option < usize > {
393+ self . 0 . periodic_exemptions_offset ( )
394+ }
395+
396+ fn evaluate_verifier (
397+ & self ,
398+ ctx : & TransitionEvaluationContext < F , E > ,
399+ evals : & mut [ FieldElement < E > ] ,
400+ ) {
401+ let idx = self . 0 . constraint_idx ( ) ;
402+ match ctx {
403+ TransitionEvaluationContext :: Prover { frame, .. } => {
404+ evals[ idx] = self . 0 . evaluate ( frame. get_evaluation_step ( 0 ) ) . to_extension ( ) ;
405+ }
406+ TransitionEvaluationContext :: Verifier { frame, .. } => {
407+ evals[ idx] = self . 0 . evaluate ( frame. get_evaluation_step ( 0 ) ) ;
408+ }
409+ }
410+ }
411+
412+ fn evaluate_prover (
413+ & self ,
414+ ctx : & TransitionEvaluationContext < F , E > ,
415+ base_evals : & mut [ FieldElement < F > ] ,
416+ ext_evals : & mut [ FieldElement < E > ] ,
417+ ) {
418+ let idx = self . 0 . constraint_idx ( ) ;
419+ if idx < base_evals. len ( ) {
420+ // Base-field fast path: write FieldElement<F> directly
421+ if let TransitionEvaluationContext :: Prover { frame, .. } = ctx {
422+ base_evals[ idx] = self . 0 . evaluate ( frame. get_evaluation_step ( 0 ) ) ;
423+ } else {
424+ unreachable ! ( "evaluate_prover called with non-Prover context" ) ;
425+ }
426+ } else {
427+ // Fallback: AIR did not opt into base-field splitting,
428+ // delegate to the verifier path which writes E evals.
429+ self . evaluate_verifier ( ctx, ext_evals) ;
430+ }
431+ }
432+ }
0 commit comments