diff --git a/include/ethash/keccak.h b/include/ethash/keccak.h index b513b6d0..a5b4f8f3 100644 --- a/include/ethash/keccak.h +++ b/include/ethash/keccak.h @@ -22,6 +22,25 @@ union ethash_hash256 ethash_keccak256_32(const uint8_t data[32]) noexcept; union ethash_hash512 ethash_keccak512(const uint8_t* data, size_t size) noexcept; union ethash_hash512 ethash_keccak512_64(const uint8_t data[64]) noexcept; +struct ethash_keccak256_context { + size_t hash_size; + size_t block_size; + uint64_t state[25]; + uint64_t* state_iter; + uint64_t last_word; + uint8_t* last_word_iter; + uint8_t buffer[136]; + size_t buffer_index; +}; + +void ethash_keccak256_init(struct ethash_keccak256_context* ctx) noexcept; +void ethash_keccak256_update(struct ethash_keccak256_context* ctx, const uint8_t* data, size_t size) noexcept; +union ethash_hash256 ethash_keccak256_final(struct ethash_keccak256_context* ctx) noexcept; + +void ethash_keccak256_init_2(struct ethash_keccak256_context* ctx) noexcept; +void ethash_keccak256_update_2(struct ethash_keccak256_context* ctx, const uint8_t* data, size_t size) noexcept; +union ethash_hash256 ethash_keccak256_final_2(struct ethash_keccak256_context* ctx) noexcept; + #ifdef __cplusplus } #endif diff --git a/include/ethash/keccak.hpp b/include/ethash/keccak.hpp index d5dad63b..3bb57995 100644 --- a/include/ethash/keccak.hpp +++ b/include/ethash/keccak.hpp @@ -32,4 +32,34 @@ inline hash512 keccak512(const hash512& input) noexcept static constexpr auto keccak256_32 = ethash_keccak256_32; static constexpr auto keccak512_64 = ethash_keccak512_64; +inline void keccak256_init(struct ethash_keccak256_context* ctx) noexcept +{ + ethash_keccak256_init(ctx); +} + +inline void keccak256_update(struct ethash_keccak256_context* ctx, const uint8_t* data, size_t size) noexcept +{ + ethash_keccak256_update(ctx, data, size); +} + +inline hash256 keccak256_final(struct ethash_keccak256_context* ctx) noexcept +{ + return ethash_keccak256_final(ctx); +} + +inline void keccak256_init_2(struct ethash_keccak256_context* ctx) noexcept +{ + ethash_keccak256_init_2(ctx); +} + +inline void keccak256_update_2(struct ethash_keccak256_context* ctx, const uint8_t* data, size_t size) noexcept +{ + ethash_keccak256_update_2(ctx, data, size); +} + +inline hash256 keccak256_final_2(struct ethash_keccak256_context* ctx) noexcept +{ + return ethash_keccak256_final_2(ctx); +} + } // namespace ethash diff --git a/lib/keccak/keccak.c b/lib/keccak/keccak.c index 286ea9d3..a621e665 100644 --- a/lib/keccak/keccak.c +++ b/lib/keccak/keccak.c @@ -8,6 +8,7 @@ #if !__has_builtin(__builtin_memcpy) && !defined(__GNUC__) #include #define __builtin_memcpy memcpy +#define __builtin_memset memset #endif #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ @@ -44,7 +45,6 @@ static const uint64_t round_constants[24] = { // 0x8000000000008002, 0x8000000000000080, 0x000000000000800a, 0x800000008000000a, 0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008}; - /// The Keccak-f[1600] function. /// /// The implementation of the Keccak-f function with 1600-bit width of the permutation (b). @@ -342,11 +342,11 @@ static inline ALWAYS_INLINE void keccak( ++data; --size; } + *last_word_iter = 0x01; *state_iter ^= to_le64(last_word); state[(block_size / word_size) - 1] ^= 0x8000000000000000; - keccakf1600_best(state); for (i = 0; i < (hash_size / word_size); ++i) @@ -380,3 +380,255 @@ union ethash_hash512 ethash_keccak512_64(const uint8_t data[64]) keccak(hash.word64s, 512, data, 64); return hash; } + +static inline ALWAYS_INLINE void keccak_init(struct ethash_keccak256_context* ctx, size_t bits) +{ + __builtin_memset((uint8_t*)ctx->state, 0, sizeof ctx->state); + ctx->hash_size = bits / 8; + ctx->block_size = (1600 - bits * 2) / 8; + + ctx->last_word = 0; + ctx->last_word_iter = (uint8_t*)&(ctx->last_word); + + ctx->state_iter = ctx->state; +} + +static inline ALWAYS_INLINE void keccak_update( + struct ethash_keccak256_context* ctx, const uint8_t* data, size_t size) +{ + static const size_t word_size = sizeof(uint64_t); + size_t i; + + size_t block_size_b = ctx->block_size / word_size; // block size in bytes + size_t last_word_unfilled_size = // calculate unfilled space in last word + (word_size - (size_t)(ctx->last_word_iter - (uint8_t*)&(ctx->last_word))); + size_t state_unfilled_size = // calculate unfilled space in state + (block_size_b - (size_t)(ctx->state_iter - ctx->state)); + + // fill the last word unfilled space with bytes until it's full + while(last_word_unfilled_size > 0 && size > 0) + { + *ctx->last_word_iter = *data; + ++ctx->last_word_iter; + ++data; + --size; + --last_word_unfilled_size; + } + + // if the last word is full, move it to state. + if(ctx->last_word_iter == (uint8_t*)&(ctx->last_word) + word_size) + { + *ctx->state_iter ^= to_le64(ctx->last_word); + ++ctx->state_iter; + ctx->last_word = 0; + ctx->last_word_iter = (uint8_t*)&(ctx->last_word); + --state_unfilled_size; + } + + // fill the state unfilled space with words until it's full + while(state_unfilled_size > 0 && size >= word_size) + { + *ctx->state_iter ^= load_le(data); + ++ctx->state_iter; + data += word_size; + size -= word_size; + --state_unfilled_size; + } + + // if the state is full, calculate keccak and reset the state iterator + if(ctx->state_iter == ctx->state + (ctx->block_size / word_size)) + { + keccakf1600_best(ctx->state); + ctx->state_iter = ctx->state; + } + + // if there is more data then block size, fill the whole blocks + if(size >= ctx->block_size) + { + while(size >= ctx->block_size) + { + for (i = 0; i < (ctx->block_size / word_size); ++i) + { + ctx->state[i] ^= load_le(data); + data += word_size; + } + + keccakf1600_best(ctx->state); + + size -= ctx->block_size; + } + + ctx->state_iter = ctx->state; + } + + // if there is more data then word size, fill the whole words + while (size >= word_size) + { + *ctx->state_iter ^= load_le(data); + ++ctx->state_iter; + data += word_size; + size -= word_size; + } + + // if there is still some data put it into last word. + while (size > 0) + { + *ctx->last_word_iter = *data; + ++ctx->last_word_iter; + ++data; + --size; + } + + // if the last word is full, move it to state. + if(ctx->last_word_iter == (uint8_t*)&(ctx->last_word) + word_size) + { + *ctx->state_iter ^= to_le64(ctx->last_word); + ++ctx->state_iter; + ctx->last_word = 0; + ctx->last_word_iter = (uint8_t*)&(ctx->last_word); + } + + // if the state is full, calculate keccak and reset the state iterator + if(ctx->state_iter == ctx->state + (ctx->block_size / word_size)) + { + keccakf1600_best(ctx->state); + ctx->state_iter = ctx->state; + } +} + +static inline ALWAYS_INLINE void keccak_final(struct ethash_keccak256_context* ctx, uint64_t* out) +{ + static const size_t word_size = sizeof(uint64_t); + size_t i; + + *ctx->last_word_iter = 0x01; + *ctx->state_iter ^= to_le64(ctx->last_word); + + ctx->state[(ctx->block_size / word_size) - 1] ^= 0x8000000000000000; + + keccakf1600_best(ctx->state); + + for (i = 0; i < (ctx->hash_size / word_size); ++i) + out[i] = to_le64(ctx->state[i]); +} + +void ethash_keccak256_init(struct ethash_keccak256_context* ctx) +{ + keccak_init(ctx, 256); +} + +void ethash_keccak256_update(struct ethash_keccak256_context* ctx, const uint8_t* data, size_t size) +{ + keccak_update(ctx, data, size); +} + +union ethash_hash256 ethash_keccak256_final(struct ethash_keccak256_context* ctx) +{ + union ethash_hash256 hash; + keccak_final(ctx, hash.word64s); + return hash; +} + +static inline ALWAYS_INLINE void keccak_init_2(struct ethash_keccak256_context* ctx, size_t bits) +{ + __builtin_memset((uint8_t*)ctx->state, 0, sizeof ctx->state); + ctx->state_iter = ctx->state; + + ctx->hash_size = bits / 8; + ctx->block_size = (1600 - bits * 2) / 8; + ctx->last_word = 0; + ctx->last_word_iter = (uint8_t*)&ctx->last_word; + + __builtin_memset(ctx->buffer, 0, sizeof ctx->buffer); + ctx->buffer_index = 0; +} + +static inline ALWAYS_INLINE void keccak_update_2( + struct ethash_keccak256_context* ctx, const uint8_t* data, size_t size) +{ + static const size_t word_size = sizeof(uint64_t); + + while(size > 0) + { + size_t empty_space_size = ctx->block_size - ctx->buffer_index; + size_t data_to_load_size = size >= empty_space_size ? empty_space_size : size; + + __builtin_memcpy(&ctx->buffer[ctx->buffer_index], data, data_to_load_size); + ctx->buffer_index += data_to_load_size; + size -= data_to_load_size; + data += data_to_load_size; + + if(ctx->buffer_index == ctx->block_size) + { + size_t i; + uint8_t* d = &ctx->buffer[0]; + + for (i = 0; i < (ctx->block_size / word_size); ++i) + { + *ctx->state_iter ^= load_le(d); + ++ctx->state_iter; + d += word_size; + } + + keccakf1600_best(ctx->state); + ctx->state_iter = ctx->state; + ctx->buffer_index = 0; + } + } +} + +static inline ALWAYS_INLINE void keccak_final_2(struct ethash_keccak256_context* ctx, uint64_t* out) +{ + static const size_t word_size = sizeof(uint64_t); + size_t i; + + if(ctx->buffer_index != 0) + { + uint8_t* d = ctx->buffer; + for (i = 0; i < (ctx->buffer_index / word_size); ++i) + { + *ctx->state_iter ^= load_le(d); + ++ctx->state_iter; + d += word_size; + } + + size_t last_word_size = ctx->buffer_index % word_size; + d = &ctx->buffer[ctx->buffer_index - last_word_size]; + ctx->last_word_iter = (uint8_t*)&ctx->last_word; + + while (last_word_size > 0) + { + *ctx->last_word_iter = *d; + ++ctx->last_word_iter; + ++d; + --last_word_size; + } + } + + *ctx->last_word_iter = 0x01; + *ctx->state_iter ^= to_le64(ctx->last_word); + + ctx->state[(ctx->block_size / word_size) - 1] ^= 0x8000000000000000; + + keccakf1600_best(ctx->state); + + for (i = 0; i < (ctx->hash_size / word_size); ++i) + out[i] = to_le64(ctx->state[i]); +} + +void ethash_keccak256_init_2(struct ethash_keccak256_context* ctx) +{ + keccak_init_2(ctx, 256); +} + +void ethash_keccak256_update_2(struct ethash_keccak256_context* ctx, const uint8_t* data, size_t size) +{ + keccak_update_2(ctx, data, size); +} + +union ethash_hash256 ethash_keccak256_final_2(struct ethash_keccak256_context* ctx) +{ + union ethash_hash256 hash; + keccak_final_2(ctx, hash.word64s); + return hash; +} diff --git a/test/unittests/test_keccak.cpp b/test/unittests/test_keccak.cpp index c4838729..6a69240e 100644 --- a/test/unittests/test_keccak.cpp +++ b/test/unittests/test_keccak.cpp @@ -282,3 +282,141 @@ TEST(helpers, to_hash256_empty) hash256 h = to_hash256(hex); EXPECT_EQ(h, hash256{}); } + +static size_t next_data_size_to_load() +{ + static size_t next = 1; + static const size_t sizes[216] + = {225, 50, 73, 72, 285, 273, 139, 299, 193, 243, 43, 102, 294, 262, 209, 114, 88, 94, 179, 233, 248, + 182, 34, 246, 106, 148, 198, 77, 160, 289, 115, 249, 198, 26, 203, 21, 24, 65, 222, 187, 170, 281, + 285, 185, 261, 121, 130, 270, 22, 227, 235, 206, 116, 205, 171, 30, 163, 94, 66, 98, 112, 271, 98, + 91, 181, 181, 145, 290, 12, 261, 183, 82, 40, 25, 40, 164, 293, 150, 96, 195, 218, 122, 240, 99, 7, + 71, 155, 165, 180, 238, 131, 271, 286, 186, 255, 62, 65, 110, 205, 86, 2, 83, 182, 225, 5, 156, 138, + 139, 146, 3, 250, 51, 184, 282, 9, 109, 34, 245, 169, 63, 240, 100, 151, 134, 74, 125, 225, 30, 215, + 199, 275, 131, 128, 89, 37, 195, 286, 220, 117, 183, 32, 132, 51, 122, 244, 104, 59, 73, 45, 188, 115, + 231, 179, 217, 16, 110, 58, 142, 186, 215, 201, 223, 198, 152, 64, 9, 113, 39, 141, 209, 170, 84, 209, + 16, 92, 64, 85, 239, 128, 88, 298, 153, 28, 269, 30, 38, 46, 97, 242, 119, 102, 299, 103, 288, 251, 187, + 192, 8, 57, 293, 221, 43, 184, 207, 50, 129, 203, 45, 139, 0, 129, 82, 285, 254, 214, 213}; + + next = (next + 1) % 216; + return sizes[next]; +} + +TEST(keccak, iuf_test_simple) +{ + const uint8_t* const data = reinterpret_cast(test_text); + + for (auto& t : test_cases) + { + const auto h256 = keccak256(data, t.input_size); + ASSERT_EQ(to_hex(h256), t.expected_hash256) << t.input_size; + + struct ethash_keccak256_context ctx = {}; + keccak256_init(&ctx); + keccak256_update(&ctx, data, t.input_size); + const auto h2561 = keccak256_final(&ctx); + ASSERT_EQ(to_hex(h2561), t.expected_hash256) << t.input_size; + + size_t i; + + keccak256_init(&ctx); + for(i = 0; i < t.input_size; ++i) + { + keccak256_update(&ctx, &data[i], 1); + } + const auto h2562 = keccak256_final(&ctx); + ASSERT_EQ(to_hex(h2562), t.expected_hash256) << t.input_size; + + size_t step = 0; + for(step = 1; step < 256; ++step) + { + keccak256_init(&ctx); + + for(i = 0; i < t.input_size; i = i + step) + { + size_t l = t.input_size - i >= step ? step : t.input_size - i; + keccak256_update(&ctx, &data[i], l); + } + const auto h2563 = keccak256_final(&ctx); + ASSERT_EQ(to_hex(h2563), t.expected_hash256) << t.input_size; + } + + + keccak256_init(&ctx); + + i = 0; + while(i < t.input_size) + { + step = next_data_size_to_load(); + size_t l = t.input_size - i >= step ? step : t.input_size - i; + keccak256_update(&ctx, &data[i], l); + i = i + step; + } + const auto h2563 = keccak256_final(&ctx); + ASSERT_EQ(to_hex(h2563), t.expected_hash256) << t.input_size; + } +} + +TEST(keccak, iuf_test_simple_2) +{ + const uint8_t* const data = reinterpret_cast(test_text); + + for (auto& t : test_cases) + { + { + struct ethash_keccak256_context ctx = {}; + keccak256_init_2(&ctx); + keccak256_update_2(&ctx, data, t.input_size); + const auto h256 = keccak256_final_2(&ctx); + ASSERT_EQ(to_hex(h256), t.expected_hash256) << t.input_size; + } + + { + size_t i; + + struct ethash_keccak256_context ctx = {}; + keccak256_init_2(&ctx); + for(i = 0; i < t.input_size; ++i) + { + keccak256_update_2(&ctx, &data[i], 1); + } + const auto h256 = keccak256_final_2(&ctx); + ASSERT_EQ(to_hex(h256), t.expected_hash256) << t.input_size; + } + + { + size_t i; + size_t step = 0; + struct ethash_keccak256_context ctx = {}; + for(step = 1; step < 256; ++step) + { + keccak256_init_2(&ctx); + + for(i = 0; i < t.input_size; i = i + step) + { + size_t l = t.input_size - i >= step ? step : t.input_size - i; + keccak256_update_2(&ctx, &data[i], l); + } + const auto h256 = keccak256_final_2(&ctx); + ASSERT_EQ(to_hex(h256), t.expected_hash256) << t.input_size; + } + } + + { + struct ethash_keccak256_context ctx = {}; + keccak256_init_2(&ctx); + + size_t i = 0; + size_t step = 0; + while(i < t.input_size) + { + step = next_data_size_to_load(); + size_t l = t.input_size - i >= step ? step : t.input_size - i; + keccak256_update_2(&ctx, &data[i], l); + i = i + step; + } + const auto h256 = keccak256_final_2(&ctx); + ASSERT_EQ(to_hex(h256), t.expected_hash256) << t.input_size; + } + } +}