From 3bba7f9369b2b979c82323f1148223dcb21da389 Mon Sep 17 00:00:00 2001 From: Oleg Komendant <44612825+Hrom131@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:14:45 +0300 Subject: [PATCH] Add Proof testing functionality (#4) * Add base proof testing functions * Update proof testing logic & clean up * Fix unit tests * fix readme --------- Co-authored-by: Artem Chystiakov --- README.md | 35 +- package-lock.json | 515 +++++++----------- package.json | 4 +- src/chai-zkit.ts | 2 + src/constants.ts | 7 + src/proof.ts | 75 +++ src/types.ts | 20 +- src/utils/compare-utils.ts | 66 +++ src/utils/index.ts | 2 + src/{ => utils}/utils.ts | 23 +- src/witness.ts | 63 +-- test/chai-zkit.test.ts | 238 -------- .../generated-types/zkit/core/Matrix.ts | 73 ++- .../generated-types/zkit/core/NoInputs.ts | 30 +- .../generated-types/zkit/utils.ts | 44 ++ .../complex-circuits/package.json | 2 +- test/helpers.ts | 2 +- test/proof.test.ts | 190 +++++++ test/witness.test.ts | 200 +++++++ 19 files changed, 911 insertions(+), 680 deletions(-) create mode 100644 src/constants.ts create mode 100644 src/proof.ts create mode 100644 src/utils/compare-utils.ts create mode 100644 src/utils/index.ts rename src/{ => utils}/utils.ts (85%) delete mode 100644 test/chai-zkit.test.ts create mode 100644 test/fixture-projects/complex-circuits/generated-types/zkit/utils.ts create mode 100644 test/proof.test.ts create mode 100644 test/witness.test.ts diff --git a/README.md b/README.md index a5d4402..0614a47 100644 --- a/README.md +++ b/README.md @@ -19,14 +19,14 @@ npm install --save-dev @solarity/chai-zkit And add the following line to your `hardhat.config`: -```js -require("@solarity/chai-zkit"); +```ts +import "@solarity/chai-zkit"; ``` -Or if you are using TypeScript: +Or if you are using JavaScript: -```ts -import "@solarity/chai-zkit"; +```js +require("@solarity/chai-zkit"); ``` ## Usage @@ -36,6 +36,8 @@ import "@solarity/chai-zkit"; After installing the package, you may use the following assertions: +### Witness testing + ```ts const matrix = await zkit.getCircuit("Matrix"); @@ -49,14 +51,27 @@ await expect(matrix).with.witnessInputs({ a, b, c }).to.have.strict.witnessOutpu await expect(expect(matrix).with.witnessInputs({ a, b, c }).to.have.witnessOutputs({ e })).to.be.rejectedWith( `Expected output "e" to be "[[2,5,0],[17,26,0],[0,0,0]]", but got "[[1,4,0],[16,25,0],[0,0,0]]"`, ); +``` + +### Proof testing + +```ts +const matrix = await zkit.getCircuit("Matrix"); + +// proof generation assertion +await expect(matrix).to.generateProof({ a, b, c }); +await expect(matrix).to.not.generateProof({ b, c, d }); + +const proof = await matrix.generateProof({ a, b, c }); + +// proof verification assertion +await expect(matrix).to.verifyProof(proof); +await expect(matrix).to.not.verifyProof(otherProof); -// `not` negation used, provided output `d` matches the obtained one -await expect( - expect(matrix).with.witnessInputs({ a, b, c }).to.not.have.witnessOutputs({ d }), -).to.be.rejectedWith(`Expected output "d" NOT to be "[[2,5,0],[17,26,0],[0,0,0]]", but it is"`); +// use generated solidity verifier to verify the proof +await expect(matrix).to.useSolidityVerifier(matrixVerifier).and.verifyProof(proof); ``` ## Known limitations - Do not use `not` chai negation prior `witnessInputs` call, this will break the typization. -- Temporarily, only the witness `input <> output` signals testing is supported. diff --git a/package-lock.json b/package-lock.json index 89f9cd5..61a0c17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@solarity/chai-zkit", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@solarity/chai-zkit", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "dependencies": { "@solarity/zkit": "0.2.4", @@ -14,7 +14,7 @@ }, "devDependencies": { "@nomicfoundation/hardhat-ethers": "3.0.5", - "@solarity/hardhat-zkit": "^0.3.0", + "@solarity/hardhat-zkit": "^0.3.1", "@types/chai": "^4.3.16", "@types/chai-as-promised": "^7.1.8", "@types/mocha": "^10.0.6", @@ -66,30 +66,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.9.tgz", - "integrity": "sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", + "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz", - "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.9", - "@babel/helper-compilation-targets": "^7.24.8", - "@babel/helper-module-transforms": "^7.24.9", - "@babel/helpers": "^7.24.8", - "@babel/parser": "^7.24.8", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.8", - "@babel/types": "^7.24.9", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -111,12 +111,12 @@ "dev": true }, "node_modules/@babel/generator": { - "version": "7.24.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.10.tgz", - "integrity": "sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", "dev": true, "dependencies": { - "@babel/types": "^7.24.9", + "@babel/types": "^7.25.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -126,12 +126,12 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz", - "integrity": "sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.24.8", + "@babel/compat-data": "^7.25.2", "@babel/helper-validator-option": "^7.24.8", "browserslist": "^4.23.1", "lru-cache": "^5.1.1", @@ -156,43 +156,6 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", - "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", - "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", @@ -207,16 +170,15 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz", - "integrity": "sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", "@babel/helper-module-imports": "^7.24.7", "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -238,18 +200,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.24.8", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", @@ -278,13 +228,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.8.tgz", - "integrity": "sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", + "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", "dev": true, "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.8" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -377,10 +327,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", - "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", + "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", "dev": true, + "dependencies": { + "@babel/types": "^7.25.2" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -389,33 +342,30 @@ } }, "node_modules/@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.8.tgz", - "integrity": "sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", + "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.8", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.8", - "@babel/types": "^7.24.8", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -424,9 +374,9 @@ } }, "node_modules/@babel/types": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.9.tgz", - "integrity": "sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.24.8", @@ -470,15 +420,18 @@ } }, "node_modules/@distributedlab/circom2": { - "version": "0.2.18-rc.2", - "resolved": "https://registry.npmjs.org/@distributedlab/circom2/-/circom2-0.2.18-rc.2.tgz", - "integrity": "sha512-hBFZ6J7qvxXYgERDspIeG+hh4QfUrN4O7S7JftR1J3zvF5tJ5hpXyguHDn6STQOc4fLtnjTAWDZyIbnCihJWQA==", + "version": "0.2.18-rc.4", + "resolved": "https://registry.npmjs.org/@distributedlab/circom2/-/circom2-0.2.18-rc.4.tgz", + "integrity": "sha512-5ALgnpk+mdzZDeKwR3ZIjJXjVeWt+Qz4g/lWAR4cxTrnEhiQptDacg0wtd7WKBUghDpEruLZ4wXyIdRoXuJSaQ==", "dev": true, "dependencies": { "@wasmer/wasi": "^0.12.0", "is-typed-array": "^1.1.8", "path-browserify": "^1.0.1" }, + "bin": { + "circom2": "cli.js" + }, "engines": { "node": ">=15" }, @@ -892,6 +845,15 @@ "resolved": "https://registry.npmjs.org/@iden3/bigarray/-/bigarray-0.0.2.tgz", "integrity": "sha512-Xzdyxqm1bOFF6pdIsiHLLl3HkSLjbhqJHVyqaTxXt3RqXBEnmsUmEW47H7VOi/ak7TdkRpNkxjyK5Zbkm+y52g==" }, + "node_modules/@iden3/binfileutils": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@iden3/binfileutils/-/binfileutils-0.0.11.tgz", + "integrity": "sha512-LylnJoZ0CTdgErnKY8OxohvW4K+p6UHD3sxt+3P9AmMyBQjYR4IpoqoYZZ+9aMj89cmCQ21UvdhndAx04er3NA==", + "dependencies": { + "fastfile": "0.0.20", + "ffjavascript": "^0.2.48" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1861,17 +1823,14 @@ "dev": true }, "node_modules/@solarity/hardhat-zkit": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@solarity/hardhat-zkit/-/hardhat-zkit-0.3.0.tgz", - "integrity": "sha512-D9p6SIWri1LnY5AXSSCPRH/Qbx1YRYefVj/D5OLclOelRpIm38i7ADg1HTNgPSw0P3HMJFctUpzUI7/t/CLmNg==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@solarity/hardhat-zkit/-/hardhat-zkit-0.3.1.tgz", + "integrity": "sha512-ZS1F33cMbq2lQyZf0GlPoB65umOflWqJkj1DeYboJA7ITJ26dTIBN6C1P5MtvBm2Uc++SfMbTu7Gfk5bvY35bQ==", "dev": true, - "workspaces": [ - "test/fixture-projects/*" - ], "dependencies": { - "@distributedlab/circom2": "0.2.18-rc.2", + "@distributedlab/circom2": "0.2.18-rc.4", "@solarity/zkit": "0.2.4", - "@solarity/zktype": "0.2.4", + "@solarity/zktype": "0.2.7", "chalk": "4.1.2", "cli-progress": "3.12.0", "cli-table3": "0.6.5", @@ -1886,92 +1845,21 @@ "hardhat": "^2.16.0" } }, - "node_modules/@solarity/hardhat-zkit/node_modules/@iden3/binfileutils": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/@iden3/binfileutils/-/binfileutils-0.0.11.tgz", - "integrity": "sha512-LylnJoZ0CTdgErnKY8OxohvW4K+p6UHD3sxt+3P9AmMyBQjYR4IpoqoYZZ+9aMj89cmCQ21UvdhndAx04er3NA==", - "dev": true, - "dependencies": { - "fastfile": "0.0.20", - "ffjavascript": "^0.2.48" - } - }, - "node_modules/@solarity/hardhat-zkit/node_modules/circom_runtime": { - "version": "0.1.24", - "resolved": "https://registry.npmjs.org/circom_runtime/-/circom_runtime-0.1.24.tgz", - "integrity": "sha512-H7/7I2J/cBmRnZm9docOCGhfxzS61BEm4TMCWcrZGsWNBQhePNfQq88Oj2XpUfzmBTCd8pRvRb3Mvazt3TMrJw==", + "node_modules/@solarity/hardhat-zkit/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "dependencies": { - "ffjavascript": "0.2.60" + "ms": "2.1.2" }, - "bin": { - "calcwit": "calcwit.js" - } - }, - "node_modules/@solarity/hardhat-zkit/node_modules/circom_runtime/node_modules/ffjavascript": { - "version": "0.2.60", - "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.60.tgz", - "integrity": "sha512-T/9bnEL5xAZRDbQoEMf+pM9nrhK+C3JyZNmqiWub26EQorW7Jt+jR54gpqDhceA4Nj0YctPQwYnl8xa52/A26A==", - "dev": true, - "dependencies": { - "wasmbuilder": "0.0.16", - "wasmcurves": "0.2.2", - "web-worker": "^1.2.0" - } - }, - "node_modules/@solarity/hardhat-zkit/node_modules/ffjavascript": { - "version": "0.2.63", - "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.63.tgz", - "integrity": "sha512-dBgdsfGks58b66JnUZeZpGxdMIDQ4QsD3VYlRJyFVrKQHb2kJy4R2gufx5oetrTxXPT+aEjg0dOvOLg1N0on4A==", - "dev": true, - "dependencies": { - "wasmbuilder": "0.0.16", - "wasmcurves": "0.2.2", - "web-worker": "1.2.0" - } - }, - "node_modules/@solarity/hardhat-zkit/node_modules/r1csfile": { - "version": "0.0.47", - "resolved": "https://registry.npmjs.org/r1csfile/-/r1csfile-0.0.47.tgz", - "integrity": "sha512-oI4mAwuh1WwuFg95eJDNDDL8hCaZkwnPuNZrQdLBWvDoRU7EG+L/MOHL7SwPW2Y+ZuYcTLpj3rBkgllBQZN/JA==", - "dev": true, - "dependencies": { - "@iden3/bigarray": "0.0.2", - "@iden3/binfileutils": "0.0.11", - "fastfile": "0.0.20", - "ffjavascript": "0.2.60" - } - }, - "node_modules/@solarity/hardhat-zkit/node_modules/r1csfile/node_modules/ffjavascript": { - "version": "0.2.60", - "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.60.tgz", - "integrity": "sha512-T/9bnEL5xAZRDbQoEMf+pM9nrhK+C3JyZNmqiWub26EQorW7Jt+jR54gpqDhceA4Nj0YctPQwYnl8xa52/A26A==", - "dev": true, - "dependencies": { - "wasmbuilder": "0.0.16", - "wasmcurves": "0.2.2", - "web-worker": "^1.2.0" - } - }, - "node_modules/@solarity/hardhat-zkit/node_modules/snarkjs": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/snarkjs/-/snarkjs-0.7.3.tgz", - "integrity": "sha512-cDLpWqdqEJSCQNc+cXYX1XTKdUZBtYEisuOsgmXf/HUsN5WmGN+FO7HfCS+cMQT1Nzbm1a9gAEpKH6KRtDtS1Q==", - "dev": true, - "dependencies": { - "@iden3/binfileutils": "0.0.11", - "bfj": "^7.0.2", - "blake2b-wasm": "^2.4.0", - "circom_runtime": "0.1.24", - "ejs": "^3.1.6", - "fastfile": "0.0.20", - "ffjavascript": "0.2.63", - "js-sha3": "^0.8.0", - "logplease": "^1.2.15", - "r1csfile": "0.0.47" + "engines": { + "node": ">=6.0" }, - "bin": { - "snarkjs": "build/cli.cjs" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/@solarity/zkit": { @@ -1983,91 +1871,10 @@ "snarkjs": "0.7.3" } }, - "node_modules/@solarity/zkit/node_modules/@iden3/binfileutils": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/@iden3/binfileutils/-/binfileutils-0.0.11.tgz", - "integrity": "sha512-LylnJoZ0CTdgErnKY8OxohvW4K+p6UHD3sxt+3P9AmMyBQjYR4IpoqoYZZ+9aMj89cmCQ21UvdhndAx04er3NA==", - "dependencies": { - "fastfile": "0.0.20", - "ffjavascript": "^0.2.48" - } - }, - "node_modules/@solarity/zkit/node_modules/circom_runtime": { - "version": "0.1.24", - "resolved": "https://registry.npmjs.org/circom_runtime/-/circom_runtime-0.1.24.tgz", - "integrity": "sha512-H7/7I2J/cBmRnZm9docOCGhfxzS61BEm4TMCWcrZGsWNBQhePNfQq88Oj2XpUfzmBTCd8pRvRb3Mvazt3TMrJw==", - "dependencies": { - "ffjavascript": "0.2.60" - }, - "bin": { - "calcwit": "calcwit.js" - } - }, - "node_modules/@solarity/zkit/node_modules/circom_runtime/node_modules/ffjavascript": { - "version": "0.2.60", - "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.60.tgz", - "integrity": "sha512-T/9bnEL5xAZRDbQoEMf+pM9nrhK+C3JyZNmqiWub26EQorW7Jt+jR54gpqDhceA4Nj0YctPQwYnl8xa52/A26A==", - "dependencies": { - "wasmbuilder": "0.0.16", - "wasmcurves": "0.2.2", - "web-worker": "^1.2.0" - } - }, - "node_modules/@solarity/zkit/node_modules/ffjavascript": { - "version": "0.2.63", - "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.63.tgz", - "integrity": "sha512-dBgdsfGks58b66JnUZeZpGxdMIDQ4QsD3VYlRJyFVrKQHb2kJy4R2gufx5oetrTxXPT+aEjg0dOvOLg1N0on4A==", - "dependencies": { - "wasmbuilder": "0.0.16", - "wasmcurves": "0.2.2", - "web-worker": "1.2.0" - } - }, - "node_modules/@solarity/zkit/node_modules/r1csfile": { - "version": "0.0.47", - "resolved": "https://registry.npmjs.org/r1csfile/-/r1csfile-0.0.47.tgz", - "integrity": "sha512-oI4mAwuh1WwuFg95eJDNDDL8hCaZkwnPuNZrQdLBWvDoRU7EG+L/MOHL7SwPW2Y+ZuYcTLpj3rBkgllBQZN/JA==", - "dependencies": { - "@iden3/bigarray": "0.0.2", - "@iden3/binfileutils": "0.0.11", - "fastfile": "0.0.20", - "ffjavascript": "0.2.60" - } - }, - "node_modules/@solarity/zkit/node_modules/r1csfile/node_modules/ffjavascript": { - "version": "0.2.60", - "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.60.tgz", - "integrity": "sha512-T/9bnEL5xAZRDbQoEMf+pM9nrhK+C3JyZNmqiWub26EQorW7Jt+jR54gpqDhceA4Nj0YctPQwYnl8xa52/A26A==", - "dependencies": { - "wasmbuilder": "0.0.16", - "wasmcurves": "0.2.2", - "web-worker": "^1.2.0" - } - }, - "node_modules/@solarity/zkit/node_modules/snarkjs": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/snarkjs/-/snarkjs-0.7.3.tgz", - "integrity": "sha512-cDLpWqdqEJSCQNc+cXYX1XTKdUZBtYEisuOsgmXf/HUsN5WmGN+FO7HfCS+cMQT1Nzbm1a9gAEpKH6KRtDtS1Q==", - "dependencies": { - "@iden3/binfileutils": "0.0.11", - "bfj": "^7.0.2", - "blake2b-wasm": "^2.4.0", - "circom_runtime": "0.1.24", - "ejs": "^3.1.6", - "fastfile": "0.0.20", - "ffjavascript": "0.2.63", - "js-sha3": "^0.8.0", - "logplease": "^1.2.15", - "r1csfile": "0.0.47" - }, - "bin": { - "snarkjs": "build/cli.cjs" - } - }, "node_modules/@solarity/zktype": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@solarity/zktype/-/zktype-0.2.4.tgz", - "integrity": "sha512-Vp87y0mFLli0VDoZV9WCAr1wExICi0pNZqDBFUyILvmvRnrjB9ILhZQuux8YEDSO6pBq+RSqPpx9M7IssFmd3Q==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@solarity/zktype/-/zktype-0.2.7.tgz", + "integrity": "sha512-L9Z0YweqBMtaKpkrlREifgasHKWUR7iNkHaL6BUefjjtZnPj6Ft8ycLVh5V+mn+vixVkHjPRyTVTWu04plmq1Q==", "dev": true, "dependencies": { "ejs": "3.1.10", @@ -2115,9 +1922,9 @@ } }, "node_modules/@types/chai": { - "version": "4.3.16", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.16.tgz", - "integrity": "sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==", + "version": "4.3.17", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.17.tgz", + "integrity": "sha512-zmZ21EWzR71B4Sscphjief5djsLre50M6lI622OSySTmn9DB3j+C3kWroHfBQWXbOBwbgg/M8CG/hUxDLIloow==", "dev": true }, "node_modules/@types/chai-as-promised": { @@ -2157,12 +1964,12 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.14.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz", - "integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==", + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.2.0.tgz", + "integrity": "sha512-bm6EG6/pCpkxDf/0gDNDdtDILMOHgaQBVOJGdwsqClnxA3xL6jtMv76rLBc006RVMWbmaf0xbmom4Z/5o2nRkQ==", "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.13.0" } }, "node_modules/@types/pbkdf2": { @@ -2622,9 +2429,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", - "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "dev": true, "funding": [ { @@ -2641,9 +2448,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001640", - "electron-to-chromium": "^1.4.820", - "node-releases": "^2.0.14", + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", "update-browserslist-db": "^1.1.0" }, "bin": { @@ -2771,9 +2578,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001643", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz", - "integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==", + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", "dev": true, "funding": [ { @@ -2791,9 +2598,9 @@ ] }, "node_modules/chai": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.0.tgz", - "integrity": "sha512-x9cHNq1uvkCdU+5xTkNh5WtgD4e4yDFCsp9jVc7N7qVeKeftv3gO/ZrviX5d+3ZfxdYnZXZYujjRInu1RogU6A==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -2801,7 +2608,7 @@ "get-func-name": "^2.0.2", "loupe": "^2.3.6", "pathval": "^1.1.1", - "type-detect": "^4.0.8" + "type-detect": "^4.1.0" }, "engines": { "node": ">=4" @@ -2896,6 +2703,27 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/circom_runtime": { + "version": "0.1.24", + "resolved": "https://registry.npmjs.org/circom_runtime/-/circom_runtime-0.1.24.tgz", + "integrity": "sha512-H7/7I2J/cBmRnZm9docOCGhfxzS61BEm4TMCWcrZGsWNBQhePNfQq88Oj2XpUfzmBTCd8pRvRb3Mvazt3TMrJw==", + "dependencies": { + "ffjavascript": "0.2.60" + }, + "bin": { + "calcwit": "calcwit.js" + } + }, + "node_modules/circom_runtime/node_modules/ffjavascript": { + "version": "0.2.60", + "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.60.tgz", + "integrity": "sha512-T/9bnEL5xAZRDbQoEMf+pM9nrhK+C3JyZNmqiWub26EQorW7Jt+jR54gpqDhceA4Nj0YctPQwYnl8xa52/A26A==", + "dependencies": { + "wasmbuilder": "0.0.16", + "wasmcurves": "0.2.2", + "web-worker": "^1.2.0" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -3090,9 +2918,9 @@ } }, "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -3208,9 +3036,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.2.tgz", - "integrity": "sha512-kc4r3U3V3WLaaZqThjYz/Y6z8tJe+7K0bbjUVo3i+LWIypVdMx5nXCkwRe6SWbY6ILqLdc1rKcKmr3HoH7wjSQ==", + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.6.tgz", + "integrity": "sha512-jwXWsM5RPf6j9dPYzaorcBSUg6AiqocPEyMpkchkvntaH9HGfOOMZwxMJjDY/XEs3T5dM7uyH1VhRMkqUU9qVw==", "dev": true }, "node_modules/elliptic": { @@ -3532,6 +3360,16 @@ "resolved": "https://registry.npmjs.org/fastfile/-/fastfile-0.0.20.tgz", "integrity": "sha512-r5ZDbgImvVWCP0lA/cGNgQcZqR+aYdFx3u+CtJqUE510pBUVGMn4ulL/iRTI4tACTYsNJ736uzFxEBXesPAktA==" }, + "node_modules/ffjavascript": { + "version": "0.2.63", + "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.63.tgz", + "integrity": "sha512-dBgdsfGks58b66JnUZeZpGxdMIDQ4QsD3VYlRJyFVrKQHb2kJy4R2gufx5oetrTxXPT+aEjg0dOvOLg1N0on4A==", + "dependencies": { + "wasmbuilder": "0.0.16", + "wasmcurves": "0.2.2", + "web-worker": "1.2.0" + } + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -4257,9 +4095,9 @@ } }, "node_modules/husky": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.2.tgz", - "integrity": "sha512-1/aDMXZdhr1VdJJTLt6e7BipM0Jd9qkpubPiIplon1WmCeOy3nnzsCMeBqS9AsL5ioonl8F8y/F2CLOmk19/Pw==", + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.4.tgz", + "integrity": "sha512-bho94YyReb4JV7LYWRWxZ/xr6TtOTt8cMfmQ39MQYJ7f/YE268s3GdghGwi+y4zAeqewE5zYLvuhV0M0ijsDEA==", "dev": true, "bin": { "husky": "bin.js" @@ -5002,9 +4840,9 @@ } }, "node_modules/mocha": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.0.tgz", - "integrity": "sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA==", + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", + "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", "dev": true, "dependencies": { "ansi-colors": "^4.1.3", @@ -5779,6 +5617,27 @@ "node": ">=6" } }, + "node_modules/r1csfile": { + "version": "0.0.47", + "resolved": "https://registry.npmjs.org/r1csfile/-/r1csfile-0.0.47.tgz", + "integrity": "sha512-oI4mAwuh1WwuFg95eJDNDDL8hCaZkwnPuNZrQdLBWvDoRU7EG+L/MOHL7SwPW2Y+ZuYcTLpj3rBkgllBQZN/JA==", + "dependencies": { + "@iden3/bigarray": "0.0.2", + "@iden3/binfileutils": "0.0.11", + "fastfile": "0.0.20", + "ffjavascript": "0.2.60" + } + }, + "node_modules/r1csfile/node_modules/ffjavascript": { + "version": "0.2.60", + "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.60.tgz", + "integrity": "sha512-T/9bnEL5xAZRDbQoEMf+pM9nrhK+C3JyZNmqiWub26EQorW7Jt+jR54gpqDhceA4Nj0YctPQwYnl8xa52/A26A==", + "dependencies": { + "wasmbuilder": "0.0.16", + "wasmcurves": "0.2.2", + "web-worker": "^1.2.0" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -6107,6 +5966,26 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/snarkjs": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/snarkjs/-/snarkjs-0.7.3.tgz", + "integrity": "sha512-cDLpWqdqEJSCQNc+cXYX1XTKdUZBtYEisuOsgmXf/HUsN5WmGN+FO7HfCS+cMQT1Nzbm1a9gAEpKH6KRtDtS1Q==", + "dependencies": { + "@iden3/binfileutils": "0.0.11", + "bfj": "^7.0.2", + "blake2b-wasm": "^2.4.0", + "circom_runtime": "0.1.24", + "ejs": "^3.1.6", + "fastfile": "0.0.20", + "ffjavascript": "0.2.63", + "js-sha3": "^0.8.0", + "logplease": "^1.2.15", + "r1csfile": "0.0.47" + }, + "bin": { + "snarkjs": "build/cli.cjs" + } + }, "node_modules/solc": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/solc/-/solc-0.7.3.tgz", @@ -6622,9 +6501,9 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", + "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==", "dev": true }, "node_modules/universal-url": { diff --git a/package.json b/package.json index 0f2d650..cb20822 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@solarity/chai-zkit", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "author": "Distributed Lab", "readme": "README.md", @@ -47,7 +47,7 @@ }, "devDependencies": { "@nomicfoundation/hardhat-ethers": "3.0.5", - "@solarity/hardhat-zkit": "^0.3.0", + "@solarity/hardhat-zkit": "^0.3.1", "@types/chai": "^4.3.16", "@types/chai-as-promised": "^7.1.8", "@types/mocha": "^10.0.6", diff --git a/src/chai-zkit.ts b/src/chai-zkit.ts index e182aa5..22b14cf 100644 --- a/src/chai-zkit.ts +++ b/src/chai-zkit.ts @@ -1,5 +1,7 @@ import { witness } from "./witness"; +import { proof } from "./proof"; export function chaiZkit(chai: Chai.ChaiStatic, utils: Chai.ChaiUtils): void { witness(chai, utils); + proof(chai, utils); } diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..7e720af --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,7 @@ +export const GENERATE_PROOF_METHOD: string = "generateProof"; +export const USE_SOLIDITY_VERIFIER_METHOD: string = "useSolidityVerifier"; +export const VERIFY_PROOF_METHOD: string = "verifyProof"; + +export const STRICT_PROPERTY: string = "strict"; +export const WITNESS_INPUTS_METHOD: string = "witnessInputs"; +export const WITNESS_OUTPUTS_METHOD: string = "witnessOutputs"; diff --git a/src/proof.ts b/src/proof.ts new file mode 100644 index 0000000..637747b --- /dev/null +++ b/src/proof.ts @@ -0,0 +1,75 @@ +import { Signals } from "@solarity/zkit"; + +import { GENERATE_PROOF_METHOD, USE_SOLIDITY_VERIFIER_METHOD, VERIFY_PROOF_METHOD } from "./constants"; +import { checkCircuitZKit } from "./utils"; + +export function proof(chai: Chai.ChaiStatic, utils: Chai.ChaiUtils): void { + chai.Assertion.addMethod(GENERATE_PROOF_METHOD, function (this: any, inputs: Signals) { + const obj = utils.flag(this, "object"); + + checkCircuitZKit(obj, GENERATE_PROOF_METHOD); + + const promise = (this.then === undefined ? Promise.resolve() : this).then(async () => { + let isGenerated = true; + + try { + const proof = await obj.generateProof(inputs); + + utils.flag(this, "generatedProof", proof); + } catch (e) { + isGenerated = false; + } + + this.assert( + isGenerated, + "Expected proof generation to be successful, but it isn't", + "Expected proof generation NOT to be successful, but it is", + ); + }); + + this.then = promise.then.bind(promise); + this.catch = promise.catch.bind(promise); + + return this; + }); + + chai.Assertion.addMethod(USE_SOLIDITY_VERIFIER_METHOD, function (this: any, verifierContract: any) { + const obj = utils.flag(this, "object"); + + checkCircuitZKit(obj, USE_SOLIDITY_VERIFIER_METHOD); + + utils.flag(this, "solidityVerifier", verifierContract); + + return this; + }); + + chai.Assertion.addMethod(VERIFY_PROOF_METHOD, function (this: any, proof: any) { + const obj = utils.flag(this, "object"); + + checkCircuitZKit(obj, VERIFY_PROOF_METHOD); + + const promise = (this.then === undefined ? Promise.resolve() : this).then(async () => { + let verificationResult: boolean; + const solidityVerifier = utils.flag(this, "solidityVerifier"); + + if (solidityVerifier) { + const calldata = await obj.generateCalldata(proof); + + verificationResult = await solidityVerifier.verifyProof(...calldata); + } else { + verificationResult = await obj.verifyProof(proof); + } + + this.assert( + verificationResult, + "Expected proof verification result to be true, but it isn't", + "Expected proof verification result NOT to be true, but it is", + ); + }); + + this.then = promise.then.bind(promise); + this.catch = promise.catch.bind(promise); + + return this; + }); +} diff --git a/src/types.ts b/src/types.ts index cfdc6dd..c23b3b4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,6 +4,7 @@ export type ExtractOutputs = Omit, keyof ExtractInput type ExtractPublicSignals = T extends { verifyProof(proof: { publicSignals: infer P }): Promise } ? P : never; +type ExtractProofType = T extends { verifyProof(proof: infer P): Promise } ? P : never; type Circuit = { generateProof(inputs: any): Promise; @@ -20,7 +21,18 @@ declare global { (val: T, message?: string): Assertion; } - interface AsyncAssertion extends Promise { + interface Witness { + witnessInputs(inputs: T extends Circuit ? ExtractInputs : never): AsyncAssertion; + witnessOutputs(outputs: T extends Circuit ? Partial> : never): AsyncAssertion; + } + + interface Proof { + generateProof(inputs: T extends Circuit ? ExtractInputs : never): AsyncAssertion; + verifyProof(proof: T extends Circuit ? ExtractProofType : never): AsyncAssertion; + useSolidityVerifier(verifierContract: any): AsyncAssertion; + } + + interface AsyncAssertion extends Promise, Witness, Proof { not: AsyncAssertion; strict: AsyncAssertion; to: AsyncAssertion; @@ -38,11 +50,9 @@ declare global { same: AsyncAssertion; but: AsyncAssertion; does: AsyncAssertion; - witnessInputs(inputs: T extends Circuit ? ExtractInputs : never): AsyncAssertion; - witnessOutputs(outputs: T extends Circuit ? Partial> : never): AsyncAssertion; } - interface Assertion { + interface Assertion extends Witness, Proof { to: Assertion; be: Assertion; been: Assertion; @@ -58,8 +68,6 @@ declare global { same: Assertion; but: Assertion; does: Assertion; - witnessInputs(inputs: T extends Circuit ? ExtractInputs : never): AsyncAssertion; - witnessOutputs(outputs: T extends Circuit ? Partial> : never): AsyncAssertion; } } } diff --git a/src/utils/compare-utils.ts b/src/utils/compare-utils.ts new file mode 100644 index 0000000..532d60d --- /dev/null +++ b/src/utils/compare-utils.ts @@ -0,0 +1,66 @@ +import { NumberLike, Signals, Signal, CircuitZKit } from "@solarity/zkit"; + +import { flattenSignals, flattenSignal, stringifySignal } from "./utils"; + +export function outputSignalsCompare( + instance: any, + actualOutputSignals: Signals, + expectedOutputSignals: Signals | NumberLike[], + isStrict?: boolean, +) { + if (Array.isArray(expectedOutputSignals)) { + const actualOutputsArr: NumberLike[] = flattenSignals(actualOutputSignals); + + if ( + (isStrict && actualOutputsArr.length !== expectedOutputSignals.length) || + actualOutputsArr.length < expectedOutputSignals.length + ) { + throw new Error(`Expected ${actualOutputsArr.length} output signals, but got ${expectedOutputSignals.length}`); + } + + expectedOutputSignals.forEach((output: NumberLike, index: number) => { + instance.assert( + BigInt(output) === BigInt(actualOutputsArr[index]), + `Expected output signal with index "${index}" to be "${output}", but got "${actualOutputsArr[index]}"`, + `Expected output signal "${output}" NOT to be "${output}", but it is"`, + ); + }); + } else { + if (isStrict && Object.keys(actualOutputSignals).length !== Object.keys(expectedOutputSignals).length) { + throw new Error( + `Expected ${Object.keys(actualOutputSignals).length} output signals, but got ${Object.keys(expectedOutputSignals).length}`, + ); + } + + for (const output of Object.keys(expectedOutputSignals)) { + instance.assert( + compareSignals(actualOutputSignals[output], expectedOutputSignals[output]), + `Expected output signal "${output}" to be "${stringifySignal(expectedOutputSignals[output])}", but got "${stringifySignal(actualOutputSignals[output])}"`, + `Expected output signal "${output}" NOT to be "${stringifySignal(expectedOutputSignals[output])}", but it is"`, + ); + } + } +} + +export function checkCircuitZKit(circuitZKit: any, methodName: string) { + if (!(circuitZKit instanceof CircuitZKit)) { + throw new Error(`'${methodName}' is expected to be called on 'CircuitZKit'`); + } +} + +function compareSignals(actualSignal: Signal, expectedSignal: Signal): boolean { + const actualSignalValues: NumberLike[] = flattenSignal(actualSignal); + const expectedSignalValues: NumberLike[] = flattenSignal(expectedSignal); + + if (actualSignalValues.length !== expectedSignalValues.length) { + return false; + } + + for (let i = 0; i < actualSignalValues.length; i++) { + if (BigInt(actualSignalValues[i]) !== BigInt(expectedSignalValues[i])) { + return false; + } + } + + return true; +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..49ee4be --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from "./utils"; +export * from "./compare-utils"; diff --git a/src/utils.ts b/src/utils/utils.ts similarity index 85% rename from src/utils.ts rename to src/utils/utils.ts index 13deb13..1aa6fa6 100644 --- a/src/utils.ts +++ b/src/utils/utils.ts @@ -34,21 +34,10 @@ export function flattenSignals(signals: Signals): NumberLike[] { return flattenSignalsArr; } -export function compareSignals(actualSignal: Signal, expectedSignal: Signal): boolean { - const actualSignalValues: NumberLike[] = flattenSignal(actualSignal); - const expectedSignalValues: NumberLike[] = flattenSignal(expectedSignal); - - if (actualSignalValues.length !== expectedSignalValues.length) { - return false; - } - - for (let i = 0; i < actualSignalValues.length; i++) { - if (BigInt(actualSignalValues[i]) !== BigInt(expectedSignalValues[i])) { - return false; - } - } +export function flattenSignal(signal: Signal): NumberLike[] { + const flatValue = Array.isArray(signal) ? signal.flatMap((signal) => flattenSignal(signal)) : signal; - return true; + return Array.isArray(flatValue) ? flatValue : [flatValue]; } export function stringifySignal(signal: Signal): string { @@ -137,9 +126,3 @@ function countSignalDimensions(signal: Signal): number { return countSignalDimensions(signal[0]) + 1; } - -function flattenSignal(signal: Signal): NumberLike[] { - const flatValue = Array.isArray(signal) ? signal.flatMap((signal) => flattenSignal(signal)) : signal; - - return Array.isArray(flatValue) ? flatValue : [flatValue]; -} diff --git a/src/witness.ts b/src/witness.ts index 30f7f58..6ebd676 100644 --- a/src/witness.ts +++ b/src/witness.ts @@ -1,23 +1,22 @@ import { CircuitZKit, NumberLike, Signals } from "@solarity/zkit"; -import { compareSignals, flattenSignals, loadOutputs, stringifySignal } from "./utils"; +import { checkCircuitZKit, loadOutputs, outputSignalsCompare } from "./utils"; +import { STRICT_PROPERTY, WITNESS_INPUTS_METHOD, WITNESS_OUTPUTS_METHOD } from "./constants"; export function witness(chai: Chai.ChaiStatic, utils: Chai.ChaiUtils): void { - chai.Assertion.addProperty("strict", function (this: any) { + chai.Assertion.addProperty(STRICT_PROPERTY, function (this: any) { utils.flag(this, "strict", true); return this; }); - chai.Assertion.addMethod("witnessInputs", function (this: any, inputs: Signals) { + chai.Assertion.addMethod(WITNESS_INPUTS_METHOD, function (this: any, inputs: Signals) { const obj = utils.flag(this, "object"); - if (!(obj instanceof CircuitZKit)) { - throw new Error("`witnessInputs` is expected to be called on `CircuitZKit`"); - } + checkCircuitZKit(obj, WITNESS_INPUTS_METHOD); const promise = (this.then === undefined ? Promise.resolve() : this).then(async () => { - const witness = await (obj as CircuitZKit).calculateWitness(inputs); + const witness = await obj.calculateWitness(inputs); utils.flag(this, "inputs", inputs); utils.flag(this, "witness", witness); @@ -29,15 +28,11 @@ export function witness(chai: Chai.ChaiStatic, utils: Chai.ChaiUtils): void { return this; }); - chai.Assertion.addChainableMethod; - - chai.Assertion.addMethod("witnessOutputs", function (this: any, outputs: Signals | NumberLike[]) { + chai.Assertion.addMethod(WITNESS_OUTPUTS_METHOD, function (this: any, outputs: Signals | NumberLike[]) { const obj = utils.flag(this, "object"); const isStrict = utils.flag(this, "strict"); - if (!(obj instanceof CircuitZKit)) { - throw new Error("`witnessOutputs` is expected to be called on `CircuitZKit`"); - } + checkCircuitZKit(obj, WITNESS_OUTPUTS_METHOD); const promise = (this.then === undefined ? Promise.resolve() : this).then(async () => { const witness = utils.flag(this, "witness"); @@ -53,7 +48,7 @@ export function witness(chai: Chai.ChaiStatic, utils: Chai.ChaiUtils): void { const actual = loadOutputs(obj as CircuitZKit, witness, inputs); - witnessOutputsCompare(this, actual, outputs, isStrict); + outputSignalsCompare(this, actual, outputs, isStrict); }); this.then = promise.then.bind(promise); @@ -62,43 +57,3 @@ export function witness(chai: Chai.ChaiStatic, utils: Chai.ChaiUtils): void { return this; }); } - -function witnessOutputsCompare( - instance: any, - actualOutputs: Signals, - expectedOutputs: Signals | NumberLike[], - isStrict?: boolean, -) { - if (Array.isArray(expectedOutputs)) { - const actualOutputsArr: NumberLike[] = flattenSignals(actualOutputs); - - if ( - (isStrict && actualOutputsArr.length !== expectedOutputs.length) || - actualOutputsArr.length < expectedOutputs.length - ) { - throw new Error(`Expected ${actualOutputsArr.length} outputs, but got ${expectedOutputs.length}`); - } - - expectedOutputs.forEach((output: NumberLike, index: number) => { - instance.assert( - BigInt(output) === BigInt(actualOutputsArr[index]), - `Expected output with index "${index}" to be "${output}", but got "${actualOutputsArr[index]}"`, - `Expected output "${output}" NOT to be "${output}", but it is"`, - ); - }); - } else { - if (isStrict && Object.keys(actualOutputs).length !== Object.keys(expectedOutputs).length) { - throw new Error( - `Expected ${Object.keys(expectedOutputs).length} outputs, but got ${Object.keys(actualOutputs).length}`, - ); - } - - for (const output of Object.keys(expectedOutputs)) { - instance.assert( - compareSignals(actualOutputs[output], expectedOutputs[output]), - `Expected output "${output}" to be "${stringifySignal(expectedOutputs[output])}", but got "${stringifySignal(actualOutputs[output])}"`, - `Expected output "${output}" NOT to be "${stringifySignal(expectedOutputs[output])}", but it is"`, - ); - } - } -} diff --git a/test/chai-zkit.test.ts b/test/chai-zkit.test.ts deleted file mode 100644 index 5fbc01c..0000000 --- a/test/chai-zkit.test.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { expect } from "chai"; - -import * as fs from "fs"; -import path from "path"; - -import { NumberLike, CircuitZKit } from "@solarity/zkit"; - -import { useFixtureProject } from "./helpers"; - -import "../src"; - -import { Matrix, NoInputs } from "./fixture-projects/complex-circuits/generated-types/zkit"; - -describe("chai-zkit", () => { - function getArtifactsFullPath(circuitDirSourceName: string): string { - return path.join(process.cwd(), "zkit", "artifacts", "circuits", circuitDirSourceName); - } - - function getVerifiersDirFullPath(): string { - return path.join(process.cwd(), "contracts", "verifiers"); - } - - let baseMatrix: CircuitZKit; - let matrix: Matrix; - - useFixtureProject("complex-circuits"); - - beforeEach(() => { - const circuitName = "Matrix"; - const circuitArtifactsPath = getArtifactsFullPath(`${circuitName}.circom`); - const verifierDirPath = getVerifiersDirFullPath(); - - baseMatrix = new CircuitZKit({ circuitName, circuitArtifactsPath, verifierDirPath }); - matrix = new Matrix({ circuitName, circuitArtifactsPath, verifierDirPath }); - }); - - describe("witness", () => { - let a: NumberLike[][]; - let b: NumberLike[][]; - let c: NumberLike; - let d: string[][]; - let e: string[][]; - let f: string; - - beforeEach(() => { - a = [ - ["1", "2", "3"], - ["4", "5", "6"], - ["7", "8", "9"], - ]; - b = [ - ["1", "2", "3"], - ["4", "5", "6"], - ["7", "8", "9"], - ]; - c = "1"; - d = [ - ["2", "5", "0"], - ["17", "26", "0"], - ["0", "0", "0"], - ]; - e = [ - ["1", "4", "0"], - ["16", "25", "0"], - ["0", "0", "0"], - ]; - f = "1"; - }); - - describe("witnessInputs", () => { - it("should not pass if not called on zkit", async () => { - /// @ts-ignore - expect(() => expect(1).to.have.witnessInputs({ d, e })).to.throw( - "`witnessInputs` is expected to be called on `CircuitZKit`", - ); - }); - - it("should pass if multiple witnessInputs", async () => { - await expect(matrix) - .with.witnessInputs({ a, b, c: "1337" }) - .with.witnessInputs({ a, b, c }) - .to.have.witnessOutputs({ - d, - e: [ - ["1", "4", "0"], - ["16", "25", "0"], - ["0", "0", "0"], - ], - f: "0x1", - }); - }); - }); - - describe("witnessOutputs", () => { - it("should not pass if not called on zkit", async () => { - /// @ts-ignore - expect(() => expect(1).to.have.witnessOutputs({ d, e })).to.throw( - "`witnessOutputs` is expected to be called on `CircuitZKit`", - ); - }); - - it("should not pass if called not before witnessInputs", async () => { - await expect(expect(matrix).to.have.witnessOutputs({ d, e })).to.be.rejectedWith( - "`witnessOutputs` is expected to be called after `witnessInputs`", - ); - }); - - it("should not pass if no inputs", async () => { - const circuitName = "NoInputs"; - const circuitArtifactsPath = getArtifactsFullPath(`${circuitName}.circom`); - const verifierDirPath = getVerifiersDirFullPath(); - - const noInputs = new NoInputs({ circuitName, circuitArtifactsPath, verifierDirPath }); - - await expect( - expect(noInputs) - .with.witnessInputs({} as any) - .to.have.witnessOutputs({ c: "1337" }), - ).to.be.rejectedWith("Circuit must have at least one input to extract outputs"); - }); - - it("should not pass if outputs are incorrect for given inputs", async () => { - e = d; - - await expect(expect(matrix).with.witnessInputs({ a, b, c }).to.have.witnessOutputs({ e })).to.be.rejectedWith( - `Expected output "e" to be "[[2,5,0],[17,26,0],[0,0,0]]", but got "[[1,4,0],[16,25,0],[0,0,0]]"`, - ); - }); - - it("should not pass if negated but outputs are correct", async () => { - await expect( - expect(matrix).with.witnessInputs({ a, b, c }).to.not.have.witnessOutputs({ d }), - ).to.be.rejectedWith(`Expected output "d" NOT to be "[[2,5,0],[17,26,0],[0,0,0]]", but it is"`); - }); - - it("should not pass if sym file is missing input signals", async () => { - const circuitArtifactsPath = getArtifactsFullPath(`${matrix.getCircuitName()}.circom`); - const symFile = path.join(circuitArtifactsPath, `${matrix.getCircuitName()}.sym`); - - fs.rmSync(symFile); - fs.writeFileSync(symFile, ""); - - await expect( - expect(matrix).with.witnessInputs({ a, b, c }).to.not.have.witnessOutputs({ d }), - ).to.be.rejectedWith("Sym file is missing input signals"); - }); - - it("should not pass if not the same amount of outputs and strict", async () => { - await expect( - expect(matrix).with.witnessInputs({ a, b, c }).to.have.strict.witnessOutputs({ d }), - ).to.be.rejectedWith("Expected 1 outputs, but got 3"); - }); - - it("should not pass if pass output arr with invalid length", async () => { - await expect( - expect(matrix) - .with.witnessInputs({ a, b, c }) - .to.have.witnessOutputs({ d: [["123"]] }), - ).to.be.rejectedWith(`Expected output "d" to be "[[123]]", but got "[[2,5,0],[17,26,0],[0,0,0]]"`); - }); - - it("should not pass if not the same amount of outputs and strict and base CircuitZKit object", async () => { - const wrongOutputs: string[] = ["2", "0x5", "0", "17", "26", "0"]; - - await expect( - expect(baseMatrix).with.witnessInputs({ a, b, c }).to.have.strict.witnessOutputs(wrongOutputs), - ).to.be.rejectedWith(`Expected 19 outputs, but got ${wrongOutputs.length}`); - }); - - it("should not pass for base CircuitZKit object and expected outputs length bigger than actual", async () => { - const wrongOutputs: string[] = [ - "2", - "0x5", - "0", - "17", - "26", - "0", - "0", - "0", - "0", - "1", - "4", - "0", - "16", - "25", - "0", - "0", - "0", - "0", - "1", - "1", - ]; - - await expect( - expect(baseMatrix).with.witnessInputs({ a, b, c }).to.have.witnessOutputs(wrongOutputs), - ).to.be.rejectedWith(`Expected 19 outputs, but got ${wrongOutputs.length}`); - }); - - it("should pass if not the same amount of outputs and not strict", async () => { - await expect(matrix).with.witnessInputs({ a, b, c }).to.have.witnessOutputs({ d }); - }); - - it("should pass if outputs are correct for given inputs", async () => { - await expect(matrix).with.witnessInputs({ a, b, c }).to.have.witnessOutputs({ d, e }); - }); - - it("should pass if outputs are correct for given inputs and not all outputs passed", async () => { - await expect(matrix).with.witnessInputs({ a, b, c }).to.have.witnessOutputs({ d }); - }); - - it("should pass for base CircuitZKit object", async () => { - await expect(baseMatrix) - .with.witnessInputs({ a, b, c }) - .to.have.witnessOutputs([ - "2", - "0x5", - "0", - "17", - "26", - "0", - "0", - "0", - "0", - "1", - "4", - "0", - "16", - "25", - "0", - "0", - "0", - "0", - "1", - ]); - }); - }); - }); -}); diff --git a/test/fixture-projects/complex-circuits/generated-types/zkit/core/Matrix.ts b/test/fixture-projects/complex-circuits/generated-types/zkit/core/Matrix.ts index b2c2deb..3154b94 100644 --- a/test/fixture-projects/complex-circuits/generated-types/zkit/core/Matrix.ts +++ b/test/fixture-projects/complex-circuits/generated-types/zkit/core/Matrix.ts @@ -5,6 +5,8 @@ import { CircuitZKit, CircuitZKitConfig, Groth16Proof, NumberLike, NumericString, PublicSignals } from "@solarity/zkit"; +import { normalizePublicSignals, denormalizePublicSignals } from "../utils"; + export type PrivateMatrix = { a: NumberLike[][]; b: NumberLike[][]; @@ -12,10 +14,10 @@ export type PrivateMatrix = { }; export type PublicMatrix = { - d: NumericString[][]; - e: NumericString[][]; - f: NumericString; - a: NumericString[][]; + d: NumberLike[][]; + e: NumberLike[][]; + f: NumberLike; + a: NumberLike[][]; }; export type ProofMatrix = { @@ -27,7 +29,36 @@ export type Calldata = [ [NumericString, NumericString], [[NumericString, NumericString], [NumericString, NumericString]], [NumericString, NumericString], - [NumericString, NumericString, NumericString, NumericString], + [ + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + NumericString, + ], ]; export class Matrix extends CircuitZKit { @@ -45,18 +76,18 @@ export class Matrix extends CircuitZKit { } public async calculateWitness(inputs: PrivateMatrix): Promise { - return await super.calculateWitness(inputs as any); + return super.calculateWitness(inputs as any); } public async verifyProof(proof: ProofMatrix): Promise { - return await super.verifyProof({ + return super.verifyProof({ proof: proof.proof, publicSignals: this._denormalizePublicSignals(proof.publicSignals), }); } public async generateCalldata(proof: ProofMatrix): Promise { - return await super.generateCalldata({ + return super.generateCalldata({ proof: proof.proof, publicSignals: this._denormalizePublicSignals(proof.publicSignals), }); @@ -66,19 +97,27 @@ export class Matrix extends CircuitZKit { return ["d", "e", "f", "a"]; } - private _normalizePublicSignals(publicSignals: PublicSignals): PublicMatrix { - const signalNames = this.getSignalNames(); + public getSignalDimensions(name: string): number[] { + switch (name) { + case "d": + return [3, 3]; + case "e": + return [3, 3]; + case "f": + return []; + case "a": + return [3, 3]; + default: + throw new Error(`Unknown signal name: ${name}`); + } + } - return signalNames.reduce((acc: any, signalName, index) => { - acc[signalName] = publicSignals[index]; - return acc; - }, {}); + private _normalizePublicSignals(publicSignals: PublicSignals): PublicMatrix { + return normalizePublicSignals(publicSignals, this.getSignalNames(), this.getSignalDimensions); } private _denormalizePublicSignals(publicSignals: PublicMatrix): PublicSignals { - const signalNames = this.getSignalNames(); - - return signalNames.map((signalName) => (publicSignals as any)[signalName]); + return denormalizePublicSignals(publicSignals, this.getSignalNames()); } } diff --git a/test/fixture-projects/complex-circuits/generated-types/zkit/core/NoInputs.ts b/test/fixture-projects/complex-circuits/generated-types/zkit/core/NoInputs.ts index 819c9a0..25b1f26 100644 --- a/test/fixture-projects/complex-circuits/generated-types/zkit/core/NoInputs.ts +++ b/test/fixture-projects/complex-circuits/generated-types/zkit/core/NoInputs.ts @@ -5,10 +5,12 @@ import { CircuitZKit, CircuitZKitConfig, Groth16Proof, NumberLike, NumericString, PublicSignals } from "@solarity/zkit"; +import { normalizePublicSignals, denormalizePublicSignals } from "../utils"; + export type PrivateNoInputs = {}; export type PublicNoInputs = { - c: NumericString; + c: NumberLike; }; export type ProofNoInputs = { @@ -38,18 +40,18 @@ export class NoInputs extends CircuitZKit { } public async calculateWitness(inputs: PrivateNoInputs): Promise { - return await super.calculateWitness(inputs as any); + return super.calculateWitness(inputs as any); } public async verifyProof(proof: ProofNoInputs): Promise { - return await super.verifyProof({ + return super.verifyProof({ proof: proof.proof, publicSignals: this._denormalizePublicSignals(proof.publicSignals), }); } public async generateCalldata(proof: ProofNoInputs): Promise { - return await super.generateCalldata({ + return super.generateCalldata({ proof: proof.proof, publicSignals: this._denormalizePublicSignals(proof.publicSignals), }); @@ -59,19 +61,21 @@ export class NoInputs extends CircuitZKit { return ["c"]; } - private _normalizePublicSignals(publicSignals: PublicSignals): PublicNoInputs { - const signalNames = this.getSignalNames(); + public getSignalDimensions(name: string): number[] { + switch (name) { + case "c": + return []; + default: + throw new Error(`Unknown signal name: ${name}`); + } + } - return signalNames.reduce((acc: any, signalName, index) => { - acc[signalName] = publicSignals[index]; - return acc; - }, {}); + private _normalizePublicSignals(publicSignals: PublicSignals): PublicNoInputs { + return normalizePublicSignals(publicSignals, this.getSignalNames(), this.getSignalDimensions); } private _denormalizePublicSignals(publicSignals: PublicNoInputs): PublicSignals { - const signalNames = this.getSignalNames(); - - return signalNames.map((signalName) => (publicSignals as any)[signalName]); + return denormalizePublicSignals(publicSignals, this.getSignalNames()); } } diff --git a/test/fixture-projects/complex-circuits/generated-types/zkit/utils.ts b/test/fixture-projects/complex-circuits/generated-types/zkit/utils.ts new file mode 100644 index 0000000..edd1fdb --- /dev/null +++ b/test/fixture-projects/complex-circuits/generated-types/zkit/utils.ts @@ -0,0 +1,44 @@ +import { PublicSignals } from "@solarity/zkit"; + +export function normalizePublicSignals( + publicSignals: any[], + signalNames: string[], + getSignalDimensions: (name: string) => number[], +): any { + let index = 0; + return signalNames.reduce((acc: any, signalName) => { + const dimensions = getSignalDimensions(signalName); + const size = dimensions.reduce((a, b) => a * b, 1); + + acc[signalName] = reshape(publicSignals.slice(index, index + size), dimensions); + index += size; + + return acc; + }, {}); +} + +export function denormalizePublicSignals(publicSignals: any, signalNames: string[]): PublicSignals { + return signalNames.reduce((acc: any[], signalName) => { + return acc.concat(flatten(publicSignals[signalName])); + }, []); +} + +function reshape(array: number[], dimensions: number[]): any { + if (dimensions.length === 0) { + return array[0]; + } + + const [first, ...rest] = dimensions; + const size = rest.reduce((a, b) => a * b, 1); + + const result = []; + for (let i = 0; i < first; i++) { + result.push(reshape(array.slice(i * size, (i + 1) * size), rest)); + } + + return result; +} + +function flatten(array: any): number[] { + return Array.isArray(array) ? array.flatMap((array) => flatten(array)) : array; +} diff --git a/test/fixture-projects/complex-circuits/package.json b/test/fixture-projects/complex-circuits/package.json index 2d48f4b..d19c8ce 100644 --- a/test/fixture-projects/complex-circuits/package.json +++ b/test/fixture-projects/complex-circuits/package.json @@ -4,7 +4,7 @@ "scripts": { "compile": "npm run zkit-compile && npm run zkit-verifiers && npm run solidity-compile", "solidity-compile": "npx hardhat compile --force", - "zkit-compile": "npx hardhat zkit:compile", + "zkit-compile": "npx hardhat zkit:compile --force", "zkit-verifiers": "npx hardhat zkit:verifiers" } } diff --git a/test/helpers.ts b/test/helpers.ts index 01b8591..a340bc6 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -10,7 +10,7 @@ export function useFixtureProject(fixtureProjectName: string, networkName = "har this.hre = require("hardhat"); await this.hre.run("compile", { quiet: true }); - await this.hre.run("zkit:compile", { quiet: true, sym: true }); + await this.hre.run("zkit:compile", { quiet: true }); await this.hre.run("zkit:verifiers", { quiet: true }); }); diff --git a/test/proof.test.ts b/test/proof.test.ts new file mode 100644 index 0000000..8118c19 --- /dev/null +++ b/test/proof.test.ts @@ -0,0 +1,190 @@ +import { expect } from "chai"; +import path from "path"; + +import { NumberLike, CircuitZKit } from "@solarity/zkit"; + +import { useFixtureProject } from "./helpers"; + +import "../src"; + +import { Matrix } from "./fixture-projects/complex-circuits/generated-types/zkit"; + +describe("proof", () => { + let a: NumberLike[][]; + let b: NumberLike[][]; + let c: NumberLike; + let d: NumberLike[][]; + let e: NumberLike[][]; + let f: NumberLike; + + let baseMatrix: CircuitZKit; + let matrix: Matrix; + + function getArtifactsFullPath(circuitDirSourceName: string): string { + return path.join(process.cwd(), "zkit", "artifacts", "circuits", circuitDirSourceName); + } + + function getVerifiersDirFullPath(): string { + return path.join(process.cwd(), "contracts", "verifiers"); + } + + useFixtureProject("complex-circuits"); + + beforeEach(() => { + const circuitName = "Matrix"; + const circuitArtifactsPath = getArtifactsFullPath(`${circuitName}.circom`); + const verifierDirPath = getVerifiersDirFullPath(); + + baseMatrix = new CircuitZKit({ circuitName, circuitArtifactsPath, verifierDirPath }); + matrix = new Matrix({ circuitName, circuitArtifactsPath, verifierDirPath }); + + a = [ + ["1", "2", "3"], + ["4", "5", "6"], + ["7", "8", "9"], + ]; + b = [ + ["1", "2", "3"], + ["4", "5", "6"], + ["7", "8", "9"], + ]; + c = "1"; + d = [ + ["2", "5", "0"], + ["17", "26", "0"], + ["0", "0", "0"], + ]; + e = [ + ["1", "4", "0"], + ["16", "25", "0"], + ["0", "0", "0"], + ]; + f = "1"; + }); + + describe("generateProof", () => { + it("should not pass if not called on zkit", async () => { + /// @ts-ignore + expect(() => expect(1).to.generateProof({ d, e })).to.throw( + "'generateProof' is expected to be called on 'CircuitZKit'", + ); + }); + + it("should not pass if pass invalid inputs", async () => { + await expect(expect(matrix).to.generateProof({ a, b } as any)).to.be.rejectedWith( + "Expected proof generation to be successful, but it isn't", + ); + }); + + it("should not pass if pass valid inputs with negation", async () => { + await expect(expect(matrix).to.not.generateProof({ a, b, c })).to.be.rejectedWith( + "Expected proof generation NOT to be successful, but it is", + ); + }); + + it("should correctly generate proof", async () => { + await expect(matrix).to.generateProof({ a, b, c }); + }); + + it("should pass with invalid inputs and negation", async () => { + await expect(matrix).to.not.generateProof({ a, b } as any); + }); + + it("should pass if multiple generate proof", async () => { + await expect(matrix).to.generateProof({ a, b, c }).and.generateProof({ a, b, c }); + }); + }); + + describe("useSolidityVerifier", () => { + it("should not pass if not called on zkit", async () => { + /// @ts-ignore + expect(() => expect(1).to.useSolidityVerifier({ a: 1 })).to.throw( + "'useSolidityVerifier' is expected to be called on 'CircuitZKit'", + ); + }); + }); + + describe("verifyProof", () => { + it("should not pass if not called on zkit", async () => { + /// @ts-ignore + expect(() => expect(1).to.have.verifyProof({ a })).to.throw( + "'verifyProof' is expected to be called on 'CircuitZKit'", + ); + }); + + it("should not pass if pass invalid proof without negation", async () => { + const proof = await matrix.generateProof({ a, b, c }); + + proof.publicSignals.f = "30"; + + await expect(expect(matrix).to.verifyProof(proof)).to.be.rejectedWith( + "Expected proof verification result to be true, but it isn't", + ); + }); + + it("should not pass if pass valid proof with negation", async () => { + const proof = await matrix.generateProof({ a, b, c }); + + await expect(expect(matrix).to.not.verifyProof(proof)).to.be.rejectedWith( + "Expected proof verification result NOT to be true, but it is", + ); + }); + + it("should not pass if pass valid proof with solidity contract verifier with negation", async function () { + const matrixVerifier = await this.hre.ethers.deployContract("MatrixVerifier"); + const proof = await matrix.generateProof({ a, b, c }); + + await expect(expect(matrix).to.useSolidityVerifier(matrixVerifier).and.not.verifyProof(proof)).to.be.rejectedWith( + "Expected proof verification result NOT to be true, but it is", + ); + }); + + it("should not pass if pass invalid proof with solidity contract verifier without negation", async function () { + const matrixVerifier = await this.hre.ethers.deployContract("MatrixVerifier"); + const proof = await matrix.generateProof({ a, b, c }); + + proof.publicSignals.f = "30"; + + await expect(expect(matrix).to.useSolidityVerifier(matrixVerifier).and.verifyProof(proof)).to.be.rejectedWith( + "Expected proof verification result to be true, but it isn't", + ); + }); + + it("should correctly verify proof without negation", async () => { + const proof = await matrix.generateProof({ a, b, c }); + + await expect(matrix).to.verifyProof(proof); + }); + + it("should correctly verify proof with solidity contract verifier", async function () { + const matrixVerifier = await this.hre.ethers.deployContract("MatrixVerifier"); + const proof = await matrix.generateProof({ a, b, c }); + + await expect(matrix).to.useSolidityVerifier(matrixVerifier).and.verifyProof(proof); + }); + + it("should correctly verify proof several times", async () => { + const proof = await matrix.generateProof({ a, b, c }); + const proof2 = await matrix.generateProof({ a, b, c }); + + await expect(matrix).to.verifyProof(proof).and.verifyProof(proof2); + }); + + it("should correctly verify proof with negation", async () => { + const proof = await matrix.generateProof({ a, b, c }); + + proof.publicSignals.f = "30"; + + await expect(matrix).to.not.verifyProof(proof); + }); + + it("should correctly verify proof with solidity contract verifier and negation", async function () { + const matrixVerifier = await this.hre.ethers.deployContract("MatrixVerifier"); + const proof = await matrix.generateProof({ a, b, c }); + + proof.publicSignals.f = "30"; + + await expect(matrix).to.useSolidityVerifier(matrixVerifier).and.not.verifyProof(proof); + }); + }); +}); diff --git a/test/witness.test.ts b/test/witness.test.ts new file mode 100644 index 0000000..6211ef4 --- /dev/null +++ b/test/witness.test.ts @@ -0,0 +1,200 @@ +import { expect } from "chai"; + +import * as fs from "fs"; +import path from "path"; + +import { NumberLike, CircuitZKit, Signals } from "@solarity/zkit"; + +import { useFixtureProject } from "./helpers"; + +import "../src"; + +import { Matrix, NoInputs } from "./fixture-projects/complex-circuits/generated-types/zkit"; +import { flattenSignals } from "../src/utils"; + +describe("witness", () => { + let a: NumberLike[][]; + let b: NumberLike[][]; + let c: NumberLike; + let d: NumberLike[][]; + let e: NumberLike[][]; + let f: NumberLike; + + let baseMatrix: CircuitZKit; + let matrix: Matrix; + + function getArtifactsFullPath(circuitDirSourceName: string): string { + return path.join(process.cwd(), "zkit", "artifacts", "circuits", circuitDirSourceName); + } + + function getVerifiersDirFullPath(): string { + return path.join(process.cwd(), "contracts", "verifiers"); + } + + function getSignalsArr(signals: Signals): string[] { + return flattenSignals(signals).map((val: NumberLike) => val.toString()); + } + + useFixtureProject("complex-circuits"); + + beforeEach(() => { + const circuitName = "Matrix"; + const circuitArtifactsPath = getArtifactsFullPath(`${circuitName}.circom`); + const verifierDirPath = getVerifiersDirFullPath(); + + baseMatrix = new CircuitZKit({ circuitName, circuitArtifactsPath, verifierDirPath }); + matrix = new Matrix({ circuitName, circuitArtifactsPath, verifierDirPath }); + + a = [ + ["1", "2", "3"], + ["4", "5", "6"], + ["7", "8", "9"], + ]; + b = [ + ["1", "2", "3"], + ["4", "5", "6"], + ["7", "8", "9"], + ]; + c = "1"; + d = [ + ["2", "5", "0"], + ["17", "26", "0"], + ["0", "0", "0"], + ]; + e = [ + ["1", "4", "0"], + ["16", "25", "0"], + ["0", "0", "0"], + ]; + f = "1"; + }); + + describe("witnessInputs", () => { + it("should not pass if not called on zkit", async () => { + /// @ts-ignore + expect(() => expect(1).to.have.witnessInputs({ d, e })).to.throw( + "'witnessInputs' is expected to be called on 'CircuitZKit'", + ); + }); + + it("should pass if multiple witnessInputs", async () => { + await expect(matrix) + .with.witnessInputs({ a, b, c: "1337" }) + .with.witnessInputs({ a, b, c }) + .to.have.witnessOutputs({ + d, + e: [ + ["1", "4", "0"], + ["16", "25", "0"], + ["0", "0", "0"], + ], + f: "0x1", + }); + }); + }); + + describe("witnessOutputs", () => { + it("should not pass if not called on zkit", async () => { + /// @ts-ignore + expect(() => expect(1).to.have.witnessOutputs({ d, e })).to.throw( + "'witnessOutputs' is expected to be called on 'CircuitZKit'", + ); + }); + + it("should not pass if called not after witnessInputs", async () => { + await expect(expect(matrix).to.have.witnessOutputs({ d, e })).to.be.rejectedWith( + "`witnessOutputs` is expected to be called after `witnessInputs`", + ); + }); + + it("should not pass if no inputs", async () => { + const circuitName = "NoInputs"; + const circuitArtifactsPath = getArtifactsFullPath(`${circuitName}.circom`); + const verifierDirPath = getVerifiersDirFullPath(); + + const noInputs = new NoInputs({ circuitName, circuitArtifactsPath, verifierDirPath }); + + await expect( + expect(noInputs) + .with.witnessInputs({} as any) + .to.have.witnessOutputs({ c: "1337" }), + ).to.be.rejectedWith("Circuit must have at least one input to extract outputs"); + }); + + it("should not pass if outputs are incorrect for given inputs", async () => { + e = d; + + await expect(expect(matrix).with.witnessInputs({ a, b, c }).to.have.witnessOutputs({ e })).to.be.rejectedWith( + `Expected output signal "e" to be "[[2,5,0],[17,26,0],[0,0,0]]", but got "[[1,4,0],[16,25,0],[0,0,0]]"`, + ); + }); + + it("should not pass if negated but outputs are correct", async () => { + await expect(expect(matrix).with.witnessInputs({ a, b, c }).to.not.have.witnessOutputs({ d })).to.be.rejectedWith( + `Expected output signal "d" NOT to be "[[2,5,0],[17,26,0],[0,0,0]]", but it is"`, + ); + }); + + it("should not pass if sym file is missing input signals", async () => { + const circuitArtifactsPath = getArtifactsFullPath(`${matrix.getCircuitName()}.circom`); + const symFile = path.join(circuitArtifactsPath, `${matrix.getCircuitName()}.sym`); + + const fileContent: string = fs.readFileSync(symFile, "utf-8"); + + fs.rmSync(symFile); + fs.writeFileSync(symFile, ""); + + await expect(expect(matrix).with.witnessInputs({ a, b, c }).to.not.have.witnessOutputs({ d })).to.be.rejectedWith( + "Sym file is missing input signals", + ); + + fs.writeFileSync(symFile, fileContent); + }); + + it("should not pass if not the same amount of outputs and strict", async () => { + await expect( + expect(matrix).with.witnessInputs({ a, b, c }).to.have.strict.witnessOutputs({ d }), + ).to.be.rejectedWith("Expected 3 output signals, but got 1"); + }); + + it("should not pass if pass output arr with invalid length", async () => { + await expect( + expect(matrix) + .with.witnessInputs({ a, b, c }) + .to.have.witnessOutputs({ d: [["123"]] }), + ).to.be.rejectedWith(`Expected output signal "d" to be "[[123]]", but got "[[2,5,0],[17,26,0],[0,0,0]]"`); + }); + + it("should not pass if not the same amount of outputs and strict and base CircuitZKit object", async () => { + const wrongOutputs: string[] = ["2", "0x5", "0", "17", "26", "0"]; + + await expect( + expect(baseMatrix).with.witnessInputs({ a, b, c }).to.have.strict.witnessOutputs(wrongOutputs), + ).to.be.rejectedWith(`Expected 19 output signals, but got ${wrongOutputs.length}`); + }); + + it("should not pass for base CircuitZKit object and expected outputs length bigger than actual", async () => { + const wrongOutputs: string[] = getSignalsArr({ d, e, f, c }); + + await expect( + expect(baseMatrix).with.witnessInputs({ a, b, c }).to.have.witnessOutputs(wrongOutputs), + ).to.be.rejectedWith(`Expected 19 output signals, but got ${wrongOutputs.length}`); + }); + + it("should pass if not the same amount of outputs and not strict", async () => { + await expect(matrix).with.witnessInputs({ a, b, c }).to.have.witnessOutputs({ d }); + }); + + it("should pass if outputs are correct for given inputs", async () => { + await expect(matrix).with.witnessInputs({ a, b, c }).to.have.witnessOutputs({ d, e }); + }); + + it("should pass if outputs are correct for given inputs and not all outputs passed", async () => { + await expect(matrix).with.witnessInputs({ a, b, c }).to.have.witnessOutputs({ d }); + }); + + it("should pass for base CircuitZKit object", async () => { + await expect(baseMatrix).with.witnessInputs({ a, b, c }).to.have.witnessOutputs(getSignalsArr({ d, e, f })); + }); + }); +});