-
Notifications
You must be signed in to change notification settings - Fork 410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Frontend refactoring #200
Frontend refactoring #200
Conversation
frontend/compiler/compile.go
Outdated
@@ -48,7 +49,7 @@ import ( | |||
// | |||
// initialCapacity is an optional parameter that reserves memory in slices | |||
// it should be set to the estimated number of constraints in the circuit, if known. | |||
func Compile(curveID ecc.ID, zkpID backend.ID, circuit Circuit, opts ...func(opt *CompileOption) error) (ccs CompiledConstraintSystem, err error) { | |||
func Compile(curveID ecc.ID, zkpID backend.ID, circuit frontend.Circuit, opts ...func(opt *CompileOption) error) (ccs compiled.CompiledConstraintSystem, err error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure it makes sense to move that in a separate package; it adds API breaking change for all existing circuits, but the logic is not living in this new package anyway.
I suppose it was made this way to avoid import cycles??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes 😁
I have refactored the branch a bit more. I reverted the move of Compile function into a sub-package. In addition, I added two registries:
Now, when someone wants to add (or even use their internal backend), it is possible to use a gnark compiler without having to change the library. Similarly, it is possible to define a new compiler (a frontend actually as every frontend can have only a single compiler). This has a small side-effect though -- as the registrations are performed lazily in inits (due to needing to avoid the import cycles), then in strange workflows the compilers won't be registered and compilation will fail (for example, when compiling a circuit without later proving or verifying it). I tried making the corresponding errors quite explanatory so that the user would know what to do. There are still tons of things to do (and I would actually split some stuff into smaller PRs), for example:
Does this API make sense? I'll move on polishing it more. |
@ivokub I think this is not the best solution -->
--> I think this may happen quite often (use of the backend or the frontend independently), typically in dev cycles or serializing only the compiled circuit. I don't see why the my proposition is essentially:
package level function. + public (or wrap it as you did in another function if you want to manage errors)
so that the caller code looks like
I think it was a mistake to put the I started a branch (refactor-flexible-compiler) that did that, but can't finish tonight. You can pick it up or throw it away and come w/ a better approach :) . I stopped when I realized that the APIs of the |
It was not a fatal error - due to lazy registration the compiler does not get registered if it is not explicitly imported by anything. Adding a blank import (
But there is a dependence between a backend and a constraint system? Any Groth16 backend requires R1CS and PLONK backend requires SparseR1CS. Right now we have the constraint system (i.e. a list of constraints) and a circuit->CS compiler coupled. A backend does not necessarily require a specific compiler, but it still requires a specific CS. In my branch the built-in backends register default compilers themselves. It would make sense to add a compile option to pick a particular compiler.
What about separating the CS and compilers? This means having structure:
I think having it makes some sense -- from a user point of view who just wants to construct a proof from a circuit, it doesn't really matter what is the underlying CS. Right now, due to the default mapping from a backend to compiler the user can just define which backend they intend to use and the frontend.Compile function picks a default one. It would make sense to add a compile option to allow choosing different compilers if the user really knows what they are doing, but otherwise the default choices should be sane. Most of all, I think it wouldn't be great to change the
I'll try. |
OK makes sense. Not a fan either of changing the |
Yes, that is the idea. edit: looks like decoupling the frontend and backend definitions of constraint systems requires a bit more effort. It is definitely worth it's own PR. |
The structures are intermediate for the builders. We output compiled constraint system. Unpublish for now so that no one would depend on it.
The input is cast to *big.Int when the hint function is being computed. Cast the inputs to the hint function to *big.Int already when calling NewHint for more stable type. This allows later to restrict the number of types a hint input can take (e.g. during serializing).
4ae2e3c
to
c3ee76a
Compare
Rebased on top of develop, it now mergeable without conflicts. All the tests are now green and the discovered bugs fixed. Orthogonal to the refactoring work I had to modify compiled circuit serialization to/from CBOR so that the serialization tests would succeed. |
looks awesome thanks @ivokub & @ThomasPiellard , will do few cosmetic edits and merge 👍 |
@ivo.kubjas I still don't like that we have to do this:
instead of this
|
This PR is a long overdue, it cleans up the way the plonk constraint system is added, and makes the code upgradable for different new constraint systems (custom gates, generalised arithmetic circuits, square constraints, etc).
Architecture
The frontend is now organised like this:
frontend/
├── compiler
├── cs
│ ├── plonk
│ └── r1cs
└── utils
frontend/
api.go
provides theAPI
interface that a constraint system should implement (with the usual fonctions like Add, Mul, etc). It also provides an interfaceSystem
with inherits from API, and provides the fonctionscircuit.go
provides the interface that a circuit should implement (as before).compiler/
compile.go
provides the logic to build a constraint system to its target form (r1cs or sparse r1cs). It provides the same functionCompile
as indevelop
. The difference is thatbuildCS
has been renamedbootStrap
and takes as parameter theSystem
interface. It acts exactly as in develop, recursively instantiating the inputs and callingDefine
afterwards.cs/
cs.go
provides the common data shared by each constraint system:It is essentially a list of coefficients, to be affected to wires, it is agnostic of the constraints. It inherits from
CS
defined in packagecompile
.r1cs/, plonk/
Those folders contain the actual instantiation of plonk constraint systme and Groth16 constraint system (respectively sparse_r1cs and r1cs in our naming).
r1cs/
contains the same api as in develop, the files were merely moved fromfrontend/
tor1cs/
.'plonk/' contains the equivalent data but for sparseR1CS. In particular an
api.go
has been created for handling plonk constraints, so there is no longer a conversion from r1cs to sparser1cs.Both constraint systems inherit from
ConstraintSystem
, with an additional field which is the slice of the actual constraints (ex:).
In both
r1cs/
andplonk/
there is aconversion.go
file providing aCompile
function which essentially shifts the IDs of the variables in the logs, etc after a circuit is built. This function is internally called byCompile
defined infrontend/compiler/compile.go
.API breaking changes
frontend.Compile
-->compiler.Compile
type Hint struct {ID hint.ID , Inputs []Variable}
-->type Hint struct {ID hint.ID, Inputs []interface{}}
Status
Tests pass, except the circuits_stats tests for several of the circuits used in integration tests have been extended to cover more cases. There is no change in Groth16 in terms of number of constraints (Groth16 logic hasn't been modified at all, the files were merely moved around).