@ -0,0 +1,117 @@ |
|||
/* |
|||
|
|||
SMTVerifier is a component to verify inclusion/exclusion of an element in the tree |
|||
|
|||
|
|||
fnc: 0 -> VERIFY INCLUSION |
|||
1 -> VERIFY NOT INCLUSION |
|||
|
|||
*/ |
|||
|
|||
|
|||
include "../gates.circom"; |
|||
include "../bitify.circom"; |
|||
include "../comparators.circom"; |
|||
include "../switcher.circom"; |
|||
include "smtlevins.circom"; |
|||
include "smtverifierlevel.circom"; |
|||
include "smtverifiersm.circom"; |
|||
include "smthash.circom"; |
|||
|
|||
template SMTVerifier(nLevels) { |
|||
signal input root; |
|||
signal input siblings[nLevels]; |
|||
signal input oldKey; |
|||
signal input oldValue; |
|||
signal input isOld0; |
|||
signal input key; |
|||
signal input value; |
|||
signal input fnc; |
|||
|
|||
component hash1Old = SMTHash1(); |
|||
hash1Old.key <== oldKey; |
|||
hash1Old.value <== oldValue; |
|||
|
|||
component hash1New = SMTHash1(); |
|||
hash1New.key <== key; |
|||
hash1New.value <== value; |
|||
|
|||
component n2bOld = Num2Bits_strict(); |
|||
component n2bNew = Num2Bits_strict(); |
|||
|
|||
n2bOld.in <== oldKey; |
|||
n2bNew.in <== key; |
|||
|
|||
component smtLevIns = SMTLevIns(nLevels); |
|||
for (var i=0; i<nLevels; i++) smtLevIns.siblings[i] <== siblings[i]; |
|||
smtLevIns.enabled <== 1; |
|||
|
|||
component xors[nLevels]; |
|||
for (var i=0; i<nLevels; i++) { |
|||
xors[i] = XOR(); |
|||
xors[i].a <== n2bOld.out[i]; |
|||
xors[i].b <== n2bNew.out[i]; |
|||
} |
|||
|
|||
component sm[nLevels]; |
|||
for (var i=0; i<nLevels; i++) { |
|||
sm[i] = SMTVerifierSM(); |
|||
if (i==0) { |
|||
sm[i].prev_top <== 1; |
|||
sm[i].prev_i0 <== 0; |
|||
sm[i].prev_inew <== 0; |
|||
sm[i].prev_iold <== 0; |
|||
sm[i].prev_na <== 0; |
|||
} else { |
|||
sm[i].prev_top <== sm[i-1].st_top; |
|||
sm[i].prev_i0 <== sm[i-1].st_i0; |
|||
sm[i].prev_inew <== sm[i-1].st_inew; |
|||
sm[i].prev_iold <== sm[i-1].st_iold; |
|||
sm[i].prev_na <== sm[i-1].st_na; |
|||
} |
|||
sm[i].is0 <== isOld0; |
|||
sm[i].xor <== xors[i].out; |
|||
sm[i].fnc <== fnc; |
|||
sm[i].levIns <== smtLevIns.levIns[i]; |
|||
} |
|||
// sm[nLevels-1].st_na === 1; |
|||
|
|||
component levels[nLevels]; |
|||
for (var i=nLevels-1; i != -1; i--) { |
|||
levels[i] = SMTVerifierLevel(); |
|||
|
|||
levels[i].st_top <== sm[i].st_top; |
|||
levels[i].st_i0 <== sm[i].st_i0; |
|||
levels[i].st_inew <== sm[i].st_inew; |
|||
levels[i].st_iold <== sm[i].st_iold; |
|||
levels[i].st_na <== sm[i].st_na; |
|||
|
|||
levels[i].sibling <== siblings[i]; |
|||
levels[i].old1leaf <== hash1Old.out; |
|||
levels[i].new1leaf <== hash1New.out; |
|||
|
|||
levels[i].lrbit <== n2bNew.out[i]; |
|||
if (i==nLevels-1) { |
|||
levels[i].child <== 0; |
|||
} else { |
|||
levels[i].child <== levels[i+1].root; |
|||
} |
|||
} |
|||
|
|||
|
|||
// Check that if checking for non inclussuin and isOld0==0 then key!=old |
|||
component areKeyEquals = IsEqual(); |
|||
areKeyEquals.in[0] <== oldKey; |
|||
areKeyEquals.in[1] <== key; |
|||
|
|||
component keysOk = MultiAND(3); |
|||
keysOk.in[0] <== fnc; |
|||
keysOk.in[1] <== 1-isOld0; |
|||
keysOk.in[2] <== areKeyEquals.out; |
|||
|
|||
keysOk.out === 0; |
|||
|
|||
// Check the roots |
|||
levels[0].root === root; |
|||
|
|||
} |
@ -0,0 +1,52 @@ |
|||
/****** |
|||
|
|||
SMTVerifierLevel |
|||
|
|||
This circuit has 1 hash |
|||
|
|||
Outputs according to the state. |
|||
|
|||
State root |
|||
===== ======= |
|||
top H'(child, sibling) |
|||
i0 0 |
|||
iold old1leaf |
|||
inew new1leaf |
|||
na 0 |
|||
|
|||
H' is the Hash function with the inputs shifted acordingly. |
|||
|
|||
*****/ |
|||
|
|||
|
|||
template SMTVerifierLevel() { |
|||
signal input st_top; |
|||
signal input st_i0; |
|||
signal input st_iold; |
|||
signal input st_inew; |
|||
signal input st_na; |
|||
|
|||
signal output root; |
|||
signal input sibling; |
|||
signal input old1leaf; |
|||
signal input new1leaf; |
|||
signal input lrbit; |
|||
signal input child; |
|||
|
|||
signal aux[2]; |
|||
|
|||
component proofHash = SMTHash2(); |
|||
component switcher = Switcher(); |
|||
|
|||
switcher.L <== child; |
|||
switcher.R <== sibling; |
|||
|
|||
switcher.sel <== lrbit; |
|||
proofHash.L <== switcher.outL; |
|||
proofHash.R <== switcher.outR; |
|||
|
|||
aux[0] <== proofHash.out * st_top; |
|||
aux[1] <== old1leaf*st_iold; |
|||
|
|||
root <== aux[0] + aux[1] + new1leaf*st_inew; |
|||
} |
@ -0,0 +1,97 @@ |
|||
/* |
|||
Each level in the SMTVerifier has a state. |
|||
|
|||
This is the state machine. |
|||
|
|||
The signals are |
|||
|
|||
levIns: 1 if we are in the level where the insertion should happen |
|||
xor: 1 if the bitKey of the old and new keys are different in this level |
|||
is0: Input that indicates that the oldKey is 0 |
|||
fnc: 0 -> VERIFY INCLUSION |
|||
1 -> VERIFY NOT INCLUSION |
|||
|
|||
err state is not a state itself. It's a lack of state. |
|||
|
|||
The end of the last level will have to be `na` |
|||
|
|||
levIns=0 ########### |
|||
xor=1 # # |
|||
fnc=1 ┌──────────▶# err # |
|||
│ ## ## |
|||
levIns=0 │ ######### |
|||
xor=0 || fnc=0 │ any |
|||
┌────┐ │ ┌────┐ |
|||
│ │ │ │ │ |
|||
│ ▼ │ levIns=1 ▼ │ |
|||
│ ########### │ is0=1 ########### ########### │ |
|||
│ # # ───────────┘ fnc=1 # # any # # │ |
|||
└──# top # ─────────────────────▶# i0 #───────────────▶# na #──┘ |
|||
## ## ──────────┐ ## ## ┌───────▶## ## |
|||
########─────────────┐│ ######### │┌────────▶######### |
|||
││ levIns=1 ││ |
|||
││ is0=0 ########### ││ |
|||
││ fnc=1 # # any│ |
|||
│└──────────▶ # iold #────────┘│ |
|||
│ ## ## │ |
|||
│ ######### │ |
|||
│ │ |
|||
│ levIns=1 ########### │ |
|||
│ fnc=0 # # any |
|||
└────────────▶# inew #─────────┘ |
|||
## ## |
|||
######### |
|||
|
|||
*/ |
|||
|
|||
|
|||
template SMTVerifierSM() { |
|||
signal input xor; |
|||
signal input is0; |
|||
signal input levIns; |
|||
signal input fnc; |
|||
|
|||
signal input prev_top; |
|||
signal input prev_i0; |
|||
signal input prev_iold; |
|||
signal input prev_inew; |
|||
signal input prev_na; |
|||
|
|||
signal output st_top; |
|||
signal output st_i0; |
|||
signal output st_iold; |
|||
signal output st_inew; |
|||
signal output st_na; |
|||
|
|||
signal prev_top_lev_ins; |
|||
signal prev_top_lev_ins_fnc; |
|||
signal xor_fnc; |
|||
|
|||
prev_top_lev_ins <== prev_top * levIns; |
|||
prev_top_lev_ins_fnc <== prev_top_lev_ins*fnc; // prev_top * levIns * fnc |
|||
xor_fnc <== xor*fnc; |
|||
|
|||
|
|||
// st_top = prev_top * (1-levIns) * (1 - xor*fnc) |
|||
// = + prev_top |
|||
// - prev_top * levIns |
|||
// - prev_top * xor * fnc |
|||
// + prev_top * levIns * xor * fnc |
|||
st_top <== (prev_top - prev_top_lev_ins)*(1-xor_fnc); |
|||
|
|||
// st_inew = prev_top * levIns * (1-fnc) |
|||
// = + prev_top * levIns |
|||
// - prev_top * levIns * fnc |
|||
st_inew <== prev_top_lev_ins - prev_top_lev_ins_fnc; |
|||
|
|||
// st_iold = prev_top * levIns * (1-is0)*fnc |
|||
// = + prev_top * levIns * fnc |
|||
// - prev_top * levIns * fnc * is0 |
|||
st_iold <== prev_top_lev_ins_fnc * (1 - is0); |
|||
|
|||
// st_i0 = prev_top * levIns * is0 |
|||
// = + prev_top * levIns * is0 |
|||
st_i0 <== prev_top_lev_ins * is0; |
|||
|
|||
st_na <== prev_na + prev_inew + prev_iold + prev_i0; |
|||
} |
@ -0,0 +1,3 @@ |
|||
include "../../circuits/smt/smtverifier.circom"; |
|||
|
|||
component main = SMTVerifier(10); |
@ -0,0 +1,95 @@ |
|||
const chai = require("chai"); |
|||
const path = require("path"); |
|||
const snarkjs = require("snarkjs"); |
|||
const compiler = require("circom"); |
|||
|
|||
const smt = require("../src/smt.js"); |
|||
|
|||
const assert = chai.assert; |
|||
|
|||
const bigInt = snarkjs.bigInt; |
|||
|
|||
function print(circuit, w, s) { |
|||
console.log(s + ": " + w[circuit.getSignalIdx(s)]); |
|||
} |
|||
|
|||
async function testInclusion(tree, key, circuit) { |
|||
|
|||
const res = await tree.find(key); |
|||
|
|||
assert(res.found); |
|||
let siblings = res.siblings; |
|||
while (siblings.length<10) siblings.push(bigInt(0)); |
|||
|
|||
const w = circuit.calculateWitness({ |
|||
fnc: 0, |
|||
root: tree.root, |
|||
siblings: siblings, |
|||
oldKey: 0, |
|||
oldValue: 0, |
|||
isOld0: 0, |
|||
key: key, |
|||
value: res.foundValue |
|||
}); |
|||
|
|||
assert(circuit.checkWitness(w)); |
|||
} |
|||
|
|||
async function testExclusion(tree, key, circuit) { |
|||
const res = await tree.find(key); |
|||
|
|||
assert(!res.found); |
|||
let siblings = res.siblings; |
|||
while (siblings.length<10) siblings.push(bigInt(0)); |
|||
|
|||
const w = circuit.calculateWitness({ |
|||
fnc: 1, |
|||
root: tree.root, |
|||
siblings: siblings, |
|||
oldKey: res.isOld0 ? 0 : res.notFoundKey, |
|||
oldValue: res.isOld0 ? 0 : res.notFoundValue, |
|||
isOld0: res.isOld0 ? 1 : 0, |
|||
key: key, |
|||
value: 0 |
|||
}, console.log); |
|||
|
|||
assert(circuit.checkWitness(w)); |
|||
} |
|||
|
|||
describe("SMT test", function () { |
|||
let circuit; |
|||
let tree; |
|||
|
|||
this.timeout(100000); |
|||
|
|||
before( async () => { |
|||
const cirDef = await compiler(path.join(__dirname, "circuits", "smtverifier10_test.circom")); |
|||
|
|||
circuit = new snarkjs.Circuit(cirDef); |
|||
|
|||
console.log("NConstrains SMTVerifier: " + circuit.nConstraints); |
|||
|
|||
tree = await smt.newMemEmptyTrie(); |
|||
await tree.insert(7,77); |
|||
await tree.insert(8,88); |
|||
await tree.insert(32,3232); |
|||
}); |
|||
|
|||
it("Check inclussion in a tree of 3", async () => { |
|||
await testInclusion(tree, 7, circuit); |
|||
await testInclusion(tree, 8, circuit); |
|||
await testInclusion(tree, 32, circuit); |
|||
}); |
|||
|
|||
it("Check exclussion in a tree of 3", async () => { |
|||
// await testExclusion(tree, 0, circuit);
|
|||
await testExclusion(tree, 6, circuit); |
|||
/* await testExclusion(tree, 9, circuit); |
|||
await testExclusion(tree, 33, circuit); |
|||
await testExclusion(tree, 31, circuit); |
|||
await testExclusion(tree, 16, circuit); |
|||
await testExclusion(tree, 64, circuit); */ |
|||
}); |
|||
|
|||
|
|||
}); |