diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..35ef5ca Binary files /dev/null and b/.DS_Store differ diff --git a/c/calcwit.cpp b/c/calcwit.cpp new file mode 100644 index 0000000..d1b454f --- /dev/null +++ b/c/calcwit.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include +#include +#include "calcwit.h" +#include "utils.h" + +Circom_CalcWit::Circom_CalcWit(Circom_Circuit *aCircuit) { + circuit = aCircuit; + +#ifdef SANITY_CHECK + signalAssigned = new bool[circuit->NSignals]; + signalAssigned[0] = true; +#endif + + inputSignalsToTrigger = new int[circuit->NComponents]; + signalValues = new BigInt[circuit->NSignals]; + + // Set one signal + mpz_init_set_ui(signalValues[0], 1); + + // Initialize remaining signals + for (int i=1; iNSignals; i++) mpz_init2(signalValues[i], 256); + + reset(); + +} + +void Circom_CalcWit::reset() { + +#ifdef SANITY_CHECK + for (int i=1; iNComponents; i++) signalAssigned[i] = false; +#endif + + for (int i=0; iNComponents; i++) { + inputSignalsToTrigger[i] = circuit->components[i].inputSignals; + if (inputSignalsToTrigger[i] == 0) triggerComponent(i); + } +} + + +Circom_CalcWit::~Circom_CalcWit() { +#ifdef SANITY_CHECK + delete signalAssigned; +#endif + + for (int i=0; iNSignals; i++) mpz_clear(signalValues[i]); + + delete[] signalValues; + delete inputSignalsToTrigger; + +} + +int Circom_CalcWit::getSubComponentOffset(int cIdx, u64 hash) { + int hIdx; + for(hIdx = int(hash & 0xFF); hash!=circuit->components[cIdx].hashTable[hIdx].hash; hIdx++) { + if (!circuit->components[cIdx].hashTable[hIdx].hash) throw std::runtime_error("hash not found: " + int_to_hex(hash)); + } + int entryPos = circuit->components[cIdx].hashTable[hIdx].pos; + if (circuit->components[cIdx].entries[entryPos].type != _typeComponent) { + throw std::runtime_error("invalid type"); + } + return circuit->components[cIdx].entries[entryPos].offset; +} + + +Circom_Sizes Circom_CalcWit::getSubComponentSizes(int cIdx, u64 hash) { + int hIdx; + for(hIdx = int(hash & 0xFF); hash!=circuit->components[cIdx].hashTable[hIdx].hash; hIdx++) { + if (!circuit->components[cIdx].hashTable[hIdx].hash) throw std::runtime_error("hash not found: " + int_to_hex(hash)); + } + int entryPos = circuit->components[cIdx].hashTable[hIdx].pos; + if (circuit->components[cIdx].entries[entryPos].type != _typeComponent) { + throw std::runtime_error("invalid type"); + } + return circuit->components[cIdx].entries[entryPos].sizes; +} + +int Circom_CalcWit::getSignalOffset(int cIdx, u64 hash) { + int hIdx; + for(hIdx = int(hash & 0xFF); hash!=circuit->components[cIdx].hashTable[hIdx].hash; hIdx++) { + if (!circuit->components[cIdx].hashTable[hIdx].hash) throw std::runtime_error("hash not found: " + int_to_hex(hash)); + } + int entryPos = circuit->components[cIdx].hashTable[hIdx].pos; + if (circuit->components[cIdx].entries[entryPos].type != _typeSignal) { + throw std::runtime_error("invalid type"); + } + return circuit->components[cIdx].entries[entryPos].offset; +} + +Circom_Sizes Circom_CalcWit::getSignalSizes(int cIdx, u64 hash) { + int hIdx; + for(hIdx = int(hash & 0xFF); hash!=circuit->components[cIdx].hashTable[hIdx].hash; hIdx++) { + if (!circuit->components[cIdx].hashTable[hIdx].hash) throw std::runtime_error("hash not found: " + int_to_hex(hash)); + } + int entryPos = circuit->components[cIdx].hashTable[hIdx].pos; + if (circuit->components[cIdx].entries[entryPos].type != _typeSignal) { + throw std::runtime_error("invalid type"); + } + return circuit->components[cIdx].entries[entryPos].sizes; +} + +PBigInt Circom_CalcWit::allocBigInts(Circom_Sizes sizes) { + PBigInt res = new BigInt[sizes[0]]; + for (int i=0; imapIsInput, sIdx) ) { + inputSignalsToTrigger[cIdx]--; + if (inputSignalsToTrigger[cIdx] == 0) triggerComponent(cIdx); + } + +} + +void Circom_CalcWit::checkConstraint(PBigInt value1, PBigInt value2, char const *err) { +#ifdef SANITY_CHECK + if (mpz_cmp(*value1, *value2) != 0) { + char *pcV1 = mpz_get_str(0, 10, *value1); + char *pcV2 = mpz_get_str(0, 10, *value1); + std::string sV1 = std::string(pcV1); + std::string sV2 = std::string(pcV2); + free(pcV1); + free(pcV2); + throw std::runtime_error(std::string("Constraint does not match,") + err + ". " + sV1 + " != " + sV2 ); + } +#endif +} + + +void Circom_CalcWit::triggerComponent(int newCIdx) { + int oldCIdx = cIdx; + cIdx = newCIdx; + (*(circuit->components[newCIdx].fn))(this); + cIdx = oldCIdx; +} + diff --git a/c/calcwit.h b/c/calcwit.h new file mode 100644 index 0000000..259fe4d --- /dev/null +++ b/c/calcwit.h @@ -0,0 +1,61 @@ +#ifndef CIRCOM_CALCWIT_H +#define CIRCOM_CALCWIT_H + +#include "circom.h" + +class Circom_CalcWit { + +#ifdef SANITY_CHECK + bool *signalAssigned; +#endif + + // componentStatus -> For each component + // >0 Signals required to trigger + // == 0 Component triggered + int *inputSignalsToTrigger; + + BigInt *signalValues; + + Circom_Circuit *circuit; + + + + void triggerComponent(int newCIdx); + void calculateWitness(void *input, void *output); + + +public: + int cIdx; +// Functions called by the circuit + Circom_CalcWit(Circom_Circuit *aCircuit); + ~Circom_CalcWit(); + + int getSubComponentOffset(int cIdx, u64 hash); + Circom_Sizes getSubComponentSizes(int cIdx, u64 hash); + int getSignalOffset(int cIdx, u64 hash); + Circom_Sizes getSignalSizes(int cIdx, u64 hash); + + PBigInt allocBigInts(Circom_Sizes sizes); + void freeBigInts(PBigInt bi, Circom_Sizes sizes); + + void getSignal(int cIdx, int sIdx, PBigInt value); + void setSignal(int cIdx, int sIdx, PBigInt value); + + void checkConstraint(PBigInt value1, PBigInt value2, char const *err); + + +// Public functions + inline void setInput(int idx, PBigInt val) { + setSignal(0, circuit->wit2sig[idx], val); + } + inline void getWitness(int idx, PBigInt val) { + mpz_set(*val, signalValues[circuit->wit2sig[idx]]); + } + + void reset(); + +}; + + + +#endif // CIRCOM_CALCWIT_H diff --git a/c/circom.h b/c/circom.h new file mode 100644 index 0000000..d81400d --- /dev/null +++ b/c/circom.h @@ -0,0 +1,56 @@ +#ifndef __CIRCOM_H +#define __CIRCOM_H + +#include +#include + +class Circom_CalcWit; +typedef unsigned long long u64; +typedef uint32_t u32; +typedef uint8_t u8; +typedef mpz_t BigInt; +typedef BigInt *PBigInt; + +typedef int Circom_Size; +typedef Circom_Size *Circom_Sizes; + +struct Circom_HashEntry { + u64 hash; + int pos; +}; +typedef Circom_HashEntry *Circom_HashTable; + +typedef enum { _typeSignal, _typeComponent} Circom_EntryType; + +struct Circom_ComponentEntry { + int offset; + Circom_Sizes sizes; + Circom_EntryType type; +}; +typedef Circom_ComponentEntry *Circom_ComponentEntries; + +typedef void (*Circom_ComponentFunction)(Circom_CalcWit *ctx); + +struct Circom_Component { + Circom_HashTable hashTable; + Circom_ComponentEntries entries; + Circom_ComponentFunction fn; + int inputSignals; +}; + +class Circom_Circuit { +public: + int NSignals; + int NComponents; + int NInputs; + int NOutputs; + int NVars; + int *wit2sig; + Circom_Component *components; + u32 *mapIsInput; +}; + +#define BITMAP_ISSET(m, b) (m[b>>5] & (1 << (b&0x1F))) +extern struct Circom_Circuit _circuit; + +#endif diff --git a/c/main.cpp b/c/main.cpp new file mode 100644 index 0000000..9abc482 --- /dev/null +++ b/c/main.cpp @@ -0,0 +1,196 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using json = nlohmann::json; + +#include "calcwit.h" +#include "circom.h" +#include "utils.h" + +#define handle_error(msg) \ + do { perror(msg); exit(EXIT_FAILURE); } while (0) + +void loadBin(Circom_CalcWit *ctx, std::string filename) { + int fd; + struct stat sb; + + // map input + fd = open(filename.c_str(), O_RDONLY); + if (fd == -1) + handle_error("open"); + + if (fstat(fd, &sb) == -1) /* To obtain file size */ + handle_error("fstat"); + + + u8 *in; + + in = (u8 *)mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (in == MAP_FAILED) + handle_error("mmap"); + + close(fd); + + BigInt v; + mpz_init2(v, 256); + u8 *p = in; + for (int i=0; i<_circuit.NInputs; i++) { + int len = *(u8 *)p; + p++; + mpz_import(v,len , -1 , 1, 0, 0, p); + p+=len; + ctx->setSignal(0, _circuit.wit2sig[1 + _circuit.NOutputs + i], &v); + } +} + + +typedef void (*ItFunc)(Circom_CalcWit *ctx, int idx, json val); + +void iterateArr(Circom_CalcWit *ctx, int o, Circom_Sizes sizes, json jarr, ItFunc f) { + if (!jarr.is_array()) { + assert((sizes[0] == 1)&&(sizes[1] == 0)); + f(ctx, o, jarr); + } else { + int n = sizes[0] / sizes[1]; + for (int i=0; i(); + } else if (val.is_number()) { + + double vd = val.get(); + std::stringstream stream; + stream << std::fixed << std::setprecision(0) << vd; + s = stream.str(); + } else { + handle_error("Invalid JSON type"); + } + + mpz_set_str (v, s.c_str(), 10); + + ctx->setSignal(0, o, &v); +} + + +void loadJson(Circom_CalcWit *ctx, std::string filename) { + std::ifstream inStream(filename); + json j; + inStream >> j; + + for (json::iterator it = j.begin(); it != j.end(); ++it) { +// std::cout << it.key() << " => " << it.value() << '\n'; + u64 h = fnv1a(it.key()); + int o = ctx->getSignalOffset(0, h); + Circom_Sizes sizes = ctx->getSignalSizes(0, h); + iterateArr(ctx, o, sizes, it.value(), itFunc); + } + +} + + +void writeOutBin(Circom_CalcWit *ctx, std::string filename) { + FILE *write_ptr; + + write_ptr = fopen(filename.c_str(),"wb"); + + BigInt v; + mpz_init2(v, 256); + + u8 buffOut[256]; + for (int i=0;i<_circuit.NVars;i++) { + size_t size=256; + ctx->getWitness(i, &v); + mpz_export(buffOut+1, &size, -1, 1, -1, 0, v); + *buffOut = (u8)size; + fwrite(buffOut, size+1, 1, write_ptr); + } + fclose(write_ptr); + +} + + +void writeOutJson(Circom_CalcWit *ctx, std::string filename) { + + std::ofstream outFile; + outFile.open (filename); + + outFile << "[\n"; + + BigInt v; + mpz_init2(v, 256); + + char pcV[256]; + + for (int i=0;i<_circuit.NVars;i++) { + ctx->getWitness(i, &v); + mpz_get_str(pcV, 10, v); + std::string sV = std::string(pcV); + outFile << (i ? "," : " ") << "\"" << sV << "\"\n"; + } + + outFile << "]\n"; + outFile.close(); +} + +bool hasEnding (std::string const &fullString, std::string const &ending) { + if (fullString.length() >= ending.length()) { + return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending)); + } else { + return false; + } +} + +int main(int argc, char *argv[]) { + if (argc!=3) { + std::string cl = argv[0]; + std::string base_filename = cl.substr(cl.find_last_of("/\\") + 1); + std::cout << "Usage: " << base_filename << " > >\n"; + } else { + + // open output + Circom_CalcWit *ctx = new Circom_CalcWit(&_circuit); + + std::string infilename = argv[1]; + + if (hasEnding(infilename, std::string(".bin"))) { + loadBin(ctx, infilename); + } else if (hasEnding(infilename, std::string(".json"))) { + loadJson(ctx, infilename); + } else { + handle_error("Invalid input extension (.bin / .json)"); + } + + + std::string outfilename = argv[2]; + + if (hasEnding(outfilename, std::string(".bin"))) { + writeOutBin(ctx, outfilename); + } else if (hasEnding(outfilename, std::string(".json"))) { + writeOutJson(ctx, outfilename); + } else { + handle_error("Invalid output extension (.bin / .json)"); + } + + delete ctx; + exit(EXIT_SUCCESS); + } +} diff --git a/c/mainjson b/c/mainjson new file mode 100644 index 0000000..3809f76 Binary files /dev/null and b/c/mainjson differ diff --git a/c/mainjson.cpp b/c/mainjson.cpp new file mode 100644 index 0000000..b711e1a --- /dev/null +++ b/c/mainjson.cpp @@ -0,0 +1,47 @@ +#include +#include +using json = nlohmann::json; + + +#include "utils.h" +#include "circom.h" +#include "calcwit.h" + +auto j = R"( + { + "in": "314" + } +)"_json; + +typedef void (*ItFunc)(int idx, json val); + +void iterateArr(int o, Circom_Sizes sizes, json jarr, ItFunc f) { + if (!jarr.is_array()) { + assert((sizes[0] == 1)&&(sizes[1] == 0)); + f(o, jarr); + } else { + int n = sizes[0] / sizes[1]; + for (int i=0; i " << it.value() << '\n'; + u64 h = fnv1a(it.key()); + int o = ctx->getSignalOffset(0, h); + Circom_Sizes sizes = ctx->getSignalSizes(0, h); + iterateArr(o, sizes, it.value(), itFunc); + } +} + diff --git a/c/utils.cpp b/c/utils.cpp new file mode 100644 index 0000000..45124d8 --- /dev/null +++ b/c/utils.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include + +#include "utils.h" + +std::string int_to_hex( u64 i ) +{ + std::stringstream stream; + stream << "0x" + << std::setfill ('0') << std::setw(16) + << std::hex << i; + return stream.str(); +} + +u64 fnv1a(std::string s) { + u64 hash = 0xCBF29CE484222325LL; + for(char& c : s) { + hash ^= u64(c); + hash *= 0x100000001B3LL; + } + return hash; +} diff --git a/c/utils.h b/c/utils.h new file mode 100644 index 0000000..e2d72bf --- /dev/null +++ b/c/utils.h @@ -0,0 +1,10 @@ +#ifndef __UTILS_H +#define __UTILS_H + +#include "circom.h" + +std::string int_to_hex( u64 i ); +u64 fnv1a(std::string s); + + +#endif // __UTILS_H diff --git a/cli.js b/cli.js index 3aec47b..86c7cef 100755 --- a/cli.js +++ b/cli.js @@ -30,8 +30,11 @@ const version = require("./package").version; const argv = require("yargs") .version(version) - .usage("circom [input source circuit file] -o [output definition circuit file]") + .usage("circom [input source circuit file] -o [output definition circuit file] -c [output c file]") .alias("o", "output") + .alias("c", "csource") + .alias("s", "sym") + .alias("r", "r1cs") .help("h") .alias("h", "help") .option("verbose", { @@ -63,11 +66,49 @@ if (argv._.length == 0) { } const fullFileName = path.resolve(process.cwd(), inputFile); -const outName = argv.output ? argv.output : "circuit.json"; +const fileName = path.basename(fullFileName, ".circom"); +const outName = argv.output ? argv.output : fileName + ".json"; +const cSourceName = typeof(argv.csource) === "string" ? argv.csource : fileName + ".cpp"; +const r1csName = typeof(argv.r1cs) === "string" ? argv.r1cs : fileName + ".r1cs"; +const symName = typeof(argv.sym) === "string" ? argv.sym : fileName + ".sym"; + +const options = {}; +options.reduceConstraints = !argv.fast; +options.verbose = argv.verbose || false; +if (argv.csource) { + options.cSourceWriteStream = fs.createWriteStream(cSourceName); +} +if (argv.r1cs) { + options.r1csWriteStream = fs.createWriteStream(r1csName); +} +if (argv.sym) { + options.symWriteStream = fs.createWriteStream(symName); +} -compiler(fullFileName, {reduceConstraints: !argv.fast, verbose: argv.verbose}).then( (cir) => { - fs.writeFileSync(outName, JSON.stringify(cir, null, 1), "utf8"); - process.exit(0); +compiler(fullFileName, options).then( () => { + let r1csDone = false; + let cSourceDone = false; + if (options.r1csWriteStream) { + options.r1csWriteStream.end(() => { + r1csDone = true; + finishIfDone(); + }); + } else { + r1csDone = true; + } + if (options.cSourceWriteStream) { + options.cSourceWriteStream.end(() => { + cSourceDone = true; + finishIfDone(); + }); + } else { + cSourceDone = true; + } + function finishIfDone() { + if ((r1csDone)&&(cSourceDone)) { + process.exit(0); + } + } }, (err) => { // console.log(err); console.log(err.stack); diff --git a/doc/lc_example.monopic b/doc/lc_example.monopic new file mode 100644 index 0000000..2293d99 Binary files /dev/null and b/doc/lc_example.monopic differ diff --git a/doc/r1cs_bin_format.md b/doc/r1cs_bin_format.md new file mode 100644 index 0000000..d1e8cc6 --- /dev/null +++ b/doc/r1cs_bin_format.md @@ -0,0 +1,489 @@ +# Binary format for R1CS + +--- +eip: +title: r1cs binary format +author: Jordi Baylina +discussions-to: +status: draft +type: Standards Track +category: ERC +created: 2019-09-24 +requires: +--- + +## Simple Summary + +This standard defines a standard format for a binery representation of a r1cs constraint system. + +## Abstract + + +## Motivation + +The zero knowledge primitives, requires the definition of a statment that wants to be proved. This statment can be expressed as a deterministric program or an algebraic circuit. Lots of primitives like zkSnarks, bulletProofs or aurora, requires to convert this statment to a rank-one constraint system. + +This standard specifies a format for a r1cs and allows the to connect a set of tools that compiles a program or a circuit to r1cs that can be used for the zksnarks or bulletproofs primitives. + +## Specification + +### General considerations + +All integers are represented in Little Endian Fix size format + +The standard extension is `.r1cs` + +The constraint is in the form + +$$ +\left\{ \begin{array}{rclclcl} +(a_{0,0}s_0 + a_{0,1}s_1 + ... + a_{0,n-1}s_{n-1}) &\cdot& (b_{0,0} s_0 + b_{0,1} s_1 + ... + b_{0,n-1} s_{n-1}) &-& (c_{0,0} s_0 + c_{0,1} s_1 + ... + c_{0,n-1}s_{n-1}) &=& 0 \\ +(a_{1,0}s_0 + a_{1,1}s_1 + ... + a_{1,n-1}s_{n-1}) &\cdot& (b_{1,0} s_0 + b_{1,1} s_1 + ... + b_{1,n-1} s_{n-1}) &-& (c_{1,0} s_0 + c_{1,1}s_1 + ... + c_{1,n-1}s_{n-1}) &=& 0 \\ +...\\ +(a_{m-1,0}s_0 + a_{m-1,1}s_1 + ... + a_{m-1,n-1}s_{n-1}) &\cdot& (b_{m-1,0} s_0 + b_{m-1,1} s_1 + ... + b_{m-1,n-1} s_{n-1}) &-& (c_{m-1,0} s_0 + c_{m-1,1}s_1 + ... + c_{m-1,n-1}s_{n-1}) &=& 0 + \end{array} \right. +$$ + + +### Format + +```` + + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ + ┃ 4 │ 72 31 63 73 ┃ Magic "r1cs" + ┗━━━━┻━━━━━━━━━━━━━━━━━┛ + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ + ┃ 4 │ 01 00 00 00 ┃ Version 1 + ┗━━━━┻━━━━━━━━━━━━━━━━━┛ + + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ + ┃ 4 │ nW ┃ + ┗━━━━┻━━━━━━━━━━━━━━━━━┛ + + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ + ┃ nW │ 01 00 00 00 ┃ nWires + ┗━━━━┻━━━━━━━━━━━━━━━━━┛ + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ + ┃ nW │ 01 00 00 00 ┃ nPubOut + ┗━━━━┻━━━━━━━━━━━━━━━━━┛ + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ + ┃ nW │ 01 00 00 00 ┃ nPubIn + ┗━━━━┻━━━━━━━━━━━━━━━━━┛ + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ + ┃ nW │ 01 00 00 00 ┃ nPrvIn + ┗━━━━┻━━━━━━━━━━━━━━━━━┛ + + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ + ┃ nW │m := NConstraints┃ + ┗━━━━┻━━━━━━━━━━━━━━━━━┛ + + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ ╲ + ┃ nW │ nA ┃ ╲ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ ╲ + ┃ nW │ idx_1 ┃ V │ a_{0,idx_1} ┃ │ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ │ + ┃ nW │ idx_2 ┃ V │ a_{0,idx_2} ┃ │ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │ + ... ... │ + ┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ idx_nA ┃ V │ a_{0,idx_nA} ┃ │ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │ + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ nB ┃ │ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ idx_1 ┃ V │ b_{0,idx_1} ┃ │ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ ╲ + ┃ nW │ idx_2 ┃ V │ b_{0,idx_2} ┃ ╲ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ ╱ Constraint_0 + ... ... ╱ + ┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ idx_nB ┃ V │ b_{0,idx_nB} ┃ │ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │ + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ nC ┃ │ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ idx_1 ┃ V │ c_{0,idx_1} ┃ │ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ │ + ┃ nW │ idx_2 ┃ V │ c_{0,idx_2} ┃ │ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │ + ... ... │ + ┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ idx_nB ┃ V │ c_{0,idx_nC} ┃ ╱ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ ╱ + ╱ + + + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ ╲ + ┃ nW │ nA ┃ ╲ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ ╲ + ┃ nW │ idx_1 ┃ V │ a_{1,idx_1} ┃ │ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ │ + ┃ nW │ idx_2 ┃ V │ a_{1,idx_2} ┃ │ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │ + ... ... │ + ┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ idx_nA ┃ V │ a_{1,idx_nA} ┃ │ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │ + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ nB ┃ │ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ idx_1 ┃ V │ b_{1,idx_1} ┃ │ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ ╲ + ┃ nW │ idx_2 ┃ V │ b_{1,idx_2} ┃ ╲ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ ╱ Constraint_1 + ... ... ╱ + ┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ idx_nB ┃ V │ b_{1,idx_nB} ┃ │ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │ + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ nC ┃ │ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ idx_1 ┃ V │ c_{1,idx_1} ┃ │ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ │ + ┃ nW │ idx_2 ┃ V │ c_{1,idx_2} ┃ │ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │ + ... ... │ + ┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ idx_nB ┃ V │ c_{1,idx_nC} ┃ ╱ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ ╱ + ╱ + + ... + ... + ... + + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ ╲ + ┃ nW │ nA ┃ ╲ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ ╲ + ┃ nW │ idx_1 ┃ V │ a_{m-1,idx_1} ┃ │ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ │ + ┃ nW │ idx_2 ┃ V │ a_{m-1,idx_2} ┃ │ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │ + ... ... │ + ┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ idx_nA ┃ V │ a_{m-1,idx_nA} ┃ │ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │ + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ nB ┃ │ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ idx_1 ┃ V │ b_{m-1,idx_1} ┃ │ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ ╲ + ┃ nW │ idx_2 ┃ V │ b_{m-1,idx_2} ┃ ╲ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ ╱ Constraint_{m-1} + ... ... ╱ + ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │ + ┃ nW idx_nB ┃ V │ b_{m-1,idx_nB} ┃ │ + ┗━━━━━━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │ + ┏━━━━┳━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ nC ┃ │ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ idx_1 ┃ V │ c_{m-1,idx_1} ┃ │ + ┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ │ + ┃ nW │ idx_2 ┃ V │ c_{m-1,idx_2} ┃ │ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │ + ... ... │ + ┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │ + ┃ nW │ idx_nB ┃ V │ c_{m-1,idx_nC} ┃ ╱ + ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ ╱ + ╱ ╱ +```` + + +### Magic Number + +Size: 4 bytes +The file start with a constant 4 byts (magic number) "r1cs" + +``` +0x72 0x31 0x63 0x73 +``` + +### Version + +Size: 4 bytes +Format: Little-Endian + +For this standard it's fixed to + +``` +0x01 0x00 0x00 0x00 +``` + +### Word With (nW) + +Size: 4 bytes +Format: Little-Endian + +This is the standard word size in bytes used to specify lenghts and indexes in the file. + +The format of this field is little endian. + +In most of the cases this will be 4 (32bit values) + +Example: +``` +0x04 0x00 0x00 0x00 +``` + +### Number of wires + +Size: nW bytes +Format: Little-Endian + +Total Number of wires including ONE signal (Index 0). + +### Number of public outputs + +Size: nW bytes +Format: Little-Endian + +Total Number of wires public output wires. They should be starting at idx 1 + +### Number of public inputs + +Size: nW bytes +Format: Little-Endian + +Total Number of wires public input wires. They should be starting just after the public output + +### Number of private inputs + +Size: nW bytes +Format: Little-Endian + +Total Number of wires private input wires. They should be starting just after the public inputs + +### Number of constraints + +Size: nW bytes +Format: Little-Endian + +Total Number of constraints + +### Constraints + +Each constraint contains 3 linear combinations A, B, C. + +The constraint is such that: +``` +A*B-C = 0 +``` + +### Linear combination + +Each linear combination is of the form: + +$$ +a_{0,0}s_0 + a_{0,1}s_1 + ... + a_{0,n-1}s_{n-1} +$$ + +### Number of nonZero Factors + +Size: nW bytes +Format: Little-Endian + +Total number of non Zero factors in the linear compination. + +The factors MUST be sorted in ascending order. + +### Factor + +For each factor we have the index of the factor and the value of the factor. + +### Index of the factor + + +Size: nW bytes +Format: Little-Endian + +Index of the nonZero Factor + +### Value of the factor + +The first byte indicate the length N in bytes of the number in the upcoming bytes. + +The next N bytes represent the value in Little Endian format. + +For example, to represent the linear combination: + +$$ +5s_4 +8s_5 + 260s_886 +$$ + +The linear combination would be represented as: + +```` + ┏━━━━━━━━━━━━━━━━━┓ + ┃ 03 00 00 00 ┃ + ┣━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┓ + ┃ 04 00 00 00 ┃ 01 05 ┃ + ┣━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┫ + ┃ 05 00 00 00 ┃ 01 08 ┃ + ┣━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┫ + ┃ 76 03 00 00 ┃ 02 04 01 ┃ + ┗━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━┛ +```` +## Rationale + +Variable size for field elements allows to shrink the size of the file and allows to work with any field. + +Version allows to update the format. + +Have a very good comprasion ratio for sparse r1cs as it's the normal case. + + +## Backward Compatibility + +N.A. + +## Test Cases +### Example + +Given this r1cs in a 256 bit Field: + + +$$ +\left\{ \begin{array}{rclclcl} +(3s_5 + 8s_6) &\cdot& (2s_0 + 20s_2 + 12s_3) &-& (5s_0 + 7s_9) &=& 0 \\ +(4s_1 + 8s_5 + 3s_9) &\cdot& (6s_6 + 44s_3) && &=& 0 \\ +(4s_6) &\cdot& (6s_0 + 5s_3 + 11s_9) &-& (600s_700) &=& 0 +\end{array} \right. +$$ + +The format will be: + +```` + + ┏━━━━━━━━━━━━━━┓ + ┃ 72 31 63 77 ┃ Magic + ┣━━━━━━━━━━━━━━┫ + ┃ 01 00 00 00 ┃ Version + ┣━━━━━━━━━━━━━━┫ + ┃ 04 00 00 00 ┃ nW + ┣━━━━━━━━━━━━━━┫ + ┃ 04 23 45 00 ┃ # of wires + ┣━━━━━━━━━━━━━━┫ + ┃ 01 00 00 00 ┃ # Public Outs + ┣━━━━━━━━━━━━━━┫ + ┃ 02 00 00 00 ┃ # Public Ins + ┣━━━━━━━━━━━━━━┫ + ┃ 05 00 00 00 ┃ # Private Ins + ┗━━━━━━━━━━━━━━┛ + + ┏━━━━━━━━━━━━━━┓ + ┃ 03 00 00 00 ┃ # of constraints + ┗━━━━━━━━━━━━━━┛ + + ┏━━━━━━━━━━━━━━┓ Constraint 0: (3s_5 + 8s_6) * (2s_0 + 20s_2 + 12s_3) - (5s_0 + 7s_9) = 0 + ┃ 02 00 00 00 ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━┓ + ┃ 05 00 00 00 ┃ 01 03 ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━┫ + ┃ 06 00 00 00 ┃ 01 08 ┃ + ┗━━━━━━━━━━━━━━┻━━━━━━━━┛ + ┏━━━━━━━━━━━━━━┓ + ┃ 03 00 00 00 ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━┓ + ┃ 00 00 00 00 ┃ 01 02 ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━┫ + ┃ 02 00 00 00 ┃ 01 14 ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━┫ + ┃ 03 00 00 00 ┃ 01 0C ┃ + ┗━━━━━━━━━━━━━━┻━━━━━━━━┛ + ┏━━━━━━━━━━━━━━┓ + ┃ 02 00 00 00 ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━┓ + ┃ 00 00 00 00 ┃ 01 05 ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━┫ + ┃ 09 00 00 00 ┃ 01 07 ┃ + ┗━━━━━━━━━━━━━━┻━━━━━━━━┛ + + + ┏━━━━━━━━━━━━━━┓ Constraint 1: (4s_1 + 8s_5 + 3s_9) * (6s_6 + 44s_3) = 0 + ┃ 03 00 00 00 ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━━┓ + ┃ 01 00 00 00 ┃ 01 04 ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━━┫ + ┃ 05 00 00 00 ┃ 01 08 ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━━┫ + ┃ 09 00 00 00 ┃ 01 03 ┃ + ┗━━━━━━━━━━━━━━┻━━━━━━━━━┛ + ┏━━━━━━━━━━━━━━┓ + ┃ 02 00 00 00 ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━━┓ + ┃ 03 00 00 00 ┃ 01 2C ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━━┫ + ┃ 06 00 00 00 ┃ 01 06 ┃ + ┗━━━━━━━━━━━━━━┻━━━━━━━━━┛ + ┏━━━━━━━━━━━━━━┓ + ┃ 00 00 00 00 ┃ + ┗━━━━━━━━━━━━━━┛ + + + ┏━━━━━━━━━━━━━━┓ Constraint 2: (4s_6) * (6s_0 + 5s_3 + 11s_9) - (600s_700) = 0 + ┃ 01 00 00 00 ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━━┓ + ┃ 06 00 00 00 ┃ 01 04 ┃ + ┗━━━━━━━━━━━━━━┻━━━━━━━━━┛ + ┏━━━━━━━━━━━━━━┓ + ┃ 03 00 00 00 ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━━┓ + ┃ 00 00 00 00 ┃ 01 06 ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━━┫ + ┃ 03 00 00 00 ┃ 01 05 ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━━┫ + ┃ 09 00 00 00 ┃ 01 0B ┃ + ┗━━━━━━━━━━━━━━┻━━━━━━━━━┛ + ┏━━━━━━━━━━━━━━┓ + ┃ 01 00 00 00 ┃ + ┣━━━━━━━━━━━━━━╋━━━━━━━━━━━━━┓ + ┃ BC 02 00 00 ┃ 02 58 02 ┃ + ┗━━━━━━━━━━━━━━┻━━━━━━━━━━━━━┛ +```` + +And the binary representation in Hex: + +```` +72 31 63 77 +01 00 00 00 +04 00 00 00 +04 23 45 00 +01 00 00 00 +02 00 00 00 +05 00 00 00 +03 00 00 00 +02 00 00 00 +05 00 00 00 01 03 +06 00 00 00 01 08 +03 00 00 00 +00 00 00 00 01 02 +02 00 00 00 01 14 +03 00 00 00 01 0C +02 00 00 00 +00 00 00 00 01 05 +09 00 00 00 01 07 +03 00 00 00 +01 00 00 00 01 04 +05 00 00 00 01 08 +09 00 00 00 01 03 +02 00 00 00 +03 00 00 00 01 2C +06 00 00 00 01 06 +00 00 00 00 +01 00 00 00 +06 00 00 00 01 04 +03 00 00 00 +00 00 00 00 01 06 +03 00 00 00 01 05 +09 00 00 00 01 0B +01 00 00 00 +BC 02 00 00 02 58 02 +```` + +## Implementation + +circom will output this format. + +## Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). diff --git a/doc/r1cs_bin_format.monopic b/doc/r1cs_bin_format.monopic new file mode 100644 index 0000000..b49bf3a Binary files /dev/null and b/doc/r1cs_bin_format.monopic differ diff --git a/doc/r1cs_example.monopic b/doc/r1cs_example.monopic new file mode 100644 index 0000000..e4aa83b Binary files /dev/null and b/doc/r1cs_example.monopic differ diff --git a/index.js b/index.js index 2ef5011..069964a 100644 --- a/index.js +++ b/index.js @@ -1 +1,2 @@ -module.exports = require("./src/compiler.js"); +module.exports.compiler = require("./src/compiler.js"); +module.exports.c_tester = require("./src/c_tester.js"); diff --git a/package-lock.json b/package-lock.json index 6e6df2e..66bb739 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,8 +111,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "big-integer": { "version": "1.6.43", @@ -132,7 +131,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -249,8 +247,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "cross-spawn": { "version": "6.0.5", @@ -585,11 +582,15 @@ "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", "dev": true }, + "fnv-plus": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/fnv-plus/-/fnv-plus-1.3.1.tgz", + "integrity": "sha512-Gz1EvfOneuFfk4yG458dJ3TLJ7gV19q3OM/vVvvHf7eT02Hm1DleB4edsia6ahbKgAYxO9gvyQ1ioWZR+a00Yw==" + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "functional-red-black-tree": { "version": "1.0.1", @@ -620,7 +621,6 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -677,7 +677,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -686,8 +685,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "inquirer": { "version": "6.2.2", @@ -911,7 +909,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1107,8 +1104,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -1196,11 +1192,18 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, "requires": { "glob": "^7.1.3" } }, + "rimraf-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/rimraf-promise/-/rimraf-promise-2.0.0.tgz", + "integrity": "sha1-PdvkN4wa3slmvDZt37yYUUPHaVI=", + "requires": { + "rimraf": "^2.4.3" + } + }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", @@ -1405,6 +1408,24 @@ "os-tmpdir": "~1.0.2" } }, + "tmp-promise": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-2.0.2.tgz", + "integrity": "sha512-zl71nFWjPKW2KXs+73gEk8RmqvtAeXPxhWDkTUoa3MSMkjq3I+9OeknjF178MQoMYsdqL730hfzvNfEkePxq9Q==", + "requires": { + "tmp": "0.1.0" + }, + "dependencies": { + "tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "requires": { + "rimraf": "^2.6.3" + } + } + } + }, "tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", diff --git a/package.json b/package.json index 626e695..c0809a1 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,10 @@ }, "dependencies": { "big-integer": "^1.6.32", + "fnv-plus": "^1.3.1", "optimist": "^0.6.1", + "rimraf-promise": "^2.0.0", + "tmp-promise": "^2.0.2", "yargs": "^12.0.2" }, "devDependencies": { diff --git a/src/buildwasm.js b/src/buildwasm.js new file mode 100644 index 0000000..1e0d257 --- /dev/null +++ b/src/buildwasm.js @@ -0,0 +1,52 @@ + +const ModuleBuilder = require("wasmbuilder").ModuleBuilder; +const gen = require("./gencode"); + + +module.exports = function buildWasm(ctx) { + + const fDefined = {}; + + ctx.module = new ModuleBuilder(); + for (let f in ctx.functions) { + ctx.f = ctx.module.addFunction(f); + ctx.c = ctx.f.getCodeBuilder(); + + ctx.scope = {}; + for (let p in ctx.functions[f].params) { + const param = ctx.functions[f].params[p]; + ctx.f.addParam(param.name, "i32"); + ctx.scope[param.name] = { + type: "PARAM", + sels: param.sels, + getter: () => { return ctx.c.getLocal(param.name); }, + setter: (v) => { return ctx.c.setLocal(param.name, v); } + }; + } + + gen(ctx, ctx.functions[f].block); + } + + for (let i=0; i. +*/ + +const utils = require("./utils"); +const assert = require("assert"); +const gen = require("./c_gen"); + +module.exports = buildC; + + +function buildC(ctx) { + ctx.code = ""; + ctx.tmpNames = {}; + ctx.getTmpName = getTmpName; + ctx.codes_sizes = []; + ctx.definedSizes = {}; + ctx.addSizes = addSizes; + + const entryTables = buildEntryTables(ctx); + const code = buildCode(ctx); + const compnentsArray = buildComponentsArray(ctx); + + const headder = buildHeader(ctx); + const sizes = buildSizes(ctx); + const mapIsInput = buildMapIsInput(ctx); + const wit2Sig = buildWit2Sig(ctx); + const circuitVar = buildCircuitVar(ctx); + + return "" + + headder + "\n" + + sizes + "\n" + + entryTables + "\n" + + code + "\n" + + compnentsArray + "\n" + + mapIsInput + "\n" + + wit2Sig +"\n" + + circuitVar; +} + +function buildEntryTables(ctx) { + + const codes_hashMaps = []; + const codes_componentEntries = []; + const definedHashTables = {}; + for (let i=0; i0 ? " ," : " "; + const sizeName = ctx.addSizes(entry.sizes); + + const ty = entry.type == "S" ? "_typeSignal" : "_typeComponent"; + code += `{${entry.offset},${sizeName}, ${ty}}\n`; + } + code += "};\n"; + codes_componentEntries.push(code); + + ctx.components[i].htName = htName; + ctx.components[i].etName = componentEntriesTableName; + } + + + return "" + + "// HashMaps\n" + + codes_hashMaps.join("\n") + "\n" + + "\n" + + "// Component Entries\n" + + codes_componentEntries.join("\n") + "\n" + + "\n"; + + function addHashTable(cIdx) { + const keys = Object.keys(ctx.components[cIdx].names.o); + assert(keys.length<128); + keys.sort((a,b) => ((a>b) ? 1 : -1)); + const h = utils.fnvHash(keys.join(",")); + if (definedHashTables[h]) return definedHashTables[h]; + definedHashTables[h] = {}; + definedHashTables[h].htName = ctx.getTmpName("ht_"+ctx.components[cIdx].template); + definedHashTables[h].htMap = []; + const t = []; + for (let i=0; i0 ? "," : ""; + if (t[i]) { + code += `{0x${t[i][0]}LL, ${t[i][1]}} /* ${keys[t[i][1]]} */`; + } else { + code += "{0,0}"; + } + } + code += "};\n"; + + codes_hashMaps.push(code); + + return definedHashTables[h]; + } +} + +function buildCode(ctx) { + const globalNames = ctx.tmpNames; + + const fDefined = {}; + + const functions = []; + for (let f in ctx.functions) { + ctx.scope = {}; + const paramsList = []; + for (let p in ctx.functions[f].params) { + const param = ctx.functions[f].params[p]; + paramsList.push("POINTER "+param.name); + + ctx.scope[param.name] = { + type: "LOCAL", + sels: param.sels, + getter: () => { return param.name; }, + }; + } + + ctx.code += "void "+f+"(POINTER _ret, "+paramsList.join(",")+") {\n"; + + ctx.code += gen(ctx, ctx.functions[f].block); + ctx.code += "}"; + } + + for (let i=0; i0 ? " ," : " "); + ccodes.push(`{${ctx.components[i].htName},${ctx.components[i].etName},${ctx.components[i].fnName}, ${ctx.components[i].nInSignals}}\n`); + } + ccodes.push("};\n"); + const codeComponents = ccodes.join(""); + + return "" + + "// Components\n" + + codeComponents + + "\n"; +} + + +function buildHeader(ctx) { + return "#include \"circom.h\"\n" + + "#include \"calcwit.h\"\n" + + `#define NSignals ${ctx.signals.length}\n` + + `#define NComponents ${ctx.components.length}\n` + + `#define NInputs ${ctx.components[ ctx.getComponentIdx("main") ].nInSignals}\n`+ + `#define NOutputs ${ctx.totals[ ctx.stOUTPUT ]}\n`+ + `#define NVars ${ctx.totals[ctx.stONE] + ctx.totals[ctx.stOUTPUT] + ctx.totals[ctx.stPUBINPUT] + ctx.totals[ctx.stPRVINPUT] + ctx.totals[ctx.stINTERNAL]}\n` + + "\n"; +} + +function buildSizes(ctx) { + return "// Sizes\n" + + ctx.codes_sizes.join("\n"); +} + + +function buildMapIsInput(ctx) { + const arr = []; + let line = ""; + let acc = 0; + let i; + for (i=0; i31) ? "," : " "; + line += toHex(acc); + acc = 0; + if ( i % (32*64) == 0) { + arr.push(line); + line = ""; + } + } + } + + if ((i%32) != 0) { + line += (i>31) ? "," : " "; + line += toHex(acc); + } + if (line != "") { + arr.push(line); + } + + return "// mapIsArray\n" + + `u32 _mapIsInput[${Math.floor((ctx.signals.length-1) / 32)+1}] = {\n`+ + arr.join("\n") + "\n" + + "};\n"; + + function toHex(number) { + if (number < 0) number = 0xFFFFFFFF + number + 1; + let S=number.toString(16).toUpperCase(); + while (S.length<8) S = "0" + S; + return "0x"+S; + } +} + + +function buildWit2Sig(ctx) { + const codes = []; + const NVars = + ctx.totals[ctx.stONE] + + ctx.totals[ctx.stOUTPUT] + + ctx.totals[ctx.stPUBINPUT] + + ctx.totals[ctx.stPRVINPUT] + + ctx.totals[ctx.stINTERNAL]; + const arr = Array(NVars); + for (let i=0; i=0) continue; // If has an alias, continue.. + assert(outIdx0) ? ",": " "; + code += arr[i]; + if ((i>0)&&(i%64 == 0)) { + if (code != "") codes.push(code + "\n"); + codes.push(code); + code =0; + } + } + if (code != "") codes.push(code + "\n"); + codes.push("};\n"); + + return codes.join(""); + +} + + +function buildCircuitVar() { + return "Circom_Circuit _circuit = {\n" + + " NSignals,\n"+ + " NComponents,\n"+ + " NInputs,\n"+ + " NOutputs,\n"+ + " NVars,\n"+ + " _wit2sig,\n"+ + " _components,\n"+ + " _mapIsInput\n"+ + "};\n"; +} + + + +function hashComponentCall(ctx, cIdx) { + // TODO: At the moment generate a diferent function for each instance of the component + return cIdx; +} + +function getTmpName(_suggestedName) { + let suggestedName; + if (_suggestedName) { + suggestedName = trimUnderscore(_suggestedName); + } else { + suggestedName = "tmp"; + } + + if (typeof(this.tmpNames[suggestedName]) == "undefined") { + this.tmpNames[suggestedName] = 1; + return "_"+suggestedName; + } else { + const name = "_" + suggestedName + "_" + this.tmpNames[suggestedName]; + this.tmpNames[suggestedName]++; + return name; + } + + function trimUnderscore(str) { + let p1=0; + while ((p1 < str.length)&&(str[p1] == "_")) p1++; + let p2=str.length; + while ((p2 > 0)&&(str[p2-1] == "_")) p2--; + + return str.slice(p1,p2); + } + +} + +function addSizes(_sizes) { + const sizes = _sizes || []; + let name = "sizes"; + for (let i=0; i0) code += ","; + code += accSizes[i]; + } + code += "};\n"; + this.codes_sizes.push(code); + + return labelName; +} + diff --git a/src/c_gen.js b/src/c_gen.js new file mode 100644 index 0000000..d7945b7 --- /dev/null +++ b/src/c_gen.js @@ -0,0 +1,941 @@ +const bigInt = require("big-integer"); +const utils = require("./utils"); + +module.exports = gen; + + +function newRef(ctx, type, _name, value, _sizes) { + const isValue = ((typeof(value) != "undefined")&&(value != null)); + let name; + let sizes; + if (!_name) { + name = ctx.getTmpName(); + } else { + if (_name[0] =="_") { + name = ctx.getTmpName(_name); + } else { + name = _name; + } + } + if (typeof(_sizes) == "string") { + sizes = _sizes; + } else if (Array.isArray(_sizes)) { + sizes = newSizes(ctx, _sizes); + } else if (isValue) { + sizes = newSizes(ctx, utils.extractSizes(value)); + } else { + sizes = newSizes(ctx, []); + } + + const scope = ctx.scopes[ctx.scopes.length-1]; + if (scope[name]) return error("Variable already exists: " + name); + + const label = scope._prefix + name; + scope[name] = { + stack: true, + type: type, + used: false, + sizes: sizes, + label: label + }; + + if (isValue) { + scope[name].value = value; + } + + return name; +} + +function newSizes(ctx, sizes) { + const scope = ctx.scopes[ctx.scopes.length-1]; + + const name = ctx.getTmpName("_sz"); + + scope[name] = { + stack: true, + type: "SIZES", + used: false, + dim: sizes.length, + label: name, + value: sizes + }; + + return name; +} + + +function instantiateRef(ctx, name) { + const v = getScope(ctx, name); + if (!v.stack) return error("Using a non existing var: " + name); + if (v.used) return; + + if (v.type=="BIGINT") { + + const iSize = getScope(ctx, v.sizes); + + if (iSize.used) { + const labelSize = iSize.label; + ctx.codeHeader += `PBigInt ${v.label};\n`; + ctx.code += `${v.label} = ctx->allocBigInts(${labelSize});\n`; + ctx.codeFooter += `ctx->freeBigInts(${v.label}, ${labelSize});\n`; + } else if (iSize.value) { + const labelSize = ctx.addSizes(iSize.value); + ctx.codeHeader += `PBigInt ${v.label} = ctx->allocBigInts(${labelSize});\n`; + ctx.codeFooter += `ctx->freeBigInts(${v.label}, ${labelSize});\n`; + } else { + return error(ctx, null, "Undefined Sizes: " +name); + } + + } else if (v.type=="INT") { + ctx.codeHeader += `int ${v.label};\n`; + } else if (v.type=="SIZES") { + ctx.codeHeader += `Circom_Sizes ${v.label};\n`; + } + v.used = true; +} + + +function error(ctx, ast, errStr) { + ctx.error = { + pos: { + first_line: ast.first_line, + first_column: ast.first_column, + last_line: ast.last_line, + last_column: ast.last_column + }, + errStr: errStr, + ast: ast, + message: errStr + }; +} + + +function getScope(ctx, name) { + for (let i=ctx.scopes.length-1; i>=0; i--) { + if (ctx.scopes[i][name]) return ctx.scopes[i][name]; + } + return null; +} + + + +function gen(ctx, ast) { + if ((ast.type == "NUMBER") ) { + return genNumber(ctx, ast); + } else if (ast.type == "VARIABLE") { + return genVariable(ctx, ast); + } else if (ast.type == "PIN") { + return genPin(ctx, ast); + } else if (ast.type == "OP") { + if (ast.op == "=") { + return genVarAssignement(ctx, ast); + } else if (ast.op == "<--") { + return genVarAssignement(ctx, ast); + } else if (ast.op == "<==") { + return genSignalAssignConstrain(ctx, ast); + } else if (ast.op == "===") { + return genConstraint(ctx, ast); + } else if (ast.op == "+=") { + return genVarAddAssignement(ctx, ast); + } else if (ast.op == "*=") { + return genVarMulAssignement(ctx, ast); + } else if (ast.op == "+") { + return genAdd(ctx, ast); + } else if (ast.op == "-") { + return genSub(ctx, ast); + } else if (ast.op == "UMINUS") { + return genUMinus(ctx, ast); + } else if (ast.op == "*") { + return genMul(ctx, ast); + } else if (ast.op == "%") { + return genMod(ctx, ast); + } else if (ast.op == "PLUSPLUSRIGHT") { + return genPlusPlusRight(ctx, ast); + } else if (ast.op == "PLUSPLUSLEFT") { + return genPlusPlusLeft(ctx, ast); + } else if (ast.op == "MINUSMINUSRIGHT") { + return genMinusMinusRight(ctx, ast); + } else if (ast.op == "MINUSMINUSLEFT") { + return genMinusMinusLeft(ctx, ast); + } else if (ast.op == "**") { + return genExp(ctx, ast); + } else if (ast.op == "/") { + return genDiv(ctx, ast); + } else if (ast.op == "\\") { + return genIDiv(ctx, ast); + } else if (ast.op == "&") { + return genBAnd(ctx, ast); + } else if (ast.op == "&&") { + return genAnd(ctx, ast); + } else if (ast.op == "||") { + return genOr(ctx, ast); + } else if (ast.op == "<<") { + return genShl(ctx, ast); + } else if (ast.op == ">>") { + return genShr(ctx, ast); + } else if (ast.op == "<") { + return genLt(ctx, ast); + } else if (ast.op == ">") { + return genGt(ctx, ast); + } else if (ast.op == "<=") { + return genLte(ctx, ast); + } else if (ast.op == ">=") { + return genGte(ctx, ast); + } else if (ast.op == "==") { + return genEq(ctx, ast); + } else if (ast.op == "!=") { + return genNeq(ctx, ast); + } else if (ast.op == "?") { + return genTerCon(ctx, ast); + } else { + error(ctx, ast, "Invalid operation: " + ast.op); + } + } else if (ast.type == "DECLARE") { + if (ast.declareType == "COMPONENT") { + return genDeclareComponent(ctx, ast); + } else if ((ast.declareType == "SIGNALIN")|| + (ast.declareType == "SIGNALOUT")|| + (ast.declareType == "SIGNAL")) { + return genDeclareSignal(ctx, ast); + } else if (ast.declareType == "VARIABLE") { + return genDeclareVariable(ctx, ast); + } else { + error(ctx, ast, "Invalid declaration: " + ast.declareType); + } + } else if (ast.type == "FUNCTIONCALL") { + return genFunctionCall(ctx, ast); + } else if (ast.type == "BLOCK") { + return genBlock(ctx, ast); + } else if (ast.type == "COMPUTE") { + return genCompute(ctx, ast); + } else if (ast.type == "FOR") { + return genFor(ctx, ast); + } else if (ast.type == "WHILE") { + return genWhile(ctx, ast); + } else if (ast.type == "IF") { + return genIf(ctx, ast); + } else if (ast.type == "RETURN") { + return genReturn(ctx, ast); + } else if (ast.type == "INCLUDE") { + return genInclude(ctx, ast); + } else if (ast.type == "ARRAY") { + return genArray(ctx, ast); + } else { + error(ctx, ast, "GEN -> Invalid AST node type: " + ast.type); + } +} + +function genBlock(ctx, ast) { + const oldCode = ctx.code; + let res = null; + ctx.code = ""; + for (let i=0; i1) { + ctx.code = oldCode + `{\n${utils.ident(ctx.code)}}\n`; + } else { + ctx.code = oldCode + ctx.code; + } + return res; +} + +function genSrcComment(ctx, ast) { + let codes = []; + const fl= ast.first_line-1; + const ll = ast.last_line-1; + const fc = ast.first_column-1; + const lc = ast.last_column; + for (let i=fl; i<=ll; i++) { + const p1 = i==fl ? fc : 0; + const p2 = i==ll ? lc : -1; + codes.push(ctx.includedFiles[ctx.fileName][i].slice(p1, p2>=0 ? p2 : undefined)); + } + ctx.code += "\n/* "+codes.join("\n")+"*/\n"; +} + + +function genDeclareComponent(ctx, ast) { + const scope = ctx.scopes[ctx.scopes.length - 1]; + + if (ast.name.type != "VARIABLE") return error(ctx, ast, "Invalid component name"); + if (getScope(ctx, ast.name.name)) return error(ctx, ast, "Name already exists: "+ast.name.name); + + scope[ast.name.name] = { + type: "COMPONENT", + label: ast.name.name + }; + + return ast.name.name; +} + +function genDeclareSignal(ctx, ast) { + const scope = ctx.scopes[ctx.scopes.length-1]; + + if (ast.name.type != "VARIABLE") return error(ctx, ast, "Invalid component name"); + if (getScope(ctx, ast.name.name)) return error(ctx, ast, "Name already exists: "+ast.name.name); + + const res = { + type: "SIGNAL", + label: ast.name.name + }; + + scope[ast.name.name] = res; + + return res; +} + +function genDeclareVariable(ctx, ast) { + + const scope = ctx.scopes[ctx.scopes.length-1]; + + const varName = ast.name.name; + const labelName = scope._prefix + ast.name.name; + + if (ast.name.type != "VARIABLE") return error(ctx, ast, "Invalid component name"); + if (ctx.scope[varName]) return error(ctx, ast, "Name already exists: "+varName); + + const sizes=[]; + let instantiate = false; + for (let i=0; i< ast.name.selectors.length; i++) { + const size = gen(ctx, ast.name.selectors[i]); + if (ctx.error) return; + + if (size.used) { + instantiate = true; + sizes.push(`BigInt2Int(${scope[size].label})`); + } else { + sizes.push(size.value.toJSNumber()); + } + + sizes.push( size.value.toJSNumber() ); + } + + const vSizes = newRef(ctx, "SIZES", "_sizes"); + const iSizes = scope[vSizes]; + iSizes.dim = ast.name.selectors.length; + if (instantiate) { + instantiateRef(ctx, vSizes); + ctx.code += `${iSizes.label}[${iSizes.dim+1}]=0`; + ctx.code += `${iSizes.label}[${iSizes.dim}]=1`; + for (let i=iSizes.dim-1; i>=0; i--) { + ctx.code += `${iSizes.label}[${i}] = ${sizes[i]}*${iSizes.label}[${i+1}];\n`; + } + } else { + iSizes.value = sizes; + } + + const res = ctx.scope[varName] = { + stack: true, + type: "BIGINT", + sizes: vSizes, + label: labelName + }; + + scope[varName] = res; + + return res; +} + +function genNumber(ctx, ast) { + return newRef(ctx, "BIGINT", "_num", ast.value); +} + + + +function genGetOffset(ctx, vOffset, vSizes, sels) { + + let rN = 0; + let rStr = ""; + let iOffset; + + if (vOffset) { + iOffset = getScope(ctx, vOffset); + if (iOffset.used) { + rStr += iOffset.label; + } else { + rN += iOffset.value.toJSNumber(); + } + } + + if ((sels)&&(sels.length>0)) { + + const iSizes = getScope(ctx, vSizes); + + for (let i=0; i0) { + if (rStr != "") rStr += " + "; + rStr += rN; + rN =0; + } + + if (rStr != "") rStr += " + "; + if (iIdx.used) { + rStr += vIdx; + } else { + rStr += iIdx.value; + } + rStr += "*"; + if (iSizes.used) { + rStr += `${iSizes.label}[${i+1}]`; + } else { + rStr += iSizes.sizes[i+1]; + } + } + } + } + + if (rStr == "") { + const o = newRef(ctx, "INT", "_offset", rN); + return o; + } else { + if (rN>0) { + if (rStr != "") rStr += " + "; + rStr += rN; + rN =0; + } + if (rStr == iOffset.label) { + return vOffset; + } else { + const res = newRef(ctx, "INT", "_offset"); + instantiateRef(ctx, res); + ctx.code += `${res} = ${rStr};\n`; + return res; + } + } +} + +function genVariable(ctx, ast) { + const v = getScope(ctx, ast.name); + + + if (v.type == "SIGNAL") { + let vOffset; + if (ast.selectors.length>0) { + const vsOffset = genGetSigalOffset(ctx, "ctx->cIdx", ast.name); + const vsSizes = genGetSignalSizes(ctx, "ctx->cIdx", ast.name); + vOffset = genGetOffset(ctx, vsOffset, vsSizes, ast.selectors ); + } else { + vOffset = genGetSigalOffset(ctx, "ctx->cIdx", ast.name); + } + return genGetSignal(ctx, "ctx->cIdx", vOffset); + + } else if (v.type == "BIGINT") { + const vOffset = genGetOffset(ctx, 0, v.sizes, ast.sels ); + if (v.used) { + if (vOffset == 0) { + return ast.name; + } else { + const res = newRef(ctx, "BIGINT", "_v"); + instantiateRef(ctx, res); + ctx.code += `${res} = ${ast.name} + ${vOffset};\n`; + return res; + } + } else { + if (typeof(vOffset) == "string") { + instantiateRef(ctx, ast.name); + const vConstant = instantiateConstant(ctx, v.value); + const res = newRef(ctx, "BIGINT", "_v"); + instantiateRef(ctx, res); + ctx.code += `${res} = ${vConstant} + ${vOffset};\n`; + return res; + } else { + const sa = utils.subArray(v.value, ast.selectors); + const res = newRef(ctx, "BIGINT", "_v", sa); + return res; + } + } + } + + const sels = []; + for (let i=0; iallocBigInts(${flatedValue.length});\n`; + for (let i=0; ifreeBigInts(${res.label});\n`; + return res; +} + +function genPin(ctx, ast) { + let vcIdx; + if (ast.component.selectors.length>0) { + const vcOffset = genGetSubComponentOffset(ctx, "ctx->cIdx", ast.component.name); + const vcSizes = genGetSubComponentSizes(ctx, "ctx->cIdx", ast.component.name); + vcIdx = genGetOffset(ctx, vcOffset, vcSizes, ast.component.selectors ); + } else { + vcIdx = genGetSubComponentOffset(ctx, "ctx->cIdx", ast.component.name); + } + + let vsIdx; + if (ast.pin.selectors.length>0) { + const vsOffset = genGetSigalOffset(ctx, vcIdx, ast.pin.name); + const vsSizes = genGetSignalSizes(ctx, vcIdx, ast.pin.name); + vsIdx = genGetOffset(ctx, vsOffset, vsSizes, ast.pin.selectors ); + } else { + vsIdx = genGetSigalOffset(ctx, vcIdx, ast.pin.name); + } + + return genGetSignal(ctx, vcIdx, vsIdx); +} + +function genGetSubComponentOffset(ctx, cIdx, label) { + const vOffset = newRef(ctx, "INT", "_compIdx"); + instantiateRef(ctx, vOffset); + + const h = utils.fnvHash(label); + ctx.code += `${vOffset} = ctx->getSubComponentOffset(${cIdx}, 0x${h}LL /* ${label} */);\n`; + return vOffset; +} + +function genGetSubComponentSizes(ctx, cIdx, label) { + const vSizes = newRef(ctx, "SIZES", "_compSizes"); + instantiateRef(ctx, vSizes); + + const h = utils.fnvHash(label); + ctx.code += `${vSizes} = ctx->getSubComponentSizes(${cIdx}, 0x${h}LL /* ${label} */);\n`; + return vSizes; +} + +function genGetSigalOffset(ctx, sIdx, label) { + const vOffset = newRef(ctx, "INT", "_sigIdx"); + instantiateRef(ctx, vOffset); + + const h = utils.fnvHash(label); + ctx.code += `${vOffset} = ctx->getSignalOffset(${sIdx}, 0x${h}LL /* ${label} */);\n`; + return vOffset; +} + +function genGetSignalSizes(ctx, sIdx, label) { + const vSizes = newRef(ctx, "SIZES", "_sigSizes"); + instantiateRef(ctx, vSizes); + + const h = utils.fnvHash(label); + ctx.code += `${vSizes} = ctx->getSignalSizes(${sIdx}, 0x${h}LL /* ${label} */);\n`; + return vSizes; +} + +function genSetSignal(ctx, cIdx, sIdx, value) { + ctx.code += `ctx->setSignal(${cIdx}, ${sIdx}, ${value});\n`; + return value; +} + +function genGetSignal(ctx, cIdx, sIdx) { + const res = newRef(ctx, "BIGINT", "_sigValue"); + instantiateRef(ctx, res); + ctx.code += `ctx->getSignal(${cIdx}, ${sIdx}, ${res});\n`; + return res; +} + +function genVarAssignement(ctx, ast) { + + let lName; + let sels; + + if (ctx.error) return; + + if (ast.values[0].type == "PIN") { + + let vcIdx; + if (ast.values[0].component.selectors.length>0) { + const vcOffset = genGetSubComponentOffset(ctx, "ctx->cIdx", ast.values[0].component.name); + const vcSizes = genGetSubComponentSizes(ctx, "ctx->cIdx", ast.values[0].component.name); + vcIdx = genGetOffset(ctx, vcOffset, vcSizes, ast.values[0].component.selectors ); + } else { + vcIdx = genGetSubComponentOffset(ctx, "ctx->cIdx", ast.values[0].component.name); + } + + let vsIdx; + if (ast.values[0].pin.selectors.length>0) { + const vsOffset = genGetSigalOffset(ctx, vcIdx, ast.values[0].pin.name); + const vsSizes = genGetSignalSizes(ctx, vcIdx, ast.values[0].pin.name); + vsIdx = genGetOffset(ctx, vsOffset, vsSizes, ast.values[0].pin.selectors ); + } else { + vsIdx = genGetSigalOffset(ctx, vcIdx, ast.values[0].pin.name); + } + + const vVal = gen(ctx, ast.values[1]); + + genSetSignal(ctx, vcIdx, vsIdx, vVal); + + return vVal; + } + + if (ast.values[0].type == "DECLARE") { + lName = gen(ctx, ast.values[0]); + if (ctx.error) return; + sels = []; + } else { + lName = ast.values[0].name; + sels = ast.values[0].selectors; + } + + + const left = getScope(ctx, lName); + if (!left) return error(ctx, ast, "Variable does not exists: "+ast.values[0].name); + + // Component instantiation is already done. + if (left.type == "COMPONENT") { + ctx.last = lName; + return; + } + + + const rName = gen(ctx, ast.values[1]); + const right = getScope(ctx, rName); + + if (left.type == "SIGNAL") { + + let vsIdx; + if (sels.length>0) { + const vsOffset = genGetSigalOffset(ctx, "ctx->cIdx", lName); + const vsSizes = genGetSignalSizes(ctx, "ctx->cIdx", lName); + vsIdx = genGetOffset(ctx, vsOffset, vsSizes, sels ); + } else { + vsIdx = genGetSigalOffset(ctx, "ctx->cIdx", lName); + } + + return genSetSignal(ctx, "ctx->cIdx", vsIdx, rName); + } else if (left.type == "VAR") { + if (left.used) { + if (!right.used) { + instantiateRef(ctx, rName); + } + ctx.code += `BigInt_copy(${left.label}, ${right.label});\n`; + return lName; + } else { + if (right.used) { + instantiateRef(ctx, lName); + ctx.code += `BigInt_copy(${left.label}, ${right.label});\n`; + return lName; + } else { + if (!left.value) { + left.value = right.value; + } else { + if (!left.value.equals(right.value)) { + if (ctx.scopes.length > 1) { + instantiateRef(ctx, lName); + ctx.code += `BigInt_copy(${left.label}, ${right.label});\n`; + } else { + left.value = right.value; + } + } + } + } + } + } else { + return error(ctx, ast, "Assigning to invalid"); + } +} + +function genConstraint(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + const strErr = ast.fileName + ":" + ast.first_line + ":" + ast.first_column; + ctx.code += `ctx->checkConstraint(${a}, ${b}, "${strErr}");`; +} + + +function genArray(ctx, ast) { + let S = "["; + for (let i=0; i0) S += ","; + S += gen(ctx, ast.values[i]); + } + S+="]"; + return S; +} + + +function genFunctionCall(ctx, ast) { + let S = "["; + for (let i=0; i0) S += ","; + S += gen(ctx, ast.params[i]); + } + S+="]"; + + return `ctx.callFunction("${ast.name}", ${S})`; +} + +function genFor(ctx, ast) { + ctx.scopes.push({}); + const init = gen(ctx, ast.init); + if (ctx.error) return; + const condition = gen(ctx, ast.condition); + if (ctx.error) return; + const step = gen(ctx, ast.step); + if (ctx.error) return; + const body = gen(ctx, ast.body); + if (ctx.error) return; + ctx.scopes.pop(); + return `for (${init};bigInt(${condition}).neq(bigInt(0));${step}) { \n${body}\n }\n`; +} + +function genWhile(ctx, ast) { + const condition = gen(ctx, ast.condition); + if (ctx.error) return; + const body = gen(ctx, ast.body); + if (ctx.error) return; + return `while (bigInt(${condition}).neq(bigInt(0))) {\n${body}\n}\n`; +} + +function genCompute(ctx, ast) { + const body = gen(ctx, ast.body); + if (ctx.error) return; + return `{\n${body}\n}\n`; +} + +function genIf(ctx, ast) { + const condition = gen(ctx, ast.condition); + if (ctx.error) return; + const thenBody = gen(ctx, ast.then); + if (ctx.error) return; + if (ast.else) { + const elseBody = gen(ctx, ast.else); + if (ctx.error) return; + return `if (bigInt(${condition}).neq(bigInt(0))) {\n${thenBody}\n} else {\n${elseBody}\n}\n`; + } else { + return `if (bigInt(${condition}).neq(bigInt(0))) {\n${thenBody}\n}\n`; + } +} + + +function genReturn(ctx, ast) { + const value = gen(ctx, ast.value); + if (ctx.error) return; + return `return ${value};`; +} + + + +function genSignalAssignConstrain(ctx, ast) { + const res = genVarAssignement(ctx, ast); + genConstraint(ctx, ast); + return res; +// return genVarAssignement(ctx, ast); +} + +function genVarAddAssignement(ctx, ast) { + return genVarAssignement(ctx, {values: [ast.values[0], {type: "OP", op: "+", values: ast.values}]}); +} + +function genVarMulAssignement(ctx, ast) { + return genVarAssignement(ctx, {values: [ast.values[0], {type: "OP", op: "*", values: ast.values}]}); +} + +function genPlusPlusRight(ctx, ast) { + return `(${genVarAssignement(ctx, {values: [ast.values[0], {type: "OP", op: "+", values: [ast.values[0], {type: "NUMBER", value: bigInt(1)}]}]})}).add(__P__).sub(bigInt(1)).mod(__P__)`; +} + +function genPlusPlusLeft(ctx, ast) { + return genVarAssignement(ctx, {values: [ast.values[0], {type: "OP", op: "+", values: [ast.values[0], {type: "NUMBER", value: bigInt(1)}]}]}); +} + +function genMinusMinusRight(ctx, ast) { + return `(${genVarAssignement(ctx, {values: [ast.values[0], {type: "OP", op: "-", values: [ast.values[0], {type: "NUMBER", value: bigInt(1)}]}]})}).add(__P__).sub(bigInt(1)).mod(__P__)`; +} + +function genMinusMinusLeft(ctx, ast) { + return genVarAssignement(ctx, {values: [ast.values[0], {type: "OP", op: "-", values: [ast.values[0], {type: "NUMBER", value: bigInt(1)}]}]}); +} + +function genAdd(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + return `bigInt(${a}).add(bigInt(${b})).mod(__P__)`; +} + +function genMul(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + return `bigInt(${a}).mul(bigInt(${b})).mod(__P__)`; +} + +function genSub(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + return `bigInt(${a}).add(__P__).sub(bigInt(${b})).mod(__P__)`; +} + +function genDiv(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + + return `bigInt(${a}).mul( bigInt(${b}).inverse(__P__) ).mod(__P__)`; +} + +function genIDiv(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + + return `bigInt(${a}).div( bigInt(${b}))`; +} + +function genExp(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + return `bigInt(${a}).modPow(bigInt(${b}), __P__)`; +} + +function genBAnd(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + return `bigInt(${a}).and(bigInt(${b})).and(__MASK__)`; +} + +function genAnd(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + return `((bigInt(${a}).neq(bigInt(0)) && bigInt(${b}).neq(bigInt(0))) ? bigInt(1) : bigInt(0))`; +} + +function genOr(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + return `((bigInt(${a}).neq(bigInt(0)) || bigInt(${b}).neq(bigInt(0))) ? bigInt(1) : bigInt(0))`; +} + +function genShl(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + return `bigInt(${b}).greater(bigInt(256)) ? 0 : bigInt(${a}).shl(bigInt(${b})).and(__MASK__)`; +} + +function genShr(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + return `bigInt(${b}).greater(bigInt(256)) ? 0 : bigInt(${a}).shr(bigInt(${b})).and(__MASK__)`; +} + +function genMod(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + return `bigInt(${a}).mod(bigInt(${b}))`; +} + +function genLt(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + return `bigInt(${a}).lt(bigInt(${b})) ? 1 : 0`; +} + +function genGt(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + return `bigInt(${a}).gt(bigInt(${b})) ? 1 : 0`; +} + +function genLte(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + return `bigInt(${a}).lesserOrEquals(bigInt(${b})) ? 1 : 0`; +} + +function genGte(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + return `bigInt(${a}).greaterOrEquals(bigInt(${b})) ? 1 : 0`; +} + +function genEq(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + return `(bigInt(${a}).eq(bigInt(${b})) ? 1 : 0)`; +} + +function genNeq(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + return `(bigInt(${a}).eq(bigInt(${b})) ? 0 : 1)`; +} + +function genUMinus(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + return `__P__.sub(bigInt(${a})).mod(__P__)`; +} + +function genTerCon(ctx, ast) { + const a = gen(ctx, ast.values[0]); + if (ctx.error) return; + const b = gen(ctx, ast.values[1]); + if (ctx.error) return; + const c = gen(ctx, ast.values[2]); + if (ctx.error) return; + return `bigInt(${a}).neq(bigInt(0)) ? (${b}) : (${c})`; +} + +function genInclude(ctx, ast) { + return ast.block ? gen(ctx, ast.block) : ""; +} + + diff --git a/src/c_tester.js b/src/c_tester.js new file mode 100644 index 0000000..48ee424 --- /dev/null +++ b/src/c_tester.js @@ -0,0 +1,119 @@ +const chai = require("chai"); +const assert = chai.assert; + +const fs = require("fs"); +var tmp = require("tmp-promise"); +const path = require("path"); +const compiler = require("./compiler"); +const util = require("util"); +const exec = util.promisify(require("child_process").exec); + +const stringifyBigInts = require("snarkjs").stringifyBigInts; +const unstringifyBigInts = require("snarkjs").unstringifyBigInts; +const bigInt = require("snarkjs").bigInt; + +module.exports = c_tester; + + +async function c_tester(circomFile, mainComponent, _options) { + tmp.setGracefulCleanup(); + mainComponent = mainComponent || "main"; + + const dir = await tmp.dir({prefix: "circom_", unsafeCleanup: true }); + + const baseName = path.basename(circomFile, ".circom"); + const options = Object.assign({}, _options); + + options.cSourceWriteStream = fs.createWriteStream(path.join(dir.path, baseName + ".cpp")); + options.symWriteStream = fs.createWriteStream(path.join(dir.path, baseName + ".sym")); + options.mainComponent = mainComponent; + await compiler(circomFile, options); + + const cdir = path.join(__dirname, "..", "c"); + await exec("g++" + + ` ${path.join(dir.path, baseName + ".cpp")} ` + + ` ${path.join(cdir, "main.cpp")}` + + ` ${path.join(cdir, "calcwit.cpp")}` + + ` ${path.join(cdir, "utils.cpp")}` + + ` -o ${path.join(dir.path, baseName)}` + + ` -I ${cdir}` + + " -lgmp -std=c++11 -DSANITY_CHECK" + ); + + console.log(dir.path); + return new CTester(dir, baseName, mainComponent); +} + +class CTester { + + constructor(dir, baseName, mainComponent) { + this.dir=dir; + this.baseName = baseName; + this.mainComponent = mainComponent; + } + + async release() { + await this.dir.cleanup(); + } + + async calculateWitness(input) { + await fs.promises.writeFile( + path.join(this.dir.path, "in.json"), + JSON.stringify(stringifyBigInts(input), null, 1) + ); + await exec(`${path.join(this.dir.path, this.baseName)}` + + ` ${path.join(this.dir.path, "in.json")}` + + ` ${path.join(this.dir.path, "out.json")}` + ); + const resStr = await fs.promises.readFile( + path.join(this.dir.path, "out.json") + ); + + const res = unstringifyBigInts(JSON.parse(resStr)); + return res; + } + + async _loadSymbols() { + this.symbols = {}; + const symsStr = await fs.promises.readFile( + path.join(this.dir.path, this.baseName + ".sym"), + "utf8" + ); + const lines = symsStr.split("\n"); + for (let i=0; i=0) { + lSignal = ctx.signals[lSignal.e]; } else { end=true; } } - if (tAll == "error") { + if (tAll == ERROR) { throw new Error("Incompatible types in signal: " + s); } - lSignal.category = tAll; + lSignal.c = tAll; } } function generateWitnessNames(ctx) { - - const totals = { - "output": 0, - "pubInput": 0, - "one": 0, - "prvInput": 0, - "internal": 0, - "constant": 0, - }; + const totals = {}; + totals[ctx.stONE] = 0; + totals[ctx.stOUTPUT] = 0; + totals[ctx.stPUBINPUT] = 0; + totals[ctx.stPRVINPUT] = 0; + totals[ctx.stINTERNAL] = 0; + totals[ctx.stDISCARDED] = 0; + totals[ctx.stCONSTANT] = 0; const ids = {}; - const counted = {}; - // First classify the signals - for (let s in ctx.signals) { + for (let s=0; s=0) lSignal = ctx.signals[lSignal.e]; - if (!counted[lSignal.fullName]) { - counted[lSignal.fullName] = true; - totals[lSignal.category] ++; + if (!( lSignal.o & ctx.COUNTED) ) { + lSignal.o |= ctx.COUNTED; + totals[lSignal.c] ++; } } - ids["one"] = 0; - ids["output"] = 1; - ids["pubInput"] = ids["output"] + totals["output"]; - ids["prvInput"] = ids["pubInput"] + totals["pubInput"]; - ids["internal"] = ids["prvInput"] + totals["prvInput"]; - ids["constant"] = ids["internal"] + totals["internal"]; - const nSignals = ids["constant"] + totals["constant"]; + ids[ctx.stONE] = 0; + ids[ctx.stOUTPUT] = 1; + ids[ctx.stPUBINPUT] = ids[ctx.stOUTPUT] + totals[ctx.stOUTPUT]; + ids[ctx.stPRVINPUT] = ids[ctx.stPUBINPUT] + totals[ctx.stPUBINPUT]; + ids[ctx.stINTERNAL] = ids[ctx.stPRVINPUT] + totals[ctx.stPRVINPUT]; + ids[ctx.stDISCARDED] = ids[ctx.stINTERNAL] + totals[ctx.stINTERNAL]; + ids[ctx.stCONSTANT] = ids[ctx.stDISCARDED] + totals[ctx.stDISCARDED]; + const nSignals = ids[ctx.stCONSTANT] + totals[ctx.stCONSTANT]; - ctx.signalNames = new Array(nSignals); - for (let i=0; i< nSignals; i++) ctx.signalNames[i] = []; - ctx.signalName2Idx = {}; + for (let s=0; s=0) { + lSignal = ctx.signals[lSignal.e]; } if ( typeof(lSignal.id) === "undefined" ) { - lSignal.id = ids[lSignal.category] ++; + lSignal.id = ids[lSignal.c] ++; } signal.id = lSignal.id; - ctx.signalNames[signal.id].push(signal.fullName); - ctx.signalName2Idx[signal.fullName] = signal.id; } ctx.totals = totals; @@ -225,6 +233,7 @@ function reduceConstants(ctx) { if (!lc.isZero(c)) { newConstraints.push(c); } + delete ctx.constraints[i]; } ctx.constraints = newConstraints; } @@ -232,10 +241,12 @@ function reduceConstants(ctx) { function reduceConstrains(ctx) { indexVariables(); let possibleConstraints = Object.keys(ctx.constraints); + let ii=0; while (possibleConstraints.length>0) { let nextPossibleConstraints = {}; for (let i in possibleConstraints) { - if ((ctx.verbose)&&(i%10000 == 0)) console.log("reducing constraints: ", i); + ii++; + if ((ctx.verbose)&&(ii%10000 == 0)) console.log("reducing constraints: ", i); if (!ctx.constraints[i]) continue; const c = ctx.constraints[i]; @@ -248,13 +259,13 @@ function reduceConstrains(ctx) { // Mov to C if possible. if (isConstant(c.a)) { - const ct = {type: "NUMBER", value: c.a.values["one"]}; + const ct = {type: "NUMBER", value: c.a.values[sONE]}; c.c = lc.add(lc.mul(c.b, ct), c.c); c.a = { type: "LINEARCOMBINATION", values: {} }; c.b = { type: "LINEARCOMBINATION", values: {} }; } if (isConstant(c.b)) { - const ct = {type: "NUMBER", value: c.b.values["one"]}; + const ct = {type: "NUMBER", value: c.b.values[sONE]}; c.c = lc.add(lc.mul(c.a, ct), c.c); c.a = { type: "LINEARCOMBINATION", values: {} }; c.b = { type: "LINEARCOMBINATION", values: {} }; @@ -265,8 +276,8 @@ function reduceConstrains(ctx) { if (isolatedSignal) { let lSignal = ctx.signals[isolatedSignal]; - while (lSignal.equivalence) { - lSignal = ctx.signals[lSignal.equivalence]; + while (lSignal.e>=0) { + lSignal = ctx.signals[lSignal.e]; } @@ -296,7 +307,7 @@ function reduceConstrains(ctx) { ctx.constraints[i] = null; - lSignal.category = "constant"; + lSignal.c = ctx.stDISCARDED; } else { if (lc.isZero(c.c)) ctx.constraints[i] = null; } @@ -332,8 +343,8 @@ function reduceConstrains(ctx) { function unindexVariables() { for (let s in ctx.signals) { let lSignal = ctx.signals[s]; - while (lSignal.equivalence) { - lSignal = ctx.signals[lSignal.equivalence]; + while (lSignal.e>=0) { + lSignal = ctx.signals[lSignal.e]; } if (lSignal.inConstraints) delete lSignal.inConstraints; } @@ -342,8 +353,8 @@ function reduceConstrains(ctx) { /* function unlinkSignal(signalName, cidx) { let lSignal = ctx.signals[signalName]; - while (lSignal.equivalence) { - lSignal = ctx.signals[lSignal.equivalence]; + while (lSignal.e>=0) { + lSignal = ctx.signals[lSignal.e]; } if ((lSignal.inConstraints)&&(lSignal.inConstraints[cidx])) { delete lSignal.inConstraints[cidx]; @@ -353,8 +364,8 @@ function reduceConstrains(ctx) { function linkSignal(signalName, cidx) { let lSignal = ctx.signals[signalName]; - while (lSignal.equivalence) { - lSignal = ctx.signals[lSignal.equivalence]; + while (lSignal.e>=0) { + lSignal = ctx.signals[lSignal.e]; } if (!lSignal.inConstraints) lSignal.inConstraints = {}; lSignal.inConstraints[cidx] = true; @@ -363,21 +374,22 @@ function reduceConstrains(ctx) { function getFirstInternalSignal(ctx, l) { for (let k in l.values) { const signal = ctx.signals[k]; - if (signal.category == "internal") return k; + if (signal.c == ctx.stINTERNAL) return k; } return null; } function isConstant(l) { for (let k in l.values) { - if ((k != "one") && (!l.values[k].isZero())) return false; + if ((k != sONE) && (!l.values[k].isZero())) return false; } - if (!l.values["one"] || l.values["one"].isZero()) return false; + if (!l.values[sONE] || l.values[sONE].isZero()) return false; return true; } } +/* function buildCircuitDef(ctx, mainCode) { const res = { @@ -436,6 +448,9 @@ function buildCircuitDef(ctx, mainCode) { return res; } +*/ + + /* Build constraints @@ -485,5 +500,96 @@ function buildConstraints(ctx) { return res; } +function buildR1cs(ctx, strm) { + + strm.write(Buffer.from([0x72,0x31,0x63,0x73])); + writeU32(1); + writeU32(4); + writeU32(1 + ctx.totals.output + ctx.totals.pubInput + ctx.totals.prvInput + ctx.totals.internal); + writeU32(ctx.totals.output); + writeU32(ctx.totals.pubInput); + writeU32(ctx.totals.prvInput); + writeU32(ctx.constraints.length); + + for (let i=0; i=0 ) lSignal = ctx.signals[lSignal.e]; + + writeU32(lSignal.id); + writeBigInt(lc.values[s]); + } + } + + function writeBigInt(n) { + const bytes = []; + let r = bigInt(n); + while (r.greater(bigInt.zero)) { + bytes.push(r.and(bigInt("255")).toJSNumber()); + r = r.shiftRight(8); + } + assert(bytes.length<=32); + assert(bytes.length>0); + strm.write( Buffer.from([bytes.length, ...bytes ])); + } +} + +function buildSyms(ctx, strm) { + + + + addSymbolsComponent(ctx.mainComponent + ".", ctx.getComponentIdx(ctx.mainComponent)); + + + function addSymbolsComponent(prefix, idComponet) { + for (let n in ctx.components[idComponet].names.o) { + const entrie = ctx.components[idComponet].names.o[n]; + addSymbolArray(prefix+n, entrie.type, entrie.sizes, entrie.offset); + } + } + + function addSymbolArray(prefix, type, sizes, offset) { + if (sizes.length==0) { + if (type == "S") { + let s=offset; + while (ctx.signals[s].e >= 0) s = ctx.signals[s].e; + let wId = ctx.signals[s].id; + if (typeof(wId) == "undefined") wId=-1; + strm.write(`${offset},${wId},${prefix}\n`); + } else { + addSymbolsComponent(prefix+".", offset); + } + return 1; + } else { + let acc = 0; + for (let i=0; i1) { + return [o, o+l]; + } else { + return o; + } + } + + addComponent(name, sizes) { + const l = this._allocElement(name, sizes, "C"); + const o = this.ctx.nComponents; + this.o[name].offset = o; + this.ctx.nComponents += l; + if (l>1) { + return [o, o+l]; + } else { + return o; + } + } + + _getElement(name, _sels, type) { + const sels = _sels || []; + const s = this.o[name]; + if (!s) return -1; + if (s.type != type) return -1; + if (sels.length > s.sizes.length) return -1; + let l=1; + for (let i = s.sizes.length-1; i>sels.length; i--) { + l = l*s.sizes[i]; + } + let o =0; + let p=1; + for (let i=sels.length-1; i>=0; i--) { + if (sels[i] > s.sizes[i]) return -1; // Out of range + if (sels[i] < 0) return -1; // Out of range + o += p*sels[i]; + p *= s.sizes[i]; + } + if (l>1) { + return [s.offset + o, s.offset + o + l]; + } else { + return s.offset + o; + } + } + + getSignalIdx(name, sels) { + return this._getElement(name, sels, "S"); + } + + getComponentIdx(name, sels) { + return this._getElement(name, sels, "C"); + } + + getSizes(name) { + return this.o[name].sels; + } + +} + +module.exports = class Ctx { + + constructor() { + + this.stONE = 1; + this.stOUTPUT = 2; + this.stPUBINPUT = 3; + this.stPRVINPUT = 4; + this.stINTERNAL = 5; + this.stDISCARDED = 6; + this.stCONSTANT = 7; + + this.IN = 0x01; + this.OUT = 0x02; + this.PRV = 0x04; + this.ONE = 0x08; + this.MAIN = 0x10; + this.COUNTED = 0x20; + + this.scopes = [{}]; + this.signals = []; + + this.currentComponent= -1; + this.constraints= []; + this.components= []; + this.templates= {}; + this.functions= {}; + this.functionParams= {}; + this.nSignals = 0; + this.nComponents =0; + this.names = new TableName(this); + this.main=false; + + const oneIdx = this.addSignal("one"); + this.signals[oneIdx] = { + v: bigInt(1), + o: this.ONE, + e: -1, + }; + } + + addSignal(name, sizes) { + if (this.currentComponent>=0) { + return this.components[this.currentComponent].names.addSignal(name, sizes); + } else { + return this.names.addSignal(name, sizes); + } + } + + addComponent(name, sizes) { + if (this.currentComponent>=0) { + return this.components[this.currentComponent].names.addComponent(name, sizes); + } else { + return this.names.addComponent(name, sizes); + } + } + + getSignalIdx(name, sels) { + if (this.currentComponent>=0) { + return this.components[this.currentComponent].names.getSignalIdx(name, sels); + } else { + return this.names.getSignalIdx(name, sels); + } + } + + getComponentIdx(name, sels) { + if (this.currentComponent>=0) { + return this.components[this.currentComponent].names.getComponentIdx(name, sels); + } else { + return this.names.getComponentIdx(name, sels); + } + } + + getSizes(name) { + if (this.currentComponent>=0) { + return this.components[this.currentComponent].names.getSizes(name); + } else { + return this.names.getSizes(name); + } + } + + newTableName() { + return new TableName(this); + } + +}; diff --git a/src/exec.js b/src/exec.js index 92e6a7c..70a9439 100644 --- a/src/exec.js +++ b/src/exec.js @@ -214,16 +214,38 @@ function getScope(ctx, name, selectors) { } - function select(v, s) { - s = s || []; - if (s.length == 0) return v; - return select(v[s[0]], s.slice(1)); + function select(v, sels) { + if (v.type == "SIGNAL") { + return reduce(v, sels, "sIdx"); + } else if (v.type == "COMPONENT") { + return reduce(v, sels, "cIdx"); + } else { + const s = sels || []; + if (s.length == 0) return v; + return select(v[s[0]], s.slice(1)); + } } for (let i=ctx.scopes.length-1; i>=0; i--) { if (ctx.scopes[i][name]) return select(ctx.scopes[i][name], sels); } return null; + + function reduce(v, _sels, idxName) { + let sels = _sels || []; + let sizes = v.sizes || []; + + let accSizes = [1]; + for (let i=sizes.length-1; i>0; i--) { + accSizes = [accSizes[0]*sizes[i], ...accSizes]; + } + const res = Object.assign({}, v); + res.sizes = sizes.slice(sels.length); + for (let i=0; i=0) { + sDest=ctx.signals[sDest.e]; + isOut = isOut || ((sDest.o & ctx.MAIN)&&(sDest.o & ctx.OUT)); } if (sDest.value) return error(ctx, ast, "Signals cannot be assigned twice"); @@ -1029,18 +1098,17 @@ function execSignalAssign(ctx, ast) { let assignValue = true; if (src.type == "SIGNAL") { - let sSrc = ctx.signals[src.fullName]; - let isIn = (sSrc.component == "main")&&(sSrc.direction == "IN"); - while (sSrc.equivalence) { - sSrc=ctx.signals[sSrc.equivalence]; - isIn = isIn || ((sSrc.component == "main")&&(sSrc.direction == "IN")); + let sSrc = ctx.signals[src.sIdx]; + let isIn = (sSrc.o & ctx.main)&&(sSrc.o & ctx.IN); + while (sSrc.e>=0) { + sSrc=ctx.signals[sSrc.e]; + isIn = isIn || ((sSrc.o & ctx.main)&&(sSrc.o & ctx.IN)); } // Skip if an out is assigned directly to an input. if ((!isIn)||(!isOut)) { - sDest.equivalence = src.fullName; - sDest.alias = sDest.alias.concat(src.alias); - while (sDest.equivalence) sDest=ctx.signals[sDest.equivalence]; + sDest.e = src.sIdx; + while (sDest.e >= 0) sDest=ctx.signals[sDest.e]; assignValue = false; } } @@ -1095,10 +1163,11 @@ function execInclude(ctx, ast) { ctx.includedFiles = ctx.includedFiles || []; if (ctx.includedFiles[incFileName]) return; - ctx.includedFiles[incFileName] = true; const src = fs.readFileSync(incFileName, "utf8"); + ctx.includedFiles[incFileName] = src.split("\n"); + if (!src) return error(ctx, ast, "Include file not found: "+incFileName); const incAst = parser.parse(src); diff --git a/src/gencode.js b/src/gencode.js index fa3d683..0688345 100644 --- a/src/gencode.js +++ b/src/gencode.js @@ -188,26 +188,28 @@ function genBlock(ctx, ast) { } function genTemplateDef(ctx, ast) { - let S = "function(ctx) "; - const newScope = {}; - for (let i=0; i< ast.params.length; i++) { - newScope[ast.params[i]] = { type: "VARIABLE" }; - } + if (ctx.f) return error(ctx, ast, "Already in function"); - ctx.scopes.push(newScope); - S += genBlock(ctx, ast.block); - ctx.scopes.pop(); + ctx.f = ctx.module.addFunction(ast.name); + ctx.c = ctx.f.getCodeBuilder(); -// const scope = ctx.scopes[ctx.scopes.length-1]; - const scope = ctx.scopes[0]; // Scope for templates is top + ctx.scope = {}; + for (let i=0; i< ast.params.length; i++) { + ctx.f.addParam(ast.params[i].name, "i32"); + ctx.scope[ast.params[i].name] = { + type: "PARAM", + sels: ast.params[i].sels, + getter: () => { return ctx.c.getLocal(ast.params[i].name); }, + setter: (v) => { return ctx.c.setLocal(ast.params[i].name, v); } + }; + } - scope[ast.name] = { - type: "TEMPLATE" - }; + genBlock(ctx, ast.block); - ctx.templates[ast.name] = S; - return ""; + ctx.scope = null; + ctx.c = null; + ctx.f = null; } function genFunctionDef(ctx, ast) { diff --git a/src/lcalgebra.js b/src/lcalgebra.js index 0aa52b4..d039577 100644 --- a/src/lcalgebra.js +++ b/src/lcalgebra.js @@ -60,6 +60,7 @@ QEQ QEQ ERR ERR const bigInt = require("big-integer"); const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617"); +const sONE = 0; exports.add = add; exports.mul = mul; @@ -79,7 +80,7 @@ function signal2lc(a) { type: "LINEARCOMBINATION", values: {} }; - lc.values[a.fullName] = bigInt(1); + lc.values[a.sIdx] = bigInt(1); return lc; } else { return a; @@ -163,10 +164,10 @@ function addLCNum(a,b) { return { type: "ERROR", errStr: "LinearCombination + undefined" }; } if (b.value.isZero()) return res; - if (!res.values["one"]) { - res.values["one"]=bigInt(b.value); + if (!res.values[sONE]) { + res.values[sONE]=bigInt(b.value); } else { - res.values["one"]= res.values["one"].add(b.value).mod(__P__); + res.values[sONE]= res.values[sONE].add(b.value).mod(__P__); } return res; } @@ -278,16 +279,16 @@ function mulQEQNum(a,b) { return res; } -function getSignalValue(ctx, signalName) { - const s = ctx.signals[signalName]; - if (s.equivalence != "") { - return getSignalValue(ctx, s.equivalence); +function getSignalValue(ctx, sIdx) { + const s = ctx.signals[sIdx]; + if (s.e >= 0) { + return getSignalValue(ctx, s.e); } else { const res = { type: "NUMBER" }; - if (s.value) { - res.value = s.value; + if (s.v) { + res.value = s.v; } return res; } @@ -297,7 +298,7 @@ function evaluate(ctx, n) { if (n.type == "NUMBER") { return n; } else if (n.type == "SIGNAL") { - return getSignalValue(ctx, n.fullName); + return getSignalValue(ctx, n.sIdx); } else if (n.type == "LINEARCOMBINATION") { const v= { type: "NUMBER", @@ -362,7 +363,7 @@ function toQEQ(a) { type: "QEQ", a: {type: "LINEARCOMBINATION", values: {}}, b: {type: "LINEARCOMBINATION", values: {}}, - c: {type: "LINEARCOMBINATION", values: {"one": bigInt(a.value)}} + c: {type: "LINEARCOMBINATION", values: {sONE: bigInt(a.value)}} }; } else if (a.type == "LINEARCOMBINATION") { return { @@ -415,11 +416,11 @@ function toString(a, ctx) { if (!c.equals(1)) { S = S + c.toString() + "*"; } - let sigName = k; + let sIdx = k; if (ctx) { - while (ctx.signals[sigName].equivalence) sigName = ctx.signals[sigName].equivalence; + while (ctx.signals[sIdx].e>=0) sIdx = ctx.signals[sIdx].e; } - S = S + sigName; + S = S + "[" + sIdx + "]"; } } if (S=="") return "0"; else return S; @@ -437,13 +438,13 @@ function canonize(ctx, a) { const res = clone(a); for (let k in a.values) { let s = k; - while (ctx.signals[s].equivalence) s= ctx.signals[s].equivalence; - if ((typeof(ctx.signals[s].value) != "undefined")&&(k != "one")) { + while (ctx.signals[s].e>=0) s= ctx.signals[s].e; + if ((typeof(ctx.signals[s].value) != "undefined")&&(k != sONE)) { const v = res.values[k].times(ctx.signals[s].value).mod(__P__); - if (!res.values["one"]) { - res.values["one"]=v; + if (!res.values[sONE]) { + res.values[sONE]=v; } else { - res.values["one"]= res.values["one"].add(v).mod(__P__); + res.values[sONE]= res.values[sONE].add(v).mod(__P__); } delete res.values[k]; } else if (s != k) { diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..8ac8473 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,55 @@ +const fnv = require("fnv-plus"); + +module.exports.ident =ident; + +module.exports.extractSizes =extractSizes; +module.exports.csArr = csArr; +module.exports.subArray = subArray; +module.exports.accSizes = accSizes; +module.exports.fnvHash = fnvHash; + +function ident(text) { + let lines = text.split("\n"); + for (let i=0; i=0; i--) { + accSizes.unshift(accSizes[0]*sizes[i]); + } + return accSizes; +} + +function fnvHash(str) { + return fnv.hash(str, 64).hex(); +} + + + diff --git a/test/circuits/in.bin b/test/circuits/in.bin new file mode 100644 index 0000000..594e923 --- /dev/null +++ b/test/circuits/in.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/circuits/in.json b/test/circuits/in.json new file mode 100644 index 0000000..7bd9486 --- /dev/null +++ b/test/circuits/in.json @@ -0,0 +1 @@ +{ "in1": 1, "in2": [2,3], "in3":[[4,5],[6,7],[8,9]] } diff --git a/test/circuits/inout.circom b/test/circuits/inout.circom index cf42e36..1ac591f 100644 --- a/test/circuits/inout.circom +++ b/test/circuits/inout.circom @@ -1,18 +1,54 @@ template Internal() { - signal input in; - signal output out; + signal input in1; + signal input in2[2]; + signal input in3[3][2]; - out <== in; + signal output out1; + signal output out2[2]; + signal output out3[3][2]; + + out1 <== in1; + out2[0] <== in2[0]; + out2[1] <== in2[1]; + + out3[0][0] <== in3[0][0]; + out3[0][1] <== in3[0][1]; + out3[1][0] <== in3[1][0]; + out3[1][1] <== in3[1][1]; + out3[2][0] <== in3[2][0]; + out3[2][1] <== in3[2][1]; } template InOut() { - signal input in; - signal output out; + signal input in1; + signal input in2[2]; + signal input in3[3][2]; + + signal output out1; + signal output out2[2]; + signal output out3[3][2]; component internal = Internal(); - internal.in <== in; - internal.out ==> out; + internal.in1 <== in1; + internal.in2[0] <== in2[0]; + internal.in2[1] <== in2[1]; + internal.in3[0][0] <== in3[0][0]; + internal.in3[0][1] <== in3[0][1]; + internal.in3[1][0] <== in3[1][0]; + internal.in3[1][1] <== in3[1][1]; + internal.in3[2][0] <== in3[2][0]; + internal.in3[2][1] <== in3[2][1]; + + internal.out1 ==> out1; + internal.out2[0] ==> out2[0]; + internal.out2[1] ==> out2[1]; + internal.out3[0][0] ==> out3[0][0]; + internal.out3[0][1] ==> out3[0][1]; + internal.out3[1][0] ==> out3[1][0]; + internal.out3[1][1] ==> out3[1][1]; + internal.out3[2][0] ==> out3[2][0]; + internal.out3[2][1] ==> out3[2][1]; } component main = InOut(); diff --git a/test/inout.js b/test/inout.js new file mode 100644 index 0000000..ddb563c --- /dev/null +++ b/test/inout.js @@ -0,0 +1,20 @@ +const chai = require("chai"); +const path = require("path"); + +const c_tester = require("../index.js").c_tester; +const stringifyBigInts = require("snarkjs").stringifyBigInts; + + +describe("inout test", function () { + this.timeout(100000); + it("Should compile a code with vars inside a for", async () => { + const cir = await c_tester(path.join(__dirname, "circuits", "inout.circom")); + + const out = await cir.calculateWitness({in1: 1, in2: [2,3], in3:[[4,5], [6,7], [8,9]]}); + + // console.log(JSON.stringify(stringifyBigInts(out),null,1)); + await cir.assertOut(out, {out1: 1, out2: [2,3], out3: [[4,5], [6,7],[8,9]]} ); + + await cir.release(); + }); +});