diff --git a/apps/circuit-compiler/src/app/services/circomPluginClient.ts b/apps/circuit-compiler/src/app/services/circomPluginClient.ts index e97609fbac..0eccddb281 100644 --- a/apps/circuit-compiler/src/app/services/circomPluginClient.ts +++ b/apps/circuit-compiler/src/app/services/circomPluginClient.ts @@ -4,7 +4,7 @@ import EventManager from 'events' import pathModule from 'path' import { parse, compile, generate_witness, generate_r1cs, compiler_list } from 'circom_wasm' import { extractNameFromKey, extractParentFromKey } from '@remix-ui/helper' -import { CompilationConfig, CompilerReport } from '../types' +import { CompilationConfig, CompilerReport, ResolverOutput } from '../types' export class CircomPluginClient extends PluginClient { public internalEvents: EventManager @@ -37,10 +37,7 @@ export class CircomPluginClient extends PluginClient { // @ts-ignore fileContent = await this.call('fileManager', 'readFile', path) } - this.lastParsedFiles = { - [path]: fileContent, - } - this.lastParsedFiles = await this.resolveDependencies(path, fileContent, this.lastParsedFiles) + this.lastParsedFiles = await this.resolveDependencies(path, fileContent, { [path]: { content: fileContent, parent: null } }) const parsedOutput = parse(path, this.lastParsedFiles) try { @@ -211,14 +208,13 @@ export class CircomPluginClient extends PluginClient { this.internalEvents.emit('circuit_computing_witness_done') } - async resolveDependencies(filePath: string, fileContent: string, output = {}, depPath: string = '', blackPath: string[] = []): Promise> { + async resolveDependencies(filePath: string, fileContent: string, output: ResolverOutput = {}, depPath: string = '', parent: string = ''): Promise> { // extract all includes const includes = (fileContent.match(/include ['"].*['"]/g) || []).map((include) => include.replace(/include ['"]/g, '').replace(/['"]/g, '')) await Promise.all( includes.map(async (include) => { // fix for endless recursive includes - if (blackPath.includes(include)) return let dependencyContent = '' let path = include // @ts-ignore @@ -268,20 +264,43 @@ export class CircomPluginClient extends PluginClient { } } } - // extract all includes from the dependency content - const dependencyIncludes = (dependencyContent.match(/include ['"].*['"]/g) || []).map((include) => include.replace(/include ['"]/g, '').replace(/['"]/g, '')) - - blackPath.push(include) - // recursively resolve all dependencies of the dependency - if (dependencyIncludes.length > 0) { - await this.resolveDependencies(filePath, dependencyContent, output, path, blackPath) - output[include] = dependencyContent + const fileNameToInclude = extractNameFromKey(include) + const similarFile = Object.keys(output).find(path => { + return path.indexOf(fileNameToInclude) > -1 + }) + const isDuplicateContent = similarFile && output[similarFile] ? output[similarFile].content === dependencyContent : false + + if (output[include] && output[include].parent) { + // if include import already exists, remove the include import from the parent file + const regexPattern = new RegExp(`include ['"]${include}['"];`, 'g') + + output[output[include].parent].content = output[output[include].parent].content.replace(regexPattern, "") + } else if (isDuplicateContent) { + // if include import has the same content as another file, replace the include import with the file name of the other file (similarFile) + if (output[similarFile].parent) output[output[similarFile].parent].content = output[output[similarFile].parent].content.replace(similarFile, include) + if (include !== similarFile) { + output[include] = output[similarFile] + delete output[similarFile] + } } else { - output[include] = dependencyContent + // extract all includes from the dependency content + const dependencyIncludes = (dependencyContent.match(/include ['"].*['"]/g) || []).map((include) => include.replace(/include ['"]/g, '').replace(/['"]/g, '')) + + output[include] = { + content: dependencyContent, + parent + } + // recursively resolve all dependencies of the dependency + if (dependencyIncludes.length > 0) await this.resolveDependencies(filePath, dependencyContent, output, path, include) } }) ) - return output + const result: Record = {} + + Object.keys(output).forEach((key) => { + result[key] = output[key].content + }) + return result } async resolveReportPath (path: string): Promise { diff --git a/apps/circuit-compiler/src/app/types/index.ts b/apps/circuit-compiler/src/app/types/index.ts index 029b8bea9e..71e1dbfced 100644 --- a/apps/circuit-compiler/src/app/types/index.ts +++ b/apps/circuit-compiler/src/app/types/index.ts @@ -84,4 +84,11 @@ export type CompileOptionsProps = { setCircuitHideWarnings: (value: boolean) => void, autoCompile: boolean, hideWarnings: boolean +} + +export type ResolverOutput = { + [name: string]: { + content: string, + parent: string + } } \ No newline at end of file diff --git a/apps/remix-ide/src/app/tabs/locales/en/filePanel.json b/apps/remix-ide/src/app/tabs/locales/en/filePanel.json index 6b3b8524d0..a645dfc06c 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/filePanel.json +++ b/apps/remix-ide/src/app/tabs/locales/en/filePanel.json @@ -105,6 +105,7 @@ "filePanel.pausable": "Pausable", "filePanel.semaphore": "Semaphore", "filePanel.hashchecker": "Hash Checker", + "filePanel.rln": "Rate-Limiting Nullifier", "filePanel.transparent": "Transparent", "filePanel.initGitRepoTitle": "Check option to initialize workspace as a new git repository", "filePanel.switchToBranchTitle1": "Checkout new branch from remote branch", diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts index e2d4ed4658..84adb0741d 100644 --- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts @@ -176,7 +176,7 @@ export const createWorkspace = async ( const isActive = await plugin.call('manager', 'isActive', 'dgit') if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit') } - if (workspaceTemplateName === 'semaphore' || workspaceTemplateName === 'hashchecker') { + if (workspaceTemplateName === 'semaphore' || workspaceTemplateName === 'hashchecker' || workspaceTemplateName === 'rln') { const isCircomActive = await plugin.call('manager', 'isActive', 'circuit-compiler') if (!isCircomActive) await plugin.call('manager', 'activatePlugin', 'circuit-compiler') } diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx index 1e203d994d..ba0ab4c3da 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -753,6 +753,9 @@ export function Workspace() { + diff --git a/libs/remix-ui/workspace/src/lib/types/index.ts b/libs/remix-ui/workspace/src/lib/types/index.ts index b49fd94aba..0129232331 100644 --- a/libs/remix-ui/workspace/src/lib/types/index.ts +++ b/libs/remix-ui/workspace/src/lib/types/index.ts @@ -17,7 +17,7 @@ export interface JSONStandardInput { } } export type MenuItems = action[] -export type WorkspaceTemplate = 'gist-template' | 'code-template' | 'remixDefault' | 'blank' | 'ozerc20' | 'zeroxErc20' | 'ozerc721' | 'playground' | 'semaphore' | 'hashchecker' +export type WorkspaceTemplate = 'gist-template' | 'code-template' | 'remixDefault' | 'blank' | 'ozerc20' | 'zeroxErc20' | 'ozerc721' | 'playground' | 'semaphore' | 'hashchecker' | 'rln' export interface WorkspaceProps { plugin: FilePanelType } diff --git a/libs/remix-ui/workspace/src/lib/utils/constants.ts b/libs/remix-ui/workspace/src/lib/utils/constants.ts index 716652df6d..df0265fd26 100644 --- a/libs/remix-ui/workspace/src/lib/utils/constants.ts +++ b/libs/remix-ui/workspace/src/lib/utils/constants.ts @@ -83,5 +83,6 @@ export const TEMPLATE_NAMES = { 'gnosisSafeMultisig': 'Gnosis Safe', 'playground': 'Playground', 'semaphore': 'Semaphore', - 'hashchecker': 'Hash Checker' + 'hashchecker': 'Hash Checker', + 'rln': 'Rate-Limiting Nullifier' } diff --git a/libs/remix-ws-templates/src/index.ts b/libs/remix-ws-templates/src/index.ts index 1dab217e0a..2955f961db 100644 --- a/libs/remix-ws-templates/src/index.ts +++ b/libs/remix-ws-templates/src/index.ts @@ -8,6 +8,7 @@ export { default as gnosisSafeMultisig } from './templates/gnosisSafeMultisig' export { default as playground } from './templates/playground' export { default as semaphore } from './templates/semaphore' export { default as hashchecker } from './templates/hashchecker' +export { default as rln } from './templates/rln' export { contractDeployerScripts } from './script-templates/contract-deployer' export { etherscanScripts } from './script-templates/etherscan' diff --git a/libs/remix-ws-templates/src/templates/rln/LICENSE-APACHE b/libs/remix-ws-templates/src/templates/rln/LICENSE-APACHE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/libs/remix-ws-templates/src/templates/rln/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/libs/remix-ws-templates/src/templates/rln/LICENSE-MIT b/libs/remix-ws-templates/src/templates/rln/LICENSE-MIT new file mode 100644 index 0000000000..489fcb5103 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/rln/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Rate-Limiting Nullifier (RLN) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libs/remix-ws-templates/src/templates/rln/README.md b/libs/remix-ws-templates/src/templates/rln/README.md new file mode 100644 index 0000000000..0b0bdf6ff9 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/rln/README.md @@ -0,0 +1,26 @@ +

Rate-Limiting Nullifier circuits in Circom

+

+ +

+ +
+ +*The project was audited by Veridise, yAcademy fellows and internally.* + +
+ +___ + +## What's RLN? + +RLN is a zero-knowledge gadget that enables spam +prevention in anonymous environments. + +The core parts of RLN are: +* zk-circuits in Circom (this repo); +* [registry smart-contract](https://github.com/Rate-Limiting-Nullifier/rln-contract); +* set of libraries to build app with RLN ([rlnjs](https://github.com/Rate-Limiting-Nullifier/rlnjs), [zerokit](https://github.com/vacp2p/zerokit)). + +--- + +To learn more on RLN and how it works - check out [documentation](https://rate-limiting-nullifier.github.io/rln-docs/). diff --git a/libs/remix-ws-templates/src/templates/rln/circuits/rln.circom b/libs/remix-ws-templates/src/templates/rln/circuits/rln.circom new file mode 100644 index 0000000000..94fc59024f --- /dev/null +++ b/libs/remix-ws-templates/src/templates/rln/circuits/rln.circom @@ -0,0 +1,40 @@ +pragma circom 2.1.0; + +include "./utils.circom"; +include "circomlib/poseidon.circom"; + +template RLN(DEPTH, LIMIT_BIT_SIZE) { + // Private signals + signal input identitySecret; + signal input userMessageLimit; + signal input messageId; + signal input pathElements[DEPTH]; + signal input identityPathIndex[DEPTH]; + + // Public signals + signal input x; + signal input externalNullifier; + + // Outputs + signal output y; + signal output root; + signal output nullifier; + + signal identityCommitment <== Poseidon(1)([identitySecret]); + signal rateCommitment <== Poseidon(2)([identityCommitment, userMessageLimit]); + + // Membership check + root <== MerkleTreeInclusionProof(DEPTH)(rateCommitment, identityPathIndex, pathElements); + + // messageId range check + RangeCheck(LIMIT_BIT_SIZE)(messageId, userMessageLimit); + + // SSS share calculations + signal a1 <== Poseidon(3)([identitySecret, externalNullifier, messageId]); + y <== identitySecret + a1 * x; + + // nullifier calculation + nullifier <== Poseidon(1)([a1]); +} + +component main { public [x, externalNullifier] } = RLN(20, 16); \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/rln/circuits/utils.circom b/libs/remix-ws-templates/src/templates/rln/circuits/utils.circom new file mode 100644 index 0000000000..788e3a4987 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/rln/circuits/utils.circom @@ -0,0 +1,45 @@ +pragma circom 2.1.0; + +include "circomlib/poseidon.circom"; +include "circomlib/mux1.circom"; +include "circomlib/bitify.circom"; +include "circomlib/comparators.circom"; + +template MerkleTreeInclusionProof(DEPTH) { + signal input leaf; + signal input pathIndex[DEPTH]; + signal input pathElements[DEPTH]; + + signal output root; + + signal mux[DEPTH][2]; + signal levelHashes[DEPTH + 1]; + + levelHashes[0] <== leaf; + for (var i = 0; i < DEPTH; i++) { + pathIndex[i] * (pathIndex[i] - 1) === 0; + + mux[i] <== MultiMux1(2)( + [ + [levelHashes[i], pathElements[i]], + [pathElements[i], levelHashes[i]] + ], + pathIndex[i] + ); + + levelHashes[i + 1] <== Poseidon(2)([mux[i][0], mux[i][1]]); + } + + root <== levelHashes[DEPTH]; +} + +template RangeCheck(LIMIT_BIT_SIZE) { + assert(LIMIT_BIT_SIZE < 253); + + signal input messageId; + signal input limit; + + signal bitCheck[LIMIT_BIT_SIZE] <== Num2Bits(LIMIT_BIT_SIZE)(messageId); + signal rangeCheck <== LessThan(LIMIT_BIT_SIZE)([messageId, limit]); + rangeCheck === 1; +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/rln/circuits/withdraw.circom b/libs/remix-ws-templates/src/templates/rln/circuits/withdraw.circom new file mode 100644 index 0000000000..3eae931e1d --- /dev/null +++ b/libs/remix-ws-templates/src/templates/rln/circuits/withdraw.circom @@ -0,0 +1,15 @@ +pragma circom 2.1.0; + +include "circomlib/poseidon.circom"; + +template Withdraw() { + signal input identitySecret; + signal input address; + + signal output identityCommitment <== Poseidon(1)([identitySecret]); + + // Dummy constraint to prevent compiler optimizing it + signal addressSquared <== address * address; +} + +component main { public [address] } = Withdraw(); diff --git a/libs/remix-ws-templates/src/templates/rln/index.ts b/libs/remix-ws-templates/src/templates/rln/index.ts new file mode 100644 index 0000000000..03231f7e66 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/rln/index.ts @@ -0,0 +1,20 @@ +export default async () => { + return { + // @ts-ignore + 'circuits/rln.circom': (await import('raw-loader!./circuits/rln.circom')).default, + // @ts-ignore + 'circuits/utils.circom': (await import('!!raw-loader!./circuits/utils.circom')).default, + // @ts-ignore + 'circuits/withdraw.circom': (await import('!!raw-loader!./circuits/withdraw.circom')).default, + // @ts-ignore + 'scripts/run_setup.ts': (await import('!!raw-loader!./scripts/run_setup.ts')).default, + // @ts-ignore + 'templates/groth16_verifier.sol.ejs': (await import('!!raw-loader!./templates/groth16_verifier.sol.ejs')).default, + // @ts-ignore + 'LICENSE-APACHE': (await import('!!raw-loader!./LICENSE-APACHE')).default, + // @ts-ignore + 'LICENSE-MIT': (await import('!!raw-loader!./LICENSE-MIT')).default, + // @ts-ignore + 'README.md': (await import('raw-loader!./README.md')).default + } +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/rln/scripts/run_setup.ts b/libs/remix-ws-templates/src/templates/rln/scripts/run_setup.ts new file mode 100644 index 0000000000..2f9e9684cf --- /dev/null +++ b/libs/remix-ws-templates/src/templates/rln/scripts/run_setup.ts @@ -0,0 +1,59 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const snarkjs = require('snarkjs'); + +const logger = { + info: (...args) => console.log(...args), + debug: (...args) => console.log(...args) +}; + +(async () => { + try { + // @ts-ignore + await remix.call('circuit-compiler', 'generateR1cs', 'circuits/rln.circom'); + + const ptau_final = "https://ipfs-cluster.ethdevops.io/ipfs/QmTiT4eiYz5KF7gQrDsgfCSTRv3wBPYJ4bRN1MmTRshpnW"; + // @ts-ignore + const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/rln.r1cs', true); + // @ts-ignore + const r1cs = new Uint8Array(r1csBuffer); + const zkey_0 = { type: "mem" }; + const zkey_1 = { type: "mem" }; + const zkey_final = { type: "mem" }; + + console.log('newZkey') + await snarkjs.zKey.newZKey(r1cs, ptau_final, zkey_0); + + console.log('contribute') + await snarkjs.zKey.contribute(zkey_0, zkey_1, "p2_C1", "pa_Entropy1"); + + console.log('beacon') + await snarkjs.zKey.beacon(zkey_1, zkey_final, "B3", "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", 10); + + console.log('verifyFromR1cs') + const verifyFromR1csResult = await snarkjs.zKey.verifyFromR1cs(r1cs, ptau_final, zkey_final); + console.assert(verifyFromR1csResult); + + console.log('verifyFromInit') + const verifyFromInit = await snarkjs.zKey.verifyFromInit(zkey_0, ptau_final, zkey_final); + console.assert(verifyFromInit); + + console.log('exportVerificationKey') + const vKey = await snarkjs.zKey.exportVerificationKey(zkey_final) + await remix.call('fileManager', 'writeFile', './zk/build/verification_key.json', JSON.stringify(vKey)) + + const templates = { + groth16: await remix.call('fileManager', 'readFile', 'templates/groth16_verifier.sol.ejs') + } + const solidityContract = await snarkjs.zKey.exportSolidityVerifier(zkey_final, templates) + + await remix.call('fileManager', 'writeFile', './zk/build/zk_verifier.sol', solidityContract) + + console.log('buffer', (zkey_final as any).data.length) + await remix.call('fileManager', 'writeFile', './zk/build/zk_setup.txt', JSON.stringify(Array.from(((zkey_final as any).data)))) + + console.log('setup done.') + + } catch (e) { + console.error(e.message) + } +})() \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/rln/templates/groth16_verifier.sol.ejs b/libs/remix-ws-templates/src/templates/rln/templates/groth16_verifier.sol.ejs new file mode 100644 index 0000000000..692aedf612 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/rln/templates/groth16_verifier.sol.ejs @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-3.0 +/* + Copyright 2021 0KIMS association. + + This file is generated with [snarkJS](https://github.com/iden3/snarkjs). + + snarkJS is a free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + snarkJS is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with snarkJS. If not, see . +*/ + +pragma solidity >=0.7.0 <0.9.0; + +contract Groth16Verifier { + // Scalar field size + uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // Base field size + uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + // Verification Key data + uint256 constant alphax = <%= vk_alpha_1[0] %>; + uint256 constant alphay = <%= vk_alpha_1[1] %>; + uint256 constant betax1 = <%= vk_beta_2[0][1] %>; + uint256 constant betax2 = <%= vk_beta_2[0][0] %>; + uint256 constant betay1 = <%= vk_beta_2[1][1] %>; + uint256 constant betay2 = <%= vk_beta_2[1][0] %>; + uint256 constant gammax1 = <%= vk_gamma_2[0][1] %>; + uint256 constant gammax2 = <%= vk_gamma_2[0][0] %>; + uint256 constant gammay1 = <%= vk_gamma_2[1][1] %>; + uint256 constant gammay2 = <%= vk_gamma_2[1][0] %>; + uint256 constant deltax1 = <%= vk_delta_2[0][1] %>; + uint256 constant deltax2 = <%= vk_delta_2[0][0] %>; + uint256 constant deltay1 = <%= vk_delta_2[1][1] %>; + uint256 constant deltay2 = <%= vk_delta_2[1][0] %>; + + <% for (let i=0; i + uint256 constant IC<%=i%>x = <%=IC[i][0]%>; + uint256 constant IC<%=i%>y = <%=IC[i][1]%>; + <% } %> + + // Memory data + uint16 constant pVk = 0; + uint16 constant pPairing = 128; + + uint16 constant pLastMem = 896; + + function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[<%=IC.length-1%>] calldata _pubSignals) public view returns (bool) { + assembly { + function checkField(v) { + if iszero(lt(v, q)) { + mstore(0, 0) + return(0, 0x20) + } + } + + // G1 function to multiply a G1 value(x,y) to value in an address + function g1_mulAccC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn, 32), y) + mstore(add(mIn, 64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + + mstore(add(mIn, 64), mload(pR)) + mstore(add(mIn, 96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + } + + function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk { + let _pPairing := add(pMem, pPairing) + let _pVk := add(pMem, pVk) + + mstore(_pVk, IC0x) + mstore(add(_pVk, 32), IC0y) + + // Compute the linear combination vk_x + <% for (let i = 1; i <= nPublic; i++) { %> + g1_mulAccC(_pVk, IC<%=i%>x, IC<%=i%>y, calldataload(add(pubSignals, <%=(i-1)*32%>))) + <% } %> + + // -A + mstore(_pPairing, calldataload(pA)) + mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) + + // B + mstore(add(_pPairing, 64), calldataload(pB)) + mstore(add(_pPairing, 96), calldataload(add(pB, 32))) + mstore(add(_pPairing, 128), calldataload(add(pB, 64))) + mstore(add(_pPairing, 160), calldataload(add(pB, 96))) + + // alpha1 + mstore(add(_pPairing, 192), alphax) + mstore(add(_pPairing, 224), alphay) + + // beta2 + mstore(add(_pPairing, 256), betax1) + mstore(add(_pPairing, 288), betax2) + mstore(add(_pPairing, 320), betay1) + mstore(add(_pPairing, 352), betay2) + + // vk_x + mstore(add(_pPairing, 384), mload(add(pMem, pVk))) + mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) + + + // gamma2 + mstore(add(_pPairing, 448), gammax1) + mstore(add(_pPairing, 480), gammax2) + mstore(add(_pPairing, 512), gammay1) + mstore(add(_pPairing, 544), gammay2) + + // C + mstore(add(_pPairing, 576), calldataload(pC)) + mstore(add(_pPairing, 608), calldataload(add(pC, 32))) + + // delta2 + mstore(add(_pPairing, 640), deltax1) + mstore(add(_pPairing, 672), deltax2) + mstore(add(_pPairing, 704), deltay1) + mstore(add(_pPairing, 736), deltay2) + + + let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) + + isOk := and(success, mload(_pPairing)) + } + + let pMem := mload(0x40) + mstore(0x40, add(pMem, pLastMem)) + + // Validate that all evaluations ∈ F + <% for (let i=0; i + checkField(calldataload(add(_pubSignals, <%=i*32%>))) + <% } %> + + // Validate all evaluations + let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) + + mstore(0, isValid) + return(0, 0x20) + } + } + } \ No newline at end of file