Browse Source

C generation

feature/witness_bin
Jordi Baylina 4 years ago
parent
commit
66291a0efe
No known key found for this signature in database GPG Key ID: 7480C80C1BE43112
31 changed files with 3295 additions and 239 deletions
  1. BIN
      .DS_Store
  2. +155
    -0
      c/calcwit.cpp
  3. +61
    -0
      c/calcwit.h
  4. +56
    -0
      c/circom.h
  5. +196
    -0
      c/main.cpp
  6. BIN
      c/mainjson
  7. +47
    -0
      c/mainjson.cpp
  8. +25
    -0
      c/utils.cpp
  9. +10
    -0
      c/utils.h
  10. +46
    -5
      cli.js
  11. BIN
      doc/lc_example.monopic
  12. +489
    -0
      doc/r1cs_bin_format.md
  13. BIN
      doc/r1cs_bin_format.monopic
  14. BIN
      doc/r1cs_example.monopic
  15. +2
    -1
      index.js
  16. +36
    -15
      package-lock.json
  17. +3
    -0
      package.json
  18. +52
    -0
      src/buildwasm.js
  19. +377
    -0
      src/c_build.js
  20. +941
    -0
      src/c_gen.js
  21. +119
    -0
      src/c_tester.js
  22. +203
    -97
      src/compiler.js
  23. +171
    -0
      src/ctx.js
  24. +148
    -79
      src/exec.js
  25. +17
    -15
      src/gencode.js
  26. +21
    -20
      src/lcalgebra.js
  27. +55
    -0
      src/utils.js
  28. +1
    -0
      test/circuits/in.bin
  29. +1
    -0
      test/circuits/in.json
  30. +43
    -7
      test/circuits/inout.circom
  31. +20
    -0
      test/inout.js

BIN
.DS_Store


+ 155
- 0
c/calcwit.cpp

@ -0,0 +1,155 @@
#include <string>
#include <stdexcept>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <stdlib.h>
#include <gmp.h>
#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; i<circuit->NSignals; i++) mpz_init2(signalValues[i], 256);
reset();
}
void Circom_CalcWit::reset() {
#ifdef SANITY_CHECK
for (int i=1; i<circuit->NComponents; i++) signalAssigned[i] = false;
#endif
for (int i=0; i<circuit->NComponents; 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; i<circuit->NSignals; 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; i<sizes[0]; i++) mpz_init2(res[i], 256);
return res;
}
void Circom_CalcWit::freeBigInts(PBigInt bi, Circom_Sizes sizes) {
for (int i=0; i<sizes[0]; i++) mpz_clear(bi[i]);
delete[] bi;
}
void Circom_CalcWit::getSignal(int cIdx, int sIdx, PBigInt value) {
mpz_set(*value, signalValues[sIdx]);
}
void Circom_CalcWit::setSignal(int cIdx, int sIdx, PBigInt value) {
#ifdef SANITY_CHECK
assert(signalAssigned[sIdx] == false);
signalAssigned[sIdx] = true;
#endif
mpz_set(signalValues[sIdx], *value);
if ( BITMAP_ISSET(circuit->mapIsInput, 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;
}

+ 61
- 0
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

+ 56
- 0
c/circom.h

@ -0,0 +1,56 @@
#ifndef __CIRCOM_H
#define __CIRCOM_H
#include <gmp.h>
#include <stdint.h>
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

+ 196
- 0
c/main.cpp

@ -0,0 +1,196 @@
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <gmp.h>
#include <unistd.h>
#include <nlohmann/json.hpp>
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<n; i++) {
iterateArr(ctx, o + i*sizes[1], sizes+1, jarr[i], f);
}
}
}
void itFunc(Circom_CalcWit *ctx, int o, json val) {
BigInt v;
mpz_init2(v, 256);
std::string s;
if (val.is_string()) {
s = val.get<std::string>();
} else if (val.is_number()) {
double vd = val.get<double>();
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 << " <input.<bin|json>> <output.<bin|json>>\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);
}
}

BIN
c/mainjson


+ 47
- 0
c/mainjson.cpp

@ -0,0 +1,47 @@
#include <iostream>
#include <nlohmann/json.hpp>
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<n; i++) {
iterateArr(o + i*sizes[1], sizes+1, jarr[i], f);
}
}
}
void itFunc(int o, json v) {
std::cout << o << " <-- " << v << '\n';
}
int main(int argc, char **argv) {
Circom_CalcWit *ctx = new Circom_CalcWit(&_circuit);
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(o, sizes, it.value(), itFunc);
}
}

+ 25
- 0
c/utils.cpp

@ -0,0 +1,25 @@
#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <stdlib.h>
#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;
}

+ 10
- 0
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

+ 46
- 5
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);

BIN
doc/lc_example.monopic


+ 489
- 0
doc/r1cs_bin_format.md

@ -0,0 +1,489 @@
# Binary format for R1CS
---
eip:
title: r1cs binary format
author: Jordi Baylina <jordi@baylina.cat>
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/).

BIN
doc/r1cs_bin_format.monopic


BIN
doc/r1cs_example.monopic


+ 2
- 1
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");

+ 36
- 15
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",

+ 3
- 0
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": {

+ 52
- 0
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<ctx.components.length; i++) {
const h = hashComponentCall(ctx, i);
const fName = ctx.components[i].temlate+"_"+h;
if (!fDefined[fName]) {
ctx.f = ctx.module.addFunction(fName);
ctx.c = ctx.f.getCodeBuilder();
ctx.scope = {};
for (let p in ctx.components[i].params) {
ctx.scope[p] = createConstant(ctx, ctx.components[i].params[p]);
}
gen(ctx, ctx.templates[ctx.components[i].temlate].block);
}
ctx.components[i].f = fName;
}
};
function buildSetSignal(ctx) {
}

+ 377
- 0
src/c_build.js

@ -0,0 +1,377 @@
/*
Copyright 2018 0KIMS association.
This file is part of circom (Zero Knowledge Circuit Compiler).
circom is a free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
circom is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with circom. If not, see <https://www.gnu.org/licenses/>.
*/
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; i<ctx.components.length; i++) {
const {htName, htMap} = addHashTable(i);
let code = "";
const componentEntriesTableName = ctx.getTmpName("entryTable_" + ctx.components[i].template);
code += `Circom_ComponentEntry ${componentEntriesTableName}[${htMap.length}] = {\n`;
for (let j=0; j<htMap.length; j++) {
const entry = ctx.components[i].names.o[htMap[j]];
code += j>0 ? " ," : " ";
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; i<keys.length; i++) {
definedHashTables[h].htMap[i] = keys[i];
const h2 = utils.fnvHash(keys[i]);
let pos = parseInt(h2.slice(-2), 16);
while (t[pos]) pos = (pos + 1) % 256;
t[pos] = [h2, i];
}
let code = `Circom_HashEntry ${definedHashTables[h].htName}[256] = {`;
for (let i=0; i<256; i++) {
code += i>0 ? "," : "";
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; i<ctx.components.length; i++) {
const h = hashComponentCall(ctx, i);
const fName = ctx.components[i].template+"_"+h;
if (!fDefined[fName]) {
const scope = {_prefix : ""};
ctx.scopes = [scope];
ctx.nScopes = 0;
ctx.code = "";
ctx.codeHeader = "// Header\n";
ctx.codeFooter = "// Footer\n";
ctx.tmpNames = Object.assign({},globalNames);
for (let p in ctx.components[i].params) {
newRef(ctx, "BIGINT", p, ctx.components[i].params[p]);
}
gen(ctx, ctx.templates[ctx.components[i].template].block);
const S = `void ${fName}(Circom_CalcWit *ctx) {\n` +
utils.ident(
ctx.codeHeader + "\n" +
ctx.code + "\n" +
ctx.codeFooter
) +
"}\n";
functions.push(S);
}
ctx.components[i].fnName = fName;
}
return functions.join("\n");
}
function buildComponentsArray(ctx) {
const ccodes = [];
ccodes.push(`Circom_Component _components[${ctx.components.length}] = {\n`);
for (let i=0; i< ctx.components.length; i++) {
ccodes.push(i>0 ? " ," : " ");
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; i<ctx.signals.length; i++) {
if (ctx.signals[i].o & ctx.IN) {
acc = acc | (1 << (i%32) );
}
if ((i+1)%32==0) {
line += (i>31) ? "," : " ";
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<ctx.signals.length; i++) {
const outIdx = ctx.signals[i].id;
if (typeof outIdx == "undefined") continue;
if (ctx.signals[i].e>=0) continue; // If has an alias, continue..
assert(outIdx<NVars);
if (typeof arr[ctx.signals[i].id] == "undefined") {
arr[outIdx] = i;
}
}
codes.push("// Signal Table\n");
codes.push(`int _wit2sig[${NVars}] = {\n`);
let code = "";
for (let i=0; i<NVars; i++) {
code += (i>0) ? ",": " ";
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; i<sizes.length;i++) {
name+="_"+sizes[i];
}
if (name=="sizes") name="sizes_0";
if (this.definedSizes[name]) return this.definedSizes[name];
const labelName = this.getTmpName(name);
this.definedSizes[name] = labelName;
const accSizes = utils.accSizes(sizes);
let code = `Circom_Size ${labelName}[${accSizes.length}] = {`;
for (let i=0; i<accSizes.length; i++) {
if (i>0) code += ",";
code += accSizes[i];
}
code += "};\n";
this.codes_sizes.push(code);
return labelName;
}

+ 941
- 0
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; i<ast.statements.length; i++) {
if (["BLOCK", "COMPUTE", "FOR", "WHILE", "IF"].indexOf(ast.statements[i].type)<0) {
genSrcComment(ctx, ast.statements[i]);
}
res = gen(ctx, ast.statements[i]);
if (ctx.error) return;
}
if (ctx.scopes.length>1) {
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; i<sels.length; i++) {
const vIdx = gen(ctx, sels[i]);
const iIdx = getScope(ctx, vIdx);
if ((!iIdx.used) && (!iSizes.used)) {
rN = rN + iIdx.value * iSizes.sizes[i+1];
} else {
if (rN>0) {
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; i<ast.selectors.length; i++) {
sels.push(gen(ctx, ast.selectors[i]));
if (ctx.error) return;
}
if (!v) {
return error(ctx, ast, "Invalid left operand");
}
if (v.type == "VARIABLE") {
return `ctx.getVar("${ast.name}",[${sels.join(",")}])`;
} else if (v.type == "SIGNAL") {
return `ctx.getSignal("${ast.name}", [${sels.join(",")}])`;
} else {
error(ctx, ast, "Invalid Variable type");
}
}
function instantiateConstant(ctx, value) {
const flatedValue = utils.flatArray(value);
const res = ctx.getTmpName("_const");
ctx.codeHeader += `PBigInt ${res};\n`;
ctx.code += `${res.label} = ctx->allocBigInts(${flatedValue.length});\n`;
for (let i=0; i<flatedValue.length; i++) {
ctx.code += `mpz_init_set_str(${res.label}[${i}], ${flatedValue[i].toString(16)}, 16);\n`;
}
ctx.codeFooter += `ctx->freeBigInts(${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; i<ast.values.length; i++) {
if (i>0) S += ",";
S += gen(ctx, ast.values[i]);
}
S+="]";
return S;
}
function genFunctionCall(ctx, ast) {
let S = "[";
for (let i=0; i<ast.params.length; i++) {
if (i>0) 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) : "";
}

+ 119
- 0
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<lines.length; i++) {
const arr = lines[i].split(",");
if (arr.length!=3) continue;
this.symbols[arr[2]] = {
idx: Number(arr[0]),
idxWit: Number(arr[1])
};
}
}
async assertOut(actualOut, expectedOut) {
const self = this;
if (!self.symbols) await self._loadSymbols();
checkObject("main", expectedOut);
function checkObject(prefix, eOut) {
if (Array.isArray(eOut)) {
for (let i=0; i<eOut.length; i++) {
checkObject(prefix + "["+i+"]", eOut[i]);
}
} else if (typeof eOut == "object") {
for (let k in eOut) {
checkObject(prefix + "."+k, eOut[k]);
}
} else {
const ba = bigInt(actualOut[self.symbols[prefix].idxWit]).toString();
const be = bigInt(eOut).toString();
assert.strictEqual(ba, be, prefix);
}
}
}
}

+ 203
- 97
src/compiler.js

@ -22,10 +22,12 @@ const path = require("path");
const bigInt = require("big-integer");
const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
const __MASK__ = new bigInt(2).pow(253).minus(1);
const sONE = 0;
const assert = require("assert");
const gen = require("./gencode");
const buildC = require("./c_build");
const exec = require("./exec");
const lc = require("./lcalgebra");
const Ctx = require("./ctx");
module.exports = compile;
@ -48,41 +50,34 @@ async function compile(srcFile, options) {
assert(ast.type == "BLOCK");
const ctx = {
scopes: [{}],
signals: {
one: {
fullName: "one",
value: bigInt(1),
equivalence: "",
direction: ""
}
},
currentComponent: "",
constraints: [],
components: {},
templates: {},
functions: {},
functionParams: {},
filePath: fullFilePath,
fileName: fullFileName,
verbose: options.verbose || false
};
const ctx = new Ctx();
ctx.mainComponent = options.mainComponent || "main";
ctx.filePath= fullFilePath;
ctx.fileName= fullFileName;
ctx.includedFiles = {};
ctx.includedFiles[fullFileName] = src.split("\n");
ctx.verbose= options.verbose || false;
exec(ctx, ast);
if (!ctx.components["main"]) {
if (ctx.error) {
throw(ctx.error);
}
if (ctx.getComponentIdx(ctx.mainComponent)<0) {
throw new Error("A main component must be defined");
}
if (ctx.verbose) console.log("Classify Signals");
classifySignals(ctx);
if (ctx.verbose) console.log("Reduce Constraints");
if (ctx.verbose) console.log("Reduce Constants");
reduceConstants(ctx);
if (options.reduceConstraints) {
if (ctx.verbose) console.log("Reduce Constraints");
// Repeat while reductions are performed
let oldNConstrains = -1;
while (ctx.constraints.length != oldNConstrains) {
@ -97,121 +92,134 @@ async function compile(srcFile, options) {
throw(ctx.error);
}
ctx.scopes = [{}];
const mainCode = gen(ctx,ast);
if (options.cSourceWriteStream) {
const cSrc = buildC(ctx);
options.cSourceWriteStream.write(cSrc);
}
// const mainCode = gen(ctx,ast);
if (ctx.error) throw(ctx.error);
const def = buildCircuitDef(ctx, mainCode);
if (options.r1csWriteStream) {
buildR1cs(ctx, options.r1csWriteStream);
}
if (options.symWriteStream) {
buildSyms(ctx, options.symWriteStream);
}
// const def = buildCircuitDef(ctx, mainCode);
return def;
}
function classifySignals(ctx) {
const ERROR = 0xFFFF;
function priorize(t1, t2) {
if ((t1 == "error") || (t2=="error")) return "error";
if (t1 == "internal") {
if ((t1 == ERROR) || (t2==ERROR)) return ERROR;
if (t1 == ctx.stINTERNAL) {
return t2;
} else if (t2=="internal") {
} else if (t2==ctx.stINTERNAL) {
return t1;
}
if ((t1 == "one") || (t2 == "one")) return "one";
if ((t1 == "constant") || (t2 == "constant")) return "constant";
if (t1!=t2) return "error";
if ((t1 == ctx.stONE) || (t2 == ctx.stONE)) return ctx.stONE;
if ((t1 == ctx.stCONSTANT) || (t2 == ctx.stCONSTANT)) return ctx.stCONSTANT;
if ((t1 == ctx.stDISCARDED) || (t2 == ctx.stDISCARDED)) return ctx.stDISCARDED;
if (t1!=t2) return ERROR;
return t1;
}
// First classify the signals
for (let s in ctx.signals) {
const signal = ctx.signals[s];
let tAll = "internal";
let tAll = ctx.stINTERNAL;
let lSignal = signal;
let end = false;
while (!end) {
let t = lSignal.category || "internal";
if (s == "one") {
t = "one";
} else if (lSignal.value) {
t = "constant";
} else if (lSignal.component=="main") {
if (lSignal.direction == "IN") {
if (lSignal.private) {
t = "prvInput";
let t = lSignal.c || ctx.stINTERNAL;
if (s == 0) {
t = ctx.stONE;
} else if (lSignal.v) {
t = ctx.stCONSTANT;
} else if (lSignal.o & ctx.MAIN) {
if (lSignal.o & ctx.IN) {
if (lSignal.o & ctx.PRV) {
t = ctx.stPRVINPUT;
} else {
t = "pubInput";
t = ctx.stPUBINPUT;
}
} else if (lSignal.direction == "OUT") {
t = "output";
} else if (lSignal.o & ctx.OUT) {
t = ctx.stOUTPUT;
}
}
tAll = priorize(t,tAll);
if (lSignal.equivalence) {
lSignal = ctx.signals[lSignal.equivalence];
if (lSignal.e>=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<ctx.signals.length; s++) {
if ((ctx.verbose)&&(s%10000 == 0)) console.log("generate witness (counting): ", s);
const signal = ctx.signals[s];
let lSignal = signal;
while (lSignal.equivalence) lSignal = ctx.signals[lSignal.equivalence];
while (lSignal.e>=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<ctx.signals.length; s++) {
if ((ctx.verbose)&&(s%10000 == 0)) console.log("seting id: ", s);
for (let s in ctx.signals) {
const signal = ctx.signals[s];
let lSignal = signal;
while (lSignal.equivalence) {
lSignal = ctx.signals[lSignal.equivalence];
while (lSignal.e>=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<ctx.constraints.length; i++) {
if ((ctx.verbose)&&(i%10000 == 0)) console.log("writing constraint: ", i);
writeConstraint(ctx.constraints[i]);
}
function writeU32(v) {
const b = Buffer.allocUnsafe(4);
b.writeInt32LE(v);
strm.write(b);
}
function writeConstraint(c) {
writeLC(c.a);
writeLC(c.b);
writeLC(lc.negate(c.c));
}
function writeLC(lc) {
const idxs = Object.keys(lc.values);
writeU32(idxs.length);
for (let s in lc.values) {
let lSignal = ctx.signals[s];
while (lSignal.e >=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; i<sizes[0]; i++) {
acc += addSymbolArray(`${prefix}[${i}]`, type, sizes.slice(1), offset + acc );
}
return acc;
}
}
}

+ 171
- 0
src/ctx.js

@ -0,0 +1,171 @@
const bigInt = require("big-integer");
class TableName {
constructor (ctx) {
this.ctx = ctx;
this.o = {};
}
_allocElement(name, _sizes, type) {
const sizes = _sizes || [];
let l = 1;
for (let i=0; i<sizes.length; i++) {
l = l*sizes[i];
}
this.o[name] = {
sizes: sizes,
type: type
};
return l;
}
addSignal(name, sizes) {
const l = this._allocElement(name, sizes, "S");
const o = this.ctx.nSignals;
this.o[name].offset = o;
this.ctx.nSignals += l;
if (l>1) {
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);
}
};

+ 148
- 79
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<sels.length; i++) {
res[idxName] += sels[i]*accSizes[i];
}
return res;
}
}
function getScopeLevel(ctx, name) {
@ -255,6 +277,10 @@ function execTemplateDef(ctx, ast) {
filePath: ctx.filePath,
scopes: copyScope(ctx.scopes)
};
ctx.templates[ast.name] = {
block: ast.block,
params: ast.params
};
}
function execFunctionDef(ctx, ast) {
@ -272,6 +298,10 @@ function execFunctionDef(ctx, ast) {
filePath: ctx.filePath,
scopes: copyScope(ctx.scopes)
};
ctx.functions[ast.name] = {
block: ast.block,
params: ast.params
};
}
@ -281,8 +311,6 @@ function execDeclareComponent(ctx, ast) {
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 baseName = ctx.currentComponent ? ctx.currentComponent + "." + ast.name.name : ast.name.name;
const sizes=[];
for (let i=0; i< ast.name.selectors.length; i++) {
const size = exec(ctx, ast.name.selectors[i]);
@ -293,16 +321,13 @@ function execDeclareComponent(ctx, ast) {
sizes.push( size.value.toJSNumber() );
}
const cIdx = ctx.addComponent(ast.name.name, sizes);
scope[ast.name.name] = iterateSelectors(ctx, sizes, baseName, function(fullName) {
ctx.components[fullName] = "UNINSTANTIATED";
return {
type: "COMPONENT",
fullName: fullName
};
});
scope[ast.name.name] = {
type: "COMPONENT",
sizes: sizes,
cIdx: Array.isArray(cIdx) ? cIdx[0] : cIdx
};
return {
type: "VARIABLE",
@ -333,15 +358,20 @@ function execInstantiateComponet(ctx, vr, fn) {
paramValues.push(v);
}
if (template.params.length != paramValues.length) error(ctx, fn, "Invalid Number of parameters");
const vv = getScope(ctx, componentName, vr.selectors);
if (template.params.length != paramValues.length) return error(ctx, fn, "Invalid Number of parameters");
if (!vv) return error(ctx, vr, "Component not defined"+ componentName);
instantiateComponent(vv);
const vComp = getScope(ctx, componentName, vr.selectors);
if (vComp.type != "COMPONENT") return error(ctx, fn, "Assigning to a non component");
const cIdx = vComp.cIdx;
if (cIdx == -1) return error(ctx, fn, "Component not defined");
let l=1;
for (let i=0; i<vComp.sizes.length; i++) l = l*vComp.sizes[i];
for (let i=0; i<l; i++) {
instantiateComponent(cIdx+i);
}
function instantiateComponent(varVal) {
function instantiateComponent(cIdx) {
function extractValue(v) {
if (Array.isArray(v)) {
@ -351,23 +381,25 @@ function execInstantiateComponet(ctx, vr, fn) {
}
}
if (Array.isArray(varVal)) {
for (let i =0; i<varVal.length; i++) {
instantiateComponent(varVal[i]);
}
return;
}
if (ctx.components[varVal.fullName] != "UNINSTANTIATED") error(ctx, fn, "Component already instantiated");
if (ctx.components[cIdx]) return error(ctx, fn, "Component already instantiated");
const oldComponent = ctx.currentComponent;
const oldFileName = ctx.fileName;
const oldFilePath = ctx.filePath;
ctx.currentComponent = varVal.fullName;
const oldMain = ctx.main;
if ((componentName == "main")&&(ctx.currentComponent==-1)) {
ctx.main=true;
} else {
ctx.main=false;
}
ctx.components[ctx.currentComponent] = {
signals: [],
params: {}
ctx.currentComponent = cIdx;
ctx.components[cIdx] = {
params: {},
names: ctx.newTableName(),
nInSignals: 0
};
const oldScopes = ctx.scopes;
@ -379,20 +411,22 @@ function execInstantiateComponet(ctx, vr, fn) {
const scope = {};
for (let i=0; i< template.params.length; i++) {
scope[template.params[i]] = paramValues[i];
ctx.components[ctx.currentComponent].params[template.params[i]] = extractValue(paramValues[i]);
ctx.components[cIdx].params[template.params[i]] = extractValue(paramValues[i]);
}
ctx.components[ctx.currentComponent].template = templateName;
ctx.components[cIdx].template = templateName;
ctx.fileName = template.fileName;
ctx.filePath = template.filePath;
ctx.scopes = copyScope( template.scopes );
ctx.scopes.push(scope);
execBlock(ctx, template.block);
ctx.fileName = oldFileName;
ctx.filePath = oldFilePath;
ctx.currentComponent = oldComponent;
ctx.main = oldMain;
ctx.scopes = oldScopes;
}
}
@ -461,32 +495,47 @@ function execDeclareSignal(ctx, ast) {
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 baseName = ctx.currentComponent ? ctx.currentComponent + "." + ast.name.name : ast.name.name;
const sizes=[];
let totalSize=1;
for (let i=0; i< ast.name.selectors.length; i++) {
const size = exec(ctx, ast.name.selectors[i]);
if (ctx.error) return;
if (size.type != "NUMBER") return error(ctx, ast.name.selectors[i], "expected a number");
sizes.push( size.value.toJSNumber() );
const s = size.value.toJSNumber();
totalSize *= s;
sizes.push( s );
}
scope[ast.name.name] = iterateSelectors(ctx, sizes, baseName, function(fullName) {
ctx.signals[fullName] = {
fullName: fullName,
direction: ast.declareType == "SIGNALIN" ? "IN" : (ast.declareType == "SIGNALOUT" ? "OUT" : ""),
private: ast.private,
component: ctx.currentComponent,
equivalence: "",
alias: [fullName]
};
ctx.components[ctx.currentComponent].signals.push(fullName);
return {
type: "SIGNAL",
fullName: fullName,
let sIdx = ctx.addSignal(ast.name.name, sizes);
if (!Array.isArray(sIdx)) sIdx = [sIdx, sIdx+1];
for (let i=sIdx[0]; i<sIdx[1]; i++) {
ctx.signals[i] = {
o: 0,
e: -1
};
});
if (ast.declareType == "SIGNALIN") {
ctx.signals[i].o |= ctx.IN;
ctx.components[ctx.currentComponent].nInSignals+=1;
}
if (ast.declareType == "SIGNALOUT") {
ctx.signals[i].o |= ctx.OUT;
}
if (ast.private ) {
ctx.signals[i].o |= ctx.PRV;
}
if (ctx.main) {
ctx.signals[i].o |= ctx.MAIN;
}
// ctx.components[ctx.currentComponent].signals.push(i);
}
scope[ast.name.name] = {
type: "SIGNAL",
sizes: sizes,
sIdx: sIdx[0]
};
return {
type: "VARIABLE",
name: ast.name.name,
@ -535,10 +584,10 @@ function execVariable(ctx, ast) {
if (!v) return error(ctx, ast, "Variable not defined");
// If the signal has an assigned value (constant) just return the constant
if ((v.type == "SIGNAL") && (ctx.signals[v.fullName].value)) {
if ((v.type == "SIGNAL") && (ctx.signals[v.sIdx].value)) {
return {
type: "NUMBER",
value: ctx.signals[v.fullName].value
value: ctx.signals[v.sIdx].value
};
}
let res;
@ -547,23 +596,43 @@ function execVariable(ctx, ast) {
}
function execPin(ctx, ast) {
const component = getScope(ctx, ast.component.name, ast.component.selectors);
if (!component) return error(ctx, ast.component, "Component does not exists: "+ast.component.name);
if (ctx.error) return;
let signalFullName = component.fullName + "." + ast.pin.name;
const selsC = [];
for (let i=0; i< ast.component.selectors.length; i++) {
const sel = exec(ctx, ast.component.selectors[i]);
if (sel.type != "NUMBER") return error(ctx, ast.pin.selectors[i], "expected a number");
selsC.push(sel.value.toJSNumber());
}
const cIdx = ctx.getComponentIdx(ast.component.name, selsC);
if (cIdx<0) return error(ctx, ast.component, "Component does not exists: "+ast.component.name);
const selsP = [];
for (let i=0; i< ast.pin.selectors.length; i++) {
const sel = exec(ctx, ast.pin.selectors[i]);
if (ctx.error) return;
if (sel.type != "NUMBER") return error(ctx, ast.pin.selectors[i], "expected a number");
signalFullName += "[" + sel.value.toJSNumber() + "]";
selsP.push(sel.value.toJSNumber());
}
if (!ctx.signals[signalFullName]) error(ctx, ast, "Signal not defined:" + signalFullName);
const sIdx = ctx.components[cIdx].names.getSignalIdx(ast.pin.name, selsP);
if (sIdx<0) error(ctx, ast, "Signal not defined:" + buildFullName() );
return {
type: "SIGNAL",
fullName: signalFullName
sIdx: sIdx
};
function buildFullName() {
return ast.component.name + sels2str(selsC) + "." + ast.pin.name + sels2str(selsP);
}
function sels2str(sels) {
let S = "";
for (let i=0; i< sels.length; i++) {
const sel = exec(ctx, ast.pin.selectors[i]);
if (sel.type != "NUMBER") return error(ctx, ast.pin.selectors[i], "expected a number");
S += "[" + sel.value.toString() + "]";
}
return S;
}
}
function execFor(ctx, ast) {
@ -994,13 +1063,13 @@ function execSignalAssign(ctx, ast) {
if (!dst) return error(ctx, ast, "Signal not defined");
if (dst.type != "SIGNAL") return error(ctx, ast, "Signal assigned to a non signal");
let sDest=ctx.signals[dst.fullName];
if (!sDest) return error(ctx, ast, "Invalid signal: "+dst.fullName);
let sDest=ctx.signals[dst.sIdx];
if (!sDest) return error(ctx, ast, "Invalid signal: "+dst.sIdx);
let isOut = (sDest.component == "main")&&(sDest.direction=="OUT");
while (sDest.equivalence) {
sDest=ctx.signals[sDest.equivalence];
isOut = isOut || ((sDest.component == "main")&&(sDest.direction=="OUT"));
let isOut = (sDest.o & ctx.MAIN)&&(sDest.o & ctx.OUT);
while (sDest.e>=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);

+ 17
- 15
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) {

+ 21
- 20
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) {

+ 55
- 0
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<lines.length; i++) {
if (lines[i]) lines[i] = " "+lines[i];
}
return lines.join("\n");
}
function extractSizes (o) {
if (! Array.isArray(o)) return [1, 0];
return [o.length, ...extractSizes(o[0])];
}
// Input [1,2,3]
// Returns " ,1 ,2, 3"
function csArr(_arr) {
let S = "";
const arr = _arr || [];
for (let i=0; i<arr.length; i++) {
S = " ,"+arr[i];
}
return S;
}
function subArray(value, sels) {
if ((!sels) || (sels.length == 0)) return value;
return subArray(value[sels[0]], sels.slice(1));
}
function accSizes(_sizes) {
const sizes = _sizes || [];
const accSizes = [1, 0];
for (let i=sizes.length-1; i>=0; i--) {
accSizes.unshift(accSizes[0]*sizes[i]);
}
return accSizes;
}
function fnvHash(str) {
return fnv.hash(str, 64).hex();
}

+ 1
- 0
test/circuits/in.bin

@ -0,0 +1 @@


+ 1
- 0
test/circuits/in.json

@ -0,0 +1 @@
{ "in1": 1, "in2": [2,3], "in3":[[4,5],[6,7],[8,9]] }

+ 43
- 7
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();

+ 20
- 0
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();
});
});

Loading…
Cancel
Save