├── CHANGELOG.md ├── images └── icon.png ├── .vscodeignore ├── test └── corpi │ ├── circuit.config.json │ └── circuits │ └── multiply.circom ├── src ├── settings.js ├── features │ ├── commands.js │ ├── hover │ │ ├── static.builtins.js │ │ └── hover.js │ ├── deco.js │ └── compile.js ├── extension.web.js └── extension.js ├── .vscode └── launch.json ├── LICENSE ├── .gitignore ├── README.md ├── snippets └── circom.json └── package.json /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | 4 | ## 0.0.1 - 0.0.4 5 | - Initial release -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tintinweb/vscode-circom-pro/HEAD/images/icon.png -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | **/*.ts 2 | **/tsconfig.json 3 | !file.ts 4 | **/*.zip 5 | *.gitignore 6 | *.py 7 | *.vsix 8 | **/.vscode/** 9 | **/tmp/** 10 | **/test/** 11 | **/webpack/** -------------------------------------------------------------------------------- /test/corpi/circuit.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "MyCircuits", 3 | "outputDir": "./out", 4 | "build": { 5 | "inputDir": "./circuits", 6 | "circuits": [ 7 | { 8 | "cID": "multiply", 9 | "fileName": "multiply.circom", 10 | "compilationMode": "wasm" 11 | } 12 | ] 13 | } 14 | } -------------------------------------------------------------------------------- /test/corpi/circuits/multiply.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.0.1; 2 | 3 | /*This circuit template checks that c is the multiplication of a and b.*/ 4 | 5 | template Multiplier2 () { 6 | 7 | // Declaration of signals. 8 | signal input a; 9 | signal input b; 10 | signal output c; 11 | signal output f; 12 | // Constraints. 13 | c <-- a * b; 14 | c <== a*b; 15 | a * b === c; 16 | } 17 | 18 | 19 | component main = Multiplier2(); 20 | /* 21 | proof.input = { 22 | "a":-5, 23 | "b":23 24 | } 25 | */ 26 | -------------------------------------------------------------------------------- /src/settings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @author github.com/tintinweb 4 | * @license MIT 5 | * 6 | * 7 | * */ 8 | /** imports */ 9 | const vscode = require('vscode'); 10 | 11 | const LANGUAGE_ID = "circom"; 12 | const SETTINGS_ID = "circompro"; 13 | 14 | function extensionConfig() { 15 | return vscode.workspace.getConfiguration(SETTINGS_ID); 16 | } 17 | 18 | function LOG(msg, prefix, file, data) { 19 | const logline = `${prefix ? prefix + ' ' : ''}(circom-pro)${file ? ` [${file}]` : ''} ${msg} ${data ? `\n${JSON.stringify(data, null, 4)}` : ""}` 20 | vscode.window.outputChannel?.appendLine(logline) 21 | console.log(logline); 22 | } 23 | 24 | 25 | module.exports = { 26 | LANGUAGE_ID: LANGUAGE_ID, 27 | SETTINGS_ID: SETTINGS_ID, 28 | extensionConfig: extensionConfig, 29 | LOG:LOG 30 | }; -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ] 16 | }, 17 | { 18 | "name": "Extension Tests", 19 | "type": "extensionHost", 20 | "request": "launch", 21 | "runtimeExecutable": "${execPath}", 22 | "args": [ 23 | "--extensionDevelopmentPath=${workspaceFolder}", 24 | "--extensionTestsPath=${workspaceFolder}/test/suite/index" 25 | ] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 tintinweb 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/features/commands.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @author github.com/tintinweb 4 | * @license MIT 5 | * 6 | * */ 7 | 8 | const vscode = require("vscode"); 9 | const path = require("path"); 10 | 11 | function workspaceForFile(uri) { 12 | let workspace = vscode.workspace.getWorkspaceFolder(uri); 13 | return workspace ? workspace.uri : ""; 14 | } 15 | 16 | class CircuitConfig { 17 | constructor(projectName, outputDir, inputDir) { 18 | this.config = { 19 | projectName: projectName, 20 | outputDir: outputDir || "./out", 21 | build: { 22 | inputDir: inputDir || "./circuits", 23 | circuits: [ 24 | ] 25 | } 26 | } 27 | } 28 | 29 | addCircuitByFsPath(fsPath) { 30 | const ws = workspaceForFile(vscode.Uri.file(fsPath)); 31 | let fname = path.basename(fsPath, ws.fsPath); 32 | let relpath = path.relative(ws.fsPath, fsPath) 33 | let firstdir = relpath.split(path.sep,1)[0]; 34 | this.config.build.inputDir = `.${path.sep}${firstdir}`; 35 | relpath = path.relative(vscode.Uri.joinPath(ws, firstdir).fsPath, fsPath) 36 | this.addCircuit(fname.replace(".circom", ""), relpath) 37 | } 38 | 39 | addCircuit(cID, fileName, compilationMode) { 40 | this.config.build.circuits.push({ 41 | cID: cID, 42 | fileName: fileName, 43 | compilationMode: compilationMode || "wasm" 44 | }); 45 | } 46 | } 47 | 48 | module.exports = { 49 | CircuitConfig 50 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | .env.test 64 | 65 | # parcel-bundler cache (https://parceljs.org/) 66 | .cache 67 | 68 | # next.js build output 69 | .next 70 | 71 | # nuxt.js build output 72 | .nuxt 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless/ 79 | 80 | # FuseBox cache 81 | .fusebox/ 82 | 83 | # DynamoDB Local files 84 | .dynamodb/ 85 | 86 | dist/** -------------------------------------------------------------------------------- /src/features/hover/static.builtins.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @author github.com/tintinweb 4 | * @license MIT 5 | * 6 | * */ 7 | 8 | const BUILTINS = { 9 | "pragma custom_templates": { 10 | "prefix": "pragma custom_templates", 11 | "description": "Instruction to indicate the usage of custom templates.", 12 | "security": "" 13 | }, 14 | "assert": { 15 | "prefix": "assert", 16 | "description": "Check the condition at construction time.", 17 | "security": "" 18 | }, 19 | "component": { 20 | "prefix": "component", 21 | "description": "Instantiates a template.", 22 | "security": "" 23 | }, 24 | "template": { 25 | "prefix": "template", 26 | "description": "Defines a new circuit.", 27 | "security": "" 28 | }, 29 | "signal": { 30 | "prefix": "signal", 31 | "description": "Declares a new signal.", 32 | "security": "" 33 | }, 34 | "input": { 35 | "prefix": "input", 36 | "description": "Declares the signal as input.", 37 | "security": "" 38 | }, 39 | "output": { 40 | "prefix": "output", 41 | "description": "Declares the signal as output.", 42 | "security": "" 43 | }, 44 | "public": { 45 | "prefix": "public", 46 | "description": "Declares the signal as public.", 47 | "security": "" 48 | }, 49 | "parallel": { 50 | "prefix": "parallel", 51 | "description": "Generates C code with the parallel component or template.", 52 | "security": "" 53 | } 54 | } 55 | 56 | module.exports = { 57 | BUILTINS 58 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [The Creed Rebellion!](https://thecreed.xyz/)
2 | [[ 🌐 ](https://thecreed.xyz/)[ 🫂 ](https://community.thecreed.xyz/c/start-here)] 3 |

4 | 5 | 6 | get in touch with Consensys Diligence 7 | 8 | # 👩‍💻 vscode extension 9 | 10 | 11 | ### Snippets 12 | 13 | [Snippets](https://user-images.githubusercontent.com/2865694/233330364-4ade0a9a-23f4-4083-a9dc-369169ce9a15.gif) 14 | 15 | 16 | ### Compilation, Proof Generation, and Verification 17 | 18 | [Compiler](https://user-images.githubusercontent.com/2865694/233328350-7b9d5c29-1328-4e10-947c-bc5a15561e83.gif) 19 | 20 | Provide the proof input is as easy as declaring JSON encoded `proof.input` struct, inline, within a comment block in the main circom file. 21 | 22 | ```javascript 23 | /* 24 | proof.input = { 25 | "a":4, 26 | "b":5 27 | } 28 | */ 29 | ``` 30 | 31 | 32 | ### Bootstrapping a circomjs circuit config for the compiler 33 | 34 | [Config](https://user-images.githubusercontent.com/2865694/233328327-57561823-bd88-4288-a44c-cee6325c5465.gif) 35 | 36 | ### Commands 37 | 38 | [Commands](https://user-images.githubusercontent.com/2865694/233327059-5579da45-a464-43b2-af4a-3c4f332511c1.png) 39 | 40 | ### Settings 41 | 42 | [Settings](https://user-images.githubusercontent.com/2865694/233334155-c15ce183-cadb-4cc6-b657-af71bc19f9aa.png) 43 | 44 | # 🐞 Feedback/Bugs 45 | 46 | Please file questions/bugs with the projects [GitHub Issue Tracker](https://github.com/tintinweb/vscode-circom-pro/issues) 🙌. 47 | 48 | # 🫶 Acknowledgements 49 | 50 | * [iden3.circom](https://marketplace.visualstudio.com/items?itemName=iden3.circom) - bundled extension: circom language support / highlighting 51 | * [@zefi/circomjs](https://www.npmjs.com/package/@zefi/circomjs) - compiler framework -------------------------------------------------------------------------------- /src/features/deco.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @author github.com/tintinweb 4 | * @license MIT 5 | * 6 | * */ 7 | const vscode = require('vscode'); 8 | 9 | const styles = { 10 | foreGroundOk: vscode.window.createTextEditorDecorationType({ 11 | dark: { 12 | color: "#C0C0C0", 13 | }, 14 | light: { 15 | color: "#000000", 16 | }, 17 | fontWeight: "bold" 18 | }), 19 | foreGroundWarning: vscode.window.createTextEditorDecorationType({ 20 | dark: { 21 | color: "#f56262" 22 | }, 23 | light: { 24 | color: "#d65353" 25 | }, 26 | fontWeight: "bold", 27 | }), 28 | foreGroundWarningUnderline: vscode.window.createTextEditorDecorationType({ 29 | dark: { 30 | color: "#f56262" 31 | }, 32 | light: { 33 | color: "#d65353" 34 | }, 35 | textDecoration: "underline" 36 | }), 37 | foreGroundInfoUnderline: vscode.window.createTextEditorDecorationType({ 38 | dark: { 39 | color: "#ffc570" 40 | }, 41 | light: { 42 | color: "#e4a13c" 43 | }, 44 | textDecoration: "underline" 45 | }), 46 | foreGroundNewEmit: vscode.window.createTextEditorDecorationType({ 47 | dark: { 48 | color: "#fffffff5", 49 | }, 50 | light: { 51 | color: "" 52 | }, 53 | fontWeight: "#c200b2ad" 54 | }), 55 | boldUnderline: vscode.window.createTextEditorDecorationType({ 56 | fontWeight: "bold", 57 | textDecoration: "underline" 58 | }), 59 | }; 60 | 61 | async function decorateWords(editor, decorules, decoStyle) { 62 | return new Promise(resolve => { 63 | if (!editor) { 64 | return; 65 | } 66 | var decos = []; 67 | const text = editor.document.getText(); 68 | 69 | decorules.forEach(function (rule) { 70 | var regEx = new RegExp(rule.regex, "g"); 71 | let match; 72 | while (match = regEx.exec(text)) { 73 | var startPos = editor.document.positionAt(match.index); 74 | var endPos = editor.document.positionAt(match.index + match[rule.captureGroup].trim().length); 75 | //endPos.line = startPos.line; //hacky force 76 | var decoration = { 77 | range: new vscode.Range(startPos, endPos), 78 | hoverMessage: rule.hoverMessage 79 | }; 80 | decos.push(decoration); 81 | } 82 | }); 83 | editor.setDecorations(decoStyle, decos); 84 | resolve(); 85 | }); 86 | } 87 | 88 | module.exports = { 89 | decorateWords: decorateWords, 90 | styles: styles 91 | }; -------------------------------------------------------------------------------- /src/extension.web.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @author github.com/tintinweb 4 | * @license MIT 5 | * */ 6 | 7 | /** imports */ 8 | const vscode = require("vscode"); 9 | const { CancellationTokenSource } = require('vscode'); 10 | const settings = require("./settings"); 11 | const { provideHoverHandler } = require("./features/hover/hover"); 12 | const mod_deco = require("./features/deco"); 13 | 14 | 15 | /** global vars */ 16 | var activeEditor; 17 | /** events */ 18 | async function onDidSave(document) { 19 | if (document.languageId != settings.LANGUAGE_ID) { 20 | return; 21 | } 22 | } 23 | 24 | async function onDidChange(event) { 25 | if (event?.document.languageId != settings.LANGUAGE_ID) { 26 | return; 27 | } 28 | 29 | if (settings.extensionConfig().decoration.enable) { 30 | mod_deco.decorateWords(activeEditor, [ 31 | { 32 | regex: "(<--|-->)", 33 | hoverMessage: "❗**potentially unsafe** signal assignment", 34 | captureGroup: 0, 35 | } 36 | ], mod_deco.styles.foreGroundWarning); 37 | } 38 | } 39 | 40 | function onActivate(context) { 41 | 42 | const active = vscode.window.activeTextEditor; 43 | activeEditor = active; 44 | 45 | if (!settings.extensionConfig().mode.active) { 46 | console.log("ⓘ activate extension: entering passive mode. not registering any active code augmentation support."); 47 | return; 48 | } 49 | /** module init */ 50 | onDidChange({ document: active.document }); 51 | onDidSave(active.document); 52 | 53 | /** event setup */ 54 | /***** OnChange */ 55 | vscode.window.onDidChangeActiveTextEditor(editor => { 56 | activeEditor = editor; 57 | if (editor) { 58 | onDidChange(); 59 | } 60 | }, null, context.subscriptions); 61 | /***** OnChange */ 62 | vscode.workspace.onDidChangeTextDocument(event => { 63 | activeEditor = vscode.window.activeTextEditor; 64 | if (event.document === activeEditor.document) { 65 | onDidChange(event); 66 | } 67 | }, null, context.subscriptions); 68 | /***** OnSave */ 69 | 70 | vscode.workspace.onDidSaveTextDocument(document => { 71 | onDidSave(document); 72 | }, null, context.subscriptions); 73 | 74 | /****** OnOpen */ 75 | vscode.workspace.onDidOpenTextDocument(document => { 76 | onDidSave(document); 77 | }, null, context.subscriptions); 78 | 79 | /** hover provider */ 80 | 81 | context.subscriptions.push( 82 | vscode.languages.registerHoverProvider({ language: settings.LANGUAGE_ID }, { 83 | provideHover(document, position, token) { 84 | return provideHoverHandler(document, position, token, { language: settings.LANGUAGE_ID }); 85 | } 86 | }) 87 | ); 88 | } 89 | 90 | /* exports */ 91 | exports.activate = onActivate; 92 | -------------------------------------------------------------------------------- /src/features/hover/hover.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @author github.com/tintinweb 4 | * @license MIT 5 | * 6 | * */ 7 | const vscode = require('vscode'); 8 | const settings = require("../../settings"); 9 | const { BUILTINS } = require("./static.builtins"); 10 | 11 | 12 | function createHover(name, snippet, type) { 13 | function isSet(val) { 14 | return typeof val != "undefined" && val != ""; 15 | } 16 | 17 | var text = Array(); 18 | 19 | if (isSet(snippet.instr_args) || isSet(snippet.instr_returns)) { 20 | text.push("_asm_ :: __" + name + "__ (" + snippet.instr_args.join(", ") + ")" + (isSet(snippet.instr_returns) ? " : " + snippet.instr_returns.join(", ") : "")); 21 | } 22 | 23 | if (text.length > 0) text.push(""); 24 | if (isSet(snippet.instr_gas)) { 25 | text.push("__⟶__ gas (min): " + snippet.instr_gas); 26 | } 27 | if (isSet(snippet.instr_fork)) { 28 | text.push("__⟶__ since: " + snippet.instr_fork); 29 | } 30 | 31 | if (text.length > 0) text.push(""); 32 | if (isSet(snippet.example)) { 33 | text.push(snippet.example); 34 | } 35 | 36 | if (text.length > 0) text.push(""); 37 | if (isSet(snippet.description)) { 38 | var txt_descr = snippet.description instanceof Array ? snippet.description.join("\n ") : snippet.description; 39 | text.push("💡 " + txt_descr); 40 | } 41 | 42 | if (text.length > 0) text.push(""); 43 | if (isSet(snippet.security)) { 44 | text.push(""); 45 | var txt_security = snippet.security instanceof Array ? snippet.security.join("\n* ❗") : snippet.security; 46 | text.push("* ❗ " + txt_security); 47 | } 48 | 49 | if (text.length > 0) text.push(""); 50 | if (isSet(snippet.reference)) { 51 | text.push("🌎 [more...](" + snippet.reference + ")"); 52 | } 53 | 54 | //const commentCommandUri = vscode.Uri.parse(`command:editor.action.addCommentLine`); 55 | //text.push("[Add comment](${commentCommandUri})") 56 | const contents = new vscode.MarkdownString(text.join(" \n")); 57 | contents.isTrusted = true; 58 | return new vscode.Hover(contents); 59 | } 60 | 61 | function provideHoverHandler(document, position, token, type) { 62 | if (!settings.extensionConfig().hover.enable) { 63 | return; 64 | } 65 | const range = document.getWordRangeAtPosition(position, /(tx\.gasprice|tx\.origin|msg\.data|msg\.sender|msg\.sig|msg\.value|block\.coinbase|block\.difficulty|block\.gaslimit|block\.number|block\.timestamp|abi\.encodePacked|abi\.encodeWithSelector|abi\.encodeWithSignature|abi\.decode|abi\.encode|\.?[0-9_\w>]+)/); 66 | if (!range || range.length <= 0) 67 | return; 68 | const word = document.getText(range); 69 | 70 | //console.log(word); 71 | 72 | for (const snippet in BUILTINS) { 73 | if ( 74 | BUILTINS[snippet].prefix == word || 75 | BUILTINS[snippet].hover == word 76 | ) { 77 | return createHover(snippet, BUILTINS[snippet], type); 78 | } 79 | } 80 | } 81 | 82 | 83 | module.exports = { 84 | provideHoverHandler: provideHoverHandler 85 | }; -------------------------------------------------------------------------------- /snippets/circom.json: -------------------------------------------------------------------------------- 1 | { 2 | ".source.circom": { 3 | "template declaration": { 4 | "prefix": "template", 5 | "body": "template ${1:_name}(${2:_arg}) {\n\n\t// Declaration of signals.\n\tsignal input ${3:_in};\n\tsignal output ${4:_out};\n\n\t// Constraints.\n\n}\n" 6 | }, 7 | "template parallel declaration": { 8 | "prefix": "template parallel", 9 | "body": "template parallel ${1:_name}(${2:_arg}) {\n\n\t// Declaration of signals.\n\tsignal input ${3:_in};\n\tsignal output ${4:_out};\n\n\t// Constraints.\n\n}\n" 10 | }, 11 | "template custom declaration": { 12 | "prefix": "template custom", 13 | "body": "template custom ${1:_name}(${2:_arg}) {\n\n\t// Declaration of signals.\n\tsignal input ${3:_in};\n\tsignal output ${4:_out};\n\n\t// Constraints.\n\n}\n" 14 | }, 15 | "component main": { 16 | "prefix": "component main", 17 | "body": "component main = ${1:_template}(${2:_arg});" 18 | }, 19 | "component": { 20 | "prefix": "component", 21 | "body": "component ${1:_var} = ${1:_template}(${2:_arg});" 22 | }, 23 | "pragma circom": { 24 | "prefix": "pragma circom", 25 | "body": "pragma circom ${1:_version};" 26 | }, 27 | "pragma custom": { 28 | "prefix": "pragma custom", 29 | "body": "pragma custom_templates;" 30 | }, 31 | "for loop": { 32 | "prefix": "for", 33 | "body": "for (i = ${1:_start}; i < ${1:_end}; i++){\n\n}" 34 | }, 35 | "while loop": { 36 | "prefix": "while", 37 | "body": "while (${1:_condition}){\n\n}" 38 | }, 39 | "if condition": { 40 | "prefix": "ifelse", 41 | "body": "if (${1:_condition}){\n\n} else {\n\n}" 42 | }, 43 | "function": { 44 | "prefix": "function ", 45 | "body": "function ${1:_name} (${2:_arg}) {\n\n}\n" 46 | }, 47 | "proof.input": { 48 | "prefix": "proof.input ", 49 | "body": "/*\nproof.input = {\n\t\t\"${1:_var}\":${2:_value},\n\t\t\"${3:_var}\":${4:_value},\n\t}\n*/\n" 50 | }, 51 | "proof.verify": { 52 | "prefix": "proof.verify ", 53 | "body": "/*\nproof.verify = {\n\t\t\"${1:_var}\":${2:_value},\n\t\t\"${3:_var}\":${4:_value},\n\t}\n*/\n" 54 | }, 55 | "include": { 56 | "prefix": "include ", 57 | "body": "include \"${1:_file}\";" 58 | }, 59 | "signal declaration": { 60 | "prefix": "signal ", 61 | "body": "signal ${1:input|output} ${2:_name};" 62 | }, 63 | "var declaration": { 64 | "prefix": "var ", 65 | "body": "var ${1:_name} = ${2:_value};" 66 | } 67 | , 68 | "assert": { 69 | "prefix": "assert", 70 | "body": "assert(${1:_condition});" 71 | } 72 | , 73 | "log": { 74 | "prefix": "log", 75 | "body": "log(${1:_msg});" 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-circom-pro", 3 | "displayName": "Circom Pro", 4 | "description": "Circom compiler, snippets, hover and language support for Visual Studio Code", 5 | "license": "MIT", 6 | "version": "0.0.4", 7 | "preview": true, 8 | "keywords": [ 9 | "circom", 10 | "compiler", 11 | "snippets" 12 | ], 13 | "publisher": "tintinweb", 14 | "icon": "images/icon.png", 15 | "engines": { 16 | "vscode": "^1.76.2" 17 | }, 18 | "categories": [ 19 | "Programming Languages", 20 | "Other", 21 | "Snippets" 22 | ], 23 | "bugs": { 24 | "url": "https://github.com/tintinweb/vscode-circom-pro/issues" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/tintinweb/vscode-circom-pro" 29 | }, 30 | "activationEvents": [ 31 | "onLanguage:circom" 32 | ], 33 | "main": "./src/extension.js", 34 | "browser": "./dist/web/extension.js", 35 | "contributes": { 36 | "snippets": [ 37 | { 38 | "language": "circom", 39 | "path": "./snippets/circom.json" 40 | } 41 | ], 42 | "configuration": { 43 | "type": "object", 44 | "title": "Circom Pro", 45 | "properties": { 46 | "circompro.compile.onSave": { 47 | "type": "boolean", 48 | "default": false, 49 | "description": "Automatically compile when saving and annotate code with compile results." 50 | }, 51 | "circompro.mode.active": { 52 | "type": "boolean", 53 | "default": true, 54 | "description": "Enable/Disable all active components of this extension (emergency)." 55 | }, 56 | "circompro.decoration.enable": { 57 | "type": "boolean", 58 | "default": true, 59 | "description": "Whether to enable/disable circom active syntax highlighting for security." 60 | }, 61 | "circompro.hover.enable": { 62 | "type": "boolean", 63 | "default": true, 64 | "description": "Whether to enable/disable circom tooltips/hover information." 65 | } 66 | } 67 | }, 68 | "commands": [ 69 | { 70 | "command": "circompro.compile.this", 71 | "title": "CircomPro: Compile This Circuit" 72 | }, 73 | { 74 | "command": "circompro.compile.all", 75 | "title": "CircomPro: Compile All Circuits" 76 | }, 77 | { 78 | "command": "circompro.circuit.config.new", 79 | "title": "CircomPro: Generate circuit.config.json" 80 | } 81 | ] 82 | }, 83 | "extensionPack": [ 84 | "iden3.circom" 85 | ], 86 | "scripts": { 87 | "test": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=. --extensionTestsPath=dist/web/test/suite/index.js", 88 | "pretest": "npm run compile-web", 89 | "vscode:prepublish": "npm run package-web", 90 | "compile-web": "webpack -c config/web.webpack.config.js", 91 | "watch-web": "webpack -c config/web.webpack.config.js --watch", 92 | "package-web": "webpack -c config/web.webpack.config.js --mode production --devtool hidden-source-map", 93 | "test-in-browser": "npm run compile-web && vscode-test-web --browserType=chromium --extensionDevelopmentPath=. ." 94 | }, 95 | "dependencies": { 96 | "@zefi/circomjs": "^1.0.6" 97 | }, 98 | "devDependencies": { 99 | "webpack-cli": "^5.0.1", 100 | "webpack": "^5.78.0", 101 | "@vscode/test-web": "^0.0.41", 102 | "path-browserify": "^1.0.1" 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/extension.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @author github.com/tintinweb 4 | * @license MIT 5 | * */ 6 | 7 | /** imports */ 8 | const vscode = require("vscode"); 9 | const { CancellationTokenSource } = require('vscode'); 10 | const settings = require("./settings"); 11 | const { CircomCompiler } = require("./features/compile"); 12 | const { provideHoverHandler } = require("./features/hover/hover"); 13 | const mod_deco = require("./features/deco"); 14 | const { CircuitConfig } = require("./features/commands"); 15 | 16 | 17 | 18 | /** global vars */ 19 | var activeEditor; 20 | var suppressPopupShowShown = { 21 | generateConfig: false 22 | } 23 | 24 | const currentCancellationTokens = { 25 | onDidChange: new CancellationTokenSource(), 26 | }; 27 | const compiler = new CircomCompiler(); 28 | 29 | function getCircuitConfigPath() { 30 | const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; 31 | if (workspaceFolder) { 32 | // Construct the file URI 33 | return vscode.Uri.joinPath(workspaceFolder.uri, 'circuit.config.json'); 34 | } 35 | } 36 | 37 | async function checkAutogenerateConfig() { 38 | const configFile = getCircuitConfigPath(); 39 | 40 | // Check if the file already exists 41 | try { 42 | await vscode.workspace.fs.stat(configFile); 43 | return true; 44 | } catch (error) { 45 | if(suppressPopupShowShown.generateConfig){ 46 | return false; 47 | } 48 | suppressPopupShowShown.generateConfig = true; 49 | let choice = await vscode.window.showInformationMessage('Project configuration file `circuit.config.json` not found. Create it?', "Create", "Abort"); 50 | if(choice == "Create"){ 51 | await vscode.commands.executeCommand(`${settings.SETTINGS_ID}.circuit.config.new`) 52 | return true; 53 | } 54 | } 55 | return false; 56 | } 57 | 58 | /** events */ 59 | async function onDidSave(document) { 60 | if (document.languageId != settings.LANGUAGE_ID) { 61 | return; 62 | } 63 | 64 | //always run on save 65 | if (settings.extensionConfig().compile.onSave) { 66 | 67 | if(!await checkAutogenerateConfig()){ 68 | return; //no config, nothing to do 69 | } 70 | 71 | currentCancellationTokens.onDidChange.cancel(); 72 | currentCancellationTokens.onDidChange = new CancellationTokenSource(); 73 | 74 | compiler.compile({ 75 | cancellationToken: currentCancellationTokens.onDidChange.token, 76 | inputFilePath: document.uri.fsPath 77 | }); 78 | vscode.window.outputChannel.show() 79 | } 80 | } 81 | 82 | async function onDidChange(event) { 83 | if (event?.document.languageId != settings.LANGUAGE_ID) { 84 | return; 85 | } 86 | 87 | if (settings.extensionConfig().decoration.enable) { 88 | mod_deco.decorateWords(activeEditor, [ 89 | { 90 | regex: "(<--|-->)", 91 | hoverMessage: "❗**potentially unsafe** signal assignment", 92 | captureGroup: 0, 93 | } 94 | ], mod_deco.styles.foreGroundWarning); 95 | } 96 | } 97 | 98 | function onActivate(context) { 99 | 100 | const active = vscode.window.activeTextEditor; 101 | activeEditor = active; 102 | 103 | /** diag */ 104 | context.subscriptions.push(compiler.diagnosticCollections.compiler); 105 | 106 | /** commands */ 107 | context.subscriptions.push( 108 | vscode.commands.registerCommand(`${settings.SETTINGS_ID}.compile.this`, async () => { 109 | 110 | if(!await checkAutogenerateConfig()){ 111 | return; //no config, nothing to do 112 | } 113 | 114 | currentCancellationTokens.onDidChange.cancel(); 115 | currentCancellationTokens.onDidChange = new CancellationTokenSource(); 116 | 117 | compiler.compile({ 118 | cancellationToken: currentCancellationTokens.onDidChange.token, 119 | inputFilePath: vscode.window.activeTextEditor.document.uri.fsPath 120 | }); 121 | vscode.window.outputChannel.show(); 122 | }) 123 | ); 124 | 125 | context.subscriptions.push( 126 | vscode.commands.registerCommand(`${settings.SETTINGS_ID}.compile.all`, () => { 127 | currentCancellationTokens.onDidChange.cancel(); 128 | currentCancellationTokens.onDidChange = new CancellationTokenSource(); 129 | 130 | return compiler.compile({ 131 | cancellationToken: currentCancellationTokens.onDidChange.token, 132 | }) 133 | }) 134 | ); 135 | 136 | context.subscriptions.push( 137 | vscode.commands.registerCommand(`${settings.SETTINGS_ID}.circuit.config.new`, async () => { 138 | const circuitConfig = new CircuitConfig("MyCircuits") 139 | const uris = await vscode.workspace.findFiles('{**/*.circom,*.circom}', '**​/node_modules/**', 300); 140 | for (let u of uris) { 141 | circuitConfig.addCircuitByFsPath(u.fsPath) 142 | } 143 | 144 | 145 | const fileUri = getCircuitConfigPath(); 146 | if(!fileUri){ 147 | return; 148 | } 149 | 150 | // Check if the file already exists 151 | try { 152 | await vscode.workspace.fs.stat(fileUri); 153 | settings.LOG('"circuit.config.json" already exists in the workspace. rename or remove the file to autogenerate a new one.',"🤷‍♂️") 154 | vscode.window.showWarningMessage('🤷‍♂️ "circuit.config.json" already exists in the workspace.'); 155 | } catch (error) { 156 | // If the file does not exist, create it with the initial content of an empty JSON object 157 | await vscode.workspace.fs.writeFile(fileUri, new Uint8Array(Buffer.from(JSON.stringify(circuitConfig.config, null, 4)))); 158 | settings.LOG('"circuit.config.json" created in the workspace.',"👍") 159 | vscode.window.showInformationMessage('👍 "circuit.config.json" created in the workspace.'); 160 | } 161 | 162 | vscode.workspace.openTextDocument(fileUri).then(document => { 163 | vscode.window.showTextDocument(document); 164 | }, error => { 165 | vscode.window.showErrorMessage(`Failed to open "circuit.config.json": ${error}`); 166 | }); 167 | 168 | }) 169 | ); 170 | 171 | /** register output channel */ 172 | const outputChannel = vscode.window.createOutputChannel("Circom Pro"); 173 | vscode.window.outputChannel = outputChannel 174 | context.subscriptions.push(outputChannel); 175 | settings.LOG("Welcome to Circom Pro (https://github.com/tintinweb/vscode-circom-pro).", "👑") 176 | settings.LOG("Please note that you can enable/disable certain featurs (autocomple, highlighting, etc.) in the vscode settings:\nvscode -> settings -> 'circompro.*'", "ℹ️") 177 | 178 | /** hover provider */ 179 | context.subscriptions.push( 180 | vscode.languages.registerHoverProvider({ language: settings.LANGUAGE_ID }, { 181 | provideHover(document, position, token) { 182 | return provideHoverHandler(document, position, token, { language: settings.LANGUAGE_ID }); 183 | } 184 | }) 185 | ); 186 | 187 | if (!settings.extensionConfig().mode.active) { 188 | console.log("ⓘ activate extension: entering passive mode. not registering any active code augmentation support."); 189 | return; 190 | } 191 | /** module init */ 192 | onDidChange({ document: active.document }); 193 | onDidSave(active.document); 194 | 195 | /** event setup */ 196 | /***** OnChange */ 197 | vscode.window.onDidChangeActiveTextEditor(editor => { 198 | activeEditor = editor; 199 | if (editor) { 200 | onDidChange(); 201 | } 202 | }, null, context.subscriptions); 203 | /***** OnChange */ 204 | vscode.workspace.onDidChangeTextDocument(event => { 205 | activeEditor = vscode.window.activeTextEditor; 206 | if (event.document === activeEditor.document) { 207 | onDidChange(event); 208 | } 209 | }, null, context.subscriptions); 210 | /***** OnSave */ 211 | 212 | vscode.workspace.onDidSaveTextDocument(document => { 213 | onDidSave(document); 214 | }, null, context.subscriptions); 215 | 216 | /****** OnOpen */ 217 | vscode.workspace.onDidOpenTextDocument(document => { 218 | onDidSave(document); 219 | }, null, context.subscriptions); 220 | 221 | 222 | } 223 | 224 | /* exports */ 225 | exports.activate = onActivate; -------------------------------------------------------------------------------- /src/features/compile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @author github.com/tintinweb 4 | * @license MIT 5 | * 6 | * */ 7 | 8 | const vscode = require("vscode"); 9 | const path = require("path"); 10 | const { CircomJS } = require("@zefi/circomjs") 11 | const settings = require("../settings"); 12 | 13 | 14 | function fixCircuitPaths(c) { 15 | for (let k of Object.keys(c._circuitConfig).filter(key => key.endsWith('Path') || key.endsWith('Dir'))) { 16 | c._circuitConfig[k] = vscode.Uri.joinPath(vscode.workspace.workspaceFolders[0].uri, c._circuitConfig[k]).fsPath 17 | } 18 | return c; 19 | } 20 | 21 | function circomjsExceptionToVscode(e) { 22 | let msg = e.message.replace(/\[[\d;]+m/g, "").replace(/\x1b/gu, ""); 23 | let loc = { 24 | line: 1, 25 | col: 1 26 | } 27 | settings.LOG(msg, '🔴') 28 | 29 | let parts = msg.match(/error\[([^\]]+)\]\s*:\s*([^\n]+)\n/); 30 | 31 | if (!parts) { 32 | parts = [ 33 | "00000", 34 | "00000", 35 | "Circom Compiler Error" 36 | ] 37 | } 38 | 39 | let location = msg.match(/\"([^\"]+)\":(\d+):(\d+)/) 40 | let fname = '.'; 41 | 42 | if (location) { 43 | loc.line = parseInt(location[2]) 44 | loc.col = parseInt(location[3]) 45 | fname = location[1] 46 | } 47 | 48 | const issue = { 49 | code: parts[1], 50 | message: `${e.operator} - ${parts[2]}`, 51 | range: new vscode.Range( 52 | new vscode.Position(loc.line - 1, loc.col - 1), 53 | new vscode.Position(loc.line - 1, 255)), 54 | severity: vscode.DiagnosticSeverity.Error, 55 | relatedInformation: [] 56 | } 57 | 58 | let result = {}; 59 | result[fname] = [issue]; 60 | return result; 61 | } 62 | 63 | async function withCwd(cwd, f) { 64 | const current = process.cwd(); 65 | process.chdir(cwd); 66 | await f(); 67 | process.chdir(current); 68 | } 69 | 70 | class CircomCompiler { 71 | constructor() { 72 | this.diagnosticCollections = { 73 | compiler: vscode.languages.createDiagnosticCollection('Circom Compiler') 74 | }; 75 | this.workspace = vscode.workspace.workspaceFolders[0]; 76 | } 77 | 78 | 79 | async compile(options) { 80 | options = { 81 | //circuitId 82 | //inputFilePath: abspath, 83 | //generateProof = true|false 84 | //verifyProof = true|fale 85 | compile: true, 86 | generateProof: true, 87 | proofInput: undefined, 88 | verifyProof: true, 89 | proofData: undefined, 90 | //merge input options 91 | cancellationToken: undefined, 92 | ...options 93 | } 94 | await withCwd(this.workspace.uri.fsPath, async () => { 95 | const circomjs = new CircomJS(); 96 | const allCids = circomjs.getCIDs(); 97 | let cids = []; 98 | 99 | // fix circuits once and for all 100 | allCids.forEach(cid => fixCircuitPaths(circomjs.getCircuit(cid))); 101 | 102 | if (allCids.includes(options.circuitId)) { 103 | cids.push(options.circuitId); 104 | } 105 | 106 | if (options.inputFilePath) { 107 | allCids.map(c => circomjs.getCircuit(c)) 108 | .filter(f => f._circuitConfig.inputFilePath == options.inputFilePath) 109 | .forEach(c => cids.push(c._circuitConfig.cId)) 110 | } 111 | 112 | 113 | if (!options.circuitId && !options.inputFilePath && !cids.length) { // default to all 114 | cids = allCids; 115 | } 116 | 117 | 118 | for (let cid of [...new Set(cids)]) { 119 | if (options.cancellationToken.isCancellationRequested) { 120 | settings.LOG("", "🐟") 121 | return; 122 | } 123 | 124 | 125 | let circuit = circomjs.getCircuit(cid); 126 | circuit._circuitConfig.baseName = path.basename(circuit._circuitConfig.inputFilePath, this.workspace.uri.fsPath); 127 | 128 | let data = (await vscode.workspace.openTextDocument(vscode.Uri.file(circuit._circuitConfig.inputFilePath))).getText() 129 | if (!data.match(/^\s*component\s+main\s+=[^;]+;\s*/gm)) { 130 | settings.LOG("compiling circuit ... skipped (no main)", "👷", circuit._circuitConfig.baseName); 131 | continue; 132 | } 133 | 134 | if (options.cancellationToken.isCancellationRequested) { 135 | settings.LOG("", "🐟") 136 | return; 137 | } 138 | 139 | if (options.compile) { 140 | settings.LOG("compiling circuit ...", "👷", circuit._circuitConfig.baseName); 141 | await circuit.compile().then(() => { 142 | this.diagnosticCollections.compiler.delete(vscode.Uri.file(circuit._circuitConfig.inputFilePath)); 143 | }).catch((e) => { 144 | const issuesMap = circomjsExceptionToVscode(e); 145 | for (let f of Object.keys(issuesMap)) { 146 | this.diagnosticCollections.compiler.set(vscode.Uri.file(f && f == '.' ? circuit._circuitConfig.inputFilePath : f), issuesMap[f]) 147 | } 148 | }) 149 | } 150 | 151 | if (options.cancellationToken.isCancellationRequested) { 152 | settings.LOG("", "🐟") 153 | return; 154 | } 155 | 156 | let input = options.proofInput; 157 | let proof = options.proofData; 158 | 159 | if (options.generateProof) { 160 | settings.LOG("generateProof for circuit ...", "👷", circuit._circuitConfig.baseName); 161 | if (input === undefined) { 162 | try { 163 | let _inputs = data.match(/\/\*\s*\n\s*(proof.input)\s*=\s*(\{.*\})/s); 164 | if (!_inputs) { 165 | settings.LOG("invalid proof input", "🔴"); 166 | throw new Error("no proof.input data found!") 167 | } 168 | input = JSON.parse(_inputs[2]); 169 | } catch (e) { 170 | settings.LOG("invalid proof input", "🔴"); 171 | vscode.window.showErrorMessage(`🔴 [${circuit._circuitConfig.baseName}] invalid proof input: ${_inputs}`); 172 | continue 173 | } 174 | 175 | } 176 | 177 | try { 178 | settings.LOG("generating proof for input:", "👾", circuit._circuitConfig.baseName, input); 179 | proof = await circuit.genProof(input); 180 | settings.LOG("proof generated:", "👾", circuit._circuitConfig.baseName, proof); 181 | } catch (e){ 182 | settings.LOG(e, "🔴", circuit._circuitConfig.baseName); 183 | vscode.window.showErrorMessage(`🔴 [${circuit._circuitConfig.baseName}] exception generating proof for input: ${JSON.stringify(input)}`); 184 | continue 185 | } 186 | 187 | } 188 | 189 | if (options.cancellationToken.isCancellationRequested) { 190 | settings.LOG("", "🐟") 191 | return; 192 | } 193 | 194 | if (options.verifyProof) { 195 | if (proof === undefined) { 196 | try { 197 | let _proof = data.match(/\/\*\s*\n\s*(proof.verify)\s*=\s*(\{.*\})/s) 198 | if (!_proof) { 199 | throw new Error("no proof.verify data found!") 200 | } 201 | input = JSON.parse(_proof[2]); 202 | } catch (e) { 203 | settings.LOG("invalid proof data", "🔴"); 204 | vscode.window.showErrorMessage(`🔴 [${circuit._circuitConfig.baseName}] invalid proof data: ${_proof}`); 205 | continue 206 | } 207 | 208 | } 209 | try { 210 | const res = await circuit.verifyProof(proof); 211 | if (res) { 212 | settings.LOG(`proof verification successful!`, '👑', circuit._circuitConfig.baseName, res) 213 | vscode.window.showInformationMessage(`✔️ [${circuit._circuitConfig.baseName}] proof verification successful!`); 214 | } 215 | else { 216 | settings.LOG(`proof verification failed!`, '🔴', circuit._circuitConfig.baseName, res) 217 | vscode.window.showErrorMessage(`🔴 [${circuit._circuitConfig.baseName}] proof verification failed!`); 218 | } 219 | } catch(e){ 220 | settings.LOG(e, "🔴", circuit._circuitConfig.baseName); 221 | vscode.window.showErrorMessage(`🔴 [${circuit._circuitConfig.baseName}] exception verifying proof: ${JSON.stringify(proof)}`); 222 | continue 223 | } 224 | 225 | } 226 | } 227 | }); 228 | settings.LOG("done compiling circuit", '🏁') 229 | } 230 | 231 | async proof(curcuitId, input) { 232 | await withCwd(this.workspace.uri.fsPath, async () => { 233 | const circomjs = new CircomJS(); 234 | const cids = curcuitId ? [curcuitId] : circomjs.getCIDs(); 235 | for (let cid of cids) { 236 | let circuit = fixCircuitPaths(circomjs.getCircuit(cid)); 237 | settings.LOG("compile ...", "👷", path.basename(circuit._circuitConfig.inputFilePath, this.workspace.uri.fsPath)); 238 | await circuit.compile(); 239 | 240 | input = input || { 241 | a: 3, 242 | b: 5 243 | } 244 | const proof = await circuit.genProof(input); 245 | console.settings.LOG(proof) 246 | const res = await circuit.verifyProof(proof); 247 | console.settings.LOG(res) 248 | if (res) { 249 | console.settings.LOG("verification succeed") 250 | } 251 | else { 252 | console.settings.LOG("verification failed") 253 | } 254 | } 255 | }); 256 | settings.LOG("proof", '🏁') 257 | } 258 | } 259 | 260 | module.exports = { 261 | CircomCompiler 262 | }; --------------------------------------------------------------------------------