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/**" |
||||
|
] |
||||
|
} |
||||
|
} |