From 207bd1d2f2f1e7e604b07863c25292781a57114e Mon Sep 17 00:00:00 2001 From: Kyryl R Date: Thu, 21 Nov 2024 14:37:26 +0200 Subject: [PATCH 01/12] Synced with hardhat-zkit --- test/circom-template-inputs-visitor.test.ts | 16 +++--- test/mocks/CircomFilesVisitor.ts | 11 ++-- test/mocks/CircomTemplateInputsVisitor.ts | 60 ++++++++++++++------- test/mocks/types.ts | 6 +-- 4 files changed, 56 insertions(+), 37 deletions(-) diff --git a/test/circom-template-inputs-visitor.test.ts b/test/circom-template-inputs-visitor.test.ts index fe6ea86..006f7b7 100644 --- a/test/circom-template-inputs-visitor.test.ts +++ b/test/circom-template-inputs-visitor.test.ts @@ -44,25 +44,25 @@ describe("Circom Template Inputs Visitor", () => { expect(visitor.templateInputs.encapsulatedContent.type).to.equal("input"); expect(visitor.templateInputs.encapsulatedContent.dimension).to.deep.equal([ - 131072n, + 131072, ]); expect(visitor.templateInputs.dg1.type).to.equal("input"); - expect(visitor.templateInputs.dg1.dimension).to.deep.equal([1024n]); + expect(visitor.templateInputs.dg1.dimension).to.deep.equal([1024]); expect(visitor.templateInputs.dg15.type).to.equal("input"); - expect(visitor.templateInputs.dg15.dimension).to.deep.equal([32768n]); + expect(visitor.templateInputs.dg15.dimension).to.deep.equal([32768]); expect(visitor.templateInputs.signedAttributes.type).to.equal("input"); expect(visitor.templateInputs.signedAttributes.dimension).to.deep.equal([ - 1024n, + 1024, ]); expect(visitor.templateInputs.signature.type).to.equal("input"); - expect(visitor.templateInputs.signature.dimension).to.deep.equal([64n]); + expect(visitor.templateInputs.signature.dimension).to.deep.equal([64]); expect(visitor.templateInputs.pubkey.type).to.equal("input"); - expect(visitor.templateInputs.pubkey.dimension).to.deep.equal([64n]); + expect(visitor.templateInputs.pubkey.dimension).to.deep.equal([64]); expect(visitor.templateInputs.slaveMerkleRoot.type).to.equal("input"); expect(visitor.templateInputs.slaveMerkleRoot.dimension).to.deep.equal([]); @@ -72,7 +72,7 @@ describe("Circom Template Inputs Visitor", () => { ); expect( visitor.templateInputs.slaveMerkleInclusionBranches.dimension, - ).to.deep.equal([80n]); + ).to.deep.equal([80]); expect(visitor.templateInputs.skIdentity.type).to.equal("input"); expect(visitor.templateInputs.skIdentity.dimension).to.deep.equal([]); @@ -112,7 +112,7 @@ describe("Circom Template Inputs Visitor", () => { expect(visitor.templateInputs.in1.dimension).to.deep.equal([]); expect(visitor.templateInputs.in2.type).to.equal("input"); - expect(visitor.templateInputs.in2.dimension).to.deep.equal([3n, 2n]); + expect(visitor.templateInputs.in2.dimension).to.deep.equal([3, 2]); expect(visitor.templateInputs.out.type).to.equal("output"); expect(visitor.templateInputs.out.dimension).to.deep.equal([]); diff --git a/test/mocks/CircomFilesVisitor.ts b/test/mocks/CircomFilesVisitor.ts index 6d26435..6639a20 100644 --- a/test/mocks/CircomFilesVisitor.ts +++ b/test/mocks/CircomFilesVisitor.ts @@ -55,7 +55,7 @@ export class CircomFilesVisitor extends CircomVisitor { visitTemplateDefinition = (ctx: TemplateDefinitionContext) => { if (ctx.ID().getText() in this.fileData.templates) { this.errors.push({ - type: ErrorType.TemplateAlreadyUsed, + type: ErrorType.TemplateAlreadyVisited, context: ctx, fileIdentifier: this.fileIdentifier, templateIdentifier: ctx.ID().getText(), @@ -66,7 +66,7 @@ export class CircomFilesVisitor extends CircomVisitor { } this.fileData.templates[ctx.ID().getText()] = { - parameters: parseSimpleIdentifierList(ctx._argNames), + parameters: ctx._argNames ? parseSimpleIdentifierList(ctx._argNames) : [], isCustom: !!ctx.CUSTOM(), parallel: !!ctx.PARALLEL(), context: ctx, @@ -82,8 +82,8 @@ export class CircomFilesVisitor extends CircomVisitor { visitComponentMainDeclaration = (ctx: ComponentMainDeclarationContext) => { this.fileData.mainComponentInfo.templateName = ctx.ID().getText(); - this.visit(ctx.publicInputsDefinition()); - this.visit(ctx._argValues); + if (ctx.publicInputsDefinition()) this.visit(ctx.publicInputsDefinition()); + if (ctx._argValues) this.visit(ctx._argValues); }; visitPublicInputsDefinition = (ctx: PublicInputsDefinitionContext) => { @@ -106,8 +106,7 @@ export class CircomFilesVisitor extends CircomVisitor { context: ctx.expression(i), fileIdentifier: this.fileIdentifier, linkedParserErrors: errors, - message: `Failed to parse array parameter with index ${i}.\r - \rParameter: ${ctx.expression(i).getText()} (${ctx.expression(i).start.line}:${ctx.expression(i).start.column})`, + message: `Failed to parse array parameter with index ${i}. Parameter: ${ctx.expression(i).getText()} (${ctx.expression(i).start.line}:${ctx.expression(i).start.column})`, }); continue; diff --git a/test/mocks/CircomTemplateInputsVisitor.ts b/test/mocks/CircomTemplateInputsVisitor.ts index 1ca37f0..d164cdb 100644 --- a/test/mocks/CircomTemplateInputsVisitor.ts +++ b/test/mocks/CircomTemplateInputsVisitor.ts @@ -23,6 +23,7 @@ import { VariableContext, VarIdentifierContext, ParserRuleContext, + BusDeclarationContext, } from "../../src"; import { @@ -92,7 +93,7 @@ export class CircomTemplateInputsVisitor extends CircomVisitor { visitSignalIdentifier = (ctx: SignalIdentifierContext) => { const base = ctx.identifier(); const baseName = base.ID(); - const resolvedDimensions: bigint[] = []; + const resolvedDimensions: number[] = []; for (const dimension of base.arrayDimension_list()) { const [dimensionValue, linkedErrors] = new ExpressionHelper( @@ -126,7 +127,7 @@ export class CircomTemplateInputsVisitor extends CircomVisitor { return; } - resolvedDimensions.push(dimensionValue); + resolvedDimensions.push(Number(dimensionValue)); } if ( @@ -147,6 +148,10 @@ export class CircomTemplateInputsVisitor extends CircomVisitor { ); } + if (ctx.parentCtx!.parentCtx instanceof BusDeclarationContext) { + throw new Error("Buses are not supported"); + } + const signalDeclarationContext = ctx.parentCtx! .parentCtx as SignalDeclarationContext; @@ -172,7 +177,7 @@ export class CircomTemplateInputsVisitor extends CircomVisitor { }; visitIfWithFollowUpIf = (ctx: IfWithFollowUpIfContext) => { - let [condition, linkedErrors] = new ExpressionHelper(this.fileIdentifier) + const [condition, linkedErrors] = new ExpressionHelper(this.fileIdentifier) .setExpressionContext(ctx._cond) .setVariableContext(this._vars) .parseExpression(); @@ -187,7 +192,7 @@ export class CircomTemplateInputsVisitor extends CircomVisitor { }; visitIfRegular = (ctx: IfRegularContext) => { - let [condition, linkedErrors] = new ExpressionHelper(this.fileIdentifier) + const [condition, linkedErrors] = new ExpressionHelper(this.fileIdentifier) .setExpressionContext(ctx._cond) .setVariableContext(this._vars) .parseExpression(); @@ -204,7 +209,7 @@ export class CircomTemplateInputsVisitor extends CircomVisitor { visitIfRegularElseWithFollowUpIf = ( ctx: IfRegularElseWithFollowUpIfContext, ) => { - let [condition, linkedErrors] = new ExpressionHelper(this.fileIdentifier) + const [condition, linkedErrors] = new ExpressionHelper(this.fileIdentifier) .setExpressionContext(ctx._cond) .setVariableContext(this._vars) .parseExpression(); @@ -221,7 +226,7 @@ export class CircomTemplateInputsVisitor extends CircomVisitor { }; visitIfRegularElseRegular = (ctx: IfRegularElseRegularContext) => { - let [condition, linkedErrors] = new ExpressionHelper(this.fileIdentifier) + const [condition, linkedErrors] = new ExpressionHelper(this.fileIdentifier) .setExpressionContext(ctx._cond) .setVariableContext(this._vars) .parseExpression(); @@ -450,19 +455,24 @@ export class CircomTemplateInputsVisitor extends CircomVisitor { switch (ctx.ASSIGNMENT_WITH_OP().getText()) { case "+=": - this._vars[assigneeName] = this._vars[assigneeName] + value; + this._vars[assigneeName] = + BigInt(this._vars[assigneeName] as any) + value; break; case "-=": - this._vars[assigneeName] = this._vars[assigneeName] - value; + this._vars[assigneeName] = + BigInt(this._vars[assigneeName] as any) - value; break; case "*=": - this._vars[assigneeName] = this._vars[assigneeName] * value; + this._vars[assigneeName] = + BigInt(this._vars[assigneeName] as any) * value; break; case "**=": - this._vars[assigneeName] = this._vars[assigneeName] ** value; + this._vars[assigneeName] = + BigInt(this._vars[assigneeName] as any) ** value; break; case "/=": - this._vars[assigneeName] = this._vars[assigneeName] / value; + this._vars[assigneeName] = + BigInt(this._vars[assigneeName] as any) / value; break; case "\\\\=": this.errors.push({ @@ -473,26 +483,32 @@ export class CircomTemplateInputsVisitor extends CircomVisitor { }); break; case "%=": - this._vars[assigneeName] = this._vars[assigneeName] % value; + this._vars[assigneeName] = + BigInt(this._vars[assigneeName] as any) % value; break; case "<<=": - this._vars[assigneeName] = this._vars[assigneeName] << value; + this._vars[assigneeName] = + BigInt(this._vars[assigneeName] as any) << value; break; case ">>=": - this._vars[assigneeName] = this._vars[assigneeName] >> value; + this._vars[assigneeName] = + BigInt(this._vars[assigneeName] as any) >> value; break; case "&=": - this._vars[assigneeName] = this._vars[assigneeName] & value; + this._vars[assigneeName] = + BigInt(this._vars[assigneeName] as any) & value; break; case "^=": - this._vars[assigneeName] = this._vars[assigneeName] ^ value; + this._vars[assigneeName] = + BigInt(this._vars[assigneeName] as any) ^ value; break; case "|=": - this._vars[assigneeName] = this._vars[assigneeName] | value; + this._vars[assigneeName] = + BigInt(this._vars[assigneeName] as any) | value; break; default: this.errors.push({ - type: ErrorType.ReachedUnkownOperation, + type: ErrorType.ReachedUnknownOperation, context: ctx, fileIdentifier: this.fileIdentifier, message: `Invalid operation type ${ctx.ASSIGNMENT_WITH_OP().getText()} (${ctx.start.line}:${ctx.start.column})`, @@ -542,14 +558,18 @@ export class CircomTemplateInputsVisitor extends CircomVisitor { switch (ctx.SELF_OP().getText()) { case "++": + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore this._vars[assigneeName]++; break; case "--": + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore this._vars[assigneeName]--; break; default: this.errors.push({ - type: ErrorType.ReachedUnkownOperation, + type: ErrorType.ReachedUnknownOperation, context: ctx, fileIdentifier: this.fileIdentifier, message: `Invalid operation type ${ctx.SELF_OP().getText()} (${ctx.start.line}:${ctx.start.column})`, @@ -600,7 +620,7 @@ export class CircomTemplateInputsVisitor extends CircomVisitor { const expressionHelper = new ExpressionHelper(this.fileIdentifier); let result: IdentifierObject[] | null = []; - let resolvedDimensions: bigint[] = []; + const resolvedDimensions: bigint[] = []; for (let i = 0; i < ctx.arrayDimension_list().length; i++) { const [dimension, linkedErrors] = expressionHelper diff --git a/test/mocks/types.ts b/test/mocks/types.ts index efe1ac0..74fac14 100644 --- a/test/mocks/types.ts +++ b/test/mocks/types.ts @@ -7,7 +7,7 @@ import { export enum ErrorType { SignalDimensionResolution, - TemplateAlreadyUsed, + TemplateAlreadyVisited, InvalidPragmaVersion, FailedToResolveMainComponentParameter, InternalExpressionHelperError, @@ -23,13 +23,13 @@ export enum ErrorType { ComplexAccessNotSupported, AssigneeNotDeclared, QUOOperationNotSupported, - ReachedUnkownOperation, + ReachedUnknownOperation, InvalidIncDecOperation, } export type InputData = { type: string; - dimension: bigint[]; + dimension: number[]; }; export type IdentifierObject = { From f4fb24c781d8799f952c0077c72bfa8569d65d99 Mon Sep 17 00:00:00 2001 From: Kyryl R Date: Thu, 21 Nov 2024 16:11:34 +0200 Subject: [PATCH 02/12] Added some resolvers --- src/types/common.ts | 2 + src/utils/common.ts | 68 +++++++++++++++++++++ test/circom-template-inputs-visitor.test.ts | 24 ++++++-- test/data/ComplexMainComponent.circom | 10 +++ test/file-visitor.test.ts | 13 ++++ test/utils.ts | 10 +++ 6 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 test/data/ComplexMainComponent.circom create mode 100644 test/utils.ts diff --git a/src/types/common.ts b/src/types/common.ts index b8cdd85..fedfd6d 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -1,3 +1,5 @@ export type CircomValueType = bigint | CircomValueType[]; export type VariableContext = Record; + +export type VariableContextWithNull = Record; diff --git a/src/utils/common.ts b/src/utils/common.ts index 5df7df6..ed2ba51 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,4 +1,5 @@ import { SimpleIdentifierListContext } from "../generated"; +import { VariableContext, VariableContextWithNull } from "../types"; export function parseSimpleIdentifierList( ctx: SimpleIdentifierListContext, @@ -15,3 +16,70 @@ export function parseSimpleIdentifierList( return result; } + +export function bindVariableContext( + variableName: string, + dimensions: number[], + values: any[], +): VariableContextWithNull { + const context: VariableContextWithNull = {}; + + const resolved = resolveDimensions(variableName, dimensions); + for (const variable of resolved) { + try { + context[variable] = parseVariable( + values, + variable.replace(variableName, ""), + ); + } catch { + context[variable] = null; + } + } + + return context; +} + +// reference MUST be similar to [0][1] +function parseVariable(value: any, reference: string): bigint { + const parts = reference + .split("[") + .map((part) => part.replace("]", "")) + .filter((part) => part !== "") + .map((part) => parseInt(part)); + + return getReferenceValueInternal(value, parts); +} + +function getReferenceValueInternal(value: any, reference: number[]): bigint { + if (reference.length === 0) { + return value; + } + + return getReferenceValueInternal(value[reference[0]], reference.slice(1)); +} + +export function resolveDimensions( + variableName: string, + dimensions: number[], +): string[] { + return internalResolveDimensions([variableName], dimensions, 0); +} + +function internalResolveDimensions( + variables: string[], + dimensions: number[], + layer: number, +): string[] { + if (layer >= dimensions.length) { + return variables; + } + + const result: string[] = []; + for (let i = 0; i < dimensions[layer]; i++) { + for (const variable of variables) { + result.push(`${variable}[${i}]`); + } + } + + return internalResolveDimensions(result, dimensions, layer + 1); +} diff --git a/test/circom-template-inputs-visitor.test.ts b/test/circom-template-inputs-visitor.test.ts index 006f7b7..ff2a76f 100644 --- a/test/circom-template-inputs-visitor.test.ts +++ b/test/circom-template-inputs-visitor.test.ts @@ -1,19 +1,19 @@ import { expect } from "chai"; import { getCircomParser, VariableContext } from "../src"; -import { Templates } from "./mocks/types"; +import { CircomFileData, Templates } from "./mocks/types"; import { CircomFilesVisitor } from "./mocks/CircomFilesVisitor"; import { CircomTemplateInputsVisitor } from "./mocks/CircomTemplateInputsVisitor"; describe("Circom Template Inputs Visitor", () => { - function getData(fileName: string): Templates { + function getData(fileName: string): CircomFileData { const visitor = new CircomFilesVisitor(fileName); const parser = getCircomParser(`test/data/${fileName}`); visitor.visit(parser.circuit()); - return visitor.fileData.templates; + return visitor.fileData; } it("should analyse the curve.circom circuit", () => { @@ -34,7 +34,7 @@ describe("Circom Template Inputs Visitor", () => { const visitor = new CircomTemplateInputsVisitor( "curve.circom", - data["RegisterIdentityBuilder"].context, + data.templates["RegisterIdentityBuilder"].context, mainComponentData, ); @@ -100,7 +100,7 @@ describe("Circom Template Inputs Visitor", () => { const visitor = new CircomTemplateInputsVisitor( "MainComponent.circom", - data["C"].context, + data.templates["C"].context, mainComponentData, ); @@ -117,4 +117,18 @@ describe("Circom Template Inputs Visitor", () => { expect(visitor.templateInputs.out.type).to.equal("output"); expect(visitor.templateInputs.out.dimension).to.deep.equal([]); }); + + it.only("should analyse the ComplexMainComponent.circom circuit", () => { + const data = getData("ComplexMainComponent.circom"); + + // const visitor = new CircomTemplateInputsVisitor( + // "MainComponent.circom", + // data., + // {}, + // ); + // + // visitor.startParse(); + // + // console.log(visitor.errors); + }); }); diff --git a/test/data/ComplexMainComponent.circom b/test/data/ComplexMainComponent.circom new file mode 100644 index 0000000..cd1b89d --- /dev/null +++ b/test/data/ComplexMainComponent.circom @@ -0,0 +1,10 @@ +pragma circom 2.1.6; + +template C(p1, p2){ + signal input in1; + signal input in2[3][p1[1][0][0][0][0][0][0]]; + + signal output out <== in1 * in2[0][0] * p2[0][1][0]; +} + +component main {public [in1]} = C([[[[[[[10]]]]]], [[[[[[20]]]]]]], [[[30], [40]]]); diff --git a/test/file-visitor.test.ts b/test/file-visitor.test.ts index 19c8f14..0edbcea 100644 --- a/test/file-visitor.test.ts +++ b/test/file-visitor.test.ts @@ -173,4 +173,17 @@ describe("Circom File Visitor", () => { expect(templateA.parallel).to.equal(true); expect(templateA.parameters).to.deep.equal(["a1"]); }); + + it("should analyse the ComplexMainComponent.circom circuit", () => { + const [errors, fileData] = getData("ComplexMainComponent.circom"); + + expect(errors.length).to.equal(0); + expect(fileData.mainComponentInfo.parameters[0]).to.deep.equal([ + [[[[[[10n]]]]]], + [[[[[[20n]]]]]], + ]); + expect(fileData.mainComponentInfo.parameters[1]).to.deep.equal([ + [[30n], [40n]], + ]); + }); }); diff --git a/test/utils.ts b/test/utils.ts new file mode 100644 index 0000000..f02fb5b --- /dev/null +++ b/test/utils.ts @@ -0,0 +1,10 @@ +import { bindVariableContext, resolveDimensions } from "../src"; + +describe("utils", () => { + describe("resolve dimensions", () => { + it.only("should resolve the case: [1]", () => { + console.log(bindVariableContext("a", [1, 2], [[3, 5]])); + console.log(bindVariableContext("a", [1, 2], [])); + }); + }); +}); From f23f0543e6dfc98cf1a3aff37cbce8395b3ebcd3 Mon Sep 17 00:00:00 2001 From: Kyryl R Date: Thu, 21 Nov 2024 17:47:40 +0200 Subject: [PATCH 03/12] Added resolver for arrays in the component main --- src/utils/ExpressionHelper.ts | 37 +++++++++--- src/utils/common.ts | 38 ++++++++++++- test/circom-template-inputs-visitor.test.ts | 62 +++++++++++++++++---- test/data/AnotherMainComponent.circom | 10 ++++ test/data/ComplexMainComponent.circom | 2 +- test/mocks/CircomTemplateInputsVisitor.ts | 27 --------- test/utils.ts | 49 ++++++++++++++-- 7 files changed, 171 insertions(+), 54 deletions(-) create mode 100644 test/data/AnotherMainComponent.circom diff --git a/src/utils/ExpressionHelper.ts b/src/utils/ExpressionHelper.ts index a959dcd..cf6f68e 100644 --- a/src/utils/ExpressionHelper.ts +++ b/src/utils/ExpressionHelper.ts @@ -121,10 +121,10 @@ class ExpressionVisitor extends ExtendedCircomVisitor { ctx: PIdentifierStatementContext, ): CircomValueType | null => { if (ctx.identifierStatement().idetifierAccess_list().length == 0) { - const variableName = + const variableValue = this.variableContext[ctx.identifierStatement().ID().getText()]; - if (variableName === undefined) { + if (variableValue === undefined) { this.addError( `Variable ${ctx.identifierStatement().ID().getText()} is not defined`, ctx.identifierStatement(), @@ -133,14 +133,35 @@ class ExpressionVisitor extends ExtendedCircomVisitor { return null; } - return variableName; + return variableValue; } - this.addError( - "IdentifierStatement is not supported with access references", - ctx, - ); - return null; + const reference = ctx + .identifierStatement() + .idetifierAccess_list() + .map((access) => access.getText()) + .join(""); + + if (reference.indexOf(".") !== -1) { + this.addError( + "IdentifierStatement is not supported with access references that are not arrays", + ctx, + ); + return null; + } + + const variableName = ctx.identifierStatement().ID().getText() + reference; + + if (this.variableContext[variableName] === undefined) { + this.addError( + `Variable ${variableName} is not defined`, + ctx.identifierStatement(), + ); + + return null; + } + + return this.variableContext[variableName]; }; visitPUnderscore = (_ctx: PUnderscoreContext): CircomValueType | null => { diff --git a/src/utils/common.ts b/src/utils/common.ts index ed2ba51..ce5bef9 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -17,10 +17,44 @@ export function parseSimpleIdentifierList( return result; } +export function buildVariableContext( + names: string[], + values: any[], +): VariableContext { + if (names.length !== values.length) { + throw new Error("Names and values must have the same length"); + } + + const context: VariableContext = {}; + + for (let i = 0; i < names.length; i++) { + const bindContext = bindVariableContext( + names[i], + getArrayDimensions(values[i]), + values[i], + ); + for (const key in bindContext) { + if (bindContext[key] !== null) { + context[key] = bindContext[key]; + } + } + } + + return context; +} + +export function getArrayDimensions(value: any): number[] { + if (Array.isArray(value)) { + return [value.length, ...getArrayDimensions(value[0])]; + } + + return []; +} + export function bindVariableContext( variableName: string, dimensions: number[], - values: any[], + values: any, ): VariableContextWithNull { const context: VariableContextWithNull = {}; @@ -52,7 +86,7 @@ function parseVariable(value: any, reference: string): bigint { function getReferenceValueInternal(value: any, reference: number[]): bigint { if (reference.length === 0) { - return value; + return BigInt(value); } return getReferenceValueInternal(value[reference[0]], reference.slice(1)); diff --git a/test/circom-template-inputs-visitor.test.ts b/test/circom-template-inputs-visitor.test.ts index ff2a76f..2f61dc4 100644 --- a/test/circom-template-inputs-visitor.test.ts +++ b/test/circom-template-inputs-visitor.test.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; -import { getCircomParser, VariableContext } from "../src"; +import { buildVariableContext, getCircomParser, VariableContext } from "../src"; -import { CircomFileData, Templates } from "./mocks/types"; +import { CircomFileData } from "./mocks/types"; import { CircomFilesVisitor } from "./mocks/CircomFilesVisitor"; import { CircomTemplateInputsVisitor } from "./mocks/CircomTemplateInputsVisitor"; @@ -118,17 +118,55 @@ describe("Circom Template Inputs Visitor", () => { expect(visitor.templateInputs.out.dimension).to.deep.equal([]); }); - it.only("should analyse the ComplexMainComponent.circom circuit", () => { + it("should analyse the ComplexMainComponent.circom circuit", () => { const data = getData("ComplexMainComponent.circom"); - // const visitor = new CircomTemplateInputsVisitor( - // "MainComponent.circom", - // data., - // {}, - // ); - // - // visitor.startParse(); - // - // console.log(visitor.errors); + const visitor = new CircomTemplateInputsVisitor( + "ComplexMainComponent.circom", + data.templates[data.mainComponentInfo.templateName!].context, + buildVariableContext( + data.templates[data.mainComponentInfo.templateName!].parameters, + data.mainComponentInfo.parameters, + ), + ); + + visitor.startParse(); + + expect(visitor.errors.length).to.equal(0); + + expect(visitor.templateInputs.in1.type).to.equal("input"); + expect(visitor.templateInputs.in1.dimension).to.deep.equal([]); + + expect(visitor.templateInputs.in2.type).to.equal("input"); + expect(visitor.templateInputs.in2.dimension).to.deep.equal([3, 600]); + + expect(visitor.templateInputs.out.type).to.equal("output"); + expect(visitor.templateInputs.out.dimension).to.deep.equal([]); + }); + + it("should analyse the AnotherMainComponent.circom circuit", () => { + const data = getData("AnotherMainComponent.circom"); + + const visitor = new CircomTemplateInputsVisitor( + "AnotherMainComponent.circom", + data.templates[data.mainComponentInfo.templateName!].context, + buildVariableContext( + data.templates[data.mainComponentInfo.templateName!].parameters, + data.mainComponentInfo.parameters, + ), + ); + + visitor.startParse(); + + expect(visitor.errors.length).to.equal(0); + + expect(visitor.templateInputs.in1.type).to.equal("input"); + expect(visitor.templateInputs.in1.dimension).to.deep.equal([]); + + expect(visitor.templateInputs.in2.type).to.equal("input"); + expect(visitor.templateInputs.in2.dimension).to.deep.equal([15, 10]); + + expect(visitor.templateInputs.out.type).to.equal("output"); + expect(visitor.templateInputs.out.dimension).to.deep.equal([]); }); }); diff --git a/test/data/AnotherMainComponent.circom b/test/data/AnotherMainComponent.circom new file mode 100644 index 0000000..ce6bd41 --- /dev/null +++ b/test/data/AnotherMainComponent.circom @@ -0,0 +1,10 @@ +pragma circom 2.1.6; + +template SomeCircuit(p1){ + signal input in1; + signal input in2[p1[1] * p1[0]][p1[3] * p1[2]]; + + signal output out <== in1 * in2[0][0]; +} + +component main {public [in1]} = SomeCircuit([5, 3, 2, 5]); diff --git a/test/data/ComplexMainComponent.circom b/test/data/ComplexMainComponent.circom index cd1b89d..bd1641c 100644 --- a/test/data/ComplexMainComponent.circom +++ b/test/data/ComplexMainComponent.circom @@ -2,7 +2,7 @@ pragma circom 2.1.6; template C(p1, p2){ signal input in1; - signal input in2[3][p1[1][0][0][0][0][0][0]]; + signal input in2[3][p1[1][0][0][0][0][0][0] * p2[0][0][0]]; signal output out <== in1 * in2[0][0] * p2[0][1][0]; } diff --git a/test/mocks/CircomTemplateInputsVisitor.ts b/test/mocks/CircomTemplateInputsVisitor.ts index d164cdb..8df0514 100644 --- a/test/mocks/CircomTemplateInputsVisitor.ts +++ b/test/mocks/CircomTemplateInputsVisitor.ts @@ -9,7 +9,6 @@ import { IfRegularElseWithFollowUpIfContext, IfWithFollowUpIfContext, ParserErrorItem, - parseSimpleIdentifierList, PIdentifierStatementContext, PUnderscoreContext, SignalDeclarationContext, @@ -50,8 +49,6 @@ export class CircomTemplateInputsVisitor extends CircomVisitor { this.templateInputs = {}; this._vars = parameterValues; - - this._validateVariableContext(); } startParse = () => { @@ -587,30 +584,6 @@ export class CircomTemplateInputsVisitor extends CircomVisitor { }); }; - private _validateVariableContext() { - const templateParameters = parseSimpleIdentifierList( - this.templateContext.simpleIdentifierList(), - ); - - for (const parameter of templateParameters) { - if ( - this._vars[parameter] === undefined || - this._vars[parameter] === null - ) { - this.errors.push({ - type: ErrorType.MissingTemplateParameterValue, - context: this.templateContext, - fileIdentifier: this.templateContext.ID().getText(), - message: `Missing value for parameter ${parameter} in template ${this.templateContext.ID().getText()}`, - }); - - continue; - } - - this._declaredVariables[parameter] = true; - } - } - private _resolveIdentifier( ctx: IdentifierContext, variableContext: VariableContext = {}, diff --git a/test/utils.ts b/test/utils.ts index f02fb5b..24ffbe0 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,10 +1,51 @@ -import { bindVariableContext, resolveDimensions } from "../src"; +import { expect } from "chai"; + +import { bindVariableContext } from "../src"; describe("utils", () => { describe("resolve dimensions", () => { - it.only("should resolve the case: [1]", () => { - console.log(bindVariableContext("a", [1, 2], [[3, 5]])); - console.log(bindVariableContext("a", [1, 2], [])); + it("should resolve the case: [1, 2], when value: [[3, 5]]", () => { + expect(bindVariableContext("a", [1, 2], [[3, 5]])).to.deep.equal({ + "a[0][0]": 3n, + "a[0][1]": 5n, + }); + }); + + it("should resolve the case: [1, 2], when value: []", () => { + expect(bindVariableContext("a", [1, 2], [])).to.deep.equal({ + "a[0][0]": null, + "a[0][1]": null, + }); + }); + + it("should resolve the case: [], when value: 1", () => { + expect(bindVariableContext("a", [], 1)).to.deep.equal({ + a: 1n, + }); + }); + + it("should resolve the case: [1], when value: 1", () => { + expect(bindVariableContext("a", [1], 1)).to.deep.equal({ + "a[0]": null, + }); + }); + + it("should resolve the case: [2, 2], when value: [[1, 2], [3, 4]]", () => { + expect( + bindVariableContext( + "a", + [2, 2], + [ + [1, 2], + [3, 4], + ], + ), + ).to.deep.equal({ + "a[0][0]": 1n, + "a[0][1]": 2n, + "a[1][0]": 3n, + "a[1][1]": 4n, + }); }); }); }); From 5eebe8c25fcddf118c984667c07a4a44f954e01e Mon Sep 17 00:00:00 2001 From: Kyryl R Date: Thu, 21 Nov 2024 17:56:51 +0200 Subject: [PATCH 04/12] Added file identifier to error messages --- src/ExtendedCircomParser.ts | 18 +++++++++++++----- src/ExtendedCircomVisitor.ts | 2 +- src/errors/ErrorListener.ts | 5 ++--- src/index.ts | 13 +++++++++---- src/types/errors.ts | 2 +- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/ExtendedCircomParser.ts b/src/ExtendedCircomParser.ts index ca47ab0..de0210f 100644 --- a/src/ExtendedCircomParser.ts +++ b/src/ExtendedCircomParser.ts @@ -12,16 +12,24 @@ export class ExtendedCircomParser extends CircomParser { parserErrorListener: ErrorListener; lexerErrorListener: ErrorListener; - constructor(tokens: antlr4.CommonTokenStream, lexer: CircomLexer) { + fileIdentifier: string; + + constructor( + fileIdentifier: string, + tokens: antlr4.CommonTokenStream, + lexer: CircomLexer, + ) { super(tokens); this.lexer = lexer; - this.lexerErrorListener = new ErrorListener(); - this.parserErrorListener = new ErrorListener(); + this.lexerErrorListener = new ErrorListener(fileIdentifier); + this.parserErrorListener = new ErrorListener(fileIdentifier); this.initErrorListeners(); this.buildParseTrees = true; + + this.fileIdentifier = fileIdentifier; } circuit() { @@ -42,11 +50,11 @@ export class ExtendedCircomParser extends CircomParser { } initErrorListeners() { - this.parserErrorListener = new ErrorListener(); + this.parserErrorListener = new ErrorListener(this.fileIdentifier); this.removeErrorListeners(); this.addErrorListener(this.parserErrorListener); - this.lexerErrorListener = new ErrorListener(); + this.lexerErrorListener = new ErrorListener(this.fileIdentifier); this.lexer.removeErrorListeners(); this.lexer.addErrorListener(this.lexerErrorListener); } diff --git a/src/ExtendedCircomVisitor.ts b/src/ExtendedCircomVisitor.ts index 72c65b1..becd10a 100644 --- a/src/ExtendedCircomVisitor.ts +++ b/src/ExtendedCircomVisitor.ts @@ -15,7 +15,7 @@ export class ExtendedCircomVisitor extends CircomVisitor { protected addError(message: string, context: ParserRuleContext) { this.errors.push({ - templateName: this.templateIdentifier, + fileIdentifier: this.templateIdentifier, message, line: context.start.line, column: context.start.column, diff --git a/src/errors/ErrorListener.ts b/src/errors/ErrorListener.ts index de856ee..04b18f0 100644 --- a/src/errors/ErrorListener.ts +++ b/src/errors/ErrorListener.ts @@ -5,13 +5,12 @@ import { ParserErrorItem } from "../types"; class ErrorListener extends AntlrErrorListener { private readonly _errors: ParserErrorItem[]; - constructor() { + constructor(public fileIdentifier: string) { super(); this._errors = []; } - // TODO: improve error handling syntaxError( recognizer: Recognizer, offendingSymbol: TSymbol, @@ -23,8 +22,8 @@ class ErrorListener extends AntlrErrorListener { message, line, column, + fileIdentifier: this.fileIdentifier, context: null as any, - templateName: null, }); } diff --git a/src/index.ts b/src/index.ts index aecc424..c3b6f3f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,14 +6,19 @@ import { CircomLexer } from "./generated"; import { ExtendedCircomParser } from "./ExtendedCircomParser"; export function getCircomParser(source: string): ExtendedCircomParser { - const inputStream = fs.existsSync(source) - ? antlr4.CharStreams.fromPathSync(source, "utf8") - : antlr4.CharStreams.fromString(source); + let inputStream: antlr4.CharStream; + let fileIdentifier = "Built from source"; + if (fs.existsSync(source)) { + inputStream = antlr4.CharStreams.fromPathSync(source, "utf8"); + fileIdentifier = source; + } else { + inputStream = antlr4.CharStreams.fromString(source); + } const lexer = new CircomLexer(inputStream); const tokens = new antlr4.CommonTokenStream(lexer); - return new ExtendedCircomParser(tokens, lexer); + return new ExtendedCircomParser(fileIdentifier, tokens, lexer); } export * from "./types"; diff --git a/src/types/errors.ts b/src/types/errors.ts index d4ad109..c5c7168 100644 --- a/src/types/errors.ts +++ b/src/types/errors.ts @@ -1,7 +1,7 @@ import { ParserRuleContext } from "antlr4"; export type ParserErrorItem = { - templateName: string | null; + fileIdentifier: string; message: string; line: number; column: number; From 7e74ffee7c23d1cbc2657f98d1657747c8e1d1b4 Mon Sep 17 00:00:00 2001 From: Kyryl R Date: Thu, 21 Nov 2024 20:12:52 +0200 Subject: [PATCH 05/12] Tested non array value --- test/circom-template-inputs-visitor.test.ts | 2 +- test/data/AnotherMainComponent.circom | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/circom-template-inputs-visitor.test.ts b/test/circom-template-inputs-visitor.test.ts index 2f61dc4..292683e 100644 --- a/test/circom-template-inputs-visitor.test.ts +++ b/test/circom-template-inputs-visitor.test.ts @@ -164,7 +164,7 @@ describe("Circom Template Inputs Visitor", () => { expect(visitor.templateInputs.in1.dimension).to.deep.equal([]); expect(visitor.templateInputs.in2.type).to.equal("input"); - expect(visitor.templateInputs.in2.dimension).to.deep.equal([15, 10]); + expect(visitor.templateInputs.in2.dimension).to.deep.equal([15, 30]); expect(visitor.templateInputs.out.type).to.equal("output"); expect(visitor.templateInputs.out.dimension).to.deep.equal([]); diff --git a/test/data/AnotherMainComponent.circom b/test/data/AnotherMainComponent.circom index ce6bd41..3601e51 100644 --- a/test/data/AnotherMainComponent.circom +++ b/test/data/AnotherMainComponent.circom @@ -1,10 +1,10 @@ pragma circom 2.1.6; -template SomeCircuit(p1){ +template SomeCircuit(p1, p2){ signal input in1; - signal input in2[p1[1] * p1[0]][p1[3] * p1[2]]; + signal input in2[p1[1] * p1[0]][p1[3] * p1[2] * p2]; signal output out <== in1 * in2[0][0]; } -component main {public [in1]} = SomeCircuit([5, 3, 2, 5]); +component main {public [in1]} = SomeCircuit([5, 3, 2, 5], 3); From e643528ba40a80c5b65402c2fa616f25b7ca9de4 Mon Sep 17 00:00:00 2001 From: Kyrylo Riabov Date: Thu, 21 Nov 2024 20:13:09 +0200 Subject: [PATCH 06/12] Update src/utils/ExpressionHelper.ts Co-authored-by: Artem Chystiakov <47551140+Arvolear@users.noreply.github.com> --- src/utils/ExpressionHelper.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/ExpressionHelper.ts b/src/utils/ExpressionHelper.ts index cf6f68e..dc749c4 100644 --- a/src/utils/ExpressionHelper.ts +++ b/src/utils/ExpressionHelper.ts @@ -147,6 +147,7 @@ class ExpressionVisitor extends ExtendedCircomVisitor { "IdentifierStatement is not supported with access references that are not arrays", ctx, ); + return null; } From 342792826c87a54761d5fa2aa7f601c0e30ae306 Mon Sep 17 00:00:00 2001 From: Kyrylo Riabov Date: Thu, 21 Nov 2024 20:13:14 +0200 Subject: [PATCH 07/12] Update src/index.ts Co-authored-by: Artem Chystiakov <47551140+Arvolear@users.noreply.github.com> --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index c3b6f3f..60c36e4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ import { ExtendedCircomParser } from "./ExtendedCircomParser"; export function getCircomParser(source: string): ExtendedCircomParser { let inputStream: antlr4.CharStream; let fileIdentifier = "Built from source"; + if (fs.existsSync(source)) { inputStream = antlr4.CharStreams.fromPathSync(source, "utf8"); fileIdentifier = source; From 4a9f025e09aa856ead6e13c0a74301efc31d4843 Mon Sep 17 00:00:00 2001 From: Kyrylo Riabov Date: Thu, 21 Nov 2024 20:13:19 +0200 Subject: [PATCH 08/12] Update src/utils/common.ts Co-authored-by: Artem Chystiakov <47551140+Arvolear@users.noreply.github.com> --- src/utils/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/common.ts b/src/utils/common.ts index ce5bef9..451837a 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -57,8 +57,8 @@ export function bindVariableContext( values: any, ): VariableContextWithNull { const context: VariableContextWithNull = {}; - const resolved = resolveDimensions(variableName, dimensions); + for (const variable of resolved) { try { context[variable] = parseVariable( From 409ecaf9695dc94022636dfbda65908cb909f4a2 Mon Sep 17 00:00:00 2001 From: Kyrylo Riabov Date: Thu, 21 Nov 2024 20:13:25 +0200 Subject: [PATCH 09/12] Update src/utils/common.ts Co-authored-by: Artem Chystiakov <47551140+Arvolear@users.noreply.github.com> --- src/utils/common.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/common.ts b/src/utils/common.ts index 451837a..58def13 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -33,6 +33,7 @@ export function buildVariableContext( getArrayDimensions(values[i]), values[i], ); + for (const key in bindContext) { if (bindContext[key] !== null) { context[key] = bindContext[key]; From b35225eb0fe0678a1e389c35163f7d267d127641 Mon Sep 17 00:00:00 2001 From: Kyryl R Date: Thu, 21 Nov 2024 20:14:11 +0200 Subject: [PATCH 10/12] Refactored code --- src/utils/common.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/utils/common.ts b/src/utils/common.ts index ce5bef9..bd3f147 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -73,6 +73,13 @@ export function bindVariableContext( return context; } +export function resolveDimensions( + variableName: string, + dimensions: number[], +): string[] { + return internalResolveDimensions([variableName], dimensions, 0); +} + // reference MUST be similar to [0][1] function parseVariable(value: any, reference: string): bigint { const parts = reference @@ -92,13 +99,6 @@ function getReferenceValueInternal(value: any, reference: number[]): bigint { return getReferenceValueInternal(value[reference[0]], reference.slice(1)); } -export function resolveDimensions( - variableName: string, - dimensions: number[], -): string[] { - return internalResolveDimensions([variableName], dimensions, 0); -} - function internalResolveDimensions( variables: string[], dimensions: number[], From a1d43715d09ec1b60760e311d9e73056dd794dfe Mon Sep 17 00:00:00 2001 From: Kyryl R Date: Thu, 21 Nov 2024 20:21:53 +0200 Subject: [PATCH 11/12] Fixed usage of any --- src/utils/common.ts | 28 +++++++++++++++++++++------- test/utils.ts | 10 +++++----- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/utils/common.ts b/src/utils/common.ts index 66a6f19..37af205 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,5 +1,9 @@ import { SimpleIdentifierListContext } from "../generated"; -import { VariableContext, VariableContextWithNull } from "../types"; +import { + CircomValueType, + VariableContext, + VariableContextWithNull, +} from "../types"; export function parseSimpleIdentifierList( ctx: SimpleIdentifierListContext, @@ -19,7 +23,7 @@ export function parseSimpleIdentifierList( export function buildVariableContext( names: string[], - values: any[], + values: CircomValueType[], ): VariableContext { if (names.length !== values.length) { throw new Error("Names and values must have the same length"); @@ -44,7 +48,7 @@ export function buildVariableContext( return context; } -export function getArrayDimensions(value: any): number[] { +export function getArrayDimensions(value: CircomValueType): number[] { if (Array.isArray(value)) { return [value.length, ...getArrayDimensions(value[0])]; } @@ -55,7 +59,7 @@ export function getArrayDimensions(value: any): number[] { export function bindVariableContext( variableName: string, dimensions: number[], - values: any, + values: CircomValueType, ): VariableContextWithNull { const context: VariableContextWithNull = {}; const resolved = resolveDimensions(variableName, dimensions); @@ -82,7 +86,10 @@ export function resolveDimensions( } // reference MUST be similar to [0][1] -function parseVariable(value: any, reference: string): bigint { +function parseVariable( + value: CircomValueType, + reference: string, +): CircomValueType { const parts = reference .split("[") .map((part) => part.replace("]", "")) @@ -92,9 +99,16 @@ function parseVariable(value: any, reference: string): bigint { return getReferenceValueInternal(value, parts); } -function getReferenceValueInternal(value: any, reference: number[]): bigint { +function getReferenceValueInternal( + value: CircomValueType, + reference: number[], +): CircomValueType { if (reference.length === 0) { - return BigInt(value); + return value; + } + + if (!Array.isArray(value)) { + throw new Error("INTERNAL ERROR! Reference is invalid"); } return getReferenceValueInternal(value[reference[0]], reference.slice(1)); diff --git a/test/utils.ts b/test/utils.ts index 24ffbe0..abc7e1c 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -5,7 +5,7 @@ import { bindVariableContext } from "../src"; describe("utils", () => { describe("resolve dimensions", () => { it("should resolve the case: [1, 2], when value: [[3, 5]]", () => { - expect(bindVariableContext("a", [1, 2], [[3, 5]])).to.deep.equal({ + expect(bindVariableContext("a", [1, 2], [[3n, 5n]])).to.deep.equal({ "a[0][0]": 3n, "a[0][1]": 5n, }); @@ -19,13 +19,13 @@ describe("utils", () => { }); it("should resolve the case: [], when value: 1", () => { - expect(bindVariableContext("a", [], 1)).to.deep.equal({ + expect(bindVariableContext("a", [], 1n)).to.deep.equal({ a: 1n, }); }); it("should resolve the case: [1], when value: 1", () => { - expect(bindVariableContext("a", [1], 1)).to.deep.equal({ + expect(bindVariableContext("a", [1], 1n)).to.deep.equal({ "a[0]": null, }); }); @@ -36,8 +36,8 @@ describe("utils", () => { "a", [2, 2], [ - [1, 2], - [3, 4], + [1n, 2n], + [3n, 4n], ], ), ).to.deep.equal({ From 43e3ae92c132b3264b51b13ea5f3c7370a8455fe Mon Sep 17 00:00:00 2001 From: Kyryl R Date: Thu, 21 Nov 2024 20:26:58 +0200 Subject: [PATCH 12/12] Updated versions --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0b17f2b..f3593c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@distributedlab/circom-parser", - "version": "0.2.1", + "version": "0.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@distributedlab/circom-parser", - "version": "0.2.1", + "version": "0.2.2", "license": "MIT", "dependencies": { "antlr4": "4.13.1-patch-1", diff --git a/package.json b/package.json index 19a2389..dd440c2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@distributedlab/circom-parser", "description": "Circom circuit parser built with ANTLR4", - "version": "0.2.1", + "version": "0.2.2", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [