Merge pull request #5 from codex-storage/review

Code review of the circom circuit
This commit is contained in:
Balazs Komuves 2024-03-12 10:53:39 +01:00 committed by GitHub
commit 2a4a71acf6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 60 additions and 90 deletions

View File

@ -3,8 +3,9 @@ pragma circom 2.0.0;
//------------------------------------------------------------------------------
//
// given two numbers in `n`-bit binary decomposition (little-endian), we compute
//
// given two numbers in `n`-bit binary decomposition
// (least significant bit first), we compute
//
// / -1 if A < B
// out := { 0 if A == B
// \ +1 if A > B
@ -12,7 +13,7 @@ pragma circom 2.0.0;
// NOTE: we don't check that the digits are indeed binary;
// that's the responsibility of the caller!
//
// This version uses `(3*n-1)` nonlinear constraints.
// This version uses `(3*n-1)` nonlinear constraints.
// Question: can we do better? (note that this has to work with n >= 254 digits too!)
//
@ -42,11 +43,11 @@ template BinaryCompare(n) {
//------------------------------------------------------------------------------
//
// given two numbers in `n`-bit binary decomposition (little-endian), we compute
// given two numbers in `n`-bit binary decomposition (little-endian), we compute
//
// out := (A <= B) ? 1 : 0
//
// NOTE: we don't check that the digits are indeed binary;
// NOTE: we don't check that the digits are indeed binary;
// that's the responsibility of the caller!
//
@ -69,11 +70,11 @@ template BinaryLessOrEqual(n) {
//------------------------------------------------------------------------------
//
// given two numbers in `n`-bit binary decomposition (little-endian), we compute
// given two numbers in `n`-bit binary decomposition (little-endian), we compute
//
// out := (A >= B) ? 1 : 0
//
// NOTE: we don't check that the digits are indeed binary;
// NOTE: we don't check that the digits are indeed binary;
// that's the responsibility of the caller!
//

View File

@ -5,12 +5,12 @@ include "misc.circom";
//------------------------------------------------------------------------------
//
//
// extract the lowest `n` bits from a field element.
//
// 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).
//
//
// TODO: optimize this
//
@ -23,7 +23,7 @@ template ExtractLowerBits(n) {
component tb = ToBits(254); // note: 2^253 < r < 2^254
tb.inp <== inp;
// bits of field prime `r` in little-endian order
// bits of field prime `r`, least significant bit first
var primeBits[254] = [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,1,0,0,1,1,0,1,0,1,1,1,1,1,0,0,0,0,1,1,1,1,1,0,0,0,0,1,0,1,0,0,0,1,0,0,1,0,0,0,0,1,1,1,0,1,0,0,1,1,1,0,1,1,0,0,1,1,1,1,0,0,0,0,1,0,0,1,0,0,0,0,1,0,1,1,1,1,1,0,0,1,1,0,0,0,0,0,1,0,1,0,0,1,0,1,1,1,0,1,0,0,0,0,1,1,0,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,0,1,1,0,1,1,0,1,1,0,1,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,0,1,1,1,0,1,1,0,0,1,0,1,0,0,0,0,0,0,0,1,0,1,1,0,0,0,1,1,0,0,1,0,0,0,0,1,1,1,0,1,0,0,1,1,1,0,0,1,1,1,0,0,1,0,0,0,1,0,0,1,1,0,0,0,0,0,1,1];
// enforce that the binary representation is < r
@ -33,7 +33,7 @@ template ExtractLowerBits(n) {
le.out === -1; // enforce `A < B`, that is, `bits < prime`
// extract the lowest `n` bits
for(var i=0; i<n; i++) {
for(var i=0; i<n; i++) {
tb.out[i] ==> out[i];
}
@ -56,7 +56,7 @@ template ExtractLowerBits_testfield65537(n) {
component tb = ToBits(18); // note: 2^16 < r < 2^18
tb.inp <== inp;
// bits of field prime `r` in little-endian order
// bits of field prime `r`, least significant bit first
var primeBits[18] = [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0];
// enforce that the binary representation is < r
@ -66,7 +66,7 @@ template ExtractLowerBits_testfield65537(n) {
le.out === -1; // enforce `A < B`, that is, `bits < prime`
// extract the lowest `n` bits
for(var i=0; i<n; i++) {
for(var i=0; i<n; i++) {
tb.out[i] ==> out[i];
}

View File

@ -3,7 +3,7 @@ pragma circom 2.0.0;
include "misc.circom";
//------------------------------------------------------------------------------
//
//
// given an input `inp`, this template checks that inp == 2^out
// with 0 < out <= n
//
@ -13,14 +13,14 @@ include "misc.circom";
template Log2(n) {
signal input inp;
signal output out;
signal output out;
signal output mask[n+1];
// mask will be a vector [1,1,1,...1,0,...,0,0]
// which can change only where inp == 2^out
// which can change only where index == out
var log2 = -1;
for(var i=0; i<=n; i++) {
for(var i=0; i<=n; i++) {
mask[i] <-- ((2**i) < inp) ? 1 : 0;
if (2**i == inp) { log2 = i; }
}
@ -31,19 +31,19 @@ template Log2(n) {
mask[n] === 0;
var sum = 0;
for(var i=0; i<n; i++) {
for(var i=0; i<n; i++) {
sum += (2**(i+1)) * (mask[i] - mask[i+1]);
0 === (mask[i] - mask[i+1]) * (i + 1 - out);
}
inp === sum;
inp === sum;
}
//------------------------------------------------------------------------------
//
//
// 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 decomposition
// of the mask `(2^k-1)`
//
// we also output a mask vector which is 1 for i=0..k-1, and 0 elsewhere
@ -54,12 +54,12 @@ template Log2(n) {
template CeilingLog2(n) {
signal input inp;
signal output out;
signal output out;
signal output bits[n];
signal output mask[n+1];
component tb = ToBits(n);
tb.inp <== inp - 1;
tb.inp <== inp - 1;
tb.out ==> bits;
signal aux[n+1];

View File

@ -1,7 +1,6 @@
pragma circom 2.0.0;
include "poseidon2_perm.circom";
include "poseidon2_hash.circom";
include "misc.circom";
@ -15,7 +14,7 @@ include "misc.circom";
//
// inputs and outputs:
// - leaf: the leaf hash
// - pathBits: the linear index of the leaf, in binary decomposition (little-endian)
// - pathBits: the linear index of the leaf, in binary decomposition (least significant bit first)
// - 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)
@ -26,7 +25,7 @@ include "misc.circom";
//
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`
@ -38,14 +37,17 @@ template RootFromMerklePath( maxDepth ) {
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
// Determine whether nodes from the path are last in their row and are odd,
// by computing which binary prefixes of the index are the same as the
// corresponding prefix of the last index.
// This is done in reverse bit order, because pathBits and lastBits have the
// least significant bit first.
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].A <== pathBits[i];
eq[i].B <== lastBits[i];
isLast[i] <== isLast[i+1] * eq[i].out;
}
@ -64,7 +66,7 @@ template RootFromMerklePath( maxDepth ) {
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];

View File

@ -1,7 +1,6 @@
pragma circom 2.0.0;
//------------------------------------------------------------------------------
// compute (compile time) the log2 of a number
function FloorLog2(n) {
return (n==0) ? -1 : (1 + FloorLog2(n>>1));
@ -12,7 +11,7 @@ function CeilLog2(n) {
}
//------------------------------------------------------------------------------
// decompose an n-bit number into bits
// decompose an n-bit number into bits (least significant bit first)
template ToBits(n) {
signal input inp;
@ -44,11 +43,11 @@ template IsZero() {
out <== 1 - inp * inv;
// enfore that either `inp` or `out` must be zero
inp*out === 0;
inp*out === 0;
}
//------------------------------------------------------------------------------
// check equality of two field elements; that is, computes `(A==B) ? 1 : 0`
// check equality of two field elements; that is, computes `(A==B) ? 1 : 0`
template IsEqual() {
signal input A,B;

View File

@ -2,20 +2,6 @@ pragma circom 2.0.0;
include "poseidon2_sponge.circom";
//------------------------------------------------------------------------------
// Hash `n` field elements into 1, with approximately 127-254 bits of preimage security
// Note that the output size must be at least twice than the desired security level (?)
// (assuming bn128 scalar field. We use capacity=2, rate=1, t=3).
template Poseidon2_hash_rate1(n) {
signal input inp[n];
signal output out;
component sponge = PoseidonSponge(3,2,n,1);
sponge.inp <== inp;
sponge.out[0] ==> out;
}
//------------------------------------------------------------------------------
// Hash `n` field elements into 1, with approximately 127 bits of preimage security
// (assuming bn128 scalar field. We use capacity=1, rate=2, t=3).

View File

@ -1,7 +1,7 @@
pragma circom 2.0.0;
//
// The Poseidon2 permutation for bn128 and t=3
// The Poseidon2 permutation for bn128 and t=3
//
//------------------------------------------------------------------------------
@ -24,7 +24,7 @@ template InternalRound(i) {
signal input inp[3];
signal output out[3];
var round_consts[56] =
var round_consts[56] =
[ 0x15ce7e5ae220e8623a40b3a3b22d441eff0c9be1ae1d32f1b777af84eea7e38c
, 0x1bf60ac8bfff0f631983c93e218ca0d4a4059c254b4299b1d9984a07edccfaf0
, 0x0fab0c9387cb2bec9dc11b2951088b9e1e1d2978542fc131f74a8f8fdac95b40
@ -99,11 +99,11 @@ template ExternalRound(i) {
signal input inp[3];
signal output out[3];
var round_consts[8][3] =
var round_consts[8][3] =
[ [ 0x2c4c51fd1bb9567c27e99f5712b49e0574178b41b6f0a476cddc41d242cf2b43
, 0x1c5f8d18acb9c61ec6fcbfcda5356f1b3fdee7dc22c99a5b73a2750e5b054104
, 0x2d3c1988b4541e4c045595b8d574e98a7c2820314a82e67a4e380f1c4541ba90
, 0x2d3c1988b4541e4c045595b8d574e98a7c2820314a82e67a4e380f1c4541ba90
]
, [ 0x052547dc9e6d936cab6680372f1734c39f490d0cb970e2077c82f7e4172943d3
, 0x29d967f4002adcbb5a6037d644d36db91f591b088f69d9b4257694f5f9456bc2
@ -120,19 +120,19 @@ template ExternalRound(i) {
, [ 0x25672a14b5d085e31a30a7e1d5675ebfab034fb04dc2ec5e544887523f98dede
, 0x0cf702434b891e1b2f1d71883506d68cdb1be36fa125674a3019647b3a98accd
, 0x1837e75235ff5d112a5eddf7a4939448748339e7b5f2de683cf0c0ae98bdfbb3
]
]
, [ 0x1cd8a14cff3a61f04197a083c6485581a7d836941f6832704837a24b2d15613a
, 0x266f6d85be0cef2ece525ba6a54b647ff789785069882772e6cac8131eecc1e4
, 0x0538fde2183c3f5833ecd9e07edf30fe977d28dd6f246d7960889d9928b506b3
]
]
, [ 0x07a0693ff41476abb4664f3442596aa8399fdccf245d65882fce9a37c268aa04
, 0x11eb49b07d33de2bd60ea68e7f652beda15644ed7855ee5a45763b576d216e8e
, 0x08f8887da6ce51a8c06041f64e22697895f34bacb8c0a39ec12bf597f7c67cfc
]
]
, [ 0x2a912ec610191eb7662f86a52cc64c0122bd5ba762e1db8da79b5949fdd38092
, 0x2031d7fd91b80857aa1fef64e23cfad9a9ba8fe8c8d09de92b1edb592a44c290
, 0x0f81ebce43c47711751fa64d6c007221016d485641c28c507d04fd3dc7fba1d2
]
]
];
component sb[3];
@ -154,7 +154,7 @@ template LinearLayer() {
signal output out[3];
out[0] <== 2*inp[0] + inp[1] + inp[2];
out[1] <== inp[0] + 2*inp[1] + inp[2];
out[2] <== inp[0] + inp[1] + 2*inp[2];
out[2] <== inp[0] + inp[1] + 2*inp[2];
}
//------------------------------------------------------------------------------
@ -172,7 +172,7 @@ template Permutation() {
component ext[8];
for(var k=0; k<8; k++) { ext[k] = ExternalRound(k); }
component int[56];
for(var k=0; k<56; k++) { int[k] = InternalRound(k); }
@ -201,20 +201,6 @@ template Permutation() {
// the "compression function" takes 2 field elements as input and produces
// 1 field element as output. It is a trivial application of the permutation.
template Compression() {
signal input inp[2];
signal output out;
component perm = Permutation();
perm.inp[0] <== inp[0];
perm.inp[1] <== inp[1];
perm.inp[2] <== 0;
perm.out[0] ==> out;
}
//--------------------------------------
template KeyedCompression() {
signal input key;
signal input inp[2];

View File

@ -17,10 +17,10 @@ function min(a,b) {
// c = capacity (1 or 2)
// r = rate = t - c
//
// everything is measured in number of field elements
// everything is measured in number of field elements
//
// we use the padding `10*` from the original Poseidon paper,
// and initial state constant zero. Note that this is different
// and initial state constant zero. Note that this is different
// from the "SAFE padding" recommended in the Poseidon2 paper
// (which uses `0*` padding and a nontrivial initial state)
//
@ -47,7 +47,7 @@ template PoseidonSponge(t, capacity, input_len, output_len) {
signal padded[padded_len];
for(var i=0; i<input_len; i++) { padded[i] <== inp[i]; }
padded[input_len ] <== 1;
for(var i=input_len+1; i<padded_len; i++) { padded[i] <== 0; }
for(var i=input_len+1; i<padded_len; i++) { padded[i] <== 0; }
signal state [nblocks+nout][t ];
signal sorbed[nblocks ][rate];
@ -58,7 +58,7 @@ template PoseidonSponge(t, capacity, input_len, output_len) {
// initialize state
for(var i=0; i<t-1; i++) { state[0][i] <== 0; }
state[0][t-1] <== civ;
state[0][t-1] <== civ;
component absorb [nblocks];
component squeeze[nout-1];
@ -70,7 +70,7 @@ template PoseidonSponge(t, capacity, input_len, output_len) {
var b = padded[m*rate+i];
sorbed[m][i] <== a + b;
}
absorb[m] = Permutation();
for(var j=0 ; j<rate; j++) { absorb[m].inp[j] <== sorbed[m][j]; }
for(var j=rate; j<t ; j++) { absorb[m].inp[j] <== state [m][j]; }

View File

@ -21,16 +21,16 @@ include "misc.circom";
//
template CalculateCellIndexBits( maxLog2N ) {
signal input entropy;
signal input slotRoot;
signal input counter;
signal input cellIndexBitMask[maxLog2N]; // bit mask for the cell index range
signal output indexBits[maxLog2N];
// calculate the hash
component pos = Poseidon2_hash_rate2( 3 ); // input is 3 field elements
component pos = Poseidon2_hash_rate2( 3 ); // input is 3 field elements
signal hash;
pos.inp[0] <== entropy;
pos.inp[1] <== slotRoot;
@ -49,7 +49,7 @@ template CalculateCellIndexBits( maxLog2N ) {
//------------------------------------------------------------------------------
//
//
// sample `nSamples` number of cells; calculate their hashes;
// reconstruct the slot root using the Merkle paths, then
// the dataset root too, and checks if everything is consistent.
@ -76,7 +76,7 @@ template SampleAndProve( maxDepth, maxLog2NSlots, blockTreeDepth, nFieldElemsPer
// -------------------------------------------------------
//
// first we prove the inclusion of the slot root in the dataset-level
// first we prove the inclusion of the slot root in the dataset-level
// (small) Merkle tree
component tbtp = ToBits( maxLog2NSlots );
@ -94,17 +94,15 @@ template SampleAndProve( maxDepth, maxLog2NSlots, blockTreeDepth, nFieldElemsPer
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
// NOTE: in general we need for the 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?
//
@ -118,7 +116,7 @@ log("top root check = ", mtop.recRoot == dataSetRoot);
calci[cnt] = CalculateCellIndexBits( maxDepth );
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]; }

View File

@ -1,10 +1,8 @@
pragma circom 2.0.0;
include "poseidon2_perm.circom";
include "poseidon2_hash.circom";
include "merkle.circom";
include "misc.circom";
//------------------------------------------------------------------------------