mirror of
https://github.com/logos-storage/logos-storage-proofs-circuits.git
synced 2026-01-05 23:13:06 +00:00
the circuit seems to work
This commit is contained in:
parent
0ae539a0ed
commit
55015008e7
@ -11,6 +11,8 @@ include "misc.circom";
|
|||||||
// NOTE: this is rather nontrivial, as everything is computed modulo `r`,
|
// NOTE: this is rather nontrivial, as everything is computed modulo `r`,
|
||||||
// so naive bit decomposition does not work (there are multiple solutions).
|
// so naive bit decomposition does not work (there are multiple solutions).
|
||||||
//
|
//
|
||||||
|
// TODO: optimize this
|
||||||
|
//
|
||||||
|
|
||||||
template ExtractLowerBits(n) {
|
template ExtractLowerBits(n) {
|
||||||
|
|
||||||
|
|||||||
@ -42,11 +42,13 @@ template Log2(n) {
|
|||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
//
|
//
|
||||||
// given an input `inp`, this template computes `k` such that 2^k <= inp < 2^{k+1}
|
// given an input `inp`, this template computes `out := k` such that 2^k <= inp < 2^{k+1}
|
||||||
// it also returns the binary decomposition of `inp-1`, and the binary deocmpositiom
|
// it also returns the binary decomposition of `inp-1`, and the binary deocmpositiom
|
||||||
// of the mask `(2^k-1)`
|
// of the mask `(2^k-1)`
|
||||||
//
|
//
|
||||||
// we also output a mask vector which is 1 for i=0..out-1, and 0 elsewhere
|
// we also output a mask vector which is 1 for i=0..k-1, and 0 elsewhere
|
||||||
|
//
|
||||||
|
// we require `k <= n`, otherwise this will fail.
|
||||||
//
|
//
|
||||||
|
|
||||||
template CeilingLog2(n) {
|
template CeilingLog2(n) {
|
||||||
@ -54,7 +56,7 @@ template CeilingLog2(n) {
|
|||||||
signal input inp;
|
signal input inp;
|
||||||
signal output out;
|
signal output out;
|
||||||
signal output bits[n];
|
signal output bits[n];
|
||||||
signal output mask[n];
|
signal output mask[n+1];
|
||||||
|
|
||||||
component tb = ToBits(n);
|
component tb = ToBits(n);
|
||||||
tb.inp <== inp - 1;
|
tb.inp <== inp - 1;
|
||||||
@ -68,6 +70,7 @@ template CeilingLog2(n) {
|
|||||||
mask[i] <== 1 - aux[i];
|
mask[i] <== 1 - aux[i];
|
||||||
sum = sum + (aux[i+1] - aux[i]) * (i+1);
|
sum = sum + (aux[i+1] - aux[i]) * (i+1);
|
||||||
}
|
}
|
||||||
|
mask[n] <== 0;
|
||||||
|
|
||||||
out <== sum;
|
out <== sum;
|
||||||
}
|
}
|
||||||
|
|||||||
87
circuit/merkle.circom
Normal file
87
circuit/merkle.circom
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
pragma circom 2.0.0;
|
||||||
|
|
||||||
|
include "poseidon2_perm.circom";
|
||||||
|
include "poseidon2_hash.circom";
|
||||||
|
|
||||||
|
include "misc.circom";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
//
|
||||||
|
// reconstruct the Merkle root using a Merkle inclusion proof
|
||||||
|
//
|
||||||
|
// parameters:
|
||||||
|
// - depth: the depth of the Merkle tree = log2( numberOfLeaves )
|
||||||
|
//
|
||||||
|
// inputs and outputs:
|
||||||
|
// - leaf: the leaf hash
|
||||||
|
// - pathBits: the linear index of the leaf, in binary decomposition (little-endian)
|
||||||
|
// - lastBits: the index of the last leaf (= nLeaves-1), in binary decomposition
|
||||||
|
// - maskBits: the bits of the the mask `2^ceilingLog2(size) - 1`
|
||||||
|
// - merklePath: the Merkle inclusion proof (required hashes, starting from the leaf and ending near the root)
|
||||||
|
// - recRoot: the reconstructod Merkle root
|
||||||
|
//
|
||||||
|
// NOTE: we don't check whether the bits are really bits, that's the
|
||||||
|
// responsability of the caller!
|
||||||
|
//
|
||||||
|
|
||||||
|
template RootFromMerklePath( maxDepth ) {
|
||||||
|
|
||||||
|
signal input leaf;
|
||||||
|
signal input pathBits[ maxDepth ]; // bits of the linear index
|
||||||
|
signal input lastBits[ maxDepth ]; // bits of the last linear index `= size-1`
|
||||||
|
signal input maskBits[ maxDepth+1 ]; // bit mask for `2^ceilingLog(size) - 1`
|
||||||
|
signal input merklePath[ maxDepth ];
|
||||||
|
signal output recRoot;
|
||||||
|
|
||||||
|
// the sequence of reconstructed hashes along the path
|
||||||
|
signal aux[ maxDepth+1 ];
|
||||||
|
aux[0] <== leaf;
|
||||||
|
|
||||||
|
// compute which prefixes (in big-endian) of the index is
|
||||||
|
// the same as the corresponding prefix of the last index
|
||||||
|
component eq[ maxDepth ];
|
||||||
|
signal isLast[ maxDepth+1 ];
|
||||||
|
isLast[ maxDepth ] <== 1;
|
||||||
|
for(var i=maxDepth-1; i>=0; i--) {
|
||||||
|
eq[i] = IsEqual();
|
||||||
|
eq[i].A <== pathBits[i];
|
||||||
|
eq[i].B <== lastBits[i];
|
||||||
|
isLast[i] <== isLast[i+1] * eq[i].out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute the sequence of hashes
|
||||||
|
signal switch[ maxDepth ];
|
||||||
|
component comp[ maxDepth ];
|
||||||
|
for(var i=0; i<maxDepth; i++) {
|
||||||
|
|
||||||
|
var bottom = (i==0) ? 1 : 0; // is it the bottom layer?
|
||||||
|
var odd = isLast[i] * (1-pathBits[i]); // is it an odd node?
|
||||||
|
|
||||||
|
comp[i] = KeyedCompression();
|
||||||
|
|
||||||
|
var L = aux[i];
|
||||||
|
var R = merklePath[i];
|
||||||
|
|
||||||
|
// based on pathBits[i], we switch or not
|
||||||
|
|
||||||
|
switch[i] <== (R-L) * pathBits[i];
|
||||||
|
comp[i].key <== bottom + 2*odd;
|
||||||
|
comp[i].inp[0] <== L + switch[i];
|
||||||
|
comp[i].inp[1] <== R - switch[i];
|
||||||
|
|
||||||
|
comp[i].out ==> aux[i+1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we need to select the right layer from the sequence of hashes
|
||||||
|
var sum = 0;
|
||||||
|
signal prods[maxDepth];
|
||||||
|
for(var i=0; i<maxDepth; i++) {
|
||||||
|
prods[i] <== (maskBits[i] - maskBits[i+1]) * aux[i+1];
|
||||||
|
sum += prods[i];
|
||||||
|
}
|
||||||
|
recRoot <== sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ template IsEqual() {
|
|||||||
signal output out;
|
signal output out;
|
||||||
|
|
||||||
component isz = IsZero();
|
component isz = IsZero();
|
||||||
isz.in <== A - B;
|
isz.inp <== A - B;
|
||||||
isz.out ==> out;
|
isz.out ==> out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,46 +0,0 @@
|
|||||||
pragma circom 2.0.0;
|
|
||||||
|
|
||||||
include "poseidon2_perm.circom";
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Merkle tree built using the Poseidon2 permutation
|
|
||||||
//
|
|
||||||
// The number of leaves is `2**nlevels`
|
|
||||||
//
|
|
||||||
|
|
||||||
template PoseidonMerkle(nlevels) {
|
|
||||||
|
|
||||||
var nleaves = 2**nlevels;
|
|
||||||
|
|
||||||
signal input inp[nleaves];
|
|
||||||
signal output out_root;
|
|
||||||
|
|
||||||
component hsh[ nleaves-1];
|
|
||||||
signal aux[2*nleaves-1];
|
|
||||||
|
|
||||||
for(var k=0; k<nleaves; k++) { aux[k] <== inp[k]; }
|
|
||||||
|
|
||||||
var a = 0; // aux[a..b) = input layer
|
|
||||||
var u = 0; // hsh[u..v) = hash compression components
|
|
||||||
|
|
||||||
for(var lev=0; lev<nlevels; lev++) {
|
|
||||||
|
|
||||||
var b = a + 2**(nlevels-lev );
|
|
||||||
var v = u + 2**(nlevels-lev-1);
|
|
||||||
|
|
||||||
var ncherries = 2**(nlevels-lev-1);
|
|
||||||
for(var k=0; k<ncherries; k++) {
|
|
||||||
hsh[u+k] = Compression();
|
|
||||||
hsh[u+k].inp[0] <== aux[a+2*k ];
|
|
||||||
hsh[u+k].inp[1] <== aux[a+2*k+1];
|
|
||||||
hsh[u+k].out ==> aux[b+ k ];
|
|
||||||
}
|
|
||||||
|
|
||||||
a = b;
|
|
||||||
u = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
aux[2*nleaves-2] ==> out_root;
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
@ -213,5 +213,20 @@ template Compression() {
|
|||||||
perm.out[0] ==> out;
|
perm.out[0] ==> out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------
|
||||||
|
|
||||||
|
template KeyedCompression() {
|
||||||
|
signal input key;
|
||||||
|
signal input inp[2];
|
||||||
|
signal output out;
|
||||||
|
|
||||||
|
component perm = Permutation();
|
||||||
|
perm.inp[0] <== inp[0];
|
||||||
|
perm.inp[1] <== inp[1];
|
||||||
|
perm.inp[2] <== key;
|
||||||
|
|
||||||
|
perm.out[0] ==> out;
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@ -51,9 +51,14 @@ template PoseidonSponge(t, capacity, input_len, output_len) {
|
|||||||
|
|
||||||
signal state [nblocks+nout][t ];
|
signal state [nblocks+nout][t ];
|
||||||
signal sorbed[nblocks ][rate];
|
signal sorbed[nblocks ][rate];
|
||||||
|
|
||||||
|
// domain separation, capacity IV := 2^64 + 256*t + rate
|
||||||
|
var civ = 2**64 + 256*t + rate;
|
||||||
|
// log("capacity IV = ",civ);
|
||||||
|
|
||||||
// initialize state
|
// initialize state
|
||||||
for(var i=0; i<t; i++) { state[0][i] <== 0; }
|
for(var i=0; i<t-1; i++) { state[0][i] <== 0; }
|
||||||
|
state[0][t-1] <== civ;
|
||||||
|
|
||||||
component absorb [nblocks];
|
component absorb [nblocks];
|
||||||
component squeeze[nout-1];
|
component squeeze[nout-1];
|
||||||
|
|||||||
@ -3,6 +3,7 @@ pragma circom 2.0.0;
|
|||||||
include "single_cell.circom";
|
include "single_cell.circom";
|
||||||
include "poseidon2_hash.circom";
|
include "poseidon2_hash.circom";
|
||||||
include "extract_bits.circom";
|
include "extract_bits.circom";
|
||||||
|
include "log2.circom";
|
||||||
include "misc.circom";
|
include "misc.circom";
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@ -19,96 +20,116 @@ include "misc.circom";
|
|||||||
// NOTE: we assume `nCells` is a power of two.
|
// NOTE: we assume `nCells` is a power of two.
|
||||||
//
|
//
|
||||||
|
|
||||||
template CalculateCellIndexBits( nCells ) {
|
template CalculateCellIndexBits( maxLog2N ) {
|
||||||
|
|
||||||
var log2N = CeilLog2(nCells);
|
|
||||||
assert( nCells == (1<<log2N) );
|
|
||||||
|
|
||||||
signal input entropy;
|
signal input entropy;
|
||||||
signal input slotRoot;
|
signal input slotRoot;
|
||||||
signal input counter;
|
signal input counter;
|
||||||
signal output indexBits[log2N];
|
signal input cellIndexBitMask[maxLog2N]; // bit mask for the cell index range
|
||||||
|
|
||||||
|
signal output indexBits[maxLog2N];
|
||||||
|
|
||||||
// calculate the hash
|
// calculate the hash
|
||||||
component pos = Poseidon2_hash_rate2(3);
|
component pos = Poseidon2_hash_rate2( 3 ); // input is 3 field elements
|
||||||
signal hash;
|
signal hash;
|
||||||
pos.inp[0] <== entropy;
|
pos.inp[0] <== entropy;
|
||||||
pos.inp[1] <== slotRoot;
|
pos.inp[1] <== slotRoot;
|
||||||
pos.inp[2] <== counter;
|
pos.inp[2] <== counter;
|
||||||
pos.out ==> hash;
|
pos.out ==> hash;
|
||||||
|
|
||||||
// extract the lowest `log2(nCells)` bits
|
// extract the lowest `maxLog2N = 32` bits
|
||||||
component md = ExtractLowerBits(log2N);
|
component md = ExtractLowerBits(maxLog2N);
|
||||||
md.inp <== hash;
|
md.inp <== hash;
|
||||||
md.out ==> indexBits;
|
|
||||||
|
|
||||||
}
|
for(var i=0; i<maxLog2N; i++ ) {
|
||||||
|
indexBits[i] <== cellIndexBitMask[i] * md.out[i];
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
//
|
|
||||||
// same as above, but returns an integer index instead of its binary decomposition.
|
|
||||||
//
|
|
||||||
|
|
||||||
template CalculateCellIndexInteger( nCells ) {
|
|
||||||
|
|
||||||
var log2N = CeilLog2(nCells);
|
|
||||||
assert( nCells == (1<<log2N) );
|
|
||||||
|
|
||||||
signal input entropy;
|
|
||||||
signal input slotRoot;
|
|
||||||
signal input counter;
|
|
||||||
signal output linearIndex;
|
|
||||||
|
|
||||||
component calc = CalculateCellIndexBits( nCells );
|
|
||||||
calc.entropy <== entropy;
|
|
||||||
calc.slotRoot <== slotRoot;
|
|
||||||
calc.counter <== counter;
|
|
||||||
|
|
||||||
var sum = 0;
|
|
||||||
for(var i=0; i<log2N; i++) {
|
|
||||||
sum += (1<<i) * calc.indexBits[i];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
linearIndex <== sum;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
//
|
//
|
||||||
// sample `nSamples` number of cells; calculate their hashes;
|
// sample `nSamples` number of cells; calculate their hashes;
|
||||||
// reconstruct the slot root using the Merkle pathes, and
|
// reconstruct the slot root using the Merkle paths, then
|
||||||
// checks if it matches the externally given slot root.
|
// the dataset root too, and checks if everything is consistent.
|
||||||
//
|
|
||||||
// NOTE: difference between V1 and V2 is whether we use the slot root
|
|
||||||
// or the dataset root.
|
|
||||||
//
|
//
|
||||||
|
|
||||||
template SampleAndProveV1( nCells, nFieldElemsPerCell, nSamples ) {
|
template SampleAndProve( maxDepth, maxLog2NSlots, blockTreeDepth, nFieldElemsPerCell, nSamples ) {
|
||||||
|
|
||||||
var log2N = CeilLog2(nCells);
|
// var maxDepth = 32; // maximum depth of the slot Merkle tree (so max `2^maxDepth` cells in a slot)
|
||||||
assert( nCells == (1<<log2N) );
|
// var maxLog2NSlots = 8; // maximum depth of the dataset-level Merkle tree (so max 2^8 slots per dataset)
|
||||||
|
// var blockTreeDepth = 5; // depth of the "network block tree" (= log2(64k / 2k))
|
||||||
|
|
||||||
signal input entropy; // public input
|
signal input entropy; // public input
|
||||||
signal input slotRoot; // public input
|
signal input dataSetRoot; // public input
|
||||||
|
signal input slotIndex; // must be public, otherwise we could prove a different slot
|
||||||
|
|
||||||
signal input cellData[nSamples][nFieldElemsPerCell]; // private input
|
signal input slotRoot; // can be private input
|
||||||
signal input merklePaths[nSamples][log2N]; // private input
|
signal input nCellsPerSlot; // can be private input (Merkle tree is safe)
|
||||||
|
signal input nSlotsPerDataSet; // can be private input (Merkle tree is safe)
|
||||||
|
|
||||||
|
signal input slotProof[maxLog2NSlots]; // path from the slot root the the dataset root (private input)
|
||||||
|
|
||||||
|
signal input cellData[nSamples][nFieldElemsPerCell]; // private input
|
||||||
|
signal input merklePaths[nSamples][maxDepth]; // private input
|
||||||
|
|
||||||
|
// -------------------------------------------------------
|
||||||
|
//
|
||||||
|
// first we prove the inclusion of the slot root in the dataset-level
|
||||||
|
// (small) Merkle tree
|
||||||
|
|
||||||
|
component tbtp = ToBits( maxLog2NSlots );
|
||||||
|
component clog = CeilingLog2( maxLog2NSlots );
|
||||||
|
component mtop = RootFromMerklePath( maxLog2NSlots );
|
||||||
|
|
||||||
|
tbtp.inp <== slotIndex;
|
||||||
|
clog.inp <== nSlotsPerDataSet;
|
||||||
|
mtop.leaf <== slotRoot;
|
||||||
|
mtop.pathBits <== tbtp.out;
|
||||||
|
mtop.lastBits <== clog.bits;
|
||||||
|
mtop.maskBits <== clog.mask;
|
||||||
|
mtop.merklePath <== slotProof;
|
||||||
|
|
||||||
|
log("top root check = ", mtop.recRoot == dataSetRoot);
|
||||||
|
|
||||||
|
mtop.recRoot === dataSetRoot;
|
||||||
|
|
||||||
|
// -------------------------------------------------------
|
||||||
|
//
|
||||||
|
// then we prove the individual sampled cells
|
||||||
|
|
||||||
|
signal log2N;
|
||||||
|
component lg = Log2(maxDepth); // we allow at most 2^32 cells per slot
|
||||||
|
lg.inp <== nCellsPerSlot;
|
||||||
|
lg.out ==> log2N;
|
||||||
|
|
||||||
|
// NOTE: in general we need the for Merkle prover the binary decomposition
|
||||||
|
// of `nLeaves - 1`. But currently this is in fact a power of two, so we
|
||||||
|
// can reuse the binary mask for this. Later we may change this?
|
||||||
|
//
|
||||||
|
signal lastBits[maxDepth];
|
||||||
|
for(var i=0; i<maxDepth; i++) { lastBits[i] <== lg.mask[i]; }
|
||||||
|
|
||||||
component calci[nSamples];
|
component calci[nSamples];
|
||||||
component prove[nSamples];
|
component prove[nSamples];
|
||||||
|
|
||||||
for(var cnt=0; cnt<nSamples; cnt++) {
|
for(var cnt=0; cnt<nSamples; cnt++) {
|
||||||
|
|
||||||
calci[cnt] = CalculateCellIndexBits( nCells );
|
calci[cnt] = CalculateCellIndexBits( maxDepth );
|
||||||
prove[cnt] = ProveSingleCell( nFieldElemsPerCell, log2N );
|
prove[cnt] = ProveSingleCell( nFieldElemsPerCell, blockTreeDepth, maxDepth );
|
||||||
|
|
||||||
|
// calci[cnt].cellIndexBitMask <== lg.mask;
|
||||||
|
for(var i=0; i<maxDepth; i++) { calci[cnt].cellIndexBitMask[i] <== lg.mask[i]; }
|
||||||
|
|
||||||
calci[cnt].entropy <== entropy;
|
calci[cnt].entropy <== entropy;
|
||||||
calci[cnt].slotRoot <== slotRoot;
|
calci[cnt].slotRoot <== slotRoot;
|
||||||
calci[cnt].counter <== cnt + 1;
|
calci[cnt].counter <== cnt + 1;
|
||||||
calci[cnt].indexBits ==> prove[cnt].indexBits;
|
calci[cnt].indexBits ==> prove[cnt].indexBits;
|
||||||
|
|
||||||
prove[cnt].slotRoot <== slotRoot;
|
prove[cnt].slotRoot <== slotRoot;
|
||||||
|
prove[cnt].lastBits <== lastBits;
|
||||||
|
prove[cnt].maskBits <== lg.mask;
|
||||||
prove[cnt].data <== cellData[cnt];
|
prove[cnt].data <== cellData[cnt];
|
||||||
prove[cnt].merklePath <== merklePaths[cnt];
|
prove[cnt].merklePath <== merklePaths[cnt];
|
||||||
|
|
||||||
|
|||||||
@ -3,124 +3,74 @@ pragma circom 2.0.0;
|
|||||||
include "poseidon2_perm.circom";
|
include "poseidon2_perm.circom";
|
||||||
include "poseidon2_hash.circom";
|
include "poseidon2_hash.circom";
|
||||||
|
|
||||||
|
include "merkle.circom";
|
||||||
include "misc.circom";
|
include "misc.circom";
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
//
|
|
||||||
// reconstruct the Merkle root using a Merkle inclusion proof
|
|
||||||
//
|
|
||||||
// parameters:
|
|
||||||
// - depth: the depth of the Merkle tree = log2( numberOfLeaves )
|
|
||||||
//
|
|
||||||
// inputs and outputs:
|
|
||||||
// - leaf: the leaf hash
|
|
||||||
// - pathBits: the linear index of the leaf, in binary decomposition (little-endian)
|
|
||||||
// - merklePath: the Merkle inclusion proof (required hashes, starting from the leaf and ending near the root)
|
|
||||||
// - recRoot: the reconstructod Merkle root
|
|
||||||
//
|
|
||||||
// NOTE: we don't check whether the bits are really bits, that's the
|
|
||||||
// responsability of the caller!
|
|
||||||
//
|
|
||||||
|
|
||||||
template MerklePathBits( depth ) {
|
|
||||||
|
|
||||||
signal input leaf;
|
|
||||||
signal input pathBits[ depth ];
|
|
||||||
signal input merklePath[ depth ];
|
|
||||||
signal output recRoot;
|
|
||||||
|
|
||||||
// the sequence of reconstructed hashes along the path
|
|
||||||
signal aux[ depth+1 ];
|
|
||||||
aux[0] <== leaf;
|
|
||||||
|
|
||||||
signal switch[ depth];
|
|
||||||
|
|
||||||
component comp[ depth ];
|
|
||||||
for(var i=0; i<depth; i++) {
|
|
||||||
comp[i] = Compression();
|
|
||||||
|
|
||||||
var L = aux[i];
|
|
||||||
var R = merklePath[i];
|
|
||||||
|
|
||||||
// based on pathBits[i], we switch or not
|
|
||||||
|
|
||||||
switch[i] <== (R-L) * pathBits[i];
|
|
||||||
comp[i].inp[0] <== L + switch[i];
|
|
||||||
comp[i].inp[1] <== R - switch[i];
|
|
||||||
|
|
||||||
comp[i].out ==> aux[i+1];
|
|
||||||
}
|
|
||||||
|
|
||||||
aux[depth] ==> recRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
//--------------------------------------
|
|
||||||
|
|
||||||
//
|
|
||||||
// a version of the above where the leaf index is given as an integer
|
|
||||||
// instead of a sequence of bits
|
|
||||||
//
|
|
||||||
|
|
||||||
template MerklePathIndex( depth ) {
|
|
||||||
|
|
||||||
signal input leaf;
|
|
||||||
signal input linearIndex;
|
|
||||||
signal input merklePath[ depth ];
|
|
||||||
signal output recRoot;
|
|
||||||
|
|
||||||
// decompose the linear cell index into bits (0 = left, 1 = right)
|
|
||||||
component tb = ToBits( depth );
|
|
||||||
component path = MerklePathBits( depth );
|
|
||||||
|
|
||||||
tb.inp <== linearIndex;
|
|
||||||
tb.out ==> path.pathBits;
|
|
||||||
|
|
||||||
path.leaf <== leaf;
|
|
||||||
path.merklePath <== merklePath;
|
|
||||||
path.recRoot ==> recRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// calculates a single cell's hash and reconstructs the Merkle root,
|
// calculates a single cell's hash and reconstructs the Merkle root,
|
||||||
// checking whether it matches the given slot root
|
// checking whether it matches the given slot root
|
||||||
//
|
//
|
||||||
// parameters:
|
// parameters:
|
||||||
// - nFieldElemsPerCell: how many field elements a cell consists of
|
// - nFieldElemsPerCell: how many field elements a cell consists of (= 2048/31 = 67)
|
||||||
// - merkleDepth: the depth of slot subtree = log2(nCellsPerSlot)
|
// - botDepth: the depth of the per-block minitree (= 5)
|
||||||
|
// - maxDepth: the maximum depth of slot subtree (= 32)
|
||||||
//
|
//
|
||||||
// inputs and outputs:
|
// inputs and outputs:
|
||||||
// - indexBits: the linear index of the cell, within the slot subtree, in binary
|
// - indexBits: the linear index of the cell, within the slot subtree, in binary
|
||||||
// - data: the cell data (already encoded as field elements)
|
// - lastBits: the index of the last cell (size - 1), in binary (required for odd-even node key)
|
||||||
// - merklePath: the Merkle inclusion proof
|
// - maskBits: the binary mask of the size rounded up to a power of two
|
||||||
// - slotRoot: the expected slot root
|
// - data: the cell data (already encoded as field elements)
|
||||||
|
// - merklePath: the Merkle inclusion proof
|
||||||
|
// - slotRoot: the expected slot root
|
||||||
//
|
//
|
||||||
// NOTE: we don't check whether the bits are really bits, that's the
|
// NOTE: we don't check whether the bits are really bits, that's the
|
||||||
// responsability of the caller!
|
// responsability of the caller!
|
||||||
//
|
//
|
||||||
|
|
||||||
template ProveSingleCell( nFieldElemsPerCell, merkleDepth ) {
|
template ProveSingleCell( nFieldElemsPerCell, botDepth, maxDepth ) {
|
||||||
|
|
||||||
signal input slotRoot;
|
signal input slotRoot;
|
||||||
signal input data[ nFieldElemsPerCell ];
|
signal input data[ nFieldElemsPerCell ];
|
||||||
|
signal input lastBits [ maxDepth ];
|
||||||
|
signal input indexBits [ maxDepth ];
|
||||||
|
signal input maskBits [ maxDepth + 1 ];
|
||||||
|
signal input merklePath[ maxDepth ];
|
||||||
|
|
||||||
signal input indexBits [ merkleDepth ];
|
// these will reconstruct the Merkle path up to the slot root
|
||||||
signal input merklePath[ merkleDepth ];
|
// in two steps: first the block-level ("bottom"), then the slot-level ("middle")
|
||||||
|
component pbot = RootFromMerklePath( botDepth );
|
||||||
|
component pmid = RootFromMerklePath( maxDepth - botDepth );
|
||||||
|
|
||||||
// this will reconstruct the Merkle path up to the slot root
|
for(var i=0; i<maxDepth; i++) {
|
||||||
component path = MerklePathBits( merkleDepth );
|
if (i<botDepth) {
|
||||||
path.pathBits <== indexBits;
|
pbot.pathBits[i] <== indexBits[i];
|
||||||
path.merklePath <== merklePath;
|
pbot.maskBits[i] <== maskBits[i];
|
||||||
|
pbot.lastBits[i] <== lastBits[i];
|
||||||
|
pbot.merklePath[i] <== merklePath[i];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var j = i - botDepth;
|
||||||
|
pmid.pathBits[j] <== indexBits[i];
|
||||||
|
pmid.maskBits[j] <== maskBits[i];
|
||||||
|
pmid.lastBits[j] <== lastBits[i];
|
||||||
|
pmid.merklePath[j] <== merklePath[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pbot.maskBits[ botDepth ] <== 0;
|
||||||
|
pmid.maskBits[ maxDepth - botDepth ] <== 0;
|
||||||
|
|
||||||
// compute the cell hash
|
// compute the cell hash
|
||||||
component hash = Poseidon2_hash_rate2( nFieldElemsPerCell );
|
component hash = Poseidon2_hash_rate2( nFieldElemsPerCell );
|
||||||
hash.inp <== data;
|
hash.inp <== data;
|
||||||
hash.out ==> path.leaf;
|
hash.out ==> pbot.leaf;
|
||||||
|
pmid.leaf <== pbot.recRoot;
|
||||||
|
|
||||||
|
log("middle bottom root check = ", pmid.recRoot == slotRoot);
|
||||||
|
|
||||||
// check if the reconstructed root matches the actual slot root
|
// check if the reconstructed root matches the actual slot root
|
||||||
path.recRoot === slotRoot;
|
pmid.recRoot === slotRoot;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
pragma circom 2.0.0;
|
pragma circom 2.0.0;
|
||||||
include "sample_cells.circom";
|
include "sample_cells.circom";
|
||||||
component main {public [entropy,slotRoot]} = SampleAndProveV1(1024, 5, 10);
|
// SampleAndProven( maxDepth, maxLog2NSlots, blockTreeDepth, nFieldElemsPerCell, nSamples )
|
||||||
|
component main {public [entropy,dataSetRoot,slotIndex]} = SampleAndProve(16, 5, 3, 5, 10);
|
||||||
|
|||||||
35
circuit/test_prove.sh
Executable file
35
circuit/test_prove.sh
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
ORIG=`pwd`
|
||||||
|
|
||||||
|
PTAU_DIR="/Users/bkomuves/zk/ptau/"
|
||||||
|
PTAU_FILE="${PTAU_DIR}/powersOfTau28_hez_final_20.ptau"
|
||||||
|
|
||||||
|
NAME="slot_main"
|
||||||
|
|
||||||
|
# --- setup ---
|
||||||
|
|
||||||
|
cd $ORIG/build
|
||||||
|
echo "circuit-specific ceremony..."
|
||||||
|
snarkjs groth16 setup ${NAME}.r1cs ${PTAU_FILE} ${NAME}_0000.zkey
|
||||||
|
echo "some_entropy_xxx" | snarkjs zkey contribute ${NAME}_0000.zkey ${NAME}_0001.zkey --name="1st Contributor Name"
|
||||||
|
rm ${NAME}_0000.zkey
|
||||||
|
mv ${NAME}_0001.zkey ${NAME}.zkey
|
||||||
|
snarkjs zkey export verificationkey ${NAME}.zkey ${NAME}_verification_key.json
|
||||||
|
|
||||||
|
# --- prove ---
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "trying to prove... (with snarkjs)"
|
||||||
|
|
||||||
|
cd $ORIG/build
|
||||||
|
time snarkjs groth16 prove ${NAME}.zkey ${NAME}.wtns ${NAME}_proof.json ${NAME}_public.json
|
||||||
|
|
||||||
|
# --- verify ---
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "verifyng proof..."
|
||||||
|
snarkjs groth16 verify ${NAME}_verification_key.json ${NAME}_public.json ${NAME}_proof.json
|
||||||
|
|
||||||
|
cd $ORIG
|
||||||
|
cd $ORIG
|
||||||
@ -1,21 +1,25 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
ORIG=`pwd`
|
ORIG=`pwd`
|
||||||
|
mkdir -p build
|
||||||
|
|
||||||
cd ../reference/haskell
|
cd ../reference/haskell
|
||||||
runghc testMain.hs || { echo "ghc failed"; exit 101; }
|
mkdir -p json
|
||||||
|
cabal v1-run cli/testMain.hs || { echo "ghc failed"; cd $ORIG; exit 101; }
|
||||||
|
|
||||||
mv input_example.json ${ORIG}/build/
|
mv json/input_example.json ${ORIG}/build/
|
||||||
mv slot_main.circom ${ORIG}
|
mv json/slot_main.circom ${ORIG}
|
||||||
|
|
||||||
cd ${ORIG}/build
|
cd ${ORIG}/build
|
||||||
|
|
||||||
NAME="slot_main"
|
NAME="slot_main"
|
||||||
circom ../${NAME}.circom --r1cs --wasm || { echo "circom failed"; exit 102; }
|
circom ../${NAME}.circom --r1cs --wasm || { echo "circom failed"; cd $ORIG; exit 102; }
|
||||||
|
|
||||||
echo "generating witness... (WASM)"
|
echo "generating witness... (WASM)"
|
||||||
cd ${NAME}_js
|
cd ${NAME}_js
|
||||||
node generate_witness.js ${NAME}.wasm ../input_example.json ../${NAME}_witness.wtns || { echo "witness gen failed"; exit 101; }
|
node generate_witness.js ${NAME}.wasm ../input_example.json ../${NAME}.wtns || { echo "witness gen failed"; cd $ORIG; exit 101; }
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
|
echo "witness generation succeeded"
|
||||||
|
|
||||||
cd $ORIG
|
cd $ORIG
|
||||||
|
|||||||
@ -11,17 +11,21 @@ import Sampling
|
|||||||
|
|
||||||
smallDataSetCfg :: DataSetCfg
|
smallDataSetCfg :: DataSetCfg
|
||||||
smallDataSetCfg = MkDataSetCfg
|
smallDataSetCfg = MkDataSetCfg
|
||||||
{ _nSlots = 5
|
{ _maxDepth = 16
|
||||||
|
, _maxLog2NSlots = 5
|
||||||
|
, _nSlots = 5
|
||||||
, _cellSize = 128
|
, _cellSize = 128
|
||||||
, _blockSize = 4096
|
, _blockSize = 1024
|
||||||
, _nCells = 256
|
, _nCells = 64
|
||||||
, _nSamples = 5
|
, _nSamples = 10
|
||||||
, _dataSrc = FakeData (Seed 12345)
|
, _dataSrc = FakeData (Seed 12345)
|
||||||
}
|
}
|
||||||
|
|
||||||
bigDataSetCfg :: DataSetCfg
|
bigDataSetCfg :: DataSetCfg
|
||||||
bigDataSetCfg = MkDataSetCfg
|
bigDataSetCfg = MkDataSetCfg
|
||||||
{ _nSlots = 13
|
{ _maxDepth = 32
|
||||||
|
, _maxLog2NSlots = 8
|
||||||
|
, _nSlots = 13
|
||||||
, _cellSize = 2048
|
, _cellSize = 2048
|
||||||
, _blockSize = 65536
|
, _blockSize = 65536
|
||||||
, _nCells = 512
|
, _nCells = 512
|
||||||
|
|||||||
@ -4,19 +4,24 @@ module DataSet where
|
|||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import Data.List
|
||||||
import System.FilePath
|
import System.FilePath
|
||||||
|
|
||||||
import Slot hiding ( MkSlotCfg(..) )
|
import Slot hiding ( MkSlotCfg(..) )
|
||||||
import qualified Slot as Slot
|
import qualified Slot as Slot
|
||||||
|
|
||||||
|
import Misc
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
data DataSetCfg = MkDataSetCfg
|
data DataSetCfg = MkDataSetCfg
|
||||||
{ _nSlots :: Int -- ^ number of slots per dataset
|
{ _maxDepth :: Int -- ^ @nCells@ must fit into this many bits
|
||||||
, _cellSize :: Int
|
, _maxLog2NSlots :: Int -- ^ @nSlots@ must fit into this many bits
|
||||||
, _blockSize :: Int
|
, _nSlots :: Int -- ^ number of slots per dataset
|
||||||
, _nCells :: Int
|
, _cellSize :: Int -- ^ cell size in bytes
|
||||||
, _nSamples :: Int
|
, _blockSize :: Int -- ^ slot size in bytes
|
||||||
|
, _nCells :: Int -- ^ number of cells per slot
|
||||||
|
, _nSamples :: Int -- ^ number of cells we sample in a proof
|
||||||
, _dataSrc :: DataSource
|
, _dataSrc :: DataSource
|
||||||
}
|
}
|
||||||
deriving Show
|
deriving Show
|
||||||
@ -57,12 +62,20 @@ loadDataSetBlock dsetCfg slotIdx@(SlotIdx idx) blockidx
|
|||||||
circomMainComponent :: DataSetCfg -> FilePath -> IO ()
|
circomMainComponent :: DataSetCfg -> FilePath -> IO ()
|
||||||
circomMainComponent dsetCfg circomFile = do
|
circomMainComponent dsetCfg circomFile = do
|
||||||
|
|
||||||
let params = show (DataSet.fieldElemsPerCell dsetCfg)
|
let cellsPerBlock = (DataSet._blockSize dsetCfg) `div` (DataSet._cellSize dsetCfg)
|
||||||
++ ", " ++ show (DataSet._nSamples dsetCfg)
|
let blockDepth = ceilingLog2 (fromIntegral cellsPerBlock)
|
||||||
|
|
||||||
|
let params = intercalate ", " $ map show
|
||||||
|
[ DataSet._maxDepth dsetCfg
|
||||||
|
, DataSet._maxLog2NSlots dsetCfg
|
||||||
|
, blockDepth
|
||||||
|
, DataSet.fieldElemsPerCell dsetCfg
|
||||||
|
, DataSet._nSamples dsetCfg
|
||||||
|
]
|
||||||
writeFile circomFile $ unlines
|
writeFile circomFile $ unlines
|
||||||
[ "pragma circom 2.0.0;"
|
[ "pragma circom 2.0.0;"
|
||||||
, "include \"sample_cells.circom\";"
|
, "include \"sample_cells.circom\";"
|
||||||
|
, "// SampleAndProven( maxDepth, maxLog2NSlots, blockTreeDepth, nFieldElemsPerCell, nSamples ) "
|
||||||
, "component main {public [entropy,dataSetRoot,slotIndex]} = SampleAndProve(" ++ params ++ ");"
|
, "component main {public [entropy,dataSetRoot,slotIndex]} = SampleAndProve(" ++ params ++ ");"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,15 @@ sampleCellIndex cfg entropy slotRoot counter = CellIdx (fromInteger idx) where
|
|||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
padWithZeros :: Int -> [Fr] -> [Fr]
|
||||||
|
padWithZeros n xs
|
||||||
|
| m <= n = xs ++ replicate (n-m) Fr.zero
|
||||||
|
| otherwise = error "padWithZeros: input too long"
|
||||||
|
where
|
||||||
|
m = length xs
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
data CircuitInput = MkInput
|
data CircuitInput = MkInput
|
||||||
{ _entropy :: Entropy -- ^ public input
|
{ _entropy :: Entropy -- ^ public input
|
||||||
, _dataSetRoot :: Hash -- ^ public input
|
, _dataSetRoot :: Hash -- ^ public input
|
||||||
@ -72,11 +81,11 @@ calculateCircuitInput dataSetCfg slotIdx@(SlotIdx sidx) entropy = do
|
|||||||
, _dataSetRoot = dsetRoot
|
, _dataSetRoot = dsetRoot
|
||||||
, _slotIndex = sidx
|
, _slotIndex = sidx
|
||||||
, _slotRoot = ourSlotRoot
|
, _slotRoot = ourSlotRoot
|
||||||
, _slotProof = extractMerkleProof_ dsetTree sidx
|
, _slotProof = padWithZeros (_maxLog2NSlots dataSetCfg) $ extractMerkleProof_ dsetTree sidx
|
||||||
, _slotsPerDSet = nslots
|
, _slotsPerDSet = nslots
|
||||||
, _cellsPerSlot = Slot._nCells ourSlotCfg
|
, _cellsPerSlot = Slot._nCells ourSlotCfg
|
||||||
, _cellData = cellData
|
, _cellData = cellData
|
||||||
, _merklePaths = merklePaths
|
, _merklePaths = map (padWithZeros (_maxDepth dataSetCfg)) merklePaths
|
||||||
}
|
}
|
||||||
|
|
||||||
-- | Export the inputs of the storage proof circuits in JSON format,
|
-- | Export the inputs of the storage proof circuits in JSON format,
|
||||||
|
|||||||
@ -30,12 +30,12 @@ type TestCase = Integer
|
|||||||
type Output = (Int,[Bool],[Bool])
|
type Output = (Int,[Bool],[Bool])
|
||||||
|
|
||||||
semantics :: GP -> TestCase -> Expected Output
|
semantics :: GP -> TestCase -> Expected Output
|
||||||
semantics n a
|
semantics n a
|
||||||
| a >0 && k >= 0 && k < n = Expecting (k,bits,mask)
|
| a > 0 && k >= 0 && k <= n = Expecting (k,bits,mask)
|
||||||
| otherwise = ShouldFail
|
| otherwise = ShouldFail
|
||||||
where
|
where
|
||||||
k = ceilingLog2 a
|
k = ceilingLog2 a
|
||||||
mask = [ i < k | i<-[0..n-1] ]
|
mask = [ i < k | i<-[0..n] ]
|
||||||
bits = [ testBit (a-1) i | i<-[0..n-1] ]
|
bits = [ testBit (a-1) i | i<-[0..n-1] ]
|
||||||
|
|
||||||
-- | Smallest integer @k@ such that @2^k@ is larger or equal to @n@
|
-- | Smallest integer @k@ such that @2^k@ is larger or equal to @n@
|
||||||
|
|||||||
@ -40,5 +40,5 @@ testSimple' verbosity = do
|
|||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
main = do
|
main = do
|
||||||
testSimple' Silent -- Verbose -- Silent
|
testSimple' Info --Silent -- Verbose -- Silent
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user