github.com/consensys/gnark@v0.11.0/frontend/compile.go (about)

     1  package frontend
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math/big"
     7  	"reflect"
     8  
     9  	"github.com/consensys/gnark/constraint"
    10  	"github.com/consensys/gnark/debug"
    11  	"github.com/consensys/gnark/frontend/schema"
    12  	"github.com/consensys/gnark/internal/circuitdefer"
    13  	"github.com/consensys/gnark/logger"
    14  )
    15  
    16  // Compile will generate a ConstraintSystem from the given circuit
    17  //
    18  // 1. it will first allocate the user inputs (see [schema.TagOpt] and [Circuit] for more info)
    19  // example:
    20  //
    21  //	type MyCircuit struct {
    22  //		Y frontend.Variable `gnark:"exponent,public"`
    23  //	}
    24  //
    25  // in that case, Compile() will allocate one public variable with id "exponent"
    26  //
    27  // 2. it then calls circuit.Define(curveID, R1CS) to build the internal constraint system
    28  // from the declarative code
    29  //
    30  //  3. finally, it converts that to a ConstraintSystem.
    31  //     if zkpID == backend.GROTH16	→ R1CS
    32  //     if zkpID == backend.PLONK 	→ SparseR1CS
    33  //
    34  // initialCapacity is an optional parameter that reserves memory in slices
    35  // it should be set to the estimated number of constraints in the circuit, if known.
    36  func Compile(field *big.Int, newBuilder NewBuilder, circuit Circuit, opts ...CompileOption) (constraint.ConstraintSystem, error) {
    37  	log := logger.Logger()
    38  	log.Info().Msg("compiling circuit")
    39  	// parse options
    40  	opt := defaultCompileConfig()
    41  	for _, o := range opts {
    42  		if err := o(&opt); err != nil {
    43  			log.Err(err).Msg("applying compile option")
    44  			return nil, fmt.Errorf("apply option: %w", err)
    45  		}
    46  	}
    47  
    48  	// instantiate new builder
    49  	builder, err := newBuilder(field, opt)
    50  	if err != nil {
    51  		log.Err(err).Msg("instantiating builder")
    52  		return nil, fmt.Errorf("new compiler: %w", err)
    53  	}
    54  
    55  	// parse the circuit builds a schema of the circuit
    56  	// and call circuit.Define() method to initialize a list of constraints in the compiler
    57  	if err = parseCircuit(builder, circuit); err != nil {
    58  		log.Err(err).Msg("parsing circuit")
    59  		return nil, fmt.Errorf("parse circuit: %w", err)
    60  
    61  	}
    62  
    63  	// compile the circuit into its final form
    64  	return builder.Compile()
    65  }
    66  
    67  func parseCircuit(builder Builder, circuit Circuit) (err error) {
    68  	// ensure circuit.Define has pointer receiver
    69  	if reflect.ValueOf(circuit).Kind() != reflect.Ptr {
    70  		return errors.New("frontend.Circuit methods must be defined on pointer receiver")
    71  	}
    72  
    73  	s, err := schema.Walk(circuit, tVariable, nil)
    74  	if err != nil {
    75  		return err
    76  	}
    77  
    78  	log := logger.Logger()
    79  	log.Info().Int("nbSecret", s.Secret).Int("nbPublic", s.Public).Msg("parsed circuit inputs")
    80  
    81  	// leaf handlers are called when encountering leafs in the circuit data struct
    82  	// leafs are Constraints that need to be initialized in the context of compiling a circuit
    83  	variableAdder := func(targetVisibility schema.Visibility) func(f schema.LeafInfo, tInput reflect.Value) error {
    84  		return func(f schema.LeafInfo, tInput reflect.Value) error {
    85  			if tInput.CanSet() {
    86  				if f.Visibility == schema.Unset {
    87  					return errors.New("can't set val " + f.FullName() + " visibility is unset")
    88  				}
    89  				if f.Visibility == targetVisibility {
    90  					if f.Visibility == schema.Public {
    91  						tInput.Set(reflect.ValueOf(builder.PublicVariable(f)))
    92  					} else if f.Visibility == schema.Secret {
    93  						tInput.Set(reflect.ValueOf(builder.SecretVariable(f)))
    94  					}
    95  				}
    96  
    97  				return nil
    98  			}
    99  			return errors.New("can't set val " + f.FullName())
   100  		}
   101  	}
   102  
   103  	// add public inputs first to compute correct offsets
   104  	_, err = schema.Walk(circuit, tVariable, variableAdder(schema.Public))
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	// add secret inputs
   110  	_, err = schema.Walk(circuit, tVariable, variableAdder(schema.Secret))
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	// recover from panics to print user-friendlier messages
   116  	defer func() {
   117  		if r := recover(); r != nil {
   118  			err = fmt.Errorf("%v\n%s", r, debug.Stack())
   119  		}
   120  	}()
   121  
   122  	// call Define() to fill in the Constraints
   123  	if err = circuit.Define(builder); err != nil {
   124  		return fmt.Errorf("define circuit: %w", err)
   125  	}
   126  	if err = callDeferred(builder); err != nil {
   127  		return fmt.Errorf("deferred: %w", err)
   128  	}
   129  
   130  	return
   131  }
   132  
   133  func callDeferred(builder Builder) error {
   134  	for i := 0; i < len(circuitdefer.GetAll[func(API) error](builder)); i++ {
   135  		if err := circuitdefer.GetAll[func(API) error](builder)[i](builder); err != nil {
   136  			return fmt.Errorf("defer fn %d: %w", i, err)
   137  		}
   138  	}
   139  	return nil
   140  }
   141  
   142  // CompileOption defines option for altering the behaviour of the Compile
   143  // method. See the descriptions of the functions returning instances of this
   144  // type for available options.
   145  type CompileOption func(opt *CompileConfig) error
   146  
   147  func defaultCompileConfig() CompileConfig {
   148  	return CompileConfig{
   149  		CompressThreshold: 300,
   150  	}
   151  }
   152  
   153  type CompileConfig struct {
   154  	Capacity                  int
   155  	IgnoreUnconstrainedInputs bool
   156  	CompressThreshold         int
   157  }
   158  
   159  // WithCapacity is a compile option that specifies the estimated capacity needed
   160  // for internal variables and constraints. If not set, then the initial capacity
   161  // is 0 and is dynamically allocated as needed.
   162  func WithCapacity(capacity int) CompileOption {
   163  	return func(opt *CompileConfig) error {
   164  		opt.Capacity = capacity
   165  		return nil
   166  	}
   167  }
   168  
   169  // IgnoreUnconstrainedInputs is a compile option which allow compiling input
   170  // circuits where not all inputs are not constrained. If not set, then the
   171  // compiler returns an error if there exists an unconstrained input.
   172  //
   173  // This option is useful for debugging circuits, but should not be used in
   174  // production settings as it means that there is a potential error in the
   175  // circuit definition or that it is possible to optimize witness size.
   176  func IgnoreUnconstrainedInputs() CompileOption {
   177  	return func(opt *CompileConfig) error {
   178  		opt.IgnoreUnconstrainedInputs = true
   179  		return nil
   180  	}
   181  }
   182  
   183  // WithCompressThreshold is a compile option which enforces automatic variable
   184  // compression if the length of the linear expression in the variable exceeds
   185  // given threshold.
   186  //
   187  // This option is usable in arithmetisations where the variable is a linear
   188  // combination, as for example in R1CS. If variable is not a linear combination,
   189  // then this option does not change the compile behaviour.
   190  //
   191  // This compile option should be used in cases when it is known that there are
   192  // long addition chains and the compile time and memory usage start are growing
   193  // fast. The compression adds some overhead in the number of constraints. The
   194  // overhead and compile performance depends on threshold value, and it should be
   195  // chosen carefully.
   196  //
   197  // If this option is not given then by default we use the compress threshold of
   198  // 300.
   199  func WithCompressThreshold(threshold int) CompileOption {
   200  	return func(opt *CompileConfig) error {
   201  		opt.CompressThreshold = threshold
   202  		return nil
   203  	}
   204  }
   205  
   206  var tVariable reflect.Type
   207  
   208  func init() {
   209  	tVariable = reflect.ValueOf(struct{ A Variable }{}).FieldByName("A").Type()
   210  }