From 404c6f35918407b157c9750d26a099687e3439bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 4 Apr 2024 17:45:58 +0200 Subject: [PATCH 1/3] crypto: Implement BLAKE2b compress function F Adds the implementation of the BLAKE2b compress function F following the [RFC 7693](https://datatracker.ietf.org/doc/html/rfc7693). --- lib/evmone_precompiles/CMakeLists.txt | 2 + lib/evmone_precompiles/blake2b.cpp | 74 +++++++++++++++++++ lib/evmone_precompiles/blake2b.hpp | 20 ++++++ test/unittests/CMakeLists.txt | 1 + test/unittests/precompiles_blake2b_test.cpp | 80 +++++++++++++++++++++ 5 files changed, 177 insertions(+) create mode 100644 lib/evmone_precompiles/blake2b.cpp create mode 100644 lib/evmone_precompiles/blake2b.hpp create mode 100644 test/unittests/precompiles_blake2b_test.cpp diff --git a/lib/evmone_precompiles/CMakeLists.txt b/lib/evmone_precompiles/CMakeLists.txt index b3b917a090..2d02e86853 100644 --- a/lib/evmone_precompiles/CMakeLists.txt +++ b/lib/evmone_precompiles/CMakeLists.txt @@ -7,6 +7,8 @@ add_library(evmone::precompiles ALIAS evmone_precompiles) target_link_libraries(evmone_precompiles PUBLIC evmc::evmc_cpp PRIVATE evmone::evmmax) target_sources( evmone_precompiles PRIVATE + blake2b.hpp + blake2b.cpp bn254.hpp bn254.cpp ecc.hpp diff --git a/lib/evmone_precompiles/blake2b.cpp b/lib/evmone_precompiles/blake2b.cpp new file mode 100644 index 0000000000..60ef1aa600 --- /dev/null +++ b/lib/evmone_precompiles/blake2b.cpp @@ -0,0 +1,74 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2024 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "blake2b.hpp" +#include +#include + +namespace evmone::crypto +{ +void blake2b_compress( + uint32_t rounds, uint64_t h[8], const uint64_t m[16], const uint64_t t[2], bool last) noexcept +{ + // Message Schedule SIGMA. + // https://datatracker.ietf.org/doc/html/rfc7693#section-2.7 + static constexpr uint8_t sigma[10][16]{ + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, + {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, + {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, + {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, + {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, + {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, + {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, + {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, + {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, + }; + + // Mixing Function G. + // https://datatracker.ietf.org/doc/html/rfc7693#section-3.1 + // + // The G primitive function mixes two input words, "x" and "y", into + // four words indexed by "a", "b", "c", and "d" in the working vector v[0..15]. + static constexpr auto g = [](uint64_t v[16], size_t a, size_t b, size_t c, size_t d, uint64_t x, + uint64_t y) noexcept { + v[a] = v[a] + v[b] + x; + v[d] = std::rotr(v[d] ^ v[a], 32); + v[c] = v[c] + v[d]; + v[b] = std::rotr(v[b] ^ v[c], 24); + v[a] = v[a] + v[b] + y; + v[d] = std::rotr(v[d] ^ v[a], 16); + v[c] = v[c] + v[d]; + v[b] = std::rotr(v[b] ^ v[c], 63); + }; + + // Initialize local work vector v[0..15]. + uint64_t v[16]{h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7], // First half from state. + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, // Second half from IV. + 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1 ^ t[0], // Low word of the offset. + 0x9b05688c2b3e6c1f ^ t[1], // High word. + 0x1f83d9abfb41bd6b ^ (0 - uint64_t{last}), // Last block flag? Invert all bits. + 0x5be0cd19137e2179}; + + // Cryptographic mixing. + for (size_t i = 0; i < rounds; ++i) + { + // Message word selection permutation for this round. + const auto& s = sigma[i % std::size(sigma)]; + + g(v, 0, 4, 8, 12, m[s[0]], m[s[1]]); + g(v, 1, 5, 9, 13, m[s[2]], m[s[3]]); + g(v, 2, 6, 10, 14, m[s[4]], m[s[5]]); + g(v, 3, 7, 11, 15, m[s[6]], m[s[7]]); + g(v, 0, 5, 10, 15, m[s[8]], m[s[9]]); + g(v, 1, 6, 11, 12, m[s[10]], m[s[11]]); + g(v, 2, 7, 8, 13, m[s[12]], m[s[13]]); + g(v, 3, 4, 9, 14, m[s[14]], m[s[15]]); + } + + for (size_t i = 0; i < 8; ++i) // XOR the two halves. + h[i] ^= v[i] ^ v[i + 8]; +} +} // namespace evmone::crypto diff --git a/lib/evmone_precompiles/blake2b.hpp b/lib/evmone_precompiles/blake2b.hpp new file mode 100644 index 0000000000..36dc282c80 --- /dev/null +++ b/lib/evmone_precompiles/blake2b.hpp @@ -0,0 +1,20 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2024 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 +#pragma once +#include +#include + +namespace evmone::crypto +{ +/// BLAKE2b compress function F. +/// https://datatracker.ietf.org/doc/html/rfc7693#section-3.2 +/// +/// @param rounds the number of rounds to perform +/// @param[in,out] h the state vector +/// @param m the block vector +/// @param t the 128-bit offset counter, {low word, high word} +/// @param last the final block indicator flag "f" +void blake2b_compress( + uint32_t rounds, uint64_t h[8], const uint64_t m[16], const uint64_t t[2], bool last) noexcept; +} // namespace evmone::crypto diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index af4695e925..24953ab90d 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -45,6 +45,7 @@ target_sources( exportable_fixture.hpp exportable_fixture.cpp instructions_test.cpp + precompiles_blake2b_test.cpp precompiles_ripemd160_test.cpp state_block_test.cpp state_bloom_filter_test.cpp diff --git a/test/unittests/precompiles_blake2b_test.cpp b/test/unittests/precompiles_blake2b_test.cpp new file mode 100644 index 0000000000..1e0b415a08 --- /dev/null +++ b/test/unittests/precompiles_blake2b_test.cpp @@ -0,0 +1,80 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2024 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "../utils/utils.hpp" +#include +#include +#include +#include +#include + +using evmone::crypto::blake2b_compress; + +// Initialization Vector. +// https://datatracker.ietf.org/doc/html/rfc7693#appendix-C.2 +constexpr std::array blake2b_iv{ + 0x6a09e667f3bcc908, + 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, + 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, + 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, + 0x5be0cd19137e2179, +}; + +TEST(blake2b_compress, reference_test) +{ + // The reference test from the RFC. + // https://datatracker.ietf.org/doc/html/rfc7693#appendix-A + // with some extensions by modifying the "rounds" and "last" values. + + auto h_init = blake2b_iv; + h_init[0] ^= 0x01010000 ^ /*outlen = */ 64; + + const std::string_view data = "abc"; + uint64_t m[16]{}; + std::memcpy(m, data.data(), data.size()); + + const uint64_t t[2]{data.size(), 0}; + + auto h = h_init; + blake2b_compress(12, h.data(), m, t, true); + EXPECT_EQ(hex({reinterpret_cast(h.data()), sizeof(h)}), + "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1" + "7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"); + + // https://github.com/ethereum/tests/blob/v13.2/src/GeneralStateTestsFiller/stPreCompiledContracts/blake2BFiller.yml#L301-L302 + h = h_init; + blake2b_compress(12, h.data(), m, t, false); + EXPECT_EQ(hex({reinterpret_cast(h.data()), sizeof(h)}), + "75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d28752" + "98743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735"); + + // https://github.com/ethereum/tests/blob/v13.2/src/GeneralStateTestsFiller/stPreCompiledContracts/blake2BFiller.yml#L268-L269 + h = h_init; + blake2b_compress(0, h.data(), m, t, true); + EXPECT_EQ(hex({reinterpret_cast(h.data()), sizeof(h)}), + "08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5" + "d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b"); + + // this gives the same result because the xor zeros out the "last" flag + h = h_init; + blake2b_compress(0, h.data(), m, t, false); + EXPECT_EQ(hex({reinterpret_cast(h.data()), sizeof(h)}), + "08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5" + "d282e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b"); +} + +TEST(blake2b_compress, null_input) +{ + std::array h{}; + const uint64_t t[2]{}; + + // the data block is unused so be pass nullptr. + blake2b_compress(0, h.data(), nullptr, t, false); + + // For null input you get the IV as the result. + EXPECT_EQ(h, blake2b_iv); +} From d46e3b9fff5367b98c70b3a490bfdb37566f08d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Fri, 5 Apr 2024 14:18:33 +0200 Subject: [PATCH 2/3] precompiles: Add the BLAKE2bf precompile Implement [EIP-152](https://eips.ethereum.org/EIPS/eip-152) "BLAKE2 compression function `F` precompile" by exposing the `evmone::crypto::blake2b_compress()`. --- test/state/precompiles.cpp | 37 +++++++++++++++++++++++++++-- test/state/precompiles_internal.hpp | 2 ++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/test/state/precompiles.cpp b/test/state/precompiles.cpp index 3ad3548ed2..eb0cf1dc73 100644 --- a/test/state/precompiles.cpp +++ b/test/state/precompiles.cpp @@ -5,6 +5,7 @@ #include "precompiles.hpp" #include "precompiles_cache.hpp" #include "precompiles_internal.hpp" +#include #include #include #include @@ -251,6 +252,38 @@ ExecutionResult identity_execute(const uint8_t* input, size_t input_size, uint8_ return {EVMC_SUCCESS, input_size}; } +ExecutionResult blake2bf_execute(const uint8_t* input, [[maybe_unused]] size_t input_size, + uint8_t* output, [[maybe_unused]] size_t output_size) noexcept +{ + static_assert(std::endian::native == std::endian::little, + "blake2bf only works correctly on little-endian architectures"); + assert(input_size >= 213); + assert(output_size >= 64); + + const auto rounds = intx::be::unsafe::load(input); + input += sizeof(rounds); + + uint64_t h[8]; + std::memcpy(h, input, sizeof(h)); + input += sizeof(h); + + uint64_t m[16]; + std::memcpy(m, input, sizeof(m)); + input += sizeof(m); + + uint64_t t[2]; + std::memcpy(t, input, sizeof(t)); + input += sizeof(t); + + const auto f = *input; + if (f != 0 && f != 1) [[unlikely]] + return {EVMC_PRECOMPILE_FAILURE, 0}; + + crypto::blake2b_compress(rounds, h, m, t, f != 0); + std::memcpy(output, h, sizeof(h)); + return {EVMC_SUCCESS, sizeof(h)}; +} + namespace { struct PrecompileTraits @@ -277,7 +310,7 @@ inline constexpr auto traits = []() noexcept { {ecadd_analyze, ecadd_execute}, {ecmul_analyze, ecmul_execute}, {ecpairing_analyze, dummy_execute}, - {blake2bf_analyze, dummy_execute}, + {blake2bf_analyze, blake2bf_execute}, {point_evaluation_analyze, dummy_execute}, }}; #ifdef EVMONE_PRECOMPILES_SILKPRE @@ -288,7 +321,7 @@ inline constexpr auto traits = []() noexcept { // tbl[static_cast(PrecompileId::ecadd)].execute = silkpre_ecadd_execute; // tbl[static_cast(PrecompileId::ecmul)].execute = silkpre_ecmul_execute; tbl[static_cast(PrecompileId::ecpairing)].execute = silkpre_ecpairing_execute; - tbl[static_cast(PrecompileId::blake2bf)].execute = silkpre_blake2bf_execute; + // tbl[static_cast(PrecompileId::blake2bf)].execute = silkpre_blake2bf_execute; #endif return tbl; }(); diff --git a/test/state/precompiles_internal.hpp b/test/state/precompiles_internal.hpp index bf450cfd01..990a5f7f72 100644 --- a/test/state/precompiles_internal.hpp +++ b/test/state/precompiles_internal.hpp @@ -40,4 +40,6 @@ ExecutionResult ecadd_execute( const uint8_t* input, size_t input_size, uint8_t* output, size_t output_size) noexcept; ExecutionResult ecmul_execute( const uint8_t* input, size_t input_size, uint8_t* output, size_t output_size) noexcept; +ExecutionResult blake2bf_execute( + const uint8_t* input, size_t input_size, uint8_t* output, size_t output_size) noexcept; } // namespace evmone::state From b2af04206abfed2743fc8a99f054b305548dcf43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Fri, 5 Apr 2024 14:19:09 +0200 Subject: [PATCH 3/3] precompiles: Do not cache the BLAKE2bf precompile --- circle.yml | 2 +- test/state/precompiles_cache.cpp | 1 + test/state/precompiles_stub.json | 29 +---------------------------- 3 files changed, 3 insertions(+), 29 deletions(-) diff --git a/circle.yml b/circle.yml index 07a1adf541..a0b2c72265 100644 --- a/circle.yml +++ b/circle.yml @@ -437,7 +437,7 @@ jobs: command: > EVMONE_PRECOMPILES_STUB=~/project/test/state/precompiles_stub.json bin/evmone-blockchaintest - --gtest_filter='*:-VMTests/vmPerformance.*:*.*Call50000_sha256' + --gtest_filter='*:-VMTests/vmPerformance.*:*.*Call50000_sha256:*.CALLBlake2f_MaxRounds' ~/tests/BlockchainTests/GeneralStateTests - run: name: "Blockchain tests (ValidBlocks)" diff --git a/test/state/precompiles_cache.cpp b/test/state/precompiles_cache.cpp index eedee4c9a4..424e08241e 100644 --- a/test/state/precompiles_cache.cpp +++ b/test/state/precompiles_cache.cpp @@ -36,6 +36,7 @@ void Cache::insert(PrecompileId id, bytes_view input, const evmc::Result& result { case PrecompileId::ripemd160: case PrecompileId::identity: + case PrecompileId::blake2bf: return; // Do not cache. default: const auto input_hash = keccak256(input); diff --git a/test/state/precompiles_stub.json b/test/state/precompiles_stub.json index e68b15e182..29f4f60807 100644 --- a/test/state/precompiles_stub.json +++ b/test/state/precompiles_stub.json @@ -664,34 +664,7 @@ "da9978f0bc086d41a18d2b99f16c51583c2a38ee4c9346aa834dc50e66b4964d": null, "e45e2aa1bb354e722458c3425e59c37bb6a544397e5b4e8a5814ec97283dbfc7": null }, - { - "0537dab8bcfa337395fdfbd203d054ac25c08722e9416a0ac259b779a2c87721": "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923", - "05bd989b8f157e23dbb02b8bc54d344a60558de44a7ee3f804cf1dfc9e82bbda": "fc59093aafa9ab43daae0e914c57635c5402d8e3d2130eb9b3cc181de7f0ecf9b22bf99a7815ce16419e200e01846e6b5df8cc7703041bbceb571de6631d2615", - "13d12ef157bef72bdae72d1981ac6bc16ab74176742f7fb689ecef08d3d6bf0d": "d82c6a670dc90af9d7f77644eacbeddfed91b760c65c927871784abceaab3f813759733a1736254fb1cfc515dbfee467930955af56e27ee435f836fc3e65969f", - "2699f65d0bde17d17b834840e823616c33fb78e1a63f31fb6613bd1b08b888b4": "68790ca7594dd6fc28f0a86b7ddce0a225a8ea8fc2637f910eb71f6e54d9f8fa3e6302691015f11b15b755076d316823e6ce2ee4dd4aef60efc9189f6bd21bfd", - "30e40ece3441a7de9a9451e74fab98b455c4823f2485b2373a43a8b6c928a9fb": null, - "44b96fb380480dfeed4c297b46250c1ecc1a09e3e2fc2db0c1fb62fbe396eae4": "75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735", - "47a0bef21f27e645f4fcee8531e8ee64c91472c3c49f6dc12ba19eca9ab49e05": "cf087b9b6ae026d358a13c577a04a4af4e65293be70b7cb2a7e00bd1317440af94b896bca7c282001aeb86cc307973096917897eadc97ed8eefb0d2b3c309a8b", - "5440bab497f09b1558b910e1f0df278ae9ac9fedc88daa3648115e004cddc8ce": "59c7c9896cdc9fda0a77bd41adba14bdd6cb47cbbd7c338482f5382d7be16ac44a2ddfe6833bf9a15737dd66469d2d0495d39a9cb3c8ed93152684d92a74f8bc", - "72834c36258c46436fcd43d43893fb32103ebd8827774c031a2e64decbe71d6a": "a2c1eb780a6e1249156fe0751e5d4687ea9357b0651c78df660ab004cb4773636298bbbc683e4a0261574b6d857a6a99e06b2eea50b16f86343d2625ff222b98", - "7b12d31724098d4879b481a4d462ebc685bda0f24252af010a62ccc6f4ffc60c": "bc5e888ed71b546da7b1506179bdd6c184a6410c40de33f9c3302074177978895dbe74144468aefe5c2afce693c62dbca99e5e076dd467fe90a41278b16d691e", - "7ded868868aebb7359c79ef488bed027b61633040eb7da66493e546245a4a662": "38dfb111db6c59c39c9bea26c8c872620d89dec22fd7da93c47d0708a3973f522858fd9c60fae53f366d7e2820040a8662b336de6d859764f20747acbb8999fe", - "87d33c8eabd067a14a94f02cb8ccfdb7ecff66aedbc0f0b13ae4ab0e06a5c4c7": "689419d2bf32b5a9901a2c733b9946727026a60d8773117eabb35f04a52cdcf1b8fb4473454cf03d46c36a10b3f784aae4dc80a24424960e66a8ad5a8c2bfb30", - "8dea8fe46ba1734457ae75ffdbb4ef76cc7dfccbb059a8eed9612f2369c2ea8f": "b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421", - "8ff18398e840c4041ffc0bcafac552cfb6c3440c67ce4075f3d370e227d14476": "7476f1f8e159b30b156b0e9fffbaee5badbb45abb488ea9cfa04f60d3a0964087535a649e438ba993f03cfc0d8d8676a792030a996b6a6fde5c29108b6bfb871", - "93e382390cb513261e11e03f772356de95ce13c4ff3d1d91e8db0bf8e10f516c": "afd469613835ad3c75a54cd3087160eb308f5d6cd2151f1490f51b182dc5d13016428bf21e474e2921023bbeec971429210a51f0b63741583b0153fe8f6c27b6", - "98e4e90f2de200ae941b184f513fe7cb2a07db782ef99db0552da0a88e6c08ce": "94e963dcaa85d33dad6c0043b6700f5e227a2d8bed804bd16970e64fa6f1e16307399239bcddf968612c8c9ba953d9b173575c31ef269c3a8721cb9bf1c02012", - "ac284f457255542cfce05626f14734707a56698403c663e598a9305ce509b665": "74097ae7b16ffd18c742aee5c55dc89d54b6f1a8a19e6139ccfb38afba56b6b02cc35c441c19c21194fefb6841e72202f7c9d05eb9c3cfd8f94c67aa77d473c1", - "af40afaebdf61faac67fca7412c81973e9b053a85195ebc80a6f10c6a4b5d233": "5d6ff04d5ebaee5687d634613ab21e9a7d36f782033c74f91d562669aaf9d592c86346cb2df390243a952834306b389e656876a67934e2c023bce4918a016d4e", - "b7c8e0e46ba3b6a9b345fd795b9e328c51f3eb535bc9d775517a965af6b72521": "f3e89a60ec4b0b1854744984e421d22b82f181bd4601fb9b1726b2662da61c29dff09e75814acb2639fd79e56616e55fc135f8476f0302b3dc8d44e082eb83a8", - "bed4c5ce99729a220c87ca0ef21acbbe1274b29855df4cccba2510fd58ae1353": "a65aadbf393aad57c4b06d6471134c5c01002c23dfe8290e115e024e05bc1bf1084d1651de54a83902ed582cb8f2ba381c69687cceaecea0cd8fe5529f86686e", - "de7f79eb16d7357b75271fd4aa93af1237cbdd2297894cdec0033c1efb33ab2a": "bc112be5618b20d24be64c9e1c6efd63fea38cc79d53692fad6568b16e953eb6128c1ec8ffaf9a2d69e3cb043d6e11e1c7afd48573311052b6e7ec0960371186", - "ded8b44cbafe26629d64d2019a702d6eea0d6bfe68e313beacfa7935a9433640": "7df6f69476a03ae29e944814846460b058d1762fffe77f938ea723d1033de0d5bb1f8234bd73afaf955622fa2cdde95594577a8d53191908eb69b316a53c985b", - "eea1b2d664c19b902ea74b63cc5166346e9ce4de1de41dd5b72dbc75709871a1": "08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b", - "f352cd3242546c7cb1ee7c4dbf7c4cba948d505150b17fae38bff05efb7018fb": "a8ef8236e5f48a74af375df15681d128457891c1cc4706f30747b2d40300b2f49d19f80fbd0945fd87736e1fc1ff10a80fd85a7aa5125154f3aaa3789ddff673", - "f6f043c5d533a3310a6ccbc5ec11199f7038a750a570ff5be9e715eb3614c0b1": "6d2ce9e534d50e18ff866ae92d70cceba79bbcd14c63819fe48752c8aca87a4bb7dcc230d22a4047f0486cfcfb50a17b24b2899eb8fca370f22240adb5170189", - "fb59d1e38c2473ebc92cd9d9bded35e13a214ab589f11fb07ffc0703784eb33d": "08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b" - }, + null, { "00921f4c0512d38d3e47f51c61fe0462d685d7165b7a330a79e072daacf56d3b": null, "0431ab5e28f24e5ee4c929cb3c1385d8f13c1318b58d2301854c6f326fecbcbe": null,