template<Number_T, Wire_T>
class MyBackend : public wtk::TypeBackend<Number_T, Wire_T>
{
public:
void mulGate(Element_T* const out,
Element_T const* const left_in, Element_T const* const right_in)
{
/* Your Code Here */
}
/** Etc. */
};
NAILS Circuit-IR Interpreter
NAILS is WizToolKit’s interpreter library for the Circuit-IR (IR0+). NAILS will handle the IR semantics and pass off the details of ZK to your backend. It stands for "Naive Amenity for Interpreting Long Streams", because it just reads things and passes them to the backend, it can’t do much in the way of smart things to speed up processing.
NAILS requires you to implement an abstract class with callbacks to handle each gate. You will need one of these "backend" objects for each type in the statement. Once these are provided, the NAILS system can invoke the appropriate callbacks corresponding to the gates in the relation.
Implementing a Backend
A ZK backend should implement the wtk::TypeBackend<Number_T, Wire_T>
interface defined in #include <wtk/TypeBackend.h>
.
the Number_T
template should correspond with the Number_T
used for the Parser API.
the Wire_T
template is representative of a wire or a field element within the backend, it has few requirements other than being default and move constructible.
Implement methods of the backend to perform arithmetic like this
The failure method of wtk::TypeBackend
is to cache them until the end of the relation (for example if an @assert_zero
turns out non-zero) and report the failure at end using the bool check()
method.
Invoking NAILS
The main worker of NAILS is the wtk::nails::Interpreter<Number_T>
class from #include <wtk/nails/Interpreter.h>
.
The interpreter manages gates and memory and passes off gates to the appropriate wtk::TypeBackend
callback for doing ZK logic.
It is templated on just the Number_T
, for example GMP’s mpz_class
, same as the parser.
It is not templated with the backend’s Wire_T
because it must handle multiple backends, each of which may have a different Wire_T template.
wtk::nails::Interpreter<mpz_class> interpreter("circuit_file_name (for error reporting)");
You will need to add each type in your relation to the interpreter
, along with public and private input streams.
The circuit_parser->types
vector holds a type specification for each conversion (wtk::circuit::TypeSpec<Number_T>
from #include <wtk/circuit/Data.h>
.
The public and private input streams may be nullptr
, for example in preprocessing or as the verifier.
As mentioned, the backend’s Wire_T
template may differ on each invocation of the interpreter.addBackend(...)
call.
for(size_t i = 0; i < circuit_parser->types.size(); i++)
{
/* Create the appropriate backend and streams */
interpreter.addType(my_backend, public_in_stream, private_in_stream);
}
Next is a little bit of necessary boilerplate to accomodate functions and plugins.
-
wtk::nails::GatesFunctionFactory<Number_T>
(from#include <wtk/nails/Functions.h>
-
wtk::plugin::PluginsManager<Number_T, Wire_Ts…>
(from#include <wtk/plugins/Plugin.h>
)-
This one requires a list with each distinct
Wire_T
from all backends.
-
wtk::nails::GatesFunctionFactory<mpz_class> function_factory;
wtk::plugins::PluginsManager<mpz_class, BoolWire, ArithmeticWire> plugins_manager;
Finally, to invoke NAILS, you’ll need to invoke the Circuit-IR Parser with a wtk::nails::Handler<Number_T>
.
The wtk::nails::Handler<Number_T>
implements the Parser’s callback API, either passing off gates to NAILS or recording them within functions.
Find it in #include <wtk/nails/Handler.h>
.
wtk::nails::Handler<mpz_class> handler(&interpreter, &function_factory, &plugins_manager);
if(!circuit_parser->parse(&handler))
{
printf("syntax error or poorly formed relation\n")
}
Last thing to do is call check()
and finish()
to report errors and clean up.
if(!my_field.check())
{
printf("Something asserted non-zero");
}
// if implemented by your backend
my_field.finish()
Field Switching and Conversions
The parser API includes a wtk::circuit::ConversionSpec
type with details about each conversion (see #include <wtk/circuit/Data.h>
).
Most notably, the conversion spec defines a type and a size for the inputs and outputs.
You will need to provide one converter object to correspond to each conversion spec.
Implement the wtk::Converter<OutWire_T, InWire_T>
interface for field switching (see #include <wtk/Converter.h>
).
The OutWire_T
and InWire_T
templates correspond to Wire_T
of the output and input wtk::TypeBackend
s.
The constructor must set the ouLength
and inLength
parent attributes.
class MyConversion : public wtk::Converter<MyWireOut, MyWireIn>
{
// constructor recieves in/out lengths
MyConversion(size_t out_len, size_t in_len)
: wtk::Converter<MyWireOut, MyWireIn>(out_len, in_len) { }
// convert method recieves pointers of length outLength and inLength
bool convert(
OutWire_T* const out_wires, InWire_T const* const in_wires) override
{
size_t out_len const = this->outLength;
size_t in_len const = this->inLength;
/* Your code here */
}
};
In pseudocode, the precise plaintext algorithm for SIEVE IR conversion is as follows.
void plaintext_conversion(out_len, in_len, out_prime, in_prime, out_vals[], in_vals[]) { int_equiv = 0; for(i from 0 to in_len) { int_equiv *= in_prime int_equiv += in_vals[i] } for(i from 1 to out_len + 1) { out_vals[out_len - i] = int_equiv % out_prime; int_equiv = int_equiv / out_prime; // division rounds down } // It is allowable for int_equiv to be non-zero here. }
Once your conversion implementation is working you can begin allocation.
The circuit_parser->conversions
vector holds specifications for each conversion (wtk::circuit::ConversionSpec
from #include <wtk/circuit/Data.h>
, and wtk::circuit::Parser<Number_T>
from #include <wtk/circuit/Parser.h>
).
The appropriate converter->convert(out_wires, in_wires)
method will be called when a convert gate is encountered.
Conversions must be added to NAILS via the interpreter.addConversion(/* ... */)
method.
for(size_t i = 0; i < circuit_parser->conversions.size(); i++)
{
wtk::circuit::ConversionSpec spec = &circuit_parser->conversions[i];
/* Your code here */
interpreter.addConversion(spec, my_converter);
}