/* 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 . */ const path = require("path"); const fs = require("fs"); const utils = require("./utils"); const bigInt = require("big-integer"); const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617"); const __MASK__ = new bigInt(2).pow(253).minus(1); const lc = require("./lcalgebra"); const parser = require("../parser/jaz.js").parser; /* TODO: Add lines information function setLines(dst, first, last) { last = last || first; dst.first_line = first.first_line; dst.first_column = first.first_column; dst.last_line = last.last_line; dst.last_column = last.last_column; } */ module.exports = exec; function exec(ctx, ast) { if (!ast) { return error(ctx, ast, "Null AST"); } if ((ast.type == "NUMBER") || (ast.type == "LINEARCOMBINATION") || (ast.type =="SIGNAL") || (ast.type == "QEQ")) { return ast; } else if (ast.type == "VARIABLE") { return execVariable(ctx, ast); } else if (ast.type == "PIN") { return execPin(ctx, ast); } else if (ast.type == "OP") { if (ast.op == "=") { return execVarAssignement(ctx, ast); } else if (ast.op == "<--") { return execSignalAssign(ctx, ast); } else if (ast.op == "<==") { return execSignalAssignConstrain(ctx, ast); } else if (ast.op == "===") { return execConstrain(ctx, ast); } else if (ast.op == "+=") { return execVarAddAssignement(ctx, ast); } else if (ast.op == "*=") { return execVarMulAssignement(ctx, ast); } else if (ast.op == "+") { return execAdd(ctx, ast); } else if (ast.op == "-") { return execSub(ctx, ast); } else if (ast.op == "UMINUS") { return execUMinus(ctx, ast); } else if (ast.op == "*") { return execMul(ctx, ast); } else if (ast.op == "%") { return execMod(ctx, ast); } else if (ast.op == "PLUSPLUSRIGHT") { return execPlusPlusRight(ctx, ast); } else if (ast.op == "PLUSPLUSLEFT") { return execPlusPlusLeft(ctx, ast); } else if (ast.op == "MINUSMINUSRIGHT") { return execMinusMinusRight(ctx, ast); } else if (ast.op == "MINUSMINUSLEFT") { return execMinusMinusLeft(ctx, ast); } else if (ast.op == "/") { return execDiv(ctx, ast); } else if (ast.op == "\\") { return execIDiv(ctx, ast); } else if (ast.op == "**") { return execExp(ctx, ast); } else if (ast.op == "&") { return execBAnd(ctx, ast); } else if (ast.op == "&&") { return execAnd(ctx, ast); } else if (ast.op == "||") { return execOr(ctx, ast); } else if (ast.op == "<<") { return execShl(ctx, ast); } else if (ast.op == ">>") { return execShr(ctx, ast); } else if (ast.op == "<") { return execLt(ctx, ast); } else if (ast.op == ">") { return execGt(ctx, ast); } else if (ast.op == "<=") { return execLte(ctx, ast); } else if (ast.op == ">=") { return execGte(ctx, ast); } else if (ast.op == "==") { return execEq(ctx, ast); } else if (ast.op == "!=") { return execNeq(ctx, ast); } else if (ast.op == "?") { return execTerCon(ctx, ast); } else { error(ctx, ast, "Invalid operation: " + ast.op); } } else if (ast.type == "DECLARE") { if (ast.declareType == "COMPONENT") { return execDeclareComponent(ctx, ast); } else if ((ast.declareType == "SIGNALIN")|| (ast.declareType == "SIGNALOUT")|| (ast.declareType == "SIGNAL")) { return execDeclareSignal(ctx, ast); } else if (ast.declareType == "VARIABLE") { return execDeclareVariable(ctx, ast); } else { error(ctx, ast, "Invalid declaration: " + ast.declareType); } } else if (ast.type == "FUNCTIONCALL") { return execFunctionCall(ctx, ast); } else if (ast.type == "BLOCK") { return execBlock(ctx, ast); } else if (ast.type == "COMPUTE") { return ; } else if (ast.type == "FOR") { return execFor(ctx, ast); } else if (ast.type == "WHILE") { return execWhile(ctx, ast); } else if (ast.type == "IF") { return execIf(ctx, ast); } else if (ast.type == "RETURN") { return execReturn(ctx, ast); } else if (ast.type == "TEMPLATEDEF") { return execTemplateDef(ctx, ast); } else if (ast.type == "FUNCTIONDEF") { return execFunctionDef(ctx, ast); } else if (ast.type == "INCLUDE") { return execInclude(ctx, ast); } else if (ast.type == "ARRAY") { return execArray(ctx, ast); } else { error(ctx, ast, "Invalid AST node type: " + ast.type); } } 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, errFile: ctx.fileName, ast: ast, message: errStr }; } function iterateSelectors(ctx, sizes, baseName, fn) { if (sizes.length == 0) { return fn(baseName); } const res = []; for (let i=0; i=0; i--) { if (ctx.scopes[i][name]) return select(ctx.scopes[i][name].value, sels); } return null; function reduce(v, _sels, idxName) { let sels = _sels || []; let sizes = v.sizes || []; let accSizes = [1]; for (let i=sizes.length-1; i>0; i--) { accSizes = [accSizes[0]*sizes[i], ...accSizes]; } const res = Object.assign({}, v); res.sizes = sizes.slice(sels.length); for (let i=0; i=0; i--) { if (ctx.scopes[i][name]) return select(ctx.scopes[i][name].value, sels, ctx.scopes[i][name].type); } return [null, [], ""]; } function setScopeRef(ctx, name, sels, value) { let l = getScopeLevel(ctx, name); if (l==-1) l= ctx.scopes.length-1; if (sels.length == 0) { ctx.scopes[l][name].value = value; } else { setScopeArray(ctx.scopes[l][name].value, sels); } function setScopeArray(a, sels) { if (sels.length == 1) { a[sels[0]] = value; } else { setScopeArray(a[sels[0]], sels.slice(1)); } } } function getScopeLevel(ctx, name) { for (let i=ctx.scopes.length-1; i>=0; i--) { if (ctx.scopes[i][name]) return i; } return -1; } function execBlock(ctx, ast) { for (let i=0; i= 0 )&&(ast.op != "=")) return error(ctx, ast, `Cannot assign to a var with ${ast.op}. use = op`); const res = exec(ctx, ast.values[1]); if (ctx.error) return; setScopeRef(ctx, v.name, sels, res); return v; } function execLt(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; if (a.type != "NUMBER") return { type: "NUMBER" }; const b = exec(ctx, ast.values[1]); if (ctx.error) return; if (b.type != "NUMBER") return { type: "NUMBER" }; if (!a.value || !b.value) return { type: "NUMBER" }; return { type: "NUMBER", value: a.value.lt(b.value) ? bigInt(1) : bigInt(0) }; } function execGt(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; if (a.type != "NUMBER") return { type: "NUMBER" }; const b = exec(ctx, ast.values[1]); if (ctx.error) return; if (b.type != "NUMBER") return { type: "NUMBER" }; if (!a.value || !b.value) return { type: "NUMBER" }; return { type: "NUMBER", value: a.value.gt(b.value) ? bigInt(1) : bigInt(0) }; } function execLte(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; if (a.type != "NUMBER") return { type: "NUMBER" }; const b = exec(ctx, ast.values[1]); if (ctx.error) return; if (b.type != "NUMBER") return { type: "NUMBER" }; if (!a.value || !b.value) return { type: "NUMBER" }; return { type: "NUMBER", value: a.value.lesserOrEquals(b.value) ? bigInt(1) : bigInt(0) }; } function execGte(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; if (a.type != "NUMBER") return { type: "NUMBER" }; const b = exec(ctx, ast.values[1]); if (ctx.error) return; if (b.type != "NUMBER") return { type: "NUMBER" }; if (!a.value || !b.value) return { type: "NUMBER" }; return { type: "NUMBER", value: a.value.greaterOrEquals(b.value) ? bigInt(1) : bigInt(0) }; } function execEq(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; if (a.type != "NUMBER") return { type: "NUMBER" }; const b = exec(ctx, ast.values[1]); if (ctx.error) return; if (b.type != "NUMBER") return { type: "NUMBER" }; if (!a.value || !b.value) return { type: "NUMBER" }; return { type: "NUMBER", value: a.value.eq(b.value) ? bigInt(1) : bigInt(0) }; } function execNeq(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; if (a.type != "NUMBER") return { type: "NUMBER" }; const b = exec(ctx, ast.values[1]); if (ctx.error) return; if (b.type != "NUMBER") return { type: "NUMBER" }; if (!a.value || !b.value) return { type: "NUMBER" }; return { type: "NUMBER", value: a.value.eq(b.value) ? bigInt(0) : bigInt(1) }; } function execBAnd(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; if (a.type != "NUMBER") return { type: "NUMBER" }; const b = exec(ctx, ast.values[1]); if (ctx.error) return; if (b.type != "NUMBER") return { type: "NUMBER" }; if (!a.value || !b.value) return { type: "NUMBER" }; return { type: "NUMBER", value: a.value.and(b.value).and(__MASK__) }; } function execAnd(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; if (a.type != "NUMBER") return { type: "NUMBER" }; const b = exec(ctx, ast.values[1]); if (ctx.error) return; if (b.type != "NUMBER") return { type: "NUMBER" }; if (!a.value || !b.value) return { type: "NUMBER" }; return { type: "NUMBER", value: (a.value.neq(0) && b.value.neq(0)) ? bigInt(1) : bigInt(0) }; } function execOr(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; if (a.type != "NUMBER") return { type: "NUMBER" }; const b = exec(ctx, ast.values[1]); if (ctx.error) return; if (b.type != "NUMBER") return { type: "NUMBER" }; if (!a.value || !b.value) return { type: "NUMBER" }; return { type: "NUMBER", value: (a.value.neq(0) || b.value.neq(0)) ? bigInt(1) : bigInt(0) }; } function execShl(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; if (a.type != "NUMBER") return { type: "NUMBER" }; const b = exec(ctx, ast.values[1]); if (ctx.error) return; if (b.type != "NUMBER") return { type: "NUMBER" }; if (!a.value || !b.value) return { type: "NUMBER" }; const v = b.value.greater(256) ? 256 : b.value.value; return { type: "NUMBER", value: a.value.shiftLeft(v).and(__MASK__) }; } function execShr(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; if (a.type != "NUMBER") return { type: "NUMBER" }; const b = exec(ctx, ast.values[1]); if (ctx.error) return; if (b.type != "NUMBER") return { type: "NUMBER" }; if (!a.value || !b.value) return { type: "NUMBER" }; const v = b.value.greater(256) ? 256 : b.value.value; return { type: "NUMBER", value: a.value.shiftRight(v).and(__MASK__) }; } function execMod(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; if (a.type != "NUMBER") return { type: "NUMBER" }; const b = exec(ctx, ast.values[1]); if (ctx.error) return; if (b.type != "NUMBER") return { type: "NUMBER" }; if (!a.value || !b.value) return { type: "NUMBER" }; return { type: "NUMBER", value: a.value.mod(b.value) }; } function execExp(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; if (a.type != "NUMBER") return { type: "NUMBER" }; const b = exec(ctx, ast.values[1]); if (ctx.error) return; if (b.type != "NUMBER") return { type: "NUMBER" }; if (!a.value || !b.value) return { type: "NUMBER" }; return { type: "NUMBER", value: a.value.modPow(b.value, __P__) }; } function execDiv(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; if (a.type != "NUMBER") return { type: "NUMBER" }; const b = exec(ctx, ast.values[1]); if (ctx.error) return; if (b.type != "NUMBER") return { type: "NUMBER" }; if (!a.value || !b.value) return { type: "NUMBER" }; if (b.value.isZero()) return error(ctx, ast, "Division by zero"); return { type: "NUMBER", value: a.value.times(b.value.modInv(__P__)).mod(__P__) }; } function execIDiv(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; if (a.type != "NUMBER") return { type: "NUMBER" }; const b = exec(ctx, ast.values[1]); if (ctx.error) return; if (b.type != "NUMBER") return { type: "NUMBER" }; if (!a.value || !b.value) return { type: "NUMBER" }; if (b.value.isZero()) return error(ctx, ast, "Division by zero"); return { type: "NUMBER", value: a.value.divide(b.value) }; } function execAdd(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; const b = exec(ctx, ast.values[1]); if (ctx.error) return; const res = lc.add(a,b); if (res.type == "ERROR") return error(ctx, ast, res.errStr); return res; } function execSub(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; const b = exec(ctx, ast.values[1]); if (ctx.error) return; const res = lc.sub(a,b); if (res.type == "ERROR") return error(ctx, ast, res.errStr); return res; } function execUMinus(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; const res = lc.negate(a); if (res.type == "ERROR") return error(ctx, ast, res.errStr); return res; } function execMul(ctx, ast) { const a = exec(ctx, ast.values[0]); if (ctx.error) return; const b = exec(ctx, ast.values[1]); if (ctx.error) return; const res = lc.mul(a,b); if (res.type == "ERROR") return error(ctx, ast, res.errStr); return res; } function execVarAddAssignement(ctx, ast) { const res = execAdd(ctx,{ values: [ast.values[0], ast.values[1]] } ); if (ctx.error) return; return execVarAssignement(ctx, { op:"=", values: [ast.values[0], res] }); } function execVarMulAssignement(ctx, ast) { const res = execMul(ctx,{ values: [ast.values[0], ast.values[1]] } ); if (ctx.error) return; return execVarAssignement(ctx, { op:"=", values: [ast.values[0], res] }); } function execPlusPlusRight(ctx, ast) { const resBefore = exec(ctx, ast.values[0]); if (ctx.error) return; const resAfter = execAdd(ctx,{ values: [ast.values[0], {type: "NUMBER", value: bigInt(1)}] } ); if (ctx.error) return; execVarAssignement(ctx, { op:"=", values: [ast.values[0], resAfter] }); return resBefore; } function execPlusPlusLeft(ctx, ast) { if (ctx.error) return; const resAfter = execAdd(ctx,{ values: [ast.values[0], {type: "NUMBER", value: bigInt(1)}] } ); if (ctx.error) return; execVarAssignement(ctx, { op:"=", values: [ast.values[0], resAfter] }); return resAfter; } function execMinusMinusRight(ctx, ast) { const resBefore = exec(ctx, ast.values[0]); if (ctx.error) return; const resAfter = execSub(ctx,{ values: [ast.values[0], {type: "NUMBER", value: bigInt(1)}] } ); if (ctx.error) return; execVarAssignement(ctx, { op:"=", values: [ast.values[0], resAfter] }); return resBefore; } function execMinusMinusLeft(ctx, ast) { if (ctx.error) return; const resAfter = execSub(ctx,{ values: [ast.values[0], {type: "NUMBER", value: bigInt(1)}] } ); if (ctx.error) return; execVarAssignement(ctx, { op:"=", values: [ast.values[0], resAfter] }); return resAfter; } function execTerCon(ctx, ast) { const cond = exec(ctx, ast.values[0]); if (ctx.error) return; if (!cond.value) return { type: "NUMBER" }; if (cond.value.neq(0)) { return exec(ctx, ast.values[1]); } else { return exec(ctx, ast.values[2]); } } function execSignalAssign(ctx, ast) { let vDest; if (ast.values[0].type == "DECLARE") { vDest = exec(ctx, ast.values[0]); if (ctx.error) return; } else { vDest = ast.values[0]; } let dst; if (vDest.type == "VARIABLE") { dst = getScope(ctx, vDest.name, vDest.selectors); if (ctx.error) return; } else if (vDest.type == "PIN") { dst = execPin(ctx, vDest); if (ctx.error) return; } else { error(ctx, ast, "Bad assignement"); } 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.sIdx]; if (!sDest) return error(ctx, ast, "Invalid signal: "+dst.sIdx); 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"); let src = exec(ctx, ast.values[1]); if (ctx.error) return; /* let vSrc; if (ast.values[1].type == "DECLARE") { vSrc = exec(ctx, ast.values[1]); if (ctx.error) return; } else { vSrc = ast.values[1]; } if (vSrc.type == "VARIABLE") { src = getScope(ctx, vSrc.name, vSrc.selectors); if (!src) error(ctx, ast, "Variable not defined: " + vSrc.name); if (ctx.error) return; } else if (vSrc.type == "PIN") { src = execPin(ctx, vSrc); } */ let assignValue = true; if (src.type == "SIGNAL") { 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.e = src.sIdx; while (sDest.e >= 0) sDest=ctx.signals[sDest.e]; assignValue = false; } } if (assignValue) { // const resLC = exec(ctx, vSrc); if (ctx.error) return; // const v = lc.evaluate(ctx, resLC); const v = lc.evaluate(ctx, src); if (v.value) { sDest.v = v.value; } } return vDest; } function execConstrain(ctx, ast) { ast.fileName = ctx.fileName; ast.filePath = ctx.filePath; const a = exec(ctx, ast.values[0]); if (ctx.error) return; const b = exec(ctx, ast.values[1]); if (ctx.error) return; const res = lc.sub(a,b); if (res.type == "ERROR") return error(ctx, ast, res.errStr); if (!lc.isZero(res)) { ctx.constraints.push(lc.toQEQ(res)); } if ((ctx.constraints.length % 10000 == 0)&&(ctx.constraints.length>0)) console.log("Constraints: " + ctx.constraints.length); return res; } function execSignalAssignConstrain(ctx, ast) { const v = execSignalAssign(ctx,ast); if (ctx.error) return; execConstrain(ctx, ast); if (ctx.error) return; return v; } function execInclude(ctx, ast) { const incFileName = path.resolve(ctx.filePath, ast.file); const incFilePath = path.dirname(incFileName); ctx.includedFiles = ctx.includedFiles || []; if (ctx.includedFiles[incFileName]) return; 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); const oldFilePath = ctx.filePath; const oldFileName = ctx.fileName; ctx.filePath = incFilePath; ctx.fileName = incFileName; exec(ctx, incAst); ast.block = incAst; ctx.filePath = oldFilePath; ctx.fileName = oldFileName; } function execArray(ctx, ast) { const res = []; for (let i=0; i