Skip to content

Commit 8fe879e

Browse files
committed
Noir support
1 parent 17a641d commit 8fe879e

7 files changed

Lines changed: 187 additions & 18 deletions

File tree

README.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ Deploy and verify your circuits to Circuitscan with a simple command.
55
See also: [Circuitscan CLI Documentation](https://circuitscan.readthedocs.io/en/latest/usage.html)
66

77
> [!NOTE]
8-
> Noir support coming soon!
9-
>
108
> Supports circom 2.0.8-2.1.9, snarkjs 0.6.11-0.7.4
9+
>
10+
> Supports noir 0.31.0-0.34.0
1111
1212

1313
## Installation
@@ -154,6 +154,39 @@ Found 1 file(s):
154154
https://circuitscan.org/chain/137/address/0x269e831b930f4c1ec7eee28aa53e5756b0f96d0c
155155
```
156156

157+
### verify:noir
158+
159+
```
160+
Usage: circuitscan verify:noir [options] <chainId> <verifierContractAddress> [packageDir]
161+
162+
Verify verifier contracts by their noir sources. Can also specify chain by name.
163+
164+
Options:
165+
-v, --nargo-version <version> Specify nargo version
166+
-i, --instance <memorySize> Specify the memory (GB) of compiler instance: 4, 8, 16, 32, 64, 128, 256, 384, 512 (default: 4 for smallest circuits)
167+
-r, --resume <requestId> In case of errors during compilation, reattach to a job and attempt a new deploy. Overrides all other options.
168+
-c, --config <configUrl> Specify a different configuration file (default: https://circuitscan.org/cli.json)
169+
-a, --api-key <apiKey> Specify your API Key as a command line argument
170+
-h, --help display help for command
171+
```
172+
173+
### deploy:noir
174+
175+
```
176+
Usage: circuitscan deploy:noir [options] <chainId> [packageDir]
177+
178+
Deploy verifier contracts by their noir sources. Can also specify chain by name.
179+
180+
Options:
181+
-v, --nargo-version <version> Specify nargo version
182+
-i, --instance <memorySize> Specify the memory (GB) of compiler instance: 4, 8, 16, 32, 64, 128, 256, 384, 512 (default: 4 for smallest circuits)
183+
-r, --resume <requestId> In case of errors during compilation, reattach to a job and attempt a new deploy. Overrides all other options.
184+
-c, --config <configUrl> Specify a different configuration file (default: https://circuitscan.org/cli.json)
185+
-a, --api-key <apiKey> Specify your API Key as a command line argument
186+
-b, --browser-wallet Send transaction in browser instead of by passing private key env var (overrides chainId argument)
187+
-h, --help display help for command
188+
```
189+
157190
## Additional Configuration
158191

159192
Env Var | Description

cli.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77

88
import circomCommands from './src/circom/index.js';
99
import circomMultiCommands from './src/circomMulti/index.js';
10+
import noirCommands from './src/noir/index.js';
1011

1112
const program = new Command();
1213

@@ -18,5 +19,6 @@ program
1819
// Each pipeline adds its commands
1920
circomCommands(program);
2021
circomMultiCommands(program);
22+
noirCommands(program);
2123

2224
program.parse(process.argv);

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "circuitscan",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
44
"main": "cli.js",
55
"type": "module",
66
"author": "numtel <ben@latenightsketches.com>",

src/circom/index.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,13 @@ async function verify(file, chainId, contractAddr, options) {
5555
if(!chain) throw new Error('INVALID_CHAIN');
5656
try {
5757
const compiled = await compileFile(file, options);
58-
await verifyCircuit(compiled.pkgName, chain.id, contractAddr, options);
58+
await verifyCircuit(
59+
'verifyCircom',
60+
compiled.pkgName,
61+
chain.id,
62+
contractAddr,
63+
options,
64+
);
5965
} catch(error) {
6066
console.error(error);
6167
process.exit(1);
@@ -75,6 +81,7 @@ async function deploy(file, chainId, options) {
7581
const contractSource = await (await fetch(`${options.config.blobUrl}build/${compiled.pkgName}/verifier.sol`)).text();
7682
const deployResult = await deployAndVerifyContractFromSource(contractSource, chain, privateKey, options);
7783
await verifyCircuit(
84+
'verifyCircom',
7885
compiled.pkgName,
7986
deployResult.chain.id,
8087
deployResult.contractAddress,

src/circuitscan.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,8 @@ export async function invokeRemoteMachine(payload, options) {
9999
return body;
100100
}
101101

102-
export async function verifyCircuit(pkgName, chainId, contractAddr, options) {
103-
const event = {
104-
payload: {
105-
action: 'verifyCircom',
106-
pkgName,
107-
chainId,
108-
contract: contractAddr,
109-
},
110-
};
102+
export async function verifyCircuit(action, pkgName, chainId, contract, options) {
103+
const event = {payload: {action, pkgName, chainId, contract}};
111104
console.log(`# Verifying circuit...`);
112105

113106
const response = await fetch(options.config.serverURL, {
@@ -128,7 +121,7 @@ export async function verifyCircuit(pkgName, chainId, contractAddr, options) {
128121

129122
if(body && body.status === 'verified') {
130123
console.log(`# Completed successfully!`);
131-
console.log(`\nhttps://circuitscan.org/chain/${chainId}/address/${contractAddr}`);
124+
console.log(`\nhttps://circuitscan.org/chain/${chainId}/address/${contract}`);
132125
} else {
133126
console.log(`# Verification failed.`);
134127
}

src/noir/index.js

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import {readdirSync, statSync, readFileSync} from 'node:fs';
2+
import {join, extname, resolve} from 'node:path';
3+
4+
import {isHex} from 'viem';
5+
6+
import {
7+
instanceSizes,
8+
loadConfig,
9+
viemChain,
10+
DEFAULT_CONFIG,
11+
} from '../utils.js';
12+
import {deployAndVerifyContractFromSource} from '../solidity.js';
13+
14+
import {
15+
invokeRemoteMachine,
16+
verifyCircuit,
17+
} from '../circuitscan.js';
18+
19+
const DEFAULT_NARGO = "0.33.0";
20+
const VERSIONS = {
21+
"0.34.0": "0.55.0",
22+
"0.33.0": "0.47.1",
23+
"0.32.0": "0.46.1",
24+
"0.31.0": "0.41.0",
25+
};
26+
27+
export default function(program) {
28+
program
29+
.command('verify:noir <chainId> <verifierContractAddress> [packageDir]')
30+
.description('Verify verifier contracts by their noir sources. Can also specify chain by name.')
31+
.option('-v, --nargo-version <version>', 'Specify nargo version')
32+
.option('-i, --instance <memorySize>', `Specify the memory (GB) of compiler instance: ${Object.keys(instanceSizes).join(', ')} (default: 4 for smallest circuits)`)
33+
.option('-r, --resume <requestId>', 'In case of errors during compilation, reattach to a job and attempt a new deploy. Overrides all other options.')
34+
.option('-c, --config <configUrl>', `Specify a different configuration file (default: ${DEFAULT_CONFIG})`)
35+
.option('-a, --api-key <apiKey>', `Specify your API Key as a command line argument`)
36+
.action(verify);
37+
38+
program
39+
.command('deploy:noir <chainId> [packageDir]')
40+
.description('Deploy verifier contracts by their noir sources. Can also specify chain by name.')
41+
.option('-v, --nargo-version <version>', 'Specify nargo version')
42+
.option('-i, --instance <memorySize>', `Specify the memory (GB) of compiler instance: ${Object.keys(instanceSizes).join(', ')} (default: 4 for smallest circuits)`)
43+
.option('-r, --resume <requestId>', 'In case of errors during compilation, reattach to a job and attempt a new deploy. Overrides all other options.')
44+
.option('-c, --config <configUrl>', `Specify a different configuration file (default: ${DEFAULT_CONFIG})`)
45+
.option('-a, --api-key <apiKey>', `Specify your API Key as a command line argument`)
46+
.option('-b, --browser-wallet', 'Send transaction in browser instead of by passing private key env var (overrides chainId argument)')
47+
.action(deploy);
48+
}
49+
50+
async function verify(chainId, contractAddr, packageDir, options) {
51+
options = await loadConfig(options);
52+
const chain = viemChain(chainId);
53+
if(!chain) throw new Error('INVALID_CHAIN');
54+
const compiled = await compileFile(packageDir, options);
55+
await verifyCircuit(
56+
'verifyNoir',
57+
compiled.pkgName,
58+
chain.id,
59+
contractAddr,
60+
options,
61+
);
62+
}
63+
64+
async function deploy(chainId, packageDir, options) {
65+
options = await loadConfig(options);
66+
const chain = viemChain(chainId);
67+
if(!options.browserWallet && !chain) throw new Error('INVALID_CHAIN');
68+
const privateKey = process.env.DEPLOYER_PRIVATE_KEY;
69+
if(!options.browserWallet && (!privateKey || !isHex(privateKey) || privateKey.length !== 66))
70+
throw new Error('INVALID_DEPLOYER_PRIVATE_KEY')
71+
const compiled = await compileFile(packageDir, options);
72+
const contractSource = await (await fetch(`${options.config.blobUrl}build/${compiled.pkgName}/verifier.sol`)).text();
73+
const deployResult = await deployAndVerifyContractFromSource(contractSource, chain, privateKey, options);
74+
await verifyCircuit(
75+
'verifyNoir',
76+
compiled.pkgName,
77+
deployResult.chain.id,
78+
deployResult.contractAddress,
79+
options,
80+
);
81+
}
82+
83+
async function compileFile(packageDir, options) {
84+
packageDir = resolve(packageDir || '.');
85+
const fileList = listFilesWithExtension(packageDir, '.nr').map(x => resolve(x).slice(packageDir.length + 1));
86+
const files = fileList.map(filename => {
87+
return {
88+
filename,
89+
content: readFileSync(join(packageDir, filename), 'utf8'),
90+
};
91+
});
92+
const nargoToml = readFileSync(join(packageDir, 'Nargo.toml'), 'utf8');
93+
const nargoVersion = options.nargoVersion || DEFAULT_NARGO;
94+
if(!VERSIONS.hasOwnProperty(nargoVersion))
95+
throw new error('INVALID_NARGO_VERSION');
96+
const payload = {
97+
pipeline: 'noir',
98+
files,
99+
nargoToml,
100+
nargoVersion,
101+
bbupVersion: VERSIONS[nargoVersion],
102+
};
103+
const compiled = await invokeRemoteMachine(payload, options);
104+
if('errorMessage' in compiled) {
105+
throw new Error(compiled.errorMessage);
106+
}
107+
return compiled;
108+
}
109+
110+
function listFilesWithExtension(dirPath, extension, fileList = []) {
111+
const files = readdirSync(dirPath);
112+
113+
files.forEach(file => {
114+
const fullPath = join(dirPath, file);
115+
const stat = statSync(fullPath);
116+
117+
if (stat.isDirectory()) {
118+
listFilesWithExtension(fullPath, extension, fileList);
119+
} else if (extname(fullPath) === extension) {
120+
fileList.push(fullPath);
121+
}
122+
});
123+
124+
return fileList;
125+
}

src/solidity.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,19 @@ export function compileContract(source) {
198198
};
199199
}
200200

201-
function findContractName(soliditySource) {
202-
const regex = /contract\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\{/;
203-
const match = soliditySource.match(regex);
204-
return match ? match[1] : null;
201+
function findContractName(soliditySource, returnAll) {
202+
const regex = /contract\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:is\s+[a-zA-Z_][a-zA-Z0-9_,\s]*)?\s*\{/g;
203+
const matches = [];
204+
205+
for (const match of soliditySource.matchAll(regex)) {
206+
matches.push(match[1]);
207+
}
208+
209+
// Return the last contract by default
210+
// Because noir outputs 2 contracts in the verifier file
211+
if(!returnAll && matches.length > 0) return matches[matches.length - 1];
212+
213+
return matches.length > 0 ? matches : null;
205214
}
206215

207216
function standardJson(soliditySource) {

0 commit comments

Comments
 (0)