Open HarryR opened 5 years ago
There is a programming style that could be used with Ethsnarks to make implementation easier:
auto m = pb.module("example");
const auto sig = m( EdDSA_Signature() );
auto verify = m( PureEdDSA_Verify(sig) );
But you need separate functions for defining the circuit and generating the witness, e.g.:
sig.witness(...);
I am using a declarative programming style for defining circuits, where it's split into 4 sections:
Adding each gadget to a list could avoid needing a generate_r1cs_constraints()
function for every instance - only the bas implementation which iterates through the gadgets in the group.
For example:
struct GadgetT {
ProtoboardT &pb;
};
struct MyGadget : public GadgetT {
EdDSA_Signature sig{pb};
PureEdDSA_Verify verify {sig};
};
ProtoboardT pb;
MyGadget gadget{.pb = pb};
The assumption is that this is a more efficient way of passing the .pb
member through, and uses explicit ordering of the default initialisations of fields to allow the construction of more complex gadgets. The problem is - what happens when no ProtoboardT parameter is passed?
Encoding to/from json automatically would be a good feature, reducing the need to create custom serialisation functions for each type, e.g. using https://github.com/xyz347/x2struct or Qt slots would make the 'witness' phase easier as fields could be filled with their appropriate values from an arbitrary dictionary.
Encoding to/from json automatically would be a good feature, reducing the need to create custom serialisation functions for each type, e.g. using https://github.com/xyz347/x2struct or Qt slots would make the 'witness' phase easier as fields could be filled with their appropriate values from an arbitrary dictionary.
The BOOST_FUSION_ADAPT_STRUCT
macro can be used to make the names and types of struct members available introspectively, for example: http://andres.senac.es/2011/04/generic-json-serializer.html
Witness data could be passed as a JSON dictionary or boost property tree and converted into the correct types with error checking before setting the value of variables used by the circuit. Only fields which need to be supplied by the user are required.
Going to avoid using Boost, as it's a huge dependency and makes it difficult to build for WASM, iOS, Android etc.
There are some things which could be easier:
These can be addressed in several ways:
.val(pb)
method to VariableT, in addition to pb.val(var)
.vals(pb)
method to VariableArrayTpb.vals(vars)
method to ProtoboardTAllow the above methods to accept VariableT, VariableArrayT, linear combination and an array of linear combinations.
When in debug mode, all constraints require an annotation, when in DEBUG mode protoboard will assert when variable has no annotation. Are annotations really 100% necessary (although, they do make debugging easier).
add_r1cs_constraint
is a bad method name, this should be much simpler, for example:
pb << ConstraintT(a, b, c, "annotation");
Is much nicer than:
pb.add_r1cs_constraint(ConstraintT(a, b, c), "annotation");
Alternatively, it may be possible to do stuff like:
pb.enforce({
a * b == c,
d * e == f
}, "annotation");
Where a*b
is a quadratic relation if both A and B are variable. Multiplying a linear combination or variable with another variable or non-constant linear combination would result in a QuadraticRelation (is QuadraticRelation
and appropriate term?).
The equality operator overload on QuadraticRelation would result in a ConstraintT, which can then be added to the constraint system
The idea being that we should be able to automatically emit constraints for more complex relationships in a predictable manner.
Say you have a matrix[t][t]
, and variables[t]
You want to perform a matrix vector product of matrix * variables
to get a result of length [t]
.
It should be trivial to perform this without writing for loops.
Including generating the witness, and the constraints
To make it easier to write code, instead of having to produce essentially 3 pieces of code:
We should be able to have one piece of code which does all three, possibly using templates to enable witness/constrain/both, where the result in 'witness mode' will be actual values, but in 'constrain mode' they will be variables.
Things which need to be easily supported, but to be implemented cleanly would require us to extend libsnark
Interface improvements:
make_variable
into VariableT constructorThis should get rid of everything from "utils.hpp"
General improvements:
==
operation emits constraints (which must be added to context)Bitness:
When a variable is declared to be a Bit, it should have an automatic bitness constraint added. Functions which output bits means the type can be 'Implicitly binary' - meaning it doesn't need a constraint of its own.
A global bitmap is kept with all of the 'bit variables', then the constraints are added at the end.
Currently gadgets and circuits require defining a class, which splits it into 3 parts:
One reason for this separation is that generating the proving key requires no inputs, and generating the witness (or simulating the program) requires no evaluation of the constraints.
Ideally it should be possible to write programs in a more procedural style as it would reduce the size of many circuits - requiring smaller and less verbose headers and more immediately understandable programs that closer resemble the pseudocode they implement.
Todo:
A scripting system would be similar to the Pinocchio parser, and would allow all gadgets to be usable from within the parser without modification.