diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index b387a75b0..3049a767b 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -39,6 +39,85 @@ int Equihash::InitialiseState(eh_HashState& base_state) personalization); } +void ExpandArray(const unsigned char* in, size_t in_len, + unsigned char* out, size_t out_len, + size_t bit_len) +{ + assert(bit_len >= 8); + assert(8*sizeof(uint32_t) >= 7+bit_len); + + size_t out_width { (bit_len+7)/8 }; + assert(out_len == 8*out_width*in_len/bit_len); + + uint32_t bit_len_mask { ((uint32_t)1 << bit_len) - 1 }; + + // The acc_bits least-significant bits of acc_value represent a bit sequence + // in big-endian order. + size_t acc_bits = 0; + uint32_t acc_value = 0; + + size_t j = 0; + for (size_t i = 0; i < in_len; i++) { + acc_value = (acc_value << 8) | in[i]; + acc_bits += 8; + + // When we have bit_len or more bits in the accumulator, write the next + // output element. + if (acc_bits >= bit_len) { + acc_bits -= bit_len; + for (size_t x = 0; x < out_width; x++) { + out[j+x] = ( + // Big-endian + acc_value >> (acc_bits+(8*(out_width-x-1))) + ) & ( + // Apply bit_len_mask across byte boundaries + (bit_len_mask >> (8*(out_width-x-1))) & 0xFF + ); + } + j += out_width; + } + } +} + +void CompressArray(const unsigned char* in, size_t in_len, + unsigned char* out, size_t out_len, + size_t bit_len) +{ + assert(bit_len >= 8); + assert(8*sizeof(uint32_t) >= 7+bit_len); + + size_t in_width { (bit_len+7)/8 }; + assert(out_len == bit_len*in_len/(8*in_width)); + + uint32_t bit_len_mask { ((uint32_t)1 << bit_len) - 1 }; + + // The acc_bits least-significant bits of acc_value represent a bit sequence + // in big-endian order. + size_t acc_bits = 0; + uint32_t acc_value = 0; + + size_t j = 0; + for (size_t i = 0; i < out_len; i++) { + // When we have fewer than 8 bits left in the accumulator, read the next + // input element. + if (acc_bits < 8) { + acc_value = acc_value << bit_len; + for (size_t x = 0; x < in_width; x++) { + acc_value = acc_value | ( + ( + // Apply bit_len_mask across byte boundaries + in[j+x] & ((bit_len_mask >> (8*(in_width-x-1))) & 0xFF) + ) << (8*(in_width-x-1))); // Big-endian + } + j += in_width; + acc_bits += bit_len; + } + + acc_bits -= 8; + out[i] = (acc_value >> acc_bits) & 0xFF; + } +} + // Big-endian so that lexicographic array comparison is equivalent to integer // comparison void EhIndexToArray(const eh_index i, unsigned char* array) diff --git a/src/crypto/equihash.h b/src/crypto/equihash.h index 7b06ecb08..f2515b327 100644 --- a/src/crypto/equihash.h +++ b/src/crypto/equihash.h @@ -24,6 +24,13 @@ typedef crypto_generichash_blake2b_state eh_HashState; typedef uint32_t eh_index; typedef uint8_t eh_trunc; +void ExpandArray(const unsigned char* in, size_t in_len, + unsigned char* out, size_t out_len, + size_t bit_len); +void CompressArray(const unsigned char* in, size_t in_len, + unsigned char* out, size_t out_len, + size_t bit_len); + eh_index ArrayToEhIndex(const unsigned char* array); eh_trunc TruncateIndex(const eh_index i, const unsigned int ilen); diff --git a/src/gtest/test_equihash.cpp b/src/gtest/test_equihash.cpp index 81ddf1908..f8337a5b4 100644 --- a/src/gtest/test_equihash.cpp +++ b/src/gtest/test_equihash.cpp @@ -3,6 +3,33 @@ #include "crypto/equihash.h" +void TestExpandAndCompress(const std::string &scope, size_t bit_len, + std::vector compact, + std::vector expanded) +{ + SCOPED_TRACE(scope); + + std::vector out(expanded.size()); + ExpandArray(compact.data(), compact.size(), out.data(), out.size(), bit_len); + EXPECT_EQ(expanded, out); + + out.resize(compact.size()); + CompressArray(expanded.data(), expanded.size(), out.data(), out.size(), bit_len); + EXPECT_EQ(compact, out); +} + +TEST(equihash_tests, expand_and_contract_arrays) { + TestExpandAndCompress("8 11-bit chunks, all-ones", 11, + ParseHex("ffffffffffffffffffffff"), + ParseHex("07ff07ff07ff07ff07ff07ff07ff07ff")); + TestExpandAndCompress("8 21-bit chunks, alternating 1s and 0s", 21, + ParseHex("aaaaad55556aaaab55555aaaaad55556aaaab55555"), + ParseHex("155555155555155555155555155555155555155555155555")); + TestExpandAndCompress("16 14-bit chunks, alternating 11s and 00s", 14, + ParseHex("cccf333cccf333cccf333cccf333cccf333cccf333cccf333cccf333"), + ParseHex("3333333333333333333333333333333333333333333333333333333333333333")); +} + TEST(equihash_tests, is_probably_duplicate) { std::shared_ptr p1 (new eh_trunc[4] {0, 1, 2, 3}); std::shared_ptr p2 (new eh_trunc[4] {0, 1, 1, 3});