mirror of
https://github.com/arnaucube/circom.git
synced 2026-02-07 11:16:42 +01:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6985892f86 | ||
|
|
bacb7afde7 | ||
|
|
d04eff6c0d | ||
|
|
230894921e | ||
|
|
64029e1842 | ||
|
|
700412f23d | ||
|
|
832077fbe9 | ||
|
|
0df0ac712d | ||
|
|
67a35ee400 | ||
|
|
680e3fe139 | ||
|
|
f05c4e1338 | ||
|
|
597deb1eaa | ||
|
|
7a1c606ca6 | ||
|
|
6642d4cf93 | ||
|
|
da0c60a919 | ||
|
|
534efcf355 | ||
|
|
a43154241e | ||
|
|
859c98d2a4 | ||
|
|
8048a5ef7d | ||
|
|
b7a41cda14 | ||
|
|
34049f2fbd | ||
|
|
a602551ee5 | ||
|
|
4d5760ff67 | ||
|
|
4a8bcff3da | ||
|
|
b8068e8d05 | ||
|
|
54092044ae | ||
|
|
11275d59d9 | ||
|
|
b0607a6e53 | ||
|
|
5fccdd6ef1 | ||
|
|
6611f2f024 | ||
|
|
e37386115c | ||
|
|
b6a00c6d17 | ||
|
|
b0c21ce622 | ||
|
|
b10b574816 | ||
|
|
3a4352afbe | ||
|
|
23f153e91d |
@@ -9,7 +9,9 @@ In particular, it is designed to work in [zksnarks JavaScript library](https://g
|
|||||||
|
|
||||||
### Tutorial
|
### Tutorial
|
||||||
|
|
||||||
A good starting point [is this tutorial](https://iden3.io/blog/circom-and-snarkjs-tutorial2.html)
|
A good starting point [is this tutorial](https://github.com/iden3/circom/blob/master/TUTORIAL.md)
|
||||||
|
|
||||||
|
Also this [video](https://www.youtube.com/watch?v=-9TJa1hVsKA) is a good starting point.
|
||||||
|
|
||||||
### First circuit
|
### First circuit
|
||||||
|
|
||||||
|
|||||||
254
TUTORIAL.md
Normal file
254
TUTORIAL.md
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
# circom and snarkjs tutorial
|
||||||
|
|
||||||
|
This tutorial will guide you in creating your first Zero Knowledge zkSnark circuit. It will take you through the various techniques to write circuits, and will show you how to create proofs and verify them off-chain and on-chain on Ethereum.
|
||||||
|
|
||||||
|
## 1. Installing the tools
|
||||||
|
|
||||||
|
### 1.1 Pre-requisites
|
||||||
|
|
||||||
|
If you don't have it installed yet, you need to install `Node.js`.
|
||||||
|
|
||||||
|
The last stable version of `Node.js` (or 8.12.0) works just fine, but if you install the latest current version `Node.js` (10.12.0) you will see a significant increase in performance. This is because last versions of node includes Big Integer Libraries nativelly. The `snarkjs` library makes use of this feature if available, and this improves the performance x10 (!).
|
||||||
|
|
||||||
|
### 1.2 Install **circom** and **snarkjs**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install -g circom
|
||||||
|
npm install -g snarkjs
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 2. Working with a circuit
|
||||||
|
|
||||||
|
Let's create a circuit that tries to prove that you are able to factor a number!
|
||||||
|
|
||||||
|
### 2.1 Create a circuit in a new directory
|
||||||
|
|
||||||
|
1. Create an empty directory called `factor` where you will put all the files that you will use in this tutorial.
|
||||||
|
```
|
||||||
|
mkdir factor
|
||||||
|
cd factor
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
> In a real circuit, you will probably want to create a `git` repository with a `circuits` directory and a `test` directory with all your tests, and the needed scripts to build all the circuits.
|
||||||
|
|
||||||
|
2. Create a new file named `circuit.circom` with the following content:
|
||||||
|
|
||||||
|
```
|
||||||
|
template Multiplier() {
|
||||||
|
signal private input a;
|
||||||
|
signal private input b;
|
||||||
|
signal output c;
|
||||||
|
|
||||||
|
c <== a*b;
|
||||||
|
}
|
||||||
|
|
||||||
|
component main = Multiplier();
|
||||||
|
```
|
||||||
|
|
||||||
|
This circuit has 2 private input signals named `a` and `b` and one output named `c`.
|
||||||
|
|
||||||
|
The only thing that the circuit does is forcing the signal `c` to be the value of `a*b`
|
||||||
|
|
||||||
|
After declaring the `Multiplier` template, we instantiate it with a component named`main`.
|
||||||
|
|
||||||
|
Note: When compiling a circuit, a component named `main` must always exist.
|
||||||
|
|
||||||
|
### 2.2 Compile the circuit
|
||||||
|
|
||||||
|
We are now ready to compile the circuit. Run the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
circom circuit.circom -o circuit.json
|
||||||
|
```
|
||||||
|
|
||||||
|
to compile the circuit to a file named `circuit.json`
|
||||||
|
|
||||||
|
|
||||||
|
## 3. Taking the compiled circuit to *snarkjs*
|
||||||
|
|
||||||
|
Now that the circuit is compiled, we will continue with `snarkjs`.
|
||||||
|
Please note that you can always access the help of `snarkjs` by typing:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
snarkjs --help
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.1 View information and stats regarding a circuit
|
||||||
|
|
||||||
|
To show general statistics of this circuit, you can run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
snarkjs info -c circuit.json
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also print the constraints of the circuit by running:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
snarkjs printconstraints -c circuit.json
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 3.2 Setting up using *snarkjs*
|
||||||
|
|
||||||
|
|
||||||
|
Ok, let's run a setup for our circuit:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
snarkjs setup
|
||||||
|
```
|
||||||
|
|
||||||
|
> By default `snarkjs` will look for and use `circuit.json`. You can always specify a different circuit file by adding `-c <circuit JSON file name>`
|
||||||
|
|
||||||
|
The output of the setup will in the form of 2 files: `proving_key.json` and `verification_key.json`
|
||||||
|
|
||||||
|
### 3.3. Calculating a witness
|
||||||
|
|
||||||
|
Before creating any proof, we need to calculate all the signals of the circuit that match (all) the constrains of the circuit.
|
||||||
|
|
||||||
|
`snarkjs` calculates those for you. You need to provide a file with the inputs and it will execute the circuit and calculate all the intermediate signals and the output. This set of signals is the *witness*.
|
||||||
|
|
||||||
|
The zero knowledge proofs prove that you know a set of signals (witness) that match all the constraints, without revealing any of the signals except the public inputs plus the outputs.
|
||||||
|
|
||||||
|
For example, imagine you want to prove you are able to factor 33. It means that you know two numbers `a` and `b` and when you multiply them, it results in 33.
|
||||||
|
|
||||||
|
> Of course you can always use one and the same number as `a` and `b`. We will deal with this problem later.
|
||||||
|
|
||||||
|
So you want to prove that you know 3 and 11.
|
||||||
|
|
||||||
|
Let's create a file named `input.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"a": 3, "b": 11}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now let's calculate the witness:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
snarkjs calculatewitness
|
||||||
|
```
|
||||||
|
|
||||||
|
You may want to take a look at `witness.json` file with all the signals.
|
||||||
|
|
||||||
|
### Create the proof
|
||||||
|
|
||||||
|
Now that we have the witness generated, we can create the proof.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
snarkjs proof
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will use the `proving_key.json` and the `witness.json` files by default to generate `proof.json` and `public.json`
|
||||||
|
|
||||||
|
The `proof.json` file will contain the actual proof. And the `public.json` file will contain just the values of the public inputs and the outputs.
|
||||||
|
|
||||||
|
|
||||||
|
### Verifying the proof
|
||||||
|
|
||||||
|
To verify the proof run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
snarkjs verify
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will use `verification_key.json`, `proof.json` and `public.json` to verify that is valid.
|
||||||
|
|
||||||
|
Here we are verifying that we know a witness that the public inputs and the outputs matches the ones in the `public.json` file.
|
||||||
|
|
||||||
|
|
||||||
|
If the proof is ok, you will see `OK` or `INVALID` if not ok.
|
||||||
|
|
||||||
|
### Generate the solidity verifier
|
||||||
|
|
||||||
|
```sh
|
||||||
|
snarkjs generateverifier
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will take the `verification_key.json` and generate solidity code in `verifier.sol` file.
|
||||||
|
|
||||||
|
You can take the code in `verifier.sol` and cut and paste it in remix.
|
||||||
|
|
||||||
|
This code contains two contracts: Pairings and Verifier. You only need to deploy the `Verifier` contract.
|
||||||
|
|
||||||
|
> You may want to use a test net like Rinkeby, Kovan or Ropsten. You can also use the Javascript VM, but in some browsers, the verification takes long and it may hang the page.
|
||||||
|
|
||||||
|
|
||||||
|
### Verifying the proof on-chain
|
||||||
|
|
||||||
|
The verifier contract deployed in the last step has a `view` function called `verifyProof`.
|
||||||
|
|
||||||
|
This function will return true if the proof and the inputs are valid.
|
||||||
|
|
||||||
|
To facilitate the call, you can use snarkjs to generate the parameters of the call by typing:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
snarkjs generatecall
|
||||||
|
```
|
||||||
|
|
||||||
|
Just cut and paste the output to the parameters field of the `verifyProof` method in Remix.
|
||||||
|
|
||||||
|
If every thing works ok, this method should return true.
|
||||||
|
|
||||||
|
If you change any bit in the parameters, the result will be verifiably false.
|
||||||
|
|
||||||
|
|
||||||
|
## Bonus track
|
||||||
|
|
||||||
|
We can fix the circuit to not accept one as any of the values by adding some extra constraints.
|
||||||
|
|
||||||
|
Here the trick is that we use the property that 0 has no inverse. So `(a-1)` should not have an inverse.
|
||||||
|
|
||||||
|
That means that `(a-1)*inv = 1` will be inpossible to match if `a` is 1.
|
||||||
|
|
||||||
|
We just calculate inv by `1/(a-1)`
|
||||||
|
|
||||||
|
So let's modify the circuit:
|
||||||
|
|
||||||
|
```
|
||||||
|
template Multiplier() {
|
||||||
|
signal private input a;
|
||||||
|
signal private input b;
|
||||||
|
signal output c;
|
||||||
|
signal inva;
|
||||||
|
signal invb;
|
||||||
|
|
||||||
|
inva <-- 1/(a-1);
|
||||||
|
(a-1)*inva === 1;
|
||||||
|
|
||||||
|
invb <-- 1/(b-1);
|
||||||
|
(b-1)*invb === 1;
|
||||||
|
|
||||||
|
c <== a*b;
|
||||||
|
}
|
||||||
|
|
||||||
|
component main = Multiplier();
|
||||||
|
```
|
||||||
|
|
||||||
|
A nice thing of the circom language is that you can split a <== into two independent actions: <-- and ===
|
||||||
|
|
||||||
|
The <-- and --> operators assign a value to a signal without creating any constraints.
|
||||||
|
|
||||||
|
The === operator adds a constraint without assigning any value to any signal.
|
||||||
|
|
||||||
|
The circuit also has another problem: the operation works in Zr, so we need to guarantee the multiplication does not overflow. This can be done by converting the inputs to binary and checking the ranges, but we will reserve it for future tutorials.
|
||||||
|
|
||||||
|
## Where to go from here:
|
||||||
|
|
||||||
|
You may want to read the [README](https://github.com/iden3/circom) to learn more features about circom.
|
||||||
|
|
||||||
|
You can also check a library with many basic circuits lib binarizations, comparators, eddsa, hashes, merkle trees etc [here](https://github.com/iden3/circomlib) (Work in progress).
|
||||||
|
|
||||||
|
|
||||||
|
Or a exponentiation in the Baby Jub curve [here](https://github.com/iden3/circomlib) (Work in progress).
|
||||||
|
|
||||||
|
|
||||||
|
# Final note
|
||||||
|
|
||||||
|
There is nothing worse for a dev than working with a buggy compiler. This is a very early stage of the compiler, so there are many bugs and lots of work needs to be done. Please have it present if you are doing anything serious with it.
|
||||||
|
|
||||||
|
And please contact us for any isue you have. In general, a github issue with a small piece of code with the bug is very useful to us.
|
||||||
|
|
||||||
|
Enjoy zero knowledge proving!
|
||||||
44
circuit.json
44
circuit.json
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"mainCode": "{\n}\n",
|
|
||||||
"signalName2Idx": {
|
|
||||||
"one": 0,
|
|
||||||
"main.out": 1
|
|
||||||
},
|
|
||||||
"components": [
|
|
||||||
{
|
|
||||||
"name": "main",
|
|
||||||
"params": {},
|
|
||||||
"template": "A",
|
|
||||||
"inputSignals": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"componentName2Idx": {
|
|
||||||
"main": 0
|
|
||||||
},
|
|
||||||
"signals": [
|
|
||||||
{
|
|
||||||
"names": [
|
|
||||||
"one"
|
|
||||||
],
|
|
||||||
"triggerComponents": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"names": [
|
|
||||||
"main.out"
|
|
||||||
],
|
|
||||||
"triggerComponents": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"constraints": [],
|
|
||||||
"templates": {
|
|
||||||
"A": "function(ctx) {\n ctx.setSignal(\"out\", [], \"3\");\n ctx.assert(ctx.getSignal(\"out\", []), \"3\");\n}\n"
|
|
||||||
},
|
|
||||||
"functions": {},
|
|
||||||
"nPrvInputs": 0,
|
|
||||||
"nPubInputs": 0,
|
|
||||||
"nInputs": 0,
|
|
||||||
"nOutputs": 0,
|
|
||||||
"nVars": 1,
|
|
||||||
"nConstants": 1,
|
|
||||||
"nSignals": 2
|
|
||||||
}
|
|
||||||
3
cli.js
3
cli.js
@@ -35,6 +35,7 @@ const argv = require("yargs")
|
|||||||
.help("h")
|
.help("h")
|
||||||
.alias("h", "help")
|
.alias("h", "help")
|
||||||
.alias("v", "verbose")
|
.alias("v", "verbose")
|
||||||
|
.alias("f", "fast")
|
||||||
.epilogue(`Copyright (C) 2018 0kims association
|
.epilogue(`Copyright (C) 2018 0kims association
|
||||||
This program comes with ABSOLUTELY NO WARRANTY;
|
This program comes with ABSOLUTELY NO WARRANTY;
|
||||||
This is free software, and you are welcome to redistribute it
|
This is free software, and you are welcome to redistribute it
|
||||||
@@ -56,7 +57,7 @@ if (argv._.length == 0) {
|
|||||||
const fullFileName = path.resolve(process.cwd(), inputFile);
|
const fullFileName = path.resolve(process.cwd(), inputFile);
|
||||||
const outName = argv.output ? argv.output : "circuit.json";
|
const outName = argv.output ? argv.output : "circuit.json";
|
||||||
|
|
||||||
compiler(fullFileName).then( (cir) => {
|
compiler(fullFileName, {reduceConstraints: !argv.fast}).then( (cir) => {
|
||||||
fs.writeFileSync(outName, JSON.stringify(cir, null, 1), "utf8");
|
fs.writeFileSync(outName, JSON.stringify(cir, null, 1), "utf8");
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
|
|||||||
1213
package-lock.json
generated
1213
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "circom",
|
"name": "circom",
|
||||||
"version": "0.0.23",
|
"version": "0.0.34",
|
||||||
"description": "Language to generate logic circuits",
|
"description": "Language to generate logic circuits",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
@@ -34,10 +34,10 @@
|
|||||||
"yargs": "^12.0.2"
|
"yargs": "^12.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chai": "^4.1.2",
|
"chai": "^4.2.0",
|
||||||
"eslint": "^5.0.1",
|
"eslint": "^5.16.0",
|
||||||
"eslint-plugin-mocha": "^5.0.0",
|
"eslint-plugin-mocha": "^5.3.0",
|
||||||
"jison": "^0.4.18",
|
"jison": "^0.4.18",
|
||||||
"snarkjs": "0.1.8"
|
"snarkjs": "0.1.14"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ if { return 'if'; }
|
|||||||
else { return 'else'; }
|
else { return 'else'; }
|
||||||
for { return 'for'; }
|
for { return 'for'; }
|
||||||
while { return 'while'; }
|
while { return 'while'; }
|
||||||
|
compute { return 'compute'; }
|
||||||
do { return 'do'; }
|
do { return 'do'; }
|
||||||
return { return 'return'; }
|
return { return 'return'; }
|
||||||
include { return 'include'; }
|
include { return 'include'; }
|
||||||
@@ -198,6 +199,10 @@ statment
|
|||||||
{
|
{
|
||||||
$$ = $1;
|
$$ = $1;
|
||||||
}
|
}
|
||||||
|
| computeStatment
|
||||||
|
{
|
||||||
|
$$ = $1;
|
||||||
|
}
|
||||||
| returnStatment
|
| returnStatment
|
||||||
{
|
{
|
||||||
$$ = $1;
|
$$ = $1;
|
||||||
@@ -302,6 +307,14 @@ doWhileStatment
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
computeStatment
|
||||||
|
: 'compute' statment
|
||||||
|
{
|
||||||
|
$$ = { type: "COMPUTE", body: $2 };
|
||||||
|
setLines($$, @1, @2);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
returnStatment
|
returnStatment
|
||||||
: 'return' expression ';'
|
: 'return' expression ';'
|
||||||
{
|
{
|
||||||
@@ -514,7 +527,7 @@ e12
|
|||||||
: e12 '^' e11
|
: e12 '^' e11
|
||||||
{
|
{
|
||||||
if (($1.type == "NUMBER") && ($3.type == "NUMBER")) {
|
if (($1.type == "NUMBER") && ($3.type == "NUMBER")) {
|
||||||
$$ = { type: "NUMBER", value: $1.value.or($3.value).and(__MASK__) };
|
$$ = { type: "NUMBER", value: $1.value.xor($3.value).and(__MASK__) };
|
||||||
} else {
|
} else {
|
||||||
$$ = { type: "OP", op: "^", values: [$1, $3] };
|
$$ = { type: "OP", op: "^", values: [$1, $3] };
|
||||||
}
|
}
|
||||||
|
|||||||
332
parser/jaz.js
332
parser/jaz.js
File diff suppressed because one or more lines are too long
105
src/compiler.js
105
src/compiler.js
@@ -33,7 +33,13 @@ const parser = require("../parser/jaz.js").parser;
|
|||||||
|
|
||||||
const timeout = ms => new Promise(res => setTimeout(res, ms));
|
const timeout = ms => new Promise(res => setTimeout(res, ms));
|
||||||
|
|
||||||
async function compile(srcFile) {
|
async function compile(srcFile, options) {
|
||||||
|
if (!options) {
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
if (typeof options.reduceConstraints === "undefined") {
|
||||||
|
options.reduceConstraints = true;
|
||||||
|
}
|
||||||
const fullFileName = srcFile;
|
const fullFileName = srcFile;
|
||||||
const fullFilePath = path.dirname(fullFileName);
|
const fullFilePath = path.dirname(fullFileName);
|
||||||
|
|
||||||
@@ -70,7 +76,9 @@ async function compile(srcFile) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
classifySignals(ctx);
|
classifySignals(ctx);
|
||||||
|
|
||||||
reduceConstants(ctx);
|
reduceConstants(ctx);
|
||||||
|
if (options.reduceConstraints) {
|
||||||
|
|
||||||
// Repeat while reductions are performed
|
// Repeat while reductions are performed
|
||||||
let oldNConstrains = -1;
|
let oldNConstrains = -1;
|
||||||
@@ -78,6 +86,7 @@ async function compile(srcFile) {
|
|||||||
oldNConstrains = ctx.constraints.length;
|
oldNConstrains = ctx.constraints.length;
|
||||||
reduceConstrains(ctx);
|
reduceConstrains(ctx);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
generateWitnessNames(ctx);
|
generateWitnessNames(ctx);
|
||||||
|
|
||||||
@@ -217,8 +226,12 @@ function reduceConstants(ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function reduceConstrains(ctx) {
|
function reduceConstrains(ctx) {
|
||||||
const newConstraints = [];
|
indexVariables();
|
||||||
for (let i=0; i<ctx.constraints.length; i++) {
|
let possibleConstraints = Object.keys(ctx.constraints);
|
||||||
|
while (possibleConstraints.length>0) {
|
||||||
|
let nextPossibleConstraints = {};
|
||||||
|
for (let i in possibleConstraints) {
|
||||||
|
if (!ctx.constraints[i]) continue;
|
||||||
const c = ctx.constraints[i];
|
const c = ctx.constraints[i];
|
||||||
|
|
||||||
// Swap a and b if b has more variables.
|
// Swap a and b if b has more variables.
|
||||||
@@ -245,6 +258,13 @@ function reduceConstrains(ctx) {
|
|||||||
if (lc.isZero(c.a) || lc.isZero(c.b)) {
|
if (lc.isZero(c.a) || lc.isZero(c.b)) {
|
||||||
const isolatedSignal = getFirstInternalSignal(ctx, c.c);
|
const isolatedSignal = getFirstInternalSignal(ctx, c.c);
|
||||||
if (isolatedSignal) {
|
if (isolatedSignal) {
|
||||||
|
|
||||||
|
let lSignal = ctx.signals[isolatedSignal];
|
||||||
|
while (lSignal.equivalence) {
|
||||||
|
lSignal = ctx.signals[lSignal.equivalence];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const isolatedSignalEquivalence = {
|
const isolatedSignalEquivalence = {
|
||||||
type: "LINEARCOMBINATION",
|
type: "LINEARCOMBINATION",
|
||||||
values: {}
|
values: {}
|
||||||
@@ -259,24 +279,81 @@ function reduceConstrains(ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let j=0; j<newConstraints.length; j++) {
|
for (let j in lSignal.inConstraints) {
|
||||||
newConstraints[j] = lc.substitute(newConstraints[j], isolatedSignal, isolatedSignalEquivalence);
|
if ((j!=i)&&(ctx.constraints[j])) {
|
||||||
}
|
|
||||||
for (let j=i+1; j<ctx.constraints.length; j++ ) {
|
|
||||||
ctx.constraints[j] = lc.substitute(ctx.constraints[j], isolatedSignal, isolatedSignalEquivalence);
|
ctx.constraints[j] = lc.substitute(ctx.constraints[j], isolatedSignal, isolatedSignalEquivalence);
|
||||||
|
linkSignalsConstraint(j);
|
||||||
|
if (j<i) {
|
||||||
|
nextPossibleConstraints[j] = true;
|
||||||
}
|
}
|
||||||
c.a={ type: "LINEARCOMBINATION", values: {} };
|
|
||||||
c.b={ type: "LINEARCOMBINATION", values: {} };
|
|
||||||
c.c={ type: "LINEARCOMBINATION", values: {} };
|
|
||||||
isolatedSignal.category = "constant";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!lc.isZero(c)) {
|
ctx.constraints[i] = null;
|
||||||
newConstraints.push(c);
|
|
||||||
|
lSignal.category = "constant";
|
||||||
|
} else {
|
||||||
|
if (lc.isZero(c.c)) ctx.constraints[i] = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.constraints = newConstraints;
|
}
|
||||||
|
possibleConstraints = Object.keys(nextPossibleConstraints);
|
||||||
|
}
|
||||||
|
unindexVariables();
|
||||||
|
|
||||||
|
// Pack the constraints
|
||||||
|
let o = 0;
|
||||||
|
for (let i=0; i<ctx.constraints.length; i++) {
|
||||||
|
if (ctx.constraints[i]) {
|
||||||
|
if (o != i) {
|
||||||
|
ctx.constraints[o] = ctx.constraints[i];
|
||||||
|
}
|
||||||
|
o++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.constraints.length = o;
|
||||||
|
|
||||||
|
function indexVariables() {
|
||||||
|
for (let i=0; i<ctx.constraints.length; i++) linkSignalsConstraint(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
function linkSignalsConstraint(cidx) {
|
||||||
|
const ct = ctx.constraints[cidx];
|
||||||
|
for (let k in ct.a.values) linkSignal(k, cidx);
|
||||||
|
for (let k in ct.b.values) linkSignal(k, cidx);
|
||||||
|
for (let k in ct.c.values) linkSignal(k, cidx);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unindexVariables() {
|
||||||
|
for (let s in ctx.signals) {
|
||||||
|
let lSignal = ctx.signals[s];
|
||||||
|
while (lSignal.equivalence) {
|
||||||
|
lSignal = ctx.signals[lSignal.equivalence];
|
||||||
|
}
|
||||||
|
if (lSignal.inConstraints) delete lSignal.inConstraints;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
function unlinkSignal(signalName, cidx) {
|
||||||
|
let lSignal = ctx.signals[signalName];
|
||||||
|
while (lSignal.equivalence) {
|
||||||
|
lSignal = ctx.signals[lSignal.equivalence];
|
||||||
|
}
|
||||||
|
if ((lSignal.inConstraints)&&(lSignal.inConstraints[cidx])) {
|
||||||
|
delete lSignal.inConstraints[cidx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function linkSignal(signalName, cidx) {
|
||||||
|
let lSignal = ctx.signals[signalName];
|
||||||
|
while (lSignal.equivalence) {
|
||||||
|
lSignal = ctx.signals[lSignal.equivalence];
|
||||||
|
}
|
||||||
|
if (!lSignal.inConstraints) lSignal.inConstraints = {};
|
||||||
|
lSignal.inConstraints[cidx] = true;
|
||||||
|
}
|
||||||
|
|
||||||
function getFirstInternalSignal(ctx, l) {
|
function getFirstInternalSignal(ctx, l) {
|
||||||
for (let k in l.values) {
|
for (let k in l.values) {
|
||||||
|
|||||||
31
src/exec.js
31
src/exec.js
@@ -131,6 +131,8 @@ function exec(ctx, ast) {
|
|||||||
return execFunctionCall(ctx, ast);
|
return execFunctionCall(ctx, ast);
|
||||||
} else if (ast.type == "BLOCK") {
|
} else if (ast.type == "BLOCK") {
|
||||||
return execBlock(ctx, ast);
|
return execBlock(ctx, ast);
|
||||||
|
} else if (ast.type == "COMPUTE") {
|
||||||
|
return ;
|
||||||
} else if (ast.type == "FOR") {
|
} else if (ast.type == "FOR") {
|
||||||
return execFor(ctx, ast);
|
return execFor(ctx, ast);
|
||||||
} else if (ast.type == "WHILE") {
|
} else if (ast.type == "WHILE") {
|
||||||
@@ -397,6 +399,12 @@ function execInstantiateComponet(ctx, vr, fn) {
|
|||||||
|
|
||||||
function execFunctionCall(ctx, ast) {
|
function execFunctionCall(ctx, ast) {
|
||||||
|
|
||||||
|
if (ast.name == "log") {
|
||||||
|
const v = exec(ctx, ast.params[0]);
|
||||||
|
console.log(v.value.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const scopeLevel = getScopeLevel(ctx, ast.name);
|
const scopeLevel = getScopeLevel(ctx, ast.name);
|
||||||
if (scopeLevel == -1) return error(ctx, ast, "Function not defined: " + ast.name);
|
if (scopeLevel == -1) return error(ctx, ast, "Function not defined: " + ast.name);
|
||||||
const fnc = getScope(ctx, ast.name);
|
const fnc = getScope(ctx, ast.name);
|
||||||
@@ -750,7 +758,7 @@ function execAnd(ctx, ast) {
|
|||||||
if (!a.value || !b.value) return { type: "NUMBER" };
|
if (!a.value || !b.value) return { type: "NUMBER" };
|
||||||
return {
|
return {
|
||||||
type: "NUMBER",
|
type: "NUMBER",
|
||||||
value: (a.value.neq(0) && a.value.neq(0)) ? bigInt(1) : bigInt(0)
|
value: (a.value.neq(0) && b.value.neq(0)) ? bigInt(1) : bigInt(0)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -764,7 +772,7 @@ function execOr(ctx, ast) {
|
|||||||
if (!a.value || !b.value) return { type: "NUMBER" };
|
if (!a.value || !b.value) return { type: "NUMBER" };
|
||||||
return {
|
return {
|
||||||
type: "NUMBER",
|
type: "NUMBER",
|
||||||
value: (a.value.neq(0) || a.value.neq(0)) ? bigInt(1) : bigInt(0)
|
value: (a.value.neq(0) || b.value.neq(0)) ? bigInt(1) : bigInt(0)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -988,7 +996,12 @@ function execSignalAssign(ctx, ast) {
|
|||||||
|
|
||||||
let sDest=ctx.signals[dst.fullName];
|
let sDest=ctx.signals[dst.fullName];
|
||||||
if (!sDest) return error(ctx, ast, "Invalid signal: "+dst.fullName);
|
if (!sDest) return error(ctx, ast, "Invalid signal: "+dst.fullName);
|
||||||
while (sDest.equivalence) sDest=ctx.signals[sDest.equivalence];
|
|
||||||
|
let isOut = (sDest.component == "main")&&(sDest.direction=="OUT");
|
||||||
|
while (sDest.equivalence) {
|
||||||
|
sDest=ctx.signals[sDest.equivalence];
|
||||||
|
isOut = isOut || ((sDest.component == "main")&&(sDest.direction=="OUT"));
|
||||||
|
}
|
||||||
|
|
||||||
if (sDest.value) return error(ctx, ast, "Signals cannot be assigned twice");
|
if (sDest.value) return error(ctx, ast, "Signals cannot be assigned twice");
|
||||||
|
|
||||||
@@ -1016,11 +1029,21 @@ function execSignalAssign(ctx, ast) {
|
|||||||
|
|
||||||
let assignValue = true;
|
let assignValue = true;
|
||||||
if (src.type == "SIGNAL") {
|
if (src.type == "SIGNAL") {
|
||||||
|
let sSrc = ctx.signals[src.fullName];
|
||||||
|
let isIn = (sSrc.component == "main")&&(sSrc.direction == "IN");
|
||||||
|
while (sSrc.equivalence) {
|
||||||
|
sSrc=ctx.signals[sSrc.equivalence];
|
||||||
|
isIn = isIn || ((sSrc.component == "main")&&(sSrc.direction == "IN"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if an out is assigned directly to an input.
|
||||||
|
if ((!isIn)||(!isOut)) {
|
||||||
sDest.equivalence = src.fullName;
|
sDest.equivalence = src.fullName;
|
||||||
sDest.alias = sDest.alias.concat(src.alias);
|
sDest.alias = sDest.alias.concat(src.alias);
|
||||||
while (sDest.equivalence) sDest=ctx.signals[sDest.equivalence];
|
while (sDest.equivalence) sDest=ctx.signals[sDest.equivalence];
|
||||||
assignValue = false;
|
assignValue = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (assignValue) {
|
if (assignValue) {
|
||||||
// const resLC = exec(ctx, vSrc);
|
// const resLC = exec(ctx, vSrc);
|
||||||
@@ -1038,6 +1061,8 @@ function execSignalAssign(ctx, ast) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function execConstrain(ctx, ast) {
|
function execConstrain(ctx, ast) {
|
||||||
|
ast.fileName = ctx.fileName;
|
||||||
|
ast.filePath = ctx.filePath;
|
||||||
const a = exec(ctx, ast.values[0]);
|
const a = exec(ctx, ast.values[0]);
|
||||||
if (ctx.error) return;
|
if (ctx.error) return;
|
||||||
const b = exec(ctx, ast.values[1]);
|
const b = exec(ctx, ast.values[1]);
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ function gen(ctx, ast) {
|
|||||||
return genFunctionCall(ctx, ast);
|
return genFunctionCall(ctx, ast);
|
||||||
} else if (ast.type == "BLOCK") {
|
} else if (ast.type == "BLOCK") {
|
||||||
return genBlock(ctx, ast);
|
return genBlock(ctx, ast);
|
||||||
|
} else if (ast.type == "COMPUTE") {
|
||||||
|
return genCompute(ctx, ast);
|
||||||
} else if (ast.type == "FOR") {
|
} else if (ast.type == "FOR") {
|
||||||
return genFor(ctx, ast);
|
return genFor(ctx, ast);
|
||||||
} else if (ast.type == "WHILE") {
|
} else if (ast.type == "WHILE") {
|
||||||
@@ -245,7 +247,7 @@ function genFor(ctx, ast) {
|
|||||||
const body = gen(ctx, ast.body);
|
const body = gen(ctx, ast.body);
|
||||||
if (ctx.error) return;
|
if (ctx.error) return;
|
||||||
ctx.scopes.pop();
|
ctx.scopes.pop();
|
||||||
return `for (${init};${condition};${step}) { \n${body}\n }\n`;
|
return `for (${init};bigInt(${condition}).neq(bigInt(0));${step}) { \n${body}\n }\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function genWhile(ctx, ast) {
|
function genWhile(ctx, ast) {
|
||||||
@@ -253,7 +255,13 @@ function genWhile(ctx, ast) {
|
|||||||
if (ctx.error) return;
|
if (ctx.error) return;
|
||||||
const body = gen(ctx, ast.body);
|
const body = gen(ctx, ast.body);
|
||||||
if (ctx.error) return;
|
if (ctx.error) return;
|
||||||
return `while (${condition}) {\n${body}\n}\n`;
|
return `while (bigInt(${condition}).neq(bigInt(0))) {\n${body}\n}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function genCompute(ctx, ast) {
|
||||||
|
const body = gen(ctx, ast.body);
|
||||||
|
if (ctx.error) return;
|
||||||
|
return `{\n${body}\n}\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function genIf(ctx, ast) {
|
function genIf(ctx, ast) {
|
||||||
@@ -264,9 +272,9 @@ function genIf(ctx, ast) {
|
|||||||
if (ast.else) {
|
if (ast.else) {
|
||||||
const elseBody = gen(ctx, ast.else);
|
const elseBody = gen(ctx, ast.else);
|
||||||
if (ctx.error) return;
|
if (ctx.error) return;
|
||||||
return `if (${condition}) {\n${thenBody}\n} else {\n${elseBody}\n}\n`;
|
return `if (bigInt(${condition}).neq(bigInt(0))) {\n${thenBody}\n} else {\n${elseBody}\n}\n`;
|
||||||
} else {
|
} else {
|
||||||
return `if (${condition}) {\n${thenBody}\n}\n`;
|
return `if (bigInt(${condition}).neq(bigInt(0))) {\n${thenBody}\n}\n`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,11 +422,13 @@ function genConstrain(ctx, ast) {
|
|||||||
if (ctx.error) return;
|
if (ctx.error) return;
|
||||||
const b = gen(ctx, ast.values[1]);
|
const b = gen(ctx, ast.values[1]);
|
||||||
if (ctx.error) return;
|
if (ctx.error) return;
|
||||||
return `ctx.assert(${a}, ${b})`;
|
const strErr = ast.fileName + ":" + ast.first_line + ":" + ast.first_column;
|
||||||
|
return `ctx.assert(${a}, ${b}, \"${strErr}\")`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function genSignalAssignConstrain(ctx, ast) {
|
function genSignalAssignConstrain(ctx, ast) {
|
||||||
return genVarAssignement(ctx, ast) + ";\n" + genConstrain(ctx, ast);
|
// return genVarAssignement(ctx, ast) + ";\n" + genConstrain(ctx, ast);
|
||||||
|
return genVarAssignement(ctx, ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
function genVarAddAssignement(ctx, ast) {
|
function genVarAddAssignement(ctx, ast) {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ describe("Sum test", () => {
|
|||||||
|
|
||||||
const witness = circuit.calculateWitness({ "i": 111});
|
const witness = circuit.calculateWitness({ "i": 111});
|
||||||
assert(witness[0].equals(bigInt(1)));
|
assert(witness[0].equals(bigInt(1)));
|
||||||
assert(witness[1].equals(bigInt(111)));
|
assert(witness[1].equals(bigInt(111*111)));
|
||||||
assert(witness[2].equals(bigInt(111)));
|
assert(witness[2].equals(bigInt(111)));
|
||||||
});
|
});
|
||||||
// it("Should assign signal ERROR", async () => {
|
// it("Should assign signal ERROR", async () => {
|
||||||
@@ -49,4 +49,19 @@ describe("Sum test", () => {
|
|||||||
// await compiler(path.join(__dirname, "circuits", "assignsignal.circom"));
|
// await compiler(path.join(__dirname, "circuits", "assignsignal.circom"));
|
||||||
// }, /Cannot assign to a signal .*/);
|
// }, /Cannot assign to a signal .*/);
|
||||||
// });
|
// });
|
||||||
|
it("Should compile a code with compute", async () => {
|
||||||
|
const cirDef = await compiler(path.join(__dirname, "circuits", "compute.circom"));
|
||||||
|
|
||||||
|
const circuit = new snarkjs.Circuit(cirDef);
|
||||||
|
|
||||||
|
const witness = circuit.calculateWitness({ "x": 6});
|
||||||
|
assert(witness[0].equals(bigInt(1)));
|
||||||
|
assert(witness[1].equals(bigInt(37)));
|
||||||
|
assert(witness[2].equals(bigInt(6)));
|
||||||
|
});
|
||||||
|
it("Should compile a code with compute", async () => {
|
||||||
|
const cirDef = await compiler(path.join(__dirname, "circuits", "inout.circom"));
|
||||||
|
|
||||||
|
assert.equal(cirDef.constraints.length, 1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
75
test/circuits/circuit.json
Normal file
75
test/circuits/circuit.json
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
{
|
||||||
|
"mainCode": "{\n}\n",
|
||||||
|
"signalName2Idx": {
|
||||||
|
"one": 0,
|
||||||
|
"main.in": 2,
|
||||||
|
"main.out": 1,
|
||||||
|
"main.internal.in": 2,
|
||||||
|
"main.internal.out": 2
|
||||||
|
},
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"name": "main",
|
||||||
|
"params": {},
|
||||||
|
"template": "InOut",
|
||||||
|
"inputSignals": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "main.internal",
|
||||||
|
"params": {},
|
||||||
|
"template": "Internal",
|
||||||
|
"inputSignals": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"componentName2Idx": {
|
||||||
|
"main": 0,
|
||||||
|
"main.internal": 1
|
||||||
|
},
|
||||||
|
"signals": [
|
||||||
|
{
|
||||||
|
"names": [
|
||||||
|
"one"
|
||||||
|
],
|
||||||
|
"triggerComponents": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"names": [
|
||||||
|
"main.out"
|
||||||
|
],
|
||||||
|
"triggerComponents": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"names": [
|
||||||
|
"main.in",
|
||||||
|
"main.internal.in",
|
||||||
|
"main.internal.out"
|
||||||
|
],
|
||||||
|
"triggerComponents": [
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"constraints": [
|
||||||
|
[
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
"1": "21888242871839275222246405745257275088548364400416034343698204186575808495616",
|
||||||
|
"2": "1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"templates": {
|
||||||
|
"Internal": "function(ctx) {\n ctx.setSignal(\"out\", [], ctx.getSignal(\"in\", []));\n}\n",
|
||||||
|
"InOut": "function(ctx) {\n ctx.setPin(\"internal\", [], \"in\", [], ctx.getSignal(\"in\", []));\n ctx.setSignal(\"out\", [], ctx.getPin(\"internal\", [], \"out\", []));\n}\n"
|
||||||
|
},
|
||||||
|
"functions": {},
|
||||||
|
"nPrvInputs": 0,
|
||||||
|
"nPubInputs": 1,
|
||||||
|
"nInputs": 1,
|
||||||
|
"nOutputs": 1,
|
||||||
|
"nVars": 3,
|
||||||
|
"nConstants": 0,
|
||||||
|
"nSignals": 3
|
||||||
|
}
|
||||||
17
test/circuits/compute.circom
Normal file
17
test/circuits/compute.circom
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
template X() {
|
||||||
|
signal input x;
|
||||||
|
signal output y;
|
||||||
|
signal x2;
|
||||||
|
signal x3;
|
||||||
|
var a;
|
||||||
|
compute {
|
||||||
|
a = (x*x*x+6)/x;
|
||||||
|
y <-- a;
|
||||||
|
}
|
||||||
|
|
||||||
|
x2 <== x*x;
|
||||||
|
x3 <== x2*x;
|
||||||
|
x*y === x3+6;
|
||||||
|
}
|
||||||
|
|
||||||
|
component main = X();
|
||||||
18
test/circuits/inout.circom
Normal file
18
test/circuits/inout.circom
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
template Internal() {
|
||||||
|
signal input in;
|
||||||
|
signal output out;
|
||||||
|
|
||||||
|
out <== in;
|
||||||
|
}
|
||||||
|
|
||||||
|
template InOut() {
|
||||||
|
signal input in;
|
||||||
|
signal output out;
|
||||||
|
|
||||||
|
component internal = Internal();
|
||||||
|
|
||||||
|
internal.in <== in;
|
||||||
|
internal.out ==> out;
|
||||||
|
}
|
||||||
|
|
||||||
|
component main = InOut();
|
||||||
@@ -8,7 +8,7 @@ template X() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
i === r;
|
i === r;
|
||||||
out <== r;
|
out <== i*i;
|
||||||
}
|
}
|
||||||
|
|
||||||
component main = X();
|
component main = X();
|
||||||
|
|||||||
Reference in New Issue
Block a user