Add circom test with circuit for CircomVerifierProofs, which allows to automatically check that the data generated from arbo matches the circom circuit of a SMTVerifierProof. Added also GHA workflow to test the circuits with the output of arbo code.master
@ -0,0 +1,24 @@ |
|||
name: CircomTest |
|||
on: [ push, pull_request ] |
|||
jobs: |
|||
test: |
|||
runs-on: ubuntu-latest |
|||
strategy: |
|||
matrix: |
|||
node-version: [14.x] |
|||
go-version: [1.16.x] |
|||
steps: |
|||
- uses: actions/checkout@v2 |
|||
- name: Use Node.js ${{ matrix.node-version }} |
|||
uses: actions/setup-node@v1 |
|||
with: |
|||
node-version: ${{ matrix.node-version }} |
|||
- name: Install Go |
|||
uses: actions/setup-go@v1 |
|||
with: |
|||
go-version: ${{ matrix.go-version }} |
|||
- name: run circom tests |
|||
run: | |
|||
cd testvectors/circom |
|||
npm install |
|||
npm run test |
@ -0,0 +1,3 @@ |
|||
node_modules |
|||
dist |
|||
go-data-generator/*.json |
@ -0,0 +1,56 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"encoding/json" |
|||
"io/ioutil" |
|||
"math/big" |
|||
"testing" |
|||
|
|||
qt "github.com/frankban/quicktest" |
|||
"github.com/vocdoni/arbo" |
|||
"go.vocdoni.io/dvote/db" |
|||
) |
|||
|
|||
func TestGenerator(t *testing.T) { |
|||
c := qt.New(t) |
|||
database, err := db.NewBadgerDB(c.TempDir()) |
|||
c.Assert(err, qt.IsNil) |
|||
tree, err := arbo.NewTree(database, 4, arbo.HashFunctionPoseidon) |
|||
c.Assert(err, qt.IsNil) |
|||
|
|||
bLen := tree.HashFunction().Len() |
|||
|
|||
testVector := [][]int64{ |
|||
{1, 11}, |
|||
{2, 22}, |
|||
{3, 33}, |
|||
{4, 44}, |
|||
} |
|||
for i := 0; i < len(testVector); i++ { |
|||
k := arbo.BigIntToBytes(bLen, big.NewInt(testVector[i][0])) |
|||
v := arbo.BigIntToBytes(bLen, big.NewInt(testVector[i][1])) |
|||
if err := tree.Add(k, v); err != nil { |
|||
t.Fatal(err) |
|||
} |
|||
} |
|||
|
|||
// proof of existence
|
|||
k := arbo.BigIntToBytes(bLen, big.NewInt(int64(2))) |
|||
cvp, err := tree.GenerateCircomVerifierProof(k) |
|||
c.Assert(err, qt.IsNil) |
|||
jCvp, err := json.Marshal(cvp) |
|||
c.Assert(err, qt.IsNil) |
|||
// store the data into a file that will be used at the circom test
|
|||
err = ioutil.WriteFile("go-smt-verifier-inputs.json", jCvp, 0600) |
|||
c.Assert(err, qt.IsNil) |
|||
|
|||
// proof of non-existence
|
|||
k = arbo.BigIntToBytes(bLen, big.NewInt(int64(5))) |
|||
cvp, err = tree.GenerateCircomVerifierProof(k) |
|||
c.Assert(err, qt.IsNil) |
|||
jCvp, err = json.Marshal(cvp) |
|||
c.Assert(err, qt.IsNil) |
|||
// store the data into a file that will be used at the circom test
|
|||
err = ioutil.WriteFile("go-smt-verifier-non-existence-inputs.json", jCvp, 0600) |
|||
c.Assert(err, qt.IsNil) |
|||
} |
@ -0,0 +1,32 @@ |
|||
{ |
|||
"name": "arbo-testvectors-circom", |
|||
"version": "0.0.1", |
|||
"description": "", |
|||
"main": "index.js", |
|||
"directories": { |
|||
"test": "test" |
|||
}, |
|||
"scripts": { |
|||
"build": "npm run clean && ./node_modules/.bin/tsc", |
|||
"clean": "rimraf dist", |
|||
"pretest": "cd go-data-generator && go test", |
|||
"test": "npm run build && ./node_modules/.bin/mocha -r ts-node/register test/**/*.ts" |
|||
}, |
|||
"author": "", |
|||
"license": "GPL-3.0", |
|||
"dependencies": { |
|||
"chai": "^4.2.0", |
|||
"circom": "0.5.45", |
|||
"circomlib": "^0.5.0", |
|||
"ffjavascript": "0.2.33" |
|||
}, |
|||
"devDependencies": { |
|||
"@types/chai": "^4.2.14", |
|||
"@types/mocha": "^8.2.0", |
|||
"@types/node": "^14.14.25", |
|||
"mocha": "^8.0.1", |
|||
"ts-node": "^9.1.1", |
|||
"tslint": "^6.1.3", |
|||
"typescript": "^4.1.3" |
|||
} |
|||
} |
@ -0,0 +1,28 @@ |
|||
include "../../node_modules/circomlib/circuits/comparators.circom"; |
|||
include "../../node_modules/circomlib/circuits/poseidon.circom"; |
|||
include "../../node_modules/circomlib/circuits/smt/smtprocessor.circom"; |
|||
|
|||
template SMTProcessorTest(nLevels) { |
|||
signal input newKey; |
|||
signal input newValue; |
|||
signal private input oldKey; |
|||
signal private input oldValue; |
|||
signal private input isOld0; |
|||
signal private input siblings[nLevels]; |
|||
signal input oldRoot; |
|||
signal input newRoot; |
|||
|
|||
component smtProcessor = SMTProcessor(nLevels); |
|||
smtProcessor.oldRoot <== oldRoot; |
|||
smtProcessor.newRoot <== newRoot; |
|||
for (var i=0; i<nLevels; i++) { |
|||
smtProcessor.siblings[i] <== siblings[i]; |
|||
} |
|||
smtProcessor.oldKey <== oldKey; |
|||
smtProcessor.oldValue <== oldValue; |
|||
smtProcessor.isOld0 <== isOld0; |
|||
smtProcessor.newKey <== newKey; |
|||
smtProcessor.newValue <== newValue; |
|||
smtProcessor.fnc[0] <== 1; |
|||
smtProcessor.fnc[1] <== 0; |
|||
} |
@ -0,0 +1,27 @@ |
|||
include "../../node_modules/circomlib/circuits/comparators.circom"; |
|||
include "../../node_modules/circomlib/circuits/poseidon.circom"; |
|||
include "../../node_modules/circomlib/circuits/smt/smtverifier.circom"; |
|||
|
|||
template SMTVerifierTest(nLevels) { |
|||
signal input key; |
|||
signal input value; |
|||
signal input fnc; |
|||
signal private input oldKey; |
|||
signal private input oldValue; |
|||
signal private input isOld0; |
|||
signal private input siblings[nLevels]; |
|||
signal input root; |
|||
|
|||
component smtV = SMTVerifier(nLevels); |
|||
smtV.enabled <== 1; |
|||
smtV.fnc <== fnc; |
|||
smtV.root <== root; |
|||
for (var i=0; i<nLevels; i++) { |
|||
smtV.siblings[i] <== siblings[i]; |
|||
} |
|||
smtV.oldKey <== oldKey; |
|||
smtV.oldValue <== oldValue; |
|||
smtV.isOld0 <== isOld0; |
|||
smtV.key <== key; |
|||
smtV.value <== value; |
|||
} |
@ -0,0 +1,94 @@ |
|||
const fs = require("fs"); |
|||
const path = require("path"); |
|||
const tester = require("circom").tester; |
|||
const assert = require('assert'); |
|||
const circomlib = require("circomlib"); |
|||
const smt = require("circomlib").smt; |
|||
|
|||
describe("merkletreetree circom-proof-verifier", function () { |
|||
this.timeout(0); |
|||
const nLevels = 4; |
|||
|
|||
let circuit; |
|||
let circuitPath = path.join(__dirname, "circuits", "mt-proof-verifier-main.test.circom"); |
|||
before( async() => { |
|||
const circuitCode = `
|
|||
include "smt-proof-verifier_test.circom"; |
|||
component main = SMTVerifierTest(4); |
|||
`;
|
|||
fs.writeFileSync(circuitPath, circuitCode, "utf8"); |
|||
|
|||
circuit = await tester(circuitPath, {reduceConstraints:false}); |
|||
await circuit.loadConstraints(); |
|||
console.log("Constraints: " + circuit.constraints.length + "\n"); |
|||
}); |
|||
|
|||
after( async() => { |
|||
fs.unlinkSync(circuitPath); |
|||
}); |
|||
|
|||
let inputsVerifier, inputsVerifierNonExistence; |
|||
|
|||
before("generate smt-verifier js inputs", async () => { |
|||
let tree = await smt.newMemEmptyTrie(); |
|||
await tree.insert(1, 11); |
|||
await tree.insert(2, 22); |
|||
await tree.insert(3, 33); |
|||
await tree.insert(4, 44); |
|||
const res = await tree.find(2); |
|||
assert(res.found); |
|||
let root = tree.root; |
|||
|
|||
let siblings = res.siblings; |
|||
while (siblings.length < nLevels) { |
|||
siblings.push("0"); |
|||
}; |
|||
|
|||
inputsVerifier = { |
|||
"fnc": 0, |
|||
"key": 2, |
|||
"value": 22, |
|||
"siblings": siblings, |
|||
"root": root, |
|||
}; |
|||
|
|||
const res2 = await tree.find(5); |
|||
assert(!res2.found); |
|||
let siblings2 = res2.siblings; |
|||
while (siblings2.length < nLevels) { |
|||
siblings2.push("0"); |
|||
}; |
|||
inputsVerifierNonExistence = { |
|||
"fnc": 1, |
|||
"oldKey": 1, |
|||
"oldValue": 11, |
|||
"key": 5, |
|||
"value": 11, |
|||
"siblings": siblings2, |
|||
"root": root, |
|||
}; |
|||
}); |
|||
|
|||
it("Test smt-verifier proof of existence go inputs", async () => { |
|||
// fromGo is a json CircomVerifierProof generated from Go code using
|
|||
// https://github.com/vocdoni/arbo
|
|||
let rawdata = fs.readFileSync('go-data-generator/go-smt-verifier-inputs.json'); |
|||
let fromGo = JSON.parse(rawdata); |
|||
inputsVerifier=fromGo; |
|||
// console.log("smtverifier js inputs:\n", inputsVerifier);
|
|||
|
|||
const witness = await circuit.calculateWitness(inputsVerifier); |
|||
await circuit.checkConstraints(witness); |
|||
}); |
|||
it("Test smt-verifier proof of non-existence go inputs", async () => { |
|||
// fromGo is a json CircomVerifierProof generated from Go code using
|
|||
// https://github.com/vocdoni/arbo
|
|||
let rawdata = fs.readFileSync('go-data-generator/go-smt-verifier-non-existence-inputs.json'); |
|||
let fromGo = JSON.parse(rawdata); |
|||
inputsVerifierNonExistence=fromGo; |
|||
// console.log("smtverifier js inputs:\n", inputsVerifierNonExistence);
|
|||
|
|||
const witness = await circuit.calculateWitness(inputsVerifierNonExistence); |
|||
await circuit.checkConstraints(witness); |
|||
}); |
|||
}); |
@ -0,0 +1,19 @@ |
|||
{ |
|||
"compilerOptions": { |
|||
"module": "commonjs", |
|||
"moduleResolution": "node", |
|||
"resolveJsonModule": true, |
|||
"pretty": true, |
|||
"declaration": true, |
|||
"sourceMap": true, |
|||
"target": "es2017", |
|||
"outDir": "dist", |
|||
"baseUrl": "src" |
|||
}, |
|||
"include": [ |
|||
"test/**/*.ts" |
|||
], |
|||
"exclude": [ |
|||
"node_modules" |
|||
] |
|||
} |
@ -0,0 +1,24 @@ |
|||
{ |
|||
"defaultSeverity": "error", |
|||
"extends": [ |
|||
"tslint:recommended" |
|||
], |
|||
"jsRules": {}, |
|||
"rules": { |
|||
"indent": [ |
|||
true, |
|||
"spaces", |
|||
4 |
|||
], |
|||
"semicolon": [ |
|||
false, |
|||
"always" |
|||
] |
|||
}, |
|||
"rulesDirectory": [], |
|||
"linterOptions": { |
|||
"exclude": [ |
|||
"node_modules/**" |
|||
] |
|||
} |
|||
} |