Browse Source

Construction phase redone

feature/witness_bin
Jordi Baylina 4 years ago
parent
commit
da969a5e16
No known key found for this signature in database GPG Key ID: 7480C80C1BE43112
15 changed files with 1675 additions and 1934 deletions
  1. +2
    -2
      src/c_gen.js
  2. +37
    -54
      src/compiler.js
  3. +1070
    -0
      src/construction_phase.js
  4. +0
    -1335
      src/exec.js
  5. +6
    -0
      src/iterateast.js
  6. +472
    -408
      src/lcalgebra.js
  7. +4
    -5
      src/r1csfile.js
  8. +1
    -0
      src/utils.js
  9. +0
    -118
      src/zqfield.js
  10. +24
    -3
      test/basiccases.js
  11. +5
    -8
      test/circuits/arrays.circom
  12. +27
    -0
      test/circuits/componentarray2.circom
  13. +1
    -1
      test/circuits/constantcircuit.circom
  14. +16
    -0
      test/circuits/include.circom
  15. +10
    -0
      test/circuits/included.circom

+ 2
- 2
src/c_gen.js

@ -87,7 +87,7 @@ function instantiateConstant(ctx, value) {
function createRefs(ctx, ast) { function createRefs(ctx, ast) {
const scopeLabels = []; const scopeLabels = [];
iterateAST(ast, (ast, level) => { iterateAST(ast, (ast, level) => {
while ((scopeLabels.length>0)&&(!scopeLabels[scopeLabels.length-1].startsWith(level))) {
while ((scopeLabels.length>0)&&(!level.startsWith(scopeLabels[scopeLabels.length-1]))) {
ctx.scopes.pop(); ctx.scopes.pop();
scopeLabels.pop(); scopeLabels.pop();
} }
@ -329,7 +329,7 @@ function genDeclareVariable(ctx, ast) {
} }
sizes = utils.accSizes(sizes); sizes = utils.accSizes(sizes);
} else { } else {
sizes = null; // If not sizes, the sized are defined in the first assignement.
sizes = [1,0];
} }
if ((!v.sizes)&&(sizes)) { if ((!v.sizes)&&(sizes)) {

+ 37
- 54
src/compiler.js

@ -17,26 +17,18 @@
along with circom. If not, see <https://www.gnu.org/licenses/>. along with circom. If not, see <https://www.gnu.org/licenses/>.
*/ */
const fs = require("fs");
const path = require("path");
const bigInt = require("big-integer"); const bigInt = require("big-integer");
const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617"); const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
const sONE = 0; const sONE = 0;
const assert = require("assert");
const buildC = require("./c_build"); const buildC = require("./c_build");
const exec = require("./exec");
const lc = require("./lcalgebra");
const constructionPhase = require("./construction_phase");
const Ctx = require("./ctx"); const Ctx = require("./ctx");
const ZqField = require("./zqfield");
const ZqField = require("fflib").ZqField;
const utils = require("./utils"); const utils = require("./utils");
const buildR1cs = require("./r1csfile").buildR1cs; const buildR1cs = require("./r1csfile").buildR1cs;
module.exports = compile; module.exports = compile;
const parser = require("../parser/jaz.js").parser;
const timeout = ms => new Promise(res => setTimeout(res, ms));
async function compile(srcFile, options) { async function compile(srcFile, options) {
options.p = options.p || __P__; options.p = options.p || __P__;
if (!options) { if (!options) {
@ -45,26 +37,15 @@ async function compile(srcFile, options) {
if (typeof options.reduceConstraints === "undefined") { if (typeof options.reduceConstraints === "undefined") {
options.reduceConstraints = true; options.reduceConstraints = true;
} }
const fullFileName = srcFile;
const fullFilePath = path.dirname(fullFileName);
const src = fs.readFileSync(fullFileName, "utf8");
const ast = parser.parse(src);
assert(ast.type == "BLOCK");
const ctx = new Ctx(); const ctx = new Ctx();
ctx.field = new ZqField(options.p); ctx.field = new ZqField(options.p);
ctx.verbose= options.verbose || false;
ctx.mainComponent = options.mainComponent || "main"; ctx.mainComponent = options.mainComponent || "main";
ctx.filePath= fullFilePath;
ctx.fileName= fullFileName;
ctx.includedFiles = {};
ctx.includedFiles[fullFileName] = src.split("\n");
ctx.verbose= options.verbose || false;
constructionPhase(ctx, srcFile);
exec(ctx, ast);
console.log("NConstraints Before: "+ctx.constraints.length);
if (ctx.error) { if (ctx.error) {
throw(ctx.error); throw(ctx.error);
@ -91,6 +72,8 @@ async function compile(srcFile, options) {
} }
} }
console.log("NConstraints After: "+ctx.constraints.length);
generateWitnessNames(ctx); generateWitnessNames(ctx);
if (ctx.error) { if (ctx.error) {
@ -235,8 +218,8 @@ function reduceConstants(ctx) {
const newConstraints = []; const newConstraints = [];
for (let i=0; i<ctx.constraints.length; i++) { for (let i=0; i<ctx.constraints.length; i++) {
if ((ctx.verbose)&&(i%10000 == 0)) console.log("reducing constants: ", i); if ((ctx.verbose)&&(i%10000 == 0)) console.log("reducing constants: ", i);
const c = lc.canonize(ctx, ctx.constraints[i]);
if (!lc.isZero(c)) {
const c = ctx.lc.canonize(ctx, ctx.constraints[i]);
if (!ctx.lc.isZero(c)) {
newConstraints.push(c); newConstraints.push(c);
} }
delete ctx.constraints[i]; delete ctx.constraints[i];
@ -265,19 +248,19 @@ function reduceConstrains(ctx) {
// Mov to C if possible. // Mov to C if possible.
if (isConstant(c.a)) { if (isConstant(c.a)) {
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: {} };
const ct = {t: "N", v: c.a.coefs[sONE]};
c.c = ctx.lc.add(ctx.lc.mul(c.b, ct), c.c);
c.a = { t: "LC", coefs: {} };
c.b = { t: "LC", coefs: {} };
} }
if (isConstant(c.b)) { if (isConstant(c.b)) {
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: {} };
const ct = {t: "N", v: c.b.coefs[sONE]};
c.c = ctx.lc.add(ctx.lc.mul(c.a, ct), c.c);
c.a = { t: "LC", coefs: {} };
c.b = { t: "LC", coefs: {} };
} }
if (lc.isZero(c.a) || lc.isZero(c.b)) {
if (ctx.lc.isZero(c.a) || ctx.lc.isZero(c.b)) {
const isolatedSignal = getFirstInternalSignal(ctx, c.c); const isolatedSignal = getFirstInternalSignal(ctx, c.c);
if (isolatedSignal) { if (isolatedSignal) {
@ -288,22 +271,22 @@ function reduceConstrains(ctx) {
const isolatedSignalEquivalence = { const isolatedSignalEquivalence = {
type: "LINEARCOMBINATION",
values: {}
t: "LC",
coefs: {}
}; };
const invCoef = c.c.values[isolatedSignal].modInv(__P__);
for (const s in c.c.values) {
const invCoef = c.c.coefs[isolatedSignal].modInv(__P__);
for (const s in c.c.coefs) {
if (s != isolatedSignal) { if (s != isolatedSignal) {
const v = __P__.minus(c.c.values[s]).times(invCoef).mod(__P__);
const v = __P__.minus(c.c.coefs[s]).times(invCoef).mod(__P__);
if (!v.isZero()) { if (!v.isZero()) {
isolatedSignalEquivalence.values[s] = v;
isolatedSignalEquivalence.coefs[s] = v;
} }
} }
} }
for (let j in lSignal.inConstraints) { for (let j in lSignal.inConstraints) {
if ((j!=i)&&(ctx.constraints[j])) { if ((j!=i)&&(ctx.constraints[j])) {
ctx.constraints[j] = lc.substitute(ctx.constraints[j], isolatedSignal, isolatedSignalEquivalence);
ctx.constraints[j] = ctx.lc.substitute(ctx.constraints[j], isolatedSignal, isolatedSignalEquivalence);
linkSignalsConstraint(j); linkSignalsConstraint(j);
if (j<i) { if (j<i) {
nextPossibleConstraints[j] = true; nextPossibleConstraints[j] = true;
@ -315,7 +298,7 @@ function reduceConstrains(ctx) {
lSignal.c = ctx.stDISCARDED; lSignal.c = ctx.stDISCARDED;
} else { } else {
if (lc.isZero(c.c)) ctx.constraints[i] = null;
if (ctx.lc.isZero(c.c)) ctx.constraints[i] = null;
} }
} }
} }
@ -341,9 +324,9 @@ function reduceConstrains(ctx) {
function linkSignalsConstraint(cidx) { function linkSignalsConstraint(cidx) {
const ct = ctx.constraints[cidx]; const ct = ctx.constraints[cidx];
for (let k in ct.a.values) linkSignal(k, cidx);
for (let k in ct.b.values) linkSignal(k, cidx);
for (let k in ct.c.values) linkSignal(k, cidx);
for (let k in ct.a.coefs) linkSignal(k, cidx);
for (let k in ct.b.coefs) linkSignal(k, cidx);
for (let k in ct.c.coefs) linkSignal(k, cidx);
} }
function unindexVariables() { function unindexVariables() {
@ -378,7 +361,7 @@ function reduceConstrains(ctx) {
} }
function getFirstInternalSignal(ctx, l) { function getFirstInternalSignal(ctx, l) {
for (let k in l.values) {
for (let k in l.coefs) {
const signal = ctx.signals[k]; const signal = ctx.signals[k];
if (signal.c == ctx.stINTERNAL) return k; if (signal.c == ctx.stINTERNAL) return k;
} }
@ -386,10 +369,10 @@ function reduceConstrains(ctx) {
} }
function isConstant(l) { function isConstant(l) {
for (let k in l.values) {
if ((k != sONE) && (!l.values[k].isZero())) return false;
for (let k in l.coefs) {
if ((k != sONE) && (!l.coefs[k].isZero())) return false;
} }
if (!l.values[sONE] || l.values[sONE].isZero()) return false;
if (!l.coefs[sONE] || l.coefs[sONE].isZero()) return false;
return true; return true;
} }
@ -483,9 +466,9 @@ function buildConstraints(ctx) {
const res = []; const res = [];
function fillLC(dst, src) { function fillLC(dst, src) {
if (src.type != "LINEARCOMBINATION") throw new Error("Constraint is not a LINEARCOMBINATION");
for (let s in src.values) {
const v = src.values[s].toString();
if (src.t != "LC") throw new Error("Constraint is not a LINEARCOMBINATION");
for (let s in src.coefs) {
const v = src.coefs[s].toString();
const id = ctx.signalName2Idx[s]; const id = ctx.signalName2Idx[s];
dst[id] = v; dst[id] = v;
} }
@ -498,7 +481,7 @@ function buildConstraints(ctx) {
fillLC(A, ctx.constraints[i].a); fillLC(A, ctx.constraints[i].a);
fillLC(B, ctx.constraints[i].b); fillLC(B, ctx.constraints[i].b);
fillLC(C, lc.negate(ctx.constraints[i].c));
fillLC(C, ctx.lc.negate(ctx.constraints[i].c));
res.push([A,B,C]); res.push([A,B,C]);
} }

+ 1070
- 0
src/construction_phase.js
File diff suppressed because it is too large
View File


+ 0
- 1335
src/exec.js
File diff suppressed because it is too large
View File


+ 6
- 0
src/iterateast.js

@ -61,6 +61,12 @@ function iterateAST(ast, fn, _pfx) {
iterateAST(ast.value, fn, getPfx()); iterateAST(ast.value, fn, getPfx());
} else if (ast.type == "ARRAY") { } else if (ast.type == "ARRAY") {
iterate(ast.values); iterate(ast.values);
} else if ((ast.type == "TEMPLATEDEF")) {
//
} else if ((ast.type == "FUNCTIONDEF")) {
//
} else if ((ast.type == "INCLUDE")) {
//
} else { } else {
assert(false, "GEN -> Invalid AST iteration: " + ast.type); assert(false, "GEN -> Invalid AST iteration: " + ast.type);
} }

+ 472
- 408
src/lcalgebra.js

@ -18,491 +18,555 @@
*/ */
/* /*
NUMBER: a
// Number
///////////////
N: a
{ {
type: "NUMBER",
value: bigInt(a)
t: "N",
v: bigInt(a)
} }
LINEARCOMBINATION: c1*s1 + c2*s2 + c3*s3
// Signal
///////////////
{
t: "S",
sIdx: sIdx
}
// Linear Convination
//////////////////
LC: c1*s1 + c2*s2 + c3*s3
{ {
type: "LINEARCOMBINATION",
values: {
t: "LC",
coefs: {
s1: bigInt(c1), s1: bigInt(c1),
s2: bigInt(c2), s2: bigInt(c2),
s3: bigInt(c3) s3: bigInt(c3)
} }
} }
// Quadratic Expression
//////////////////
QEX: a*b + c WHERE a,b,c are LC
{
t: "QEX"
a: { t: "LC", coefs: {...} },
b: { t: "LC", coefs: {...} },
c: { t: "LC", coefs: {...} }
}
QEQ: a*b + c WHERE a,b,c are LINEARCOMBINATION
NQ: Non quadratic expression
{ {
type: "QEQ"
a: { type: LINEARCOMBINATION, values: {...} },
b: { type: LINEARCOMBINATION, values: {...} },
c: { type: LINEARCOMBINATION, values: {...} }
t: "NQ"
} }
*/ */
/* /*
+ NUM LC QEQ
NUM NUM LC QEQ
LC LC LC QEQ
QEQ QEQ QEQ ERR
* NUM LC QEQ
NUM NUM LC QEQ
LC LC QEQ ERR
QEQ QEQ ERR ERR
+ N LC QEX NQ
N N LC QEX NQ
LC LC LC QEX NQ
QEX QEX QEX NQ NQ
NQ NQ NQ NQ NQ
* N LC QEX NQ
N N LC QEX NQ
LC LC QEX NQ NQ
QEX QEX NQ NQ NQ
NQ NQ NQ NQ NQ
*/ */
const bigInt = require("big-integer"); const bigInt = require("big-integer");
const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
const utils = require("./utils");
const sONE = 0; const sONE = 0;
const utils = require("./utils.js");
exports.add = add;
exports.mul = mul;
exports.evaluate = evaluate;
exports.negate = negate;
exports.sub = sub;
exports.toQEQ = toQEQ;
exports.isZero = isZero;
exports.toString = toString;
exports.canonize = canonize;
exports.substitute = substitute;
function signal2lc(a) {
let lc;
if (a.type == "SIGNAL") {
lc = {
type: "LINEARCOMBINATION",
values: {}
};
lc.values[a.sIdx] = bigInt(1);
return lc;
} else {
return a;
class LCAlgebra {
constructor (aField) {
const self = this;
this.field= aField;
[
["lt",2],
["leq",2],
["eq",2],
["neq",2],
["geq",2],
["gt",2],
["idiv",2],
["mod",2],
["band",2],
["bor",2],
["bxor",2],
["bnot",2],
["land",2],
["lor",2],
["lnot",2],
["shl",2],
["shr",2],
].forEach( (op) => {
self._genNQOp(op[0], op[1]);
});
} }
}
function clone(a) {
const res = {};
res.type = a.type;
if (a.type == "NUMBER") {
res.value = bigInt(a.value);
} else if (a.type == "LINEARCOMBINATION") {
res.values = {};
for (let k in a.values) {
res.values[k] = bigInt(a.values[k]);
}
} else if (a.type == "QEQ") {
res.a = clone(a.a);
res.b = clone(a.b);
res.c = clone(a.c);
} else if (a.type == "ERROR") {
res.errStr = a.errStr;
} else {
res.type = "ERROR";
res.errStr = "Invilid type when clonning: "+a.type;
_genNQOp(op, nOps) {
const self=this;
self[op] = function() {
const operands = [];
for (let i=0; i<nOps; i++) {
if (typeof(arguments[i]) !== "object") throw new Error("Invalid operand type");
if (arguments[i].t !== "N") return {t: "NQ"};
operands.push(arguments[i].v);
}
return {
t: "N",
v: self.field[op](...operands)
};
};
} }
return res;
}
function add(_a, _b) {
const a = signal2lc(_a);
const b = signal2lc(_b);
if (a.type == "ERROR") return a;
if (b.type == "ERROR") return b;
if (a.type == "NUMBER") {
if (b.type == "NUMBER") {
return addNumNum(a,b);
} else if (b.type=="LINEARCOMBINATION") {
return addLCNum(b,a);
} else if (b.type=="QEQ") {
return addQEQNum(b,a);
} else {
return { type: "ERROR", errStr: "LC Add Invalid Type 2: "+b.type };
}
} else if (a.type=="LINEARCOMBINATION") {
if (b.type == "NUMBER") {
return addLCNum(a,b);
} else if (b.type=="LINEARCOMBINATION") {
return addLCLC(a,b);
} else if (b.type=="QEQ") {
return addQEQLC(b,a);
} else {
return { type: "ERROR", errStr: "LC Add Invalid Type 2: "+b.type };
}
} else if (a.type=="QEQ") {
if (b.type == "NUMBER") {
return addQEQNum(a,b);
} else if (b.type=="LINEARCOMBINATION") {
return addQEQLC(a,b);
} else if (b.type=="QEQ") {
return { type: "ERROR", errStr: "QEQ + QEQ" };
_signal2lc(a) {
if (a.t == "S") {
const lc = {
t: "LC",
coefs: {}
};
lc.coefs[a.sIdx] = bigInt(1);
return lc;
} else { } else {
return { type: "ERROR", errStr: "LC Add Invalid Type 2: "+b.type };
return a;
} }
} else {
return { type: "ERROR", errStr: "LC Add Invalid Type 1: "+a.type };
} }
}
function addNumNum(a,b) {
if (!a.value || !b.value) return { type: "NUMBER" };
return {
type: "NUMBER",
value: a.value.add(b.value).mod(__P__)
};
}
function addLCNum(a,b) {
let res = clone(a);
if (!b.value) {
return { type: "ERROR", errStr: "LinearCombination + undefined" };
}
if (b.value.isZero()) return res;
if (!res.values[sONE]) {
res.values[sONE]=bigInt(b.value);
} else {
res.values[sONE]= res.values[sONE].add(b.value).mod(__P__);
_clone(a) {
const res = {};
res.t = a.t;
if (a.t == "N") {
res.v = a.v;
} else if (a.t == "S") {
res.sIdx = a.sIdx;
} else if (a.t == "LC") {
res.coefs = {};
for (let k in a.coefs) {
res.coefs[k] = a.coefs[k];
}
} else if (a.t == "QEX") {
res.a = this._clone(a.a);
res.b = this._clone(a.b);
res.c = this._clone(a.c);
}
return res;
} }
return res;
}
function addLCLC(a,b) {
let res = clone(a);
for (let k in b.values) {
if (!res.values[k]) {
res.values[k]=bigInt(b.values[k]);
add(_a,_b) {
const self = this;
const a = self._signal2lc(_a);
const b = self._signal2lc(_b);
if (a.t == "NQ") return a;
if (b.t == "NQ") return b;
if (a.t == "N") {
if (b.t == "N") {
return add_N_N(a,b);
} else if (b.t=="LC") {
return add_LC_N(b,a);
} else if (b.t=="QEX") {
return add_QEX_N(b,a);
} else {
return { type: "NQ" };
}
} else if (a.t=="LC") {
if (b.t == "N") {
return add_LC_N(a,b);
} else if (b.t=="LC") {
return add_LC_LC(a,b);
} else if (b.t=="QEX") {
return add_QEX_LC(b,a);
} else {
return { t: "NQ" };
}
} else if (a.t=="QEX") {
if (b.t == "N") {
return add_QEX_N(a,b);
} else if (b.t=="LC") {
return add_QEX_LC(a,b);
} else if (b.t=="QEX") {
return { t: "NQ" };
} else {
return { t: "NQ" };
}
} else { } else {
res.values[k]= res.values[k].add(b.values[k]).mod(__P__);
return { t: "NQ" };
} }
}
return res;
}
function addQEQNum(a,b) {
let res = clone(a);
res.c = addLCNum(res.c, b);
if (res.c.type == "ERROR") return res.c;
return res;
}
function add_N_N(a,b) {
return {
t: "N",
v: self.field.add(a.v, b.v)
};
}
function addQEQLC(a,b) {
let res = clone(a);
res.c = addLCLC(res.c, b);
if (res.c.type == "ERROR") return res.c;
return res;
}
function add_LC_N(a,b) {
let res = self._clone(a);
if (b.v.isZero()) return res;
if (!utils.isDefined(res.coefs[sONE])) {
res.coefs[sONE]= b.v;
} else {
res.coefs[sONE]= self.field.add(res.coefs[sONE], b.v);
}
return res;
}
function mul(_a, _b) {
const a = signal2lc(_a);
const b = signal2lc(_b);
if (a.type == "ERROR") return a;
if (b.type == "ERROR") return b;
if (a.type == "NUMBER") {
if (b.type == "NUMBER") {
return mulNumNum(a,b);
} else if (b.type=="LINEARCOMBINATION") {
return mulLCNum(b,a);
} else if (b.type=="QEQ") {
return mulQEQNum(b,a);
} else {
return { type: "ERROR", errStr: "LC Mul Invalid Type 2: "+b.type };
function add_LC_LC(a,b) {
let res = self._clone(a);
for (let k in b.coefs) {
if (!utils.isDefined(res.coefs[k])) {
res.coefs[k]=b.coefs[k];
} else {
res.coefs[k]= self.field.add(res.coefs[k], b.coefs[k]);
}
}
return res;
} }
} else if (a.type=="LINEARCOMBINATION") {
if (b.type == "NUMBER") {
return mulLCNum(a,b);
} else if (b.type=="LINEARCOMBINATION") {
return mulLCLC(a,b);
} else if (b.type=="QEQ") {
return { type: "ERROR", errStr: "LC * QEQ" };
} else {
return { type: "ERROR", errStr: "LC Mul Invalid Type 2: "+b.type };
function add_QEX_N(a,b) {
let res = self._clone(a);
res.c = add_LC_N(res.c, b);
return res;
} }
} else if (a.type=="QEQ") {
if (b.type == "NUMBER") {
return mulQEQNum(a,b);
} else if (b.type=="LINEARCOMBINATION") {
return { type: "ERROR", errStr: "QEC * LC" };
} else if (b.type=="QEQ") {
return { type: "ERROR", errStr: "QEQ * QEQ" };
} else {
return { type: "ERROR", errStr: "LC Mul Invalid Type 2: "+b.type };
function add_QEX_LC(a,b) {
let res = self._clone(a);
res.c = add_LC_LC(res.c, b);
return res;
} }
} else {
return { type: "ERROR", errStr: "LC Mul Invalid Type 1: "+a.type };
} }
}
mul(_a,_b) {
const self = this;
const a = self._signal2lc(_a);
const b = self._signal2lc(_b);
if (a.t == "NQ") return a;
if (b.t == "NQ") return b;
if (a.t == "N") {
if (b.t == "N") {
return mul_N_N(a,b);
} else if (b.t=="LC") {
return mul_LC_N(b,a);
} else if (b.t=="QEX") {
return mul_QEX_N(b,a);
} else {
return { t: "NQ"};
}
} else if (a.t=="LC") {
if (b.t == "N") {
return mul_LC_N(a,b);
} else if (b.t=="LC") {
return mul_LC_LC(a,b);
} else if (b.t=="QEX") {
return { t: "NQ" };
} else {
return { t: "NQ" };
}
} else if (a.t=="QEX") {
if (b.t == "N") {
return mul_QEX_N(a,b);
} else if (b.t=="LC") {
return { t: "NQ" };
} else if (b.t=="QEX") {
return { t: "NQ" };
} else {
return { t: "NQ" };
}
} else {
return { t: "NQ" };
}
function mulNumNum(a,b) {
if (!a.value || !b.value) return { type: "NUMBER" };
return {
type: "NUMBER",
value: a.value.times(b.value).mod(__P__)
};
}
function mul_N_N(a,b) {
return {
t: "N",
v: self.field.mul(a.v, b.v)
};
}
function mulLCNum(a,b) {
let res = clone(a);
if (!b.value) {
return {type: "ERROR", errStr: "LinearCombination * undefined"};
}
for (let k in res.values) {
res.values[k] = res.values[k].times(b.value).mod(__P__);
}
return res;
}
function mul_LC_N(a,b) {
let res = self._clone(a);
for (let k in res.coefs) {
res.coefs[k] = self.field.mul(res.coefs[k], b.v);
}
return res;
}
function mulLCLC(a,b) {
return {
type: "QEQ",
a: clone(a),
b: clone(b),
c: { type: "LINEARCOMBINATION", values: {}}
};
}
function mul_LC_LC(a,b) {
return {
t: "QEX",
a: self._clone(a),
b: self._clone(b),
c: { t: "LC", coefs: {}}
};
}
function mulQEQNum(a,b) {
let res = {
type: "QEQ",
a: mulLCNum(a.a, b),
b: clone(a.b),
c: mulLCNum(a.c, b)
};
if (res.a.type == "ERROR") return res.a;
if (res.c.type == "ERROR") return res.a;
return res;
}
function mul_QEX_N(a,b) {
return {
t: "QEX",
a: mul_LC_N(a.a, b),
b: self._clone(a.b),
c: mul_LC_N(a.c, b)
};
}
}
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.v) {
res.value = s.v;
neg(_a) {
const a = this._signal2lc(_a);
let res = this._clone(a);
if (res.t == "N") {
res.v = this.field.neg(a.v);
} else if (res.t == "LC") {
for (let k in res.coefs) {
res.coefs[k] = this.field.neg(res.coefs[k]);
}
} else if (res.t == "QEX") {
res.a = this.neg(res.a);
res.c = this.neg(res.c);
} else {
res = {t: "NQ"};
} }
return res; return res;
} }
}
function evaluate(ctx, n) {
if (n.type == "NUMBER") {
return n;
} else if (n.type == "SIGNAL") {
return getSignalValue(ctx, n.sIdx);
} else if (n.type == "LINEARCOMBINATION") {
const v= {
type: "NUMBER",
value: bigInt(0)
};
for (let k in n.values) {
const s = getSignalValue(ctx, k);
if (s.type != "NUMBER") return {type: "ERROR", errStr: "Invalid signal in linear Combination: " + k};
if (!s.value) return { type: "NUMBER" };
v.value = v.value.add( n.values[k].times(s.value)).mod(__P__);
sub(a, b) {
return this.add(a, this.neg(b));
}
div(a, b) {
if (b.t == "N") {
if (b.v.isZero()) throw new Error("Division by zero");
const inv = {
t: "N",
v: this.field.inv(b.v)
};
return this.mul(a, inv);
} else {
return {t: "NQ"};
} }
return v;
} else if (n.type == "QEQ") {
const a = evaluate(ctx, n.a);
if (a.type == "ERROR") return a;
if (!a.value) return { type: "NUMBER" };
const b = evaluate(ctx, n.b);
if (b.type == "ERROR") return b;
if (!b.value) return { type: "NUMBER" };
const c = evaluate(ctx, n.c);
if (c.type == "ERROR") return c;
if (!c.value) return { type: "NUMBER" };
return {
type: "NUMBER",
value: (a.value.times(b.value).add(c.value)).mod(__P__)
};
} else if (n.type == "ERROR") {
return n;
} else {
return {type: "ERROR", errStr: "Invalid type in evaluate: "+n.type};
} }
}
function negate(_a) {
const a = signal2lc(_a);
let res = clone(a);
if (res.type == "NUMBER") {
res.value = __P__.minus(a.value).mod(__P__);
} else if (res.type == "LINEARCOMBINATION") {
for (let k in res.values) {
res.values[k] = __P__.minus(res.values[k]).mod(__P__);
pow(a, b) {
if (b.t == "N") {
if (b.v.isZero()) {
if (this.isZero(a)) {
throw new Error("Zero to the Zero");
}
return {
t: "N",
v: this.field.one
};
} else if (b.v.eq(this.field.one)) {
return a;
} else if (b.v.eq(bigInt(2))) {
return this.mul(a,a);
} else {
if (a.t=="N") {
return {
t: "N",
v: this.field.pow(a.v, b.v)
};
} else {
return {t: "NQ"};
}
}
} else {
return {t: "NQ"};
} }
} else if (res.type == "QEQ") {
res.a = negate(res.a);
res.c = negate(res.c);
} else if (res.type == "ERROR") {
return res;
} else {
res = {type: "ERROR", errStr: "LC Negate invalid Type: "+res.type};
} }
return res;
}
function sub(a, b) {
return add(a, negate(b));
}
substitute(where, signal, equivalence) {
if (equivalence.t != "LC") throw new Error("Equivalence must be a Linear Combination");
if (where.t == "LC") {
if (!utils.isDefined(where.coefs[signal]) || where.coefs[signal].isZero()) return where;
const res=this._clone(where);
const coef = res.coefs[signal];
for (let k in equivalence.coefs) {
if (k != signal) {
const v = this.field.mul( coef, equivalence.coefs[k] );
if (!utils.isDefined(res.coefs[k])) {
res.coefs[k]=v;
} else {
res.coefs[k]= this.field.add(res.coefs[k],v);
}
if (res.coefs[k].isZero()) delete res.coefs[k];
}
}
delete res.coefs[signal];
return res;
} else if (where.t == "QEX") {
const res = {
t: "QEX",
a: this.substitute(where.a, signal, equivalence),
b: this.substitute(where.b, signal, equivalence),
c: this.substitute(where.c, signal, equivalence)
};
return res;
} else {
return where;
}
}
function toQEQ(a) {
if (a.type == "NUMBER") {
return {
type: "QEQ",
a: {type: "LINEARCOMBINATION", values: {}},
b: {type: "LINEARCOMBINATION", values: {}},
c: {type: "LINEARCOMBINATION", values: {sONE: bigInt(a.value)}}
};
} else if (a.type == "LINEARCOMBINATION") {
return {
type: "QEQ",
a: {type: "LINEARCOMBINATION", values: {}},
b: {type: "LINEARCOMBINATION", values: {}},
c: clone(a)
};
} else if (a.type == "QEQ") {
return clone(a);
} else if (a.type == "ERROR") {
return clone(a);
} else {
return {type: "ERROR", errStr: "toQEQ invalid Type: "+a.type};
toQEX(a) {
if (a.t == "N") {
const res = {
t: "QEX",
a: {t: "LC", coefs: {}},
b: {t: "LC", coefs: {}},
c: {t: "LC", coefs: {}}
};
res.c[sONE] = a.v;
return res;
} else if (a.t == "LC") {
return {
t: "QEX",
a: {t: "LC", coefs: {}},
b: {t: "LC", coefs: {}},
c: this._clone(a)
};
} else if (a.t == "QEX") {
return this._clone(a);
} else {
throw new Error(`Type ${a.t} can not be converted to QEX`);
}
} }
}
function isZero(a) {
if (a.type == "NUMBER") {
return a.value.isZero();
} else if (a.type == "LINEARCOMBINATION") {
for (let k in a.values) {
if (!a.values[k].isZero()) return false;
isZero(a) {
if (a.t == "N") {
return a.v.isZero();
} else if (a.t == "LC") {
for (let k in a.coefs) {
if (!a.coefs[k].isZero()) return false;
}
return true;
} else if (a.t == "QEX") {
return (this.isZero(a.a) || this.isZero(a.b)) && this.isZero(a.c);
} else {
return false;
} }
return true;
} else if (a.type == "QEQ") {
return (isZero(a.a) || isZero(a.b)) && isZero(a.c);
} else if (a.type == "ERROR") {
return false;
} else {
return false;
} }
}
function toString(a, ctx) {
if (a.type == "NUMBER") {
return a.value.toString();
} else if (a.type == "LINEARCOMBINATION") {
let S="";
for (let k in a.values) {
if (!a.values[k].isZero()) {
let c;
if (a.values[k].greater(__P__.divide(2))) {
S = S + "-";
c = __P__.minus(a.values[k]);
} else {
if (S!="") S=S+" + ";
c = a.values[k];
toString(a, ctx) {
if (a.t == "N") {
return a.v.toString();
} else if (a.t == "LC") {
let S="";
for (let k in a.coefs) {
if (!a.coefs[k].isZero()) {
let c;
if (a.coefs[k].greater(this.field.p.divide(2))) {
S = S + "-";
c = this.field.p.minus(a.coefs[k]);
} else {
if (S!="") S=S+" + ";
c = a.coefs[k];
}
if (!c.equals(bigInt.one)) {
S = S + c.toString() + "*";
}
let sIdx = k;
if (ctx) {
while (ctx.signals[sIdx].e>=0) sIdx = ctx.signals[sIdx].e;
}
S = S + "[" + sIdx + "]";
} }
if (!c.equals(1)) {
S = S + c.toString() + "*";
}
let sIdx = k;
if (ctx) {
while (ctx.signals[sIdx].e>=0) sIdx = ctx.signals[sIdx].e;
}
S = S + "[" + sIdx + "]";
} }
if (S=="") return "0"; else return S;
} else if (a.t == "QEX") {
return "( "+
this.toString(a.a, ctx)+" ) * ( "+
this.toString(a.b, ctx)+" ) + " +
this.toString(a.c, ctx);
} else {
return "NQ";
} }
if (S=="") return "0"; else return S;
} else if (a.type == "QEQ") {
return "( "+toString(a.a, ctx)+" ) * ( "+toString(a.b, ctx)+" ) + " + toString(a.c, ctx);
} else if (a.type == "ERROR") {
return "ERROR: "+a.errStr;
} else {
return "INVALID";
} }
}
function canonize(ctx, a) {
if (a.type == "LINEARCOMBINATION") {
const res = clone(a);
for (let k in a.values) {
let s = k;
while (ctx.signals[s].e>=0) s= ctx.signals[s].e;
if (utils.isDefined(ctx.signals[s].v)&&(k != sONE)) {
const v = res.values[k].times(ctx.signals[s].v).mod(__P__);
if (!res.values[sONE]) {
res.values[sONE]=v;
} else {
res.values[sONE]= res.values[sONE].add(v).mod(__P__);
}
delete res.values[k];
} else if (s != k) {
if (!res.values[s]) {
res.values[s]=bigInt(res.values[k]);
} else {
res.values[s]= res.values[s].add(res.values[k]).mod(__P__);
}
delete res.values[k];
evaluate(ctx, n) {
if (n.t == "N") {
return n.v;
} else if (n.t == "SIGNAL") {
return getSignalValue(ctx, n.sIdx);
} else if (n.t == "LC") {
let v= this.field.zero;
for (let k in n.coefs) {
const s = getSignalValue(ctx, k);
if (s === null) return null;
v = this.field.add(v, this.field.mul( n.coefs[k], s));
} }
return v;
} else if (n.type == "QEX") {
const a = this.evaluate(ctx, n.a);
if (a === null) return null;
const b = this.evaluate(ctx, n.b);
if (b === null) return null;
const c = this.evaluate(ctx, n.c);
if (c === null) return null;
return this.field.add(this.field.mul(a,b), c);
} else {
return null;
} }
for (let k in res.values) {
if (res.values[k].isZero()) delete res.values[k];
function getSignalValue(ctx, sIdx) {
let s = ctx.signals[sIdx];
while (s.e>=0) s = ctx.signals[s.e];
if (utils.isDefined(s.v)) return s.v;
return null;
} }
return res;
} else if (a.type == "QEQ") {
const res = {
type: "QEQ",
a: canonize(ctx, a.a),
b: canonize(ctx, a.b),
c: canonize(ctx, a.c)
};
return res;
} else {
return a;
} }
}
function substitute(where, signal, equivalence) {
if (equivalence.type != "LINEARCOMBINATION") throw new Error("Equivalence must be a Linear Combination");
if (where.type == "LINEARCOMBINATION") {
if (!where.values[signal] || where.values[signal].isZero()) return where;
const res=clone(where);
const coef = res.values[signal];
for (let k in equivalence.values) {
if (k != signal) {
const v = coef.times(equivalence.values[k]).mod(__P__);
if (!res.values[k]) {
res.values[k]=v;
} else {
res.values[k]= res.values[k].add(v).mod(__P__);
canonize(ctx, a) {
if (a.t == "LC") {
const res = this._clone(a);
for (let k in a.coefs) {
let s = k;
while (ctx.signals[s].e>=0) s= ctx.signals[s].e;
if (utils.isDefined(ctx.signals[s].v)&&(k != sONE)) {
const v = this.field.mul(res.coefs[k], ctx.signals[s].v);
if (!utils.isDefined(res.coefs[sONE])) {
res.coefs[sONE]=v;
} else {
res.coefs[sONE]= this.field.add(res.coefs[sONE], v);
}
delete res.coefs[k];
} else if (s != k) {
if (!utils.isDefined(res.coefs[s])) {
res.coefs[s]=res.coefs[k];
} else {
res.coefs[s]= this.field.add(res.coefs[s], res.coefs[k]);
}
delete res.coefs[k];
} }
if (res.values[k].isZero()) delete res.values[k];
} }
for (let k in res.coefs) {
if (res.coefs[k].isZero()) delete res.coefs[k];
}
return res;
} else if (a.t == "QEX") {
const res = {
t: "QEX",
a: this.canonize(ctx, a.a),
b: this.canonize(ctx, a.b),
c: this.canonize(ctx, a.c)
};
return res;
} else {
return a;
} }
delete res.values[signal];
return res;
} else if (where.type == "QEQ") {
const res = {
type: "QEQ",
a: substitute(where.a, signal, equivalence),
b: substitute(where.b, signal, equivalence),
c: substitute(where.c, signal, equivalence)
};
return res;
} else {
return where;
} }
} }
module.exports = LCAlgebra;

+ 4
- 5
src/r1csfile.js

@ -1,7 +1,6 @@
const fs = require("fs"); const fs = require("fs");
const assert = require("assert"); const assert = require("assert");
const lc = require("./lcalgebra");
const bigInt = require("big-integer"); const bigInt = require("big-integer");
module.exports.buildR1cs = buildR1cs; module.exports.buildR1cs = buildR1cs;
@ -262,19 +261,19 @@ async function buildR1cs(ctx, fileName) {
async function writeConstraint(c) { async function writeConstraint(c) {
await writeLC(c.a); await writeLC(c.a);
await writeLC(c.b); await writeLC(c.b);
await writeLC(lc.negate(c.c));
await writeLC(ctx.lc.neg(c.c));
} }
async function writeLC(lc) { async function writeLC(lc) {
const idxs = Object.keys(lc.values);
const idxs = Object.keys(lc.coefs);
await writeU32(idxs.length); await writeU32(idxs.length);
for (let s in lc.values) {
for (let s in lc.coefs) {
let lSignal = ctx.signals[s]; let lSignal = ctx.signals[s];
while (lSignal.e >=0 ) lSignal = ctx.signals[lSignal.e]; while (lSignal.e >=0 ) lSignal = ctx.signals[lSignal.e];
await writeU32(lSignal.id); await writeU32(lSignal.id);
await writeBigInt(lc.values[s]);
await writeBigInt(lc.coefs[s]);
} }
} }

+ 1
- 0
src/utils.js

@ -124,3 +124,4 @@ function accSizes2Str(sizes) {

+ 0
- 118
src/zqfield.js

@ -1,118 +0,0 @@
const bigInt = require("big-integer");
const assert = require("assert");
module.exports = class ZqField {
constructor(p) {
this.one = bigInt.one;
this.zero = bigInt.zero;
this.p = p;
this.bitLength = p.bitLength();
this.mask = bigInt.one.shiftLeft(this.bitLength - 1).minus(bigInt.one);
}
add(a, b) {
let res = a.add(b);
if (res.geq(this.p)) {
res = res.minus(this.p);
}
return res;
}
sub(a, b) {
if (a.geq(b)) {
return a.minus(b);
} else {
return this.p.minus(b.minus(a));
}
}
neg(a) {
if (a.isZero()) return a;
return this.p.minus(a);
}
mul(a, b) {
return a.times(b).mod(this.p);
}
lt(a, b) {
return a.lt(b) ? bigInt(1) : bigInt(0);
}
eq(a, b) {
return a.eq(b) ? bigInt(1) : bigInt(0);
}
gt(a, b) {
return a.gt(b) ? bigInt(1) : bigInt(0);
}
leq(a, b) {
return a.leq(b) ? bigInt(1) : bigInt(0);
}
geq(a, b) {
return a.geq(b) ? bigInt(1) : bigInt(0);
}
neq(a, b) {
return a.neq(b) ? bigInt(1) : bigInt(0);
}
div(a, b) {
assert(!b.isZero(), "Division by zero");
return a.times(b.modInv(this.p)).mod(this.p);
}
idiv(a, b) {
assert(!b.isZero(), "Division by zero");
return a.divide(b);
}
mod(a, b) {
return a.mod(b);
}
pow(a, b) {
return a.modPow(b, this.p);
}
band(a, b) {
return a.and(b).and(this.mask);
}
bor(a, b) {
return a.or(b).and(this.mask);
}
bxor(a, b) {
return a.xor(b).and(this.mask);
}
bnot(a) {
return a.xor(this.mask).and(this.mask);
}
shl(a, b) {
if (b.geq(this.bitLength)) return bigInt.zero;
return a.shiftLeft(b).and(this.mask);
}
shr(a, b) {
if (b.geq(this.bitLength)) return bigInt.zero;
return a.shiftRight(b).and(this.mask);
}
land(a, b) {
return (a.isZero() || b.isZero) ? bigInt.zero : bigInt.one;
}
lor(a, b) {
return (a.isZero() && b.isZero) ? bigInt.zero : bigInt.one;
}
lnot(a) {
return a.isZero() ? bigInt.one : bigInt.zero;
}
};

+ 24
- 3
test/basiccases.js

@ -44,7 +44,8 @@ async function doTest(circuit, testVectors) {
describe("basic cases", function () { describe("basic cases", function () {
this.timeout(100000); this.timeout(100000);
/* it("inout", async () => {
/*
it("inout", async () => {
await doTest( await doTest(
"inout.circom", "inout.circom",
[ [
@ -298,6 +299,17 @@ describe("basic cases", function () {
] ]
); );
}); });
*/
it("Component array 2d", async () => {
await doTest(
"componentarray2.circom",
[
[{in: [1,2]}, {out: [1, 256]}],
[{in: [0,3]}, {out: [0, 6561]}],
]
);
});
/*
it("Constant circuit", async () => { it("Constant circuit", async () => {
await doTest( await doTest(
"constantcircuit.circom", "constantcircuit.circom",
@ -306,12 +318,11 @@ describe("basic cases", function () {
[{}, {out: [1,0,1,0, 0,0,0,1, 0,1,1,1, 0,1,0,1, 1,1,1,0, 0,1,1,0, 1,1,0,1, 1,1,0,1]}], [{}, {out: [1,0,1,0, 0,0,0,1, 0,1,1,1, 0,1,0,1, 1,1,1,0, 0,1,1,0, 1,1,0,1, 1,1,0,1]}],
] ]
); );
}); */
});
it("Constant internal circuit", async () => { it("Constant internal circuit", async () => {
await doTest( await doTest(
"constantinternalcircuit.circom", "constantinternalcircuit.circom",
[ [
// 0xbb67ae85
[{in: 1}, {out: 5}], [{in: 1}, {out: 5}],
[{in: 0}, {out: 4}], [{in: 0}, {out: 4}],
[{in: -2}, {out: 2}], [{in: -2}, {out: 2}],
@ -319,4 +330,14 @@ describe("basic cases", function () {
] ]
); );
}); });
it("include", async () => {
await doTest(
"include.circom",
[
[{in: 3}, {out: 6}],
[{in: 6}, {out: 15}],
]
);
});
*/
}); });

+ 5
- 8
test/circuits/arrays.circom

@ -4,18 +4,15 @@
function Add3(arr1, arr2, arr3) { function Add3(arr1, arr2, arr3) {
var res[3]; var res[3];
var i;
var j;
res[0] = arr1; res[0] = arr1;
res[1] = 0; res[1] = 0;
for (i=0; i<2; i += 1) {
for (var i=0; i<2; i += 1) {
res[1] = res[1] + arr2[i]; res[1] = res[1] + arr2[i];
} }
res[2] = 0; res[2] = 0;
for (i=0; i<2; i++) {
for (j=0; j<3; j += 1) {
for (var i=0; i<2; i++) {
for (var j=0; j<3; j += 1) {
res[2] = res[2] + arr3[i][j]; res[2] = res[2] + arr3[i][j];
} }
} }
@ -27,8 +24,8 @@ template Main() {
signal input in; signal input in;
signal output out[3]; signal output out[3];
var c = Add3(1, [2,3], [[4,5,6], [7,8,9]]); // [1, 5, 39];
var d = Add3(in, [in+1, in+2], [[in+1, in+2, in+3], [in+1, in+2, in+3]]);
var c[3] = Add3(1, [2,3], [[4,5,6], [7,8,9]]); // [1, 5, 39];
var d[3] = Add3(in, [in+1, in+2], [[in+1, in+2, in+3], [in+1, in+2, in+3]]);
out[0] <-- d[0] + c[0]; out[0] <-- d[0] + c[0];
out[0] === in+c[0]; out[0] === in+c[0];

+ 27
- 0
test/circuits/componentarray2.circom

@ -0,0 +1,27 @@
template Square() {
signal input in;
signal output out;
out <== in**2;
}
template Main(n, nrounds) {
signal input in[n];
signal output out[n];
component squares[n][nrounds];
for (var i=0; i<n; i++) {
for (var r=0; r<nrounds; r++) {
squares[i][r] = Square();
if (r==0) {
squares[i][r].in <== in[i];
} else {
squares[i][r].in <== squares[i][r-1].out;
}
}
squares[i][nrounds-1].out ==> out[i];
}
}
component main = Main(2, 3);

+ 1
- 1
test/circuits/constantcircuit.circom

@ -1,6 +1,6 @@
template H(x) { template H(x) {
signal output out[32]; signal output out[32];
var c = [0x6a09e667,
var c[8] = [0x6a09e667,
0xbb67ae85, 0xbb67ae85,
0x3c6ef372, 0x3c6ef372,
0xa54ff53a, 0xa54ff53a,

+ 16
- 0
test/circuits/include.circom

@ -0,0 +1,16 @@
include "included.circom";
include "included.circom"; // Include twice is fine. The second one is not included
template Main() {
signal input in;
signal output out;
component t1 = T1();
var a = F1(3);
in ==> t1.in;
t1.out + a ==> out; /// out <-- in**2/3+3
}
component main = Main();

+ 10
- 0
test/circuits/included.circom

@ -0,0 +1,10 @@
template T1() {
signal input in;
signal output out;
out <== in**2/3;
}
function F1(a) {
return a**2/3;
}

Loading…
Cancel
Save