├── 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 | [
](https://thecreed.xyz/)
2 | [[ 🌐 ](https://thecreed.xyz/)[ 🫂 ](https://community.thecreed.xyz/c/start-here)]
3 |
4 |
5 |
6 |
7 |
8 | # 👩💻 vscode extension
9 |
10 |
11 | ### Snippets
12 |
13 | [
](https://user-images.githubusercontent.com/2865694/233330364-4ade0a9a-23f4-4083-a9dc-369169ce9a15.gif)
14 |
15 |
16 | ### Compilation, Proof Generation, and Verification
17 |
18 | [
](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 | [
](https://user-images.githubusercontent.com/2865694/233328327-57561823-bd88-4288-a44c-cee6325c5465.gif)
35 |
36 | ### Commands
37 |
38 | [
](https://user-images.githubusercontent.com/2865694/233327059-5579da45-a464-43b2-af4a-3c4f332511c1.png)
39 |
40 | ### Settings
41 |
42 | [
](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 | };
--------------------------------------------------------------------------------