@@ -19,7 +19,14 @@ import (
1919)
2020
2121const (
22+ // rangeCheckBaseLengthForSmallField is the base length used for range
23+ // checking when using small field optimization. We start enforcing
24+ // the base length only when the number of range checks exceeds
25+ // thresholdOptimizeOptimizedOverflow.
2226 rangeCheckBaseLengthForSmallField = 16
27+ // thresholdForInexactOverflow is the number of range checks after
28+ // which we start enforcing the base length for small field optimization.
29+ thresholdForInexactOverflow = 55000
2330)
2431
2532// Field holds the configuration for non-native field operations. The field
@@ -51,8 +58,11 @@ type Field[T FieldParams] struct {
5158
5259 log zerolog.Logger
5360
54- constrainedLimbs map [[16 ]byte ]struct {}
61+ // constrainedLimbs keeps track of already range checked limbs. The map
62+ // value indicates the range check width.
63+ constrainedLimbs map [[16 ]byte ]int
5564 checker frontend.Rangechecker
65+ nbRangeChecks int
5666
5767 deferredChecks []deferredChecker
5868
@@ -81,7 +91,7 @@ func NewField[T FieldParams](native frontend.API) (*Field[T], error) {
8191 f := & Field [T ]{
8292 api : native ,
8393 log : logger .Logger (),
84- constrainedLimbs : make (map [[16 ]byte ]struct {} ),
94+ constrainedLimbs : make (map [[16 ]byte ]int ),
8595 checker : rangecheck .New (native ),
8696 fParams : newStaticFieldParams [T ](native .Compiler ().Field ()),
8797 }
@@ -93,15 +103,6 @@ func NewField[T FieldParams](native frontend.API) (*Field[T], error) {
93103 }
94104 f .extensionApi = extapi
95105 }
96- if f .useSmallFieldOptimization () {
97- // in case of emulated small fields we use base length 16 to reduce
98- // needing to range check for [v_lo, v_hi, 2*v_hi].
99- //
100- // But this means that hints could output values which are bigger than
101- // the emulated modulus bitwidth (for example 31 bits). This means we
102- // have to set the overflow of returned elements correctly.
103- f .checker = rangecheck .New (native , rangecheck .WithBaseLength (rangeCheckBaseLengthForSmallField ))
104- }
105106
106107 // ensure prime is correctly set
107108 if f .fParams .IsPrime () {
@@ -265,7 +266,7 @@ func (f *Field[T]) enforceWidthConditional(a *Element[T]) (didConstrain bool) {
265266 // that we should enforce width for the whole element. But we
266267 // still iterate over all limbs just to mark them in the table.
267268 didConstrain = true
268- f . constrainedLimbs [ h ] = struct {}{}
269+ break
269270 }
270271 } else {
271272 // we have no way of knowing if the limb has been constrained. To be
@@ -392,3 +393,48 @@ func (f *Field[T]) useSmallFieldOptimization() bool {
392393 })
393394 return f .smallFieldMode
394395}
396+
397+ // rangeCheck performs a range check on v to ensure it fits in nbBits.
398+ // It also keeps track of the number of range checks done, and after a certain
399+ // threshold switches to using base length range checking for small field
400+ // optimization.
401+ //
402+ // It returns a boolean indicating if the range check was actually performed (i.e. if
403+ // the limb was not already constrained).
404+ func (f * Field [T ]) rangeCheck (v frontend.Variable , nbBits int ) bool {
405+ if h , ok := v .(interface { HashCode () [16 ]byte }); ok {
406+ // if the variable has a hashcode, then we can use it to see if we have
407+ // already range checked it.
408+ hc := h .HashCode ()
409+ if existingWidth , ok := f .constrainedLimbs [hc ]; ok {
410+ // already range checked with a certain width
411+ if existingWidth <= nbBits {
412+ return false
413+ }
414+ }
415+ // mark as range checked
416+ f .constrainedLimbs [hc ] = nbBits
417+ }
418+ // update the number of range checks done. This is only to keep track if we
419+ // should switch to the case where instead of exact width we range check
420+ // multiple of base length. This reduces number of range checks when
421+ // emulating small field.
422+ f .nbRangeChecks ++
423+
424+ if f .nbRangeChecks == thresholdForInexactOverflow {
425+ // the threshold is reached, set the range checker to use base length.
426+ // Now we know that when constructing non-native elements, then we should
427+ // set overflow=f.smallAdditionalOverflow()
428+ if f .useSmallFieldOptimization () {
429+ // in case of emulated small fields we use base length 16 to reduce
430+ // needing to range check for [v_lo, v_hi, 2*v_hi].
431+ //
432+ // But this means that hints could output values which are bigger than
433+ // the emulated modulus bitwidth (for example 31 bits). This means we
434+ // have to set the overflow of returned elements correctly.
435+ f .checker = rangecheck .New (f .api , rangecheck .WithBaseLength (rangeCheckBaseLengthForSmallField ))
436+ }
437+ }
438+ f .checker .Check (v , nbBits )
439+ return true
440+ }
0 commit comments