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 }