You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

571 lines
16 KiB

/*
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/>.
*/
/*
// Number
///////////////
N: a
{
t: "N",
v: bigInt(a)
}
// Signal
///////////////
{
t: "S",
sIdx: sIdx
}
// Linear Convination
//////////////////
LC: c1*s1 + c2*s2 + c3*s3
{
t: "LC",
coefs: {
s1: bigInt(c1),
s2: bigInt(c2),
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: {...} }
}
NQ: Non quadratic expression
{
t: "NQ"
}
*/
/*
+ 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 utils = require("./utils");
const sONE = 0;
class LCAlgebra {
constructor (aField) {
const self = this;
this.F= aField;
[
["idiv",2],
["mod",2],
["band",2],
["bor",2],
["bxor",2],
["bnot",2],
["land",2],
["lor",2],
["lnot",2],
["shl",2],
["shr",2],
["lt",2, true],
["leq",2, true],
["eq",2, true],
["neq",2, true],
["geq",2, true],
["gt",2, true]
].forEach( (op) => {
self._genNQOp(op[0], op[1], op[2]);
});
}
_genNQOp(op, nOps, adjustBool) {
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: adjustBool ? ( self.F[op](...operands) ? self.F.one: self.F.zero) : self.F[op](...operands)
};
};
}
_signal2lc(a) {
if (a.t == "S") {
const lc = {
t: "LC",
coefs: {}
};
lc.coefs[a.sIdx] = self.F.one;
return lc;
} else {
return a;
}
}
_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;
}
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 {
return { t: "NQ" };
}
function add_N_N(a,b) {
return {
t: "N",
v: self.F.add(a.v, b.v)
};
}
function add_LC_N(a,b) {
let res = self._clone(a);
if (self.F.isZero(b.v)) return res;
if (!utils.isDefined(res.coefs[sONE])) {
res.coefs[sONE]= b.v;
} else {
res.coefs[sONE]= self.F.add(res.coefs[sONE], b.v);
}
return res;
}
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.F.add(res.coefs[k], b.coefs[k]);
}
}
return res;
}
function add_QEX_N(a,b) {
let res = self._clone(a);
res.c = add_LC_N(res.c, b);
return res;
}
function add_QEX_LC(a,b) {
let res = self._clone(a);
res.c = add_LC_LC(res.c, b);
return res;
}
}
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 mul_N_N(a,b) {
return {
t: "N",
v: self.F.mul(a.v, b.v)
};
}
function mul_LC_N(a,b) {
let res = self._clone(a);
for (let k in res.coefs) {
res.coefs[k] = self.F.mul(res.coefs[k], b.v);
}
return res;
}
function mul_LC_LC(a,b) {
return {
t: "QEX",
a: self._clone(a),
b: self._clone(b),
c: { t: "LC", coefs: {}}
};
}
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)
};
}
}
neg(_a) {
const a = this._signal2lc(_a);
let res = this._clone(a);
if (res.t == "N") {
res.v = this.F.neg(a.v);
} else if (res.t == "LC") {
for (let k in res.coefs) {
res.coefs[k] = this.F.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;
}
sub(a, b) {
return this.add(a, this.neg(b));
}
div(a, b) {
if (b.t == "N") {
if (this.F.isZero(b.v)) throw new Error("Division by zero");
const inv = {
t: "N",
v: this.F.inv(b.v)
};
return this.mul(a, inv);
} else {
return {t: "NQ"};
}
}
pow(a, b) {
if (b.t == "N") {
if (this.F.isZero(b.v)) {
if (this.isZero(a)) {
throw new Error("Zero to the Zero");
}
return {
t: "N",
v: this.F.one
};
} else if (this.F.eq(b.v, this.F.one)) {
return a;
} else if (this.F.eq(b.v, this.F.two)) {
return this.mul(a,a);
} else {
if (a.t=="N") {
return {
t: "N",
v: this.F.pow(a.v, b.v)
};
} else {
return {t: "NQ"};
}
}
} else {
return {t: "NQ"};
}
}
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]) || this.F.isZero(where.coefs[signal])) return where;
const res=this._clone(where);
const coef = res.coefs[signal];
for (let k in equivalence.coefs) {
if (k != signal) {
const v = this.F.mul( coef, equivalence.coefs[k] );
if (!utils.isDefined(res.coefs[k])) {
res.coefs[k]=v;
} else {
res.coefs[k]= this.F.add(res.coefs[k],v);
}
if (this.F.isZero(res.coefs[k])) 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;
}
}
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`);
}
}
isZero(a) {
if (a.t == "N") {
return this.F.isZero(a.v);
} else if (a.t == "LC") {
for (let k in a.coefs) {
if (!this.F.isZero(a.coefs[k])) 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;
}
}
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 (!this.F.isZero(a.coefs[k])) {
let c;
if (a.coefs[k].greater(this.F.p.divide(2))) {
S = S + "-";
c = this.F.p.minus(a.coefs[k]);
} else {
if (S!="") S=S+" + ";
c = a.coefs[k];
}
if (!c.equals(this.F.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 (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";
}
}
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.F.zero;
for (let k in n.coefs) {
const s = getSignalValue(ctx, k);
if (s === null) return null;
v = this.F.add(v, this.F.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.F.add(this.F.mul(a,b), c);
} else {
return null;
}
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;
}
}
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.F.mul(res.coefs[k], ctx.signals[s].v);
if (!utils.isDefined(res.coefs[sONE])) {
res.coefs[sONE]=v;
} else {
res.coefs[sONE]= this.F.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.F.add(res.coefs[s], res.coefs[k]);
}
delete res.coefs[k];
}
}
for (let k in res.coefs) {
if (this.F.isZero(res.coefs[k])) 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;
}
}
}
module.exports = LCAlgebra;