@ -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); */ |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
}); |