Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom fields validation fix #429

Merged
merged 6 commits into from Mar 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion qa/rpc-tests/sc_big_block.py
Expand Up @@ -177,7 +177,7 @@ def advance_sidechains_epoch(num_of_scs):
vCfe = ["ab000100"]
vCmt = [BIT_VECTOR_BUF]

fe1 = "00000000000000000000000000000000000000000000000000000000" + "ab000100"
fe1 = "ab000100" + "00000000000000000000000000000000000000000000000000000000"
fe2 = BIT_VECTOR_FE

proofCfeArray = [fe1, fe2]
Expand Down
20 changes: 10 additions & 10 deletions qa/rpc-tests/sc_cert_customfields.py
Expand Up @@ -346,7 +346,7 @@ def run_test(self):
vCmt = []

# serialized fe for the proof has 32 byte size
fe1 = "000000000000000000000000000000000000000000000000000000000000" + "0100"
fe1 = "0100" + "000000000000000000000000000000000000000000000000000000000000"

scProof3 = mcTest.create_test_proof(
'sc2', scid2_swapped, epoch_number_1, 10, MBTR_SC_FEE, FT_SC_FEE, epoch_cum_tree_hash_1, constant2, [addr_node1], [bwt_amount],
Expand Down Expand Up @@ -426,9 +426,9 @@ def run_test(self):
# with the one declared during sidechain creation.
vCmt = [BIT_VECTOR_BUF_NOT_POW2]

fe1 = "00000000000000000000000000000000000000000000000000000000" + "ab000100"
fe2 = "0000000000000000000000000000000000000000000000000000" + "ccccdddd0000"
fe3 = "000000000000000000000000000000000000000000000000000000000000" + "0100"
fe1 = "ab000100" + "00000000000000000000000000000000000000000000000000000000"
fe2 = "ccccdddd0000" + "0000000000000000000000000000000000000000000000000000"
fe3 = "0100" + "000000000000000000000000000000000000000000000000000000000000"
fe4 = BIT_VECTOR_FE

scProof3 = mcTest.create_test_proof(
Expand Down Expand Up @@ -466,9 +466,9 @@ def run_test(self):
# Such a bit vector should be rejected from the node.
vCmt = [BIT_VECTOR_BUF_HUGE]

fe1 = "00000000000000000000000000000000000000000000000000000000" + "ab000100"
fe2 = "0000000000000000000000000000000000000000000000000000" + "ccccdddd0000"
fe3 = "000000000000000000000000000000000000000000000000000000000000" + "0100"
fe1 = "ab000100" + "00000000000000000000000000000000000000000000000000000000"
fe2 = "ccccdddd0000" + "0000000000000000000000000000000000000000000000000000"
fe3 = "0100" + "000000000000000000000000000000000000000000000000000000000000"
fe4 = BIT_VECTOR_FE

scProof3 = mcTest.create_test_proof(
Expand Down Expand Up @@ -505,9 +505,9 @@ def run_test(self):
# this is a compressed buffer which will yield a valid field element for the proof (see below)
vCmt = [BIT_VECTOR_BUF]

fe1 = "00000000000000000000000000000000000000000000000000000000" + "ab000100"
fe2 = "0000000000000000000000000000000000000000000000000000" + "ccccdddd0000"
fe3 = "000000000000000000000000000000000000000000000000000000000000" + "0100"
fe1 = "ab000100" + "00000000000000000000000000000000000000000000000000000000"
fe2 = "ccccdddd0000" + "0000000000000000000000000000000000000000000000000000"
fe3 = "0100" + "000000000000000000000000000000000000000000000000000000000000"
fe4 = BIT_VECTOR_FE

scProof3 = mcTest.create_test_proof(
Expand Down
8 changes: 4 additions & 4 deletions qa/rpc-tests/sc_rpc_cmds_json_output.py
Expand Up @@ -326,7 +326,7 @@ def dump_json_getblocktemplate(fileName, nodeid=0):
vCmt = []

# serialized fe for the proof has 32 byte size
fe1 = "000000000000000000000000000000000000000000000000000000000000" + "0100"
fe1 = "0100" + "000000000000000000000000000000000000000000000000000000000000"

quality = 72
scProof3 = certMcTest.create_test_proof(
Expand Down Expand Up @@ -370,9 +370,9 @@ def dump_json_getblocktemplate(fileName, nodeid=0):
# this is a compressed buffer which will yield a valid field element for the proof (see below)
vCmt = [BIT_VECTOR_BUF]

fe1 = "00000000000000000000000000000000000000000000000000000000" + "ab000100"
fe2 = "0000000000000000000000000000000000000000000000000000" + "ccccdddd0000"
fe3 = "000000000000000000000000000000000000000000000000000000000000" + "0100"
fe1 = "ab000100" + "00000000000000000000000000000000000000000000000000000000"
fe2 = "ccccdddd0000" + "0000000000000000000000000000000000000000000000000000"
fe3 = "0100" + "000000000000000000000000000000000000000000000000000000000000"
fe4 = BIT_VECTOR_FE

quality = 18
Expand Down
180 changes: 179 additions & 1 deletion src/gtest/test_libzendoo.cpp
Expand Up @@ -17,8 +17,21 @@
#include <clientversion.h>
#include <sc/proofverifier.h> // for MC_CRYPTO_LIB_MOCKED

#include <boost/dynamic_bitset.hpp>

using namespace blockchain_test_utils;

/**
* @brief Custom field randomly generated from seed 1641809674 using the rand() function.
* Also the seed has been chosen randomly so that the first byte of the custom field
* has the most significant bit set (to avoid that unit tests pass due to wrong endianness
* even when they should not).
*/
const uint8_t TEST_CUSTOM_FIELD[] = {0xbe, 0x61, 0x16, 0xab, 0x27, 0xee, 0xab, 0xbc,
0x09, 0x35, 0xb3, 0xe2, 0x1b, 0xc3, 0xcf, 0xcd,
0x3f, 0x06, 0xac, 0xb3, 0x8a, 0x5c, 0xeb, 0xd4,
0x42, 0xf4, 0x96, 0xd8, 0xbf, 0xd3, 0x8e, 0x7d};

static CMutableTransaction CreateDefaultTx()
{
// Create a tx with a sc creation, a fwt, a bwtr and a csw
Expand Down Expand Up @@ -1973,4 +1986,169 @@ TEST(CctpLibrary, TestInvalidProofVkWhenOversized)
EXPECT_FALSE(vkInvalid.IsValid());

//TODO: Might be useful to test the same behaviour with bit vector
}
}

TEST(CctpLibrary, TestGetLeadingZeros)
{
ASSERT_EQ(8, getLeadingZeroBitsInByte(0));

for (uint8_t n = 1; n > 0; n++)
{
ASSERT_EQ(__builtin_clz(n) % (sizeof(unsigned int) * 8 - CHAR_BIT), getLeadingZeroBitsInByte(n));
}
}

/**
* @brief Check the correctness of the GetBytesFromBits utility function
*
* In order to keep the test fast, the iteration is limited to the first 2^16 numbers.
*/
TEST(CctpLibrary, TestGetBytesFromBits)
{
int reminder = 0;

// Check that the function works properly with the "0" input
ASSERT_EQ(0, getBytesFromBits(0, reminder));
alsala marked this conversation as resolved.
Show resolved Hide resolved
ASSERT_EQ(0, reminder);

// Check that the function works properly with a negative input
ASSERT_EQ(0, getBytesFromBits(-1, reminder));
ASSERT_EQ(0, reminder);

for (uint16_t n = 1; n > 0; n++)
{
// Allocate a set of n bits
boost::dynamic_bitset<uint8_t> bitset(n);

// Convert the bit set to byte vector
std::vector<uint8_t> bytes;
boost::to_block_range(bitset, std::back_inserter(bytes));

ASSERT_EQ(bytes.size(), getBytesFromBits(n, reminder));

if (reminder == 0)
{
ASSERT_EQ(0, n % CHAR_BIT);
}
else
{
ASSERT_EQ(reminder, n - (bytes.size() - 1) * CHAR_BIT);
}
}
}

alsala marked this conversation as resolved.
Show resolved Hide resolved
TEST(CctpLibrary, TestCustomFieldsValidation)
{
for (uint8_t i = 1; i < CHAR_BIT; i++)
i-Alex marked this conversation as resolved.
Show resolved Hide resolved
{
for (uint8_t j = 1; j > 0; j++)
{
std::vector<unsigned char> rawBytes = { j };
FieldElementCertificateField certField = FieldElementCertificateField(rawBytes);
FieldElementCertificateFieldConfig config = FieldElementCertificateFieldConfig(i);
CFieldElement fe = certField.GetFieldElement(config);

if (j < 1 << i)
{
EXPECT_TRUE(fe.IsValid());
}
else
{
EXPECT_FALSE(fe.IsValid());
}
}
}
}

/**
* @brief Test the validation of a full (32 bytes) random custom field
* iteratively changing the last byte.
*/
TEST(CctpLibrary, TestFullCustomFieldValidation)
{
std::vector<unsigned char> rawBytes(std::begin(TEST_CUSTOM_FIELD), std::end(TEST_CUSTOM_FIELD));

for (uint8_t i = 1; i < CHAR_BIT; i++)
{
for (uint8_t j = 1; j > 0; j++)
{
rawBytes.back() = j;
FieldElementCertificateField certField = FieldElementCertificateField(rawBytes);
FieldElementCertificateFieldConfig config = FieldElementCertificateFieldConfig(i + 31 * CHAR_BIT);
CFieldElement fe = certField.GetFieldElement(config);

// The Field Element is valid if it matches the configuration (j < 1 << i)
// and if doesn't exceed the modulus, thus the last two bits cannot be set (j < 1 << 6).
if (j < 1 << i && j < 1 << 6)
{
EXPECT_TRUE(fe.IsValid());
alsala marked this conversation as resolved.
Show resolved Hide resolved
}
else
{
EXPECT_FALSE(fe.IsValid());
}
}
}
}

/**
* @brief Test the validation of a random custom field
* with a size equivalent to a long integer (64 bits, 8 bytes)
* iteratively changing the last byte.
*/
TEST(CctpLibrary, TestLongIntCustomFieldValidation)
{
std::vector<unsigned char> rawBytes(std::begin(TEST_CUSTOM_FIELD), std::end(TEST_CUSTOM_FIELD));
rawBytes.resize(8);

for (uint8_t i = 1; i < CHAR_BIT; i++)
{
for (uint8_t j = 1; j > 0; j++)
{
rawBytes.back() = j;
FieldElementCertificateField certField = FieldElementCertificateField(rawBytes);
FieldElementCertificateFieldConfig config = FieldElementCertificateFieldConfig(i + 7 * CHAR_BIT);
CFieldElement fe = certField.GetFieldElement(config);

if (j < 1 << i)
{
EXPECT_TRUE(fe.IsValid());
}
else
{
EXPECT_FALSE(fe.IsValid());
}
}
}
}

/**
* @brief Test the validation of a random custom field
* with a size equivalent to an integer (32 bits, 4 bytes)
* iteratively changing the last byte.
*/
TEST(CctpLibrary, TestIntCustomFieldValidation)
{
std::vector<unsigned char> rawBytes(std::begin(TEST_CUSTOM_FIELD), std::end(TEST_CUSTOM_FIELD));
rawBytes.resize(4);

for (uint8_t i = 1; i < CHAR_BIT; i++)
{
for (uint8_t j = 1; j > 0; j++)
{
rawBytes.back() = j;
FieldElementCertificateField certField = FieldElementCertificateField(rawBytes);
FieldElementCertificateFieldConfig config = FieldElementCertificateFieldConfig(i + 3 * CHAR_BIT);
CFieldElement fe = certField.GetFieldElement(config);

if (j < 1 << i)
{
EXPECT_TRUE(fe.IsValid());
}
else
{
EXPECT_FALSE(fe.IsValid());
}
}
}
}
4 changes: 2 additions & 2 deletions src/sc/sidechaintypes.cpp
Expand Up @@ -595,7 +595,7 @@ const CFieldElement& FieldElementCertificateField::GetFieldElement(const FieldEl
{
// check null bits in the last byte are as expected
unsigned char lastByte = vRawData.back();
int numbOfZeroBits = getTrailingZeroBitsInByte(lastByte);
int numbOfZeroBits = getLeadingZeroBitsInByte(lastByte);
if (numbOfZeroBits < (CHAR_BIT - rem))
{
LogPrint("sc", "%s():%d - ERROR: wrong number of null bits in last byte[0x%x]: %d vs %d\n",
Expand All @@ -605,7 +605,7 @@ const CFieldElement& FieldElementCertificateField::GetFieldElement(const FieldEl
}

std::vector<unsigned char> extendedRawData = vRawData;
extendedRawData.insert(extendedRawData.begin(), CFieldElement::ByteSize()-vRawData.size(), 0x0);
extendedRawData.insert(extendedRawData.end(), CFieldElement::ByteSize() - vRawData.size(), 0x0);

fieldElement.SetByteArray(extendedRawData);
if (fieldElement.IsValid())
Expand Down
26 changes: 15 additions & 11 deletions src/util.cpp
Expand Up @@ -17,6 +17,7 @@
#include "utiltime.h"

#include <stdarg.h>
#include <stdio.h>

#if (defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__))
#include <pthread.h>
Expand Down Expand Up @@ -934,21 +935,24 @@ int GetNumCores()
return boost::thread::physical_concurrency();
}

int getTrailingZeroBitsInByte(unsigned char inputByte)
/**
* @brief Get the Leading Zero Bits in a byte
* (e.g 00000100 => 5 trailing zero bits).
*
* @param inputByte the byte to be checked
* @return int The number of trailing zero bits found.
*/
int getLeadingZeroBitsInByte(unsigned char inputByte)
{
// output: c will count inputByte's trailing zero bits,
// so if inputByte is 1101000 (base 2), then c will be 3
int c = CHAR_BIT;
int nonZeroBits = 0;

if (inputByte)
while (inputByte > 0)
{
inputByte = (inputByte ^ (inputByte - 1)) >> 1; // Set inputByte's trailing 0s to 1s and zero rest
for (c = 0; inputByte; c++)
{
inputByte >>= 1;
}
inputByte >>= 1;
nonZeroBits++;
}
return c;

return CHAR_BIT - nonZeroBits;
}

int getBytesFromBits(int nbits, int& reminder)
Expand Down
2 changes: 1 addition & 1 deletion src/util.h
Expand Up @@ -296,7 +296,7 @@ std::string dbg_blk_unlinked();
std::string dbg_blk_candidates();
std::string dbg_blk_global_tips();

int getTrailingZeroBitsInByte(unsigned char inputByte);
int getLeadingZeroBitsInByte(unsigned char inputByte);
int getBytesFromBits(int nbits, int& reminder);

#endif // BITCOIN_UTIL_H