mirror of
https://github.com/logos-storage/logos-storage-proofs-circuits.git
synced 2026-01-02 21:43:10 +00:00
Merge pull request #5 from codex-storage/review
Code review of the circom circuit
This commit is contained in:
commit
2a4a71acf6
@ -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!
|
||||
//
|
||||
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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).
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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]; }
|
||||
|
||||
@ -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]; }
|
||||
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
pragma circom 2.0.0;
|
||||
|
||||
include "poseidon2_perm.circom";
|
||||
include "poseidon2_hash.circom";
|
||||
|
||||
include "merkle.circom";
|
||||
include "misc.circom";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user